diff options
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/busses/i2c-tegra.c | 189 |
1 files changed, 159 insertions, 30 deletions
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index cfecd81789c3..a4cd79c9f7a7 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -126,6 +126,15 @@ #define I2C_MST_FIFO_STATUS_TX_MASK 0xff0000 #define I2C_MST_FIFO_STATUS_TX_SHIFT 16 +#define I2C_INTERFACE_TIMING_0 0x94 +#define I2C_THIGH_SHIFT 8 +#define I2C_INTERFACE_TIMING_1 0x98 + +#define I2C_STANDARD_MODE 100000 +#define I2C_FAST_MODE 400000 +#define I2C_FAST_PLUS_MODE 1000000 +#define I2C_HS_MODE 3500000 + /* Packet header size in bytes */ #define I2C_PACKET_HEADER_SIZE 12 @@ -160,7 +169,10 @@ enum msg_end_type { * @has_config_load_reg: Has the config load register to load the new * configuration. * @clk_divisor_hs_mode: Clock divisor in HS mode. - * @clk_divisor_std_fast_mode: Clock divisor in standard/fast mode. It is + * @clk_divisor_std_mode: Clock divisor in standard mode. It is + * applicable if there is no fast clock source i.e. single clock + * source. + * @clk_divisor_fast_mode: Clock divisor in fast mode. It is * applicable if there is no fast clock source i.e. single clock * source. * @clk_divisor_fast_plus_mode: Clock divisor in fast mode plus. It is @@ -178,6 +190,18 @@ enum msg_end_type { * @supports_bus_clear: Bus Clear support to recover from bus hang during * SDA stuck low from device for some unknown reasons. * @has_apb_dma: Support of APBDMA on corresponding Tegra chip. + * @tlow_std_mode: Low period of the clock in standard mode. + * @thigh_std_mode: High period of the clock in standard mode. + * @tlow_fast_fastplus_mode: Low period of the clock in fast/fast-plus modes. + * @thigh_fast_fastplus_mode: High period of the clock in fast/fast-plus modes. + * @setup_hold_time_std_mode: Setup and hold time for start and stop conditions + * in standard mode. + * @setup_hold_time_fast_fast_plus_mode: Setup and hold time for start and stop + * conditions in fast/fast-plus modes. + * @setup_hold_time_hs_mode: Setup and hold time for start and stop conditions + * in HS mode. + * @has_interface_timing_reg: Has interface timing register to program the tuned + * timing settings. */ struct tegra_i2c_hw_feature { bool has_continue_xfer_support; @@ -185,7 +209,8 @@ struct tegra_i2c_hw_feature { bool has_single_clk_source; bool has_config_load_reg; int clk_divisor_hs_mode; - int clk_divisor_std_fast_mode; + int clk_divisor_std_mode; + int clk_divisor_fast_mode; u16 clk_divisor_fast_plus_mode; bool has_multi_master_mode; bool has_slcg_override_reg; @@ -193,6 +218,14 @@ struct tegra_i2c_hw_feature { const struct i2c_adapter_quirks *quirks; bool supports_bus_clear; bool has_apb_dma; + u8 tlow_std_mode; + u8 thigh_std_mode; + u8 tlow_fast_fastplus_mode; + u8 thigh_fast_fastplus_mode; + u32 setup_hold_time_std_mode; + u32 setup_hold_time_fast_fast_plus_mode; + u32 setup_hold_time_hs_mode; + bool has_interface_timing_reg; }; /** @@ -660,11 +693,13 @@ static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev) return 0; } -static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) +static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev, bool clk_reinit) { u32 val; int err; - u32 clk_divisor; + u32 clk_divisor, clk_multiplier; + u32 tsu_thd = 0; + u8 tlow, thigh; err = pm_runtime_get_sync(i2c_dev->dev); if (err < 0) { @@ -694,6 +729,41 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) I2C_CLK_DIVISOR_STD_FAST_MODE_SHIFT; i2c_writel(i2c_dev, clk_divisor, I2C_CLK_DIVISOR); + if (i2c_dev->bus_clk_rate > I2C_STANDARD_MODE && + i2c_dev->bus_clk_rate <= I2C_FAST_PLUS_MODE) { + tlow = i2c_dev->hw->tlow_fast_fastplus_mode; + thigh = i2c_dev->hw->thigh_fast_fastplus_mode; + tsu_thd = i2c_dev->hw->setup_hold_time_fast_fast_plus_mode; + } else { + tlow = i2c_dev->hw->tlow_std_mode; + thigh = i2c_dev->hw->thigh_std_mode; + tsu_thd = i2c_dev->hw->setup_hold_time_std_mode; + } + + if (i2c_dev->hw->has_interface_timing_reg) { + val = (thigh << I2C_THIGH_SHIFT) | tlow; + i2c_writel(i2c_dev, val, I2C_INTERFACE_TIMING_0); + } + + /* + * configure setup and hold times only when tsu_thd is non-zero. + * otherwise, preserve the chip default values + */ + if (i2c_dev->hw->has_interface_timing_reg && tsu_thd) + i2c_writel(i2c_dev, tsu_thd, I2C_INTERFACE_TIMING_1); + + if (!clk_reinit) { + clk_multiplier = (tlow + thigh + 2); + clk_multiplier *= (i2c_dev->clk_divisor_non_hs_mode + 1); + err = clk_set_rate(i2c_dev->div_clk, + i2c_dev->bus_clk_rate * clk_multiplier); + if (err) { + dev_err(i2c_dev->dev, + "failed changing clock rate: %d\n", err); + goto err; + } + } + if (!i2c_dev->is_dvc) { u32 sl_cfg = i2c_readl(i2c_dev, I2C_SL_CNFG); @@ -1097,7 +1167,7 @@ unlock: dmaengine_terminate_sync(i2c_dev->msg_read ? i2c_dev->rx_dma_chan : i2c_dev->tx_dma_chan); - tegra_i2c_init(i2c_dev); + tegra_i2c_init(i2c_dev, true); return -ETIMEDOUT; } @@ -1123,7 +1193,7 @@ unlock: if (time_left == 0) { dev_err(i2c_dev->dev, "i2c transfer timed out\n"); - tegra_i2c_init(i2c_dev); + tegra_i2c_init(i2c_dev, true); return -ETIMEDOUT; } @@ -1135,7 +1205,7 @@ unlock: if (likely(i2c_dev->msg_err == I2C_ERR_NONE)) return 0; - tegra_i2c_init(i2c_dev); + tegra_i2c_init(i2c_dev, true); /* start recovery upon arbitration loss in single master mode */ if (i2c_dev->msg_err == I2C_ERR_ARBITRATION_LOST) { if (!i2c_dev->is_multimaster_mode) @@ -1235,7 +1305,8 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .has_per_pkt_xfer_complete_irq = false, .has_single_clk_source = false, .clk_divisor_hs_mode = 3, - .clk_divisor_std_fast_mode = 0, + .clk_divisor_std_mode = 0, + .clk_divisor_fast_mode = 0, .clk_divisor_fast_plus_mode = 0, .has_config_load_reg = false, .has_multi_master_mode = false, @@ -1244,6 +1315,14 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .quirks = &tegra_i2c_quirks, .supports_bus_clear = false, .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_fastplus_mode = 0x4, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0x0, + .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_hs_mode = 0x0, + .has_interface_timing_reg = false, }; static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { @@ -1251,7 +1330,8 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .has_per_pkt_xfer_complete_irq = false, .has_single_clk_source = false, .clk_divisor_hs_mode = 3, - .clk_divisor_std_fast_mode = 0, + .clk_divisor_std_mode = 0, + .clk_divisor_fast_mode = 0, .clk_divisor_fast_plus_mode = 0, .has_config_load_reg = false, .has_multi_master_mode = false, @@ -1260,6 +1340,14 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .quirks = &tegra_i2c_quirks, .supports_bus_clear = false, .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_fastplus_mode = 0x4, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0x0, + .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_hs_mode = 0x0, + .has_interface_timing_reg = false, }; static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { @@ -1267,7 +1355,8 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .has_per_pkt_xfer_complete_irq = true, .has_single_clk_source = true, .clk_divisor_hs_mode = 1, - .clk_divisor_std_fast_mode = 0x19, + .clk_divisor_std_mode = 0x19, + .clk_divisor_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = false, .has_multi_master_mode = false, @@ -1276,6 +1365,14 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .quirks = &tegra_i2c_quirks, .supports_bus_clear = true, .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_fastplus_mode = 0x4, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0x0, + .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_hs_mode = 0x0, + .has_interface_timing_reg = false, }; static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { @@ -1283,7 +1380,8 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .has_per_pkt_xfer_complete_irq = true, .has_single_clk_source = true, .clk_divisor_hs_mode = 1, - .clk_divisor_std_fast_mode = 0x19, + .clk_divisor_std_mode = 0x19, + .clk_divisor_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = true, .has_multi_master_mode = false, @@ -1292,6 +1390,14 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .quirks = &tegra_i2c_quirks, .supports_bus_clear = true, .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_fastplus_mode = 0x4, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0x0, + .setup_hold_time_fast_fast_plus_mode = 0x0, + .setup_hold_time_hs_mode = 0x0, + .has_interface_timing_reg = true, }; static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { @@ -1299,7 +1405,8 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .has_per_pkt_xfer_complete_irq = true, .has_single_clk_source = true, .clk_divisor_hs_mode = 1, - .clk_divisor_std_fast_mode = 0x19, + .clk_divisor_std_mode = 0x19, + .clk_divisor_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = true, .has_multi_master_mode = true, @@ -1308,6 +1415,14 @@ static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { .quirks = &tegra_i2c_quirks, .supports_bus_clear = true, .has_apb_dma = true, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x2, + .tlow_fast_fastplus_mode = 0x4, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0, + .setup_hold_time_fast_fast_plus_mode = 0, + .setup_hold_time_hs_mode = 0, + .has_interface_timing_reg = true, }; static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { @@ -1315,7 +1430,8 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .has_per_pkt_xfer_complete_irq = true, .has_single_clk_source = true, .clk_divisor_hs_mode = 1, - .clk_divisor_std_fast_mode = 0x19, + .clk_divisor_std_mode = 0x16, + .clk_divisor_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = true, .has_multi_master_mode = true, @@ -1324,6 +1440,14 @@ static const struct tegra_i2c_hw_feature tegra186_i2c_hw = { .quirks = &tegra_i2c_quirks, .supports_bus_clear = true, .has_apb_dma = false, + .tlow_std_mode = 0x4, + .thigh_std_mode = 0x3, + .tlow_fast_fastplus_mode = 0x4, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0, + .setup_hold_time_fast_fast_plus_mode = 0, + .setup_hold_time_hs_mode = 0, + .has_interface_timing_reg = true, }; static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { @@ -1331,8 +1455,9 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .has_per_pkt_xfer_complete_irq = true, .has_single_clk_source = true, .clk_divisor_hs_mode = 1, - .clk_divisor_std_fast_mode = 0x19, - .clk_divisor_fast_plus_mode = 0x10, + .clk_divisor_std_mode = 0x4f, + .clk_divisor_fast_mode = 0x3c, + .clk_divisor_fast_plus_mode = 0x16, .has_config_load_reg = true, .has_multi_master_mode = true, .has_slcg_override_reg = true, @@ -1340,6 +1465,14 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = { .quirks = &tegra194_i2c_quirks, .supports_bus_clear = true, .has_apb_dma = false, + .tlow_std_mode = 0x8, + .thigh_std_mode = 0x7, + .tlow_fast_fastplus_mode = 0x2, + .thigh_fast_fastplus_mode = 0x2, + .setup_hold_time_std_mode = 0x08080808, + .setup_hold_time_fast_fast_plus_mode = 0x02020202, + .setup_hold_time_hs_mode = 0x090909, + .has_interface_timing_reg = true, }; /* Match table for of_platform binding */ @@ -1366,7 +1499,6 @@ static int tegra_i2c_probe(struct platform_device *pdev) phys_addr_t base_phys; int irq; int ret = 0; - int clk_multiplier = I2C_CLK_MULTIPLIER_STD_FAST_MODE; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base_phys = res->start; @@ -1438,20 +1570,17 @@ static int tegra_i2c_probe(struct platform_device *pdev) } } - i2c_dev->clk_divisor_non_hs_mode = - i2c_dev->hw->clk_divisor_std_fast_mode; - if (i2c_dev->hw->clk_divisor_fast_plus_mode && - (i2c_dev->bus_clk_rate == 1000000)) + if (i2c_dev->bus_clk_rate > I2C_FAST_MODE && + i2c_dev->bus_clk_rate <= I2C_FAST_PLUS_MODE) i2c_dev->clk_divisor_non_hs_mode = - i2c_dev->hw->clk_divisor_fast_plus_mode; - - clk_multiplier *= (i2c_dev->clk_divisor_non_hs_mode + 1); - ret = clk_set_rate(i2c_dev->div_clk, - i2c_dev->bus_clk_rate * clk_multiplier); - if (ret) { - dev_err(i2c_dev->dev, "Clock rate change failed %d\n", ret); - goto unprepare_fast_clk; - } + i2c_dev->hw->clk_divisor_fast_plus_mode; + else if (i2c_dev->bus_clk_rate > I2C_STANDARD_MODE && + i2c_dev->bus_clk_rate <= I2C_FAST_MODE) + i2c_dev->clk_divisor_non_hs_mode = + i2c_dev->hw->clk_divisor_fast_mode; + else + i2c_dev->clk_divisor_non_hs_mode = + i2c_dev->hw->clk_divisor_std_mode; ret = clk_prepare(i2c_dev->div_clk); if (ret < 0) { @@ -1484,7 +1613,7 @@ static int tegra_i2c_probe(struct platform_device *pdev) if (ret < 0) goto disable_div_clk; - ret = tegra_i2c_init(i2c_dev); + ret = tegra_i2c_init(i2c_dev, false); if (ret) { dev_err(&pdev->dev, "Failed to initialize i2c controller\n"); goto release_dma; |