diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index f47c01fccb8..db239948d29 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_CC32XX i2c_cc32xx.c) zephyr_library_sources_ifdef(CONFIG_I2C_ESP32 i2c_esp32.c) zephyr_library_sources_ifdef(CONFIG_I2C_GPIO i2c_gpio.c) zephyr_library_sources_ifdef(CONFIG_I2C_IMX i2c_imx.c) +zephyr_library_sources_ifdef(CONFIG_I2C_LPC11U6X i2c_lpc11u6x.c) zephyr_library_sources_ifdef(CONFIG_I2C_XEC i2c_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_I2C_MCUX i2c_mcux.c) zephyr_library_sources_ifdef(CONFIG_I2C_MCUX_FLEXCOMM i2c_mcux_flexcomm.c) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 726734bb3c6..3c51ee8e125 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -37,6 +37,7 @@ source "drivers/i2c/Kconfig.sifive" source "drivers/i2c/Kconfig.stm32" source "drivers/i2c/Kconfig.sam0" source "drivers/i2c/Kconfig.litex" +source "drivers/i2c/Kconfig.lpc11u6x" config I2C_INIT_PRIORITY int "Init priority" diff --git a/drivers/i2c/Kconfig.lpc11u6x b/drivers/i2c/Kconfig.lpc11u6x new file mode 100644 index 00000000000..f86690f5994 --- /dev/null +++ b/drivers/i2c/Kconfig.lpc11u6x @@ -0,0 +1,8 @@ +# Copyright (c) 2020 Seagate Technology LLC +# SPDX-License-Identifier: Apache-2.0 + +config I2C_LPC11U6X + bool "LPC11U6X I2C driver" + depends on SOC_SERIES_LPC11U6X + help + Enable I2C support on the LPC11U6X SoCs diff --git a/drivers/i2c/i2c_lpc11u6x.c b/drivers/i2c/i2c_lpc11u6x.c new file mode 100644 index 00000000000..42466a29244 --- /dev/null +++ b/drivers/i2c/i2c_lpc11u6x.c @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2020, Seagate Technology LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_lpc11u6x_i2c + +#include +#include +#include +#include +#include +#include "i2c_lpc11u6x.h" + +#define DEV_CFG(dev) ((dev)->config_info) +#define DEV_BASE(dev) (((struct lpc11u6x_i2c_config *) DEV_CFG((dev)))->base) +#define DEV_DATA(dev) ((dev)->driver_data) + +static void lpc11u6x_i2c_set_bus_speed(const struct lpc11u6x_i2c_config *cfg, + struct device *clk_dev, + uint32_t speed) +{ + uint32_t clk, div; + + clock_control_get_rate(clk_dev, (clock_control_subsys_t) cfg->clkid, + &clk); + div = clk / speed; + + cfg->base->sclh = div / 2; + cfg->base->scll = div - (div / 2); +} + +static int lpc11u6x_i2c_configure(struct device *dev, uint32_t dev_config) +{ + const struct lpc11u6x_i2c_config *cfg = DEV_CFG(dev); + struct lpc11u6x_i2c_data *data = DEV_DATA(dev); + struct device *clk_dev, *pinmux_dev; + uint32_t speed, flags = 0; + + switch (I2C_SPEED_GET(dev_config)) { + case I2C_SPEED_STANDARD: + speed = 100000; + break; + case I2C_SPEED_FAST: + speed = 400000; + break; + case I2C_SPEED_FAST_PLUS: + flags |= IOCON_FASTI2C_EN; + speed = 1000000; + break; + case I2C_SPEED_HIGH: + case I2C_SPEED_ULTRA: + return -ENOTSUP; + default: + return -EINVAL; + } + + if (dev_config & I2C_ADDR_10_BITS) { + return -ENOTSUP; + } + + clk_dev = device_get_binding(cfg->clock_drv); + if (!clk_dev) { + return -EINVAL; + } + + k_mutex_lock(&data->mutex, K_FOREVER); + lpc11u6x_i2c_set_bus_speed(cfg, clk_dev, speed); + + if (!flags) { + goto exit; + } + + pinmux_dev = device_get_binding(cfg->scl_pinmux_drv); + if (!pinmux_dev) { + goto err; + } + pinmux_pin_set(pinmux_dev, cfg->scl_pin, cfg->scl_flags | flags); + + pinmux_dev = device_get_binding(cfg->sda_pinmux_drv); + if (!pinmux_dev) { + goto err; + } + pinmux_pin_set(pinmux_dev, cfg->sda_pin, cfg->sda_flags | flags); + +exit: + k_mutex_unlock(&data->mutex); + return 0; +err: + k_mutex_unlock(&data->mutex); + return -EINVAL; +} + +static int lpc11u6x_i2c_transfer(struct device *dev, struct i2c_msg *msgs, + uint8_t num_msgs, uint16_t addr) +{ + const struct lpc11u6x_i2c_config *cfg = DEV_CFG(dev); + struct lpc11u6x_i2c_data *data = DEV_DATA(dev); + int ret = 0; + + if (!num_msgs) { + return 0; + } + + k_mutex_lock(&data->mutex, K_FOREVER); + + data->transfer.msgs = msgs; + data->transfer.curr_buf = msgs->buf; + data->transfer.curr_len = msgs->len; + data->transfer.nr_msgs = num_msgs; + data->transfer.addr = addr; + + /* Reset all control bits */ + cfg->base->con_clr = LPC11U6X_I2C_CONTROL_SI | + LPC11U6X_I2C_CONTROL_STOP | LPC11U6X_I2C_CONTROL_START; + + /* Send start and wait for completion */ + data->transfer.status = LPC11U6X_I2C_STATUS_BUSY; + cfg->base->con_set = LPC11U6X_I2C_CONTROL_START; + + k_sem_take(&data->completion, K_FOREVER); + + if (data->transfer.status != LPC11U6X_I2C_STATUS_OK) { + ret = -EIO; + } + data->transfer.status = LPC11U6X_I2C_STATUS_INACTIVE; + + /* If a slave is registered, put the controller in slave mode */ + if (data->slave) { + cfg->base->con_set = LPC11U6X_I2C_CONTROL_AA; + } + + k_mutex_unlock(&data->mutex); + return ret; +} + +static int lpc11u6x_i2c_slave_register(struct device *dev, + struct i2c_slave_config *cfg) +{ + const struct lpc11u6x_i2c_config *dev_cfg = DEV_CFG(dev); + struct lpc11u6x_i2c_data *data = DEV_DATA(dev); + int ret = 0; + + if (!cfg) { + return -EINVAL; + } + + if (cfg->flags & I2C_SLAVE_FLAGS_ADDR_10_BITS) { + return -ENOTSUP; + } + + k_mutex_lock(&data->mutex, K_FOREVER); + if (data->slave) { + ret = -EBUSY; + goto exit; + } + + data->slave = cfg; + /* Configure controller to act as slave */ + dev_cfg->base->addr0 = (cfg->address << 1); + dev_cfg->base->con_clr = LPC11U6X_I2C_CONTROL_START | + LPC11U6X_I2C_CONTROL_STOP | LPC11U6X_I2C_CONTROL_SI; + dev_cfg->base->con_set = LPC11U6X_I2C_CONTROL_AA; + +exit: + k_mutex_unlock(&data->mutex); + return ret; +} + + +static int lpc11u6x_i2c_slave_unregister(struct device *dev, + struct i2c_slave_config *cfg) +{ + const struct lpc11u6x_i2c_config *dev_cfg = DEV_CFG(dev); + struct lpc11u6x_i2c_data *data = DEV_DATA(dev); + + if (!cfg) { + return -EINVAL; + } + if (data->slave != cfg) { + return -EINVAL; + } + + k_mutex_lock(&data->mutex, K_FOREVER); + data->slave = NULL; + dev_cfg->base->con_clr = LPC11U6X_I2C_CONTROL_AA; + k_mutex_unlock(&data->mutex); + + return 0; +} + +static void lpc11u6x_i2c_isr(void *arg) +{ + struct lpc11u6x_i2c_data *data = DEV_DATA((struct device *)arg); + struct lpc11u6x_i2c_regs *i2c = DEV_BASE((struct device *) arg); + struct lpc11u6x_i2c_current_transfer *transfer = &data->transfer; + uint32_t clear = LPC11U6X_I2C_CONTROL_SI; + uint32_t set = 0; + uint8_t val; + + switch (i2c->stat) { + /* Master TX states */ + case LPC11U6X_I2C_MASTER_TX_START: + case LPC11U6X_I2C_MASTER_TX_RESTART: + i2c->dat = (transfer->addr << 1) | + (transfer->msgs->flags & I2C_MSG_READ); + clear |= LPC11U6X_I2C_CONTROL_START; + transfer->curr_buf = transfer->msgs->buf; + transfer->curr_len = transfer->msgs->len; + break; + + case LPC11U6X_I2C_MASTER_TX_ADR_ACK: + case LPC11U6X_I2C_MASTER_TX_DAT_ACK: + if (!transfer->curr_len) { + transfer->msgs++; + transfer->nr_msgs--; + if (!transfer->nr_msgs) { + transfer->status = LPC11U6X_I2C_STATUS_OK; + set |= LPC11U6X_I2C_CONTROL_STOP; + } else { + set |= LPC11U6X_I2C_CONTROL_START; + } + } else { + i2c->dat = transfer->curr_buf[0]; + transfer->curr_buf++; + transfer->curr_len--; + } + break; + + /* Master RX states */ + case LPC11U6X_I2C_MASTER_RX_DAT_NACK: + transfer->msgs++; + transfer->nr_msgs--; + set |= (transfer->nr_msgs ? LPC11U6X_I2C_CONTROL_START : + LPC11U6X_I2C_CONTROL_STOP); + if (!transfer->nr_msgs) { + transfer->status = LPC11U6X_I2C_STATUS_OK; + } + /* fallthrough */ + case LPC11U6X_I2C_MASTER_RX_DAT_ACK: + transfer->curr_buf[0] = i2c->dat; + transfer->curr_buf++; + transfer->curr_len--; + /* fallthrough */ + case LPC11U6X_I2C_MASTER_RX_ADR_ACK: + if (transfer->curr_len <= 1) { + clear |= LPC11U6X_I2C_CONTROL_AA; + } else { + set |= LPC11U6X_I2C_CONTROL_AA; + } + break; + + /* Slave States */ + case LPC11U6X_I2C_SLAVE_RX_ADR_ACK: + case LPC11U6X_I2C_SLAVE_RX_ARB_LOST_ADR_ACK: + case LPC11U6X_I2C_SLAVE_RX_GC_ACK: + case LPC11U6X_I2C_SLAVE_RX_ARB_LOST_GC_ACK: + if (data->slave->callbacks->write_requested(data->slave)) { + clear |= LPC11U6X_I2C_CONTROL_AA; + } + break; + + case LPC11U6X_I2C_SLAVE_RX_DAT_ACK: + case LPC11U6X_I2C_SLAVE_RX_GC_DAT_ACK: + val = i2c->dat; + if (data->slave->callbacks->write_received(data->slave, val)) { + clear |= LPC11U6X_I2C_CONTROL_AA; + } + break; + + case LPC11U6X_I2C_SLAVE_RX_DAT_NACK: + case LPC11U6X_I2C_SLAVE_RX_GC_DAT_NACK: + val = i2c->dat; + data->slave->callbacks->write_received(data->slave, val); + data->slave->callbacks->stop(data->slave); + set |= LPC11U6X_I2C_CONTROL_AA; + break; + + case LPC11U6X_I2C_SLAVE_RX_STOP: + data->slave->callbacks->stop(data->slave); + set |= LPC11U6X_I2C_CONTROL_AA; + break; + + case LPC11U6X_I2C_SLAVE_TX_ADR_ACK: + case LPC11U6X_I2C_SLAVE_TX_ARB_LOST_ADR_ACK: + if (data->slave->callbacks->read_requested(data->slave, &val)) { + clear |= LPC11U6X_I2C_CONTROL_AA; + } + i2c->dat = val; + break; + case LPC11U6X_I2C_SLAVE_TX_DAT_ACK: + if (data->slave->callbacks->read_processed(data->slave, &val)) { + clear |= LPC11U6X_I2C_CONTROL_AA; + } + i2c->dat = val; + break; + case LPC11U6X_I2C_SLAVE_TX_DAT_NACK: + case LPC11U6X_I2C_SLAVE_TX_LAST_BYTE: + data->slave->callbacks->stop(data->slave); + set |= LPC11U6X_I2C_CONTROL_AA; + break; + + /* Error cases */ + case LPC11U6X_I2C_MASTER_TX_ADR_NACK: + case LPC11U6X_I2C_MASTER_RX_ADR_NACK: + case LPC11U6X_I2C_MASTER_TX_DAT_NACK: + case LPC11U6X_I2C_MASTER_TX_ARB_LOST: + transfer->status = LPC11U6X_I2C_STATUS_FAIL; + set = LPC11U6X_I2C_CONTROL_STOP; + break; + + default: + set = LPC11U6X_I2C_CONTROL_STOP; + break; + } + + i2c->con_clr = clear; + i2c->con_set = set; + if ((transfer->status != LPC11U6X_I2C_STATUS_BUSY) && + (transfer->status != LPC11U6X_I2C_STATUS_INACTIVE)) { + k_sem_give(&data->completion); + } +} + + +static int lpc11u6x_i2c_init(struct device *dev) +{ + const struct lpc11u6x_i2c_config *cfg = DEV_CFG(dev); + struct lpc11u6x_i2c_data *data = DEV_DATA(dev); + struct device *pinmux_dev, *clk_dev; + + /* Configure SCL and SDA pins */ + pinmux_dev = device_get_binding(cfg->scl_pinmux_drv); + if (!pinmux_dev) { + return -EINVAL; + } + pinmux_pin_set(pinmux_dev, cfg->scl_pin, cfg->scl_flags); + + pinmux_dev = device_get_binding(cfg->sda_pinmux_drv); + if (!pinmux_dev) { + return -EINVAL; + } + pinmux_pin_set(pinmux_dev, cfg->sda_pin, cfg->sda_flags); + + /* Configure clock and de-assert reset for I2Cx */ + clk_dev = device_get_binding(cfg->clock_drv); + if (!clk_dev) { + return -EINVAL; + } + clock_control_on(clk_dev, (clock_control_subsys_t) cfg->clkid); + + /* Configure bus speed. Default is 100KHz */ + lpc11u6x_i2c_set_bus_speed(cfg, clk_dev, 100000); + + /* Clear all control bytes and enable I2C interface */ + cfg->base->con_clr = LPC11U6X_I2C_CONTROL_AA | LPC11U6X_I2C_CONTROL_SI | + LPC11U6X_I2C_CONTROL_START | LPC11U6X_I2C_CONTROL_I2C_EN; + cfg->base->con_set = LPC11U6X_I2C_CONTROL_I2C_EN; + + /* Initialize mutex and semaphore */ + k_mutex_init(&data->mutex); + k_sem_init(&data->completion, 0, 1); + + data->transfer.status = LPC11U6X_I2C_STATUS_INACTIVE; + /* Configure IRQ */ + cfg->irq_config_func(dev); + return 0; +} + +static const struct i2c_driver_api i2c_api = { + .configure = lpc11u6x_i2c_configure, + .transfer = lpc11u6x_i2c_transfer, + .slave_register = lpc11u6x_i2c_slave_register, + .slave_unregister = lpc11u6x_i2c_slave_unregister, +}; + +#define LPC11U6X_I2C_INIT(idx) \ + \ +static void lpc11u6x_i2c_isr_config_##idx(struct device *dev); \ + \ +static const struct lpc11u6x_i2c_config i2c_cfg_##idx = { \ + .base = \ + (struct lpc11u6x_i2c_regs *) DT_INST_REG_ADDR(idx), \ + .clock_drv = DT_LABEL(DT_INST_PHANDLE(idx, clocks)), \ + .scl_pinmux_drv = \ + DT_LABEL(DT_INST_PHANDLE_BY_NAME(idx, pinmuxs, scl)), \ + .sda_pinmux_drv = \ + DT_LABEL(DT_INST_PHANDLE_BY_NAME(idx, pinmuxs, sda)), \ + .irq_config_func = lpc11u6x_i2c_isr_config_##idx, \ + .scl_flags = \ + DT_INST_PHA_BY_NAME(idx, pinmuxs, scl, function), \ + .sda_flags = \ + DT_INST_PHA_BY_NAME(idx, pinmuxs, sda, function), \ + .scl_pin = DT_INST_PHA_BY_NAME(idx, pinmuxs, scl, pin), \ + .sda_pin = DT_INST_PHA_BY_NAME(idx, pinmuxs, sda, pin), \ + .clkid = DT_INST_PHA_BY_IDX(idx, clocks, 0, clkid), \ +}; \ + \ +static struct lpc11u6x_i2c_data i2c_data_##idx; \ + \ +DEVICE_AND_API_INIT(lpc11u6x_i2c_##idx, DT_INST_LABEL(idx), \ + &lpc11u6x_i2c_init, \ + &i2c_data_##idx, &i2c_cfg_##idx, \ + PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS, \ + &i2c_api); \ + \ +static void lpc11u6x_i2c_isr_config_##idx(struct device *dev) \ +{ \ + IRQ_CONNECT(DT_INST_IRQN(idx), \ + DT_INST_IRQ(idx, priority), \ + lpc11u6x_i2c_isr, DEVICE_GET(lpc11u6x_i2c_##idx), 0); \ + \ + irq_enable(DT_INST_IRQN(idx)); \ +} + +DT_INST_FOREACH_STATUS_OKAY(LPC11U6X_I2C_INIT); diff --git a/drivers/i2c/i2c_lpc11u6x.h b/drivers/i2c/i2c_lpc11u6x.h new file mode 100644 index 00000000000..95ddb34bab4 --- /dev/null +++ b/drivers/i2c/i2c_lpc11u6x.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, Seagate Technology LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_I2C_I2C_LPC11U6X_H_ +#define ZEPHYR_DRIVERS_I2C_I2C_LPC11U6X_H_ + +#define LPC11U6X_I2C_CONTROL_AA (1 << 2) +#define LPC11U6X_I2C_CONTROL_SI (1 << 3) +#define LPC11U6X_I2C_CONTROL_STOP (1 << 4) +#define LPC11U6X_I2C_CONTROL_START (1 << 5) +#define LPC11U6X_I2C_CONTROL_I2C_EN (1 << 6) + +/* I2C controller states */ +#define LPC11U6X_I2C_MASTER_TX_START 0x08 +#define LPC11U6X_I2C_MASTER_TX_RESTART 0x10 +#define LPC11U6X_I2C_MASTER_TX_ADR_ACK 0x18 +#define LPC11U6X_I2C_MASTER_TX_ADR_NACK 0x20 +#define LPC11U6X_I2C_MASTER_TX_DAT_ACK 0x28 +#define LPC11U6X_I2C_MASTER_TX_DAT_NACK 0x30 +#define LPC11U6X_I2C_MASTER_TX_ARB_LOST 0x38 + +#define LPC11U6X_I2C_MASTER_RX_ADR_ACK 0x40 +#define LPC11U6X_I2C_MASTER_RX_ADR_NACK 0x48 +#define LPC11U6X_I2C_MASTER_RX_DAT_ACK 0x50 +#define LPC11U6X_I2C_MASTER_RX_DAT_NACK 0x58 + +#define LPC11U6X_I2C_SLAVE_RX_ADR_ACK 0x60 +#define LPC11U6X_I2C_SLAVE_RX_ARB_LOST_ADR_ACK 0x68 +#define LPC11U6X_I2C_SLAVE_RX_GC_ACK 0x70 +#define LPC11U6X_I2C_SLAVE_RX_ARB_LOST_GC_ACK 0x78 +#define LPC11U6X_I2C_SLAVE_RX_DAT_ACK 0x80 +#define LPC11U6X_I2C_SLAVE_RX_DAT_NACK 0x88 +#define LPC11U6X_I2C_SLAVE_RX_GC_DAT_ACK 0x90 +#define LPC11U6X_I2C_SLAVE_RX_GC_DAT_NACK 0x98 +#define LPC11U6X_I2C_SLAVE_RX_STOP 0xA0 + +#define LPC11U6X_I2C_SLAVE_TX_ADR_ACK 0xA8 +#define LPC11U6X_I2C_SLAVE_TX_ARB_LOST_ADR_ACK 0xB0 +#define LPC11U6X_I2C_SLAVE_TX_DAT_ACK 0xB8 +#define LPC11U6X_I2C_SLAVE_TX_DAT_NACK 0xC0 +#define LPC11U6X_I2C_SLAVE_TX_LAST_BYTE 0xC8 + +/* Transfer Status */ +#define LPC11U6X_I2C_STATUS_BUSY 0x01 +#define LPC11U6X_I2C_STATUS_OK 0x02 +#define LPC11U6X_I2C_STATUS_FAIL 0x03 +#define LPC11U6X_I2C_STATUS_INACTIVE 0x04 + +struct lpc11u6x_i2c_regs { + volatile uint32_t con_set; /* Control set */ + volatile const uint32_t stat; /* Status */ + volatile uint32_t dat; /* Data */ + volatile uint32_t addr0; /* Slave address 0 */ + volatile uint32_t sclh; /* SCL Duty Cycle */ + volatile uint32_t scll; /* SCL Duty Cycle */ + volatile uint32_t con_clr; /* Control clear */ + volatile uint32_t mm_ctrl; /* Monitor mode control */ + volatile uint32_t addr[3]; /* Slave address {1,2,3} */ + volatile const uint32_t data_buffer; /* Data buffer */ + volatile uint32_t mask[4]; /* Slave address mask */ +}; + +struct lpc11u6x_i2c_config { + struct lpc11u6x_i2c_regs *base; + char *clock_drv; + char *scl_pinmux_drv; + char *sda_pinmux_drv; + void (*irq_config_func)(struct device *dev); + uint32_t clkid; + uint32_t scl_flags; + uint32_t sda_flags; + uint8_t scl_pin; + uint8_t sda_pin; +}; + +struct lpc11u6x_i2c_current_transfer { + struct i2c_msg *msgs; + uint8_t *curr_buf; + uint8_t curr_len; + uint8_t nr_msgs; + uint8_t addr; + uint8_t status; +}; + +struct lpc11u6x_i2c_data { + struct lpc11u6x_i2c_current_transfer transfer; + struct i2c_slave_config *slave; + struct k_sem completion; + struct k_mutex mutex; +}; + +#endif /* ZEPHYR_DRIVERS_I2C_I2C_LPC11U6X_H_ */ diff --git a/soc/arm/nxp_lpc/lpc11u6x/Kconfig.defconfig.series b/soc/arm/nxp_lpc/lpc11u6x/Kconfig.defconfig.series index 3d4816fe089..5a887ca4a12 100644 --- a/soc/arm/nxp_lpc/lpc11u6x/Kconfig.defconfig.series +++ b/soc/arm/nxp_lpc/lpc11u6x/Kconfig.defconfig.series @@ -28,4 +28,11 @@ config UART_LPC11U6X endif # SERIAL +if I2C + +config I2C_LPC11U6X + default y + +endif # I2C + endif # SOC_SERIES_LPC11U6X