/** * @file * * @ingroup lpc24xx_libi2c * * @brief LibI2C bus driver for the Synchronous Serial Port (SSP). */ /* * Copyright (c) 2008 * Embedded Brains GmbH * Obere Lagerstr. 30 * D-82178 Puchheim * Germany * rtems@embedded-brains.de * * 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 #define RTEMS_STATUS_CHECKS_USE_PRINTK #include #define LPC24XX_SSP_NUMBER 2 #define LPC24XX_SSP_FIFO_SIZE 8 #define LPC24XX_SSP_BAUD_RATE 2000000 typedef enum { LPC24XX_SSP_DMA_INVALID = 0, LPC24XX_SSP_DMA_AVAILABLE = 1, LPC24XX_SSP_DMA_NOT_INITIALIZED = 2, LPC24XX_SSP_DMA_INITIALIZATION = 3, LPC24XX_SSP_DMA_TRANSFER_FLAG = 0x80000000U, LPC24XX_SSP_DMA_WAIT = 1 | LPC24XX_SSP_DMA_TRANSFER_FLAG, LPC24XX_SSP_DMA_WAIT_FOR_CHANNEL_0 = 2 | LPC24XX_SSP_DMA_TRANSFER_FLAG, LPC24XX_SSP_DMA_WAIT_FOR_CHANNEL_1 = 3 | LPC24XX_SSP_DMA_TRANSFER_FLAG, LPC24XX_SSP_DMA_ERROR = 4 | LPC24XX_SSP_DMA_TRANSFER_FLAG, LPC24XX_SSP_DMA_DONE = 5 | LPC24XX_SSP_DMA_TRANSFER_FLAG } lpc24xx_ssp_dma_status; typedef struct { rtems_libi2c_bus_t bus; volatile lpc24xx_ssp *regs; unsigned clock; uint32_t idle_char; } lpc24xx_ssp_bus_entry; typedef struct { lpc24xx_ssp_dma_status status; lpc24xx_ssp_bus_entry *bus; rtems_libi2c_read_write_done_t done; int n; void *arg; } lpc24xx_ssp_dma_entry; static lpc24xx_ssp_dma_entry lpc24xx_ssp_dma_data = { .status = LPC24XX_SSP_DMA_NOT_INITIALIZED, .bus = NULL, .done = NULL, .n = 0, .arg = NULL }; static uint32_t lpc24xx_ssp_trash = 0; static inline bool lpc24xx_ssp_is_busy(const lpc24xx_ssp_bus_entry *bus) { return lpc24xx_ssp_dma_data.bus == bus && lpc24xx_ssp_dma_data.status != LPC24XX_SSP_DMA_AVAILABLE; } static void lpc24xx_ssp_handler(void *arg) { lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) arg; volatile lpc24xx_ssp *regs = e->regs; uint32_t mis = regs->mis; uint32_t icr = 0; if ((mis & SSP_MIS_RORRIS) != 0) { /* TODO */ icr |= SSP_ICR_RORRIS; } regs->icr = icr; } static void lpc24xx_ssp_dma_handler(void *arg) { lpc24xx_ssp_dma_entry *e = (lpc24xx_ssp_dma_entry *) arg; lpc24xx_ssp_dma_status status = e->status; uint32_t tc = 0; uint32_t err = 0; int rv = 0; /* Return if we are not in a transfer status */ if ((status & LPC24XX_SSP_DMA_TRANSFER_FLAG) == 0) { return; } /* Get interrupt status */ tc = GPDMA_INT_TCSTAT; err = GPDMA_INT_ERR_STAT; /* Clear interrupt status */ GPDMA_INT_TCCLR = tc; GPDMA_INT_ERR_CLR = err; /* Change status */ if (err == 0) { switch (status) { case LPC24XX_SSP_DMA_WAIT: if ((tc & (GPDMA_STATUS_CH_0 | GPDMA_STATUS_CH_1)) != 0) { status = LPC24XX_SSP_DMA_DONE; } else if ((tc & GPDMA_STATUS_CH_0) != 0) { status = LPC24XX_SSP_DMA_WAIT_FOR_CHANNEL_1; } else if ((tc & GPDMA_STATUS_CH_1) != 0) { status = LPC24XX_SSP_DMA_WAIT_FOR_CHANNEL_0; } break; case LPC24XX_SSP_DMA_WAIT_FOR_CHANNEL_0: if ((tc & GPDMA_STATUS_CH_1) != 0) { status = LPC24XX_SSP_DMA_ERROR; } else if ((tc & GPDMA_STATUS_CH_0) != 0) { status = LPC24XX_SSP_DMA_DONE; } break; case LPC24XX_SSP_DMA_WAIT_FOR_CHANNEL_1: if ((tc & GPDMA_STATUS_CH_0) != 0) { status = LPC24XX_SSP_DMA_ERROR; } else if ((tc & GPDMA_STATUS_CH_1) != 0) { status = LPC24XX_SSP_DMA_DONE; } break; default: status = LPC24XX_SSP_DMA_ERROR; break; } } else { status = LPC24XX_SSP_DMA_ERROR; } /* Error cleanup */ if (status == LPC24XX_SSP_DMA_ERROR) { lpc24xx_dma_channel_disable(0, true); lpc24xx_dma_channel_disable(1, true); status = LPC24XX_SSP_DMA_DONE; rv = -RTEMS_IO_ERROR; } /* Done */ if (status == LPC24XX_SSP_DMA_DONE) { status = LPC24XX_SSP_DMA_AVAILABLE; if (e->done != NULL) { e->done(rv, e->n, e->arg); e->done = NULL; } } /* Set status */ e->status = status; } static rtems_status_code lpc24xx_ssp_init(rtems_libi2c_bus_t *bus) { rtems_status_code sc = RTEMS_SUCCESSFUL; rtems_interrupt_level level; lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) bus; volatile lpc24xx_ssp *regs = e->regs; unsigned pclk = lpc24xx_cclk(); unsigned pre = ((pclk + LPC24XX_SSP_BAUD_RATE - 1) / LPC24XX_SSP_BAUD_RATE + 1) & ~1U; lpc24xx_module module = LPC24XX_MODULE_SSP_0; rtems_vector_number vector = UINT32_MAX; if (lpc24xx_ssp_dma_data.status == LPC24XX_SSP_DMA_NOT_INITIALIZED) { lpc24xx_ssp_dma_status status = LPC24XX_SSP_DMA_INVALID; /* Test and set DMA support status */ rtems_interrupt_disable(level); status = lpc24xx_ssp_dma_data.status; if (status == LPC24XX_SSP_DMA_NOT_INITIALIZED) { lpc24xx_ssp_dma_data.status = LPC24XX_SSP_DMA_INITIALIZATION; } rtems_interrupt_enable(level); if (status == LPC24XX_SSP_DMA_NOT_INITIALIZED) { /* Install DMA interrupt handler */ sc = rtems_interrupt_handler_install( LPC24XX_IRQ_DMA, "SSP DMA", RTEMS_INTERRUPT_SHARED, lpc24xx_ssp_dma_handler, &lpc24xx_ssp_dma_data ); RTEMS_CHECK_SC(sc, "install DMA interrupt handler"); /* Set DMA support status */ lpc24xx_ssp_dma_data.status = LPC24XX_SSP_DMA_AVAILABLE; } } /* Disable module */ regs->cr1 = 0; switch ((uintptr_t) regs) { case SSP0_BASE_ADDR: module = LPC24XX_MODULE_SSP_0; vector = LPC24XX_IRQ_SPI_SSP_0; break; case SSP1_BASE_ADDR: module = LPC24XX_MODULE_SSP_1; vector = LPC24XX_IRQ_SSP_1; break; default: return RTEMS_IO_ERROR; } /* Set clock select */ sc = lpc24xx_module_enable(module, LPC24XX_MODULE_PCLK_DEFAULT); RTEMS_CHECK_SC(sc, "enable module clock"); /* Set serial clock rate to save value */ regs->cr0 = SET_SSP_CR0_SCR(0, 255); /* Set clock prescaler */ if (pre > 254) { pre = 254; } else if (pre < 2) { pre = 2; } regs->cpsr = pre; /* Save clock value */ e->clock = pclk / pre; /* Enable module and loop back mode */ regs->cr1 = SSP_CR1_LBM | SSP_CR1_SSE; /* Install interrupt handler */ sc = rtems_interrupt_handler_install( vector, "SSP", RTEMS_INTERRUPT_UNIQUE, lpc24xx_ssp_handler, e ); RTEMS_CHECK_SC(sc, "install interrupt handler"); /* Enable receiver overrun interrupts */ e->regs->imsc = SSP_IMSC_RORIM; return RTEMS_SUCCESSFUL; } static rtems_status_code lpc24xx_ssp_send_start(rtems_libi2c_bus_t *bus) { return RTEMS_SUCCESSFUL; } static rtems_status_code lpc24xx_ssp_send_stop(rtems_libi2c_bus_t *bus) { lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) bus; /* Release DMA support */ if (lpc24xx_ssp_dma_data.bus == e) { if (lpc24xx_ssp_dma_data.status == LPC24XX_SSP_DMA_AVAILABLE) { lpc24xx_dma_channel_release(0); lpc24xx_dma_channel_release(1); lpc24xx_ssp_dma_data.bus = NULL; } else { return RTEMS_RESOURCE_IN_USE; } } return RTEMS_SUCCESSFUL; } static rtems_status_code lpc24xx_ssp_send_addr( rtems_libi2c_bus_t *bus, uint32_t addr, int rw ) { lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) bus; if (lpc24xx_ssp_is_busy(e)) { return RTEMS_RESOURCE_IN_USE; } return RTEMS_SUCCESSFUL; } static int lpc24xx_ssp_set_transfer_mode( rtems_libi2c_bus_t *bus, const rtems_libi2c_tfr_mode_t *mode ) { lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) bus; volatile lpc24xx_ssp *regs = e->regs; unsigned clk = e->clock; unsigned br = mode->baudrate; unsigned scr = (clk + br - 1) / br; if (lpc24xx_ssp_is_busy(e)) { return -RTEMS_RESOURCE_IN_USE; } if (mode->bits_per_char != 8) { return -RTEMS_INVALID_NUMBER; } if (mode->lsb_first) { return -RTEMS_INVALID_NUMBER; } if (br == 0) { return -RTEMS_INVALID_NUMBER; } /* Compute new prescaler if necessary */ if (scr > 256 || scr < 1) { unsigned pre = regs->cpsr; unsigned pclk = clk * pre; while (scr > 256) { if (pre > 252) { return -RTEMS_INVALID_NUMBER; } pre += 2; clk = pclk / pre; scr = (clk + br - 1) / br; } while (scr < 1) { if (pre < 4) { return -RTEMS_INVALID_NUMBER; } pre -= 2; clk = pclk / pre; scr = (clk + br - 1) / br; } regs->cpsr = pre; e->clock = clk; } /* Adjust SCR */ --scr; e->idle_char = mode->idle_char; while ((regs->sr & SSP_SR_TFE) == 0) { /* Wait */ } regs->cr0 = SET_SSP_CR0_DSS(0, 0x7) | SET_SSP_CR0_SCR(0, scr) | (mode->clock_inv ? SSP_CR0_CPOL : 0) | (mode->clock_phs ? SSP_CR0_CPHA : 0); return 0; } static int lpc24xx_ssp_read_write( rtems_libi2c_bus_t *bus, unsigned char *in, const unsigned char *out, int n ) { lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) bus; volatile lpc24xx_ssp *regs = e->regs; int r = 0; int w = 0; int dr = 1; int dw = 1; int m = 0; uint32_t sr = regs->sr; unsigned char trash = 0; unsigned char idle_char = (unsigned char) e->idle_char; if (lpc24xx_ssp_is_busy(e)) { return -RTEMS_RESOURCE_IN_USE; } if (n < 0) { return -RTEMS_INVALID_SIZE; } /* Disable DMA on SSP */ regs->dmacr = 0; if (in == NULL) { dr = 0; in = &trash; } if (out == NULL) { dw = 0; out = &idle_char; } /* * Assumption: The transmit and receive FIFOs are empty. If this assumption * is not true an input buffer overflow may occur or we may never exit the * loop due to data loss. This is only possible if entities external to this * driver operate on the SSP. */ while (w < n) { /* FIFO capacity */ m = w - r; /* Write */ if ((sr & SSP_SR_TNF) != 0 && m < LPC24XX_SSP_FIFO_SIZE) { regs->dr = *out; ++w; out += dw; } /* Read */ if ((sr & SSP_SR_RNE) != 0) { *in = (unsigned char) regs->dr; ++r; in += dr; } /* New status */ sr = regs->sr; } /* Read outstanding input */ while (r < n) { /* Wait */ do { sr = regs->sr; } while ((sr & SSP_SR_RNE) == 0); /* Read */ *in = (unsigned char) regs->dr; ++r; in += dr; } return n; } static int lpc24xx_ssp_read_write_async( rtems_libi2c_bus_t *bus, unsigned char *in, const unsigned char *out, int n, rtems_libi2c_read_write_done_t done, void *arg ) { rtems_interrupt_level level; lpc24xx_ssp_bus_entry *e = (lpc24xx_ssp_bus_entry *) bus; volatile lpc24xx_ssp *ssp = e->regs; volatile lpc24xx_dma_channel *receive_channel = GPDMA_CH_BASE_ADDR(0); volatile lpc24xx_dma_channel *transmit_channel = GPDMA_CH_BASE_ADDR(1); uint32_t di = GPDMA_CH_CTRL_DI; uint32_t si = GPDMA_CH_CTRL_SI; if (n < 0 || n > (int) GPDMA_CH_CTRL_TSZ_MAX) { return -RTEMS_INVALID_SIZE; } /* Try to reserve DMA support for this bus */ if (lpc24xx_ssp_dma_data.bus == NULL) { rtems_interrupt_disable(level); if (lpc24xx_ssp_dma_data.bus == NULL) { lpc24xx_ssp_dma_data.bus = e; } rtems_interrupt_enable(level); /* Try to obtain DMA channels */ if (lpc24xx_ssp_dma_data.bus == e) { rtems_status_code cs0 = lpc24xx_dma_channel_obtain(0); rtems_status_code cs1 = lpc24xx_dma_channel_obtain(1); if (cs0 != RTEMS_SUCCESSFUL || cs1 != RTEMS_SUCCESSFUL) { if (cs0 == RTEMS_SUCCESSFUL) { lpc24xx_dma_channel_release(0); } if (cs1 == RTEMS_SUCCESSFUL) { lpc24xx_dma_channel_release(1); } lpc24xx_ssp_dma_data.bus = NULL; } } } /* Check if DMA support is available */ if (lpc24xx_ssp_dma_data.bus != e || lpc24xx_ssp_dma_data.status != LPC24XX_SSP_DMA_AVAILABLE) { return -RTEMS_RESOURCE_IN_USE; } /* Set DMA support status and parameter */ lpc24xx_ssp_dma_data.status = LPC24XX_SSP_DMA_WAIT; lpc24xx_ssp_dma_data.done = done; lpc24xx_ssp_dma_data.n = n; lpc24xx_ssp_dma_data.arg = arg; /* Enable DMA on SSP */ ssp->dmacr = SSP_DMACR_RXDMAE | SSP_DMACR_TXDMAE; /* Receive */ if (in != NULL) { receive_channel->desc.dest = (uint32_t) in; } else { receive_channel->desc.dest = (uint32_t) &lpc24xx_ssp_trash; di = 0; } receive_channel->desc.src = (uint32_t) &ssp->dr; receive_channel->desc.lli = 0; receive_channel->desc.ctrl = SET_GPDMA_CH_CTRL_TSZ(0, n) | SET_GPDMA_CH_CTRL_SBSZ(0, GPDMA_CH_CTRL_BSZ_4) | SET_GPDMA_CH_CTRL_DBSZ(0, GPDMA_CH_CTRL_BSZ_4) | SET_GPDMA_CH_CTRL_SW(0, GPDMA_CH_CTRL_W_8) | SET_GPDMA_CH_CTRL_DW(0, GPDMA_CH_CTRL_W_8) | GPDMA_CH_CTRL_ITC | di; receive_channel->cfg = SET_GPDMA_CH_CFG_SRCPER(0, GPDMA_CH_CFG_PER_SSP1_RX) | SET_GPDMA_CH_CFG_FLOW(0, GPDMA_CH_CFG_FLOW_PER_TO_MEM_DMA) | GPDMA_CH_CFG_IE | GPDMA_CH_CFG_ITC | GPDMA_CH_CFG_EN; /* Transmit */ if (out != NULL) { transmit_channel->desc.src = (uint32_t) out; } else { transmit_channel->desc.src = (uint32_t) &e->idle_char; si = 0; } transmit_channel->desc.dest = (uint32_t) &ssp->dr; transmit_channel->desc.lli = 0; transmit_channel->desc.ctrl = SET_GPDMA_CH_CTRL_TSZ(0, n) | SET_GPDMA_CH_CTRL_SBSZ(0, GPDMA_CH_CTRL_BSZ_4) | SET_GPDMA_CH_CTRL_DBSZ(0, GPDMA_CH_CTRL_BSZ_4) | SET_GPDMA_CH_CTRL_SW(0, GPDMA_CH_CTRL_W_8) | SET_GPDMA_CH_CTRL_DW(0, GPDMA_CH_CTRL_W_8) | GPDMA_CH_CTRL_ITC | si; transmit_channel->cfg = SET_GPDMA_CH_CFG_DESTPER(0, GPDMA_CH_CFG_PER_SSP1_TX) | SET_GPDMA_CH_CFG_FLOW(0, GPDMA_CH_CFG_FLOW_MEM_TO_PER_DMA) | GPDMA_CH_CFG_IE | GPDMA_CH_CFG_ITC | GPDMA_CH_CFG_EN; return 0; } static int lpc24xx_ssp_read(rtems_libi2c_bus_t *bus, unsigned char *in, int n) { return lpc24xx_ssp_read_write(bus, in, NULL, n); } static int lpc24xx_ssp_write( rtems_libi2c_bus_t *bus, unsigned char *out, int n ) { return lpc24xx_ssp_read_write(bus, NULL, out, n); } static int lpc24xx_ssp_ioctl(rtems_libi2c_bus_t *bus, int cmd, void *arg) { int rv = -1; const rtems_libi2c_tfr_mode_t *tm = (const rtems_libi2c_tfr_mode_t *) arg; rtems_libi2c_read_write_t *rw = (rtems_libi2c_read_write_t *) arg; rtems_libi2c_read_write_async_t *rwa = (rtems_libi2c_read_write_async_t *) arg; switch (cmd) { case RTEMS_LIBI2C_IOCTL_READ_WRITE: rv = lpc24xx_ssp_read_write(bus, rw->rd_buf, rw->wr_buf, rw->byte_cnt); break; case RTEMS_LIBI2C_IOCTL_READ_WRITE_ASYNC: rv = lpc24xx_ssp_read_write_async( bus, rwa->rd_buf, rwa->wr_buf, rwa->byte_cnt, rwa->done, rwa->arg ); break; case RTEMS_LIBI2C_IOCTL_SET_TFRMODE: rv = lpc24xx_ssp_set_transfer_mode(bus, tm); break; default: rv = -RTEMS_NOT_DEFINED; break; } return rv; } static const rtems_libi2c_bus_ops_t lpc24xx_ssp_ops = { .init = lpc24xx_ssp_init, .send_start = lpc24xx_ssp_send_start, .send_stop = lpc24xx_ssp_send_stop, .send_addr = lpc24xx_ssp_send_addr, .read_bytes = lpc24xx_ssp_read, .write_bytes = lpc24xx_ssp_write, .ioctl = lpc24xx_ssp_ioctl }; static lpc24xx_ssp_bus_entry lpc24xx_ssp_bus_table [LPC24XX_SSP_NUMBER] = { { /* SSP 0 */ .bus = { .ops = &lpc24xx_ssp_ops, .size = sizeof(lpc24xx_ssp_bus_entry) }, .regs = (volatile lpc24xx_ssp *) SSP0_BASE_ADDR, .clock = 0, .idle_char = 0xffffffff }, { /* SSP 1 */ .bus = { .ops = &lpc24xx_ssp_ops, .size = sizeof(lpc24xx_ssp_bus_entry) }, .regs = (volatile lpc24xx_ssp *) SSP1_BASE_ADDR, .clock = 0, .idle_char = 0xffffffff } }; rtems_libi2c_bus_t * const lpc24xx_ssp_0 = (rtems_libi2c_bus_t *) &lpc24xx_ssp_bus_table [0]; rtems_libi2c_bus_t * const lpc24xx_ssp_1 = (rtems_libi2c_bus_t *) &lpc24xx_ssp_bus_table [1];