drivers: i2c: Add support for LPC11U6X I2C controllers

This commit adds a driver for the LPC11U6X I2C controllers

Signed-off-by: Maxime Bittan <maxime.bittan@seagate.com>
This commit is contained in:
Maxime Bittan 2020-05-14 16:35:27 +02:00 committed by Carles Cufí
commit 9ca6b704a7
6 changed files with 529 additions and 0 deletions

View file

@ -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)

View file

@ -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"

View file

@ -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

417
drivers/i2c/i2c_lpc11u6x.c Normal file
View file

@ -0,0 +1,417 @@
/*
* 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_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);

View file

@ -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_ */

View file

@ -28,4 +28,11 @@ config UART_LPC11U6X
endif # SERIAL
if I2C
config I2C_LPC11U6X
default y
endif # I2C
endif # SOC_SERIES_LPC11U6X