You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by vi...@apache.org on 2018/11/19 17:07:48 UTC

[mynewt-core] 01/07: Initial commit dma i2c

This is an automated email from the ASF dual-hosted git repository.

vipulrahane pushed a commit to branch dma_i2c_new_api
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git

commit 34b7101e04e7ad2f20c989961719685dd46fa5b4
Author: Vipul Rahane <vi...@runtime.io>
AuthorDate: Wed Oct 17 12:58:20 2018 -0700

    Initial commit dma i2c
---
 hw/drivers/sensors/tsl2561/src/tsl2561_shell.c |   2 +-
 hw/hal/include/hal/hal_i2c.h                   |   9 +
 hw/mcu/nordic/nrf52xxx/src/hal_i2c.c           | 376 +++++++++++++++++--------
 3 files changed, 273 insertions(+), 114 deletions(-)

diff --git a/hw/drivers/sensors/tsl2561/src/tsl2561_shell.c b/hw/drivers/sensors/tsl2561/src/tsl2561_shell.c
index 71201e6..169e9c3 100644
--- a/hw/drivers/sensors/tsl2561/src/tsl2561_shell.c
+++ b/hw/drivers/sensors/tsl2561/src/tsl2561_shell.c
@@ -348,7 +348,7 @@ tsl2561_shell_cmd_en(int argc, char **argv)
     /* Update the enable state */
     if (argc == 3) {
         lval = strtol(argv[2], &endptr, 10); /* Base 10 */
-        if (argv[2] != '\0' && *endptr == '\0' &&
+        if (*argv[2] != '\0' && *endptr == '\0' &&
             lval >= 0 && lval <= 1) {
             rc = tsl2561_enable(&g_sensor_itf, lval);
             if (rc) {
diff --git a/hw/hal/include/hal/hal_i2c.h b/hw/hal/include/hal/hal_i2c.h
index be080ca..50fd1df 100644
--- a/hw/hal/include/hal/hal_i2c.h
+++ b/hw/hal/include/hal/hal_i2c.h
@@ -79,6 +79,15 @@ extern "C" {
 /** Slave responded to data byte with NACK. */
 #define HAL_I2C_ERR_DATA_NACK           5
 
+/** Overrun error */
+#define HAL_I2C_ERR_OVERRUN             6
+
+/** I2C bus in suspended state, but last op was 0 */
+#define HAL_I2C_ERR_SUSPEND             7
+
+/** I2C bus in stopped state, but last op was 1   */
+#define HAL_I2C_ERR_STOP                8
+
 /**
  * When sending a packet, use this structure to pass the arguments.
  */
diff --git a/hw/mcu/nordic/nrf52xxx/src/hal_i2c.c b/hw/mcu/nordic/nrf52xxx/src/hal_i2c.c
index b925394..b19e2ad 100644
--- a/hw/mcu/nordic/nrf52xxx/src/hal_i2c.c
+++ b/hw/mcu/nordic/nrf52xxx/src/hal_i2c.c
@@ -26,9 +26,22 @@
 #include <hal/hal_gpio.h>
 #include <mcu/nrf52_hal.h>
 #include "nrf_twim.h"
+#include <console/console.h>
 
 #include <nrf.h>
 
+#define I2C_WRITE 0
+#define I2C_READ  1
+
+#define WAIT_FOR_EVT(__r, __e)\
+    while (!(__r)->EVENTS_####__e) {\
+        now = os_time_get();\
+        if (OS_TIME_TICK_GT(now, abs_timo)) {\
+            rc = HAL_I2C_ERR_TIMEOUT;\
+            goto err;\
+        }\
+    }
+
 #define NRF52_HAL_I2C_MAX (2)
 
 #define NRF52_SCL_PIN_CONF                                              \
@@ -48,17 +61,22 @@
 #define NRF52_SDA_PIN_CONF_CLR    NRF52_SCL_PIN_CONF_CLR
 
 struct nrf52_hal_i2c {
-    NRF_TWI_Type *nhi_regs;
+    NRF_TWIM_Type *nhi_regs;
+    /*
+     * previous last_op, this is just to check if the previous
+     * transaction actually put the I2C bus in the suspended state
+     */
+    int nhi_prev_last_op;
 };
 
 #if MYNEWT_VAL(I2C_0)
 struct nrf52_hal_i2c hal_twi_i2c0 = {
-    .nhi_regs = NRF_TWI0
+    .nhi_regs = NRF_TWIM0
 };
 #endif
 #if MYNEWT_VAL(I2C_1)
 struct nrf52_hal_i2c hal_twi_i2c1 = {
-    .nhi_regs = NRF_TWI1
+    .nhi_regs = NRF_TWIM1
 };
 #endif
 
@@ -180,6 +198,8 @@ hal_i2c_convert_status(int nrf_status)
         return HAL_I2C_ERR_DATA_NACK;
     } else if (nrf_status & NRF_TWIM_ERROR_ADDRESS_NACK) {
         return HAL_I2C_ERR_ADDR_NACK;
+    } else if (nrf_status & TWIM_ERRORSRC_OVERRUN_Msk) {
+        return HAL_I2C_ERR_OVERRUN;
     } else {
         return HAL_I2C_ERR_UNKNOWN;
     }
@@ -257,7 +277,7 @@ int
 hal_i2c_init(uint8_t i2c_num, void *usercfg)
 {
     struct nrf52_hal_i2c *i2c;
-    NRF_TWI_Type *regs;
+    NRF_TWIM_Type *regs;
     struct nrf52_hal_i2c_cfg *cfg;
     uint32_t freq;
     int rc;
@@ -275,13 +295,13 @@ hal_i2c_init(uint8_t i2c_num, void *usercfg)
 
     switch (cfg->i2c_frequency) {
     case 100:
-        freq = TWI_FREQUENCY_FREQUENCY_K100;
+        freq = TWIM_FREQUENCY_FREQUENCY_K100;
         break;
     case 250:
-        freq = TWI_FREQUENCY_FREQUENCY_K250;
+        freq = TWIM_FREQUENCY_FREQUENCY_K250;
         break;
     case 400:
-        freq = TWI_FREQUENCY_FREQUENCY_K400;
+        freq = TWIM_FREQUENCY_FREQUENCY_K400;
         break;
     default:
         rc = HAL_I2C_ERR_INVAL;
@@ -297,179 +317,309 @@ hal_i2c_init(uint8_t i2c_num, void *usercfg)
     scl_port->PIN_CNF[cfg->scl_pin] = NRF52_SCL_PIN_CONF;
     sda_port->PIN_CNF[cfg->sda_pin] = NRF52_SDA_PIN_CONF;
 
-    regs->PSELSCL = cfg->scl_pin;
-    regs->PSELSDA = cfg->sda_pin;
+    regs->PSEL.SCL = cfg->scl_pin;
+    regs->PSEL.SDA = cfg->sda_pin;
     regs->FREQUENCY = freq;
-    regs->ENABLE = TWI_ENABLE_ENABLE_Enabled;
+    regs->ENABLE = TWIM_ENABLE_ENABLE_Enabled;
+    i2c->nhi_prev_last_op = -1;
 
     return (0);
 err:
     return (rc);
 }
 
-int
-hal_i2c_master_write(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
-                     uint32_t timo, uint8_t last_op)
+/*
+ * Starts an I2C transaction either read or write using EasyDMA/TWIM
+ */
+static int
+hal_i2c_handle_transact_start(struct nrf52_hal_i2c *i2c, uint8_t op,
+                              uint32_t abs_timo)
 {
-    struct nrf52_hal_i2c *i2c;
-    NRF_TWI_Type *regs;
-    int nrf_status;
+    NRF_TWIM_Type *regs;
+    uint32_t now;
     int rc;
-    int i;
-    uint32_t start;
 
-    rc = hal_i2c_resolve(i2c_num, &i2c);
-    if (rc != 0) {
-        return rc;
-    }
     regs = i2c->nhi_regs;
 
-    regs->ADDRESS = pdata->address;
-
     regs->EVENTS_ERROR = 0;
+
+    /* If a transaction was previously suspended, resume it */
+    if (regs->EVENTS_SUSPENDED) {
+        regs->TASKS_RESUME = 1;
+    }
+
     regs->EVENTS_STOPPED = 0;
     regs->EVENTS_SUSPENDED = 0;
-    regs->SHORTS = 0;
+    if (op == I2C_WRITE) {
+        /* Start an I2C transmit transaction */
+        regs->TASKS_STARTTX = 1;
+        WAIT_FOR_EVT(regs, TXSTARTED);
+        regs->EVENTS_TXSTARTED = 0;
+    } else {
+        /* Start an I2C receive transaction */
+        regs->TASKS_STARTRX = 1;
+        WAIT_FOR_EVT(regs, RXSTARTED);
+        regs->EVENTS_RXSTARTED = 0;
+    }
 
-    regs->TASKS_STARTTX = 1;
-    regs->TASKS_RESUME = 1;
+    return 0;
+err:
+    return rc;
+}
 
-    start = os_time_get();
-    for (i = 0; i < pdata->len; i++) {
-        regs->EVENTS_TXDSENT = 0;
-        regs->TXD = pdata->buffer[i];
-        while (!regs->EVENTS_TXDSENT && !regs->EVENTS_ERROR) {
-            if (os_time_get() - start > timo) {
-                rc = HAL_I2C_ERR_TIMEOUT;
-                goto err;
-            }
-        }
-        if (regs->EVENTS_ERROR) {
-            goto err;
+static int
+hal_i2c_handle_transact_end(NRF_TWIM_Type *regs, uint32_t start,
+                            uint32_t abs_timo, uint8_t last_op,
+                            uint8_t op)
+{
+    int rc;
+    uint32_t evt;
+    os_time_t now;
+
+    if (last_op) {
+        /* this is to take care of clock stretches as per the datasheet */
+        if (regs->EVENTS_SUSPENDED) {
+            /* If there is a clock stretch, the event would be suspended for
+             * that TWIM interface, it would need to be resumed to finish the
+             * transaction
+             */
+            regs->TASKS_RESUME = 1;
         }
     }
-    /* If last_op is zero it means we dont put a stop at end. */
-    if (last_op) {
-        regs->EVENTS_STOPPED = 0;
-        regs->TASKS_STOP = 1;
-        while (!regs->EVENTS_STOPPED && !regs->EVENTS_ERROR) {
-            if (os_time_get() - start > timo) {
-                rc = HAL_I2C_ERR_TIMEOUT;
-                goto err;
+
+    while(1) {
+        /*
+         * Use last_op as the determining factor for the type of event to be
+         * monitored
+         */
+        if (last_op) {
+            evt = regs->EVENTS_STOPPED;
+        } else {
+            evt = regs->EVENTS_SUSPENDED;
+        }
+
+        if (evt) {
+            if (evt == regs->EVENTS_STOPPED) {
+                regs->EVENTS_LASTTX = 0;
+                regs->EVENTS_LASTRX = 0;
             }
+            break;
         }
+
         if (regs->EVENTS_ERROR) {
             goto err;
         }
+
+        now = os_time_get();
+        if (OS_TIME_TICK_GT(now, abs_timo)) {
+            rc = HAL_I2C_ERR_TIMEOUT;
+            goto err;
+        }
     }
 
-    rc = 0;
+    return 0;
+err:
+    return rc;
+}
+
+static int
+hal_i2c_bus_error_detect(struct nrf52_hal_i2c *i2c)
+{
+    int rc;
+    NRF_TWIM_Type *regs;
+
+    regs = i2c->nhi_regs;
+
+    if (i2c->nhi_prev_last_op == 0 && !regs->EVENTS_SUSPENDED) {
+        /*
+         * return HAL_I2C_ERR_SUSPEND if previous last_op was 0 and
+         * the bus was not in the suspended state
+         */
+        rc = HAL_I2C_ERR_SUSPEND;
+        goto err;
+    } else if (i2c->nhi_prev_last_op  == 1 &&
+               (!regs->EVENTS_STOPPED && regs->EVENTS_SUSPENDED)) {
+        /*
+         * return HAL_I2C_ERR_STOP if previous last_op was 1 and
+         * the bus is not in the stopped state or in initial state
+         * which is EVENTS_SUSPENDED is 1 and EVENTS_STOPPED is 1
+         */
+        rc = HAL_I2C_ERR_STOP;
+        goto err;
+    }
 
+    return 0;
 err:
+    return rc;
+}
+
+/* Handle errors returned from the TWIM peripheral along with timeouts */
+static int
+hal_i2c_handle_errors(NRF_TWIM_Type *regs, int rc)
+{
+    int nrf_status;
+
+    regs->TASKS_RESUME = 1;
     regs->TASKS_STOP = 1;
 
     if (regs->EVENTS_ERROR) {
+        regs->EVENTS_ERROR = 0;
         nrf_status = regs->ERRORSRC;
         regs->ERRORSRC = nrf_status;
         rc = hal_i2c_convert_status(nrf_status);
-    } else if (rc == HAL_I2C_ERR_TIMEOUT) {
+    } else if (rc) {
         /* Some I2C slave peripherals cause a glitch on the bus when they
-         * reset which puts the TWI in an unresponsive state.  Disabling and
+         * reset which puts the TWI in an unresponsive state. Disabling and
          * re-enabling the TWI returns it to normal operation.
          * A clear operation is performed in case one of the devices on
          * the bus is in a bad state.
          */
-        regs->ENABLE = TWI_ENABLE_ENABLE_Disabled;
-        hal_i2c_clear_bus(regs->PSELSCL, regs->PSELSDA);
-        regs->ENABLE = TWI_ENABLE_ENABLE_Enabled;
+        regs->ENABLE = TWIM_ENABLE_ENABLE_Disabled;
+        hal_i2c_clear_bus(regs->PSEL.SCL, regs->PSEL.SDA);
+        regs->ENABLE = TWIM_ENABLE_ENABLE_Enabled;
     }
 
-    return (rc);
+    return rc;
 }
 
+/* Perform I2C master writes using TWIM/EasyDMA */
 int
-hal_i2c_master_read(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
-                    uint32_t timo, uint8_t last_op)
+hal_i2c_master_write(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
+                     uint32_t timo, uint8_t last_op)
 {
-    struct nrf52_hal_i2c *i2c;
-    NRF_TWI_Type *regs;
-    int nrf_status;
     int rc;
-    int i;
     uint32_t start;
+    NRF_TWIM_Type *regs;
+    struct nrf52_hal_i2c *i2c;
 
+    start = os_time_get();
+
+    /* Resolve the I2C bus */
     rc = hal_i2c_resolve(i2c_num, &i2c);
     if (rc != 0) {
         return rc;
     }
+
     regs = i2c->nhi_regs;
 
-    start = os_time_get();
+    /* Detect errors on the bus based on the previous and current
+     * condition of the bus
+     */
+    rc = hal_i2c_bus_error_detect(i2c);
+    if (rc) {
+        goto err;
+    }
 
-    if (regs->EVENTS_RXDREADY) {
-        /*
-         * If previous read was interrupted, flush RXD.
+    /* Configure the TXD registers for EasyDMA access to work with buffers of
+     * specific length and address of the slave
+     */
+    regs->ADDRESS    = pdata->address;
+    regs->TXD.MAXCNT = pdata->len;
+    regs->TXD.PTR    = (uint32_t)pdata->buffer;
+    regs->TXD.LIST   = 0;
+    /* Disable and clear interrupts */
+    regs->INTENCLR   = NRF_TWIM_ALL_INTS_MASK;
+    regs->INTEN      = 0;
+
+    /* Setup shorts to end transaction based on last_op,
+     * 0 : STOP transaction,
+     * 1 : SUSPEND transaction
+     */
+    if (last_op) {
+        /* EVENT_STOPPED would get set after LASTTX gets set at
+         * the end of the transaction for the last byte
+         */
+        regs->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk;
+    } else {
+        /* EVENT_SUSPENDED would get set after LASTTX gets set at
+         * the end of the transaction for the last byte
          */
-        (void)regs->RXD;
-        (void)regs->RXD;
+        regs->SHORTS = TWIM_SHORTS_LASTTX_SUSPEND_Msk;
     }
-    regs->EVENTS_ERROR = 0;
-    regs->EVENTS_STOPPED = 0;
-    regs->EVENTS_SUSPENDED = 0;
-    regs->EVENTS_RXDREADY = 0;
 
-    regs->ADDRESS = pdata->address;
-
-    if (pdata->len == 1 && last_op) {
-        regs->SHORTS = TWI_SHORTS_BB_STOP_Msk;
-    } else {
-        regs->SHORTS = TWI_SHORTS_BB_SUSPEND_Msk;
+    /* Starts an I2C transaction using TWIM/EasyDMA */
+    rc = hal_i2c_handle_transact_start(i2c, I2C_WRITE, start + timo);
+    if (rc) {
+        goto err;
     }
-    regs->TASKS_STARTRX = 1;
 
-    for (i = 0; i < pdata->len; i++) {
-        regs->TASKS_RESUME = 1;
-        while (!regs->EVENTS_RXDREADY && !regs->EVENTS_ERROR) {
-            if (os_time_get() - start > timo) {
-                rc = HAL_I2C_ERR_TIMEOUT;
-                goto err;
-            }
-        }
-        if (regs->EVENTS_ERROR) {
-            goto err;
-        }
-        pdata->buffer[i] = regs->RXD;
-        if (i == pdata->len - 2) {
-            if (last_op) {
-                regs->SHORTS = TWI_SHORTS_BB_STOP_Msk;
-            }
-        }
-        regs->EVENTS_RXDREADY = 0;
+    /* Ends an I2C transaction using TWIM/EasyDMA */
+    rc = hal_i2c_handle_transact_end(regs, I2C_WRITE, start, start + timo, last_op);
+    if (rc) {
+        goto err;
     }
 
-    return (0);
+    /* Save the current last op to detect bus errors later */
+    i2c->nhi_prev_last_op = last_op;
 
+    return 0;
 err:
-    regs->TASKS_STOP = 1;
-    regs->SHORTS = TWI_SHORTS_BB_STOP_Msk;
+    return hal_i2c_handle_errors(regs, rc);
+}
 
-    if (regs->EVENTS_ERROR) {
-        nrf_status = regs->ERRORSRC;
-        regs->ERRORSRC = nrf_status;
-        rc = hal_i2c_convert_status(nrf_status);
-    } else if (rc == HAL_I2C_ERR_TIMEOUT) {
-       /* Some I2C slave peripherals cause a glitch on the bus when they
-        * reset which puts the TWI in an unresponsive state.  Disabling and
-        * re-enabling the TWI returns it to normal operation.
-        * A clear operation is performed in case one of the devices on
-        * the bus is in a bad state.
-        */
-        regs->ENABLE = TWI_ENABLE_ENABLE_Disabled;
-        hal_i2c_clear_bus(regs->PSELSCL, regs->PSELSDA);
-        regs->ENABLE = TWI_ENABLE_ENABLE_Enabled;
+int
+hal_i2c_master_read(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
+                    uint32_t timo, uint8_t last_op)
+{
+    int rc;
+    uint32_t start;
+    NRF_TWIM_Type *regs;
+    struct nrf52_hal_i2c *i2c;
+
+    start = os_time_get();
+
+    /* Resolve the I2C bus */
+    rc = hal_i2c_resolve(i2c_num, &i2c);
+    if (rc != 0) {
+        return rc;
     }
 
-    return (rc);
+    regs = i2c->nhi_regs;
+
+    /* Detect errors on the bus based on the previous and current
+     * condition of the bus
+     */
+    rc = hal_i2c_bus_error_detect(i2c);
+    if (rc) {
+        goto err;
+    }
+
+    /* Configure the RXD registers for EasyDMA access to work with buffers of
+     * specific length and address of the slave
+     */
+    regs->ADDRESS    = pdata->address;
+    regs->RXD.MAXCNT = pdata->len;
+    regs->RXD.PTR    = (uint32_t)pdata->buffer;
+    regs->RXD.LIST   = 0;
+    /* Disable and clear interrupts */
+    regs->INTENCLR   = NRF_TWIM_ALL_INTS_MASK;
+    regs->INTEN      = 0;
+
+    /* Only set short for RX->STOP for last_op:1 since there is no suspend short
+     * available in nrf52832
+     */
+    if (last_op) {
+        regs->SHORTS = TWIM_SHORTS_LASTRX_STOP_Msk;
+    }
+
+    /* Starts an I2C transaction using TWIM/EasyDMA */
+    rc = hal_i2c_handle_transact_start(i2c, I2C_READ, start + timo);
+    if (rc) {
+        goto err;
+    }
+
+    /* Ends an I2C transaction using TWIM/EasyDMA */
+    rc = hal_i2c_handle_transact_end(regs, I2C_READ, start, start + timo, last_op);
+    if (rc) {
+        goto err;
+    }
+
+    /* Save the current last op to detect bus errors later */
+    i2c->nhi_prev_last_op = last_op;
+
+    return 0;
+err:
+    return hal_i2c_handle_errors(regs, rc);
 }
 
 int