Skip to content

Commit

Permalink
drivers: i3c: cdns: controller handoff
Browse files Browse the repository at this point in the history
add controller handoff

Signed-off-by: Ryan McClelland <[email protected]>
  • Loading branch information
XenuIsWatching committed Sep 17, 2024
1 parent 34c22c5 commit f92cd11
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 21 deletions.
177 changes: 156 additions & 21 deletions drivers/i3c/i3c_cdns.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,13 +561,19 @@ struct cdns_i3c_config {

/* Driver instance data */
struct cdns_i3c_data {
const struct device *dev;
struct i3c_driver_data common;
struct cdns_i3c_hw_config hw_cfg;
struct k_mutex bus_lock;
struct cdns_i3c_i2c_dev_data cdns_i3c_i2c_priv_data[I3C_MAX_DEVS];
struct cdns_i3c_xfer xfer;
struct i3c_target_config *target_config;
struct k_work deftgts_work;
#ifdef CONFIG_I3C_USE_IBI
struct k_sem ibi_hj_complete;
struct k_sem ibi_cr_complete;
#endif
struct k_sem ch_complete;
uint32_t free_rr_slots;
uint16_t fifo_bytes_read;
uint8_t max_devs;
Expand Down Expand Up @@ -1116,6 +1122,31 @@ static int cdns_i3c_target_ibi_raise_hj(const struct device *dev)
return 0;
}

static int cdns_i3c_target_ibi_raise_cr(const struct device *dev)
{
const struct cdns_i3c_config *config = dev->config;
struct cdns_i3c_data *data = dev->data;

/* Check if target does not have a DA assigned to it */
if (!(sys_read32(config->base + SLV_STATUS1) & SLV_STATUS1_HAS_DA)) {
LOG_ERR("%s: CR not available, DA not assigned", dev->name);
return -EACCES;
}
/* Check if CR requests DISEC CCC with DISMR field set has been received */
if (sys_read32(config->base + SLV_STATUS1) & SLV_STATUS1_MR_DIS) {
LOG_ERR("%s: CR requests are currently disabled by DISEC", dev->name);
return -EAGAIN;
}

sys_write32(CTRL_MST_INIT | sys_read32(config->base + CTRL), config->base + CTRL);
k_sem_reset(&data->ibi_cr_complete);
if (k_sem_take(&data->ibi_cr_complete, K_MSEC(500)) != 0) {
LOG_ERR("%s: timeout waiting for GETACCCR after CR", dev->name);
return -ETIMEDOUT;
}
return 0;
}

static int cdns_i3c_target_ibi_raise_intr(const struct device *dev, struct i3c_ibi *request)
{
const struct cdns_i3c_config *config = dev->config;
Expand Down Expand Up @@ -1148,12 +1179,18 @@ static int cdns_i3c_target_ibi_raise_intr(const struct device *dev, struct i3c_i

static int cdns_i3c_target_ibi_raise(const struct device *dev, struct i3c_ibi *request)
{
const struct cdns_i3c_config *config = dev->config;
struct cdns_i3c_data *data = dev->data;

if (request == NULL) {
return -EINVAL;
}

/* make sure we are not currently the active controller */
if (sys_read32(config->base + MST_STATUS0) & MST_STATUS0_MASTER_MODE) {
return -EACCES;
}

switch (request->ibi_type) {
case I3C_IBI_TARGET_INTR:
/* Check IP Revision since older versions of CDNS IP do not support IBI interrupt*/
Expand All @@ -1163,8 +1200,7 @@ static int cdns_i3c_target_ibi_raise(const struct device *dev, struct i3c_ibi *r
return -ENOTSUP;
}
case I3C_IBI_CONTROLLER_ROLE_REQUEST:
/* TODO: Cadence I3C can support CR, but not implemented yet */
return -ENOTSUP;
return cdns_i3c_target_ibi_raise_cr(dev);
case I3C_IBI_HOTJOIN:
return cdns_i3c_target_ibi_raise_hj(dev);
default:
Expand Down Expand Up @@ -1466,6 +1502,12 @@ static int cdns_i3c_do_ccc(const struct device *dev, struct i3c_ccc_payload *pay
}

ret = data->xfer.ret;

/* TODO: decide if this is the right approach or add a new separate API for CH */
/* Wait for Controller Handoff to finish */
if (payload->ccc.id == I3C_CCC_GETACCCR) {
ret = k_sem_take(&data->ch_complete, K_MSEC(1000));
}
error:
k_mutex_unlock(&data->bus_lock);

Expand Down Expand Up @@ -2343,6 +2385,47 @@ static void cdns_i3c_handle_ibi(const struct device *dev, uint32_t ibir)
}
}

static void cdns_i3c_handle_cr(const struct device *dev, uint32_t ibir)
{
const struct cdns_i3c_config *config = dev->config;
struct cdns_i3c_data *data = dev->data;

/* The slave ID returned here is the device ID in the SIR map NOT the device ID
* in the RR map.
*/
uint8_t slave_id = IBIR_SLVID(ibir);

if (slave_id == IBIR_SLVID_INV) {
/* DA does not match any value among SIR map */
return;
}

uint32_t dev_id_rr0 = sys_read32(config->base + DEV_ID_RR0(slave_id + 1));
uint8_t dyn_addr = DEV_ID_RR0_GET_DEV_ADDR(dev_id_rr0);
struct i3c_device_desc *desc =
i3c_dev_list_i3c_addr_find(&data->common.attached_dev, dyn_addr);

/*
* Check for NAK or error conditions.
*
* Note: The logging is for debugging only so will be compiled out in most cases.
* However, if the log level for this module is DEBUG and log mode is IMMEDIATE or MINIMAL,
* this option is also set this may cause problems due to being inside an ISR.
*/
if (!(IBIR_ACKED & ibir)) {
LOG_DBG("%s: NAK for slave ID %u", dev->name, (unsigned int)slave_id);
return;
}
if (ibir & IBIR_ERROR) {
LOG_ERR("%s: Data overflow", dev->name);
return;
}

if (i3c_ibi_work_enqueue_controller_request(desc) != 0) {
LOG_ERR("%s: Error enqueue IBI IRQ work", dev->name);
}
}

static void cdns_i3c_handle_hj(const struct device *dev, uint32_t ibir)
{
if (!(IBIR_ACKED & ibir)) {
Expand Down Expand Up @@ -2371,7 +2454,7 @@ static void cnds_i3c_master_demux_ibis(const struct device *dev)
cdns_i3c_handle_hj(dev, ibir);
break;
case IBIR_TYPE_MR:
/* not implemented */
cdns_i3c_handle_cr(dev, ibir);
break;
default:
break;
Expand All @@ -2385,11 +2468,19 @@ static void cdns_i3c_target_ibi_hj_complete(const struct device *dev)

k_sem_give(&data->ibi_hj_complete);
}

static void cdns_i3c_target_ibi_cr_complete(const struct device *dev)
{
struct cdns_i3c_data *data = dev->data;

k_sem_give(&data->ibi_cr_complete);
}
#endif

static void cdns_i3c_irq_handler(const struct device *dev)
{
const struct cdns_i3c_config *config = dev->config;
struct cdns_i3c_data *data = dev->data;

if (sys_read32(config->base + MST_STATUS0) & MST_STATUS0_MASTER_MODE) {
uint32_t int_st = sys_read32(config->base + MST_ISR);
Expand Down Expand Up @@ -2449,7 +2540,6 @@ static void cdns_i3c_irq_handler(const struct device *dev)
}
} else {
uint32_t int_sl = sys_read32(config->base + SLV_ISR);
struct cdns_i3c_data *data = dev->data;
const struct i3c_target_callbacks *target_cb =
data->target_config ? data->target_config->callbacks : NULL;
/* Clear interrupts */
Expand Down Expand Up @@ -2515,21 +2605,23 @@ static void cdns_i3c_irq_handler(const struct device *dev)
if (int_sl & SLV_INT_DA_UPD) {
LOG_INF("%s: DA updated to 0x%02lx", dev->name,
SLV_STATUS1_DA(sys_read32(config->base + SLV_STATUS1)));
/* HJ could send a DISEC which would trigger the SLV_INT_EVENT_UP bit,
* but it's still expected to eventually send a DAA
*/
#ifdef CONFIG_I3C_USE_IBI
cdns_i3c_target_ibi_hj_complete(dev);
#endif
}

/* HJ complete and DA has been assigned */
/* HJ complete and DA has been assigned or HJ NACK'ed or DISEC disabled HJ */
if (int_sl & SLV_INT_HJ_DONE) {
#ifdef CONFIG_I3C_USE_IBI
cdns_i3c_target_ibi_hj_complete(dev);
#endif
}

/* Controllership has been been given */
if (int_sl & SLV_INT_MR_DONE) {
/* TODO: implement support for controllership handoff */
#ifdef CONFIG_I3C_USE_IBI
cdns_i3c_target_ibi_cr_complete(dev);
if (target_cb != NULL && target_cb->controller_handoff_cb) {
target_cb->controller_handoff_cb(data->target_config);
}
#endif
}

/* EISC or DISEC has been received */
Expand Down Expand Up @@ -2617,7 +2709,7 @@ static void cdns_i3c_irq_handler(const struct device *dev)
}
}

/*SLV DDR TX THR*/
/* SLV DDR TX THR */
if (int_sl & SLV_INT_DDR_TX_THR) {
int status = 0;

Expand All @@ -2640,6 +2732,12 @@ static void cdns_i3c_irq_handler(const struct device *dev)
}
}
}

/* DEFTGTS */
if (int_sl & SLV_INT_DEFSLVS) {
/* Execute outside of the ISR context */
k_work_submit(&data->deftgts_work);
}
}
}

