From 170df3d9b1bf53705f2d7a345f87d68d8ba41994 Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Wed, 4 Oct 2017 07:24:19 +0200 Subject: bsp/imx: Add SPI bus driver Update #3090. --- c/src/lib/libbsp/arm/imx/Makefile.am | 3 + c/src/lib/libbsp/arm/imx/include/bsp.h | 11 + c/src/lib/libbsp/arm/imx/spi/imx-ecspi.c | 449 +++++++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 c/src/lib/libbsp/arm/imx/spi/imx-ecspi.c diff --git a/c/src/lib/libbsp/arm/imx/Makefile.am b/c/src/lib/libbsp/arm/imx/Makefile.am index ff96999586..c131edc67a 100644 --- a/c/src/lib/libbsp/arm/imx/Makefile.am +++ b/c/src/lib/libbsp/arm/imx/Makefile.am @@ -123,6 +123,9 @@ libbsp_a_CPPFLAGS += -I$(srcdir)/../shared/armv467ar-basic-cache # I2C libbsp_a_SOURCES += i2c/imx-i2c.c +# SPI +libbsp_a_SOURCES += spi/imx-ecspi.c + # Start hooks libbsp_a_SOURCES += startup/bspstarthooks.c diff --git a/c/src/lib/libbsp/arm/imx/include/bsp.h b/c/src/lib/libbsp/arm/imx/include/bsp.h index d22039717b..cfea80748c 100644 --- a/c/src/lib/libbsp/arm/imx/include/bsp.h +++ b/c/src/lib/libbsp/arm/imx/include/bsp.h @@ -62,6 +62,17 @@ rtems_vector_number imx_get_irq_of_node( */ int i2c_bus_register_imx(const char *bus_path, const char *alias_or_path); +/** + * @brief Registers an IMX ECSPI bus driver. + * + * @param[in] bus_path The ECSPI bus driver device path, e.g. "/dev/spi-0". + * @param[in] alias_or_path The FDT alias or path, e.g. "spi0". + * + * @retval 0 Successful operation. + * @retval -1 An error occurred. The errno is set to indicate the error. + */ +int spi_bus_register_imx(const char *bus_path, const char *alias_or_path); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/c/src/lib/libbsp/arm/imx/spi/imx-ecspi.c b/c/src/lib/libbsp/arm/imx/spi/imx-ecspi.c new file mode 100644 index 0000000000..9a232c53e9 --- /dev/null +++ b/c/src/lib/libbsp/arm/imx/spi/imx-ecspi.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2017 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMX_ECSPI_FIFO_SIZE 64 + +typedef struct imx_ecspi_bus imx_ecspi_bus; + +struct imx_ecspi_bus { + spi_bus base; + volatile imx_ecspi *regs; + uint32_t conreg; + uint32_t speed_hz; + uint32_t mode; + uint8_t bits_per_word; + uint8_t cs; + uint32_t msg_todo; + const spi_ioc_transfer *msg; + uint32_t todo; + uint32_t in_transfer; + uint8_t *rx_buf; + const uint8_t *tx_buf; + void (*push)(imx_ecspi_bus *, volatile imx_ecspi *); + void (*pop)(imx_ecspi_bus *, volatile imx_ecspi *); + rtems_id task_id; + rtems_vector_number irq; +}; + +static bool imx_ecspi_is_rx_fifo_not_empty(volatile imx_ecspi *regs) +{ + return (regs->statreg & IMX_ECSPI_RR) != 0; +} + +static void imx_ecspi_reset(volatile imx_ecspi *regs) +{ + while (imx_ecspi_is_rx_fifo_not_empty(regs)) { + regs->rxdata; + } +} + +static void imx_ecspi_done(imx_ecspi_bus *bus) +{ + rtems_event_transient_send(bus->task_id); +} + +#define IMC_ECSPI_PUSH(type) \ +static void imx_ecspi_push_##type(imx_ecspi_bus *bus, volatile imx_ecspi *regs) \ +{ \ + type val = 0; \ + if (bus->tx_buf != NULL) { \ + val = *(type *)bus->tx_buf; \ + bus->tx_buf += sizeof(type); \ + } \ + bus->todo -= sizeof(type); \ + regs->txdata = val; \ +} + +#define IMX_ECSPI_POP(type) \ +static void imx_ecspi_pop_##type(imx_ecspi_bus *bus, volatile imx_ecspi *regs) \ +{ \ + uint32_t val = regs->rxdata; \ + if (bus->rx_buf != NULL) { \ + *(type *)bus->rx_buf = val; \ + bus->rx_buf += sizeof(type); \ + } \ +} + +IMC_ECSPI_PUSH(uint8_t) +IMX_ECSPI_POP(uint8_t) +IMC_ECSPI_PUSH(uint16_t) +IMX_ECSPI_POP(uint16_t) +IMC_ECSPI_PUSH(uint32_t) +IMX_ECSPI_POP(uint32_t) + +static void imx_ecspi_push_uint32_t_swap( + imx_ecspi_bus *bus, + volatile imx_ecspi *regs +) +{ + uint32_t val = 0; + + if (bus->tx_buf != NULL) { + val = bswap32(*(uint32_t *)bus->tx_buf); + bus->tx_buf += sizeof(uint32_t); + } + + bus->todo -= sizeof(uint32_t); + regs->txdata = val; +} + +static void imx_ecspi_pop_uint32_t_swap( + imx_ecspi_bus *bus, + volatile imx_ecspi *regs +) +{ + uint32_t val = regs->rxdata; + + if (bus->rx_buf != NULL) { + *(uint32_t *)bus->rx_buf = bswap32(val); + bus->rx_buf += sizeof(uint32_t); + } +} + +static void imx_ecspi_push(imx_ecspi_bus *bus, volatile imx_ecspi *regs) +{ + while (bus->todo > 0 && bus->in_transfer < IMX_ECSPI_FIFO_SIZE) { + (*bus->push)(bus, regs); + ++bus->in_transfer; + } +} + +static uint32_t imx_ecspi_conreg_divider(imx_ecspi_bus *bus, uint32_t speed_hz) +{ + uint32_t post; + uint32_t pre; + uint32_t clk_in; + + clk_in = bus->base.max_speed_hz; + + if (clk_in > speed_hz) { + post = fls((int) clk_in) - fls((int) speed_hz); + + if (clk_in > (speed_hz << post)) { + ++post; + } + + /* We have 2^4 == 16, use the pre-divider for this factor */ + post = MAX(4, post) - 4; + + if (post <= 0xf) { + pre = howmany(clk_in, speed_hz << post) - 1; + } else { + post = 0xf; + pre = 0xf; + } + } else { + post = 0; + pre = 0; + } + + return IMX_ECSPI_CONREG_POST_DIVIDER(post) + | IMX_ECSPI_CONREG_PRE_DIVIDER(pre); +} + +static void imx_ecspi_config( + imx_ecspi_bus *bus, + volatile imx_ecspi *regs, + uint32_t speed_hz, + uint8_t bits_per_word, + uint32_t mode, + uint8_t cs +) +{ + uint32_t conreg; + uint32_t testreg; + uint32_t configreg; + uint32_t cs_bit; + + conreg = IMX_ECSPI_CONREG_CHANNEL_MODE(0xf) + | IMX_ECSPI_CONREG_SMC | IMX_ECSPI_CONREG_EN; + testreg = regs->testreg; + configreg = regs->configreg; + cs_bit = 1U << cs; + + conreg |= imx_ecspi_conreg_divider(bus, speed_hz); + conreg |= IMX_ECSPI_CONREG_CHANNEL_SELECT(cs); + + configreg |= IMX_ECSPI_CONFIGREG_SS_CTL(cs_bit); + + if ((mode & SPI_CPHA) != 0) { + configreg |= IMX_ECSPI_CONFIGREG_SCLK_PHA(cs_bit); + } else { + configreg &= ~IMX_ECSPI_CONFIGREG_SCLK_PHA(cs_bit); + } + + if ((mode & SPI_CPOL) != 0) { + configreg |= IMX_ECSPI_CONFIGREG_SCLK_POL(cs_bit); + configreg |= IMX_ECSPI_CONFIGREG_SCLK_CTL(cs_bit); + } else { + configreg &= ~IMX_ECSPI_CONFIGREG_SCLK_POL(cs_bit); + configreg &= ~IMX_ECSPI_CONFIGREG_SCLK_CTL(cs_bit); + } + + if ((mode & SPI_CS_HIGH) != 0) { + configreg |= IMX_ECSPI_CONFIGREG_SS_POL(cs_bit); + } else { + configreg &= ~IMX_ECSPI_CONFIGREG_SS_POL(cs_bit); + } + + if ((mode & SPI_LOOP) != 0) { + testreg |= IMX_ECSPI_TESTREG_LBC; + } else { + testreg &= ~IMX_ECSPI_TESTREG_LBC; + } + + regs->conreg = conreg; + regs->testreg = testreg; + regs->configreg = configreg; + + bus->conreg = conreg; + bus->speed_hz = speed_hz; + bus->bits_per_word = bits_per_word; + bus->mode = mode; + bus->cs = cs; + + /* FIXME: Clock change delay */ +} + +static void imx_ecspi_set_push_pop( + imx_ecspi_bus *bus, + uint32_t len, + uint8_t bits_per_word +) +{ + uint32_t conreg; + + conreg = bus->conreg; + + if (len % 4 == 0 && len <= IMX_ECSPI_FIFO_SIZE) { + conreg |= IMX_ECSPI_CONREG_BURST_LENGTH((len * 8) - 1); + + bus->push = imx_ecspi_push_uint32_t_swap; + bus->pop = imx_ecspi_pop_uint32_t_swap; + } else { + conreg |= IMX_ECSPI_CONREG_BURST_LENGTH(bits_per_word - 1); + + if (bits_per_word <= 8) { + bus->push = imx_ecspi_push_uint8_t; + bus->pop = imx_ecspi_pop_uint8_t; + } else if (bits_per_word <= 16) { + bus->push = imx_ecspi_push_uint16_t; + bus->pop = imx_ecspi_pop_uint16_t; + } else { + bus->push = imx_ecspi_push_uint32_t; + bus->pop = imx_ecspi_pop_uint32_t; + } + } + + bus->regs->conreg = conreg; +} + +static void imx_ecspi_next_msg(imx_ecspi_bus *bus, volatile imx_ecspi *regs) +{ + if (bus->msg_todo > 0) { + const spi_ioc_transfer *msg; + + msg = bus->msg; + + if ( + msg->speed_hz != bus->speed_hz + || msg->bits_per_word != bus->bits_per_word + || msg->mode != bus->mode + || msg->cs != bus->cs + ) { + imx_ecspi_config( + bus, + regs, + msg->speed_hz, + msg->bits_per_word, + msg->mode, + msg->cs + ); + } + + bus->todo = msg->len; + bus->rx_buf = msg->rx_buf; + bus->tx_buf = msg->tx_buf; + imx_ecspi_set_push_pop(bus, msg->len, msg->bits_per_word); + imx_ecspi_push(bus, regs); + regs->intreg = IMX_ECSPI_TE; + } else { + regs->intreg = 0; + imx_ecspi_done(bus); + } +} + +static void imx_ecspi_interrupt(void *arg) +{ + imx_ecspi_bus *bus; + volatile imx_ecspi *regs; + + bus = arg; + regs = bus->regs; + + while (imx_ecspi_is_rx_fifo_not_empty(regs)) { + (*bus->pop)(bus, regs); + --bus->in_transfer; + } + + if (bus->todo > 0) { + imx_ecspi_push(bus, regs); + } else if (bus->in_transfer > 0) { + regs->intreg = IMX_ECSPI_RR; + } else { + --bus->msg_todo; + ++bus->msg; + imx_ecspi_next_msg(bus, regs); + } +} + +static int imx_ecspi_transfer( + spi_bus *base, + const spi_ioc_transfer *msgs, + uint32_t n +) +{ + imx_ecspi_bus *bus; + + bus = (imx_ecspi_bus *) base; + + bus->msg_todo = n; + bus->msg = &msgs[0]; + bus->task_id = rtems_task_self(); + + imx_ecspi_next_msg(bus, bus->regs); + rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); + return 0; +} + +static void imx_ecspi_destroy(spi_bus *base) +{ + imx_ecspi_bus *bus; + + bus = (imx_ecspi_bus *) base; + rtems_interrupt_handler_remove(bus->irq, imx_ecspi_interrupt, bus); + spi_bus_destroy_and_free(&bus->base); +} + +static int imx_ecspi_init(imx_ecspi_bus *bus, const void *fdt, int node) +{ + rtems_status_code sc; + int len; + const uint32_t *val; + + imx_ecspi_config( + bus, + bus->regs, + bus->base.max_speed_hz, + 8, + 0, + 0 + ); + imx_ecspi_reset(bus->regs); + + sc = rtems_interrupt_handler_install( + bus->irq, + "ECSPI", + RTEMS_INTERRUPT_UNIQUE, + imx_ecspi_interrupt, + bus + ); + if (sc != RTEMS_SUCCESSFUL) { + return EAGAIN; + } + + val = fdt_getprop(fdt, node, "pinctrl-0", &len); + if (len == 4) { + imx_iomux_configure_pins(fdt, fdt32_to_cpu(val[0])); + } + + return 0; +} + +static int imx_ecspi_setup(spi_bus *base) +{ + imx_ecspi_bus *bus; + + bus = (imx_ecspi_bus *) base; + + if ( + bus->base.speed_hz > imx_ccm_ipg_hz() + || bus->base.bits_per_word > 32 + ) { + return -EINVAL; + } + + imx_ecspi_config( + bus, + bus->regs, + bus->base.speed_hz, + bus->base.bits_per_word, + bus->base.mode, + bus->base.cs + ); + return 0; +} + +int spi_bus_register_imx(const char *bus_path, const char *alias_or_path) +{ + const void *fdt; + const char *path; + int node; + imx_ecspi_bus *bus; + int eno; + + fdt = bsp_fdt_get(); + path = fdt_get_alias(fdt, alias_or_path); + + if (path == NULL) { + path = alias_or_path; + } + + node = fdt_path_offset(fdt, path); + if (node < 0) { + rtems_set_errno_and_return_minus_one(ENXIO); + } + + bus = (imx_ecspi_bus *) spi_bus_alloc_and_init(sizeof(*bus)); + if (bus == NULL){ + return -1; + } + + bus->base.max_speed_hz = imx_ccm_ipg_hz(); + bus->base.delay_usecs = 1; + bus->regs = imx_get_reg_of_node(fdt, node); + bus->irq = imx_get_irq_of_node(fdt, node, 0); + + eno = imx_ecspi_init(bus, fdt, node); + if (eno != 0) { + (*bus->base.destroy)(&bus->base); + rtems_set_errno_and_return_minus_one(eno); + } + + bus->base.transfer = imx_ecspi_transfer; + bus->base.destroy = imx_ecspi_destroy; + bus->base.setup = imx_ecspi_setup; + + return spi_bus_register(&bus->base, bus_path); +} -- cgit v1.2.3