You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
10 KiB
386 lines
10 KiB
/* |
|
* Copyright (c) 2020, Seagate Technology LLC |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT nxp_lpc11u6x_i2c |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/i2c.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/irq.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; |
|
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 DEVICE_API(i2c, 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);
|
|
|