zephyr/drivers/i2c/i2c_sedi.c
Yuval Peress 8974c248cf rtio: Add default i2c submit handler
Use the RTIO work queue to fake the i2c submit calls for drivers which
haven't yet implemented the API. Applications can change the size of
the work queue pool depending on how much traffic they have on the buses.

Signed-off-by: Yuval Peress <peress@google.com>
2024-09-04 21:28:26 +02:00

246 lines
7.4 KiB
C

/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT intel_sedi_i2c
#include <stdint.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <sedi_driver_i2c.h>
#include <zephyr/pm/device.h>
#define I2C_SEDI_TIMEOUT_MS (1000)
struct i2c_sedi_context {
DEVICE_MMIO_RAM;
int sedi_device;
struct k_sem *sem;
struct k_mutex *mutex;
int err;
uint16_t addr_10bit;
};
struct i2c_sedi_config {
DEVICE_MMIO_ROM;
sedi_i2c_event_cb_t cb_sedi;
void (*irq_config)(const struct device *dev);
};
static int i2c_sedi_api_configure(const struct device *dev, uint32_t dev_config)
{
int ret;
int speed = I2C_SPEED_GET(dev_config);
int sedi_speed;
struct i2c_sedi_context *const context = dev->data;
context->addr_10bit = (dev_config & I2C_ADDR_10_BITS) ? SEDI_I2C_ADDRESS_10BIT : 0;
if (speed == I2C_SPEED_STANDARD) {
sedi_speed = SEDI_I2C_BUS_SPEED_STANDARD;
} else if (speed == I2C_SPEED_FAST) {
sedi_speed = SEDI_I2C_BUS_SPEED_FAST;
} else if (speed == I2C_SPEED_FAST_PLUS) {
sedi_speed = SEDI_I2C_BUS_SPEED_FAST_PLUS;
} else if (speed == I2C_SPEED_HIGH) {
sedi_speed = SEDI_I2C_BUS_SPEED_HIGH;
} else {
return -EINVAL;
}
k_mutex_lock(context->mutex, K_FOREVER);
ret = sedi_i2c_control(context->sedi_device, SEDI_I2C_BUS_SPEED, sedi_speed);
k_mutex_unlock(context->mutex);
if (ret != 0) {
return -EIO;
} else {
return 0;
}
}
static int i2c_sedi_api_full_io(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
uint16_t addr)
{
int ret = 0;
struct i2c_sedi_context *context = dev->data;
if (!num_msgs) {
return 0;
}
__ASSERT_NO_MSG(msgs);
k_mutex_lock(context->mutex, K_FOREVER);
pm_device_busy_set(dev);
for (int i = 0; i < num_msgs; i++) {
if ((msgs[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
ret = sedi_i2c_master_write_async(
context->sedi_device, addr | context->addr_10bit, msgs[i].buf,
msgs[i].len, (msgs[i].flags & I2C_MSG_STOP) == 0x0);
} else {
ret = sedi_i2c_master_read_async(
context->sedi_device, addr | context->addr_10bit, msgs[i].buf,
msgs[i].len, (msgs[i].flags & I2C_MSG_STOP) == 0x0);
}
if (ret != 0) {
ret = -EIO;
break;
}
ret = k_sem_take(context->sem, K_MSEC(I2C_SEDI_TIMEOUT_MS));
if (ret != 0) {
break;
}
if (context->err != 0) {
ret = -EIO;
break;
}
}
if (ret != 0) {
/* Abort current transfer */
sedi_i2c_control(context->sedi_device, SEDI_I2C_ABORT_TRANSFER, 0);
ret = -EIO;
}
pm_device_busy_clear(dev);
k_mutex_unlock(context->mutex);
return ret;
}
static const struct i2c_driver_api i2c_sedi_apis = {
.configure = i2c_sedi_api_configure,
.transfer = i2c_sedi_api_full_io,
#ifdef CONFIG_I2C_RTIO
.iodev_submit = i2c_iodev_submit_fallback,
#endif
};
#ifdef CONFIG_PM_DEVICE
static int i2c_sedi_suspend_device(const struct device *dev)
{
struct i2c_sedi_context *const context = dev->data;
int ret;
if (pm_device_is_busy(dev)) {
return -EBUSY;
}
ret = sedi_i2c_set_power(context->sedi_device, SEDI_POWER_SUSPEND);
if (ret != 0) {
return -EIO;
}
return 0;
}
static int i2c_sedi_resume_device_from_suspend(const struct device *dev)
{
struct i2c_sedi_context *const context = dev->data;
int ret;
ret = sedi_i2c_set_power(context->sedi_device, SEDI_POWER_FULL);
if (ret != 0) {
return -EIO;
}
return 0;
}
static int i2c_sedi_pm_action(const struct device *dev, enum pm_device_action action)
{
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
ret = i2c_sedi_suspend_device(dev);
break;
case PM_DEVICE_ACTION_RESUME:
ret = i2c_sedi_resume_device_from_suspend(dev);
break;
default:
ret = -ENOTSUP;
}
return ret;
}
#endif /* CONFIG_PM_DEVICE */
static int i2c_sedi_init(const struct device *dev)
{
int ret;
const struct i2c_sedi_config *const config = dev->config;
struct i2c_sedi_context *const context = dev->data;
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
ret = sedi_i2c_init(context->sedi_device, config->cb_sedi, DEVICE_MMIO_GET(dev));
if (ret != 0) {
return -EIO;
}
ret = sedi_i2c_set_power(context->sedi_device, SEDI_POWER_FULL);
if (ret != 0) {
return -EIO;
}
config->irq_config(dev);
return 0;
}
static void i2c_sedi_isr(const struct device *dev)
{
struct i2c_sedi_context *const context = dev->data;
sedi_i2c_isr_handler(context->sedi_device);
}
#define I2C_SEDI_IRQ_FLAGS_SENSE0(n) 0
#define I2C_SEDI_IRQ_FLAGS_SENSE1(n) DT_INST_IRQ(n, sense)
#define I2C_SEDI_IRQ_FLAGS(n) _CONCAT(I2C_SEDI_IRQ_FLAGS_SENSE, DT_INST_IRQ_HAS_CELL(n, sense))(n)
#define I2C_DEVICE_INIT_SEDI(n) \
static K_SEM_DEFINE(i2c_sedi_sem_##n, 0, 1); \
static K_MUTEX_DEFINE(i2c_sedi_mutex_##n); \
static struct i2c_sedi_context i2c_sedi_data_##n = { \
.sedi_device = DT_INST_PROP(n, peripheral_id), \
.sem = &i2c_sedi_sem_##n, \
.mutex = &i2c_sedi_mutex_##n, \
}; \
static void i2c_sedi_callback_##n(const uint32_t event) \
{ \
if (event == SEDI_I2C_EVENT_TRANSFER_DONE) { \
i2c_sedi_data_##n.err = 0; \
} else { \
i2c_sedi_data_##n.err = 1; \
} \
k_sem_give(i2c_sedi_data_##n.sem); \
}; \
static void i2c_sedi_irq_config_##n(const struct device *dev) \
{ \
ARG_UNUSED(dev); \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), i2c_sedi_isr, \
DEVICE_DT_INST_GET(n), I2C_SEDI_IRQ_FLAGS(n)); \
irq_enable(DT_INST_IRQN(n)); \
}; \
static const struct i2c_sedi_config i2c_sedi_config_##n = { \
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \
.cb_sedi = &i2c_sedi_callback_##n, \
.irq_config = &i2c_sedi_irq_config_##n, \
}; \
PM_DEVICE_DT_DEFINE(DT_NODELABEL(i2c##n), i2c_sedi_pm_action); \
I2C_DEVICE_DT_INST_DEFINE(n, i2c_sedi_init, PM_DEVICE_DT_GET(DT_NODELABEL(i2c##n)), \
&i2c_sedi_data_##n, &i2c_sedi_config_##n, PRE_KERNEL_2, \
CONFIG_I2C_INIT_PRIORITY, &i2c_sedi_apis);
DT_INST_FOREACH_STATUS_OKAY(I2C_DEVICE_INIT_SEDI)