eeprom support development ongoing
authorNathael Pajani <nathael.pajani@ed3l.fr>
Thu, 7 Mar 2013 11:00:49 +0000 (12:00 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:03 +0000 (17:03 +0100)
drivers/eeprom.c [new file with mode: 0644]
drivers/i2c.c [new file with mode: 0644]
include/drivers/i2c.h [new file with mode: 0644]

diff --git a/drivers/eeprom.c b/drivers/eeprom.c
new file mode 100644 (file)
index 0000000..4927b3d
--- /dev/null
@@ -0,0 +1,150 @@
+/****************************************************************************
+ *   drivers/eeprom.c
+ *
+ *
+ * Copyright 2012 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#include <stdint.h>
+
+#include "core/lpc_regs_12xx.h"
+#include "core/lpc_core_cm0.h"
+#include "core/system.h"
+#include "lib/string.h"
+#include "drivers/i2c.h"
+
+
+/***************************************************************************** */
+/*          Read and Write for module eeprom                                   */
+/***************************************************************************** */
+/* Config */
+/* Small eeprom : up to 2k bytes. These use a segment address on the lower three bits
+ *   of the address byte, and thus reply on 8 consecutive addresses */
+#define EEPROM_ID_SMALL_ADDR  0xA0
+#define EEPROM_ID_SMALL_I2C_SIZE  1024
+#define EEPROM_ID_SMALL_PAGE_SIZE 16
+/* Big eeprom : from 4k bytes and above : These use two address bytes, and the three
+ *   physical address pins are used to set the chip address. On DTPlug modules they should
+ *   have address 0xA2. */
+#define EEPROM_ID_BIG_ADDR  0xA2
+#define EEPROM_ID_BIG_I2C_SIZE  16*1024
+#define EEPROM_ID_BIG_PAGE_SIZE 64
+
+#define EEPROM_TYPE_SMALL  EEPROM_ID_SMALL_ADDR
+#define EEPROM_TYPE_BIG    EEPROM_ID_BIG_ADDR
+static uint8_t eeprom_type = 0; /* Will in fact store the eeprom address */
+
+/* Detect the eeprom size */
+int eeprom_detect(void)
+{
+       int ret = 0;
+       char cmd_buf[1] = { EEPROM_ID_SMALL_ADDR, };
+
+       /* Look for small eeproms first, only these would answer on EEPROM_ID_SMALL_ADDR */
+       ret = i2c_read(cmd_buf, 1, NULL, NULL, 0);
+    if (ret == 0) {
+        return EEPROM_TYPE_SMALL;
+    }
+    /* No small eeprom ... look for big ones */
+    cmd_buf[0] = EEPROM_ID_BIG_ADDR;
+    ret = i2c_read(cmd_buf, 1, NULL, NULL, 0);
+    if (ret == 0) {
+        return EEPROM_TYPE_BIG;
+    }
+
+       if (ret > 0) {
+               return = -1;
+    } else if (ret == -EREMOTEIO) {
+        return EEPROM_TYPE_NONE; /* No module */
+    }
+    return ret; /* Error or module size */
+}
+
+/* EEPROM Read
+ * Performs a non-blocking read on the eeprom.
+ *   address : data offset in eeprom.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes read. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EFAULT : address above eeprom size
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+#define CMD_BUF_SIZE 4
+int eeprom_read(uint32_t offset, void *buf, size_t count)
+{
+    int ret = 0;
+    char cmd_buf[CMD_BUF_SIZE] = { EEPROM_ID_BIG_ADDR, 0, 0, (EEPROM_ID_BIG_ADDR | 0x01), };
+    char ctrl_buf[CMD_BUF_SIZE] = { I2C_CONT, I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, };
+
+       if (eeprom_type <= 0) {
+               eeprom_type = eeprom_detect();
+               if (eeprom_type <= 0) {
+                       return -EBADFD;
+               }
+       }
+
+    /* Read the requested data */
+    switch (eeprom_type) {
+        case EEPROM_TYPE_SMALL:
+            cmd_buf[0] = EEPROM_ID_SMALL_ADDR | ((offset & 0x700) >> 7);
+            cmd_buf[1] = offset & 0xFF;
+            cmd_buf[2] = EEPROM_ID_SMALL_ADDR | 0x01;
+            ret = i2c_read(cmd_buf, CMD_BUF_SIZE - 1, ctrl_buf + 1, buf, count);
+            break;
+        case EEPROM_TYPE_BIG:
+            cmd_buf[1] = ((offset & 0xFF00) >> 8);
+            cmd_buf[2] = offset & 0xFF;
+            ret = i2c_read(cmd_buf, CMD_BUF_SIZE, ctrl_buf, buf, count);
+            break;
+        default:
+            ret = -1;
+            break;
+    }
+
+    return ret;
+}
+
+
+/* EEPROM Write
+ * Performs a non-blocking write on the eeprom.
+ *   address : data offset in eeprom.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes written. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EFAULT : address above eeprom size
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+#define CMD_SIZE_SMALL 2
+#define CMD_SIZE_BIG 3
+#define MAX_CMD_SIZE CMD_SIZE_BIG
+#define EEPROM_ID_MAX_PAGE_SIZE EEPROM_ID_BIG_PAGE_SIZE
+int eeprom_write(uint32_t offset, const void *buf, size_t count)
+{
+}
+
+
diff --git a/drivers/i2c.c b/drivers/i2c.c
new file mode 100644 (file)
index 0000000..2efe749
--- /dev/null
@@ -0,0 +1,519 @@
+/****************************************************************************
+ *   drivers/i2c.c
+ *
+ *
+ * Copyright 2012 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#include <stdint.h>
+
+#include "core/lpc_regs_12xx.h"
+#include "core/lpc_core_cm0.h"
+#include "core/system.h"
+#include "lib/string.h"
+#include "drivers/i2c.h"
+
+
+/*
+ * I2C Bus structure
+ *
+ * clock : current i2c clock.
+ * bus_lock : lock to take hold of the bus.
+ * state : global state of the i2c engine.
+ * master_status : status returned by i2c block as found in "status" register.
+ *
+ * repeated_start_restart : This buffer, if used, must have the SAME size as out_buff.
+ *           Then, instead of simply moving to the next byte, the corresponding condition
+ *           will be executed : I2C_CONT, I2C_DO_REPEATED_START, I2C_DO_STOP_START or I2C_STOP.
+ * restart_after_addr : Can be used instead of repeated_start_restart buffer to perform a
+ *           single repeated start or stop/start sequence after first adress got sent.
+ * restart_after_data : Can be used instead of repeated_start_restart buffer to perform a
+ *           single repeated start or stop/start sequence after given data byte.
+ */
+struct i2c_bus {
+       volatile struct lpc_i2c* regs;
+       volatile uint32_t clock;
+       volatile uint32_t bus_lock;
+       volatile uint32_t state;
+       volatile uint32_t master_status;
+       volatile uint32_t slave_status;
+       volatile uint32_t timeout;
+
+       volatile const char* out_buff;
+       volatile const char* repeated_start_restart;
+       volatile uint32_t write_length;
+       volatile uint32_t write_index;
+       volatile uint32_t restart_after_addr;
+       volatile uint32_t restart_after_data;
+
+       volatile char* in_buff;
+       volatile uint32_t read_length;
+       volatile uint32_t read_index;
+};
+
+static struct i2c_bus mod_i2c;
+
+
+/* FIXME : Should add a field "what to do on addr NACK" to i2c_bus structure, and use
+   it with the timeout to create a retry mechanism */
+/* FIXME : For case 58 ... What would be the use of a restart ?? perform periodic reads ? */
+/* FIXME : Implement Slave when arbitration lost ? */
+
+
+
+/* I2C Interrupt handler */
+/* Actual version will stop on NACKs */
+/* See LPC1764 user's manual UM10360 on page 457 (19.9.5) for details on I2C State machine */
+void I2C_Handler(void)
+{
+       uint8_t status;
+       struct i2c_bus* i2c = &mod_i2c;
+
+       i2c->timeout = 0;
+
+       /* this handler deals with master read and master write only */
+       status = (i2c->regs->status & 0xFF);
+       i2c->master_status = status; /* Store current status */
+
+       /* I2C State Machine ! */
+       switch (status) {
+
+       /* All modes */
+               case 0x00: /* Bus Error. Enter not addressed Slave mode and release bus. */
+                       i2c->regs->ctrl_set = (I2C_ASSERT_ACK | I2C_STOP_FLAG);
+                       i2c->state = I2C_BUS_ERROR;
+                       break;
+               case 0x08:  /* A START condition has been transmitted. */
+                       i2c->write_index = 0;
+                       if (i2c->write_length != 0) {
+                               /* Send Slave Address (SLA)
+                                * Depending on R/W bit, Master Receive or master Transmit mode will be enterred. */
+                               i2c->regs->data = i2c->out_buff[i2c->write_index++];
+                               i2c->regs->ctrl_clear = I2C_START_FLAG;
+                       } else {
+                               i2c->regs->ctrl_clear = I2C_START_FLAG;
+                               i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                               i2c->state = I2C_NO_DATA;
+                       }
+                       break;
+               case 0x10:  /* A repeated START has been transmitted. */
+                       /* Setting read_index to 0 is usefull only if next data byte is
+                        *    Slave Address + Read (SLA + R), but it's OK if we will write too. */
+                       i2c->read_index = 0;
+                       /* Send Slave Address and Read/Write bit (SLA + R/W)
+                        * Depending on R/W bit, Master Receive or master Transmit mode will be enterred. */
+                       i2c->regs->data = i2c->out_buff[i2c->write_index++];
+                       /* FIXME : Shouldn't this be done only in receiver mode (SLA + R) ? */
+                       i2c->regs->ctrl_clear = I2C_START_FLAG;
+                       break;
+               case 0x38: /* Arbitration lost. We don't deal with multiple master situation */
+                       /* FIXME : We have two option :
+                        *  - do nothing, which will release the bus. Will lead to "Not Addressed Slave" state.
+                        *      This options allows handling of multiple master topologies.
+                        *  - Set start flag, and a start condition will be transmitted when bus becomes free.
+                        */
+                       /* We choose to do nothing, we ARE the true master after all ! */
+                       i2c->state = I2C_ARBITRATION_LOST;
+                       break;
+
+       /* Master Transmitter Mode Only */
+               case 0x18:  /* Address + Write transmitted and ACK'ed */
+                       if (i2c->write_length == 1) { /* Nothing more to send, we must stop. */
+                               /* This one is used to probe devices, or wait for completion */
+                               i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                               i2c->state = I2C_NO_DATA;
+                       } else {
+                               uint32_t condition = i2c->restart_after_addr;
+                               if (i2c->repeated_start_restart)
+                                       condition = i2c->repeated_start_restart[i2c->write_index - 1];
+                               switch (condition) {
+                                       case I2C_CONT: /* Send next data byte */
+                                               i2c->regs->data = i2c->out_buff[i2c->write_index++];
+                                               break;
+                                       case I2C_DO_REPEATED_START: /* Send repeated start condition */
+                                               i2c->regs->ctrl_set = I2C_START_FLAG;
+                                               break;
+                                       case I2C_DO_STOP_START: /* Send STOP / START condition */
+                                               i2c->regs->ctrl_set = I2C_STOP_FLAG | I2C_START_FLAG;
+                                               break;
+                                       case I2C_STOP: /* Send STOP condition */
+                                               i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                                               i2c->state = I2C_NO_DATA;
+                                               break;
+                               }
+                               if (i2c->restart_after_addr)
+                                       i2c->restart_after_addr = 0; /* This one must be defused once used */
+                       }
+                       break;
+               case 0x20:  /* NACK on Address + Write (SLA + W) */
+               case 0x30:  /* NACK on Data byte */
+                       /* FIXME : We have three other options : Resend / Repeated START / STOP + START
+                        *     If I'm right, Resend would require moving write_index back and using same data
+                        *     Repeated START ? ignore the NACK and move to reading data ?
+                        *     STOP + START ? Start over ? write_index will be set to 0 in I2C_STARTED case (0x08)
+                        * Sending a STOP condition will end transactions and let the driver take the decision.
+                        */
+                       i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                       i2c->state = I2C_NACK;
+                       break;
+               case 0x28: /* Data byte has been transmitted and ACK received */
+                       if (i2c->write_index < i2c->write_length) {
+                               /* More to write, but what do we do ? */
+                               uint32_t condition = I2C_CONT;
+                               if (i2c->restart_after_data == i2c->write_index)
+                                       condition = I2C_DO_REPEATED_START;
+                               else if (i2c->repeated_start_restart)
+                                       condition = i2c->repeated_start_restart[i2c->write_index - 1];
+                               switch (condition) {
+                                       case I2C_CONT: /* Send next data byte */
+                                               i2c->regs->data = i2c->out_buff[i2c->write_index++];
+                                               break;
+                                       case I2C_DO_REPEATED_START: /* Send repeated start condition */
+                                               i2c->regs->ctrl_set = I2C_START_FLAG;
+                                               break;
+                                       case I2C_DO_STOP_START: /* Send STOP / START condition */
+                                               i2c->regs->ctrl_set = I2C_STOP_FLAG | I2C_START_FLAG;
+                                               break;
+                                       case I2C_STOP: /* Send STOP condition */
+                                               /* Hey, why sending a STOP and terminate communication if we are not at
+                                                * the end of the write buffer ?? */
+                                               i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                                               i2c->state = I2C_OK;
+                                               break;
+                               }
+                       } else {
+                               if (i2c->read_length != 0) { /* Anything to read ? */
+                                       i2c->regs->ctrl_set = I2C_START_FLAG;
+                               } else {
+                                       i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                                       i2c->state = I2C_OK;
+                               }
+                       }
+                       break;
+
+       /* Master Receiver Mode Only */
+               case 0x40:  /* Address + Read transmitted and ACK'ed */
+                       if ((i2c->read_index + 1) < i2c->read_length) {
+                               /* Will go to State 0x50 (assert ACK after data is received) */
+                               i2c->regs->ctrl_set = I2C_ASSERT_ACK;
+                       } else {
+                               /* Will go to State 0x58 (NACK after data is received) */
+                               i2c->regs->ctrl_clear = I2C_ASSERT_ACK;
+                       }
+                       break;
+
+               case 0x48:  /* NACK on Address + Read (SLA + R) */
+                       /* FIXME : We have two other options : Repeated START / STOP + START */
+                       i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                       i2c->state = I2C_NACK;
+                       break;
+
+               case 0x50: /* Data byte has been received and ACK sent */
+                       i2c->in_buff[i2c->read_index++] = i2c->regs->data;
+                       if ((i2c->read_index + 1) < i2c->read_length) {
+                               /* assert ACK after data is received, requesting next Data from slave */
+                               i2c->regs->ctrl_set = I2C_ASSERT_ACK;
+                       } else {
+                               /* Assert NACK on last byte, telling the slave to stop transmitting data
+                                  and release the I2C Bus */
+                               i2c->regs->ctrl_clear = I2C_ASSERT_ACK;
+                       }
+                       break;
+
+               case 0x58: /* Data byte has been received and NACK "sent" */
+                       /* This tells the slave it was the last byte. We should be done. */
+                       i2c->in_buff[i2c->read_index++] = i2c->regs->data;
+                       /* FIXME : We have two other options : Repeated START or STOP + START,
+                        *    but what for ? periodic reads ? */
+                       i2c->regs->ctrl_set = I2C_STOP_FLAG;
+                       i2c->state = I2C_OK;
+                       break;
+
+               default:
+                       i2c->state = I2C_ERROR_UNKNOWN;
+                       break;
+       }
+
+       /* Clear interrupt flag. This has to be done last. */
+       i2c->regs->ctrl_clear = I2C_INTR_FLAG;
+       return;
+}
+
+
+
+
+/***************************************************************************** */
+/*                        I2C access                                           */
+/***************************************************************************** */
+/* Read
+ * Performs a non-blocking read on the module's i2c bus.
+ *   cmd_buf : buffer containing all control byte to be sent on the i2c bus
+ *   cmd_size : size of the cmd_buf command buffer
+ *   ctrl_buf : buffer containing action to be done after sending, like repeated START conditions
+ *         if not NULL, ctrl_buf has the same size as cmd_buf
+ *   inbuff : the buffer where read data will be put. May be NULL if count is 0.
+ *   count : the number of bytes to be read.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes read. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EBADFD : Device not initialized (-77)
+ *   -EBUSY : Device or ressource Busy or Arbitration lost (-16)
+ *   -EAGAIN : Device already in use (-11)
+ *   -EINVAL : Invalid argument (no cmd_buf, or no inbuff when count > 0) (-22)
+ *   -EREMOTEIO : Device did not acknowledge (-121)
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine (-5)
+ */
+int i2c_read(const void *cmd_buf, size_t cmd_size, const void* ctrl_buf, void* inbuff, size_t count)
+{
+       int ret = 0;
+
+       /* Checks */
+       if (mod_i2c.regs != LPC_I2C0)
+               return -EBADFD;
+       if (cmd_buf == NULL)
+               return -EINVAL;
+       if ((inbuff == NULL) && (count > 0))
+               return -EINVAL;
+
+       /* Lock acquire */
+       if (sync_lock_test_and_set(&mod_i2c.bus_lock, 1) == 1)
+               return -EAGAIN;
+
+       if (mod_i2c.state == I2C_BUSY) {
+               /* Someone uses the bus without hold of the bus lock ?? */
+               sync_lock_release(&mod_i2c.bus_lock);
+               return -EBUSY;
+       }
+       if (mod_i2c.state != I2C_OK) {
+               /* What should we do ??? someone failed to reset status ? */
+       }
+
+       /* Set up mod_i2c for read operation */
+       /* command (write) buffer */
+       mod_i2c.out_buff = cmd_buf;
+       mod_i2c.write_length = cmd_size;
+       /* control buffer, if any. Note that it's the only way to control
+        *   operations on modules i2C bus to simplify the interface */
+       mod_i2c.repeated_start_restart = ctrl_buf;
+       mod_i2c.restart_after_addr = I2C_CONT;
+       mod_i2c.restart_after_data = 0;
+       /* read buffer */
+       mod_i2c.in_buff = inbuff;
+       mod_i2c.read_length = count;
+
+       /* Start the process */
+       mod_i2c.state = I2C_BUSY;
+       mod_i2c.regs->ctrl_set = I2C_START_FLAG;
+       /* And wait for process completion */
+       do {} while (mod_i2c.state == I2C_BUSY);
+
+       /* Handle returned state (errors or OK) */
+       switch (mod_i2c.state) {
+               case I2C_OK:
+               case I2C_NO_DATA:
+                       ret = mod_i2c.read_index;
+                       break;
+               case I2C_NACK:
+                       ret = -EREMOTEIO;
+                       break;
+               case I2C_ARBITRATION_LOST:
+                       ret = -EBUSY;
+                       break;
+               case I2C_BUS_ERROR: /* This one is bad ... */
+               case I2C_ERROR_UNKNOWN:
+               default:
+                       ret = -EIO;
+                       break;
+       }
+
+       /* Force device to release the bus :
+        *    send a START followed by a STOP (initiate transmission with nul write_length) */
+       mod_i2c.state = I2C_BUSY;
+       mod_i2c.write_length = 0;
+       mod_i2c.regs->ctrl_set = I2C_START_FLAG;
+       do {} while (mod_i2c.state == I2C_BUSY);
+       mod_i2c.state = I2C_OK;
+
+       /* Release the lock */
+       sync_lock_release(&mod_i2c.bus_lock);
+
+       return ret;
+}
+
+/* Write
+ * Performs a non-blocking write on the module's i2c bus.
+ *   buf : buffer containing all byte to be sent on the i2c bus,
+ *         including conrtol bytes (address, offsets, ...)
+ *   count : the number of bytes to be sent, including address bytes and so on.
+ *   ctrl_buf : buffer containing action to be done after sending, like repeated START conditions
+ *         FIXME : note that STOP + START conditions are not allowed, the STOP would lead to sending
+ *         the first bytes of buf, creating an infinite loop.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes written. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+int i2c_write(const void *buf, size_t count, const void* ctrl_buf)
+{
+       int ret = 0;
+
+       /* Checks */
+       if (mod_i2c.regs != LPC_I2C0)
+               return -EBADFD;
+       if (buf == NULL)
+               return -EINVAL;
+
+       /* Lock acquire */
+       if (sync_lock_test_and_set(&mod_i2c.bus_lock, 1) == 1)
+               return -EAGAIN;
+
+       if (mod_i2c.state == I2C_BUSY) {
+               /* Someone uses the bus without hold of the bus lock ?? */
+               sync_lock_release(&mod_i2c.bus_lock);
+               return -EBUSY;
+       }
+       if (mod_i2c.state != I2C_OK) {
+               /* What should we do ??? someone failed to reset status ? */
+       }
+
+       /* Clear read information to prevent entering master receiver states */
+       mod_i2c.read_length = 0;
+       mod_i2c.in_buff = NULL;
+       mod_i2c.state = I2C_BUSY;
+       /* Set up mod_i2c for write operation */
+       mod_i2c.out_buff = buf;
+       mod_i2c.write_length = count;
+       /* control buffer, if any. Note that it's the only way to control
+        *   operations on modules i2C bus to simplify the interface */
+       mod_i2c.restart_after_addr = I2C_CONT;
+       mod_i2c.repeated_start_restart = ctrl_buf;
+       mod_i2c.restart_after_data = 0;
+
+       /* Start the process */
+       mod_i2c.state = I2C_BUSY;
+       mod_i2c.regs->ctrl_set = I2C_START_FLAG;
+       /* Wait for process completion */
+       do {} while (mod_i2c.state == I2C_BUSY);
+
+       /* Handle returned state (errors or OK) */
+       switch (mod_i2c.state) {
+               case I2C_OK:
+               case I2C_NO_DATA:
+                       ret = mod_i2c.write_length;
+                       break;
+               case I2C_NACK:
+                       ret = -EREMOTEIO;
+                       break;
+               case I2C_ARBITRATION_LOST:
+                       ret = -EBUSY;
+                       break;
+               case I2C_BUS_ERROR: /* This one is bad ... */
+               case I2C_ERROR_UNKNOWN:
+               default:
+                       ret = -EIO;
+                       break;
+       }
+
+       /* Force device to release the bus :
+        *    send a START followed by a STOP (initiate transmission with nul write_length) */
+       mod_i2c.state = I2C_BUSY;
+       mod_i2c.write_length = 0;
+       mod_i2c.regs->ctrl_set = I2C_START_FLAG;
+       do {} while (mod_i2c.state == I2C_BUSY);
+       mod_i2c.state = I2C_OK;
+
+       /* Release the lock */
+       sync_lock_release(&mod_i2c.bus_lock);
+
+       return ret;
+}
+
+
+
+/***************************************************************************** */
+/*                I2C Init                                                     */
+/***************************************************************************** */
+static void i2c_clock_on(uint32_t i2c_clk_freq)
+{
+       struct lpc_i2c* i2c = LPC_I2C0;
+       uint32_t main_clock = get_main_clock();
+       uint32_t scl_clk = 0;
+
+       /* Setup I2C clock */
+       scl_clk = (main_clock / i2c_clk_freq);
+       i2c->clk_duty_high = (scl_clk / 2);
+       i2c->clk_duty_low = (scl_clk - i2c->clk_duty_high);
+}
+
+void set_i2c_pins(void)
+{
+       struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
+       struct lpc_io_control* ioctrl = LPC_IO_CONTROL;
+
+       /* Make sure IO_Config is clocked */
+       sys_ctrl->sys_AHB_clk_ctrl |= LPC_SYS_ABH_CLK_CTRL_IO_CONFIG;
+       ioctrl->pio0_10 = LPC_IO_FUNC_ALT(2) | LPC_IO_OPEN_DRAIN_ENABLE; /* True open drai */
+       ioctrl->pio0_11 = LPC_IO_FUNC_ALT(2) | LPC_IO_OPEN_DRAIN_ENABLE;
+       /* Config done, power off IO_CONFIG block */
+       sys_ctrl->sys_AHB_clk_ctrl &= ~LPC_SYS_ABH_CLK_CTRL_IO_CONFIG;
+}
+
+void i2c_on(uint32_t i2c_clk_freq)
+{
+       struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
+       struct lpc_i2c* i2c = LPC_I2C0;
+
+       NVIC_DisableIRQ(I2C0_IRQ);
+       /* Power on I2C 0 block */
+       sys_ctrl->sys_AHB_clk_ctrl |= LPC_SYS_ABH_CLK_CTRL_I2C;
+       /* Set clock */
+       i2c_clock_on(i2c_clk_freq);
+       mod_i2c.clock = i2c_clk_freq;
+       /* Set pins function for I2C 0 */
+       set_i2c_pins();
+       /* Initialize i2c_bus struct */
+       memset(&mod_i2c, 0, sizeof(struct i2c_bus));
+       mod_i2c.regs = (struct lpc_i2c*)LPC_I2C0;
+       mod_i2c.state = I2C_OK;
+       /* Enable I2C */
+       i2c->ctrl_set = 0x40; /* Fixme, if enabling slave functions, write 0x44 instead */
+       /* And enable interrupts */
+       NVIC_EnableIRQ(I2C0_IRQ);
+}
+
+void i2c_off(void)
+{
+       struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
+       NVIC_DisableIRQ(I2C0_IRQ);
+       sys_ctrl->sys_AHB_clk_ctrl &= ~(LPC_SYS_ABH_CLK_CTRL_I2C);
+       mod_i2c.clock = 0;
+}
+
+/* Allow system to propagate main clock */
+void i2c_clock_update(void)
+{
+       if (mod_i2c.clock)
+               i2c_clock_on(mod_i2c.clock); /* 6 is not a module num, nor system i2c (5) */
+}
+
+
diff --git a/include/drivers/i2c.h b/include/drivers/i2c.h
new file mode 100644 (file)
index 0000000..8b8965e
--- /dev/null
@@ -0,0 +1,166 @@
+/****************************************************************************
+ *   drivers/i2c.h
+ *
+ *
+ * Copyright 2012 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#ifndef DRIVERS_I2C_H
+#define DRIVERS_I2C_H
+
+#include <stdint.h>
+
+
+#define I2C_CLK_100KHz  (100*1000)
+#define I2C_CLK_400KHz  (400*1000)
+
+
+enum i2c_conditions {
+       I2C_CONT = 0,
+       I2C_DO_REPEATED_START,
+       I2C_DO_STOP_START,
+       I2C_STOP,
+};
+
+enum i2c_states {
+       /* Must be set before starting the state machine to be able to wait for completion. */
+       I2C_BUSY = 0,
+       I2C_OK, /* All right, default state after init has been done. */
+       /* Sent slave address and it got ACK'ed, but no data.
+        *  Used to probe or wait for completion. */
+       I2C_NO_DATA,
+       I2C_NACK, /* NACK received */
+       I2C_TIME_OUT,
+       I2C_ARBITRATION_LOST, /* Another master took the I2C Bus from us ... */
+       I2C_BUS_ERROR, /* Illegal start or stop (status of 0x00) */
+       I2C_ERROR_UNKNOWN,
+};
+
+enum i2c_state_machine_states {
+       /* Error states */
+       I2C_ILLEGAL = 0x00, /* Illegal start or stop */
+       I2C_ARBIT_LOST = 0x38,
+       /* Start condition states */
+       I2C_STARTED = 0x08,
+       I2C_RESTARTED, /* Unused, should be set when restarting (STOP+START) after a NACK */
+       I2C_REPEATED_START = 0x10,
+       /* Transmitter states */
+       I2C_ACK_ON_ADDRESS_W = 0x18,
+       I2C_NACK_ON_ADDRESS_W = 0x20,
+       I2C_ACK_ON_DATA_W = 0x28,
+       I2C_NACK_ON_DATA_W = 0x30,
+       /* Receiver states */
+       I2C_ACK_ON_ADDRESS_R = 0x40,
+       I2C_NACK_ON_ADDRESS_R = 0x48,
+       I2C_DATA_ACK = 0x50,
+       I2C_DATA_NACK = 0x58,
+};
+
+
+/***************************************************************************** */
+/*                I2C Init                                                     */
+/***************************************************************************** */
+void set_i2c_pins(void);
+void i2c_on(uint32_t i2c_clk_freq);
+void i2c_off(void);
+/* Allow system to propagate main clock */
+void i2c_clock_update(void);
+
+/***************************************************************************** */
+/*          Read and Write for system eeprom                                   */
+/***************************************************************************** */
+/* EEPROM Read
+ * Performs a non-blocking read on the eeprom.
+ *   address : data offset in eeprom.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes read. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EFAULT : address above eeprom size
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+int eeprom_read(uint32_t offset, void *buf, size_t count);
+
+/* EEPROM Write
+ * Performs a non-blocking write on the eeprom.
+ *   address : data offset in eeprom.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes written. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EFAULT : address above eeprom size
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+int eeprom_write(uint32_t offset, const void *buf, size_t count);
+
+
+
+/***************************************************************************** */
+/*                Modules I2C access                                           */
+/***************************************************************************** */
+/* I2C Read
+ * Performs a non-blocking read on the i2c bus.
+ *   cmd_buf : buffer containing all control byte to be sent on the i2c bus
+ *   cmd_size : size of the cmd_buf command buffer
+ *   ctrl_buf : buffer containing action to be done after sending, like repeated START conditions
+ *         ctrl_buf has the same size as cmd_buf
+ *   inbuff : the buffer where read data will be put.
+ *   count : the number of bytes to be read.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes read. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+int i2c_read(const void *cmd_buf, size_t cmd_size, const void* ctrl_buf, void* inbuff, size_t count);
+
+/* I2C Write
+ * Performs a non-blocking write on the i2c bus.
+ *   buf : buffer containing all byte to be sent on the i2c bus,
+ *         including conrtol bytes (address, offsets, ...)
+ *   count : the number of bytes to be sent, including address bytes and so on.
+ *   ctrl_buf : buffer containing action to be done after sending, like repeated START conditions
+ *         ctrl_buf has the same size as cmd_buf
+ *         FIXME : note that STOP + START conditions are not allowed, the STOP would lead to sending
+ *         the first bytes of buf, creating an infinite loop.
+ * RETURN VALUE
+ *   Upon successfull completion, returns the number of bytes written. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EBADFD : Device not initialized
+ *   -EBUSY : Device or ressource Busy or Arbitration lost
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ *   -EREMOTEIO : Device did not acknowledge
+ *   -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine
+ */
+int i2c_write(const void *buf, size_t count, const void* ctrl_buf);
+
+
+#endif /* DRIVERS_I2C_H */