Expand Down Expand Up @@ -2985,6 +3083,20 @@ static int cdns_i3c_i2c_api_transfer(const struct device *dev, struct i2c_msg *m
return ret;
}

static int cdns_i3c_target_controller_handoff(const struct device *dev, bool accept)
{
const struct cdns_i3c_config *config = dev->config;
uint32_t ctrl = sys_read32(config->base + CTRL);

if (accept) {
sys_write32(ctrl | CTRL_MST_ACK, config->base + CTRL);
} else {
sys_write32(ctrl & ~CTRL_MST_ACK, config->base + CTRL);
}

return 0;
}

/**
* Determine I3C bus mode from the i2c devices on the bus
*
Expand Down Expand Up @@ -3045,6 +3157,21 @@ static uint8_t cdns_i3c_clk_to_data_turnaround(const struct device *dev)
return (THD_DELAY_MAX - thd_delay);
}

static void i3c_cdns_deftgts_work_fn(struct k_work *work)
{
struct cdns_i3c_data *data;
const struct device *dev;
uint32_t devs;

data = CONTAINER_OF(work, struct cdns_i3c_data, deftgts_work);
dev = data->dev;

devs = sys_read32(config->base + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK;
data->free_rr_slots = GENMASK(data->max_devs, 1) & ~devs;

/* TODO: do me */
}

