forked from Minki/linux
i2c: tegra: Add support for the VI I2C on Tegra210
Tegra210 has an extra instance of the I2C controller that is in the domain of host1x and usually used for camera use-cases. The programming model for the VI variant of the controller is roughly the same as for the other variants, except that the I2C registers start at an offset and are spaced further apart. VI I2C also doesn't support slave mode. Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
82dd45f5cb
commit
c73178b937
@ -40,6 +40,7 @@
|
||||
#define I2C_SL_CNFG_NEWSL BIT(2)
|
||||
#define I2C_SL_ADDR1 0x02c
|
||||
#define I2C_SL_ADDR2 0x030
|
||||
#define I2C_TLOW_SEXT 0x034
|
||||
#define I2C_TX_FIFO 0x050
|
||||
#define I2C_RX_FIFO 0x054
|
||||
#define I2C_PACKET_TRANSFER_STATUS 0x058
|
||||
@ -109,6 +110,18 @@
|
||||
#define I2C_INTERFACE_TIMING_THIGH GENMASK(13, 8)
|
||||
#define I2C_INTERFACE_TIMING_TLOW GENMASK(5, 0)
|
||||
#define I2C_INTERFACE_TIMING_1 0x098
|
||||
#define I2C_INTERFACE_TIMING_TBUF GENMASK(29, 24)
|
||||
#define I2C_INTERFACE_TIMING_TSU_STO GENMASK(21, 16)
|
||||
#define I2C_INTERFACE_TIMING_THD_STA GENMASK(13, 8)
|
||||
#define I2C_INTERFACE_TIMING_TSU_STA GENMASK(5, 0)
|
||||
|
||||
#define I2C_HS_INTERFACE_TIMING_0 0x09c
|
||||
#define I2C_HS_INTERFACE_TIMING_THIGH GENMASK(13, 8)
|
||||
#define I2C_HS_INTERFACE_TIMING_TLOW GENMASK(5, 0)
|
||||
#define I2C_HS_INTERFACE_TIMING_1 0x0a0
|
||||
#define I2C_HS_INTERFACE_TIMING_TSU_STO GENMASK(21, 16)
|
||||
#define I2C_HS_INTERFACE_TIMING_THD_STA GENMASK(13, 8)
|
||||
#define I2C_HS_INTERFACE_TIMING_TSU_STA GENMASK(5, 0)
|
||||
|
||||
#define I2C_MST_FIFO_CONTROL 0x0b4
|
||||
#define I2C_MST_FIFO_CONTROL_RX_FLUSH BIT(0)
|
||||
@ -230,6 +243,7 @@ struct tegra_i2c_hw_feature {
|
||||
* @cont_id: I2C controller ID, used for packet header
|
||||
* @irq: IRQ number of transfer complete interrupt
|
||||
* @is_dvc: identifies the DVC I2C controller, has a different register layout
|
||||
* @is_vi: identifies the VI I2C controller, has a different register layout
|
||||
* @msg_complete: transfer completion notifier
|
||||
* @msg_err: error code for completed message
|
||||
* @msg_buf: pointer to current message data
|
||||
@ -253,12 +267,14 @@ struct tegra_i2c_dev {
|
||||
struct i2c_adapter adapter;
|
||||
struct clk *div_clk;
|
||||
struct clk *fast_clk;
|
||||
struct clk *slow_clk;
|
||||
struct reset_control *rst;
|
||||
void __iomem *base;
|
||||
phys_addr_t base_phys;
|
||||
int cont_id;
|
||||
int irq;
|
||||
int is_dvc;
|
||||
bool is_vi;
|
||||
struct completion msg_complete;
|
||||
int msg_err;
|
||||
u8 *msg_buf;
|
||||
@ -297,6 +313,8 @@ static unsigned long tegra_i2c_reg_addr(struct tegra_i2c_dev *i2c_dev,
|
||||
{
|
||||
if (i2c_dev->is_dvc)
|
||||
reg += (reg >= I2C_TX_FIFO) ? 0x10 : 0x40;
|
||||
else if (i2c_dev->is_vi)
|
||||
reg = 0xc00 + (reg << 2);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@ -646,6 +664,14 @@ static int __maybe_unused tegra_i2c_runtime_resume(struct device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
if (i2c_dev->slow_clk) {
|
||||
ret = clk_enable(i2c_dev->slow_clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to enable slow clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_enable(i2c_dev->div_clk);
|
||||
if (ret < 0) {
|
||||
dev_err(i2c_dev->dev,
|
||||
@ -662,6 +688,10 @@ static int __maybe_unused tegra_i2c_runtime_suspend(struct device *dev)
|
||||
struct tegra_i2c_dev *i2c_dev = dev_get_drvdata(dev);
|
||||
|
||||
clk_disable(i2c_dev->div_clk);
|
||||
|
||||
if (i2c_dev->slow_clk)
|
||||
clk_disable(i2c_dev->slow_clk);
|
||||
|
||||
if (!i2c_dev->hw->has_single_clk_source)
|
||||
clk_disable(i2c_dev->fast_clk);
|
||||
|
||||
@ -699,6 +729,35 @@ static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_i2c_vi_init(struct tegra_i2c_dev *i2c_dev)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = FIELD_PREP(I2C_INTERFACE_TIMING_THIGH, 2) |
|
||||
FIELD_PREP(I2C_INTERFACE_TIMING_TLOW, 4);
|
||||
i2c_writel(i2c_dev, value, I2C_INTERFACE_TIMING_0);
|
||||
|
||||
value = FIELD_PREP(I2C_INTERFACE_TIMING_TBUF, 4) |
|
||||
FIELD_PREP(I2C_INTERFACE_TIMING_TSU_STO, 7) |
|
||||
FIELD_PREP(I2C_INTERFACE_TIMING_THD_STA, 4) |
|
||||
FIELD_PREP(I2C_INTERFACE_TIMING_TSU_STA, 4);
|
||||
i2c_writel(i2c_dev, value, I2C_INTERFACE_TIMING_1);
|
||||
|
||||
value = FIELD_PREP(I2C_HS_INTERFACE_TIMING_THIGH, 3) |
|
||||
FIELD_PREP(I2C_HS_INTERFACE_TIMING_TLOW, 8);
|
||||
i2c_writel(i2c_dev, value, I2C_HS_INTERFACE_TIMING_0);
|
||||
|
||||
value = FIELD_PREP(I2C_HS_INTERFACE_TIMING_TSU_STO, 11) |
|
||||
FIELD_PREP(I2C_HS_INTERFACE_TIMING_THD_STA, 11) |
|
||||
FIELD_PREP(I2C_HS_INTERFACE_TIMING_TSU_STA, 11);
|
||||
i2c_writel(i2c_dev, value, I2C_HS_INTERFACE_TIMING_1);
|
||||
|
||||
value = FIELD_PREP(I2C_BC_SCLK_THRESHOLD, 9) | I2C_BC_STOP_COND;
|
||||
i2c_writel(i2c_dev, value, I2C_BUS_CLEAR_CNFG);
|
||||
|
||||
i2c_writel(i2c_dev, 0x0, I2C_TLOW_SEXT);
|
||||
}
|
||||
|
||||
static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev, bool clk_reinit)
|
||||
{
|
||||
u32 val;
|
||||
@ -723,6 +782,9 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev, bool clk_reinit)
|
||||
i2c_writel(i2c_dev, val, I2C_CNFG);
|
||||
i2c_writel(i2c_dev, 0, I2C_INT_MASK);
|
||||
|
||||
if (i2c_dev->is_vi)
|
||||
tegra_i2c_vi_init(i2c_dev);
|
||||
|
||||
/* Make sure clock divisor programmed correctly */
|
||||
clk_divisor = FIELD_PREP(I2C_CLK_DIVISOR_HSMODE,
|
||||
i2c_dev->hw->clk_divisor_hs_mode) |
|
||||
@ -766,7 +828,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev, bool clk_reinit)
|
||||
}
|
||||
}
|
||||
|
||||
if (!i2c_dev->is_dvc) {
|
||||
if (!i2c_dev->is_dvc && !i2c_dev->is_vi) {
|
||||
u32 sl_cfg = i2c_readl(i2c_dev, I2C_SL_CNFG);
|
||||
|
||||
sl_cfg |= I2C_SL_CNFG_NACK | I2C_SL_CNFG_NEWSL;
|
||||
@ -1555,6 +1617,7 @@ static const struct tegra_i2c_hw_feature tegra194_i2c_hw = {
|
||||
static const struct of_device_id tegra_i2c_of_match[] = {
|
||||
{ .compatible = "nvidia,tegra194-i2c", .data = &tegra194_i2c_hw, },
|
||||
{ .compatible = "nvidia,tegra186-i2c", .data = &tegra186_i2c_hw, },
|
||||
{ .compatible = "nvidia,tegra210-i2c-vi", .data = &tegra210_i2c_hw, },
|
||||
{ .compatible = "nvidia,tegra210-i2c", .data = &tegra210_i2c_hw, },
|
||||
{ .compatible = "nvidia,tegra124-i2c", .data = &tegra124_i2c_hw, },
|
||||
{ .compatible = "nvidia,tegra114-i2c", .data = &tegra114_i2c_hw, },
|
||||
@ -1567,6 +1630,7 @@ MODULE_DEVICE_TABLE(of, tegra_i2c_of_match);
|
||||
|
||||
static int tegra_i2c_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct tegra_i2c_dev *i2c_dev;
|
||||
struct resource *res;
|
||||
struct clk *div_clk;
|
||||
@ -1622,6 +1686,8 @@ static int tegra_i2c_probe(struct platform_device *pdev)
|
||||
i2c_dev->hw = of_device_get_match_data(&pdev->dev);
|
||||
i2c_dev->is_dvc = of_device_is_compatible(pdev->dev.of_node,
|
||||
"nvidia,tegra20-i2c-dvc");
|
||||
i2c_dev->is_vi = of_device_is_compatible(dev->of_node,
|
||||
"nvidia,tegra210-i2c-vi");
|
||||
i2c_dev->adapter.quirks = i2c_dev->hw->quirks;
|
||||
i2c_dev->dma_buf_size = i2c_dev->adapter.quirks->max_write_len +
|
||||
I2C_PACKET_HEADER_SIZE;
|
||||
@ -1637,6 +1703,17 @@ static int tegra_i2c_probe(struct platform_device *pdev)
|
||||
i2c_dev->fast_clk = fast_clk;
|
||||
}
|
||||
|
||||
if (i2c_dev->is_vi) {
|
||||
i2c_dev->slow_clk = devm_clk_get(dev, "slow");
|
||||
if (IS_ERR(i2c_dev->slow_clk)) {
|
||||
if (PTR_ERR(i2c_dev->slow_clk) != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get slow clock: %ld\n",
|
||||
PTR_ERR(i2c_dev->slow_clk));
|
||||
|
||||
return PTR_ERR(i2c_dev->slow_clk);
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, i2c_dev);
|
||||
|
||||
if (!i2c_dev->hw->has_single_clk_source) {
|
||||
@ -1647,6 +1724,14 @@ static int tegra_i2c_probe(struct platform_device *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
if (i2c_dev->slow_clk) {
|
||||
ret = clk_prepare(i2c_dev->slow_clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to prepare slow clock: %d\n", ret);
|
||||
goto unprepare_fast_clk;
|
||||
}
|
||||
}
|
||||
|
||||
if (i2c_dev->bus_clk_rate > I2C_MAX_FAST_MODE_FREQ &&
|
||||
i2c_dev->bus_clk_rate <= I2C_MAX_FAST_MODE_PLUS_FREQ)
|
||||
i2c_dev->clk_divisor_non_hs_mode =
|
||||
@ -1662,7 +1747,7 @@ static int tegra_i2c_probe(struct platform_device *pdev)
|
||||
ret = clk_prepare(i2c_dev->div_clk);
|
||||
if (ret < 0) {
|
||||
dev_err(i2c_dev->dev, "Clock prepare failed %d\n", ret);
|
||||
goto unprepare_fast_clk;
|
||||
goto unprepare_slow_clk;
|
||||
}
|
||||
|
||||
pm_runtime_irq_safe(&pdev->dev);
|
||||
@ -1749,6 +1834,10 @@ disable_rpm:
|
||||
unprepare_div_clk:
|
||||
clk_unprepare(i2c_dev->div_clk);
|
||||
|
||||
unprepare_slow_clk:
|
||||
if (i2c_dev->is_vi)
|
||||
clk_unprepare(i2c_dev->slow_clk);
|
||||
|
||||
unprepare_fast_clk:
|
||||
if (!i2c_dev->hw->has_single_clk_source)
|
||||
clk_unprepare(i2c_dev->fast_clk);
|
||||
@ -1770,6 +1859,10 @@ static int tegra_i2c_remove(struct platform_device *pdev)
|
||||
tegra_i2c_runtime_suspend(&pdev->dev);
|
||||
|
||||
clk_unprepare(i2c_dev->div_clk);
|
||||
|
||||
if (i2c_dev->slow_clk)
|
||||
clk_unprepare(i2c_dev->slow_clk);
|
||||
|
||||
if (!i2c_dev->hw->has_single_clk_source)
|
||||
clk_unprepare(i2c_dev->fast_clk);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user