source: rtems/bsps/arm/imx/spi/imx-ecspi.c @ 29a73d8

Last change on this file since 29a73d8 was 29a73d8, checked in by Christian Mauderer <christian.mauderer@…>, on 07/17/20 at 05:54:44

bsp/imx: Use GPIOs for SPI CS

The chip select lines of the iMX SPI module doesn't work well for a
generic API like the one RTEMS uses. The existing solution only worked
in some special cases and had odd bugs when trying transfers of
different sizes (like deselecting between each byte for lengths that are
not dividable by 4).

With this patch the same approach like on FreeBSD or Linux is used:
Treat the CS lines as GPIOs.

Update 3869

  • Property mode set to 100644
File size: 11.5 KB
Line 
1/*
2 * Copyright (c) 2017 embedded brains GmbH.  All rights reserved.
3 *
4 *  embedded brains GmbH
5 *  Dornierstr. 4
6 *  82178 Puchheim
7 *  Germany
8 *  <info@embedded-brains.de>
9 *
10 * The license and distribution terms for this file may be
11 * found in the file LICENSE in this distribution or at
12 * http://www.rtems.org/license/LICENSE.
13 */
14
15#include <bsp.h>
16#include <bsp/fdt.h>
17#include <bsp/imx-gpio.h>
18#include <libfdt.h>
19#include <arm/freescale/imx/imx_ccmvar.h>
20#include <arm/freescale/imx/imx_ecspireg.h>
21#include <dev/spi/spi.h>
22#include <rtems/irq-extension.h>
23#include <sys/param.h>
24#include <sys/endian.h>
25
26#define IMX_ECSPI_FIFO_SIZE 64
27#define IMX_ECSPI_MAX_CHIPSELECTS 4
28#define IMX_ECSPI_CS_NONE IMX_ECSPI_MAX_CHIPSELECTS
29
30typedef struct imx_ecspi_bus imx_ecspi_bus;
31
32struct imx_ecspi_bus {
33  spi_bus base;
34  volatile imx_ecspi *regs;
35  uint32_t conreg;
36  uint32_t speed_hz;
37  uint32_t mode;
38  uint8_t bits_per_word;
39  uint8_t cs;
40  uint32_t msg_todo;
41  const spi_ioc_transfer *msg;
42  uint32_t todo;
43  uint32_t in_transfer;
44  uint8_t *rx_buf;
45  const uint8_t *tx_buf;
46  void (*push)(imx_ecspi_bus *, volatile imx_ecspi *);
47  void (*pop)(imx_ecspi_bus *, volatile imx_ecspi *);
48  rtems_id task_id;
49  rtems_vector_number irq;
50  struct {
51    struct imx_gpio_pin pin;
52    bool valid;
53  } cspins[IMX_ECSPI_MAX_CHIPSELECTS];
54};
55
56static bool imx_ecspi_is_rx_fifo_not_empty(volatile imx_ecspi *regs)
57{
58  return (regs->statreg & IMX_ECSPI_RR) != 0;
59}
60
61static void imx_ecspi_reset(volatile imx_ecspi *regs)
62{
63  while (imx_ecspi_is_rx_fifo_not_empty(regs)) {
64    regs->rxdata;
65  }
66}
67
68static void imx_ecspi_done(imx_ecspi_bus *bus)
69{
70  rtems_event_transient_send(bus->task_id);
71}
72
73#define IMC_ECSPI_PUSH(type) \
74static void imx_ecspi_push_##type(imx_ecspi_bus *bus, volatile imx_ecspi *regs) \
75{ \
76  type val = 0; \
77  if (bus->tx_buf != NULL) { \
78    val = *(type *)bus->tx_buf; \
79    bus->tx_buf += sizeof(type); \
80  } \
81  bus->todo -= sizeof(type); \
82  regs->txdata = val; \
83}
84
85#define IMX_ECSPI_POP(type) \
86static void imx_ecspi_pop_##type(imx_ecspi_bus *bus, volatile imx_ecspi *regs) \
87{ \
88  uint32_t val = regs->rxdata; \
89  if (bus->rx_buf != NULL) { \
90    *(type *)bus->rx_buf = val; \
91    bus->rx_buf += sizeof(type); \
92  } \
93}
94
95IMC_ECSPI_PUSH(uint8_t)
96IMX_ECSPI_POP(uint8_t)
97IMC_ECSPI_PUSH(uint16_t)
98IMX_ECSPI_POP(uint16_t)
99IMC_ECSPI_PUSH(uint32_t)
100IMX_ECSPI_POP(uint32_t)
101
102static void imx_ecspi_push_uint32_t_swap(
103  imx_ecspi_bus *bus,
104  volatile imx_ecspi *regs
105)
106{
107  uint32_t val = 0;
108
109  if (bus->tx_buf != NULL) {
110    val = bswap32(*(uint32_t *)bus->tx_buf);
111    bus->tx_buf += sizeof(uint32_t);
112  }
113
114  bus->todo -= sizeof(uint32_t);
115  regs->txdata = val;
116}
117
118static void imx_ecspi_pop_uint32_t_swap(
119  imx_ecspi_bus *bus,
120  volatile imx_ecspi *regs
121)
122{
123  uint32_t val = regs->rxdata;
124
125  if (bus->rx_buf != NULL) {
126    *(uint32_t *)bus->rx_buf = bswap32(val);
127    bus->rx_buf += sizeof(uint32_t);
128  }
129}
130
131static void imx_ecspi_push(imx_ecspi_bus *bus, volatile imx_ecspi *regs)
132{
133  while (bus->todo > 0 && bus->in_transfer < IMX_ECSPI_FIFO_SIZE) {
134    (*bus->push)(bus, regs);
135    ++bus->in_transfer;
136  }
137}
138
139/* Call with IMX_ECSPI_CS_NONE for @a cs to set all to idle */
140static void
141imx_ecspi_set_chipsel(imx_ecspi_bus *bus, uint32_t cs)
142{
143  size_t i;
144
145  /* Currently this is fixed active low */
146  static const uint32_t idle = 1;
147  static const uint32_t select = 0;
148
149  for (i = 0; i < IMX_ECSPI_MAX_CHIPSELECTS; ++i) {
150    if (bus->cspins[i].valid) {
151      if (i != cs) {
152        imx_gpio_set_output(&bus->cspins[i].pin, idle);
153      } else {
154        imx_gpio_set_output(&bus->cspins[cs].pin, select);
155      }
156    }
157  }
158}
159
160static uint32_t imx_ecspi_conreg_divider(imx_ecspi_bus *bus, uint32_t speed_hz)
161{
162  uint32_t post;
163  uint32_t pre;
164  uint32_t clk_in;
165
166  clk_in = bus->base.max_speed_hz;
167
168  if (clk_in > speed_hz) {
169    post = fls((int) clk_in) - fls((int) speed_hz);
170
171    if (clk_in > (speed_hz << post)) {
172      ++post;
173    }
174
175    /* We have 2^4 == 16, use the pre-divider for this factor */
176    post = MAX(4, post) - 4;
177
178    if (post <= 0xf) {
179      pre = howmany(clk_in, speed_hz << post) - 1;
180    } else {
181      post = 0xf;
182      pre = 0xf;
183    }
184  } else {
185    post = 0;
186    pre = 0;
187  }
188
189  return IMX_ECSPI_CONREG_POST_DIVIDER(post)
190    | IMX_ECSPI_CONREG_PRE_DIVIDER(pre);
191}
192
193static void imx_ecspi_config(
194  imx_ecspi_bus *bus,
195  volatile imx_ecspi *regs,
196  uint32_t speed_hz,
197  uint8_t bits_per_word,
198  uint32_t mode,
199  uint8_t cs
200)
201{
202  uint32_t conreg;
203  uint32_t testreg;
204  uint32_t configreg;
205  uint32_t dmareg;
206  uint32_t cs_bit;
207
208  conreg = IMX_ECSPI_CONREG_CHANNEL_MODE(0xf)
209    | IMX_ECSPI_CONREG_SMC | IMX_ECSPI_CONREG_EN;
210  testreg = regs->testreg;
211  configreg = regs->configreg;
212  dmareg = regs->dmareg;
213  cs_bit = 1U << cs;
214
215  conreg |= imx_ecspi_conreg_divider(bus, speed_hz);
216  conreg |= IMX_ECSPI_CONREG_CHANNEL_SELECT(cs);
217
218  configreg |= IMX_ECSPI_CONFIGREG_SS_CTL(cs_bit);
219
220  if ((mode & SPI_CPHA) != 0) {
221    configreg |= IMX_ECSPI_CONFIGREG_SCLK_PHA(cs_bit);
222  } else {
223    configreg &= ~IMX_ECSPI_CONFIGREG_SCLK_PHA(cs_bit);
224  }
225
226  if ((mode & SPI_CPOL) != 0) {
227    configreg |= IMX_ECSPI_CONFIGREG_SCLK_POL(cs_bit);
228    configreg |= IMX_ECSPI_CONFIGREG_SCLK_CTL(cs_bit);
229  } else {
230    configreg &= ~IMX_ECSPI_CONFIGREG_SCLK_POL(cs_bit);
231    configreg &= ~IMX_ECSPI_CONFIGREG_SCLK_CTL(cs_bit);
232  }
233
234  if ((mode & SPI_CS_HIGH) != 0) {
235    configreg |= IMX_ECSPI_CONFIGREG_SS_POL(cs_bit);
236  } else {
237    configreg &= ~IMX_ECSPI_CONFIGREG_SS_POL(cs_bit);
238  }
239
240  if ((mode & SPI_LOOP) != 0) {
241    testreg |= IMX_ECSPI_TESTREG_LBC;
242  } else {
243    testreg &= ~IMX_ECSPI_TESTREG_LBC;
244  }
245
246  dmareg = IMX_ECSPI_DMAREG_TX_THRESHOLD_SET(dmareg, IMX_ECSPI_FIFO_SIZE/2);
247
248  regs->conreg = conreg;
249  regs->testreg = testreg;
250  regs->dmareg = dmareg;
251  regs->configreg = configreg;
252
253  bus->conreg = conreg;
254  bus->speed_hz = speed_hz;
255  bus->bits_per_word = bits_per_word;
256  bus->mode = mode;
257  bus->cs = cs;
258
259  /* FIXME: Clock change delay */
260}
261
262static void imx_ecspi_set_push_pop(
263  imx_ecspi_bus *bus,
264  uint32_t len,
265  uint8_t bits_per_word
266)
267{
268  uint32_t conreg;
269
270  conreg = bus->conreg;
271
272  if (len % 4 == 0 && len <= IMX_ECSPI_FIFO_SIZE) {
273    conreg |= IMX_ECSPI_CONREG_BURST_LENGTH((len * 8) - 1);
274
275    bus->push = imx_ecspi_push_uint32_t_swap;
276    bus->pop = imx_ecspi_pop_uint32_t_swap;
277  } else {
278    conreg |= IMX_ECSPI_CONREG_BURST_LENGTH(bits_per_word - 1);
279
280    if (bits_per_word <= 8) {
281      bus->push = imx_ecspi_push_uint8_t;
282      bus->pop = imx_ecspi_pop_uint8_t;
283    } else if (bits_per_word <= 16) {
284      bus->push = imx_ecspi_push_uint16_t;
285      bus->pop = imx_ecspi_pop_uint16_t;
286    } else {
287      bus->push = imx_ecspi_push_uint32_t;
288      bus->pop = imx_ecspi_pop_uint32_t;
289    }
290  }
291
292  bus->regs->conreg = conreg;
293}
294
295static void imx_ecspi_next_msg(imx_ecspi_bus *bus, volatile imx_ecspi *regs)
296{
297  if (bus->msg_todo > 0) {
298    const spi_ioc_transfer *msg;
299
300    msg = bus->msg;
301
302    if (
303      msg->speed_hz != bus->speed_hz
304        || msg->bits_per_word != bus->bits_per_word
305        || msg->mode != bus->mode
306        || msg->cs != bus->cs
307    ) {
308      imx_ecspi_config(
309        bus,
310        regs,
311        msg->speed_hz,
312        msg->bits_per_word,
313        msg->mode,
314        msg->cs
315      );
316    }
317    if ((msg->mode & SPI_NO_CS) != 0) {
318      imx_ecspi_set_chipsel(bus, IMX_ECSPI_CS_NONE);
319    } else {
320      imx_ecspi_set_chipsel(bus, msg->cs);
321    }
322
323    bus->todo = msg->len;
324    bus->rx_buf = msg->rx_buf;
325    bus->tx_buf = msg->tx_buf;
326    imx_ecspi_set_push_pop(bus, msg->len, msg->bits_per_word);
327    imx_ecspi_push(bus, regs);
328    regs->intreg = IMX_ECSPI_TE | IMX_ECSPI_TDR;
329  } else {
330    regs->intreg = 0;
331    imx_ecspi_done(bus);
332  }
333}
334
335static void imx_ecspi_interrupt(void *arg)
336{
337  imx_ecspi_bus *bus;
338  volatile imx_ecspi *regs;
339
340  bus = arg;
341  regs = bus->regs;
342
343  while (imx_ecspi_is_rx_fifo_not_empty(regs)) {
344    (*bus->pop)(bus, regs);
345    --bus->in_transfer;
346  }
347
348  if (bus->todo > 0) {
349    imx_ecspi_push(bus, regs);
350  } else if (bus->in_transfer > 0) {
351    regs->intreg = IMX_ECSPI_RR;
352  } else {
353    --bus->msg_todo;
354    ++bus->msg;
355    imx_ecspi_next_msg(bus, regs);
356  }
357}
358
359static int imx_ecspi_check_messages(
360  imx_ecspi_bus *bus,
361  const spi_ioc_transfer *msg,
362  uint32_t size)
363{
364  while(size > 0) {
365    if (msg->delay_usecs != 0) {
366      return -EINVAL;
367    }
368    if (msg->bits_per_word > 32) {
369      return -EINVAL;
370    }
371    if ((msg->mode &
372        ~(SPI_CPHA | SPI_CPOL | SPI_LOOP | SPI_NO_CS)) != 0) {
373      return -EINVAL;
374    }
375    if ((msg->mode & SPI_NO_CS) == 0 &&
376        (msg->cs > IMX_ECSPI_MAX_CHIPSELECTS || !bus->cspins[msg->cs].valid)) {
377      return -EINVAL;
378    }
379    if (msg->cs_change != 0) {
380      return -EINVAL;
381    }
382
383    ++msg;
384    --size;
385  }
386
387  return 0;
388}
389
390static int imx_ecspi_transfer(
391  spi_bus *base,
392  const spi_ioc_transfer *msgs,
393  uint32_t n
394)
395{
396  imx_ecspi_bus *bus;
397  int rv;
398
399  bus = (imx_ecspi_bus *) base;
400
401  rv = imx_ecspi_check_messages(bus, msgs, n);
402
403  if (rv == 0) {
404    bus->msg_todo = n;
405    bus->msg = &msgs[0];
406    bus->task_id = rtems_task_self();
407
408    imx_ecspi_next_msg(bus, bus->regs);
409    rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT);
410    imx_ecspi_set_chipsel(bus, IMX_ECSPI_CS_NONE);
411  }
412  return rv;
413}
414
415static void imx_ecspi_destroy(spi_bus *base)
416{
417  imx_ecspi_bus *bus;
418
419  bus = (imx_ecspi_bus *) base;
420  rtems_interrupt_handler_remove(bus->irq, imx_ecspi_interrupt, bus);
421  spi_bus_destroy_and_free(&bus->base);
422}
423
424static int imx_ecspi_init(imx_ecspi_bus *bus, const void *fdt, int node)
425{
426  rtems_status_code sc;
427  int len;
428  const uint32_t *val;
429  size_t i;
430
431  for (i = 0; i < IMX_ECSPI_MAX_CHIPSELECTS; ++i) {
432    rtems_status_code sc_gpio = imx_gpio_init_from_fdt_property(
433        &bus->cspins[i].pin, node, "cs-gpios", IMX_GPIO_MODE_OUTPUT, i);
434    bus->cspins[i].valid = (sc_gpio == RTEMS_SUCCESSFUL);
435  }
436  imx_ecspi_set_chipsel(bus, IMX_ECSPI_CS_NONE);
437
438  imx_ecspi_config(
439    bus,
440    bus->regs,
441    bus->base.max_speed_hz,
442    8,
443    0,
444    0
445  );
446  imx_ecspi_reset(bus->regs);
447
448  sc = rtems_interrupt_handler_install(
449    bus->irq,
450    "ECSPI",
451    RTEMS_INTERRUPT_UNIQUE,
452    imx_ecspi_interrupt,
453    bus
454  );
455  if (sc != RTEMS_SUCCESSFUL) {
456    return EAGAIN;
457  }
458
459  val = fdt_getprop(fdt, node, "pinctrl-0", &len);
460  if (len == 4) {
461    imx_iomux_configure_pins(fdt, fdt32_to_cpu(val[0]));
462  }
463
464  return 0;
465}
466
467static int imx_ecspi_setup(spi_bus *base)
468{
469  imx_ecspi_bus *bus;
470
471  bus = (imx_ecspi_bus *) base;
472
473  if (
474    bus->base.speed_hz > imx_ccm_ipg_hz()
475      || bus->base.bits_per_word > 32
476  ) {
477    return -EINVAL;
478  }
479
480  imx_ecspi_config(
481    bus,
482    bus->regs,
483    bus->base.speed_hz,
484    bus->base.bits_per_word,
485    bus->base.mode,
486    bus->base.cs
487  );
488  return 0;
489}
490
491int spi_bus_register_imx(const char *bus_path, const char *alias_or_path)
492{
493  const void *fdt;
494  const char *path;
495  int node;
496  imx_ecspi_bus *bus;
497  int eno;
498
499  fdt = bsp_fdt_get();
500  path = fdt_get_alias(fdt, alias_or_path);
501
502  if (path == NULL) {
503    path = alias_or_path;
504  }
505
506  node = fdt_path_offset(fdt, path);
507  if (node < 0) {
508    rtems_set_errno_and_return_minus_one(ENXIO);
509  }
510
511  bus = (imx_ecspi_bus *) spi_bus_alloc_and_init(sizeof(*bus));
512  if (bus == NULL){
513    return -1;
514  }
515
516  bus->base.max_speed_hz = imx_ccm_ecspi_hz();
517  bus->base.delay_usecs = 0;
518  bus->regs = imx_get_reg_of_node(fdt, node);
519  bus->irq = imx_get_irq_of_node(fdt, node, 0);
520
521  eno = imx_ecspi_init(bus, fdt, node);
522  if (eno != 0) {
523    (*bus->base.destroy)(&bus->base);
524    rtems_set_errno_and_return_minus_one(eno);
525  }
526
527  bus->base.transfer = imx_ecspi_transfer;
528  bus->base.destroy = imx_ecspi_destroy;
529  bus->base.setup = imx_ecspi_setup;
530
531  return spi_bus_register(&bus->base, bus_path);
532}
Note: See TracBrowser for help on using the repository browser.