*
* \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>
+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.
*
+ * Note: this bit is set when the I2C state changes. However, entering
+ * state F8 does not set SI since there is nothing for an interrupt service
+ * routine to do in that case.
*/
-#if 0
- /* I2C 0 */
- #define I2C I2C0_MASTER_BASE
- #define SYSCTL_RCGC1_I2C SYSCTL_RCGC1_I2C0
- #define SYSCTL_RCGC2_GPIO SYSCTL_RCGC2_GPIOB
- #define GPIO_I2C_SCL_PIN GPIO_I2C0_SCL_PIN
- #define GPIO_I2C_SDA_PIN GPIO_I2C0_SDA_PIN
- #define GPIO_PORT_BASE GPIO_PORTB_BASE
-#else
- /* I2C 1 */
- #define I2C I2C1_MASTER_BASE
- #define SYSCTL_RCGC1_I2C SYSCTL_RCGC1_I2C1
- #define SYSCTL_RCGC2_GPIO SYSCTL_RCGC2_GPIOA
- #define GPIO_I2C_SCL_PIN GPIO_I2C1_SCL_PIN
- #define GPIO_I2C_SDA_PIN GPIO_I2C1_SDA_PIN
- #define GPIO_PORT_BASE GPIO_PORTA_BASE
-#endif
-
-
-/**
- * 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)
+#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.
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
- return true;
-}
-
+ // Set start and ack bit.
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_STA);
-/**
- * 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)
-{
-
- return true;
+ WAIT_SI(i2c);
}
-void i2c_builtin_stop(void)
+static void i2c_hw_stop(I2c *i2c)
{
+ /* 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);
}
-
-bool i2c_builtin_put(const uint8_t data)
+static void i2c_lpc2_putc(I2c *i2c, uint8_t data)
{
- (void)data;
- return true;
+ HWREG(i2c->hw->base + I2C_DAT_OFF) = data;
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_SIC);
+
+ WAIT_SI(i2c);
+
+ uint32_t status = HWREG(i2c->hw->base + I2C_STAT_OFF);
+
+
+ /* 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);
+ }
+
+ if (status == I2C_STAT_DATA_NACK)
+ {
+ LOG_ERR("Data NACK\n");
+ i2c->errors |= I2C_NO_ACK;
+ i2c_hw_stop(i2c);
+ }
+ else if ((status == I2C_STAT_ERROR) || (status == I2C_STAT_UNKNOW))
+ {
+ LOG_ERR("I2C error.\n");
+ i2c->errors |= I2C_ERR;
+ i2c_hw_stop(i2c);
+ }
}
-
-int i2c_builtin_get(bool ack)
+static uint8_t i2c_lpc2_getc(I2c *i2c)
{
- (void)ack;
- return 0;
+ /*
+ * Set ack bit if we want read more byte, otherwise
+ * we disable it
+ */
+ if (i2c->xfer_size > 1)
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_AA);
+ else
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_AAC);
+
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_SIC);
+
+ WAIT_SI(i2c);
+
+ 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 data;
+ }
+ else if (status == I2C_STAT_RDATA_NACK)
+ {
+ /*
+ * 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_ERROR) || (status == I2C_STAT_UNKNOW))
+ {
+ LOG_ERR("I2C error.\n");
+ i2c->errors |= I2C_ERR;
+ i2c_hw_stop(i2c);
+ }
+
+ return 0xFF;
}
-/*
- * With this function is allowed only the atomic write.
- */
-bool i2c_send(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;
-
-
- return true;
+ if (I2C_TEST_START(i2c->flags) == I2C_START_W)
+ {
+ ticks_t start = timer_clock();
+ while (true)
+ {
+ 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 (I2C_TEST_START(i2c->flags) == I2C_START_R)
+ {
+ 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);
+
+ WAIT_SI(i2c);
+
+ status = HWREG(i2c->hw->base + I2C_STAT_OFF);
+
+ if (status == I2C_STAT_SLAR_NACK)
+ {
+ LOG_ERR("SLAR NACK:%02x\n", status);
+ i2c->errors |= I2C_NO_ACK;
+ i2c_hw_stop(i2c);
+ }
+
+ if (status == I2C_STAT_ARB_LOST)
+ {
+ LOG_ERR("Arbitration lost\n");
+ i2c->errors |= I2C_ARB_LOST;
+ i2c_hw_stop(i2c);
+ }
+ }
+ else
+ {
+ ASSERT(0);
+ }
}
-/**
- * In order to read bytes from the i2c we should make some tricks.
- */
-bool i2c_recv(void *_buf, size_t count)
+static const I2cVT i2c_lpc_vt =
{
-
- return true;
-}
-
-MOD_DEFINE(i2c);
-
-#define I2C_PCONP PCONP_PCI2C0
-#define I2C_CONSET I20CONSET
-#define I2C_CONCLR I20CONCLR
-#define I2C_SCLH I20SCLH
-#define I2C_SCLL I20SCLL
-
+ .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)
{
- PCONP |= BV(I2C_PCONP); // enable I2C0 clock
+ i2c->hw = &i2c_lpc2_hw[dev];
+ i2c->vt = &i2c_lpc_vt;
- #if (CONFIG_I2C_FREQ > 400000)
- #error i2c frequency is to hight.
- #endif
+ /* Enable I2C clock */
+ PCONP |= i2c->hw->pconp;
- I2C_CONCLR = BV(I2CON_I2ENC);
+ ASSERT(clock <= 400000);
- // Bit Frequency = Fplk / (I2C_I2SCLH + I2C_I2SCLL)
- // value of I2SCLH and I2SCLL must be different
+ HWREG(i2c->hw->base + I2C_CONCLR_OFF) = BV(I2CON_I2ENC) | BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
- PCLKSEL0 |= I2C0_PCLK_DIV8;
- I2C_SCLH = (((CPU_FREQ / 8) / CONFIG_I2C_FREQ) / 2) + 1;
- I2C_SCLL = (((CPU_FREQ / 8) / CONFIG_I2C_FREQ) / 2);
+ /*
+ * Bit Frequency = Fplk / (I2C_I2SCLH + I2C_I2SCLL)
+ * value of I2SCLH and I2SCLL must be different
+ */
+ HWREG(SCB_BASE_ADDR + i2c->hw->pclksel) &= ~i2c->hw->pclk_mask;
+ HWREG(SCB_BASE_ADDR + i2c->hw->pclksel) |= i2c->hw->pclk_div;
- ASSERT(I2C_SCLH > 4 || I2C_SCLL > 4);
+ 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);
- //I2CState = I2C_IDLE;
+ 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)
- PINSEL0 |= BV(27) | BV(28);
+ /* 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);
- I2C_CONCLR |= BV(I2CON_STAC) | BV(I2CON_SIC) | BV(I2CON_AAC);
-
-kprintf("h%ld l%ld\n", I2C_SCLH, I2C_SCLL);
-
- MOD_INIT(i2c);
+ HWREG(i2c->hw->base + I2C_CONSET_OFF) = BV(I2CON_I2EN);
}