drivers: i3c: i3c attach/detach api

There are some needs to attach and reattach i3c/i2c devices at runtime
Some I2C devices can have special registers where the address can be
changed at runtime. Also some I3C devices can be powered off at runtime
freeing up the address space they take up. These new APIs allow for these
to be changed at runtime. This also moves some config/data in to a common
i3c config/data structure which would allow the api to operate on to be
common for all I3C drivers.

Signed-off-by: Ryan McClelland <ryanmcclelland@meta.com>
This commit is contained in:
Ryan McClelland 2022-12-09 00:30:43 -08:00 committed by Christopher Friedt
commit 62f22f8d3b
5 changed files with 516 additions and 101 deletions

View file

@ -115,6 +115,7 @@ int i3c_ccc_do_rstdaa_all(const struct device *controller)
int i3c_ccc_do_setdasa(const struct i3c_device_desc *target)
{
struct i3c_driver_data *bus_data = (struct i3c_driver_data *)target->bus->data;
struct i3c_ccc_payload ccc_payload;
struct i3c_ccc_target_payload ccc_tgt_payload;
uint8_t dyn_addr;
@ -133,6 +134,14 @@ int i3c_ccc_do_setdasa(const struct i3c_device_desc *target)
dyn_addr = (target->init_dynamic_addr ?
target->init_dynamic_addr : target->static_addr) << 1;
/* check that initial dynamic address is free before setting it */
if ((target->init_dynamic_addr != 0) &&
(target->init_dynamic_addr != target->static_addr)) {
if (!i3c_addr_slots_is_free(&bus_data->attached_dev.addr_slots, dyn_addr >> 1)) {
return -EINVAL;
}
}
ccc_tgt_payload.addr = target->static_addr;
ccc_tgt_payload.rnw = 0;
ccc_tgt_payload.data = &dyn_addr;
@ -148,6 +157,7 @@ int i3c_ccc_do_setdasa(const struct i3c_device_desc *target)
int i3c_ccc_do_setnewda(const struct i3c_device_desc *target, struct i3c_ccc_address new_da)
{
struct i3c_driver_data *bus_data = (struct i3c_driver_data *)target->bus->data;
struct i3c_ccc_payload ccc_payload;
struct i3c_ccc_target_payload ccc_tgt_payload;
uint8_t new_dyn_addr;
@ -165,6 +175,13 @@ int i3c_ccc_do_setnewda(const struct i3c_device_desc *target, struct i3c_ccc_add
*/
new_dyn_addr = new_da.addr << 1;
/* check that initial dynamic address is free before setting it */
if (target->dynamic_addr != new_da.addr) {
if (!i3c_addr_slots_is_free(&bus_data->attached_dev.addr_slots, new_da.addr)) {
return -EINVAL;
}
}
ccc_tgt_payload.addr = target->dynamic_addr;
ccc_tgt_payload.rnw = 0;
ccc_tgt_payload.data = &new_dyn_addr;

View file

@ -80,65 +80,61 @@ i3c_addr_slots_status(struct i3c_addr_slots *slots,
}
int i3c_addr_slots_init(struct i3c_addr_slots *slots,
const struct i3c_dev_list *dev_list)
int i3c_addr_slots_init(const struct device *dev)
{
struct i3c_driver_data *data =
(struct i3c_driver_data *)dev->data;
const struct i3c_driver_config *config =
(const struct i3c_driver_config *)dev->config;
int i, ret = 0;
struct i3c_device_desc *i3c_dev;
struct i3c_i2c_device_desc *i2c_dev;
__ASSERT_NO_MSG(slots != NULL);
__ASSERT_NO_MSG(dev != NULL);
(void)memset(slots, 0, sizeof(*slots));
(void)memset(&data->attached_dev.addr_slots, 0, sizeof(data->attached_dev.addr_slots));
sys_slist_init(&data->attached_dev.devices.i3c);
sys_slist_init(&data->attached_dev.devices.i2c);
for (i = 0; i <= 7; i++) {
/* Addresses 0 to 7 are reserved */
i3c_addr_slots_set(slots, i, I3C_ADDR_SLOT_STATUS_RSVD);
i3c_addr_slots_set(&data->attached_dev.addr_slots, i, I3C_ADDR_SLOT_STATUS_RSVD);
/*
* Addresses within a single bit error of broadcast address
* are also reserved.
*/
i3c_addr_slots_set(slots, I3C_BROADCAST_ADDR ^ BIT(i),
i3c_addr_slots_set(&data->attached_dev.addr_slots, I3C_BROADCAST_ADDR ^ BIT(i),
I3C_ADDR_SLOT_STATUS_RSVD);
}
/* The broadcast address is reserved */
i3c_addr_slots_set(slots, I3C_BROADCAST_ADDR,
i3c_addr_slots_set(&data->attached_dev.addr_slots, I3C_BROADCAST_ADDR,
I3C_ADDR_SLOT_STATUS_RSVD);
/*
* Mark all I2C addresses first.
*/
for (i = 0; i < config->dev_list.num_i2c; i++) {
i2c_dev = &config->dev_list.i2c[i];
ret = i3c_attach_i2c_device(i2c_dev);
if (ret != 0) {
/* Address slot is not free */
ret = -EINVAL;
goto out;
}
}
/*
* If there is a static address for the I3C devices, check
* if this address is free, and there is no other devices of
* the same (pre-assigned) address on the bus.
*/
for (i = 0; i < dev_list->num_i3c; i++) {
i3c_dev = &dev_list->i3c[i];
if (i3c_dev->static_addr != 0U) {
if (i3c_addr_slots_is_free(slots, i3c_dev->static_addr)) {
/*
* Mark address slot as I3C device for now to
* detect address collisons. This marking may be
* released during address assignment.
*/
i3c_addr_slots_mark_i3c(slots, i3c_dev->static_addr);
} else {
/* Address slot is not free */
ret = -EINVAL;
goto out;
}
}
}
/*
* Mark all I2C addresses.
*/
for (i = 0; i < dev_list->num_i2c; i++) {
i2c_dev = &dev_list->i2c[i];
if (i3c_addr_slots_is_free(slots, i2c_dev->addr)) {
i3c_addr_slots_mark_i2c(slots, i2c_dev->addr);
} else {
for (i = 0; i < config->dev_list.num_i3c; i++) {
i3c_dev = &config->dev_list.i3c[i];
ret = i3c_attach_i3c_device(i3c_dev);
if (ret != 0) {
/* Address slot is not free */
ret = -EINVAL;
goto out;
@ -185,6 +181,7 @@ struct i3c_device_desc *i3c_dev_list_find(const struct i3c_dev_list *dev_list,
__ASSERT_NO_MSG(dev_list != NULL);
/* this only searches known I3C PIDs */
for (i = 0; i < dev_list->num_i3c; i++) {
struct i3c_device_desc *desc = &dev_list->i3c[i];
@ -197,16 +194,16 @@ struct i3c_device_desc *i3c_dev_list_find(const struct i3c_dev_list *dev_list,
return ret;
}
struct i3c_device_desc *i3c_dev_list_i3c_addr_find(const struct i3c_dev_list *dev_list,
struct i3c_device_desc *i3c_dev_list_i3c_addr_find(struct i3c_dev_attached_list *dev_list,
uint8_t addr)
{
int i;
sys_snode_t *node;
struct i3c_device_desc *ret = NULL;
__ASSERT_NO_MSG(dev_list != NULL);
for (i = 0; i < dev_list->num_i3c; i++) {
struct i3c_device_desc *desc = &dev_list->i3c[i];
SYS_SLIST_FOR_EACH_NODE(&dev_list->devices.i3c, node) {
struct i3c_device_desc *desc = (void *)node;
if (desc->dynamic_addr == addr) {
ret = desc;
@ -217,16 +214,16 @@ struct i3c_device_desc *i3c_dev_list_i3c_addr_find(const struct i3c_dev_list *de
return ret;
}
struct i3c_i2c_device_desc *i3c_dev_list_i2c_addr_find(const struct i3c_dev_list *dev_list,
uint16_t addr)
struct i3c_i2c_device_desc *i3c_dev_list_i2c_addr_find(struct i3c_dev_attached_list *dev_list,
uint16_t addr)
{
int i;
sys_snode_t *node;
struct i3c_i2c_device_desc *ret = NULL;
__ASSERT_NO_MSG(dev_list != NULL);
for (i = 0; i < dev_list->num_i2c; i++) {
struct i3c_i2c_device_desc *desc = &dev_list->i2c[i];
SYS_SLIST_FOR_EACH_NODE(&dev_list->devices.i2c, node) {
struct i3c_i2c_device_desc *desc = (void *)node;
if (desc->addr == addr) {
ret = desc;
@ -237,6 +234,198 @@ struct i3c_i2c_device_desc *i3c_dev_list_i2c_addr_find(const struct i3c_dev_list
return ret;
}
int i3c_determine_default_addr(struct i3c_device_desc *target, uint8_t *addr)
{
struct i3c_driver_data *data = (struct i3c_driver_data *)target->bus->data;
/* If dynamic addr is set, then it assumed that it was assigned by a primary controller */
if (target->dynamic_addr == 0) {
/* It is assumed that SETDASA or ENTDAA will be run after this */
if (target->init_dynamic_addr != 0U) {
/* initial dynamic address is requested */
if (target->static_addr == 0) {
/* SA is set to 0, so DA will be set with ENTDAA */
if (i3c_addr_slots_is_free(&data->attached_dev.addr_slots,
target->init_dynamic_addr)) {
/* Set DA during ENTDAA */
*addr = target->init_dynamic_addr;
} else {
/* address is not free, get the next one */
*addr = i3c_addr_slots_next_free_find(
&data->attached_dev.addr_slots);
}
} else {
/* Use the init dynamic address as it's DA, but the RR will need to
* be first set with it's SA to run SETDASA, the RR address will
* need be updated after SETDASA with the request dynamic address
*/
if (i3c_addr_slots_is_free(&data->attached_dev.addr_slots,
target->static_addr)) {
*addr = target->static_addr;
} else {
/* static address has already been taken */
return -EINVAL;
}
}
} else {
/* no init dynamic address is requested */
if (target->static_addr != 0) {
if (i3c_addr_slots_is_free(&data->attached_dev.addr_slots,
target->static_addr)) {
/* static exists, set DA with same SA during SETDASA*/
*addr = target->static_addr;
} else {
/* static address has already been taken */
return -EINVAL;
}
} else {
/* pick a DA to use */
*addr = i3c_addr_slots_next_free_find(
&data->attached_dev.addr_slots);
}
}
} else {
*addr = target->dynamic_addr;
}
return 0;
}
int i3c_attach_i3c_device(struct i3c_device_desc *target)
{
struct i3c_driver_data *data = (struct i3c_driver_data *)target->bus->data;
const struct i3c_driver_api *api = (const struct i3c_driver_api *)target->bus->api;
sys_snode_t *node;
uint8_t addr = 0;
int status = 0;
/* check to see if the device has already been attached */
if (!sys_slist_is_empty(&data->attached_dev.devices.i3c)) {
SYS_SLIST_FOR_EACH_NODE(&data->attached_dev.devices.i3c, node) {
if (node == &target->node) {
return -EINVAL;
}
}
}
status = i3c_determine_default_addr(target, &addr);
if (status != 0) {
return status;
}
sys_slist_append(&data->attached_dev.devices.i3c, &target->node);
if (api->attach_i3c_device != NULL) {
status = api->attach_i3c_device(target->bus, target, addr);
}
i3c_addr_slots_mark_i3c(&data->attached_dev.addr_slots, addr);
return status;
}
int i3c_reattach_i3c_device(struct i3c_device_desc *target, uint8_t old_dyn_addr)
{
struct i3c_driver_data *data = (struct i3c_driver_data *)target->bus->data;
const struct i3c_driver_api *api = (const struct i3c_driver_api *)target->bus->api;
int status = 0;
if (!i3c_addr_slots_is_free(&data->attached_dev.addr_slots, target->dynamic_addr)) {
return -EINVAL;
}
if (api->reattach_i3c_device != NULL) {
status = api->reattach_i3c_device(target->bus, target, old_dyn_addr);
}
if (old_dyn_addr) {
/* mark the old address as free */
i3c_addr_slots_mark_free(&data->attached_dev.addr_slots, old_dyn_addr);
}
i3c_addr_slots_mark_i3c(&data->attached_dev.addr_slots, target->dynamic_addr);
return status;
}
int i3c_detach_i3c_device(struct i3c_device_desc *target)
{
struct i3c_driver_data *data = (struct i3c_driver_data *)target->bus->data;
const struct i3c_driver_api *api = (const struct i3c_driver_api *)target->bus->api;
int status = 0;
if (!sys_slist_is_empty(&data->attached_dev.devices.i3c)) {
if (!sys_slist_find_and_remove(&data->attached_dev.devices.i3c, &target->node)) {
return -EINVAL;
}
} else {
return -EINVAL;
}
if (api->detach_i3c_device != NULL) {
status = api->detach_i3c_device(target->bus, target);
}
i3c_addr_slots_mark_free(&data->attached_dev.addr_slots,
target->dynamic_addr ? target->dynamic_addr : target->static_addr);
return status;
}
int i3c_attach_i2c_device(struct i3c_i2c_device_desc *target)
{
struct i3c_driver_data *data = (struct i3c_driver_data *)target->bus->data;
const struct i3c_driver_api *api = (const struct i3c_driver_api *)target->bus->api;
sys_snode_t *node;
int status = 0;
/* check to see if the device has already been attached */
if (!sys_slist_is_empty(&data->attached_dev.devices.i2c)) {
SYS_SLIST_FOR_EACH_NODE(&data->attached_dev.devices.i2c, node) {
if (node == &target->node) {
return -EINVAL;
}
}
}
if (!i3c_addr_slots_is_free(&data->attached_dev.addr_slots, target->addr)) {
return -EINVAL;
}
sys_slist_append(&data->attached_dev.devices.i2c, &target->node);
if (api->attach_i2c_device != NULL) {
status = api->attach_i2c_device(target->bus, target);
}
i3c_addr_slots_mark_i2c(&data->attached_dev.addr_slots, target->addr);
return status;
}
int i3c_detach_i2c_device(struct i3c_i2c_device_desc *target)
{
struct i3c_driver_data *data = (struct i3c_driver_data *)target->bus->data;
const struct i3c_driver_api *api = (const struct i3c_driver_api *)target->bus->api;
int status = 0;
if (!sys_slist_is_empty(&data->attached_dev.devices.i2c)) {
if (!sys_slist_find_and_remove(&data->attached_dev.devices.i2c, &target->node)) {
return -EINVAL;
}
} else {
return -EINVAL;
}
if (api->detach_i2c_device != NULL) {
status = api->detach_i2c_device(target->bus, target);
}
i3c_addr_slots_mark_free(&data->attached_dev.addr_slots, target->addr);
return status;
}
int i3c_dev_list_daa_addr_helper(struct i3c_addr_slots *addr_slots,
const struct i3c_dev_list *dev_list,
uint64_t pid, bool must_match,
@ -408,8 +597,16 @@ static int i3c_bus_setdasa(const struct device *dev,
if (ret == 0) {
desc->dynamic_addr = (desc->init_dynamic_addr ? desc->init_dynamic_addr
: desc->static_addr);
i3c_reattach_i3c_device(desc, desc->static_addr);
if (desc->dynamic_addr != desc->static_addr) {
if (i3c_reattach_i3c_device(desc, desc->static_addr) != 0) {
LOG_ERR("Failed to reattach %s (%d)", desc->dev->name, ret);
}
}
} else {
/* SETDASA failed, detach it from the controller */
if (i3c_detach_i3c_device(desc) != 0) {
LOG_ERR("Failed to detach %s (%d)", desc->dev->name, ret);
}
LOG_ERR("SETDASA error on address 0x%x (%d)",
desc->static_addr, ret);
continue;

View file

@ -66,6 +66,9 @@ LOG_MODULE_REGISTER(i3c_mcux, CONFIG_I3C_MCUX_LOG_LEVEL);
#define I3C_MSTATUS_IBITYPE_HJ I3C_MSTATUS_IBITYPE(3)
struct mcux_i3c_config {
/** Common I3C Driver Config */
struct i3c_driver_config common;
/** Pointer to controller registers. */
I3C_Type *base;
@ -80,23 +83,17 @@ struct mcux_i3c_config {
const struct pinctrl_dev_config *pincfg;
#endif
/** I3C/I2C device list struct. */
struct i3c_dev_list device_list;
/** Interrupt configuration function. */
void (*irq_config_func)(const struct device *dev);
};
struct mcux_i3c_data {
/** Common I3C Driver Data */
struct i3c_driver_data common;
/** Configuration parameter to be used with HAL. */
i3c_master_config_t ctrl_config_hal;
/** Controller configuration parameters */
struct i3c_config_controller ctrl_config;
/** Address slots */
struct i3c_addr_slots addr_slots;
/** Semaphore to serialize access for applications. */
struct k_sem lock;
@ -820,7 +817,7 @@ struct i3c_device_desc *mcux_i3c_device_find(const struct device *dev,
{
const struct mcux_i3c_config *config = dev->config;
return i3c_dev_list_find(&config->device_list, id);
return i3c_dev_list_find(&config->common.dev_list, id);
}
/**
@ -839,9 +836,9 @@ struct i3c_device_desc *mcux_i3c_device_find(const struct device *dev,
static struct i3c_i2c_device_desc *
mcux_i3c_i2c_device_find(const struct device *dev, uint16_t addr)
{
const struct mcux_i3c_config *config = dev->config;
struct cdns_i3c_data *data = dev->data;
return i3c_dev_list_i2c_addr_find(&config->device_list, addr);
return i3c_dev_list_i2c_addr_find(&data->common.attached_dev, addr);
}
/**
@ -1348,8 +1345,8 @@ static int mcux_i3c_do_daa(const struct device *dev)
LOG_DBG("DAA: Rcvd PID 0x%04x%08x", vendor_id, part_no);
ret = i3c_dev_list_daa_addr_helper(&data->addr_slots,
&config->device_list, pid,
ret = i3c_dev_list_daa_addr_helper(&data->common.attached_dev.addr_slots,
&config->common.dev_list, pid,
false, false,
&target, &dyn_addr);
if (ret != 0) {
@ -1362,7 +1359,7 @@ static int mcux_i3c_do_daa(const struct device *dev)
target->dcr = rx_buf[7];
/* Mark the address as I3C device */
i3c_addr_slots_mark_i3c(&data->addr_slots, dyn_addr);
i3c_addr_slots_mark_i3c(&data->common.attached_dev.addr_slots, dyn_addr);
/*
* If the device has static address, after address assignment,
@ -1371,7 +1368,8 @@ static int mcux_i3c_do_daa(const struct device *dev)
* newly assigned one.
*/
if ((target->static_addr != 0U) && (dyn_addr != target->static_addr)) {
i3c_addr_slots_mark_free(&data->addr_slots, dyn_addr);
i3c_addr_slots_mark_free(&data->common.attached_dev.addr_slots,
dyn_addr);
}
/* Emit process DAA again to send the address to the device */
@ -1533,7 +1531,7 @@ static void mcux_i3c_ibi_work(struct k_work *work)
const struct device *dev = i3c_ibi_work->controller;
const struct mcux_i3c_config *config = dev->config;
struct mcux_i3c_data *data = dev->data;
const struct i3c_dev_list *dev_list = &config->device_list;
struct i3c_dev_attached_list *dev_list = &data->common.attached_dev;
I3C_Type *base = config->base;
struct i3c_device_desc *target = NULL;
uint32_t mstatus, ibitype, ibiaddr;
@ -1980,7 +1978,7 @@ static int mcux_i3c_config_get(const struct device *dev,
goto out_configure;
}
(void)memcpy(config, &data->ctrl_config, sizeof(data->ctrl_config));
(void)memcpy(config, &data->common.ctrl_config, sizeof(data->common.ctrl_config));
out_configure:
return ret;
@ -1996,10 +1994,10 @@ static int mcux_i3c_init(const struct device *dev)
const struct mcux_i3c_config *config = dev->config;
struct mcux_i3c_data *data = dev->data;
I3C_Type *base = config->base;
struct i3c_config_controller *ctrl_config = &data->ctrl_config;
struct i3c_config_controller *ctrl_config = &data->common.ctrl_config;
int ret = 0;
ret = i3c_addr_slots_init(&data->addr_slots, &config->device_list);
ret = i3c_addr_slots_init(dev);
if (ret != 0) {
goto err_out;
}
@ -2038,10 +2036,10 @@ static int mcux_i3c_init(const struct device *dev)
}
/* Currently can only act as primary controller. */
data->ctrl_config.is_secondary = false;
data->common.ctrl_config.is_secondary = false;
/* HDR mode not supported at the moment. */
data->ctrl_config.supported_hdr = 0U;
data->common.ctrl_config.supported_hdr = 0U;
ret = mcux_i3c_configure(dev, I3C_CONFIG_CONTROLLER, ctrl_config);
if (ret != 0) {
@ -2070,7 +2068,7 @@ static int mcux_i3c_init(const struct device *dev)
config->irq_config_func(dev);
/* Perform bus initialization */
ret = i3c_bus_init(dev, &config->device_list);
ret = i3c_bus_init(dev, &config->common.dev_list);
err_out:
return ret;
@ -2133,9 +2131,9 @@ static const struct i3c_driver_api mcux_i3c_driver_api = {
#define I3C_MCUX_DEVICE(id) \
I3C_MCUX_PINCTRL_DEFINE(id) \
static void mcux_i3c_config_func_##id(const struct device *dev); \
static struct i3c_device_desc mcux_i3c_device_array[] = \
static struct i3c_device_desc mcux_i3c_device_array_##id[] = \
I3C_DEVICE_ARRAY_DT_INST(id); \
static struct i3c_i2c_device_desc mcux_i3c_i2c_device_array[] = \
static struct i3c_i2c_device_desc mcux_i3c_i2c_device_array_##id[] = \
I3C_I2C_DEVICE_ARRAY_DT_INST(id); \
static const struct mcux_i3c_config mcux_i3c_config_##id = { \
.base = (I3C_Type *) DT_INST_REG_ADDR(id), \
@ -2143,16 +2141,16 @@ static const struct i3c_driver_api mcux_i3c_driver_api = {
.clock_subsys = \
(clock_control_subsys_t)DT_INST_CLOCKS_CELL(id, name), \
.irq_config_func = mcux_i3c_config_func_##id, \
.device_list.i3c = mcux_i3c_device_array, \
.device_list.num_i3c = ARRAY_SIZE(mcux_i3c_device_array), \
.device_list.i2c = mcux_i3c_i2c_device_array, \
.device_list.num_i2c = ARRAY_SIZE(mcux_i3c_i2c_device_array), \
.common.dev_list.i3c = mcux_i3c_device_array_##id, \
.common.dev_list.num_i3c = ARRAY_SIZE(mcux_i3c_device_array_##id), \
.common.dev_list.i2c = mcux_i3c_i2c_device_array_##id, \
.common.dev_list.num_i2c = ARRAY_SIZE(mcux_i3c_i2c_device_array_##id), \
I3C_MCUX_PINCTRL_INIT(id) \
}; \
static struct mcux_i3c_data mcux_i3c_data_##id = { \
.clocks.i3c_od_scl_hz = DT_INST_PROP_OR(id, i3c_od_scl_hz, 0), \
.ctrl_config.scl.i3c = DT_INST_PROP_OR(id, i3c_scl_hz, 0), \
.ctrl_config.scl.i2c = DT_INST_PROP_OR(id, i2c_scl_hz, 0), \
.common.ctrl_config.scl.i3c = DT_INST_PROP_OR(id, i3c_scl_hz, 0), \
.common.ctrl_config.scl.i2c = DT_INST_PROP_OR(id, i2c_scl_hz, 0), \
.clocks.clk_div_pp = DT_INST_PROP(id, clk_divider), \
.clocks.clk_div_od = DT_INST_PROP(id, clk_divider_slow), \
.clocks.clk_div_tc = DT_INST_PROP(id, clk_divider_tc), \