drivers: i2c: enable i2c target mode for LPI2C driver
Enable I2C target mode for LPI2C driver. Verified with i2c_target_api test, on RT1060 EVK. Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
This commit is contained in:
parent
69dde41611
commit
158f63794f
1 changed files with 180 additions and 1 deletions
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2016 Freescale Semiconductor, Inc.
|
* Copyright (c) 2016 Freescale Semiconductor, Inc.
|
||||||
* Copyright (c) 2019, NXP
|
* Copyright 2019-2023, NXP
|
||||||
* Copyright (c) 2022 Vestas Wind Systems A/S
|
* Copyright (c) 2022 Vestas Wind Systems A/S
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
@ -55,6 +55,14 @@ struct mcux_lpi2c_data {
|
||||||
struct k_sem lock;
|
struct k_sem lock;
|
||||||
struct k_sem device_sync_sem;
|
struct k_sem device_sync_sem;
|
||||||
status_t callback_status;
|
status_t callback_status;
|
||||||
|
#ifdef CONFIG_I2C_TARGET
|
||||||
|
lpi2c_slave_handle_t target_handle;
|
||||||
|
struct i2c_target_config *target_cfg;
|
||||||
|
bool target_attached;
|
||||||
|
bool first_tx;
|
||||||
|
bool read_active;
|
||||||
|
bool send_ack;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static int mcux_lpi2c_configure(const struct device *dev,
|
static int mcux_lpi2c_configure(const struct device *dev,
|
||||||
|
@ -297,12 +305,179 @@ restore:
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY */
|
#endif /* CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY */
|
||||||
|
|
||||||
|
#ifdef CONFIG_I2C_TARGET
|
||||||
|
static void mcux_lpi2c_slave_irq_handler(const struct device *dev)
|
||||||
|
{
|
||||||
|
const struct mcux_lpi2c_config *config = dev->config;
|
||||||
|
struct mcux_lpi2c_data *data = dev->data;
|
||||||
|
const struct i2c_target_callbacks *target_cb = data->target_cfg->callbacks;
|
||||||
|
int ret;
|
||||||
|
uint32_t flags;
|
||||||
|
uint8_t i2c_data;
|
||||||
|
|
||||||
|
/* Note- the HAL provides a callback-based I2C slave API, but
|
||||||
|
* the API expects the user to provide a transmit buffer of
|
||||||
|
* a fixed length at the first byte received, and will not signal
|
||||||
|
* the user callback until this buffer is exhausted. This does not
|
||||||
|
* work well with the Zephyr API, which requires callbacks for
|
||||||
|
* every byte. For these reason, we handle the LPI2C IRQ
|
||||||
|
* directly.
|
||||||
|
*/
|
||||||
|
flags = LPI2C_SlaveGetStatusFlags(config->base);
|
||||||
|
|
||||||
|
if (flags & kLPI2C_SlaveAddressValidFlag) {
|
||||||
|
/* Read Slave address to clear flag */
|
||||||
|
LPI2C_SlaveGetReceivedAddress(config->base);
|
||||||
|
data->first_tx = true;
|
||||||
|
/* Reset to sending ACK, in case we NAK'ed before */
|
||||||
|
data->send_ack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & kLPI2C_SlaveRxReadyFlag) {
|
||||||
|
/* RX data is available, read it and issue callback */
|
||||||
|
i2c_data = (uint8_t)config->base->SRDR;
|
||||||
|
if (data->first_tx) {
|
||||||
|
data->first_tx = false;
|
||||||
|
if (target_cb->write_requested) {
|
||||||
|
ret = target_cb->write_requested(data->target_cfg);
|
||||||
|
if (ret < 0) {
|
||||||
|
/* NAK further bytes */
|
||||||
|
data->send_ack = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target_cb->write_received) {
|
||||||
|
ret = target_cb->write_received(data->target_cfg,
|
||||||
|
i2c_data);
|
||||||
|
if (ret < 0) {
|
||||||
|
/* NAK further bytes */
|
||||||
|
data->send_ack = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & kLPI2C_SlaveTxReadyFlag) {
|
||||||
|
/* Space is available in TX fifo, issue callback and write out */
|
||||||
|
if (data->first_tx) {
|
||||||
|
data->read_active = true;
|
||||||
|
data->first_tx = false;
|
||||||
|
if (target_cb->read_requested) {
|
||||||
|
ret = target_cb->read_requested(data->target_cfg,
|
||||||
|
&i2c_data);
|
||||||
|
if (ret < 0) {
|
||||||
|
/* Disable TX */
|
||||||
|
data->read_active = false;
|
||||||
|
} else {
|
||||||
|
/* Send I2C data */
|
||||||
|
config->base->STDR = i2c_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data->read_active) {
|
||||||
|
if (target_cb->read_processed) {
|
||||||
|
ret = target_cb->read_processed(data->target_cfg,
|
||||||
|
&i2c_data);
|
||||||
|
if (ret < 0) {
|
||||||
|
/* Disable TX */
|
||||||
|
data->read_active = false;
|
||||||
|
} else {
|
||||||
|
/* Send I2C data */
|
||||||
|
config->base->STDR = i2c_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & kLPI2C_SlaveStopDetectFlag) {
|
||||||
|
LPI2C_SlaveClearStatusFlags(config->base, flags);
|
||||||
|
if (target_cb->stop) {
|
||||||
|
target_cb->stop(data->target_cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & kLPI2C_SlaveTransmitAckFlag) {
|
||||||
|
LPI2C_SlaveTransmitAck(config->base, data->send_ack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcux_lpi2c_target_register(const struct device *dev,
|
||||||
|
struct i2c_target_config *target_config)
|
||||||
|
{
|
||||||
|
const struct mcux_lpi2c_config *config = dev->config;
|
||||||
|
struct mcux_lpi2c_data *data = dev->data;
|
||||||
|
lpi2c_slave_config_t slave_config;
|
||||||
|
uint32_t clock_freq;
|
||||||
|
|
||||||
|
LPI2C_MasterDeinit(config->base);
|
||||||
|
|
||||||
|
/* Get the clock frequency */
|
||||||
|
if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
|
||||||
|
&clock_freq)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target_config) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->target_attached) {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->target_attached = true;
|
||||||
|
data->target_cfg = target_config;
|
||||||
|
data->first_tx = false;
|
||||||
|
|
||||||
|
LPI2C_SlaveGetDefaultConfig(&slave_config);
|
||||||
|
slave_config.address0 = target_config->address;
|
||||||
|
/* Note- this setting enables clock stretching to allow the
|
||||||
|
* slave to respond to each byte with an ACK/NAK.
|
||||||
|
* this behavior may cause issues with some I2C controllers.
|
||||||
|
*/
|
||||||
|
slave_config.sclStall.enableAck = true;
|
||||||
|
LPI2C_SlaveInit(config->base, &slave_config, clock_freq);
|
||||||
|
/* Clear all flags. */
|
||||||
|
LPI2C_SlaveClearStatusFlags(config->base, (uint32_t)kLPI2C_SlaveClearFlags);
|
||||||
|
/* Enable interrupt */
|
||||||
|
LPI2C_SlaveEnableInterrupts(config->base,
|
||||||
|
(kLPI2C_SlaveTxReadyFlag |
|
||||||
|
kLPI2C_SlaveRxReadyFlag |
|
||||||
|
kLPI2C_SlaveStopDetectFlag |
|
||||||
|
kLPI2C_SlaveAddressValidFlag |
|
||||||
|
kLPI2C_SlaveTransmitAckFlag));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcux_lpi2c_target_unregister(const struct device *dev,
|
||||||
|
struct i2c_target_config *target_config)
|
||||||
|
{
|
||||||
|
const struct mcux_lpi2c_config *config = dev->config;
|
||||||
|
struct mcux_lpi2c_data *data = dev->data;
|
||||||
|
|
||||||
|
if (!data->target_attached) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->target_cfg = NULL;
|
||||||
|
data->target_attached = false;
|
||||||
|
|
||||||
|
LPI2C_SlaveDeinit(config->base);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_I2C_TARGET */
|
||||||
|
|
||||||
static void mcux_lpi2c_isr(const struct device *dev)
|
static void mcux_lpi2c_isr(const struct device *dev)
|
||||||
{
|
{
|
||||||
const struct mcux_lpi2c_config *config = dev->config;
|
const struct mcux_lpi2c_config *config = dev->config;
|
||||||
struct mcux_lpi2c_data *data = dev->data;
|
struct mcux_lpi2c_data *data = dev->data;
|
||||||
LPI2C_Type *base = config->base;
|
LPI2C_Type *base = config->base;
|
||||||
|
|
||||||
|
#ifdef CONFIG_I2C_TARGET
|
||||||
|
if (data->target_attached) {
|
||||||
|
mcux_lpi2c_slave_irq_handler(dev);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_I2C_TARGET */
|
||||||
|
|
||||||
LPI2C_MasterTransferHandleIRQ(base, &data->handle);
|
LPI2C_MasterTransferHandleIRQ(base, &data->handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +535,10 @@ static const struct i2c_driver_api mcux_lpi2c_driver_api = {
|
||||||
#if CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY
|
#if CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY
|
||||||
.recover_bus = mcux_lpi2c_recover_bus,
|
.recover_bus = mcux_lpi2c_recover_bus,
|
||||||
#endif /* CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY */
|
#endif /* CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY */
|
||||||
|
#if CONFIG_I2C_TARGET
|
||||||
|
.target_register = mcux_lpi2c_target_register,
|
||||||
|
.target_unregister = mcux_lpi2c_target_unregister,
|
||||||
|
#endif /* CONFIG_I2C_TARGET */
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_PINCTRL
|
#ifdef CONFIG_PINCTRL
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue