/******************************************************************************* * arch/arm/src/lpc31xx/lpc31_i2c.c * * Author: David Hewson * * Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * 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. * *******************************************************************************/ /******************************************************************************* * Included Files *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "wdog.h" #include "chip.h" #include "up_arch.h" #include "up_internal.h" #include "lpc31_i2c.h" #include "lpc31_evntrtr.h" #include "lpc31_syscreg.h" /******************************************************************************* * Definitions *******************************************************************************/ /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define I2C_TIMEOUT ((20 * CLK_TCK) / 1000) /* 20 mS */ /**************************************************************************** * Private Data ****************************************************************************/ struct lpc31_i2cdev_s { struct i2c_dev_s dev; /* Generic I2C device */ struct i2c_msg_s msg; /* a single message for legacy read/write */ unsigned int base; /* Base address of registers */ uint16_t clkid; /* Clock for this device */ uint16_t rstid; /* Reset for this device */ uint16_t irqid; /* IRQ for this device */ sem_t mutex; /* Only one thread can access at a time */ sem_t wait; /* Place to wait for state machine completion */ volatile uint8_t state; /* State of state machine */ WDOG_ID timeout; /* watchdog to timeout when bus hung */ struct i2c_msg_s *msgs; /* remaining transfers - first one is in progress */ unsigned int nmsg; /* number of transfer remaining */ uint16_t header[3]; /* I2C address header */ uint16_t hdrcnt; /* number of bytes of header */ uint16_t wrcnt; /* number of bytes sent to tx fifo */ uint16_t rdcnt; /* number of bytes read from rx fifo */ }; #define I2C_STATE_DONE 0 #define I2C_STATE_START 1 #define I2C_STATE_HEADER 2 #define I2C_STATE_TRANSFER 3 static struct lpc31_i2cdev_s i2cdevices[2]; /**************************************************************************** * Private Functions ****************************************************************************/ static int i2c_interrupt (int irq, FAR void *context); static void i2c_progress (struct lpc31_i2cdev_s *priv); static void i2c_timeout (int argc, uint32_t arg, ...); static void i2c_reset (struct lpc31_i2cdev_s *priv); /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * I2C device operations ****************************************************************************/ static uint32_t i2c_setfrequency(FAR struct i2c_dev_s *dev, uint32_t frequency); static int i2c_setaddress(FAR struct i2c_dev_s *dev, int addr, int nbits); static int i2c_write(FAR struct i2c_dev_s *dev, const uint8_t *buffer, int buflen); static int i2c_read(FAR struct i2c_dev_s *dev, uint8_t *buffer, int buflen); static int i2c_transfer(FAR struct i2c_dev_s *dev, FAR struct i2c_msg_s *msgs, int count); struct i2c_ops_s lpc31_i2c_ops = { .setfrequency = i2c_setfrequency, .setaddress = i2c_setaddress, .write = i2c_write, .read = i2c_read, #ifdef CONFIG_I2C_TRANSFER .transfer = i2c_transfer #endif }; /******************************************************************************* * Name: up_i2cinitialize * * Description: * Initialise an I2C device * *******************************************************************************/ struct i2c_dev_s *up_i2cinitialize(int port) { struct lpc31_i2cdev_s *priv = &i2cdevices[port]; priv->base = (port == 0) ? LPC31_I2C0_VBASE : LPC31_I2C1_VBASE; priv->clkid = (port == 0) ? CLKID_I2C0PCLK : CLKID_I2C1PCLK; priv->rstid = (port == 0) ? RESETID_I2C0RST : RESETID_I2C1RST; priv->irqid = (port == 0) ? LPC31_IRQ_I2C0 : LPC31_IRQ_I2C1; sem_init (&priv->mutex, 0, 1); sem_init (&priv->wait, 0, 0); /* Enable I2C system clocks */ lpc31_enableclock (priv->clkid); /* Reset I2C blocks */ lpc31_softreset (priv->rstid); /* Soft reset the device */ i2c_reset (priv); /* Allocate a watchdog timer */ priv->timeout = wd_create(); DEBUGASSERT(priv->timeout != 0); /* Attach Interrupt Handler */ irq_attach (priv->irqid, i2c_interrupt); /* Enable Interrupt Handler */ up_enable_irq(priv->irqid); /* Install our operations */ priv->dev.ops = &lpc31_i2c_ops; return &priv->dev; } /******************************************************************************* * Name: up_i2cuninitalize * * Description: * Uninitialise an I2C device * *******************************************************************************/ void up_i2cuninitalize (struct lpc31_i2cdev_s *priv) { /* Disable All Interrupts, soft reset the device */ i2c_reset (priv); /* Detach Interrupt Handler */ irq_detach (priv->irqid); /* Reset I2C blocks */ lpc31_softreset (priv->rstid); /* Disable I2C system clocks */ lpc31_disableclock (priv->clkid); } /******************************************************************************* * Name: lpc31_i2c_setfrequency * * Description: * Set the frequence for the next transfer * *******************************************************************************/ static uint32_t i2c_setfrequency(FAR struct i2c_dev_s *dev, uint32_t frequency) { struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *) dev; uint32_t freq = lpc31_clkfreq (priv->clkid, DOMAINID_AHB0APB1); if (freq > 100000) { /* asymetric per 400Khz I2C spec */ putreg32 (((47 * freq) / (83 + 47)) / frequency, priv->base + LPC31_I2C_CLKHI_OFFSET); putreg32 (((83 * freq) / (83 + 47)) / frequency, priv->base + LPC31_I2C_CLKLO_OFFSET); } else { /* 50/50 mark space ratio */ putreg32 (((50 * freq) / 100) / frequency, priv->base + LPC31_I2C_CLKLO_OFFSET); putreg32 (((50 * freq) / 100) / frequency, priv->base + LPC31_I2C_CLKHI_OFFSET); } /* FIXME: This function should return the actual selected frequency */ return frequency; } /******************************************************************************* * Name: lpc31_i2c_setaddress * * Description: * Set the I2C slave address for a subsequent read/write * *******************************************************************************/ static int i2c_setaddress(FAR struct i2c_dev_s *dev, int addr, int nbits) { struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *) dev; DEBUGASSERT(dev != NULL); DEBUGASSERT(nbits == 7 || nbits == 10); priv->msg.addr = addr; priv->msg.flags = (nbits == 7) ? 0 : I2C_M_TEN; return OK; } /******************************************************************************* * Name: lpc31_i2c_write * * Description: * Send a block of data on I2C using the previously selected I2C * frequency and slave address. * *******************************************************************************/ static int i2c_write(FAR struct i2c_dev_s *dev, const uint8_t *buffer, int buflen) { struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *) dev; int ret; DEBUGASSERT (dev != NULL); priv->msg.flags &= ~I2C_M_READ; priv->msg.buffer = (uint8_t*)buffer; priv->msg.length = buflen; ret = i2c_transfer (dev, &priv->msg, 1); return ret == 1 ? OK : -ETIMEDOUT; } /******************************************************************************* * Name: lpc31_i2c_read * * Description: * Receive a block of data on I2C using the previously selected I2C * frequency and slave address. * *******************************************************************************/ static int i2c_read(FAR struct i2c_dev_s *dev, uint8_t *buffer, int buflen) { struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *) dev; int ret; DEBUGASSERT (dev != NULL); priv->msg.flags |= I2C_M_READ; priv->msg.buffer = buffer; priv->msg.length = buflen; ret = i2c_transfer (dev, &priv->msg, 1); return ret == 1 ? OK : -ETIMEDOUT; } /******************************************************************************* * Name: i2c_transfer * * Description: * Perform a sequence of I2C transfers * *******************************************************************************/ static int i2c_transfer (FAR struct i2c_dev_s *dev, FAR struct i2c_msg_s *msgs, int count) { struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *) dev; irqstate_t flags; int ret; sem_wait (&priv->mutex); flags = irqsave(); priv->state = I2C_STATE_START; priv->msgs = msgs; priv->nmsg = count; i2c_progress (priv); /* start a watchdog to timeout the transfer if * the bus is locked up... */ wd_start (priv->timeout, I2C_TIMEOUT, i2c_timeout, 1, (uint32_t)priv); while (priv->state != I2C_STATE_DONE) { sem_wait (&priv->wait); } wd_cancel (priv->timeout); ret = count - priv->nmsg; irqrestore (flags); sem_post (&priv->mutex); return ret; } /******************************************************************************* * Name: i2c_interrupt * * Description: * The I2C Interrupt Handler * *******************************************************************************/ static int i2c_interrupt (int irq, FAR void *context) { if (irq == LPC31_IRQ_I2C0) { i2c_progress (&i2cdevices[0]); } if (irq == LPC31_IRQ_I2C1) { i2c_progress (&i2cdevices[1]); } return OK; } /******************************************************************************* * Name: i2c_progress * * Description: * Progress any remaining I2C transfers * *******************************************************************************/ static void i2c_progress (struct lpc31_i2cdev_s *priv) { struct i2c_msg_s *msg; uint32_t stat, ctrl; stat = getreg32 (priv->base + LPC31_I2C_STAT_OFFSET); /* Were there arbitration problems? */ if ((stat & I2C_STAT_AFI) != 0) { /* Perform a soft reset */ i2c_reset (priv); /* FIXME: automatic retry? */ priv->state = I2C_STATE_DONE; sem_post (&priv->wait); return; } while (priv->nmsg > 0) { ctrl = I2C_CTRL_NAIE | I2C_CTRL_AFIE | I2C_CTRL_TDIE; msg = priv->msgs; switch (priv->state) { case I2C_STATE_START: if ((msg->flags & I2C_M_TEN) != 0) { priv->header[0] = I2C_TX_START | 0xF0 | ((msg->addr & 0x300) >> 7); priv->header[1] = msg->addr & 0xFF; priv->hdrcnt = 2; if (msg->flags & I2C_M_READ) { priv->header[2] = priv->header[0] | 1; priv->hdrcnt++; } } else { priv->header[0] = I2C_TX_START | (msg->addr << 1) | (msg->flags & I2C_M_READ); priv->hdrcnt = 1; } putreg32 (ctrl, priv->base + LPC31_I2C_CTRL_OFFSET); priv->state = I2C_STATE_HEADER; priv->wrcnt = 0; /* DROP THROUGH */ case I2C_STATE_HEADER: while ((priv->wrcnt != priv->hdrcnt) && (stat & I2C_STAT_TFF) == 0) { putreg32(priv->header[priv->wrcnt], priv->base + LPC31_I2C_TX_OFFSET); priv->wrcnt++; stat = getreg32 (priv->base + LPC31_I2C_STAT_OFFSET); } if (priv->wrcnt < priv->hdrcnt) { /* Enable Tx FIFO Not Full Interrupt */ putreg32 (ctrl | I2C_CTRL_TFFIE, priv->base + LPC31_I2C_CTRL_OFFSET); goto out; } priv->state = I2C_STATE_TRANSFER; priv->wrcnt = 0; priv->rdcnt = 0; /* DROP THROUGH */ case I2C_STATE_TRANSFER: if (msg->flags & I2C_M_READ) { while ((priv->rdcnt != msg->length) && (stat & I2C_STAT_RFE) == 0) { msg->buffer[priv->rdcnt] = getreg32 (priv->base + LPC31_I2C_RX_OFFSET); priv->rdcnt++; stat = getreg32 (priv->base + LPC31_I2C_STAT_OFFSET); } if (priv->rdcnt < msg->length) { /* Not all data received, fill the Tx FIFO with more dummies */ while ((priv->wrcnt != msg->length) && (stat & I2C_STAT_TFF) == 0) { if ((priv->wrcnt + 1) == msg->length && priv->nmsg == 1) putreg32 (I2C_TX_STOP, priv->base + LPC31_I2C_TX_OFFSET); else putreg32 (0, priv->base + LPC31_I2C_TX_OFFSET); priv->wrcnt++; stat = getreg32 (priv->base + LPC31_I2C_STAT_OFFSET); } if (priv->wrcnt < msg->length) { /* Enable Tx FIFO not full and Rx Fifo Avail Interrupts */ putreg32 (ctrl | I2C_CTRL_TFFIE | I2C_CTRL_RFDAIE, priv->base + LPC31_I2C_CTRL_OFFSET); } else { /* Enable Rx Fifo Avail Interrupts */ putreg32 (ctrl | I2C_CTRL_RFDAIE, priv->base + LPC31_I2C_CTRL_OFFSET); } goto out; } } else /* WRITE */ { while ((priv->wrcnt != msg->length) && (stat & I2C_STAT_TFF) == 0) { if ((priv->wrcnt + 1) == msg->length && priv->nmsg == 1) putreg32 (I2C_TX_STOP | msg->buffer[priv->wrcnt], priv->base + LPC31_I2C_TX_OFFSET); else putreg32 (msg->buffer[priv->wrcnt], priv->base + LPC31_I2C_TX_OFFSET); priv->wrcnt++; stat = getreg32 (priv->base + LPC31_I2C_STAT_OFFSET); } if (priv->wrcnt < msg->length) { /* Enable Tx Fifo not full Interrupt */ putreg32 (ctrl | I2C_CTRL_TFFIE, priv->base + LPC31_I2C_CTRL_OFFSET); goto out; } } /* Transfer completed, move onto the next one */ priv->state = I2C_STATE_START; if (--priv->nmsg == 0) { /* Final transfer, wait for Transmit Done Interrupt */ putreg32 (ctrl, priv->base + LPC31_I2C_CTRL_OFFSET); goto out; } priv->msgs++; break; } } out: if (stat & I2C_STAT_TDI) { putreg32 (I2C_STAT_TDI, priv->base + LPC31_I2C_STAT_OFFSET); /* You'd expect the NAI bit to be set when no acknowledge was * received - but it gets cleared whenever a write it done to * the TXFIFO - so we've gone and cleared it while priming the * rest of the transfer! */ if ((stat = getreg32 (priv->base + LPC31_I2C_TXFL_OFFSET)) != 0) { if (priv->nmsg == 0) priv->nmsg++; i2c_reset (priv); } priv->state = I2C_STATE_DONE; sem_post (&priv->wait); } } /******************************************************************************* * Name: i2c_timeout * * Description: * Watchdog timer for timeout of I2C operation * *******************************************************************************/ static void i2c_timeout (int argc, uint32_t arg, ...) { struct lpc31_i2cdev_s *priv = (struct lpc31_i2cdev_s *) arg; irqstate_t flags = irqsave(); if (priv->state != I2C_STATE_DONE) { /* If there's data remaining in the TXFIFO, then ensure at least * one transfer has failed to complete.. */ if (getreg32 (priv->base + LPC31_I2C_TXFL_OFFSET) != 0) { if (priv->nmsg == 0) priv->nmsg++; } /* Soft reset the USB controller */ i2c_reset (priv); /* Mark the transfer as finished */ priv->state = I2C_STATE_DONE; sem_post (&priv->wait); } irqrestore (flags); } /******************************************************************************* * Name: i2c_reset * * Description: * Perform a soft reset of the I2C controller * *******************************************************************************/ static void i2c_reset (struct lpc31_i2cdev_s *priv) { putreg32 (I2C_CTRL_RESET, priv->base + LPC31_I2C_CTRL_OFFSET); /* Wait for Reset to complete */ while ((getreg32 (priv->base + LPC31_I2C_CTRL_OFFSET) & I2C_CTRL_RESET) != 0) ; }