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