Compared to the existing DW_apb_i2c layout [1], DWC_i2c present in TDA54 SoC [0] differs enough to need MODEL_TI_TDA54-gated special-casing in a few places:
- Add new register offsets table for TI TDA54 SoC. - The DW_IC_CON register bitfields are also updated. - Interrupts are acknowledged by writing a bitmask to a single CLR_INTR register instead of reading N dedicated CLR_* registers; i2c_dw_ack_intr() picks the right method based on dev->flags. - One HCNT/LCNT register pair is shared between standard and fast speed instead of having one pair each; i2c_dw_write_timings() writes whichever value set matches the configured speed. - No COMP_PARAM_1 register, so FIFO depth and high-speed-mode support can't be autodetected: FIFO depth now comes from the required snps,tx-fifo-depth/snps,rx-fifo-depth DT properties, and the high-speed capability check is skipped. - No defined CON.RESTART_EN bit; treated as always set. - TDA54 SoC does not have SMBus block, skip accesses to those registers. [0]: DWC_i2c_reference.pdf [1]: DW_apb_i2c_databook.pdf Signed-off-by: Aniket Limaye <[email protected]> --- Changes in v2: - Rename compatible/flag: snps,dwc-i2c -> ti,tda54-i2c, MODEL_DWC_I2C -> MODEL_TI_TDA54. - Remove i2c_dwc_has_smbus(); just skip the SMBUS_INTR_MASK write under MODEL_TI_TDA54. - Link to v1: https://lore.kernel.org/r/[email protected]/ --- drivers/i2c/busses/i2c-designware-common.c | 128 +++++++++++++++++++++++++--- drivers/i2c/busses/i2c-designware-core.h | 22 ++++- drivers/i2c/busses/i2c-designware-master.c | 26 +++--- drivers/i2c/busses/i2c-designware-platdrv.c | 1 + 4 files changed, 153 insertions(+), 24 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index bd25b9c582c1..8cb162a6aed8 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -72,6 +72,40 @@ static const char *const abort_sources[] = { "incorrect slave-transmitter mode configuration", }; +/* "ti,tda54-i2c" block-based register layout */ +#define DWC_ADV_OPERATION_BLK_OFFSET 0x00 +#define DWC_ADV_I2C_BLK_OFFSET 0x28 +#define DWC_ADV_FIFO_INTR_BLK_OFFSET 0xbc +#define DWC_ADV_DEBUG_BLK_OFFSET 0xf8 + +static const u32 tda54_i2c_reg_offsets[DW_IC_REG_MAX] = { + [DW_IC_ENABLE] = DWC_ADV_OPERATION_BLK_OFFSET + 0x04, + [DW_IC_CON] = DWC_ADV_I2C_BLK_OFFSET + 0x04, + [DW_IC_TAR] = DWC_ADV_I2C_BLK_OFFSET + 0x08, + [DW_IC_SAR] = DWC_ADV_I2C_BLK_OFFSET + 0x0c, + [DW_IC_DATA_CMD] = DWC_ADV_I2C_BLK_OFFSET + 0x58, + [DW_IC_SS_SCL_HCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x24, /* shared SS/FS pair */ + [DW_IC_SS_SCL_LCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x28, + [DW_IC_FS_SCL_HCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x24, + [DW_IC_FS_SCL_LCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x28, + [DW_IC_HS_SCL_HCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x2c, + [DW_IC_HS_SCL_LCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x30, + [DW_IC_SDA_HOLD] = DWC_ADV_I2C_BLK_OFFSET + 0x34, + [DW_IC_RX_TL] = DWC_ADV_I2C_BLK_OFFSET + 0x5c, + [DW_IC_TX_TL] = DWC_ADV_I2C_BLK_OFFSET + 0x60, + [DW_IC_INTR_STAT] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x04, + [DW_IC_INTR_MASK] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x08, + [DW_IC_RAW_INTR_STAT] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x0c, + [DW_IC_CLR_INTR] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x10, + [DW_IC_STATUS] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x1c, + [DW_IC_TXFLR] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x20, + [DW_IC_RXFLR] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x24, + [DW_IC_TX_ABRT_SOURCE] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x18, + [DW_IC_ENABLE_STATUS] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x14, + [DW_IC_COMP_VERSION] = DWC_ADV_DEBUG_BLK_OFFSET + 0x08, + [DW_IC_COMP_TYPE] = DWC_ADV_DEBUG_BLK_OFFSET + 0x0c, +}; + static const struct dw_i2c_con_bits dw_i2c_con_bits = { .master = DW_IC_CON_MASTER, .speed_std = DW_IC_CON_SPEED_STD, @@ -88,6 +122,26 @@ static const struct dw_i2c_con_bits dw_i2c_con_bits = { .bus_clear_ctrl = DW_IC_CON_BUS_CLEAR_CTRL, }; +/* + * DWC_IC_CTRL bit layout for "ti,tda54-i2c". + * There is no defined bit for RESTART_EN or SLAVE_DISABLE on this IP. + */ +static const struct dw_i2c_con_bits dwc_adv_i2c_con_bits = { + .master = BIT(0), + .speed_std = (1 << 4), + .speed_fast = (2 << 4), + .speed_high = (3 << 4), + .speed_mask = GENMASK(5, 4), + .bit10_slave = BIT(8), + .bit10_master = BIT(9), + .restart_en = 0, + .slave_disable = 0, + .stop_det_ifaddressed = BIT(10), + .tx_empty_ctrl = BIT(11), + .rx_fifo_full_hld_ctrl = BIT(12), + .bus_clear_ctrl = 0, +}; + /* "snps,designware-i2c": dedicated read-to-clear register ID per logical interrupt */ static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = { [DW_INTR_IDX_INTR] = DW_IC_CLR_INTR, @@ -103,6 +157,21 @@ static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = { [DW_INTR_IDX_GEN_CALL] = DW_IC_CLR_GEN_CALL, }; +/* "ti,tda54-i2c" CLR_INTR bit to write per logical interrupt */ +static const u32 dwc_adv_i2c_intr_clr[DW_INTR_IDX_MAX] = { + [DW_INTR_IDX_INTR] = DWC_ADV_IC_INTR_CLR_INTR, + [DW_INTR_IDX_RX_UNDER] = DWC_ADV_IC_INTR_CLR_RX_UNDER, + [DW_INTR_IDX_RX_OVER] = DWC_ADV_IC_INTR_CLR_RX_OVER, + [DW_INTR_IDX_TX_OVER] = DWC_ADV_IC_INTR_CLR_TX_OVER, + [DW_INTR_IDX_RD_REQ] = DWC_ADV_IC_INTR_CLR_RD_REQ, + [DW_INTR_IDX_TX_ABRT] = DWC_ADV_IC_INTR_CLR_TX_ABRT, + [DW_INTR_IDX_RX_DONE] = DWC_ADV_IC_INTR_CLR_RX_DONE, + [DW_INTR_IDX_ACTIVITY] = DWC_ADV_IC_INTR_CLR_ACTIVITY, + [DW_INTR_IDX_STOP_DET] = DWC_ADV_IC_INTR_CLR_STOP_DET, + [DW_INTR_IDX_START_DET] = DWC_ADV_IC_INTR_CLR_START_DET, + [DW_INTR_IDX_GEN_CALL] = DWC_ADV_IC_INTR_CLR_GEN_CALL, +}; + /** * i2c_dw_select_variant() - Pick the register offset table, CON-register bit * layout and interrupt-ack mapping matching this device's IP variant @@ -110,9 +179,15 @@ static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = { */ void i2c_dw_select_variant(struct dw_i2c_dev *dev) { - dev->regs = dw_i2c_reg_offsets; - dev->con_bits = &dw_i2c_con_bits; - dev->intr_clr = dw_i2c_intr_clr; + if (dev->flags & MODEL_TI_TDA54) { + dev->regs = tda54_i2c_reg_offsets; + dev->con_bits = &dwc_adv_i2c_con_bits; + dev->intr_clr = dwc_adv_i2c_intr_clr; + } else { + dev->regs = dw_i2c_reg_offsets; + dev->con_bits = &dw_i2c_con_bits; + dev->intr_clr = dw_i2c_intr_clr; + } } EXPORT_SYMBOL_GPL(i2c_dw_select_variant); @@ -432,13 +507,27 @@ static void i2c_dw_configure_mode(struct dw_i2c_dev *dev, int mode) static void i2c_dw_write_timings(struct dw_i2c_dev *dev) { - /* Write standard speed timing parameters */ - regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt); - regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt); - - /* Write fast mode/fast mode plus timing parameters */ - regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt); - regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt); + if (dev->flags & MODEL_TI_TDA54) { + /* + * Only one HCNT/LCNT register pair backs both speeds on + * this IP -- write whichever value set matches master_cfg. + */ + if ((dev->master_cfg & dev->con_bits->speed_mask) == dev->con_bits->speed_std) { + regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt); + regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt); + } else { + regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt); + regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt); + } + } else { + /* Write standard speed timing parameters */ + regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt); + regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt); + + /* Write fast mode/fast mode plus timing parameters */ + regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt); + regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt); + } /* Write high speed timing parameters */ regmap_write(dev->map, DW_IC_HS_SCL_HCNT, dev->hs_hcnt); @@ -493,8 +582,10 @@ int i2c_dw_init(struct dw_i2c_dev *dev) * Mask SMBus interrupts to block storms from broken * firmware that leaves IC_SMBUS=1; the handler never * services them. + * TDA54 SoC has no SMBus block, so skip it. */ - regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0); + if (!(dev->flags & MODEL_TI_TDA54)) + regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0); i2c_dw_write_timings(dev); @@ -538,6 +629,14 @@ int i2c_dw_fw_parse_and_configure(struct dw_i2c_dev *dev) if (device_property_read_u32(device, "snps,bus-capacitance-pf", &dev->bus_capacitance_pF)) dev->bus_capacitance_pF = DW_IC_DEFAULT_BUS_CAPACITANCE_pF; + if (dev->flags & MODEL_TI_TDA54) { + device_property_read_u32(device, "snps,tx-fifo-depth", &dev->tx_fifo_depth); + device_property_read_u32(device, "snps,rx-fifo-depth", &dev->rx_fifo_depth); + + if (!dev->tx_fifo_depth || !dev->rx_fifo_depth) + return -EINVAL; + } + dev->clk_freq_optimized = device_property_read_bool(device, "snps,clk-freq-optimized"); /* Mobileye controllers do not hold the clock on empty FIFO */ @@ -854,6 +953,13 @@ static int i2c_dw_set_fifo_size(struct dw_i2c_dev *dev) return 0; } + /* + * DW_IC_COMP_PARAM_1 not implemented on this IP; + * fifo depth set in i2c_dw_fw_parse_and_configure(). + */ + if (dev->flags & MODEL_TI_TDA54) + return 0; + /* * Try to detect the FIFO depth if not set by interface driver, * the depth could be from 2 to 256 from HW spec. diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 4c782594ab4e..bd96abf63c39 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -79,6 +79,18 @@ enum dw_i2c_intr_idx { DW_INTR_IDX_MAX, }; +#define DWC_ADV_IC_INTR_CLR_INTR BIT(0) +#define DWC_ADV_IC_INTR_CLR_RX_UNDER BIT(1) +#define DWC_ADV_IC_INTR_CLR_RX_OVER BIT(2) +#define DWC_ADV_IC_INTR_CLR_TX_OVER BIT(3) +#define DWC_ADV_IC_INTR_CLR_RD_REQ BIT(4) +#define DWC_ADV_IC_INTR_CLR_TX_ABRT BIT(5) +#define DWC_ADV_IC_INTR_CLR_RX_DONE BIT(6) +#define DWC_ADV_IC_INTR_CLR_ACTIVITY BIT(7) +#define DWC_ADV_IC_INTR_CLR_STOP_DET BIT(8) +#define DWC_ADV_IC_INTR_CLR_START_DET BIT(9) +#define DWC_ADV_IC_INTR_CLR_GEN_CALL BIT(10) + #define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \ DW_IC_INTR_TX_ABRT | \ DW_IC_INTR_STOP_DET) @@ -291,7 +303,8 @@ struct dw_i2c_dev { #define MODEL_AMD_NAVI_GPU BIT(10) #define MODEL_WANGXUN_SP BIT(11) -#define MODEL_MASK GENMASK(11, 8) +#define MODEL_TI_TDA54 BIT(12) +#define MODEL_MASK GENMASK(12, 8) /* * Enable UCSI interrupt by writing 0xd at register @@ -352,12 +365,15 @@ static inline void __i2c_dw_read_intr_mask(struct dw_i2c_dev *dev, *intr_mask = dev->sw_mask; } -/* Acknowledge a logical interrupt via dev->intr_clr[]: reg ID */ +/* Acknowledge a logical interrupt via dev->intr_clr[]: reg ID or bit value */ static inline void i2c_dw_ack_intr(struct dw_i2c_dev *dev, enum dw_i2c_intr_idx intr) { unsigned int dummy; - regmap_read(dev->map, dev->intr_clr[intr], &dummy); + if (dev->flags & MODEL_TI_TDA54) + regmap_write(dev->map, DW_IC_CLR_INTR, dev->intr_clr[intr]); + else + regmap_read(dev->map, dev->intr_clr[intr], &dummy); } void __i2c_dw_disable(struct dw_i2c_dev *dev); diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 329f958ded3e..94594f6349d4 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -42,14 +42,16 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) u32 ic_clk; int ret; - ret = i2c_dw_acquire_lock(dev); - if (ret) - return ret; - - ret = regmap_read(dev->map, DW_IC_COMP_PARAM_1, &comp_param1); - i2c_dw_release_lock(dev); - if (ret) - return ret; + if (!(dev->flags & MODEL_TI_TDA54)) { + ret = i2c_dw_acquire_lock(dev); + if (ret) + return ret; + + ret = regmap_read(dev->map, DW_IC_COMP_PARAM_1, &comp_param1); + i2c_dw_release_lock(dev); + if (ret) + return ret; + } /* Set standard and fast speed dividers for high/low periods */ sda_falling_time = t->sda_fall_ns ?: 300; /* ns */ @@ -135,7 +137,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev) /* Check is high speed possible and fall back to fast mode if not */ if ((dev->master_cfg & dev->con_bits->speed_mask) == dev->con_bits->speed_high) { - if ((comp_param1 & DW_IC_COMP_PARAM_1_SPEED_MODE_MASK) + if (!(dev->flags & MODEL_TI_TDA54) && + (comp_param1 & DW_IC_COMP_PARAM_1_SPEED_MODE_MASK) != DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH) { dev_err(dev->dev, "High Speed not supported!\n"); t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ; @@ -398,8 +401,11 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) * If both IC_EMPTYFIFO_HOLD_MASTER_EN and * IC_RESTART_EN are set, we must manually * set restart bit between messages. + * ti,tda54-i2c does not define a CON.RESTART_EN + * bit, and behaves like it is set to 1. */ - if (dev->master_cfg & dev->con_bits->restart_en && + if (((dev->master_cfg & dev->con_bits->restart_en) || + (dev->flags & MODEL_TI_TDA54)) && dev->msg_write_idx > 0) need_restart = true; } diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 42b34c678146..069c3dc0da10 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -257,6 +257,7 @@ static const struct of_device_id dw_i2c_of_match[] = { { .compatible = "mobileye,eyeq6lplus-i2c" }, { .compatible = "mscc,ocelot-i2c" }, { .compatible = "snps,designware-i2c" }, + { .compatible = "ti,tda54-i2c", .data = (void *)MODEL_TI_TDA54 }, {} }; MODULE_DEVICE_TABLE(of, dw_i2c_of_match); -- 2.53.0
