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) 2019, NXP
|
||||
* Copyright 2019-2023, NXP
|
||||
* Copyright (c) 2022 Vestas Wind Systems A/S
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
|
@ -55,6 +55,14 @@ struct mcux_lpi2c_data {
|
|||
struct k_sem lock;
|
||||
struct k_sem device_sync_sem;
|
||||
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,
|
||||
|
@ -297,12 +305,179 @@ restore:
|
|||
}
|
||||
#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)
|
||||
{
|
||||
const struct mcux_lpi2c_config *config = dev->config;
|
||||
struct mcux_lpi2c_data *data = dev->data;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -360,6 +535,10 @@ static const struct i2c_driver_api mcux_lpi2c_driver_api = {
|
|||
#if CONFIG_I2C_MCUX_LPI2C_BUS_RECOVERY
|
||||
.recover_bus = mcux_lpi2c_recover_bus,
|
||||
#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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue