1 /*
2  * Copyright (c) 2024 Stephen Boylan (stephen.boylan@beechwoods.com)
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <airoc_wifi.h>
8 #include "airoc_whd_hal_common.h"
9 
10 #include <bus_protocols/whd_bus_spi_protocol.h>
11 #include <bus_protocols/whd_spi.h>
12 #include <whd_wifi_api.h>
13 #include <zephyr/drivers/spi.h>
14 
15 LOG_MODULE_DECLARE(infineon_airoc_wifi, CONFIG_WIFI_LOG_LEVEL);
16 
17 #ifdef __cplusplus
18 extern "C" {
19 #endif
20 
21 struct whd_bus_priv {
22 	whd_spi_config_t spi_config;
23 	whd_spi_t spi_obj;
24 };
25 
26 static whd_init_config_t init_config_default = {.thread_stack_size = CY_WIFI_THREAD_STACK_SIZE,
27 						.thread_stack_start = NULL,
28 						.thread_priority =
29 							(uint32_t)CY_WIFI_THREAD_PRIORITY,
30 						.country = CY_WIFI_COUNTRY};
31 
32 /******************************************************
33  *		Function
34  ******************************************************/
35 
airoc_wifi_init_primary(const struct device * dev,whd_interface_t * interface,whd_netif_funcs_t * netif_funcs,whd_buffer_funcs_t * buffer_if)36 int airoc_wifi_init_primary(const struct device *dev, whd_interface_t *interface,
37 				whd_netif_funcs_t *netif_funcs,
38 				whd_buffer_funcs_t *buffer_if)
39 {
40 	struct airoc_wifi_data *data = dev->data;
41 	const struct airoc_wifi_config *config = dev->config;
42 
43 	whd_spi_config_t whd_spi_config = {
44 		.is_spi_normal_mode = WHD_FALSE,
45 	};
46 
47 #if DT_INST_NODE_HAS_PROP(0, wifi_host_wake_gpios)
48 	whd_oob_config_t oob_config = {
49 		.host_oob_pin = (void *)&config->wifi_host_wake_gpio,
50 		.dev_gpio_sel = DEFAULT_OOB_PIN,
51 		.is_falling_edge =
52 			(CY_WIFI_HOST_WAKE_IRQ_EVENT == GPIO_INT_TRIG_LOW) ? WHD_TRUE : WHD_FALSE,
53 		.intr_priority = CY_WIFI_OOB_INTR_PRIORITY};
54 	whd_spi_config.oob_config = oob_config;
55 #endif
56 #if defined(SPI_DATA_IRQ_SHARED)
57 	data->prev_irq_state = 0;
58 #endif
59 
60 	/* Pull bus select line low before enabling WiFi chip */
61 	gpio_pin_configure_dt(&config->bus_select_gpio, GPIO_OUTPUT_INACTIVE);
62 
63 	if (airoc_wifi_power_on(dev)) {
64 		LOG_ERR("airoc_wifi_power_on returns fail");
65 		return -ENODEV;
66 	}
67 
68 	if (!spi_is_ready_dt(&config->bus_dev.bus_spi)) {
69 		LOG_ERR("SPI device is not ready");
70 		return -ENODEV;
71 	}
72 
73 	/* Init wifi host driver (whd) */
74 	cy_rslt_t whd_ret = whd_init(&data->whd_drv, &init_config_default,
75 					&resource_ops, buffer_if, netif_funcs);
76 	if (whd_ret == CY_RSLT_SUCCESS) {
77 		whd_ret = whd_bus_spi_attach(data->whd_drv, &whd_spi_config,
78 					    (whd_spi_t)&config->bus_dev.bus_spi);
79 
80 		if (whd_ret == CY_RSLT_SUCCESS) {
81 			whd_ret = whd_wifi_on(data->whd_drv, interface);
82 		}
83 
84 		if (whd_ret != CY_RSLT_SUCCESS) {
85 			whd_deinit(*interface);
86 			return -ENODEV;
87 		}
88 	}
89 	return 0;
90 }
91 
92 /*
93  * Implement SPI Transfer wrapper
94  */
95 
whd_bus_spi_transfer(whd_driver_t whd_driver,const uint8_t * tx,size_t tx_length,uint8_t * rx,size_t rx_length,uint8_t write_fill)96 whd_result_t whd_bus_spi_transfer(whd_driver_t whd_driver, const uint8_t *tx, size_t tx_length,
97 				  uint8_t *rx, size_t rx_length, uint8_t write_fill)
98 {
99 	const struct spi_dt_spec *spi_obj = whd_driver->bus_priv->spi_obj;
100 	int ret;
101 	whd_result_t whd_ret = WHD_SUCCESS;
102 
103 #if defined(SPI_DATA_IRQ_SHARED)
104 	ret = whd_bus_spi_irq_enable(whd_driver, false);
105 	if (ret) {
106 		LOG_ERR("whd_bus_spi_irq_enable FAIL %d\n", ret);
107 		whd_ret = WHD_WLAN_SDIO_ERROR;
108 	}
109 #endif
110 
111 	/* In some cases whd_bus_spi_protocol.c places the command at    */
112 	/* the start of the rx buffer and passes NULL as the tx address, */
113 	/* reusing the same buffer (and overwriting the tx data).        */
114 	if (tx == NULL && tx_length > 0 && rx_length >= tx_length) {
115 		tx = rx;
116 	}
117 
118 	const struct spi_buf tx_buf = {.buf = (uint8_t *)tx, .len = tx_length};
119 	const struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1};
120 	struct spi_buf rx_buf[2];
121 	struct spi_buf_set rx_set = {.buffers = rx_buf, .count = 2};
122 
123 	if (rx != NULL) {
124 		rx += tx_length;
125 	}
126 
127 	if (rx_length >= tx_length) {
128 		rx_length -= tx_length;
129 	} else {
130 		rx_length = 0;
131 	}
132 
133 	if (spi_obj->config.operation & SPI_HALF_DUPLEX) {
134 		rx_buf[0].buf = rx;
135 		rx_buf[0].len = rx_length;
136 		rx_set.count = 1;
137 	} else {
138 		/* Talking to a half-duplex device via a full-duplex SPI driver,  */
139 		/* it's necessary to ignore the data returned during send.  The   */
140 		/* SPI driver should not copy out the data if the buffer is NULL. */
141 		rx_buf[0].buf = NULL;
142 		rx_buf[0].len = tx_length;
143 		rx_buf[1].buf = rx;
144 		rx_buf[1].len = rx_length;
145 	}
146 
147 	ret = spi_transceive_dt(spi_obj, &tx_set, &rx_set);
148 	if (ret) {
149 		LOG_ERR("spi_transceive FAIL %d\n", ret);
150 		whd_ret = WHD_WLAN_SDIO_ERROR;
151 	}
152 
153 #if defined(SPI_DATA_IRQ_SHARED)
154 	ret = whd_bus_spi_irq_enable(whd_driver, true);
155 	if (ret) {
156 		LOG_ERR("whd_bus_spi_irq_enable FAIL %d\n", ret);
157 		whd_ret = WHD_WLAN_SDIO_ERROR;
158 	}
159 #endif
160 
161 	return whd_ret;
162 }
163 
164 /*
165  * Implement OOB functionality
166  */
167 
whd_bus_spi_oob_irq_handler(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)168 void whd_bus_spi_oob_irq_handler(const struct device *port, struct gpio_callback *cb,
169 				 gpio_port_pins_t pins)
170 {
171 #if DT_INST_NODE_HAS_PROP(0, wifi_host_wake_gpios)
172 	struct airoc_wifi_data *data = CONTAINER_OF(cb, struct airoc_wifi_data, host_oob_pin_cb);
173 
174 	/* Get OOB pin info */
175 	const whd_oob_config_t *oob_config = &data->whd_drv->bus_priv->spi_config.oob_config;
176 	const struct gpio_dt_spec *host_oob_pin = oob_config->host_oob_pin;
177 
178 	/* Check OOB state is correct */
179 	int expected_event = (oob_config->is_falling_edge == WHD_TRUE) ? 0 : 1;
180 
181 	if (!(pins & BIT(host_oob_pin->pin)) || (gpio_pin_get_dt(host_oob_pin) != expected_event)) {
182 		WPRINT_WHD_ERROR(("Unexpected interrupt event %d\n", expected_event));
183 		return;
184 	}
185 
186 	/* Call thread notify to wake up WHD thread */
187 	whd_thread_notify_irq(data->whd_drv);
188 
189 #endif /* DT_INST_NODE_HAS_PROP(0, wifi-host-wake-gpios) */
190 }
191 
whd_bus_spi_irq_register(whd_driver_t whd_driver)192 whd_result_t whd_bus_spi_irq_register(whd_driver_t whd_driver)
193 {
194 #if DT_INST_NODE_HAS_PROP(0, wifi_host_wake_gpios)
195 	int ret;
196 	const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(0));
197 	struct airoc_wifi_data *data = dev->data;
198 
199 	/* Get OOB pin info */
200 	const whd_oob_config_t *oob_config = &whd_driver->bus_priv->spi_config.oob_config;
201 	const struct gpio_dt_spec *host_oob_pin = oob_config->host_oob_pin;
202 
203 	/* Check if OOB pin is ready */
204 	if (!gpio_is_ready_dt(host_oob_pin)) {
205 		WPRINT_WHD_ERROR(("%s: Failed at gpio_is_ready_dt for host_oob_pin\n", __func__));
206 		return WHD_HAL_ERROR;
207 	}
208 
209 	/* Configure OOB pin as input */
210 	ret = gpio_pin_configure_dt(host_oob_pin, GPIO_INPUT);
211 	if (ret != 0) {
212 		WPRINT_WHD_ERROR((
213 			" %s: Failed at gpio_pin_configure_dt for host_oob_pin, result code = %d\n",
214 			__func__, ret));
215 		return WHD_HAL_ERROR;
216 	}
217 
218 	/* Initialize/add OOB pin callback */
219 	gpio_init_callback(&data->host_oob_pin_cb, whd_bus_spi_oob_irq_handler,
220 			   BIT(host_oob_pin->pin));
221 
222 	ret = gpio_add_callback_dt(host_oob_pin, &data->host_oob_pin_cb);
223 	if (ret != 0) {
224 		WPRINT_WHD_ERROR(
225 			("%s: Failed at gpio_add_callback_dt for host_oob_pin, result code = %d\n",
226 			 __func__, ret));
227 		return WHD_HAL_ERROR;
228 	}
229 #endif /* DT_INST_NODE_HAS_PROP(0, wifi_host_wake_gpios) */
230 
231 	return WHD_SUCCESS;
232 }
233 
whd_bus_spi_irq_enable(whd_driver_t whd_driver,whd_bool_t enable)234 whd_result_t whd_bus_spi_irq_enable(whd_driver_t whd_driver, whd_bool_t enable)
235 {
236 #if DT_INST_NODE_HAS_PROP(0, wifi_host_wake_gpios)
237 	int ret;
238 	const whd_oob_config_t *oob_config = &whd_driver->bus_priv->spi_config.oob_config;
239 	struct gpio_dt_spec *gpio = (struct gpio_dt_spec *)oob_config->host_oob_pin;
240 
241 #if defined(SPI_DATA_IRQ_SHARED)
242 	const struct device *dev = DEVICE_DT_INST_GET(0);
243 	const struct airoc_wifi_config *config = dev->config;
244 	struct airoc_wifi_data *data = dev->data;
245 
246 	if (enable) {
247 		ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_HOST_WAKE);
248 		__ASSERT(ret == 0, "could not apply pinconfig, return code %d", ret);
249 		ret = gpio_pin_interrupt_configure_dt(
250 			gpio, (oob_config->is_falling_edge == WHD_TRUE) ? GPIO_INT_EDGE_FALLING
251 									: GPIO_INT_EDGE_RISING);
252 		__ASSERT(ret == 0, "gpio_pin_interrupt_configure_dt failed, return code %d", ret);
253 
254 		int state = gpio_pin_get_dt(gpio);
255 		int expected_event = (oob_config->is_falling_edge == WHD_TRUE) ? 0 : 1;
256 
257 		/* Notify only if interrupt wasn't assert before. This code assumes that if */
258 		/* the interrupt was previously asserted, then the thread has already been  */
259 		/* notified.                                                                */
260 		if (state == expected_event && state != data->prev_irq_state) {
261 			whd_thread_notify_irq(whd_driver);
262 		}
263 	} else {
264 		data->prev_irq_state = gpio_pin_get_dt(gpio);
265 		ret = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_DISABLE);
266 		__ASSERT(ret == 0, "gpio_pin_interrupt_configure_dt failed, return code %d", ret);
267 		pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
268 	}
269 #else
270 	if (enable) {
271 		ret = gpio_pin_interrupt_configure_dt(
272 			gpio, (oob_config->is_falling_edge == WHD_TRUE) ? GPIO_INT_EDGE_FALLING
273 									: GPIO_INT_EDGE_RISING);
274 		__ASSERT(ret == 0, "gpio_pin_interrupt_configure_dt failed, return code %d", ret);
275 	} else {
276 		ret = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_DISABLE);
277 		__ASSERT(ret == 0, "gpio_pin_interrupt_configure_dt failed, return code %d", ret);
278 	}
279 #endif /* defined(SPI_DATA_IRQ_SHARED) */
280 
281 #endif /* DT_INST_NODE_HAS_PROP(0, wifi_host_wake_gpios) */
282 
283 	return WHD_SUCCESS;
284 }
285 
286 #ifdef __cplusplus
287 } /* extern "C" */
288 #endif
289