/**
* @brief Initialize the hardware.
*
Expand All @@ -3056,6 +3183,8 @@ static int cdns_i3c_bus_init(const struct device *dev)
const struct cdns_i3c_config *config = dev->config;
struct i3c_config_controller *ctrl_config = &data->common.ctrl_config;

data->dev = dev;

cdns_i3c_read_hw_cfg(dev);

/* Clear all retaining regs */
Expand All @@ -3080,7 +3209,11 @@ static int cdns_i3c_bus_init(const struct device *dev)
}
k_mutex_init(&data->bus_lock);
k_sem_init(&data->xfer.complete, 0, 1);
k_work_init(&data->deftgts_work, i3c_cdns_deftgts_work_fn);
#ifdef CONFIG_I3C_USE_IBI
k_sem_init(&data->ibi_hj_complete, 0, 1);
k_sem_init(&data->ibi_cr_complete, 0, 1);
#endif

cdns_i3c_interrupts_disable(config);
cdns_i3c_interrupts_clear(config);
Expand Down Expand Up @@ -3121,8 +3254,6 @@ static int cdns_i3c_bus_init(const struct device *dev)
* Set the I3C Bus Mode based on the LVR of the I2C devices
*/
uint32_t ctrl = CTRL_HJ_DISEC | CTRL_MCS_EN | (CTRL_BUS_MODE_MASK & cdns_mode);
/* Disable Controllership requests as it is not supported yet by the driver */
ctrl &= ~CTRL_MST_ACK;

