/* * Copyright (c) 2017, Texas Instruments Incorporated * * SPDX-License-Identifier: Apache-2.0 */ /* The logic here is adapted from SimpleLink SDK's I2CCC32XX.c module. */ #define DT_DRV_COMPAT ti_cc32xx_i2c #include #include #include #include /* Driverlib includes */ #include #include #include #include #include #define LOG_LEVEL CONFIG_I2C_LOG_LEVEL #include LOG_MODULE_REGISTER(i2c_cc32xx); #include "i2c-priv.h" #define I2C_MASTER_CMD_BURST_RECEIVE_START_NACK I2C_MASTER_CMD_BURST_SEND_START #define I2C_MASTER_CMD_BURST_RECEIVE_STOP \ I2C_MASTER_CMD_BURST_RECEIVE_ERROR_STOP #define I2C_MASTER_CMD_BURST_RECEIVE_CONT_NACK I2C_MASTER_CMD_BURST_SEND_CONT #define I2C_SEM_MASK \ COMMON_REG_I2C_Properties_Register_I2C_Properties_Register_M #define I2C_SEM_TAKE \ COMMON_REG_I2C_Properties_Register_I2C_Properties_Register_S #define IS_I2C_MSG_WRITE(flags) ((flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) #define DEV_CFG(dev) \ ((const struct i2c_cc32xx_config *const)(dev)->config_info) #define DEV_DATA(dev) \ ((struct i2c_cc32xx_data *const)(dev)->driver_data) #define DEV_BASE(dev) \ ((DEV_CFG(dev))->base) /* Since this driver does not explicitly enable the TX/RX FIFOs, there * are no interrupts received which can distinguish between read and write * completion. * So, we need the READ and WRITE state flags to determine whether the * completed transmission was started as a write or a read. * The ERROR flag is used to convey error status from the ISR back to the * I2C API without having to re-read I2C registers. */ enum i2c_cc32xx_state { /* I2C was primed for a write operation */ I2C_CC32XX_WRITE_MODE, /* I2C was primed for a read operation */ I2C_CC32XX_READ_MODE, /* I2C error occurred */ I2C_CC32XX_ERROR = 0xFF }; struct i2c_cc32xx_config { uint32_t base; uint32_t bitrate; unsigned int irq_no; }; struct i2c_cc32xx_data { struct k_sem mutex; struct k_sem transfer_complete; volatile enum i2c_cc32xx_state state; struct i2c_msg msg; /* Cache msg for transfer state machine */ uint16_t slave_addr; /* Cache slave address for ISR use */ }; static void configure_i2c_irq(const struct i2c_cc32xx_config *config); #define I2C_CLK_FREQ(n) DT_PROP(DT_INST_PHANDLE(n, clocks), clock_frequency) static int i2c_cc32xx_configure(struct device *dev, uint32_t dev_config_raw) { uint32_t base = DEV_BASE(dev); uint32_t bitrate_id; if (!(dev_config_raw & I2C_MODE_MASTER)) { return -EINVAL; } if (dev_config_raw & I2C_ADDR_10_BITS) { return -EINVAL; } switch (I2C_SPEED_GET(dev_config_raw)) { case I2C_SPEED_STANDARD: bitrate_id = 0U; break; case I2C_SPEED_FAST: bitrate_id = 1U; break; default: return -EINVAL; } MAP_I2CMasterInitExpClk(base, I2C_CLK_FREQ(0), bitrate_id); return 0; } static void i2c_cc32xx_prime_transfer(struct device *dev, struct i2c_msg *msg, uint16_t addr) { struct i2c_cc32xx_data *data = DEV_DATA(dev); uint32_t base = DEV_BASE(dev); /* Initialize internal counters and buf pointers: */ data->msg = *msg; data->slave_addr = addr; /* Start transfer in Transmit mode */ if (IS_I2C_MSG_WRITE(data->msg.flags)) { /* Specify the I2C slave address */ MAP_I2CMasterSlaveAddrSet(base, addr, false); /* Update the I2C state */ data->state = I2C_CC32XX_WRITE_MODE; /* Write data contents into data register */ MAP_I2CMasterDataPut(base, *((data->msg.buf)++)); /* Start the I2C transfer in master transmit mode */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_START); } else { /* Start transfer in Receive mode */ /* Specify the I2C slave address */ MAP_I2CMasterSlaveAddrSet(base, addr, true); /* Update the I2C mode */ data->state = I2C_CC32XX_READ_MODE; if (data->msg.len < 2) { /* Start the I2C transfer in master receive mode */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_START_NACK); } else { /* Start the I2C transfer in burst receive mode */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_START); } } } static int i2c_cc32xx_transfer(struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, uint16_t addr) { struct i2c_cc32xx_data *data = DEV_DATA(dev); int retval = 0; /* Acquire the driver mutex */ k_sem_take(&data->mutex, K_FOREVER); /* Iterate over all the messages */ for (int i = 0; i < num_msgs; i++) { /* Begin the transfer */ i2c_cc32xx_prime_transfer(dev, msgs, addr); /* Wait for the transfer to complete */ k_sem_take(&data->transfer_complete, K_FOREVER); /* Return an error if the transfer didn't complete */ if (data->state == I2C_CC32XX_ERROR) { retval = -EIO; break; } /* Move to the next message */ msgs++; } /* Release the mutex */ k_sem_give(&data->mutex); return retval; } static void i2c_cc32xx_isr_handle_write(uint32_t base, struct i2c_cc32xx_data *data) { /* Decrement write Counter */ data->msg.len--; /* Check if more data needs to be sent */ if (data->msg.len) { /* Write data contents into data register */ MAP_I2CMasterDataPut(base, *(data->msg.buf)); data->msg.buf++; if (data->msg.len < 2) { /* Everything has been sent, nothing to receive */ /* Send last byte with STOP bit */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_FINISH); } else { /* * Either there is more data to be transmitted or some * data needs to be received next */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_CONT); } } else { /* * No more data needs to be sent, so follow up with * a STOP bit. */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_STOP); } } static void i2c_cc32xx_isr_handle_read(uint32_t base, struct i2c_cc32xx_data *data) { /* Save the received data */ *(data->msg.buf) = MAP_I2CMasterDataGet(base); data->msg.buf++; /* Check if any data needs to be received */ data->msg.len--; if (data->msg.len) { if (data->msg.len > 1) { /* More data to be received */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_CONT); } else { /* * Send NACK because it's the last byte to be received */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_CONT_NACK); } } else { /* * No more data needs to be received, so follow up with a * STOP bit */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_RECEIVE_STOP); } } static void i2c_cc32xx_isr(void *arg) { struct device *dev = (struct device *)arg; uint32_t base = DEV_BASE(dev); struct i2c_cc32xx_data *data = DEV_DATA(dev); uint32_t err_status; uint32_t int_status; /* Get the error status of the I2C controller */ err_status = MAP_I2CMasterErr(base); /* Get interrupt cause (from I2CMRIS (raw interrupt) reg): */ int_status = MAP_I2CMasterIntStatusEx(base, 0); /* Clear interrupt source to avoid additional interrupts */ MAP_I2CMasterIntClearEx(base, int_status); LOG_DBG("primed state: %d; err_status: 0x%x; int_status: 0x%x", data->state, err_status, int_status); /* Handle errors: */ if ((err_status != I2C_MASTER_ERR_NONE) || (int_status & (I2C_MASTER_INT_ARB_LOST | I2C_MASTER_INT_TIMEOUT))) { /* Set so API can report I/O error: */ data->state = I2C_CC32XX_ERROR; if (!(err_status & (I2C_MASTER_ERR_ARB_LOST | I2C_MASTER_ERR_ADDR_ACK))) { /* Send a STOP bit to end I2C communications */ /* * I2C_MASTER_CMD_BURST_SEND_ERROR_STOP -and- * I2C_MASTER_CMD_BURST_RECEIVE_ERROR_STOP * have the same values */ MAP_I2CMasterControl(base, I2C_MASTER_CMD_BURST_SEND_ERROR_STOP); } /* Indicate transfer complete */ k_sem_give(&data->transfer_complete); /* Handle Stop: */ } else if (int_status & I2C_MASTER_INT_STOP) { /* Indicate transfer complete */ k_sem_give(&data->transfer_complete); /* Handle (read or write) transmit complete: */ } else if (int_status & (I2C_MASTER_INT_DATA | I2C_MASTER_INT_START)) { if (data->state == I2C_CC32XX_WRITE_MODE) { i2c_cc32xx_isr_handle_write(base, data); } if (data->state == I2C_CC32XX_READ_MODE) { i2c_cc32xx_isr_handle_read(base, data); } /* Some unanticipated H/W state: */ } else { __ASSERT(1, "Unanticipated I2C Interrupt!"); data->state = I2C_CC32XX_ERROR; k_sem_give(&data->transfer_complete); } } static int i2c_cc32xx_init(struct device *dev) { uint32_t base = DEV_BASE(dev); const struct i2c_cc32xx_config *config = DEV_CFG(dev); struct i2c_cc32xx_data *data = DEV_DATA(dev); uint32_t bitrate_cfg; int error; uint32_t regval; k_sem_init(&data->mutex, 1, UINT_MAX); k_sem_init(&data->transfer_complete, 0, UINT_MAX); /* In case of app restart: disable I2C module, clear NVIC interrupt */ /* Note: this was done *during* pinmux setup in SimpleLink SDK. */ MAP_I2CMasterDisable(base); /* Clear exception INT_I2CA0 */ MAP_IntPendClear((unsigned long)(config->irq_no + 16)); configure_i2c_irq(config); /* Take I2C hardware semaphore. */ regval = HWREG(COMMON_REG_BASE); regval = (regval & ~I2C_SEM_MASK) | (0x01 << I2C_SEM_TAKE); HWREG(COMMON_REG_BASE) = regval; /* Set to default configuration: */ bitrate_cfg = i2c_map_dt_bitrate(config->bitrate); error = i2c_cc32xx_configure(dev, I2C_MODE_MASTER | bitrate_cfg); if (error) { return error; } /* Clear any pending interrupts */ MAP_I2CMasterIntClear(base); /* Enable the I2C Master for operation */ MAP_I2CMasterEnable(base); /* Unmask I2C interrupts */ MAP_I2CMasterIntEnable(base); return 0; } static const struct i2c_driver_api i2c_cc32xx_driver_api = { .configure = i2c_cc32xx_configure, .transfer = i2c_cc32xx_transfer, }; static const struct i2c_cc32xx_config i2c_cc32xx_config = { .base = DT_INST_REG_ADDR(0), .bitrate = DT_INST_PROP(0, clock_frequency), .irq_no = DT_INST_IRQN(0), }; static struct i2c_cc32xx_data i2c_cc32xx_data; DEVICE_AND_API_INIT(i2c_cc32xx, DT_INST_LABEL(0), &i2c_cc32xx_init, &i2c_cc32xx_data, &i2c_cc32xx_config, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &i2c_cc32xx_driver_api); static void configure_i2c_irq(const struct i2c_cc32xx_config *config) { IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), i2c_cc32xx_isr, DEVICE_GET(i2c_cc32xx), 0); irq_enable(config->irq_no); }