i2c: add asynchronous write
authorCyprien Laplace <cyprien@cypou.net>
Sat, 17 Jun 2017 14:58:22 +0000 (10:58 -0400)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
i2c_read() and i2c_write() are blocking functions, and their comments
have been updated to reflect this.

The new i2c_write_async() function is starting an i2c transfer, and
returns immediatly. All i2c transfer functions will now return -EAGAIN
if there is already a transfer in progress (-EBUSY was conflicting with
arbitration lost).

ssd130x_display_full_screen() has been updated to use this new
asynchronous write to start the transfer of the full screen. All
i2c_write* calls are placed in a retry loop if the call returns -EAGAIN.

drivers/i2c.c
extdrv/ssd130x_oled_driver.c
include/drivers/i2c.h

index 0d96e21..1812fc9 100644 (file)
@@ -274,26 +274,25 @@ void I2C_0_Handler(void)
 }
 
 
-
 /***************************************************************************** */
 /*                        I2C access                                           */
 /***************************************************************************** */
-static int i2c_perform_data_transfer(struct i2c_bus* i2c)
+
+/* Translate I2C state to 0 on success or a glibc error code.
+ */
+static int i2c_state(struct i2c_bus* i2c)
 {
        int ret = 0;
 
-       /* Start the process */
-       i2c->state = I2C_BUSY;
-       i2c->regs->ctrl_set = I2C_START_FLAG;
-       /* Wait for process completion */
-       do {} while (i2c->state == I2C_BUSY);
-
        /* Handle returned state (errors or OK) */
        switch (i2c->state) {
                case I2C_OK:
                case I2C_NO_DATA:
                        /* Return 0 : success */
                        break;
+               case I2C_BUSY:
+                       ret = -EAGAIN;
+                       break;
                case I2C_NACK:
                        ret = -EREMOTEIO;
                        break;
@@ -310,6 +309,7 @@ static int i2c_perform_data_transfer(struct i2c_bus* i2c)
        return ret;
 }
 
+
 /* Release Bus
  * Some devices do not release the Bus at the end of a transaction if they don't receive
  *   a start condition immediately followed by a stop condition.
@@ -328,7 +328,7 @@ void i2c_release_bus(uint8_t bus_num)
 
 
 /* Read
- * Performs a non-blocking read on the module's i2c bus.
+ * Performs a 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
@@ -353,12 +353,14 @@ int i2c_read(uint8_t bus_num, const void *cmd_buf, size_t cmd_size, const void*
                return -EINVAL;
 
        if (i2c->state == I2C_BUSY) {
-               return -EBUSY;
+               return -EAGAIN;
        }
        if (i2c->state != I2C_OK) {
                /* What should we do ??? someone failed to reset status ? */
        }
 
+       i2c->state = I2C_BUSY;
+
        /* Set up i2c structure for read operation */
        /* command (write) buffer */
        i2c->out_buff = cmd_buf;
