drivers: i2c: mchp_sercom_g1: add multi-address target support#16
drivers: i2c: mchp_sercom_g1: add multi-address target support#16sashapapo-mchp wants to merge 1 commit into
Conversation
b1d5bde to
a9c874d
Compare
| struct i2c_target_config target_config; | ||
| struct i2c_target_callbacks target_callbacks; | ||
| /* Registered target configs (hardware supports up to two addresses). */ | ||
| struct i2c_target_config *target_cfgs[2]; |
There was a problem hiding this comment.
Added I2C_MCHP_TARGET_CFG_SLOTS and used it for the array/loops.
| /* Load the first byte for host read */ | ||
| target_cb->read_requested(&data->target_config, &data->rx_tx_data); | ||
| /* Host read (target transmits) */ | ||
| if (target_cb->read_requested) { |
There was a problem hiding this comment.
Add explicit NULL check
There was a problem hiding this comment.
Added explicit NULL checks for active_target_cfg and callbacks in address-match.
There was a problem hiding this comment.
Explicit check should be added like this as per our coding guide lines
if (target_cb->read_requested != NULL)
| return; | ||
| } | ||
|
|
||
| data->active_target_cfg = cfg; |
There was a problem hiding this comment.
data->active_target_cfg further not used in the function
There was a problem hiding this comment.
Now used directly; cfg local removed and callbacks use active_target_cfg.
| { | ||
| const struct i2c_target_callbacks *target_cb = &data->target_callbacks; | ||
| uint16_t matched_addr = i2c_mchp_get_matched_addr(dev); | ||
| struct i2c_target_config *cfg = i2c_mchp_find_target_cfg(data, matched_addr); |
There was a problem hiding this comment.
Can we use data->active_target_cfg directly here instead of extra local variable
There was a problem hiding this comment.
Done; address-match uses data->active_target_cfg directly.
| const struct i2c_target_callbacks *target_cb = &data->target_callbacks; | ||
| uint16_t matched_addr = i2c_mchp_get_matched_addr(dev); | ||
| struct i2c_target_config *cfg = i2c_mchp_find_target_cfg(data, matched_addr); | ||
| const struct i2c_target_callbacks *target_cb = NULL; |
There was a problem hiding this comment.
Can we initialize here directly
const struct i2c_target_callbacks *target_cb = data->active_target_cfg->callbacks
There was a problem hiding this comment.
Done; target_cb now comes from data->active_target_cfg->callbacks.
|
|
||
| for (size_t i = 0U; i < ARRAY_SIZE(data->target_cfgs); i++) { | ||
| if (data->target_cfgs[i] == target_cfg) { | ||
| slot = (int)i; |
There was a problem hiding this comment.
Same comments applicable as above
There was a problem hiding this comment.
Same loop updates applied in unregister path as well.
|
|
||
| if (slot < 0) { | ||
| retval = -EINVAL; | ||
| goto unlock; |
There was a problem hiding this comment.
Replaced goto unlock with do { ... } while (false) in unregister.
| } | ||
|
|
||
| if (!data->target_mode) { | ||
| i2c_controller_enable(dev, false); |
There was a problem hiding this comment.
Why i2c_controller_enable is called from here to disable controller
| return -EINVAL; | ||
| } | ||
| if (data->target_mode != true) { | ||
| LOG_ERR("device are not configured as target device\n"); |
There was a problem hiding this comment.
Why LOG_ERR is removed
There was a problem hiding this comment.
These have been restored.
|
|
||
| k_mutex_lock(&data->i2c_bus_mutex, K_FOREVER); | ||
|
|
||
| if (!data->target_mode || (data->target_cfg_count == 0U)) { |
There was a problem hiding this comment.
explicit check for data->target_mode
|
Could you please amend the changes and force-push to the same commit? |
|
Hi @sashapapo-mchp , I suggest adding multi-address target support as a separate feature. You can create a config option in Thanks |
The current driver will be heavily affected regardless. This is one of several patches that will come. I can add the KConfig, and will. But the driver will be affected. |
4b93830 to
69c186e
Compare
Done. It is one commit now. KConfig added. |
| /* Load the first byte for host read */ | ||
| target_cb->read_requested(&data->target_config, &data->rx_tx_data); | ||
| /* Host read (target transmits) */ | ||
| if (target_cb->read_requested) { |
There was a problem hiding this comment.
Explicit check should be added like this as per our coding guide lines
if (target_cb->read_requested != NULL)
|
|
||
| /* Host writing */ | ||
| target_cb->write_requested(&data->target_config); | ||
| /* Host write (target receives) */ |
There was a problem hiding this comment.
Change to if (target_cb->write_requested != NULL)
| /* Request a new buffer of data to send */ | ||
| retval = target_cb->buf_read_requested( | ||
| &data->target_config, &data->tx_buf_ptr, &data->tx_len); | ||
| if (target_cb->buf_read_requested) { |
There was a problem hiding this comment.
same as previous comment if (target_cb->buf_read_requested != NULL)
| const struct i2c_target_callbacks *target_cb = | ||
| (active_cfg != NULL) ? active_cfg->callbacks : NULL; | ||
| int retval = I2C_MCHP_SUCCESS; | ||
| uint8_t last_ack_state = i2c_target_get_lastbyte_ack_status(dev); |
There was a problem hiding this comment.
Why moved from if condition it was already checked in
if ((data->firstReadAfterAddrMatch == true) || (i2c_target_get_lastbyte_ack_status(dev) == I2C_MCHP_TARGET_ACK_STATUS_RECEIVED_ACK)) {
There was a problem hiding this comment.
Updated to previous way of checking.
| if (target_cb->read_requested) { | ||
| target_cb->read_requested(data->active_target_cfg, &data->rx_tx_data); | ||
| } | ||
| i2c_byte_write(dev, data->rx_tx_data); |
There was a problem hiding this comment.
Why i2c_byte_write(dev, data->rx_tx_data); is added here. Can you clarify?. It was already done inside i2c_target_data_ready()
There was a problem hiding this comment.
You are correct, good find. This has been fixed.
| return false; | ||
| } | ||
|
|
||
| return (cfg->address & 0x3FFU) == normalized_addr; |
There was a problem hiding this comment.
can we add a bracket return ((cfg->address & 0x3FFU) == normalized_addr);
| continue; | ||
| } | ||
|
|
||
| if (i2c_mchp_target_matches_addr(data->target_cfgs[i], addr)) { |
| k_mutex_lock(&data->i2c_bus_mutex, K_FOREVER); | ||
| data->target_config.address = target_cfg->address; | ||
| data->target_callbacks.write_requested = target_cfg->callbacks->write_requested; | ||
| data->target_callbacks.write_received = target_cfg->callbacks->write_received; |
There was a problem hiding this comment.
We can't blindly remove the callbacks from here as it affects the already existing driver. The tests added for those will fail. So this can't be removed.
| i2c_target_int_flag_clear(dev, SERCOM_I2CS_INTFLAG_Msk); | ||
| i2c_target_status_clear(dev, SERCOM_I2CS_STATUS_Msk); | ||
| } else { | ||
| i2c_target_enable(dev, false); |
There was a problem hiding this comment.
Will this affects the existing driver implementation
| } | ||
| } | ||
|
|
||
| if (retval != 0) { |
There was a problem hiding this comment.
Avoid using do while() loops
The addition of this feature should not cause any failures in the existing test cases that have already passed. |
69c186e to
e6698ca
Compare
I've ran both of the suggested tests and there are no issues, both i2c async and target_api pass as expected. For async I used the onboard AT24MAC402 on my board. |
| } | ||
|
|
||
| if ((slot < 0) && (data->target_cfgs[i] == NULL)) { | ||
| slot = i; |
There was a problem hiding this comment.
Why not exit the loop, if slot is allocated?
There was a problem hiding this comment.
This loop serves two purposes. It finds a free slot and then also detects if the particular target_cfg is also registered. If we add a break here, it would just find the first free slot and break, potentially registering the same target_cfg twice. A break here will not work without restructuring this and breaking it into two loops.
There was a problem hiding this comment.
Another implemenation option is, not to use array of target_cfg. Instead You can use active_target_cfg and alternate_target_cfg pointers. It can be swapped or assigned NULL as required. (Assuming, devices supporting more than 2 client addresses may be using another i2c gx driver version.)
if((active_target_cfg == target_cfg) || (alternate_target_cfg == target_cfg)) { -EALREADY;}
else if(active_target_cfg == NULL) { .. }
else if(alternate_target_cfg == NULL) { .. }
else { -EBUSY; }
|
|
||
| /* Prefer slot 0 if both are empty */ | ||
| if ((retval == 0) && (slot == 1) && (data->target_cfgs[0] == NULL)) { | ||
| slot = 0; |
There was a problem hiding this comment.
Better to have simpler logic, instead of resetting the slot again to 0.
There was a problem hiding this comment.
Agreed, I think this can be removed based on the previous checks. It might be dead code. I will correct.
| i2c_target_status_clear(dev, SERCOM_I2CS_STATUS_Msk); | ||
| } | ||
|
|
||
| data->target_cfgs[slot] = target_cfg; |
There was a problem hiding this comment.
You are saving only the pointer to the structure, that user has provided.
target_cfg may be pointing to a structure in user stack.
There was a problem hiding this comment.
Not storing the target config within the driver is a standard pattern. Having the client responsible for the memory allocation and storage of the target configs is a pattern that other vendors such as st follow.
| uint8_t target_cfg_count; | ||
| #else | ||
| /* Single target configuration (legacy behavior). */ | ||
| struct i2c_target_config *target_cfg; |
There was a problem hiding this comment.
Where is the memory to store target config structure elements?
There was a problem hiding this comment.
We do not need to store the target config, the client will allocate memory for this. Same comment as above.
| #ifdef CONFIG_I2C_TARGET | ||
| struct i2c_target_config target_config; | ||
| struct i2c_target_callbacks target_callbacks; | ||
| #ifdef CONFIG_I2C_MCHP_TARGET_MULTI_ADDR |
There was a problem hiding this comment.
Both cases can be combined (single address and multi address)
- I2C_MCHP_TARGET_CFG_SLOTS = 1
- I2C_MCHP_TARGET_CFG_SLOTS = 2
| struct i2c_target_callbacks target_callbacks; | ||
| #ifdef CONFIG_I2C_MCHP_TARGET_MULTI_ADDR | ||
| /* Registered target configs (hardware supports up to two addresses). */ | ||
| struct i2c_target_config *target_cfgs[I2C_MCHP_TARGET_CFG_SLOTS]; |
There was a problem hiding this comment.
Get I2C_MCHP_TARGET_CFG_SLOTS, from device tree.
There was a problem hiding this comment.
The G1 i2c peripheral can only support 2 I2C addresses as a target. Having it in the device tree would allow a user to set it to whatever they want and it would cause confusion during compile time.
Ideally, we wouldn't even have a config or define. You should simply be able to call i2c_mchp_target_register twice and it would work. This is the pattern that other vendors follow, there is no configuration in the device tree or in the config.
| #include <zephyr/irq.h> | ||
| #include <zephyr/logging/log.h> | ||
| #include <zephyr/drivers/i2c.h> | ||
| #include <zephyr/drivers/dma.h> |
There was a problem hiding this comment.
Please use latest code.
dma related headers are included with
#ifdef CONFIG_I2C_MCHP_DMA_DRIVEN
| ten_bit = ((target_cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS) != 0U); | ||
| addr_limit = ten_bit ? 0x3FFU : 0x7FU; | ||
|
|
||
| if (target_cfg->address > addr_limit) { |
There was a problem hiding this comment.
This if condition can be combined with
if (target_cfg->address == I2C_INVALID_ADDR)
| retval = -EBUSY; | ||
| } | ||
|
|
||
| if (retval == 0) { |
There was a problem hiding this comment.
Could avoid checking retval == 0, multiple times, by using
k_mutex_lock()
do {
logic .....
if failure, break;
logic .....
} while(0)
k_mutex_unlock()
| } | ||
|
|
||
| if ((slot < 0) && (data->target_cfgs[i] == NULL)) { | ||
| slot = i; |
There was a problem hiding this comment.
Another implemenation option is, not to use array of target_cfg. Instead You can use active_target_cfg and alternate_target_cfg pointers. It can be swapped or assigned NULL as required. (Assuming, devices supporting more than 2 client addresses may be using another i2c gx driver version.)
if((active_target_cfg == target_cfg) || (alternate_target_cfg == target_cfg)) { -EALREADY;}
else if(active_target_cfg == NULL) { .. }
else if(alternate_target_cfg == NULL) { .. }
else { -EBUSY; }
Add support for registering up to two I2C target addresses on a single SERCOM instance, leveraging the hardware's dual-address capability via the AMODE field. Changes: - Replace single target_config/target_callbacks with target_cfgs[2] pointer array to support multiple registered configurations - Add target_cfg_count and active_target_cfg tracking - Add helper functions for address matching and configuration lookup - Update i2c_target_register() for slot-based registration with validation of 7-bit/10-bit address constraints - Update i2c_target_unregister() with slot compaction when removing the primary address - Update interrupt handlers to dispatch to the correct configuration based on the matched address The secondary address uses the ADDRMASK register with AMODE set to 2_ADDRESSES mode. Both addresses must be 7-bit when using dual-address mode per hardware requirements. Signed-off-by: Sasha Papo <sasha.papo@microchip.com> Made-with: Cursor
e6698ca to
7878a7e
Compare
Add support for registering up to two I2C target addresses on a single SERCOM instance, leveraging the hardware's dual-address capability via the AMODE field.
Changes:
The secondary address uses the ADDRMASK register with AMODE set to 2_ADDRESSES mode. Both addresses must be 7-bit when using dual-address mode per hardware requirements.