/* * Copyright (C) 2017 HAW Hamburg * 2018 Freie Universität Berlin * 2018 Mesotic SAS * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup cpu_nrf5x_common * @{ * * @file * @brief Low-level I2C (TWI) peripheral driver implementation * * @author Dimitri Nahm * @author Hauke Petersen * @author Dylan Laduranty * * As this implementation is based on the nRF5x TWIM peripheral, it can not * issue a read following a read (or a write following a write) without * creating a (repeated) start condition: * , * backed also by later experiments discussed in the [Rust embedded * channel](https://matrix.to/#/!BHcierreUuwCMxVqOf:matrix.org/$JwNejRaeJx_tvqKgS88GenDG8ZNHrkTW09896dIehQ8?via=matrix.org&via=catircservices.org&via=tchncs.de). * Due to this shortcoming in the hardware, any operations with I2C_NOSTART * fail. * * Relatedly, the successful termination of a read or write can not be detected * by an interrupt (only the eventual STOPPED condition after the event * short-circuiting of LASTTX/LASTRX to STOP triggers one). There are LASTTX / * LASTRX interrupts, but while the LASTTX is sensible enough (the last byte * has been read, is being written, the caller may now repurpose the buffers), * the LASTRX interrupt fires at the start of the last byte reading, and the * user can not reliably know when the last byte was written (at least not * easily). Therefore, reads with I2C_NOSTOP are not supported. * * In combination, these still allow the typical I2C operations: A single * write, and a write (selecting a register) followed by a read, as well as * stand-alone reads. More complex patterns are not supported; in particular, * scatter-gather reads or writes are not possible. * * @} */ #include #include #include #include "cpu.h" #include "mutex.h" #include "assert.h" #include "periph/i2c.h" #include "periph/gpio.h" #include "byteorder.h" #define ENABLE_DEBUG 0 #include "debug.h" /** * @brief If any of the 8 lower bits are set, the speed value is invalid */ #define INVALID_SPEED_MASK (0xff) /** * @brief Allocate a tx buffer */ static uint8_t tx_buf[256]; /** * @brief Mutex for locking the TX buffer */ static mutex_t buffer_lock; /** * @brief Initialized dev locks (we have a maximum of two devices...) */ static mutex_t locks[I2C_NUMOF]; /** * @brief array with a busy mutex for each I2C device, used to block the * thread until the transfer is done */ static mutex_t busy[I2C_NUMOF]; void i2c_isr_handler(void *arg); static inline NRF_TWIM_Type *bus(i2c_t dev) { return i2c_config[dev].dev; } /** * @brief Like i2c_write_bytes, but with the constraint (created by the * hardware) that data is in RAM */ static int direct_i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len, uint8_t flags); /** * Block until the interrupt described by inten_success_flag or * TWIM_INTEN_ERROR_Msk fires. * * Allowed values for inten_success_flag are * * TWIM_INTEN_STOPPED_Msk (when a stop condition is to be set and the short * circuit will pull TWIM into the stopped condition) * * TWIM_INTEN_LASTTX_Msk (when sending without a stop condition) * * (TWIM_INTEN_LASTRX_Msk makes no sense here because that interrupt fires * before the data is ready). * * Any addition needs to be added to the mask in i2c_isr_handler. */ static int finish(i2c_t dev, int inten_success_flag) { DEBUG("[i2c] waiting for success (STOPPED/LASTTX) or ERROR event\n"); /* Unmask interrupts */ bus(dev)->INTENSET = inten_success_flag | TWIM_INTEN_ERROR_Msk; mutex_lock(&busy[dev]); if ((bus(dev)->EVENTS_STOPPED)) { bus(dev)->EVENTS_STOPPED = 0; DEBUG("[i2c] finish: stop event occurred\n"); } if (inten_success_flag & TWIM_INTEN_LASTTX_Msk) { /* The interrupt is raised already when the last TX is started, but we * have to wait until it was actually transmitted lest the transmission * would be suppressed immediately by the next following write -- * careful here: enabling DEBUG introduces enough latency that the * issue doesn't show up any more. */ while (bus(dev)->TXD.AMOUNT != bus(dev)->TXD.MAXCNT && !bus(dev)->EVENTS_ERROR) {} } if (bus(dev)->EVENTS_ERROR) { bus(dev)->EVENTS_ERROR = 0; if (bus(dev)->ERRORSRC & TWIM_ERRORSRC_ANACK_Msk) { bus(dev)->ERRORSRC = TWIM_ERRORSRC_ANACK_Msk; DEBUG("[i2c] check_error: NACK on address byte\n"); return -ENXIO; } if (bus(dev)->ERRORSRC & TWIM_ERRORSRC_DNACK_Msk) { bus(dev)->ERRORSRC = TWIM_ERRORSRC_DNACK_Msk; DEBUG("[i2c] check_error: NACK on data byte\n"); return -EIO; } } return 0; } static void _init_pins(i2c_t dev) { gpio_init(i2c_config[dev].scl, GPIO_IN_OD_PU); gpio_init(i2c_config[dev].sda, GPIO_IN_OD_PU); } /* Beware: This needs to be kept in sync with the SPI version of this. * Specifically, when registers are configured that are valid to the peripheral * in both SPI and I2C mode, the register needs to be configured in both the I2C * and the SPI variant of _setup_shared_peripheral() to avoid from parameters * leaking from one bus into the other */ static void _setup_shared_peripheral(i2c_t dev) { bus(dev)->PSEL.SCL = i2c_config[dev].scl; bus(dev)->PSEL.SDA = i2c_config[dev].sda; bus(dev)->FREQUENCY = i2c_config[dev].speed; } void i2c_init(i2c_t dev) { assert(dev < I2C_NUMOF); /* Initialize mutex */ mutex_init(&busy[dev]); mutex_lock(&busy[dev]); /* disable device during initialization, will be enabled when acquire is * called */ bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Disabled; /* configure pins */ _init_pins(dev); /* configure shared periphal speed */ _setup_shared_peripheral(dev); shared_irq_register_i2c(bus(dev), i2c_isr_handler, (void *)(uintptr_t)dev); /* We expect that the device was being acquired before * the i2c_init_master() function is called, so it should be enabled when * exiting this function. */ bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Enabled; } #ifdef MODULE_PERIPH_I2C_RECONFIGURE void i2c_init_pins(i2c_t dev) { assert(dev < I2C_NUMOF); _init_pins(dev); bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Enabled; mutex_unlock(&locks[dev]); } void i2c_deinit_pins(i2c_t dev) { assert(dev < I2C_NUMOF); mutex_lock(&locks[dev]); bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Disabled; } #endif /* MODULE_PERIPH_I2C_RECONFIGURE */ void i2c_acquire(i2c_t dev) { assert(dev < I2C_NUMOF); if (IS_USED(MODULE_PERIPH_I2C_RECONFIGURE)) { mutex_lock(&locks[dev]); } nrf5x_i2c_acquire(bus(dev), i2c_isr_handler, (void *)(uintptr_t)dev); _setup_shared_peripheral(dev); bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Enabled; DEBUG("[i2c] acquired dev %i\n", (int)dev); } void i2c_release(i2c_t dev) { assert(dev < I2C_NUMOF); bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Disabled; if (IS_USED(MODULE_PERIPH_I2C_RECONFIGURE)) { mutex_unlock(&locks[dev]); } nrf5x_i2c_release(bus(dev)); DEBUG("[i2c] released dev %i\n", (int)dev); } int i2c_write_regs(i2c_t dev, uint16_t addr, uint16_t reg, const void *data, size_t len, uint8_t flags) { assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 253)); if (flags & (I2C_NOSTART | I2C_ADDR10)) { return -EOPNOTSUPP; } /* the nrf52's TWI device does not support to do two consecutive transfers * without a repeated start condition in between. So we have to put all data * to be transferred into a buffer (tx_buf). * */ uint8_t reg_addr_len; /* Length in bytes of the register address */ /* Lock tx_buf */ mutex_lock(&buffer_lock); if (flags & (I2C_REG16)) { reg_addr_len = 2; /* Prepare the 16-bit register transfer */ tx_buf[0] = reg >> 8; /* AddrH in the first byte */ tx_buf[1] = reg & 0xFF; /* AddrL in the second byte */ } else{ reg_addr_len = 1; tx_buf[0] = reg; } memcpy(&tx_buf[reg_addr_len], data, len); int ret = direct_i2c_write_bytes(dev, addr, tx_buf, reg_addr_len + len, flags); /* Release tx_buf */ mutex_unlock(&buffer_lock); return ret; } int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len, uint8_t flags) { assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 256)); if (flags & (I2C_NOSTART | I2C_ADDR10 | I2C_NOSTOP)) { return -EOPNOTSUPP; } DEBUG("[i2c] read_bytes: %i bytes from addr 0x%02x\n", (int)len, (int)addr); bus(dev)->ADDRESS = addr; bus(dev)->RXD.PTR = (uint32_t)data; bus(dev)->RXD.MAXCNT = (uint8_t)len; int inten_success_flag; bus(dev)->SHORTS = TWIM_SHORTS_LASTRX_STOP_Msk; inten_success_flag = TWIM_INTEN_STOPPED_Msk; /* Start transmission */ bus(dev)->TASKS_STARTRX = 1; return finish(dev, inten_success_flag); } int i2c_read_regs(i2c_t dev, uint16_t addr, uint16_t reg, void *data, size_t len, uint8_t flags) { assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 256)); if (flags & (I2C_NOSTART | I2C_ADDR10 | I2C_NOSTOP)) { return -EOPNOTSUPP; } DEBUG("[i2c] read_regs: %i byte(s) from reg 0x%02x at addr 0x%02x\n", (int)len, (int)reg, (int)addr); /* Prepare transfer */ bus(dev)->ADDRESS = addr; if (flags & (I2C_REG16)) { /* Register endianness for 16 bit */ reg = htons(reg); bus(dev)->TXD.MAXCNT = 2; } else { bus(dev)->TXD.MAXCNT = 1; } bus(dev)->TXD.PTR = (uint32_t)® bus(dev)->RXD.PTR = (uint32_t)data; bus(dev)->RXD.MAXCNT = (uint8_t)len; int inten_success_flag = TWIM_INTEN_STOPPED_Msk; bus(dev)->SHORTS = TWIM_SHORTS_LASTTX_STARTRX_Msk | TWIM_SHORTS_LASTRX_STOP_Msk; /* Start transfer */ bus(dev)->TASKS_STARTTX = 1; return finish(dev, inten_success_flag); } int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len, uint8_t flags) { if ((unsigned int)data >= CPU_RAM_BASE && (unsigned int)data < CPU_RAM_BASE + CPU_RAM_SIZE) { return direct_i2c_write_bytes(dev, addr, data, len, flags); } /* These are critical for the memcpy; direct_i2c_write_bytes makes some * more */ assert((len > 0) && (len < 256)); /* Lock tx_buf */ mutex_lock(&buffer_lock); memcpy(tx_buf, data, len); int result = direct_i2c_write_bytes(dev, addr, tx_buf, len, flags); /* Release tx_buf */ mutex_unlock(&buffer_lock); return result; } int direct_i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len, uint8_t flags) { assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 256)); if (flags & (I2C_NOSTART | I2C_ADDR10)) { return -EOPNOTSUPP; } DEBUG("[i2c] write_bytes: %i byte(s) to addr 0x%02x\n", (int)len, (int)addr); bus(dev)->ADDRESS = addr; bus(dev)->TXD.PTR = (uint32_t)data; bus(dev)->TXD.MAXCNT = (uint8_t)len; int inten_success_flag; if (!(flags & I2C_NOSTOP)) { bus(dev)->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk; inten_success_flag = TWIM_INTEN_STOPPED_Msk; } else { bus(dev)->SHORTS = 0; inten_success_flag = TWIM_INTEN_LASTTX_Msk; } bus(dev)->TASKS_STARTTX = 1; return finish(dev, inten_success_flag); } void i2c_isr_handler(void *arg) { i2c_t dev = (i2c_t)(uintptr_t)arg; /* Mask interrupts to ensure that they only trigger once */ bus(dev)->INTENCLR = TWIM_INTEN_STOPPED_Msk | TWIM_INTEN_ERROR_Msk | TWIM_INTEN_LASTTX_Msk; mutex_unlock(&busy[dev]); }