diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index a30b5ee801b99e1..a61b6af989e5041 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -112,6 +112,22 @@ config I3C_INIT_RSTACT This determines whether the bus initialization routine sends a reset action command to I3C targets. +config I3C_NUM_OF_DESC_MEM_SLABS + int "Number of I3C Device Descriptors Mem Slabs" + default 3 + help + This is the number of memory slabs allocated from when + there is a device encounted through ENTDAA or DEFTGTS that + is not within known I3C devices. + +config I3C_I2C_NUM_OF_DESC_MEM_SLABS + int "Number of I2C Device Descriptors Mem Slabs" + default 3 + help + This is the number of memory slabs allocated from when + there is a device encounted through DEFTGTS that is not + within known I2C devices. + comment "Device Drivers" rsource "Kconfig.nxp" diff --git a/drivers/i3c/i3c_cdns.c b/drivers/i3c/i3c_cdns.c index 1690b33f9a43be6..3ee9f4dd08d7e0e 100644 --- a/drivers/i3c/i3c_cdns.c +++ b/drivers/i3c/i3c_cdns.c @@ -14,6 +14,8 @@ #include #include +#include + #define DEV_ID 0x0 #define DEV_ID_I3C_MASTER 0x5034 @@ -344,7 +346,8 @@ #define SLV_ERR1 BIT(1) #define SLV_ERR0 BIT(0) -#define SLV_STATUS2 0xA8 +#define SLV_STATUS2 0xA8 +#define SLV_STATUS2_MRL(s) (((s) & GENMASK(23, 8)) >> 8) #define SLV_STATUS3 0xAC #define SLV_STATUS3_BC_FSM(s) (((s) & GENMASK(26, 16)) >> 16) @@ -571,7 +574,9 @@ struct cdns_i3c_config { /* Driver instance data */ struct cdns_i3c_data { + /* common must be first! */ struct i3c_driver_data common; + const struct device *dev; struct cdns_i3c_hw_config hw_cfg; #ifdef CONFIG_I3C_USE_IBI struct cdns_i3c_ibi_buf ibi_buf; @@ -580,7 +585,12 @@ struct cdns_i3c_data { 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; @@ -998,7 +1008,8 @@ static uint32_t prepare_rr0_dev_address(uint16_t addr) /** * @brief Program Retaining Registers with device lists * - * This will program the retaining register with the controller itself + * This will program the retaining register with the controller itself, this should + * only be called if it is a primary controller. * * @param dev Pointer to controller device driver instance. */ @@ -1014,7 +1025,8 @@ static void cdns_i3c_program_controller_retaining_reg(const struct device *dev) i3c_addr_slots_next_free_find(&data->common.attached_dev.addr_slots, 0); LOG_DBG("%s: 0x%02x DA selected for controller", dev->name, controller_da); } - sys_write32(prepare_rr0_dev_address(controller_da), config->base + DEV_ID_RR0(0)); + sys_write32(prepare_rr0_dev_address(controller_da) | DEV_ID_RR0_IS_I3C, + config->base + DEV_ID_RR0(0)); /* Mark the address as I3C device */ i3c_addr_slots_mark_i3c(&data->common.attached_dev.addr_slots, controller_da); } @@ -1029,7 +1041,8 @@ static int cdns_i3c_controller_ibi_enable(const struct device *dev, struct i3c_d struct i3c_ccc_events i3c_events; int ret = 0; - if (!i3c_device_is_ibi_capable(target)) { + /* Check if the device can issue IBI TIRs or CR */ + if (!i3c_device_is_ibi_capable(target) && !i3c_device_is_controller_capable(target)) { ret = -EINVAL; return ret; } @@ -1039,7 +1052,8 @@ static int cdns_i3c_controller_ibi_enable(const struct device *dev, struct i3c_d sir_cfg = SIR_MAP_DEV_ROLE(I3C_BCR_DEVICE_ROLE(target->bcr)) | SIR_MAP_DEV_DA(target->dynamic_addr) | SIR_MAP_DEV_PL(target->data_length.max_ibi); - if (target->ibi_cb != NULL) { + /* ACK if there is an ibi tir cb or if it is controller capable*/ + if ((target->ibi_cb != NULL) || i3c_device_is_controller_capable(target)) { sir_cfg |= SIR_MAP_DEV_ACK; } if (target->bcr & I3C_BCR_MAX_DATA_SPEED_LIMIT) { @@ -1049,8 +1063,8 @@ static int cdns_i3c_controller_ibi_enable(const struct device *dev, struct i3c_d LOG_DBG("%s: IBI enabling for 0x%02x (BCR 0x%02x)", dev->name, target->dynamic_addr, target->bcr); - /* Tell target to enable IBI */ - i3c_events.events = I3C_CCC_EVT_INTR; + /* Tell target to enable IBI TIRs and CRs */ + i3c_events.events = I3C_CCC_EVT_INTR | I3C_CCC_EVT_CR; ret = i3c_ccc_do_events_set(target, true, &i3c_events); if (ret != 0) { LOG_ERR("%s: Error sending IBI ENEC for 0x%02x (%d)", dev->name, @@ -1129,6 +1143,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; @@ -1161,12 +1200,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*/ @@ -1176,8 +1221,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: @@ -1479,6 +1523,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); @@ -1564,20 +1614,46 @@ static int cdns_i3c_do_daa(const struct device *dev) const struct i3c_device_id i3c_id = I3C_DEVICE_ID(pid); struct i3c_device_desc *target = i3c_device_find(dev, &i3c_id); - if (target == NULL) { + if (!target) { + /* Target found that is not known, allocate a desc */ + target = i3c_alloc_i3c_device_desc(); + if (target) { + /* + * able to allocate a descriptor + * write all known values + */ + *(const struct device **)&target->bus = dev; + *(uint64_t *)&target->pid = pid; + target->dynamic_addr = dyn_addr; + target->bcr = bcr; + target->dcr = dcr; + /* attach it to the slist */ + sys_slist_append( + &data->common.attached_dev.devices.i3c, + &target->node); + + data->cdns_i3c_i2c_priv_data[rr_idx].id = rr_idx; + target->controller_priv = + &(data->cdns_i3c_i2c_priv_data[rr_idx]); + } + LOG_INF("%s: PID 0x%012llx is not in registered device " "list, given DA 0x%02x", dev->name, pid, dyn_addr); - i3c_addr_slots_mark_i3c( - &data->common.attached_dev.addr_slots, dyn_addr); } else { target->dynamic_addr = dyn_addr; target->bcr = bcr; target->dcr = dcr; + data->cdns_i3c_i2c_priv_data[rr_idx].id = rr_idx; + target->controller_priv = + &(data->cdns_i3c_i2c_priv_data[rr_idx]); + LOG_DBG("%s: PID 0x%012llx assigned dynamic address 0x%02x", dev->name, pid, dyn_addr); } + i3c_addr_slots_mark_i3c(&data->common.attached_dev.addr_slots, + dyn_addr); } } } else { @@ -2393,6 +2469,45 @@ 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; + + /* 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(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)) { @@ -2400,6 +2515,7 @@ static void cdns_i3c_handle_hj(const struct device *dev, uint32_t ibir) return; } + /* TODO: disable CTRL_HJ_DISEC and process auto-ENTDAA*/ if (i3c_ibi_work_enqueue_hotjoin(dev) != 0) { LOG_ERR("%s: Error enqueue IBI HJ work", dev->name); } @@ -2421,7 +2537,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; @@ -2435,6 +2551,13 @@ 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_target_sdr_tx_thr_int_handler(const struct device *dev, @@ -2495,236 +2618,240 @@ 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; + uint32_t int_st = sys_read32(config->base + MST_ISR); - if (sys_read32(config->base + MST_STATUS0) & MST_STATUS0_MASTER_MODE) { - uint32_t int_st = sys_read32(config->base + MST_ISR); - sys_write32(int_st, config->base + MST_ICR); + sys_write32(int_st, config->base + MST_ICR); - /* Command queue empty */ - if (int_st & MST_INT_HALTED) { - LOG_WRN("Core Halted, 2 read aborts"); - } + /* Command queue empty */ + if (int_st & MST_INT_HALTED) { + LOG_WRN("Core Halted, 2 read aborts"); + } - /* Command queue empty */ - if (int_st & MST_INT_CMDD_EMP) { - cdns_i3c_complete_transfer(dev); - } + /* Command queue empty */ + if (int_st & MST_INT_CMDD_EMP) { + cdns_i3c_complete_transfer(dev); + } - /* In-band interrupt */ - if (int_st & MST_INT_IBIR_THR) { + /* In-band interrupt */ + if (int_st & MST_INT_IBIR_THR) { #ifdef CONFIG_I3C_USE_IBI - cnds_i3c_master_demux_ibis(dev); + cnds_i3c_master_demux_ibis(dev); #else - LOG_ERR("%s: IBI received - Kconfig for using IBIs is not enabled", - dev->name); + LOG_ERR("%s: IBI received - Kconfig for using IBIs is not enabled", dev->name); #endif - } + } - /* In-band interrupt data threshold */ - if (int_st & MST_INT_IBID_THR) { + /* In-band interrupt data threshold */ + if (int_st & MST_INT_IBID_THR) { #ifdef CONFIG_I3C_USE_IBI - /* pop data out of the IBI FIFO */ - while (!cdns_i3c_ibi_fifo_empty(config)) { - uint32_t *ptr = (uint32_t *)&data->ibi_buf - .ibi_data[data->ibi_buf.ibi_data_cnt]; - *ptr = sys_le32_to_cpu(sys_read32(config->base + IBI_DATA_FIFO)); - data->ibi_buf.ibi_data_cnt += 4; - } + /* pop data out of the IBI FIFO */ + while (!cdns_i3c_ibi_fifo_empty(config)) { + uint32_t *ptr = + (uint32_t *)&data->ibi_buf.ibi_data[data->ibi_buf.ibi_data_cnt]; + *ptr = sys_le32_to_cpu(sys_read32(config->base + IBI_DATA_FIFO)); + data->ibi_buf.ibi_data_cnt += 4; + } #else - LOG_ERR("%s: IBI received - Kconfig for using IBIs is not enabled", - dev->name); + LOG_ERR("%s: IBI received - Kconfig for using IBIs is not enabled", dev->name); #endif - } + } - /* In-band interrupt response overflow */ - if (int_st & MST_INT_IBIR_OVF) { - LOG_ERR("%s: controller ibir overflow,", dev->name); - } + /* In-band interrupt response overflow */ + if (int_st & MST_INT_IBIR_OVF) { + LOG_ERR("%s: controller ibir overflow,", dev->name); + } - /* In-band interrupt data */ - if (int_st & MST_INT_TX_OVF) { - LOG_ERR("%s: controller tx buffer overflow,", dev->name); - } + /* In-band interrupt data */ + if (int_st & MST_INT_TX_OVF) { + LOG_ERR("%s: controller tx buffer overflow,", dev->name); + } - /* In-band interrupt data */ - if (int_st & MST_INT_RX_UNF) { - LOG_ERR("%s: controller rx buffer underflow,", dev->name); - } - } else { - uint32_t int_sl = sys_read32(config->base + SLV_ISR); - const struct i3c_target_callbacks *target_cb = - data->target_config ? data->target_config->callbacks : NULL; - /* Clear interrupts */ - sys_write32(int_sl, config->base + SLV_ICR); - - /* SLV SDR rx fifo threshold */ - if (int_sl & SLV_INT_SDR_RX_THR) { - /* while rx fifo is not empty */ - while (!(sys_read32(config->base + SLV_STATUS1) & - SLV_STATUS1_SDR_RX_EMPTY)) { - if (target_cb != NULL && target_cb->write_received_cb != NULL) { - cdns_i3c_target_read_rx_fifo(dev); - } + /* In-band interrupt data */ + if (int_st & MST_INT_RX_UNF) { + LOG_ERR("%s: controller rx buffer underflow,", dev->name); + } + + if (int_st & MST_INT_MR_DONE) { + LOG_DBG("%s: controller CR Handoff done,", dev->name); + k_sem_give(&data->ch_complete); + } + + uint32_t int_sl = sys_read32(config->base + SLV_ISR); + const struct i3c_target_callbacks *target_cb = + data->target_config ? data->target_config->callbacks : NULL; + /* Clear interrupts */ + sys_write32(int_sl, config->base + SLV_ICR); + + /* SLV SDR rx fifo threshold */ + if (int_sl & SLV_INT_SDR_RX_THR) { + /* while rx fifo is not empty */ + while (!(sys_read32(config->base + SLV_STATUS1) & SLV_STATUS1_SDR_RX_EMPTY)) { + if (target_cb != NULL && target_cb->write_received_cb != NULL) { + cdns_i3c_target_read_rx_fifo(dev); } } + } - /* SLV SDR tx fifo threshold */ - if (int_sl & SLV_INT_SDR_TX_THR) { - cdns_i3c_target_sdr_tx_thr_int_handler(dev, target_cb); - } + /* SLV SDR tx fifo threshold */ + if (int_sl & SLV_INT_SDR_TX_THR) { + cdns_i3c_target_sdr_tx_thr_int_handler(dev, target_cb); + } - /* SLV SDR rx complete */ - if (int_sl & SLV_INT_SDR_RD_COMP) { - /* a read needs to be done on slv_status 0 else a NACK will happen */ - (void)sys_read32(config->base + SLV_STATUS0); - /* call stop function pointer */ - if (target_cb != NULL && target_cb->stop_cb) { - target_cb->stop_cb(data->target_config); - } + /* SLV SDR rx complete */ + if (int_sl & SLV_INT_SDR_RD_COMP) { + /* a read needs to be done on slv_status 0 else a NACK will happen */ + (void)sys_read32(config->base + SLV_STATUS0); + /* call stop function pointer */ + if (target_cb != NULL && target_cb->stop_cb) { + target_cb->stop_cb(data->target_config); } + } - /* SLV SDR tx complete */ - if (int_sl & SLV_INT_SDR_WR_COMP) { - /* a read needs to be done on slv_status 0 else a NACK will happen */ - (void)sys_read32(config->base + SLV_STATUS0); - /* clear bytes read parameter */ - data->fifo_bytes_read = 0; - /* call stop function pointer */ - if (target_cb != NULL && target_cb->stop_cb) { - target_cb->stop_cb(data->target_config); - } + /* SLV SDR tx complete */ + if (int_sl & SLV_INT_SDR_WR_COMP) { + /* a read needs to be done on slv_status 0 else a NACK will happen */ + (void)sys_read32(config->base + SLV_STATUS0); + /* clear bytes read parameter */ + data->fifo_bytes_read = 0; + /* call stop function pointer */ + if (target_cb != NULL && target_cb->stop_cb) { + target_cb->stop_cb(data->target_config); } + } - /* DA has been updated */ - 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 - */ + /* DA has been updated */ + 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))); #ifdef CONFIG_I3C_USE_IBI - cdns_i3c_target_ibi_hj_complete(dev); + cdns_i3c_target_ibi_hj_complete(dev); #endif - } + } - /* HJ complete and DA has been assigned */ - if (int_sl & SLV_INT_HJ_DONE) { - } + /* HJ complete and DA has been assigned or HJ NACK'ed or DISEC disabled HJ */ + if (int_sl & SLV_INT_HJ_DONE) { - /* Controllership has been been given */ - if (int_sl & SLV_INT_MR_DONE) { - /* TODO: implement support for controllership handoff */ - } + } - /* EISC or DISEC has been received */ - if (int_sl & SLV_INT_EVENT_UP) { + /* Controllership has been been given to us */ + if (int_sl & SLV_INT_MR_DONE) { +#ifdef CONFIG_I3C_USE_IBI + cdns_i3c_target_ibi_cr_complete(dev); + /* TODO: create common workq to attach deftgts data and retrieve i3c info */ + i3c_ibi_work_enqueue_cb(dev, i3c_sec_handoffed); + if (target_cb != NULL && target_cb->controller_handoff_cb) { + target_cb->controller_handoff_cb(data->target_config); } +#endif + } - /* sdr transfer aborted by controller */ - if (int_sl & SLV_INT_M_RD_ABORT) { - /* TODO: consider flushing tx buffer? */ - } + /* EISC or DISEC has been received */ + if (int_sl & SLV_INT_EVENT_UP) { + } - /* SLV SDR rx fifo underflow */ - if (int_sl & SLV_INT_SDR_RX_UNF) { - LOG_ERR("%s: slave sdr rx buffer underflow", dev->name); - } + /* sdr transfer aborted by controller */ + if (int_sl & SLV_INT_M_RD_ABORT) { + /* TODO: consider flushing tx buffer? */ + } - /* SLV SDR tx fifo overflow */ - if (int_sl & SLV_INT_SDR_TX_OVF) { - LOG_ERR("%s: slave sdr tx buffer overflow,", dev->name); - } + /* SLV SDR rx fifo underflow */ + if (int_sl & SLV_INT_SDR_RX_UNF) { + LOG_ERR("%s: slave sdr rx buffer underflow", dev->name); + } - if (int_sl & SLV_INT_DDR_RX_THR) { - } + /* SLV SDR tx fifo overflow */ + if (int_sl & SLV_INT_SDR_TX_OVF) { + LOG_ERR("%s: slave sdr tx buffer overflow,", dev->name); + } - /* SLV DDR WR COMPLETE */ - if (int_sl & SLV_INT_DDR_WR_COMP) { - /* initial value of CRC5 for HDR-DDR is 0x1F */ - uint8_t crc5 = 0x1F; + if (int_sl & SLV_INT_DDR_RX_THR) { + } - while (!(sys_read32(config->base + SLV_STATUS1) & - SLV_STATUS1_DDR_RX_EMPTY)) { - uint32_t ddr_rx_data = sys_read32(config->base + SLV_DDR_RX_FIFO); - uint32_t preamble = (ddr_rx_data & DDR_PREAMBLE_MASK); + /* SLV DDR WR COMPLETE */ + if (int_sl & SLV_INT_DDR_WR_COMP) { + /* initial value of CRC5 for HDR-DDR is 0x1F */ + uint8_t crc5 = 0x1F; - if (preamble == DDR_PREAMBLE_DATA_ABORT || - preamble == DDR_PREAMBLE_DATA_ABORT_ALT) { - uint16_t ddr_payload = DDR_DATA(ddr_rx_data); + while (!(sys_read32(config->base + SLV_STATUS1) & SLV_STATUS1_DDR_RX_EMPTY)) { + uint32_t ddr_rx_data = sys_read32(config->base + SLV_DDR_RX_FIFO); + uint32_t preamble = (ddr_rx_data & DDR_PREAMBLE_MASK); - if (cdns_i3c_ddr_parity(ddr_payload) != - (ddr_rx_data & (DDR_ODD_PARITY | DDR_EVEN_PARITY))) { - LOG_ERR("%s: Received incorrect DDR Parity", - dev->name); - } - /* calculate a running a crc */ - crc5 = i3c_cdns_crc5(crc5, ddr_payload); - - if (target_cb != NULL && - target_cb->write_received_cb != NULL) { - /* DDR receives 2B for each payload */ - target_cb->write_received_cb( - data->target_config, - (uint8_t)((ddr_payload >> 8) & 0xFF)); - target_cb->write_received_cb( - data->target_config, - (uint8_t)(ddr_payload)); - } + if (preamble == DDR_PREAMBLE_DATA_ABORT || + preamble == DDR_PREAMBLE_DATA_ABORT_ALT) { + uint16_t ddr_payload = DDR_DATA(ddr_rx_data); - } else if ((preamble == DDR_PREAMBLE_CMD_CRC) && - ((ddr_rx_data & DDR_CRC_TOKEN_MASK) == DDR_CRC_TOKEN)) { - /* should come through here last */ - if (crc5 != DDR_CRC(ddr_rx_data)) { - LOG_ERR("%s: Received incorrect DDR CRC5", - dev->name); - } - } else if (preamble == DDR_PREAMBLE_CMD_CRC) { - /* should come through here first */ - uint16_t ddr_header_payload = DDR_DATA(ddr_rx_data); + if (cdns_i3c_ddr_parity(ddr_payload) != + (ddr_rx_data & (DDR_ODD_PARITY | DDR_EVEN_PARITY))) { + LOG_ERR("%s: Received incorrect DDR Parity", dev->name); + } + /* calculate a running a crc */ + crc5 = i3c_cdns_crc5(crc5, ddr_payload); + + if (target_cb != NULL && target_cb->write_received_cb != NULL) { + /* DDR receives 2B for each payload */ + target_cb->write_received_cb( + data->target_config, + (uint8_t)((ddr_payload >> 8) & 0xFF)); + target_cb->write_received_cb(data->target_config, + (uint8_t)(ddr_payload)); + } - crc5 = i3c_cdns_crc5(crc5, ddr_header_payload); + } else if ((preamble == DDR_PREAMBLE_CMD_CRC) && + ((ddr_rx_data & DDR_CRC_TOKEN_MASK) == DDR_CRC_TOKEN)) { + /* should come through here last */ + if (crc5 != DDR_CRC(ddr_rx_data)) { + LOG_ERR("%s: Received incorrect DDR CRC5", dev->name); } - } + } else if (preamble == DDR_PREAMBLE_CMD_CRC) { + /* should come through here first */ + uint16_t ddr_header_payload = DDR_DATA(ddr_rx_data); - if (target_cb != NULL && target_cb->stop_cb != NULL) { - target_cb->stop_cb(data->target_config); + crc5 = i3c_cdns_crc5(crc5, ddr_header_payload); } } - /* SLV SDR rx complete */ - if (int_sl & SLV_INT_DDR_RD_COMP) { - /* a read needs to be done on slv_status 0 else a NACK will happen */ - (void)sys_read32(config->base + SLV_STATUS0); - /* call stop function pointer */ - if (target_cb != NULL && target_cb->stop_cb) { - target_cb->stop_cb(data->target_config); - } + if (target_cb != NULL && target_cb->stop_cb != NULL) { + target_cb->stop_cb(data->target_config); } + } - /*SLV DDR TX THR*/ - if (int_sl & SLV_INT_DDR_TX_THR) { - int status = 0; + /* SLV SDR rx complete */ + if (int_sl & SLV_INT_DDR_RD_COMP) { + /* a read needs to be done on slv_status 0 else a NACK will happen */ + (void)sys_read32(config->base + SLV_STATUS0); + /* call stop function pointer */ + if (target_cb != NULL && target_cb->stop_cb) { + target_cb->stop_cb(data->target_config); + } + } - if (target_cb != NULL && target_cb->read_processed_cb) { + /* SLV DDR TX THR */ + if (int_sl & SLV_INT_DDR_TX_THR) { + int status = 0; - while ((!(sys_read32(config->base + SLV_STATUS1) & - SLV_STATUS1_DDR_TX_FULL)) && - (status == 0)) { - /* call function pointer for read */ - uint8_t byte; - /* will return negative if no data left to transmit - * and 0 if data available - */ - status = target_cb->read_processed_cb(data->target_config, - &byte); - if (status == 0) { - cdns_i3c_write_ddr_tx_fifo(config, &byte, - sizeof(byte)); - } + if (target_cb != NULL && target_cb->read_processed_cb) { + + while ((!(sys_read32(config->base + SLV_STATUS1) & + SLV_STATUS1_DDR_TX_FULL)) && + (status == 0)) { + /* call function pointer for read */ + uint8_t byte; + /* will return negative if no data left to transmit + * and 0 if data available + */ + status = target_cb->read_processed_cb(data->target_config, &byte); + if (status == 0) { + cdns_i3c_write_ddr_tx_fifo(config, &byte, sizeof(byte)); } } } } + + /* DEFTGTS */ + if (int_sl & SLV_INT_DEFSLVS) { + /* Execute outside of the ISR context */ + k_work_submit(&data->deftgts_work); + } } static void cdns_i3c_read_hw_cfg(const struct device *dev) @@ -2798,18 +2925,50 @@ static void cdns_i3c_read_hw_cfg(const struct device *dev) */ static int cdns_i3c_config_get(const struct device *dev, enum i3c_config_type type, void *config) { + const struct cdns_i3c_config *dev_config = dev->config; struct cdns_i3c_data *data = dev->data; - int ret = 0; - if (config == NULL) { - ret = -EINVAL; - goto out_configure; + __ASSERT_NO_MSG(config != NULL); + + if (type == I3C_CONFIG_CONTROLLER) { + (void)memcpy(config, &data->common.ctrl_config, sizeof(data->common.ctrl_config)); + } else if (type == I3C_CONFIG_TARGET) { + struct i3c_config_target *target_config = (struct i3c_config_target *)config; + /* Read RR_0 registers for itself */ + uint32_t dev_id_rr0 = sys_read32(dev_config->base + DEV_ID_RR0(0)); + uint32_t dev_id_rr1 = sys_read32(dev_config->base + DEV_ID_RR1(0)); + uint32_t dev_id_rr2 = sys_read32(dev_config->base + DEV_ID_RR2(0)); + uint32_t slv_status1 = sys_read32(dev_config->base + SLV_STATUS1); + + /* if we are currently a target */ + target_config->enable = + !!!(sys_read32(dev_config->base + MST_STATUS0) & MST_STATUS0_MASTER_MODE); + if (data->common.ctrl_config.is_secondary) { + target_config->dynamic_addr = SLV_STATUS1_DA(slv_status1); + } else { + target_config->dynamic_addr = (dev_id_rr0 & 0xFE) >> 1; + } + target_config->static_addr = 0; + target_config->pid = ((uint64_t)dev_id_rr1 << 16) + (dev_id_rr2 >> 16); + target_config->pid_random = !!(slv_status1 & SLV_STATUS1_VEN_TM); + target_config->bcr = dev_id_rr2 >> 8; + target_config->dcr = dev_id_rr2 & 0xFF; + /* Version 1p7 supports reading MRL/MWL */ + if (REV_ID_REV(data->hw_cfg.rev_id) >= REV_ID_VERSION(1, 7)) { + target_config->max_read_len = + SLV_STATUS2_MRL(sys_read32(dev_config->base + SLV_STATUS2)); + target_config->max_write_len = + SLV_STATUS3_MWL(sys_read32(dev_config->base + SLV_STATUS3)); + } else { + target_config->max_read_len = 0; + target_config->max_write_len = 0; + } + target_config->supported_hdr = data->common.ctrl_config.supported_hdr; + } else { + return -EINVAL; } - (void)memcpy(config, &data->common.ctrl_config, sizeof(data->common.ctrl_config)); - -out_configure: - return ret; + return 0; } static int cdns_i3c_target_tx_ddr_write(const struct device *dev, uint8_t *buf, uint16_t len) @@ -3067,6 +3226,31 @@ static int cdns_i3c_i2c_api_transfer(const struct device *dev, struct i2c_msg *m return ret; } +/** + * ACK or NACK Controller Handoffs + * + * Reads the LVR of all I2C devices and returns the I3C bus + * Mode + * + * @param dev Pointer to device driver instance. + * @param accept True to accept controller handoffs, False to decline + * + * @return @see i3c_target_controller_handoff + */ +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 * @@ -3129,6 +3313,84 @@ static uint8_t cdns_i3c_sda_data_hold(const struct device *dev) return (THD_DELAY_MAX - thd_delay); } +static void i3c_cdns_deftgts_work_fn(struct k_work *work) +{ + const struct cdns_i3c_config *config; + struct cdns_i3c_data *data; + struct i3c_ccc_deftgts *deftgts; + const struct device *dev; + uint32_t devs; + uint8_t n = 0; + + data = CONTAINER_OF(work, struct cdns_i3c_data, deftgts_work); + dev = data->dev; + config = dev->config; + data = dev->data; + deftgts = data->common.deftgts; + + /* Allocate memory for deftgts if not already */ + if (!deftgts) { + deftgts = + malloc(sizeof(uint8_t) + sizeof(struct i3c_ccc_deftgts_active_controller) + + (data->max_devs * sizeof(struct i3c_ccc_deftgts_target))); + if (!deftgts) { + LOG_ERR("%s: Failed to allocate memory for DEFTGTS", dev->name); + return; + } + } + + devs = sys_read32(config->base + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK; + data->free_rr_slots = GENMASK(data->max_devs, 1) & ~devs; + + /* + * count the number of ones in devs, The IP will 'skip' writing it self to the RR if + * it was in DEFTGTS. Also, if the IP never had a DA, then the deftgts interrupt will + * never fire. Subtract 1 as the active controller is not included in `count`. + */ + deftgts->count = POPCOUNT(devs) - 1; + + for (uint8_t i = find_lsb_set(devs); i <= find_msb_set(devs); i++) { + uint8_t rr_idx = i - 1; + + if (devs & BIT(rr_idx)) { + /* Read RRx registers */ + uint32_t dev_id_rr0 = sys_read32(config->base + DEV_ID_RR0(rr_idx)); + uint32_t dev_id_rr2 = sys_read32(config->base + DEV_ID_RR2(rr_idx)); + + uint8_t addr = (dev_id_rr0 & 0xFE) >> 1; + uint8_t bcr = dev_id_rr2 >> 8; + uint8_t dcr_lvr = dev_id_rr2 & 0xFF; + bool is_i3c = !!(dev_id_rr0 & DEV_ID_RR0_IS_I3C); + + + /* RR IDX 1 should always be expected to be the AC */ + if (rr_idx == 1) { + deftgts->active_controller.addr = addr; + deftgts->active_controller.dcr = dcr_lvr; + deftgts->active_controller.bcr = bcr; + deftgts->active_controller.static_addr = 0; + } else if (is_i3c) { + deftgts->targets[n].addr = addr; + deftgts->targets[n].dcr = dcr_lvr; + deftgts->targets[n].bcr = bcr; + deftgts->targets[n].static_addr = 0; + n++; + } else { + deftgts->targets[n].addr = 0; + deftgts->targets[n].lvr = dcr_lvr; + deftgts->targets[n].bcr = 0; + deftgts->targets[n].static_addr = addr; + n++; + } + } + } + data->common.deftgts_refreshed = true; + LOG_HEXDUMP_DBG((uint8_t *)deftgts, + sizeof(uint8_t) + sizeof(struct i3c_ccc_deftgts_active_controller) + + (deftgts->count * sizeof(struct i3c_ccc_deftgts_target)), + "DEFTGTS Received"); +} + /** * @brief Initialize the hardware. * @@ -3140,6 +3402,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 */ @@ -3164,7 +3428,12 @@ 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_sem_init(&data->ch_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); @@ -3204,9 +3473,8 @@ 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; + uint32_t ctrl = + CTRL_HJ_DISEC | CTRL_MCS_EN | CTRL_MST_ACK | (CTRL_BUS_MODE_MASK & cdns_mode); /* * Cadence I3C release r104v1p0 and above support configuration of the sda data hold time @@ -3248,13 +3516,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 | - MST_INT_IBIR_OVF | MST_INT_IBID_THR, + /* Enable controller interrupts. */ + sys_write32(MST_INT_IBIR_THR | MST_INT_RX_UNF | MST_INT_HALTED | MST_INT_MR_DONE | + MST_INT_TX_OVF | MST_INT_IBIR_OVF | MST_INT_IBID_THR, config->base + MST_IER); int ret = i3c_addr_slots_init(dev); @@ -3263,11 +3532,10 @@ static int cdns_i3c_bus_init(const struct device *dev) return ret; } - /* Program retaining regs. */ - cdns_i3c_program_controller_retaining_reg(dev); - /* only primary controllers are responsible for initializing the bus */ if (!ctrl_config->is_secondary) { + /* Program retaining regs. */ + cdns_i3c_program_controller_retaining_reg(dev); /* Sleep to wait for bus idle. */ k_busy_wait(201); /* Perform bus initialization */ @@ -3304,6 +3572,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, diff --git a/drivers/i3c/i3c_common.c b/drivers/i3c/i3c_common.c index 8f958d2eb666378..f3f91bdb8063d9c 100644 --- a/drivers/i3c/i3c_common.c +++ b/drivers/i3c/i3c_common.c @@ -17,6 +17,90 @@ #include LOG_MODULE_REGISTER(i3c, CONFIG_I3C_LOG_LEVEL); + +/* Generate Names */ +#define UNKNOWN_NAME_STR(i, _) \ + { \ + .name = "unknown-" STRINGIFY(i) \ + } +const struct device dummy_devs[] = { + LISTIFY(CONFIG_I3C_NUM_OF_DESC_MEM_SLABS, UNKNOWN_NAME_STR, (,)) }; + +/* TODO: Handle CONFIG_I3C_NUM_OF_DESC_MEM_SLABS being 0 for no memory overhead */ +K_MEM_SLAB_DEFINE(i3c_common_device_desc, sizeof(struct i3c_device_desc), + CONFIG_I3C_NUM_OF_DESC_MEM_SLABS, 4); + +struct i3c_device_desc *i3c_alloc_i3c_device_desc(void) +{ + struct i3c_device_desc *desc; + + if (k_mem_slab_alloc(&i3c_common_device_desc, (void **)&desc, K_NO_WAIT) == 0) { + memset(desc, 0, sizeof(struct i3c_device_desc)); + *(const struct device **)&desc->dev = + &dummy_devs[k_mem_slab_num_free_get(&i3c_common_device_desc)]; + LOG_DBG("I3C Device Desc allocated - %d free", + k_mem_slab_num_free_get(&i3c_common_device_desc)); + } else { + LOG_WRN("No memory left for I3C descriptors"); + } + + return desc; +} + +void i3c_free_i3c_device_desc(struct i3c_device_desc *desc) +{ + k_mem_slab_free(&i3c_common_device_desc, (void *)desc); + LOG_DBG("I3C Device Desc freed"); +} + +bool i3c_is_common_i3c_device_desc(struct i3c_device_desc *desc) +{ + const char *p = (const char *)desc; + ptrdiff_t offset = p - i3c_common_device_desc.buffer; + + return (offset >= 0) && + (offset < (i3c_common_device_desc.info.block_size * + i3c_common_device_desc.info.num_blocks)) && + ((offset % i3c_common_device_desc.info.block_size) == 0); +} + +/* TODO: Handle CONFIG_I3C_I2C_NUM_OF_DESC_MEM_SLABS being 0 for no memory overhead */ +K_MEM_SLAB_DEFINE(i3c_i2c_common_device_desc, sizeof(struct i3c_i2c_device_desc), + CONFIG_I3C_I2C_NUM_OF_DESC_MEM_SLABS, 4); + +struct i3c_i2c_device_desc *i3c_alloc_i3c_i2c_device_desc(void) +{ + struct i3c_i2c_device_desc *desc; + + if (k_mem_slab_alloc(&i3c_i2c_common_device_desc, (void **)&desc, K_NO_WAIT) == 0) { + memset(desc, 0, sizeof(struct i3c_i2c_device_desc)); + LOG_INF("I2C Device Desc allocated - %d free", + k_mem_slab_num_free_get(&i3c_i2c_common_device_desc)); + } else { + LOG_WRN("No memory left for I2C descriptors"); + } + + return desc; +} + +void i3c_free_i3c_i2c_device_desc(struct i3c_i2c_device_desc *desc) +{ + k_mem_slab_free(&i3c_i2c_common_device_desc, (void *)desc); + LOG_DBG("I2C Device Desc freed"); +} + +bool i3c_is_common_i3c_i2c_device_desc(struct i3c_i2c_device_desc *desc) +{ + const char *p = (const char *)desc; + ptrdiff_t offset = p - i3c_i2c_common_device_desc.buffer; + + return (offset >= 0) && + (offset < (i3c_i2c_common_device_desc.info.block_size * + i3c_i2c_common_device_desc.info.num_blocks)) && + ((offset % i3c_i2c_common_device_desc.info.block_size) == 0); +} + + void i3c_dump_msgs(const char *name, const struct i3c_msg *msgs, uint8_t num_msgs, struct i3c_device_desc *target) { @@ -373,6 +457,161 @@ int i3c_detach_i2c_device(struct i3c_i2c_device_desc *target) return status; } +int i3c_sec_get_basic_info(const struct device *dev, + uint8_t dynamic_addr, uint8_t static_addr, uint8_t bcr, uint8_t dcr) +{ + struct i3c_ccc_getpid getpid; + struct i3c_device_desc temp_desc; + struct i3c_device_desc *desc; + struct i3c_device_id id; + const struct i3c_driver_config *config = dev->config; + int ret; + + *(const struct device **)&temp_desc.bus = dev; + temp_desc.dynamic_addr = dynamic_addr; + temp_desc.bcr = bcr; + temp_desc.dcr = dcr; + /* attach it first with a temperary value so we can at least get the pid */ + ret = i3c_attach_i3c_device(&temp_desc); + if (ret != 0) { + return ret; + } + + /* First try to look up if this is a known device in the list by PID */ + ret = i3c_ccc_do_getpid(&temp_desc, &getpid); + if (ret != 0) { + return ret; + } + + *(uint64_t *)&id = sys_get_be48(getpid.pid); + + /* try to see if we already have a device statically allocated */ + desc = i3c_dev_list_find(&config->dev_list, &id); + if (!desc) { + /* device was not found so allocate a descriptor */ + desc = i3c_alloc_i3c_device_desc(); + if (!desc) { + return -ENOMEM; + } + *(uint64_t *)&desc->pid = id.pid; + *(uint16_t *)&temp_desc.static_addr = (uint16_t)static_addr; + } + desc->dynamic_addr = dynamic_addr; + desc->bcr = bcr; + desc->dcr = dcr; + + /* Detach that temporary device */ + ret = i3c_detach_i3c_device(&temp_desc); + if (ret != 0) { + return ret; + } + ret = i3c_attach_i3c_device(desc); + if (ret != 0) { + return ret; + } + + /* TODO: somehow skip BCR and DCR in this function as it comes from DEFTGTS */ + ret = i3c_device_basic_info_get(desc); + + return ret; +} + +int i3c_sec_i2c_attach(const struct device *dev, uint8_t static_addr, uint8_t lvr) +{ + struct i3c_i2c_device_desc *i2c_desc; + int ret; + + i2c_desc = i3c_dev_list_i2c_addr_find(dev, (uint16_t)static_addr); + if (!i2c_desc) { + /* TODO: alloc a i3c_i2c_device_desc from a mem_slab */ + i2c_desc = i3c_alloc_i3c_i2c_device_desc(); + if (!i2c_desc) { + return -ENOMEM; + } + *(const struct device **)&i2c_desc->bus = dev; + *(uint16_t *)&i2c_desc->addr = (uint16_t)static_addr; + *(uint8_t *)&i2c_desc->lvr = lvr; + } + + ret = i3c_attach_i2c_device(i2c_desc); + return ret; +} + +static void i3c_sec_bus_reset(const struct device *dev) +{ + struct i3c_device_desc *i3c_desc; + struct i3c_i2c_device_desc *i3c_i2c_desc; + + I3C_BUS_FOR_EACH_I3CDEV(dev, i3c_desc) { + i3c_detach_i3c_device(i3c_desc); + } + + I3C_BUS_FOR_EACH_I2CDEV(dev, i3c_i2c_desc) { + i3c_detach_i2c_device(i3c_i2c_desc); + } +} + +/* call this from a workq after the interrupt from a controller */ +void i3c_sec_handoffed(struct k_work *work) +{ + struct i3c_ibi_work *ibi_node = CONTAINER_OF(work, struct i3c_ibi_work, work); + const struct device *dev = ibi_node->controller; + struct i3c_driver_data *data = (struct i3c_driver_data *)dev->data; + struct i3c_ccc_deftgts *deftgts = data->deftgts; + struct i3c_config_target config_target; + uint8_t n, cur_dyn_addr; + int ret; + + if (!deftgts) { + LOG_ERR("Did not receive DEFTGTS before Handoff"); + return; + } + + if (!data->deftgts_refreshed) { + LOG_DBG("Already processed DEFTGTS from previous handoff"); + return; + } + + /* Forget all devices as another controller made changes */ + i3c_sec_bus_reset(dev); + + /* + * Retrieve the active controller information + */ + ret = i3c_config_get(dev, I3C_CONFIG_TARGET, &config_target); + if (ret != 0) { + LOG_ERR("Failed to retrieve active controller info"); + return; + } + + cur_dyn_addr = config_target.dynamic_addr; + + /* Attach the previous AC */ + ret = i3c_sec_get_basic_info(dev, deftgts->active_controller.addr, + deftgts->active_controller.static_addr, + deftgts->active_controller.bcr, + deftgts->active_controller.dcr); + + /* Attach all Targets */ + for (n = 0; n < deftgts->count; n++) { + if (deftgts->targets[n].addr != 0) { + /* Must be an I3C device and skip itself */ + if (deftgts->targets[n].addr != cur_dyn_addr) { + ret = i3c_sec_get_basic_info(dev, deftgts->targets[n].addr, + deftgts->targets[n].static_addr, deftgts->targets[n].bcr, + deftgts->targets[n].dcr); + } + } else { + /* Must be an I2C device */ + ret = i3c_sec_i2c_attach(dev, deftgts->targets[n].static_addr, + deftgts->targets[n].lvr); + } + } + + /* Set false, so the next handoff doesn't retrigger regathering info */ + data->deftgts_refreshed = false; +} + 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, @@ -388,6 +627,10 @@ int i3c_dev_list_daa_addr_helper(struct i3c_addr_slots *addr_slots, const struct i3c_device_id i3c_id = I3C_DEVICE_ID(pid); desc = i3c_dev_list_find(dev_list, &i3c_id); + /* If a device was not found, try to allocate a descriptor */ + if (desc == NULL) { + desc = i3c_alloc_i3c_device_desc(); + } if (must_match && (desc == NULL)) { /* * No device descriptor matching incoming PID and @@ -665,6 +908,7 @@ int i3c_device_basic_info_get(struct i3c_device_desc *target) } } + target->dcr = dcr.dcr; target->data_length.mrl = mrl.len; target->data_length.mwl = mwl.len; target->data_length.max_ibi = mrl.ibi_len; @@ -985,8 +1229,6 @@ int i3c_bus_init(const struct device *dev, const struct i3c_dev_list *dev_list) /* * Only re-enable Hot-Join from targets. * Target interrupts will be enabled when IBI is enabled. - * And transferring controller role is not supported so not need to - * enable the event. */ i3c_events.events = I3C_CCC_EVT_HJ; ret = i3c_ccc_do_events_all_set(dev, true, &i3c_events); diff --git a/drivers/i3c/i3c_ibi_workq.c b/drivers/i3c/i3c_ibi_workq.c index 339ae8e3c4ee5f7..45cc1585b788ab6 100644 --- a/drivers/i3c/i3c_ibi_workq.c +++ b/drivers/i3c/i3c_ibi_workq.c @@ -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; @@ -167,9 +193,13 @@ static void i3c_ibi_work_handler(struct k_work *work) payload = NULL; } - ret = ibi_node->target->ibi_cb(ibi_node->target, payload); - if ((ret != 0) && (ret != -EBUSY)) { - LOG_ERR("IBI work %p cb returns %d", ibi_node, ret); + if (ibi_node->target->ibi_cb) { + ret = ibi_node->target->ibi_cb(ibi_node->target, payload); + if ((ret != 0) && (ret != -EBUSY)) { + LOG_ERR("IBI work %p cb returns %d", ibi_node, ret); + } + } else { + LOG_ERR("No IBI callback for target %s", ibi_node->target->dev->name); } break; diff --git a/drivers/i3c/i3c_shell.c b/drivers/i3c/i3c_shell.c index cde8faaff96e781..2573525fd3e6f02 100644 --- a/drivers/i3c/i3c_shell.c +++ b/drivers/i3c/i3c_shell.c @@ -1045,20 +1045,9 @@ static int cmd_i3c_ccc_getacccr(const struct shell *sh, size_t argc, char **argv struct i3c_ccc_address handoff_address; int ret; - dev = device_get_binding(argv[ARGV_DEV]); - if (!dev) { - shell_error(sh, "I3C: Device driver %s not found.", argv[ARGV_DEV]); - return -ENODEV; - } - tdev = device_get_binding(argv[ARGV_TDEV]); - if (!tdev) { - shell_error(sh, "I3C: Device driver %s not found.", argv[ARGV_TDEV]); - return -ENODEV; - } - desc = get_i3c_attached_desc_from_dev_name(dev, tdev->name); - if (!desc) { - shell_error(sh, "I3C: Device %s not attached to bus.", tdev->name); - return -ENODEV; + ret = i3c_parse_args(sh, argv, &dev, &tdev, &desc); + if (ret != 0) { + return ret; } if (!i3c_device_is_controller_capable(desc)) { diff --git a/include/zephyr/drivers/i3c.h b/include/zephyr/drivers/i3c.h index a68f9e458fb0fc1..06d6ce3503ac019 100644 --- a/include/zephyr/drivers/i3c.h +++ b/include/zephyr/drivers/i3c.h @@ -867,6 +867,24 @@ __subsystem struct i3c_driver_api { */ int (*target_tx_write)(const struct device *dev, uint8_t *buf, uint16_t len, uint8_t hdr_mode); + + /** + * ACK or NACK controller handoffs + * + * This will tell the target to ACK or NACK controller handoffs + * from the CCC GETACCCR + * + * Target device only API. + * + * @see i3c_target_controller_handoff() + * + * @param dev Pointer to the controller device driver instance. + * @param accept True to ACK controller handoffs, False to NACK. + * + * @return See i3c_target_controller_handoff() + */ + int (*target_controller_handoff)(const struct device *dev, + bool accept); }; /** @@ -920,7 +938,8 @@ struct i3c_device_desc { const struct device * const dev; /** Device Provisioned ID */ - const uint64_t pid:48; + /* TODO: bring back the bitfield */ + const uint64_t pid; /** * Static address for this target device. @@ -1227,6 +1246,12 @@ struct i3c_driver_data { /** Attached I3C/I2C devices and addresses */ struct i3c_dev_attached_list attached_dev; + + /** Received DEFTGTS Pointer */ + struct i3c_ccc_deftgts *deftgts; + + /** DEFTGTS refreshed */ + bool deftgts_refreshed; }; /** @@ -2182,6 +2207,16 @@ uint8_t i3c_odd_parity(uint8_t p); */ int i3c_device_controller_handoff(const struct i3c_device_desc *target, bool requested); +void i3c_sec_handoffed(struct k_work *work); + +struct i3c_device_desc *i3c_alloc_i3c_device_desc(void); +void i3c_free_i3c_device_desc(struct i3c_device_desc *desc); +bool i3c_is_common_i3c_device_desc(struct i3c_device_desc *desc); + +struct i3c_i2c_device_desc *i3c_alloc_i3c_i2c_device_desc(void); +void i3c_free_i3c_i2c_device_desc(struct i3c_i2c_device_desc *desc); +bool i3c_is_common_i3c_i2c_device_desc(struct i3c_i2c_device_desc *desc); + /* * This needs to be after declaration of struct i3c_driver_api, * or else compiler complains about undefined type inside diff --git a/include/zephyr/drivers/i3c/ibi.h b/include/zephyr/drivers/i3c/ibi.h index 6020609b500fa1c..443d43ecb158742 100644 --- a/include/zephyr/drivers/i3c/ibi.h +++ b/include/zephyr/drivers/i3c/ibi.h @@ -184,6 +184,21 @@ int i3c_ibi_work_enqueue(struct i3c_ibi_work *ibi_work); int i3c_ibi_work_enqueue_target_irq(struct i3c_device_desc *target, uint8_t *payload, size_t payload_len); +/** + * @brief Queue a controllership request IBI for future processing. + * + * This queues up a controllership request IBI in the IBI workqueue + * for future processing. + * + * @param target Pointer to target device descriptor. + * + * @retval 0 If work item is successfully queued. + * @retval -ENOMEM If no more free internal node to + * store IBI work item. + * @retval Others @see k_work_submit_to_queue + */ +int i3c_ibi_work_enqueue_controller_request(struct i3c_device_desc *target); + /** * @brief Queue a hot join IBI for future processing. * diff --git a/include/zephyr/drivers/i3c/target_device.h b/include/zephyr/drivers/i3c/target_device.h index b2901f084ad83f5..ec70294ec5abc5e 100644 --- a/include/zephyr/drivers/i3c/target_device.h +++ b/include/zephyr/drivers/i3c/target_device.h @@ -218,6 +218,22 @@ struct i3c_target_callbacks { * @return Ignored. */ int (*stop_cb)(struct i3c_target_config *config); + + /** + * @brief Function called when an active controller handoffs controlership + * to this target. + * + * This function is invoked by the active controller when it handoffs + * controllership to this target. This can happen wither the target has + * requested it or if the active controller chooses to handoff to the + * controller capable target. + * + * @param config Configuration structure associated with the + * device to which the operation is addressed. + * + * @return Ignored. + */ + int (*controller_handoff_cb)(struct i3c_target_config *config); }; __subsystem struct i3c_target_driver_api { @@ -225,6 +241,38 @@ __subsystem struct i3c_target_driver_api { int (*driver_unregister)(const struct device *dev); }; +/** + * @brief Writes to the target's TX FIFO + * + * Write to the TX FIFO @p dev I3C bus driver using the provided + * buffer and length. Some I3C targets will NACK read requests until data + * is written to the TX FIFO. This function will write as much as it can + * to the FIFO return the total number of bytes written. It is then up to + * the application to utalize the target callbacks to write the remaining + * data. Negative returns indicate error. + * + * Most of the existing hardware allows simultaneous support for master + * and target mode. This is however not guaranteed. + * + * @param dev Pointer to the device structure for an I3C controller + * driver configured in target mode. + * @param accept True to ACK controller handoffs, False to NACK + * + * @retval 0 Is successful + */ +static inline int i3c_target_controller_handoff(const struct device *dev, + bool accept) +{ + const struct i3c_driver_api *api = + (const struct i3c_driver_api *)dev->api; + + if (api->target_controller_handoff == NULL) { + return -ENOSYS; + } + + return api->target_controller_handoff(dev, accept); +} + /** * @brief Writes to the target's TX FIFO *