*
* \brief Driver for the LPC23xx I2C (implementation)
*
+ * \author Daniele Basile <asterix@develer.com>
*/
#include "cfg/cfg_i2c.h"
#include <cfg/debug.h>
#include <cfg/macros.h> // BV()
-#include <cfg/module.h>
#include <cpu/detect.h>
#include <cpu/irq.h>
+#include <cpu/power.h>
#include <drv/timer.h>
#include <drv/i2c.h>
-#include <drv/vic_lpc2.h> /* vic_handler_t */
#include <io/lpc23xx.h>
-/*
- *
- */
-#if 1
- /* I2C 0 */
- #define I2C_PCONP PCONP_PCI2C0
- #define I2C_CONSET I20CONSET
- #define I2C_CONCLR I20CONCLR
- #define I2C_SCLH I20SCLH
- #define I2C_SCLL I20SCLL
- #define I2C_STAT I20STAT
- #define I2C_DAT I20DAT
- #define I2C_PINSEL_PORT PINSEL1
- #define I2C_PINSEL I2C0_PINSEL
- #define I2C_PINSEL_MASK I2C0_PINSEL_MASK
- #define I2C_PCLKSEL PCLKSEL0
- #define I2C_PCLK_MASK I2C0_PCLK_MASK
- #define I2C_PCLK_DIV8 I2C0_PCLK_DIV8
-#else
- /* I2C 1 */
- #error
-#endif
-
-#define GET_STATUS() ((uint8_t)I2C_STAT)
+struct I2cHardware
+{
+ uint32_t base;
+ uint32_t pconp;
+ uint32_t pinsel_port;
+ uint32_t pinsel;
+ uint32_t pinsel_mask;
+ uint32_t pclksel;
+ uint32_t pclk_mask;
+ uint32_t pclk_div;
+};
+
/*
* Wait that SI bit is set.
*
* state F8 does not set SI since there is nothing for an interrupt service
* routine to do in that case.
*/
-#define WAIT_SI() while( !(I2C_CONSET & BV(I2CON_SI)) )
-
-static uint8_t i2c_builtin_start(void)
+#define WAIT_SI(i2c) \
+ do { \
+ ticks_t start = timer_clock(); \
+ while( !(HWREG(i2c->hw->base + I2C_CONSET_OFF) & BV(I2CON_SI)) ) \
+ { \
+ if (timer_clock() - start > ms_to_ticks(CONFIG_I2C_START_TIMEOUT)) \
+ { \
+ LOG_ERR("Timeout SI assert\n"); \
+ LOG_ERR("[%08lx]\n", HWREG(i2c->hw->base + I2C_STAT_OFF)); \
+ break; \
+ } \
+ cpu_relax(); \
+ } \
+ } while (0)
+
+static void i2c_hw_restart(I2c *i2c)
{
// Clear all pending flags.
- I2C_CONCLR = BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
// Set start and ack bit.
- I2C_CONSET = BV(I2CON_STA) | BV(I2CON_AA);
-
- WAIT_SI();
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_STA);
- return true;
+ WAIT_SI(i2c);
}
-/**
- * Send START condition and select slave for write.
- * \c id is the device id comprehensive of address left shifted by 1.
- * The LSB of \c id is ignored and reset to 0 for write operation.
- *
- * \return true on success, false otherwise.
- */
-bool i2c_builtin_start_w(uint8_t id)
+static void i2c_hw_stop(I2c *i2c)
{
- ticks_t start = timer_clock();
- while (i2c_builtin_start())
- {
- uint8_t status = GET_STATUS();
-
- /* Start status ok, set addres and the R/W bit */
- if ((status == I2C_STAT_SEND) || (status == I2C_STAT_RESEND))
- I2C_DAT = id & ~I2C_READBIT;
-
- /* Clear the start bit and clear the SI bit */
- I2C_CONCLR = BV(I2CON_SIC) | BV(I2CON_STAC);
-
- if (status == I2C_STAT_SLAW_ACK)
- return true;
- else if (status == I2C_STAT_ARB_LOST)
- {
- LOG_ERR("Arbitration lost\n");
- break;
- }
- else if (timer_clock() - start > ms_to_ticks(CONFIG_I2C_START_TIMEOUT))
- {
- LOG_ERR("Timeout on I2C START\n");
- break;
- }
- }
- return false;
+ /* Set the stop bit */
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_STO);
+ /* Clear pending flags */
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
}
-
-/**
- * Send START condition and select slave for read.
- * \c id is the device id comprehensive of address left shifted by 1.
- * The LSB of \c id is ignored and set to 1 for read operation.
- *
- * \return true on success, false otherwise.
- */
-bool i2c_builtin_start_r(uint8_t id)
+static void i2c_lpc2_putc(I2c *i2c, uint8_t data)
{
- if (i2c_builtin_start())
- {
- uint8_t status = GET_STATUS();
+ HWREG(i2c->hw->base + I2C_DAT_OFF) = data;
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_SIC);
- /* Start status ok, set addres and the R/W bit */
- if ((status == I2C_STAT_SEND) || (status == I2C_STAT_RESEND))
- I2C_DAT = id | I2C_READBIT;
+ WAIT_SI(i2c);
- /* Clear the start bit and clear the SI bit */
- I2C_CONCLR = BV(I2CON_SIC) | BV(I2CON_STAC);
+ uint32_t status = HWREG(i2c->hw->base + I2C_STAT_OFF);
- WAIT_SI();
-
- status = GET_STATUS();
-
- if (status == I2C_STAT_SLAR_ACK)
- return true;
-
- else if (status == I2C_STAT_SLAR_ACK)
- {
- LOG_ERR("SLAR NACK:%02x\n", status);
- }
- else if (status == I2C_STAT_ARB_LOST)
- {
- LOG_ERR("ARB Lost:%02x\n", status);
- }
+ /* Generate the stop if we finish to send all programmed bytes */
+ if (i2c->xfer_size == 1)
+ {
+ if (I2C_TEST_STOP(i2c->flags) == I2C_STOP)
+ i2c_hw_stop(i2c);
}
- return false;
-}
-
-
-void i2c_builtin_stop(void)
-{
- /* Set the stop bit */
- I2C_CONSET = BV(I2CON_STO);
- /* Clear pending flags */
- I2C_CONCLR = BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
-}
-
-
-bool i2c_builtin_put(const uint8_t data)
-{
- I2C_DAT = data;
- I2C_CONCLR = BV(I2CON_SIC);
-
- WAIT_SI();
- uint32_t status = GET_STATUS();
-
- if (status == I2C_STAT_DATA_ACK)
- return true;
- else if (status == I2C_STAT_DATA_NACK)
+ if (status == I2C_STAT_DATA_NACK)
{
LOG_ERR("Data NACK\n");
- return false;
+ i2c->errors |= I2C_NO_ACK;
+ i2c_hw_stop(i2c);
}
- else if (status == I2C_STAT_ERROR)
+ else if ((status == I2C_STAT_ERROR) || (status == I2C_STAT_UNKNOW))
{
LOG_ERR("I2C error.\n");
- return false;
+ i2c->errors |= I2C_ERR;
+ i2c_hw_stop(i2c);
}
- else if (status == I2C_STAT_UNKNOW)
- {
- LOG_ERR("I2C unable to read status.\n");
- return false;
- }
-
- return false;
}
-
-int i2c_builtin_get(bool ack)
+static uint8_t i2c_lpc2_getc(I2c *i2c)
{
-
/*
* Set ack bit if we want read more byte, otherwise
* we disable it
*/
- if (ack)
- I2C_CONSET = BV(I2CON_AA);
+ if (i2c->xfer_size > 1)
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_AA);
else
- I2C_CONCLR = BV(I2CON_AAC);
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_AAC);
- I2C_CONCLR = BV(I2CON_SIC);
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_SIC);
- WAIT_SI();
+ WAIT_SI(i2c);
- uint32_t status = GET_STATUS();
+ uint32_t status = HWREG(i2c->hw->base + I2C_STAT_OFF);
+ uint8_t data = (uint8_t)HWREG(i2c->hw->base + I2C_DAT_OFF);
if (status == I2C_STAT_RDATA_ACK)
- return (uint8_t)I2C_DAT;
+ {
+ return data;
+ }
else if (status == I2C_STAT_RDATA_NACK)
- return true;
- else if (status == I2C_STAT_ERROR)
{
- LOG_ERR("I2C error.\n");
- return EOF;
+ /*
+ * last byte to read generate the stop if
+ * required
+ */
+ if (I2C_TEST_STOP(i2c->flags) == I2C_STOP)
+ i2c_hw_stop(i2c);
+
+ return data;
}
- else if (status == I2C_STAT_UNKNOW)
+ else if ((status == I2C_STAT_ERROR) || (status == I2C_STAT_UNKNOW))
{
- LOG_ERR("I2C unable to read status.\n");
- return EOF;
+ LOG_ERR("I2C error.\n");
+ i2c->errors |= I2C_ERR;
+ i2c_hw_stop(i2c);
}
- return EOF;
+ return 0xFF;
}
-/*
- * With this function is allowed only the atomic write.
- */
-static bool i2c_send1(const void *_buf, size_t count)
+static void i2c_lpc2_start(struct I2c *i2c, uint16_t slave_addr)
{
- const uint8_t *buf = (const uint8_t *)_buf;
- uint8_t status = 0;
-
- while (count)
+ if (I2C_TEST_START(i2c->flags) == I2C_START_W)
{
- I2C_DAT = *buf++;
- I2C_CONCLR = BV(I2CON_SIC);
- count--;
-
- WAIT_SI();
-
- status = GET_STATUS();
-
- if (status == I2C_STAT_DATA_ACK)
- continue;
- else if (status == I2C_STAT_DATA_NACK)
- {
- LOG_ERR("Data NACK\n");
- return false;
- }
- else if (status == I2C_STAT_ERROR)
+ ticks_t start = timer_clock();
+ while (true)
{
- LOG_ERR("I2C error.\n");
- return false;
+ i2c_hw_restart(i2c);
+
+ uint8_t status = HWREG(i2c->hw->base + I2C_STAT_OFF);
+
+ /* Start status ok, set addres and the R/W bit */
+ if ((status == I2C_STAT_SEND) || (status == I2C_STAT_RESEND))
+ HWREG(i2c->hw->base + I2C_DAT_OFF) = slave_addr & ~I2C_READBIT;
+
+ /* Clear the start bit and clear the SI bit */
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_SIC) | BV(I2CON_STAC);
+
+ if (status == I2C_STAT_SLAW_ACK)
+ break;
+
+ if (status == I2C_STAT_ARB_LOST)
+ {
+ LOG_ERR("Arbitration lost\n");
+ i2c->errors |= I2C_ARB_LOST;
+ i2c_hw_stop(i2c);
+ }
+
+ if (timer_clock() - start > ms_to_ticks(CONFIG_I2C_START_TIMEOUT))
+ {
+ LOG_ERR("Timeout on I2C START\n");
+ i2c->errors |= I2C_NO_ACK;
+ i2c_hw_stop(i2c);
+ break;
+ }
}
- else if (status == I2C_STAT_UNKNOW)
- {
- LOG_ERR("I2C unable to read status.\n");
- return false;
- }
-
}
+ else if (I2C_TEST_START(i2c->flags) == I2C_START_R)
+ {
+ i2c_hw_restart(i2c);
- return true;
-}
-
-/**
- * In order to read bytes from the i2c we should make some tricks.
- */
-static bool i2c_recv1(void *_buf, size_t count)
-{
- uint8_t *buf = (uint8_t *)_buf;
- uint8_t status = GET_STATUS();
-
- /* Ready for read */
- I2C_CONSET = BV(I2CON_AA);
- I2C_CONCLR = BV(I2CON_SIC);
-
- WAIT_SI();
+ uint8_t status = HWREG(i2c->hw->base + I2C_STAT_OFF);
- while (count)
- {
- *buf++ = I2C_DAT;
- /*
- * Set ack bit if we want read more byte, otherwise
- * we disable it
- */
- if (count > 1)
- I2C_CONSET = BV(I2CON_AA);
- else
- I2C_CONCLR = BV(I2CON_AAC);
+ /* Start status ok, set addres and the R/W bit */
+ if ((status == I2C_STAT_SEND) || (status == I2C_STAT_RESEND))
+ HWREG(i2c->hw->base + I2C_DAT_OFF) = slave_addr | I2C_READBIT;
- I2C_CONCLR = BV(I2CON_SIC);
- count--;
+ /* Clear the start bit and clear the SI bit */
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_SIC) | BV(I2CON_STAC);
- WAIT_SI();
+ WAIT_SI(i2c);
- status = GET_STATUS();
+ status = HWREG(i2c->hw->base + I2C_STAT_OFF);
- if (status == I2C_STAT_RDATA_ACK)
- continue;
- else if (status == I2C_STAT_RDATA_NACK)
- return true;
- else if (status == I2C_STAT_ERROR)
+ if (status == I2C_STAT_SLAR_NACK)
{
- LOG_ERR("I2C error.\n");
- return false;
+ LOG_ERR("SLAR NACK:%02x\n", status);
+ i2c->errors |= I2C_NO_ACK;
+ i2c_hw_stop(i2c);
}
- else if (status == I2C_STAT_UNKNOW)
+
+ if (status == I2C_STAT_ARB_LOST)
{
- LOG_ERR("I2C unable to read status.\n");
- return false;
+ LOG_ERR("Arbitration lost\n");
+ i2c->errors |= I2C_ARB_LOST;
+ i2c_hw_stop(i2c);
}
}
-
- return true;
+ else
+ {
+ ASSERT(0);
+ }
}
-MOD_DEFINE(i2c);
+static const I2cVT i2c_lpc_vt =
+{
+ .start = i2c_lpc2_start,
+ .getc = i2c_lpc2_getc,
+ .putc = i2c_lpc2_putc,
+ .write = i2c_genericWrite,
+ .read = i2c_genericRead,
+};
+
+static struct I2cHardware i2c_lpc2_hw[] =
+{
+ { /* I2C0 */
+ .base = I2C0_BASE_ADDR,
+ .pconp = BV(PCONP_PCI2C0),
+ .pinsel_port = PINSEL1_OFF,
+ .pinsel = I2C0_PINSEL,
+ .pinsel_mask = I2C0_PINSEL_MASK,
+ .pclksel = PCLKSEL0_OFF,
+ .pclk_mask = I2C0_PCLK_MASK,
+ .pclk_div = I2C0_PCLK_DIV8,
+ },
+ { /* I2C1 */
+ .base = I2C1_BASE_ADDR,
+ .pconp = BV(PCONP_PCI2C1),
+ .pinsel_port = PINSEL0_OFF,
+ .pinsel = I2C1_PINSEL,
+ .pinsel_mask = I2C1_PINSEL_MASK,
+ .pclksel = PCLKSEL1_OFF,
+ .pclk_mask = I2C1_PCLK_MASK,
+ .pclk_div = I2C1_PCLK_DIV8,
+ },
+ { /* I2C2 */
+ .base = I2C2_BASE_ADDR,
+ .pconp = BV(PCONP_PCI2C2),
+ .pinsel_port = PINSEL0_OFF,
+ .pinsel = I2C2_PINSEL,
+ .pinsel_mask = I2C2_PINSEL_MASK,
+ .pclksel = PCLKSEL1_OFF,
+ .pclk_mask = I2C2_PCLK_MASK,
+ .pclk_div = I2C2_PCLK_DIV8,
+ },
+};
/**
* Initialize I2C module.
*/
-void i2c_builtin_init(void)
+void i2c_hw_init(I2c *i2c, int dev, uint32_t clock)
{
+ i2c->hw = &i2c_lpc2_hw[dev];
+ i2c->vt = &i2c_lpc_vt;
+
/* Enable I2C clock */
- PCONP |= BV(I2C_PCONP);
+ PCONP |= i2c->hw->pconp;
- #if (CONFIG_I2C_FREQ > 400000)
- #error i2c frequency is to hight.
- #endif
+ ASSERT(clock <= 400000);
- I2C_CONCLR = BV(I2CON_I2ENC) | BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_I2ENC) | BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
/*
* Bit Frequency = Fplk / (I2C_I2SCLH + I2C_I2SCLL)
* value of I2SCLH and I2SCLL must be different
*/
- I2C_PCLKSEL &= ~I2C_PCLK_MASK;
- I2C_PCLKSEL |= I2C_PCLK_DIV8;
+ HWREG(SCB_BASE_ADDR + i2c->hw->pclksel) &= ~i2c->hw->pclk_mask;
+ HWREG(SCB_BASE_ADDR + i2c->hw->pclksel) |= i2c->hw->pclk_div;
- I2C_SCLH = (((CPU_FREQ / 8) / CONFIG_I2C_FREQ) / 2) + 1;
- I2C_SCLL = (((CPU_FREQ / 8) / CONFIG_I2C_FREQ) / 2);
+ HWREG(i2c->hw->base + I2C_SCLH_OFF) = (((CPU_FREQ / 8) / clock) / 2) + 1;
+ HWREG(i2c->hw->base + I2C_SCLL_OFF) = (((CPU_FREQ / 8) / clock) / 2);
- ASSERT(I2C_SCLH > 4 || I2C_SCLL > 4);
+ ASSERT(HWREG(i2c->hw->base + I2C_SCLH_OFF) > 4);
+ ASSERT(HWREG(i2c->hw->base + I2C_SCLL_OFF) > 4);
- /* Assign pins to SCL and SDA (P0_27, P0_28) */
- I2C_PINSEL_PORT &= ~I2C_PINSEL_MASK;
- I2C_PINSEL_PORT |= I2C_PINSEL;
+ /* Assign pins to SCL and SDA */
+ HWREG(PINSEL_BASE_ADDR + i2c->hw->pinsel_port) &= ~i2c->hw->pinsel_mask;
+ HWREG(PINSEL_BASE_ADDR + i2c->hw->pinsel_port) |= i2c->hw->pinsel;
// Enable I2C
- I2C_CONSET = BV(I2CON_I2EN);
-
- MOD_INIT(i2c);
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_I2EN);
}