@@ -373,7 +375,12 @@ int i2c_read(uint8_t bus_num, const void *cmd_buf, size_t cmd_size, const void*
        i2c->read_length = count;
        i2c->read_index = 0;
 
-       ret = i2c_perform_data_transfer(i2c);
+       /* Start the process */
+       i2c->regs->ctrl_set = I2C_START_FLAG;
+       /* Wait for process completion */
+       do {} while (i2c->state == I2C_BUSY);
+
+       ret = i2c_state(i2c);
        if (ret == 0) {
                return i2c->read_index;
        }
@@ -381,7 +388,7 @@ int i2c_read(uint8_t bus_num, const void *cmd_buf, size_t cmd_size, const void*
        return ret;
 }
 
-/* Write
+/* Asynchronous 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, ...)
@@ -390,13 +397,12 @@ int i2c_read(uint8_t bus_num, const void *cmd_buf, size_t cmd_size, const void*
  *         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
+ *   Upon successfull transmition start, returns 0. On error, returns a negative
  *   integer equivalent to errors from glibc.
  */
-int i2c_write(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_buf)
+int i2c_write_async(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_buf)
 {
        struct i2c_bus* i2c = &(i2c_buses[0]);
-       int ret = 0;
 
        /* Checks */
        if (i2c->regs != LPC_I2C0)
@@ -405,16 +411,17 @@ int i2c_write(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_b
                return -EINVAL;
 
        if (i2c->state == I2C_BUSY) {
-               return -EBUSY;
+               return -EAGAIN;
        }
        if (i2c->state != I2C_OK) {
                /* What should we do ??? someone failed to reset status ? */
        }
 
+       i2c->state = I2C_BUSY;
+
        /* Clear read information to prevent entering master receiver states */
        i2c->read_length = 0;
        i2c->in_buff = NULL;
-       i2c->state = I2C_BUSY;
        /* Set up i2c_bus structure for write operation */
        i2c->out_buff = buf;
        i2c->write_length = count;
@@ -424,7 +431,39 @@ int i2c_write(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_b
        i2c->repeated_start_restart = ctrl_buf;
        i2c->restart_after_data = 0;
 
-       ret = i2c_perform_data_transfer(i2c);
+       /* Start the process */
+       i2c->regs->ctrl_set = I2C_START_FLAG;
+
+       return 0;
+}
+
+
+/* Write
+ * Performs a 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.
+ */
+int i2c_write(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_buf)
+{
+       struct i2c_bus* i2c = &(i2c_buses[0]);
+       int ret;
+
+       ret = i2c_write_async(bus_num, buf, count, ctrl_buf);
+       if (ret != 0) {
+               return ret;
+       }
+
+       /* Wait for process completion */
+       do {} while (i2c->state == I2C_BUSY);
+
+       ret = i2c_state(i2c);
        if (ret == 0) {
                return i2c->write_length;
        }
index 8f8355b..d7c1806 100644 (file)
@@ -103,7 +103,9 @@ int ssd130x_send_command(struct oled_display* conf, uint8_t cmd, uint8_t* data,
                        cmd_buf[ 4 + (2 * i) ] = data[i];
                }
        }
-       ret = i2c_write(conf->bus_num, cmd_buf, (3 + (len * 2)), NULL);
+       do {
+               ret = i2c_write(conf->bus_num, cmd_buf, (3 + (len * 2)), NULL);
+       } while (ret == -EAGAIN);
        if (ret != (3 + (len * 2))) {
                conf->probe_ok = 0;
                return ret;
@@ -330,7 +332,9 @@ int ssd130x_send_data(struct oled_display* conf, uint8_t* start, uint16_t len)
        *(start - 1) = SSD130x_DATA_ONLY;
 
        /* Send data on I2C bus */
-       ret = i2c_write(conf->bus_num, (start - 2), (2 + len), NULL);
+       do {
+               ret = i2c_write(conf->bus_num, (start - 2), (2 + len), NULL);
+       } while (ret == -EAGAIN);
 
        /* Restore gddram data */
        *(start - 2) = conf->gddram[0];
@@ -355,11 +359,17 @@ int ssd130x_display_full_screen(struct oled_display* conf)
        if (ret != 0) {
                return ret;
        }
-       ret = ssd130x_send_data(conf, conf->gddram + 4, GDDRAM_SIZE);
-       if (ret != GDDRAM_SIZE) {
-               return ret;
-       }
-       return 0;
+
+       /* Setup I2C transfer */
+       *(conf->gddram + 2) = conf->address;
+       *(conf->gddram + 3) = SSD130x_DATA_ONLY;
+
+       /* Send data on I2C bus */
+       do {
+               ret = i2c_write_async(conf->bus_num, conf->gddram + 2, 2 + GDDRAM_SIZE, NULL);
+       } while (ret == -EAGAIN);
+
+       return ret;
 }
 
 /* Change a "tile" in the GDDRAM memory.
index ecce01c..19a4a0e 100644 (file)
@@ -104,7 +104,7 @@ enum i2c_state_machine_states {
 /*                Modules I2C access                                           */
 /***************************************************************************** */
 /* I2C Read
- * Performs a non-blocking read on the i2c bus.
+ * Performs a 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
@@ -124,7 +124,7 @@ enum i2c_state_machine_states {
 int i2c_read(uint8_t bus_num, 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.
+ * Performs a 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.
@@ -136,7 +136,8 @@ int i2c_read(uint8_t bus_num, const void *cmd_buf, size_t cmd_size, const void*
  *   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
+ *   -EBUSY : Arbitration lost
  *   -EAGAIN : Device already in use
  *   -EINVAL : Invalid argument (buf)
  *   -EREMOTEIO : Device did not acknowledge
@@ -144,6 +145,24 @@ int i2c_read(uint8_t bus_num, const void *cmd_buf, size_t cmd_size, const void*
  */
 int i2c_write(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_buf);
 
+/* I2C Asynchronous 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 transmition start, returns 0. On error, returns a negative
+ *   integer equivalent to errors from glibc.
+ *   -EBADFD : Device not initialized
+ *   -EAGAIN : Device already in use
+ *   -EINVAL : Invalid argument (buf)
+ */
+int i2c_write_async(uint8_t bus_num, const void *buf, size_t count, const void* ctrl_buf);
+
 
 /* Release Bus
  * Some devices do not release the Bus at the end of a transaction if they don't receive