1 /*
2  * Copyright 2023 Cypress Semiconductor Corporation (an Infineon company) or
3  * an affiliate of Cypress Semiconductor Corporation
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT infineon_cat1_spi
9 
10 #define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(cat1_spi);
13 
14 #include "spi_context.h"
15 
16 #include <zephyr/drivers/pinctrl.h>
17 #include <zephyr/drivers/spi.h>
18 #include <zephyr/drivers/spi/rtio.h>
19 #include <zephyr/kernel.h>
20 
21 #include <cyhal_scb_common.h>
22 #include <cyhal_spi.h>
23 
24 #define IFX_CAT1_SPI_LOCK_TMOUT_MS      (30 * 1000)
25 #define IFX_CAT1_SPI_DEFAULT_OVERSAMPLE (4)
26 #define IFX_CAT1_SPI_MIN_DATA_WIDTH     (8)
27 #define IFX_CAT1_SPI_MAX_DATA_WIDTH     (32)
28 
29 /* Device config structure */
30 struct ifx_cat1_spi_config {
31 	CySCB_Type *reg_addr;
32 	const struct pinctrl_dev_config *pcfg;
33 	cy_stc_scb_spi_config_t scb_spi_config;
34 	uint8_t irq_priority;
35 };
36 
37 /* Data structure */
38 struct ifx_cat1_spi_data {
39 	struct spi_context ctx;
40 	cyhal_spi_t obj; /* SPI CYHAL object */
41 	cyhal_resource_inst_t hw_resource;
42 	uint8_t dfs_value;
43 	size_t chunk_len;
44 };
45 
get_hw_block_num(CySCB_Type * reg_addr)46 static int32_t get_hw_block_num(CySCB_Type *reg_addr)
47 {
48 	extern const uint8_t _CYHAL_SCB_BASE_ADDRESS_INDEX[_SCB_ARRAY_SIZE];
49 	extern CySCB_Type *const _CYHAL_SCB_BASE_ADDRESSES[_SCB_ARRAY_SIZE];
50 
51 	uint32_t i;
52 
53 	for (i = 0u; i < _SCB_ARRAY_SIZE; i++) {
54 		if (_CYHAL_SCB_BASE_ADDRESSES[i] == reg_addr) {
55 			return _CYHAL_SCB_BASE_ADDRESS_INDEX[i];
56 		}
57 	}
58 
59 	return -ENOMEM;
60 }
61 
get_dfs_value(struct spi_context * ctx)62 static uint8_t get_dfs_value(struct spi_context *ctx)
63 {
64 	switch (SPI_WORD_SIZE_GET(ctx->config->operation)) {
65 	case 8:
66 		return 1;
67 	case 16:
68 		return 2;
69 	case 32:
70 		return 4;
71 	default:
72 		return 1;
73 	}
74 }
75 
transfer_chunk(const struct device * dev)76 static void transfer_chunk(const struct device *dev)
77 {
78 	struct ifx_cat1_spi_data *const data = dev->data;
79 	struct spi_context *ctx = &data->ctx;
80 	int ret = 0;
81 	size_t chunk_len = spi_context_max_continuous_chunk(ctx);
82 
83 	if (chunk_len == 0) {
84 		goto exit;
85 	}
86 
87 	data->chunk_len = chunk_len;
88 
89 	cy_rslt_t result = cyhal_spi_transfer_async(
90 		&data->obj, ctx->tx_buf, spi_context_tx_buf_on(ctx) ? chunk_len : 0, ctx->rx_buf,
91 		spi_context_rx_buf_on(ctx) ? chunk_len : 0);
92 	if (result == CY_RSLT_SUCCESS) {
93 		return;
94 	}
95 
96 	ret = -EIO;
97 
98 exit:
99 	spi_context_cs_control(ctx, false);
100 	spi_context_complete(ctx, dev, ret);
101 }
102 
spi_interrupt_callback(void * arg,cyhal_spi_event_t event)103 static void spi_interrupt_callback(void *arg, cyhal_spi_event_t event)
104 {
105 	const struct device *dev = (const struct device *)arg;
106 	struct ifx_cat1_spi_data *const data = dev->data;
107 	struct spi_context *ctx = &data->ctx;
108 
109 	if (event & CYHAL_SPI_IRQ_ERROR) {
110 #if defined(CONFIG_SPI_ASYNC)
111 		cyhal_spi_abort_async(&data->obj);
112 #endif
113 		spi_context_cs_control(ctx, false);
114 		spi_context_complete(ctx, dev, -EIO);
115 	}
116 
117 	if (event & CYHAL_SPI_IRQ_DONE) {
118 		spi_context_update_tx(ctx, data->dfs_value, data->chunk_len);
119 		spi_context_update_rx(ctx, data->dfs_value, data->chunk_len);
120 
121 		transfer_chunk(dev);
122 	}
123 }
124 
spi_config(const struct device * dev,const struct spi_config * spi_cfg)125 int spi_config(const struct device *dev, const struct spi_config *spi_cfg)
126 {
127 	cy_rslt_t result;
128 	struct ifx_cat1_spi_data *const data = dev->data;
129 	const struct ifx_cat1_spi_config *const config = dev->config;
130 	cy_stc_scb_spi_config_t scb_spi_config = config->scb_spi_config;
131 	struct spi_context *ctx = &data->ctx;
132 	bool spi_mode_cpol = false;
133 	bool spi_mode_cpha = false;
134 
135 	/* check if configuration was changed from previous run, if so skip setup again */
136 	if (spi_context_configured(ctx, spi_cfg)) {
137 		/* Already configured. No need to do it again. */
138 		return 0;
139 	}
140 
141 	if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) {
142 		return -ENOTSUP;
143 	}
144 
145 	if (SPI_WORD_SIZE_GET(spi_cfg->operation) > IFX_CAT1_SPI_MAX_DATA_WIDTH) {
146 		LOG_ERR("Word size %d is greater than %d", SPI_WORD_SIZE_GET(spi_cfg->operation),
147 			IFX_CAT1_SPI_MAX_DATA_WIDTH);
148 		return -EINVAL;
149 	}
150 
151 	if (SPI_WORD_SIZE_GET(spi_cfg->operation) < IFX_CAT1_SPI_MIN_DATA_WIDTH) {
152 		LOG_ERR("Word size %d is less than %d", SPI_WORD_SIZE_GET(spi_cfg->operation),
153 			IFX_CAT1_SPI_MIN_DATA_WIDTH);
154 		return -EINVAL;
155 	}
156 
157 	if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_SLAVE) {
158 		scb_spi_config.spiMode = CY_SCB_SPI_SLAVE;
159 		scb_spi_config.oversample = 0;
160 		scb_spi_config.enableMisoLateSample = false;
161 	} else {
162 		scb_spi_config.spiMode = CY_SCB_SPI_MASTER;
163 	}
164 
165 	if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) {
166 		spi_mode_cpol = true;
167 	}
168 
169 	if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) {
170 		spi_mode_cpha = true;
171 	}
172 
173 	if (SPI_WORD_SIZE_GET(spi_cfg->operation)) {
174 		scb_spi_config.txDataWidth = SPI_WORD_SIZE_GET(spi_cfg->operation);
175 		scb_spi_config.rxDataWidth = SPI_WORD_SIZE_GET(spi_cfg->operation);
176 	}
177 
178 	if (spi_mode_cpha) {
179 		scb_spi_config.sclkMode =
180 			spi_mode_cpol ? CY_SCB_SPI_CPHA1_CPOL1 : CY_SCB_SPI_CPHA1_CPOL0;
181 	} else {
182 		scb_spi_config.sclkMode =
183 			spi_mode_cpol ? CY_SCB_SPI_CPHA0_CPOL1 : CY_SCB_SPI_CPHA0_CPOL0;
184 	}
185 
186 	scb_spi_config.enableMsbFirst = (spi_cfg->operation & SPI_TRANSFER_LSB) ? false : true;
187 
188 	/* Force free resource */
189 	if (data->obj.base != NULL) {
190 		cyhal_spi_free(&data->obj);
191 	}
192 
193 	/* Initialize the SPI peripheral */
194 	cyhal_spi_configurator_t spi_init_cfg = {.resource = &data->hw_resource,
195 						 .config = &scb_spi_config,
196 						 .gpios = {NC, {NC, NC, NC, NC}, NC, NC}};
197 
198 	result = cyhal_spi_init_cfg(&data->obj, &spi_init_cfg);
199 	if (result != CY_RSLT_SUCCESS) {
200 		return -ENOTSUP;
201 	}
202 
203 	/* Assigns a programmable divider to a selected IP block */
204 	en_clk_dst_t clk_idx = _cyhal_scb_get_clock_index(spi_init_cfg.resource->block_num);
205 
206 	result = _cyhal_utils_peri_pclk_assign_divider(clk_idx, &data->obj.clock);
207 	if (result != CY_RSLT_SUCCESS) {
208 		return -ENOTSUP;
209 	}
210 
211 	/* Configure Slave select polarity */
212 	if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_SLAVE) {
213 		Cy_SCB_SPI_SetActiveSlaveSelectPolarity(data->obj.base, CY_SCB_SPI_SLAVE_SELECT0,
214 							scb_spi_config.ssPolarity);
215 	}
216 
217 	/* Set the data rate */
218 	result = cyhal_spi_set_frequency(&data->obj, spi_cfg->frequency);
219 	if (result != CY_RSLT_SUCCESS) {
220 		return -EIO;
221 	}
222 
223 	/* Write 0 when NULL buffer is provided for Tx/Rx */
224 	data->obj.write_fill = 0;
225 
226 	/* Register common SPI callback */
227 	cyhal_spi_register_callback(&data->obj, spi_interrupt_callback, (void *)dev);
228 	cyhal_spi_enable_event(&data->obj, CYHAL_SPI_IRQ_DONE, config->irq_priority, true);
229 
230 	/* Store spi config in context */
231 	ctx->config = spi_cfg;
232 
233 	data->dfs_value = get_dfs_value(ctx);
234 
235 	return 0;
236 }
237 
transceive(const struct device * dev,const struct spi_config * spi_cfg,const struct spi_buf_set * tx_bufs,const struct spi_buf_set * rx_bufs,bool asynchronous,spi_callback_t cb,void * userdata)238 static int transceive(const struct device *dev, const struct spi_config *spi_cfg,
239 		      const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs,
240 		      bool asynchronous, spi_callback_t cb, void *userdata)
241 {
242 	int result;
243 	struct ifx_cat1_spi_data *const data = dev->data;
244 	struct spi_context *ctx = &data->ctx;
245 
246 	spi_context_lock(ctx, asynchronous, cb, userdata, spi_cfg);
247 
248 	result = spi_config(dev, spi_cfg);
249 	if (result) {
250 		LOG_ERR("Error in SPI Configuration (result: 0x%x)", result);
251 		return result;
252 	}
253 
254 	spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs_value);
255 
256 	spi_context_cs_control(ctx, true);
257 
258 	transfer_chunk(dev);
259 
260 	result = spi_context_wait_for_completion(&data->ctx);
261 
262 	spi_context_release(ctx, result);
263 
264 	return result;
265 }
266 
ifx_cat1_spi_transceive_sync(const struct device * dev,const struct spi_config * spi_cfg,const struct spi_buf_set * tx_bufs,const struct spi_buf_set * rx_bufs)267 static int ifx_cat1_spi_transceive_sync(const struct device *dev, const struct spi_config *spi_cfg,
268 					const struct spi_buf_set *tx_bufs,
269 					const struct spi_buf_set *rx_bufs)
270 {
271 	return transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL);
272 }
273 
274 #if defined(CONFIG_SPI_ASYNC)
ifx_cat1_spi_transceive_async(const struct device * dev,const struct spi_config * spi_cfg,const struct spi_buf_set * tx_bufs,const struct spi_buf_set * rx_bufs,spi_callback_t cb,void * userdata)275 static int ifx_cat1_spi_transceive_async(const struct device *dev, const struct spi_config *spi_cfg,
276 					 const struct spi_buf_set *tx_bufs,
277 					 const struct spi_buf_set *rx_bufs, spi_callback_t cb,
278 					 void *userdata)
279 {
280 	return transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata);
281 }
282 #endif
283 
ifx_cat1_spi_release(const struct device * dev,const struct spi_config * spi_cfg)284 static int ifx_cat1_spi_release(const struct device *dev, const struct spi_config *spi_cfg)
285 {
286 	struct ifx_cat1_spi_data *const data = dev->data;
287 
288 	cyhal_spi_free(&data->obj);
289 
290 	return 0;
291 }
292 
293 static DEVICE_API(spi, ifx_cat1_spi_api) = {
294 	.transceive = ifx_cat1_spi_transceive_sync,
295 #if defined(CONFIG_SPI_ASYNC)
296 	.transceive_async = ifx_cat1_spi_transceive_async,
297 #endif
298 #ifdef CONFIG_SPI_RTIO
299 	.iodev_submit = spi_rtio_iodev_default_submit,
300 #endif
301 	.release = ifx_cat1_spi_release,
302 };
303 
ifx_cat1_spi_init(const struct device * dev)304 static int ifx_cat1_spi_init(const struct device *dev)
305 {
306 	struct ifx_cat1_spi_data *const data = dev->data;
307 	const struct ifx_cat1_spi_config *const config = dev->config;
308 	int ret;
309 
310 	/* Dedicate SCB HW resource */
311 	data->hw_resource.type = CYHAL_RSC_SCB;
312 	data->hw_resource.block_num = get_hw_block_num(config->reg_addr);
313 
314 	/* Configure dt provided device signals when available */
315 	ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
316 	if (ret < 0) {
317 		return ret;
318 	}
319 
320 	/* Configure slave select (master) */
321 	spi_context_cs_configure_all(&data->ctx);
322 
323 	spi_context_unlock_unconditionally(&data->ctx);
324 
325 	return 0;
326 }
327 
328 #define IFX_CAT1_SPI_INIT(n)                                                                       \
329 	PINCTRL_DT_INST_DEFINE(n);                                                                 \
330 	static struct ifx_cat1_spi_data spi_cat1_data_##n = {                                      \
331 		SPI_CONTEXT_INIT_LOCK(spi_cat1_data_##n, ctx),                                     \
332 		SPI_CONTEXT_INIT_SYNC(spi_cat1_data_##n, ctx),                                     \
333 		SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)};                             \
334 	static struct ifx_cat1_spi_config spi_cat1_config_##n = {                                  \
335 		.reg_addr = (CySCB_Type *)DT_INST_REG_ADDR(n),                                     \
336 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                         \
337 		.scb_spi_config =                                                                  \
338 			{                                                                          \
339 				.spiMode = CY_SCB_SPI_MASTER,       /* overwrite by cfg  */        \
340 				.sclkMode = CY_SCB_SPI_CPHA0_CPOL0, /* overwrite by cfg  */        \
341 				.rxDataWidth = 8,                   /* overwrite by cfg  */        \
342 				.txDataWidth = 8,                   /* overwrite by cfg  */        \
343 				.enableMsbFirst = true,             /* overwrite by cfg  */        \
344 				.subMode = CY_SCB_SPI_MOTOROLA,                                    \
345 				.oversample = IFX_CAT1_SPI_DEFAULT_OVERSAMPLE,                     \
346 				.enableMisoLateSample = true,                                      \
347 				.ssPolarity = CY_SCB_SPI_ACTIVE_LOW,                               \
348 			},                                                                         \
349 		.irq_priority = DT_INST_IRQ(n, priority),                                          \
350 	};                                                                                         \
351 	SPI_DEVICE_DT_INST_DEFINE(n, ifx_cat1_spi_init, NULL, &spi_cat1_data_##n,                  \
352 				  &spi_cat1_config_##n, POST_KERNEL,                               \
353 				  CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &ifx_cat1_spi_api);
354 
355 DT_INST_FOREACH_STATUS_OKAY(IFX_CAT1_SPI_INIT)
356