/* * Copyright (c) 2020, Seagate Technology LLC * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_lpc11u6x_i2c #include #include #include #include #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; uint32_t speed; int ret; uint8_t mux_selection = PINCTRL_STATE_DEFAULT; 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: mux_selection = PINCTRL_STATE_FAST_PLUS; 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; } k_mutex_lock(&data->mutex, K_FOREVER); lpc11u6x_i2c_set_bus_speed(cfg, cfg->clock_dev, speed); ret = pinctrl_apply_state(cfg->pincfg, mux_selection); if (ret) { k_mutex_unlock(&data->mutex); return ret; } k_mutex_unlock(&data->mutex); return 0; } 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_target_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_TARGET_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_target_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; int err; err = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT); if (err) { return err; } if (!device_is_ready(cfg->clock_dev)) { return -ENODEV; } /* Configure clock and de-assert reset for I2Cx */ clock_control_on(cfg->clock_dev, (clock_control_subsys_t) cfg->clkid); /* Configure bus speed. Default is 100KHz */ lpc11u6x_i2c_set_bus_speed(cfg, cfg->clock_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, .target_register = lpc11u6x_i2c_slave_register, .target_unregister = lpc11u6x_i2c_slave_unregister, #ifdef CONFIG_I2C_RTIO .iodev_submit = i2c_iodev_submit_fallback, #endif }; #define LPC11U6X_I2C_INIT(idx) \ \ static void lpc11u6x_i2c_isr_config_##idx(const struct device *dev); \ \ PINCTRL_DT_INST_DEFINE(idx); \ \ static const struct lpc11u6x_i2c_config i2c_cfg_##idx = { \ .base = \ (struct lpc11u6x_i2c_regs *) DT_INST_REG_ADDR(idx), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \ .irq_config_func = lpc11u6x_i2c_isr_config_##idx, \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \ .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);