/** * @file * * @ingroup RTEMSBSPsARMLPC24XXSSP */ /* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (C) 2008, 2019 embedded brains GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include typedef struct { spi_bus base; volatile lpc24xx_ssp *regs; size_t tx_todo; const uint8_t *tx_buf; size_t tx_inc; size_t rx_todo; uint8_t *rx_buf; size_t rx_inc; const spi_ioc_transfer *msg; uint32_t msg_todo; int msg_error; rtems_binary_semaphore sem; lpc24xx_module module; rtems_vector_number irq; } lpc24xx_ssp_bus; typedef struct { volatile lpc24xx_ssp *regs; lpc24xx_module module; rtems_vector_number irq; } lpc24xx_ssp_config; static uint8_t lpc24xx_ssp_trash; static const uint8_t lpc24xx_ssp_idle = 0xff; static void lpc24xx_ssp_done(lpc24xx_ssp_bus *bus, int error) { bus->msg_error = error; rtems_binary_semaphore_post(&bus->sem); } static int lpc24xx_ssp_do_setup( lpc24xx_ssp_bus *bus, uint32_t speed_hz, uint32_t mode ) { volatile lpc24xx_ssp *regs; uint32_t clk; uint32_t scr_plus_one; uint32_t cr0; if (speed_hz > bus->base.max_speed_hz || speed_hz == 0) { return -EINVAL; } if ((mode & ~(SPI_CPOL | SPI_CPHA)) != 0) { return -EINVAL; } regs = bus->regs; clk = bus->base.max_speed_hz; scr_plus_one = (clk + speed_hz - 1) / speed_hz; if (scr_plus_one > 256) { uint32_t pre; pre = (scr_plus_one + 255) / 256; if (pre <= 127) { scr_plus_one = (clk / pre + speed_hz - 1) / speed_hz; } else { pre = 127; scr_plus_one = 256; } regs->cpsr = 2 * pre; } cr0 = SET_SSP_CR0_DSS(0, 0x7) | SET_SSP_CR0_SCR(0, scr_plus_one - 1); if ((mode & SPI_CPOL) != 0) { cr0 |= SSP_CR0_CPOL; } if ((mode & SPI_CPHA) != 0) { cr0 |= SSP_CR0_CPHA; } regs->cr0 = cr0; bus->base.speed_hz = speed_hz; bus->base.mode = mode; return 0; } static bool lpc24xx_ssp_msg_setup( lpc24xx_ssp_bus *bus, const spi_ioc_transfer *msg ) { if (msg->cs_change == 0 || msg->bits_per_word != 8) { lpc24xx_ssp_done(bus, -EINVAL); return false; } if (msg->speed_hz != bus->base.speed_hz || msg->mode != bus->base.mode) { int error; error = lpc24xx_ssp_do_setup(bus, msg->speed_hz, msg->mode); if (error != 0) { lpc24xx_ssp_done(bus, error); return false; } } bus->tx_todo = msg->len; bus->rx_todo = msg->len; if (msg->tx_buf != NULL) { bus->tx_buf = msg->tx_buf; bus->tx_inc = 1; } else { bus->tx_buf = &lpc24xx_ssp_idle; bus->tx_inc = 0; } if (msg->rx_buf != NULL) { bus->rx_buf = msg->rx_buf; bus->rx_inc = 1; } else { bus->rx_buf = &lpc24xx_ssp_trash; bus->rx_inc = 0; } return true; } static bool lpc24xx_ssp_do_tx_and_rx( lpc24xx_ssp_bus *bus, volatile lpc24xx_ssp *regs, uint32_t sr ) { size_t tx_todo; const uint8_t *tx_buf; size_t tx_inc; size_t rx_todo; uint8_t *rx_buf; size_t rx_inc; uint32_t imsc; tx_todo = bus->tx_todo; tx_buf = bus->tx_buf; tx_inc = bus->tx_inc; rx_todo = bus->rx_todo; rx_buf = bus->rx_buf; rx_inc = bus->rx_inc; while (tx_todo > 0 && (sr & SSP_SR_TNF) != 0) { regs->dr = *tx_buf; --tx_todo; tx_buf += tx_inc; if (rx_todo > 0 && (sr & SSP_SR_RNE) != 0) { *rx_buf = regs->dr; --rx_todo; rx_buf += rx_inc; } sr = regs->sr; } while (rx_todo > 0 && (sr & SSP_SR_RNE) != 0) { *rx_buf = regs->dr; --rx_todo; rx_buf += rx_inc; sr = regs->sr; } bus->tx_todo = tx_todo; bus->tx_buf = tx_buf; bus->rx_todo = rx_todo; bus->rx_buf = rx_buf; imsc = 0; if (tx_todo > 0) { imsc |= SSP_IMSC_TXIM; } else if (rx_todo > 0) { imsc |= SSP_IMSC_RXIM | SSP_IMSC_RTIM; regs->icr = SSP_ICR_RTRIS; } regs->imsc = imsc; return tx_todo == 0 && rx_todo == 0; } static void lpc24xx_ssp_start( lpc24xx_ssp_bus *bus, const spi_ioc_transfer *msg ) { while (true) { if (lpc24xx_ssp_msg_setup(bus, msg)) { volatile lpc24xx_ssp *regs; uint32_t sr; bool next_msg; regs = bus->regs; sr = regs->sr; if ((sr & (SSP_SR_RNE | SSP_SR_TFE)) != SSP_SR_TFE) { lpc24xx_ssp_done(bus, -EIO); break; } next_msg = lpc24xx_ssp_do_tx_and_rx(bus, regs, sr); if (!next_msg) { break; } --bus->msg_todo; if (bus->msg_todo == 0) { lpc24xx_ssp_done(bus, 0); break; } ++msg; bus->msg = msg; } else { break; } } } static void lpc24xx_ssp_interrupt(void *arg) { lpc24xx_ssp_bus *bus; volatile lpc24xx_ssp *regs; bus = arg; regs = bus->regs; while (true) { if (lpc24xx_ssp_do_tx_and_rx(bus, regs, regs->sr)) { --bus->msg_todo; if (bus->msg_todo > 0) { ++bus->msg; if (!lpc24xx_ssp_msg_setup(bus, bus->msg)) { break; } } else { lpc24xx_ssp_done(bus, 0); break; } } else { break; } } } static int lpc24xx_ssp_transfer( spi_bus *base, const spi_ioc_transfer *msgs, uint32_t msg_count ) { lpc24xx_ssp_bus *bus; if (msg_count == 0) { return 0; } bus = (lpc24xx_ssp_bus *) base; bus->msg = msgs; bus->msg_todo = msg_count; lpc24xx_ssp_start(bus, msgs); rtems_binary_semaphore_wait(&bus->sem); return bus->msg_error; } static void lpc24xx_ssp_destroy(spi_bus *base) { lpc24xx_ssp_bus *bus; rtems_status_code sc; bus = (lpc24xx_ssp_bus *) base; sc = rtems_interrupt_handler_remove( bus->irq, lpc24xx_ssp_interrupt, bus ); _Assert(sc == RTEMS_SUCCESSFUL); (void) sc; /* Disable SSP module */ bus->regs->cr1 = 0; sc = lpc24xx_module_disable(bus->module); _Assert(sc == RTEMS_SUCCESSFUL); (void) sc; rtems_binary_semaphore_destroy(&bus->sem); spi_bus_destroy_and_free(&bus->base); } static int lpc24xx_ssp_setup(spi_bus *base) { lpc24xx_ssp_bus *bus; bus = (lpc24xx_ssp_bus *) base; if (bus->base.bits_per_word != 8) { return -EINVAL; } return lpc24xx_ssp_do_setup(bus, bus->base.speed_hz, bus->base.mode); } static int lpc24xx_ssp_init(lpc24xx_ssp_bus *bus) { rtems_status_code sc; sc = lpc24xx_module_enable(bus->module, LPC24XX_MODULE_PCLK_DEFAULT); _Assert(sc == RTEMS_SUCCESSFUL); (void) sc; /* Disable SSP module */ bus->regs->cr1 = 0; sc = rtems_interrupt_handler_install( bus->irq, "SSP", RTEMS_INTERRUPT_UNIQUE, lpc24xx_ssp_interrupt, bus ); if (sc != RTEMS_SUCCESSFUL) { return EAGAIN; } rtems_binary_semaphore_init(&bus->sem, "SSP"); /* Initialize SSP module */ bus->regs->dmacr = 0; bus->regs->imsc = 0; bus->regs->cpsr = 2; bus->regs->cr0 = SET_SSP_CR0_DSS(0, 0x7); bus->regs->cr1 = SSP_CR1_SSE; return 0; } static int spi_bus_register_lpc24xx_ssp( const char *bus_path, const lpc24xx_ssp_config *config ) { lpc24xx_ssp_bus *bus; int eno; bus = (lpc24xx_ssp_bus *) spi_bus_alloc_and_init(sizeof(*bus)); if (bus == NULL) { return -1; } bus->base.max_speed_hz = LPC24XX_PCLK / 2; bus->base.bits_per_word = 8; bus->base.speed_hz = bus->base.max_speed_hz; bus->regs = config->regs; bus->module = config->module; bus->irq = config->irq; eno = lpc24xx_ssp_init(bus); if (eno != 0) { (*bus->base.destroy)(&bus->base); rtems_set_errno_and_return_minus_one(eno); } bus->base.transfer = lpc24xx_ssp_transfer; bus->base.destroy = lpc24xx_ssp_destroy; bus->base.setup = lpc24xx_ssp_setup; return spi_bus_register(&bus->base, bus_path); } int lpc24xx_register_ssp_0(void) { static const lpc24xx_ssp_config config = { .regs = (volatile lpc24xx_ssp *) SSP0_BASE_ADDR, .module = LPC24XX_MODULE_SSP_0, .irq = LPC24XX_IRQ_SPI_SSP_0 }; return spi_bus_register_lpc24xx_ssp( LPC24XX_SSP_0_BUS_PATH, &config ); } int lpc24xx_register_ssp_1(void) { static const lpc24xx_ssp_config config = { .regs = (volatile lpc24xx_ssp *) SSP1_BASE_ADDR, .module = LPC24XX_MODULE_SSP_1, .irq = LPC24XX_IRQ_SSP_1 }; return spi_bus_register_lpc24xx_ssp( LPC24XX_SSP_2_BUS_PATH, &config ); } #ifdef ARM_MULTILIB_ARCH_V7M int lpc24xx_register_ssp_2(void) { static const lpc24xx_ssp_config config = { .regs = (volatile lpc24xx_ssp *) SSP2_BASE_ADDR, .module = LPC24XX_MODULE_SSP_2, .irq = LPC24XX_IRQ_SSP_2 }; return spi_bus_register_lpc24xx_ssp( LPC24XX_SSP_2_BUS_PATH, &config ); } #endif