62f22f8d3b
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>
497 lines
13 KiB
C
497 lines
13 KiB
C
/*
|
|
* Copyright (c) 2022 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <zephyr/toolchain.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
|
|
#include <zephyr/drivers/i3c.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_DECLARE(i3c, CONFIG_I3C_LOG_LEVEL);
|
|
|
|
int i3c_ccc_do_getbcr(struct i3c_device_desc *target,
|
|
struct i3c_ccc_getbcr *bcr)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
__ASSERT_NO_MSG(bcr != NULL);
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 1;
|
|
ccc_tgt_payload.data = &bcr->bcr;
|
|
ccc_tgt_payload.data_len = sizeof(bcr->bcr);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_GETBCR;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_getdcr(struct i3c_device_desc *target,
|
|
struct i3c_ccc_getdcr *dcr)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
__ASSERT_NO_MSG(dcr != NULL);
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 1;
|
|
ccc_tgt_payload.data = &dcr->dcr;
|
|
ccc_tgt_payload.data_len = sizeof(dcr->dcr);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_GETDCR;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_getpid(struct i3c_device_desc *target,
|
|
struct i3c_ccc_getpid *pid)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
__ASSERT_NO_MSG(pid != NULL);
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 1;
|
|
ccc_tgt_payload.data = &pid->pid[0];
|
|
ccc_tgt_payload.data_len = sizeof(pid->pid);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_GETPID;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_rstact_all(const struct device *controller,
|
|
enum i3c_ccc_rstact_defining_byte action)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
uint8_t def_byte;
|
|
|
|
__ASSERT_NO_MSG(controller != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_RSTACT(true);
|
|
|
|
def_byte = (uint8_t)action;
|
|
ccc_payload.ccc.data = &def_byte;
|
|
ccc_payload.ccc.data_len = 1U;
|
|
|
|
return i3c_do_ccc(controller, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_rstdaa_all(const struct device *controller)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
|
|
__ASSERT_NO_MSG(controller != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_RSTDAA;
|
|
|
|
return i3c_do_ccc(controller, &ccc_payload);
|
|
}
|
|
|
|
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;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
|
|
if ((target->static_addr == 0U) || (target->dynamic_addr != 0U)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Note that the 7-bit address needs to start at bit 1
|
|
* (aka left-justified). So shift left by 1;
|
|
*/
|
|
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;
|
|
ccc_tgt_payload.data_len = 1;
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_SETDASA;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
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;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
|
|
if (target->dynamic_addr == 0U) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Note that the 7-bit address needs to start at bit 1
|
|
* (aka left-justified). So shift left by 1;
|
|
*/
|
|
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;
|
|
ccc_tgt_payload.data_len = 1;
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_SETNEWDA;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_events_all_set(const struct device *controller,
|
|
bool enable, struct i3c_ccc_events *events)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
|
|
__ASSERT_NO_MSG(controller != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
|
|
ccc_payload.ccc.id = enable ? I3C_CCC_ENEC(true) : I3C_CCC_DISEC(true);
|
|
|
|
ccc_payload.ccc.data = &events->events;
|
|
ccc_payload.ccc.data_len = sizeof(events->events);
|
|
|
|
return i3c_do_ccc(controller, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_events_set(struct i3c_device_desc *target,
|
|
bool enable, struct i3c_ccc_events *events)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
|
|
if (target->dynamic_addr == 0U) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 0;
|
|
ccc_tgt_payload.data = &events->events;
|
|
ccc_tgt_payload.data_len = sizeof(events->events);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = enable ? I3C_CCC_ENEC(false) : I3C_CCC_DISEC(false);
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_setmwl_all(const struct device *controller,
|
|
const struct i3c_ccc_mwl *mwl)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
uint8_t data[2];
|
|
|
|
__ASSERT_NO_MSG(controller != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
|
|
ccc_payload.ccc.id = I3C_CCC_SETMWL(true);
|
|
|
|
ccc_payload.ccc.data = &data[0];
|
|
ccc_payload.ccc.data_len = sizeof(data);
|
|
|
|
/* The actual data is MSB first. So order the data. */
|
|
data[0] = (uint8_t)((mwl->len & 0xFF00U) >> 8);
|
|
data[1] = (uint8_t)(mwl->len & 0xFFU);
|
|
|
|
return i3c_do_ccc(controller, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_setmwl(const struct i3c_device_desc *target,
|
|
const struct i3c_ccc_mwl *mwl)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
uint8_t data[2];
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 0;
|
|
ccc_tgt_payload.data = &data[0];
|
|
ccc_tgt_payload.data_len = sizeof(data);
|
|
|
|
ccc_payload.ccc.id = I3C_CCC_SETMWL(false);
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
/* The actual length is MSB first. So order the data. */
|
|
data[0] = (uint8_t)((mwl->len & 0xFF00U) >> 8);
|
|
data[1] = (uint8_t)(mwl->len & 0xFFU);
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_getmwl(const struct i3c_device_desc *target,
|
|
struct i3c_ccc_mwl *mwl)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
uint8_t data[2];
|
|
int ret;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
__ASSERT_NO_MSG(mwl != NULL);
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 1;
|
|
ccc_tgt_payload.data = &data[0];
|
|
ccc_tgt_payload.data_len = sizeof(data);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_GETMWL;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
ret = i3c_do_ccc(target->bus, &ccc_payload);
|
|
|
|
if (ret == 0) {
|
|
/* The actual length is MSB first. So order the data. */
|
|
mwl->len = (data[0] << 8) | data[1];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int i3c_ccc_do_setmrl_all(const struct device *controller,
|
|
const struct i3c_ccc_mrl *mrl,
|
|
bool has_ibi_size)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
uint8_t data[3];
|
|
|
|
__ASSERT_NO_MSG(controller != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
|
|
ccc_payload.ccc.id = I3C_CCC_SETMRL(true);
|
|
|
|
ccc_payload.ccc.data = &data[0];
|
|
ccc_payload.ccc.data_len = has_ibi_size ? 3 : 2;
|
|
|
|
/* The actual length is MSB first. So order the data. */
|
|
data[0] = (uint8_t)((mrl->len & 0xFF00U) >> 8);
|
|
data[1] = (uint8_t)(mrl->len & 0xFFU);
|
|
|
|
if (has_ibi_size) {
|
|
data[2] = mrl->ibi_len;
|
|
}
|
|
|
|
return i3c_do_ccc(controller, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_setmrl(const struct i3c_device_desc *target,
|
|
const struct i3c_ccc_mrl *mrl)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
uint8_t data[3];
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 0;
|
|
ccc_tgt_payload.data = &data[0];
|
|
|
|
ccc_payload.ccc.id = I3C_CCC_SETMRL(false);
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
/* The actual length is MSB first. So order the data. */
|
|
data[0] = (uint8_t)((mrl->len & 0xFF00U) >> 8);
|
|
data[1] = (uint8_t)(mrl->len & 0xFFU);
|
|
|
|
if ((target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE)
|
|
== I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE) {
|
|
ccc_tgt_payload.data_len = 3;
|
|
|
|
data[2] = mrl->ibi_len;
|
|
} else {
|
|
ccc_tgt_payload.data_len = 2;
|
|
}
|
|
|
|
return i3c_do_ccc(target->bus, &ccc_payload);
|
|
}
|
|
|
|
int i3c_ccc_do_getmrl(const struct i3c_device_desc *target,
|
|
struct i3c_ccc_mrl *mrl)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
uint8_t data[3];
|
|
bool has_ibi_sz;
|
|
int ret;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
__ASSERT_NO_MSG(mrl != NULL);
|
|
|
|
has_ibi_sz = (target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE)
|
|
== I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE;
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 1;
|
|
ccc_tgt_payload.data = &data[0];
|
|
ccc_tgt_payload.data_len = has_ibi_sz ? 3 : 2;
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_GETMRL;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
ret = i3c_do_ccc(target->bus, &ccc_payload);
|
|
|
|
if (ret == 0) {
|
|
/* The actual length is MSB first. So order the data. */
|
|
mrl->len = (data[0] << 8) | data[1];
|
|
|
|
if (has_ibi_sz) {
|
|
mrl->ibi_len = data[2];
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int i3c_ccc_do_getstatus(const struct i3c_device_desc *target,
|
|
union i3c_ccc_getstatus *status,
|
|
enum i3c_ccc_getstatus_fmt fmt,
|
|
enum i3c_ccc_getstatus_defbyte defbyte)
|
|
{
|
|
struct i3c_ccc_payload ccc_payload;
|
|
struct i3c_ccc_target_payload ccc_tgt_payload;
|
|
uint8_t defining_byte;
|
|
uint8_t data[2];
|
|
int ret;
|
|
|
|
__ASSERT_NO_MSG(target != NULL);
|
|
__ASSERT_NO_MSG(target->bus != NULL);
|
|
__ASSERT_NO_MSG(status != NULL);
|
|
|
|
ccc_tgt_payload.addr = target->dynamic_addr;
|
|
ccc_tgt_payload.rnw = 1;
|
|
ccc_tgt_payload.data = &data[0];
|
|
|
|
if (fmt == GETSTATUS_FORMAT_1) {
|
|
ccc_tgt_payload.data_len = 2;
|
|
} else if (fmt == GETSTATUS_FORMAT_2) {
|
|
switch (defbyte) {
|
|
case GETSTATUS_FORMAT_2_TGTSTAT:
|
|
__fallthrough;
|
|
case GETSTATUS_FORMAT_2_PRECR:
|
|
ccc_tgt_payload.data_len = 2;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
} else {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
memset(&ccc_payload, 0, sizeof(ccc_payload));
|
|
ccc_payload.ccc.id = I3C_CCC_GETSTATUS;
|
|
ccc_payload.targets.payloads = &ccc_tgt_payload;
|
|
ccc_payload.targets.num_targets = 1;
|
|
|
|
if (fmt == GETSTATUS_FORMAT_2) {
|
|
defining_byte = (uint8_t)defbyte;
|
|
|
|
ccc_payload.ccc.data = &defining_byte;
|
|
ccc_payload.ccc.data_len = 1;
|
|
}
|
|
|
|
ret = i3c_do_ccc(target->bus, &ccc_payload);
|
|
|
|
if (ret == 0) {
|
|
/* Received data is MSB first. So order the data. */
|
|
if (fmt == GETSTATUS_FORMAT_1) {
|
|
status->fmt1.status = (data[0] << 8) | data[1];
|
|
} else if (fmt == GETSTATUS_FORMAT_2) {
|
|
switch (defbyte) {
|
|
case GETSTATUS_FORMAT_2_TGTSTAT:
|
|
__fallthrough;
|
|
case GETSTATUS_FORMAT_2_PRECR:
|
|
status->fmt2.raw_u16 = (data[0] << 8) | data[1];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|