| /* |
| * Copyright (c) 2020, Seagate Technology LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_lpc11u6x_i2c |
| |
| #include <kernel.h> |
| #include <drivers/i2c.h> |
| #include <drivers/pinmux.h> |
| #include <drivers/clock_control.h> |
| #include <dt-bindings/pinctrl/lpc11u6x-pinctrl.h> |
| #include "i2c_lpc11u6x.h" |
| |
| #define DEV_BASE(dev) (((struct lpc11u6x_i2c_config *)(dev->config))->base) |
| |
| static void lpc11u6x_i2c_set_bus_speed(const struct lpc11u6x_i2c_config *cfg, |
| const 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(const struct device *dev, |
| uint32_t dev_config) |
| { |
| const struct lpc11u6x_i2c_config *cfg = dev->config; |
| struct lpc11u6x_i2c_data *data = dev->data; |
| const 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(const struct device *dev, |
| struct i2c_msg *msgs, |
| uint8_t num_msgs, uint16_t addr) |
| { |
| const struct lpc11u6x_i2c_config *cfg = dev->config; |
| struct lpc11u6x_i2c_data *data = dev->data; |
| 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(const struct device *dev, |
| struct i2c_slave_config *cfg) |
| { |
| const struct lpc11u6x_i2c_config *dev_cfg = dev->config; |
| struct lpc11u6x_i2c_data *data = dev->data; |
| 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(const struct device *dev, |
| struct i2c_slave_config *cfg) |
| { |
| const struct lpc11u6x_i2c_config *dev_cfg = dev->config; |
| struct lpc11u6x_i2c_data *data = dev->data; |
| |
| 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(const struct device *dev) |
| { |
| struct lpc11u6x_i2c_data *data = dev->data; |
| struct lpc11u6x_i2c_regs *i2c = DEV_BASE(dev); |
| 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(const struct device *dev) |
| { |
| const struct lpc11u6x_i2c_config *cfg = dev->config; |
| struct lpc11u6x_i2c_data *data = dev->data; |
| const 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(const 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; \ |
| \ |
| I2C_DEVICE_DT_INST_DEFINE(idx, \ |
| lpc11u6x_i2c_init, \ |
| NULL, \ |
| &i2c_data_##idx, &i2c_cfg_##idx, \ |
| PRE_KERNEL_1, CONFIG_I2C_INIT_PRIORITY, \ |
| &i2c_api); \ |
| \ |
| static void lpc11u6x_i2c_isr_config_##idx(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(idx), \ |
| DT_INST_IRQ(idx, priority), \ |
| lpc11u6x_i2c_isr, DEVICE_DT_INST_GET(idx), 0); \ |
| \ |
| irq_enable(DT_INST_IRQN(idx)); \ |
| } |
| |
| DT_INST_FOREACH_STATUS_OKAY(LPC11U6X_I2C_INIT); |