/*
* Cadence I3C release r104v1p0 and above support configuration of the clock to data
Expand Down Expand Up @@ -3164,12 +3295,14 @@ static int cdns_i3c_bus_init(const struct device *dev)
/* enable target interrupts */
sys_write32(SLV_INT_DA_UPD | SLV_INT_SDR_RD_COMP | SLV_INT_SDR_WR_COMP |
SLV_INT_SDR_RX_THR | SLV_INT_SDR_TX_THR | SLV_INT_SDR_RX_UNF |
SLV_INT_SDR_TX_OVF | SLV_INT_HJ_DONE | SLV_INT_DDR_WR_COMP |
SLV_INT_DDR_RD_COMP | SLV_INT_DDR_RX_THR | SLV_INT_DDR_TX_THR,
SLV_INT_SDR_TX_OVF | SLV_INT_HJ_DONE | SLV_INT_MR_DONE |
SLV_INT_DEFSLVS | SLV_INT_DDR_WR_COMP | SLV_INT_DDR_RD_COMP |
SLV_INT_DDR_RX_THR | SLV_INT_DDR_TX_THR,
config->base + SLV_IER);

/* Enable IBI interrupts. */
sys_write32(MST_INT_IBIR_THR | MST_INT_RX_UNF | MST_INT_HALTED | MST_INT_TX_OVF,
/* Enable controller interrupts. */
sys_write32(MST_INT_IBIR_THR | MST_INT_RX_UNF | MST_INT_HALTED | MST_INT_MR_DONE |
MST_INT_TX_OVF,
config->base + MST_IER);

int ret = i3c_addr_slots_init(dev);
Expand All @@ -3188,8 +3321,9 @@ static int cdns_i3c_bus_init(const struct device *dev)
/* Perform bus initialization */
ret = i3c_bus_init(dev, &config->common.dev_list);
#ifdef CONFIG_I3C_USE_IBI
/* Bus Initialization Complete, allow HJ ACKs */
sys_write32(CTRL_HJ_ACK | sys_read32(config->base + CTRL), config->base + CTRL);
/* Bus Initialization Complete, allow CR & HJ ACKs */
sys_write32(CTRL_HJ_ACK | CTRL_MST_ACK | sys_read32(config->base + CTRL),
config->base + CTRL);
#endif
}

Expand Down Expand Up @@ -3219,6 +3353,7 @@ static struct i3c_driver_api api = {
.target_tx_write = cdns_i3c_target_tx_write,
.target_register = cdns_i3c_target_register,
.target_unregister = cdns_i3c_target_unregister,
.target_controller_handoff = cdns_i3c_target_controller_handoff,

#ifdef CONFIG_I3C_USE_IBI
.ibi_enable = cdns_i3c_controller_ibi_enable,
Expand Down
26 changes: 26 additions & 0 deletions drivers/i3c/i3c_ibi_workq.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,32 @@ int i3c_ibi_work_enqueue_target_irq(struct i3c_device_desc *target,
return ret;
}

int i3c_ibi_work_enqueue_controller_request(struct i3c_device_desc *target)
{
sys_snode_t *node;
struct i3c_ibi_work *ibi_node;
int ret;

node = sys_slist_get(&i3c_ibi_work_nodes_free);
if (node == NULL) {
ret = -ENOMEM;
goto out;
}

ibi_node = (struct i3c_ibi_work *)node;

ibi_node->type = I3C_IBI_CONTROLLER_ROLE_REQUEST;
ibi_node->target = target;

ret = ibi_work_submit(ibi_node);
if (ret >= 0) {
ret = 0;
}

out:
return ret;
}

int i3c_ibi_work_enqueue_hotjoin(const struct device *dev)
{
sys_snode_t *node;
Expand Down
Loading

0 comments on commit f92cd11

Please sign in to comment.