From 00f4744c4e196ba3b98dea9ba33163421661de00 Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Thu, 7 Mar 2013 12:00:49 +0100 Subject: [PATCH] eeprom support development ongoing --- drivers/eeprom.c | 150 ++++++++++++ drivers/i2c.c | 519 ++++++++++++++++++++++++++++++++++++++++++ include/drivers/i2c.h | 166 ++++++++++++++ 3 files changed, 835 insertions(+) create mode 100644 drivers/eeprom.c create mode 100644 drivers/i2c.c create mode 100644 include/drivers/i2c.h diff --git a/drivers/eeprom.c b/drivers/eeprom.c new file mode 100644 index 0000000..4927b3d --- /dev/null +++ b/drivers/eeprom.c @@ -0,0 +1,150 @@ +/**************************************************************************** + * drivers/eeprom.c + * + * + * Copyright 2012 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#include + +#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 index 0000000..2efe749 --- /dev/null +++ b/drivers/i2c.c @@ -0,0 +1,519 @@ +/**************************************************************************** + * drivers/i2c.c + * + * + * Copyright 2012 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#include + +#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 index 0000000..8b8965e --- /dev/null +++ b/include/drivers/i2c.h @@ -0,0 +1,166 @@ +/**************************************************************************** + * drivers/i2c.h + * + * + * Copyright 2012 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#ifndef DRIVERS_I2C_H +#define DRIVERS_I2C_H + +#include + + +#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 */ -- 2.43.0