From d7cfbd7ad1033ad564a5ae8a5aba31da2e4c2ffb Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Mon, 2 May 2022 07:41:14 +0000 Subject: [PATCH] i2c: busses: Add I2C tegra slave driver in byte mode Add I2C slave driver for NVIDIA Tegra which works in byte mode of data transfer i.e. rx/tx interrupts per byte. Bug 3631219 Change-Id: Ic40b868a3d195495550148a89d41f9d5d799d7ac Signed-off-by: Laxman Dewangan Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2705958 GVS: Gerrit_Virtual_Submit --- drivers/Makefile | 1 + drivers/i2c/busses/Makefile | 4 + drivers/i2c/busses/i2c-tegra-slave-byte.c | 485 ++++++++++++++++++++++ 3 files changed, 490 insertions(+) create mode 100644 drivers/i2c/busses/Makefile create mode 100644 drivers/i2c/busses/i2c-tegra-slave-byte.c diff --git a/drivers/Makefile b/drivers/Makefile index e070d114..7d612e64 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -4,6 +4,7 @@ LINUXINCLUDE += -I$(srctree.nvidia-oot)/include obj-m += hwmon/ +obj-m += i2c/busses/ obj-m += mfd/ obj-m += pinctrl/ obj-m += platform/tegra/ diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile new file mode 100644 index 00000000..eea8b5eb --- /dev/null +++ b/drivers/i2c/busses/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +obj-m += i2c-tegra-slave-byte.o diff --git a/drivers/i2c/busses/i2c-tegra-slave-byte.c b/drivers/i2c/busses/i2c-tegra-slave-byte.c new file mode 100644 index 00000000..00e6f6c1 --- /dev/null +++ b/drivers/i2c/busses/i2c-tegra-slave-byte.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define I2C_SL_CNFG 0x20 +#define I2C_SL_CNFG_ENABLE_SL BIT(3) +#define I2C_SL_CNFG_NEW_SL BIT(2) + +#define I2C_SL_RCVD 0x24 + +#define I2C_SL_STATUS 0x28 +#define I2C_SL_STATUS_END_TRANS BIT(4) +#define I2C_SL_STATUS_SL_IRQ BIT(3) +#define I2C_SL_STATUS_RCVD BIT(2) +#define I2C_SL_STATUS_RNW BIT(1) + +#define I2C_SL_ADDR1 0x2c +#define I2C_SL_ADDR2 0x30 +#define I2C_SL_ADDR2_MASK 0x1FFFF +#define I2C_7BIT_ADDR_MASK 0x7F + +#define I2C_TLOW_SEXT 0x34 +#define I2C_SL_DELAY_COUNT 0x3c +#define I2C_SL_DELAY_COUNT_RESET 0x1e + +#define I2C_SL_INT_MASK 0x40 +#define I2C_SL_INT_SOURCE 0x44 + +#define I2C_SL_INT_SET 0x48 +#define I2C_FIFO_CONTROL 0x5c + +#define I2C_INTERRUPT_MASK_REGISTER 0x64 +#define I2C_INTERRUPT_STATUS_REGISTER 0x68 +#define I2C_INTERRUPT_SOURCE_REGISTER 0x70 +#define I2C_INTERRUPT_SLV_WR2RD BIT(26) +#define I2C_INTERRUPT_SET_REGISTER 0x74 + +#define I2C_CONFIG_LOAD 0x8c +#define I2C_TIMEOUT_CONFIG_LOAD BIT(2) +#define I2C_CONFIG_LOAD_SLV BIT(1) +#define I2C_CONFIG_LOAD_TIMEOUT 1000000 + +#define I2C_CLKEN_OVERRIDE 0x90 +#define I2C_DEBUG_CONTROL 0xa4 + +struct tegra_i2cslv_dev { + struct device *dev; + struct i2c_adapter adap; + struct clk *div_clk; + struct reset_control *rstc; + void __iomem *base; + struct i2c_client *slave; + raw_spinlock_t xfer_lock; +}; + +static inline u32 tegra_i2cslv_readl(struct tegra_i2cslv_dev *i2cslv_dev, + unsigned long reg) +{ + return readl(i2cslv_dev->base + reg); +} + +static inline void tegra_i2cslv_writel(struct tegra_i2cslv_dev *i2cslv_dev, + unsigned long val, unsigned long reg) +{ + writel(val, i2cslv_dev->base + reg); +} + +static void tegra_i2cslv_dump_reg(struct tegra_i2cslv_dev *i2cslv_dev) +{ + dev_warn(i2cslv_dev->dev, "I2C_I2C_SL_INT_SOURCE_0 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_SL_INT_SOURCE)); + dev_warn(i2cslv_dev->dev, "I2C_INTERRUPT_STATUS_REGISTER_0 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_INTERRUPT_STATUS_REGISTER)); + dev_warn(i2cslv_dev->dev, "I2C_I2C_SL_STATUS_0 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_SL_STATUS)); + dev_warn(i2cslv_dev->dev, "I2C_INTERRUPT_SOURCE_REGISTER 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_INTERRUPT_SOURCE_REGISTER)); + dev_warn(i2cslv_dev->dev, "I2C_I2C_SL_CNFG_0 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_SL_CNFG)); + dev_warn(i2cslv_dev->dev, "I2C_SL_ADDR1 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_SL_ADDR1)); + dev_warn(i2cslv_dev->dev, "I2C_INTERRUPT_MASK_REGISTER_0 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_INTERRUPT_MASK_REGISTER)); + dev_warn(i2cslv_dev->dev, "I2C_I2C_SL_INT_MASK_0 0x%x\n", + tegra_i2cslv_readl(i2cslv_dev, I2C_SL_INT_MASK)); +} + +static int tegra_i2cslv_load_config(struct tegra_i2cslv_dev *i2cslv_dev) +{ + u32 i2c_load_config_reg; + u32 val; + int ret = 0; + + i2c_load_config_reg = tegra_i2cslv_readl(i2cslv_dev, I2C_CONFIG_LOAD); + i2c_load_config_reg |= I2C_CONFIG_LOAD_SLV; + tegra_i2cslv_writel(i2cslv_dev, i2c_load_config_reg, I2C_CONFIG_LOAD); + + if (in_interrupt()) + ret = readl_poll_timeout_atomic(i2cslv_dev->base + + I2C_CONFIG_LOAD, val, + !(val & I2C_CONFIG_LOAD_SLV), + 1000, I2C_CONFIG_LOAD_TIMEOUT); + else + ret = readl_poll_timeout(i2cslv_dev->base + I2C_CONFIG_LOAD, + val, !(val & I2C_CONFIG_LOAD_SLV), + 1000, I2C_CONFIG_LOAD_TIMEOUT); + if (ret) { + dev_err(i2cslv_dev->dev, "ERR unable to load i2cslv config\n"); + return ret; + } + + return 0; +} + +/* tegra_i2cslv_handle_rx - To get the data byte from bus and provide the + * data to client driver + */ +static void tegra_i2cslv_handle_rx(struct tegra_i2cslv_dev *i2cslv_dev, + const unsigned long i2c_int_src, + const unsigned long i2c_slv_src) +{ + u8 value; + + if (i2c_slv_src & I2C_SL_STATUS_END_TRANS) { + /* clear the interrupts to release the SCL line */ + tegra_i2cslv_writel(i2cslv_dev, I2C_SL_STATUS_END_TRANS | + I2C_SL_STATUS_SL_IRQ, I2C_SL_STATUS); + i2c_slave_event(i2cslv_dev->slave, I2C_SLAVE_STOP, &value); + return; + } + + value = (unsigned char)tegra_i2cslv_readl(i2cslv_dev, I2C_SL_RCVD); + /* Send the received data to client driver */ + i2c_slave_event(i2cslv_dev->slave, I2C_SLAVE_WRITE_RECEIVED, &value); + /* clear the interrupt to release the SCL line */ + tegra_i2cslv_writel(i2cslv_dev, I2C_SL_STATUS_SL_IRQ, I2C_SL_STATUS); +} + +/* tegra_i2cslv_handle_tx - To get the data byte fron client driver and + * send it to master over bus. + */ +static void tegra_i2cslv_handle_tx(struct tegra_i2cslv_dev *i2cslv_dev, + const unsigned long i2c_int_src, + const unsigned long i2c_slv_src) +{ + u8 value; + + if (i2c_slv_src & I2C_SL_STATUS_END_TRANS) { + /* clear the interrupt to release the SCL line */ + tegra_i2cslv_writel(i2cslv_dev, I2C_SL_STATUS_END_TRANS | + I2C_SL_STATUS_SL_IRQ, I2C_SL_STATUS); + i2c_slave_event(i2cslv_dev->slave, I2C_SLAVE_STOP, &value); + return; + } + /* clear the interrupt to release the SCL line */ + tegra_i2cslv_writel(i2cslv_dev, I2C_SL_STATUS_SL_IRQ, I2C_SL_STATUS); + /* Get the data byte from client driver*/ + i2c_slave_event(i2cslv_dev->slave, I2C_SLAVE_READ_REQUESTED, &value); + tegra_i2cslv_writel(i2cslv_dev, value, I2C_SL_RCVD); +} + +static int tegra_i2cslv_init(struct tegra_i2cslv_dev *i2cslv_dev) +{ + u32 reg; + + /* Reset the controller */ + reset_control_assert(i2cslv_dev->rstc); + udelay(2); + reset_control_deassert(i2cslv_dev->rstc); + + /* Program the 7-bit slave address */ + tegra_i2cslv_writel(i2cslv_dev, i2cslv_dev->slave->addr & + I2C_7BIT_ADDR_MASK, I2C_SL_ADDR1); + + /* Specify its 7-bit address mode */ + reg = tegra_i2cslv_readl(i2cslv_dev, I2C_SL_ADDR2); + reg &= ~(I2C_SL_ADDR2_MASK); + tegra_i2cslv_writel(i2cslv_dev, reg, I2C_SL_ADDR2); + + /* Unmask WR2RD interrupt, just to clear it */ + tegra_i2cslv_writel(i2cslv_dev, (I2C_INTERRUPT_SLV_WR2RD), + I2C_INTERRUPT_MASK_REGISTER); + + tegra_i2cslv_writel(i2cslv_dev, (I2C_SL_STATUS_END_TRANS | + I2C_SL_STATUS_SL_IRQ | I2C_SL_STATUS_RCVD), + I2C_SL_INT_MASK); + + reg = tegra_i2cslv_readl(i2cslv_dev, I2C_SL_CNFG); + reg |= (I2C_SL_CNFG_NEW_SL | I2C_SL_CNFG_ENABLE_SL); + tegra_i2cslv_writel(i2cslv_dev, reg, I2C_SL_CNFG); + + return tegra_i2cslv_load_config(i2cslv_dev); +} + +static void tegra_i2cslv_deinit(struct tegra_i2cslv_dev *i2cslv_dev) +{ + tegra_i2cslv_writel(i2cslv_dev, 0, I2C_INTERRUPT_MASK_REGISTER); + tegra_i2cslv_writel(i2cslv_dev, 0, I2C_SL_INT_MASK); + clk_disable(i2cslv_dev->div_clk); +} + +static irqreturn_t tegra_i2cslv_isr(int irq, void *dev_id) +{ + struct tegra_i2cslv_dev *i2cslv_dev = dev_id; + u32 i2c_int_src; + u32 i2c_slv_int_src; + u32 i2c_slv_sts; + u8 value; + unsigned long flags; + + raw_spin_lock_irqsave(&i2cslv_dev->xfer_lock, flags); + i2c_int_src = tegra_i2cslv_readl(i2cslv_dev, + I2C_INTERRUPT_SOURCE_REGISTER); + i2c_slv_int_src = tegra_i2cslv_readl(i2cslv_dev, I2C_SL_INT_SOURCE); + i2c_slv_sts = tegra_i2cslv_readl(i2cslv_dev, I2C_SL_STATUS); + + /* Address received */ + if ((i2c_slv_int_src & I2C_SL_STATUS_SL_IRQ) && + (i2c_slv_int_src & I2C_SL_STATUS_RCVD)) { + /* End of transfer of previous transaction, just clear it */ + if (i2c_slv_int_src & I2C_SL_STATUS_END_TRANS) { + i2c_slave_event(i2cslv_dev->slave, I2C_SLAVE_STOP, + &value); + tegra_i2cslv_writel(i2cslv_dev, I2C_SL_STATUS_END_TRANS, + I2C_SL_STATUS); + } + /* Clear the interrupt */ + tegra_i2cslv_writel(i2cslv_dev, I2C_SL_STATUS_SL_IRQ | + I2C_SL_STATUS_RCVD, I2C_SL_STATUS); + /* if RNW, master issued read. */ + if (i2c_slv_sts & I2C_SL_STATUS_RNW) { + i2c_slave_event(i2cslv_dev->slave, + I2C_SLAVE_READ_REQUESTED, &value); + tegra_i2cslv_writel(i2cslv_dev, value, + I2C_SL_RCVD); + } else { + i2c_slave_event(i2cslv_dev->slave, + I2C_SLAVE_WRITE_REQUESTED, &value); + } + goto done; + } + if (unlikely(i2c_int_src & I2C_INTERRUPT_SLV_WR2RD)) { + /* Clear WR2RD interrupt */ + tegra_i2cslv_writel(i2cslv_dev, I2C_INTERRUPT_SLV_WR2RD, + I2C_INTERRUPT_STATUS_REGISTER); + goto done; + } + + if ((i2c_slv_int_src & I2C_SL_STATUS_SL_IRQ)) { + if (!(i2c_slv_sts & I2C_SL_STATUS_RNW)) { + /* Master write and Slave receive */ + tegra_i2cslv_handle_rx(i2cslv_dev, i2c_int_src, + i2c_slv_int_src); + goto done; + } else if (i2c_slv_sts & I2C_SL_STATUS_RNW) { + /* Master read and slave write */ + tegra_i2cslv_handle_tx(i2cslv_dev, i2c_int_src, + i2c_slv_int_src); + goto done; + } + } else { + dev_err(i2cslv_dev->dev, "Slave IRQ not set\n"); + goto err; + } +err: + tegra_i2cslv_dump_reg(i2cslv_dev); + tegra_i2cslv_init(i2cslv_dev); +done: + raw_spin_unlock_irqrestore(&i2cslv_dev->xfer_lock, flags); + return IRQ_HANDLED; +} + +static int tegra_reg_slave(struct i2c_client *slave) +{ + struct tegra_i2cslv_dev *i2cslv_dev = + i2c_get_adapdata(slave->adapter); + int ret; + + if (i2cslv_dev->slave) + return -EBUSY; + + if (slave->flags & I2C_CLIENT_TEN) + return -EAFNOSUPPORT; + i2cslv_dev->slave = slave; + + ret = clk_enable(i2cslv_dev->div_clk); + if (ret < 0) { + dev_err(i2cslv_dev->dev, "Enable div-clk failed: %d\n", ret); + return ret; + } + + return tegra_i2cslv_init(i2cslv_dev); +} + +static int tegra_unreg_slave(struct i2c_client *slave) +{ + struct tegra_i2cslv_dev *i2cslv_dev = + i2c_get_adapdata(slave->adapter); + + WARN_ON(!i2cslv_dev->slave); + tegra_i2cslv_deinit(i2cslv_dev); + + return 0; +} + +static u32 tegra_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SLAVE; +} + +static const struct i2c_algorithm tegra_i2cslv_algo = { + .functionality = tegra_i2c_func, + .reg_slave = tegra_reg_slave, + .unreg_slave = tegra_unreg_slave, +}; + +static int tegra_i2cslv_probe(struct platform_device *pdev) +{ + struct tegra_i2cslv_dev *i2cslv_dev; + struct i2c_adapter *adap; + struct resource *res; + phys_addr_t phys_addr; + void __iomem *base; + struct clk *div_clk; + struct reset_control *rstc; + int irq, ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No IO memory resource\n"); + return -ENODEV; + } + phys_addr = res->start; + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource\n"); + return -EINVAL; + } + irq = res->start; + + div_clk = devm_clk_get(&pdev->dev, "div-clk"); + if (IS_ERR(div_clk)) { + dev_err(&pdev->dev, "missing controller clock"); + return PTR_ERR(div_clk); + } + + rstc = devm_reset_control_get(&pdev->dev, "i2c"); + if (IS_ERR(rstc)) { + ret = PTR_ERR(rstc); + dev_err(&pdev->dev, "Reset control is not found: %d\n", ret); + return ret; + } + + i2cslv_dev = devm_kzalloc(&pdev->dev, sizeof(*i2cslv_dev), GFP_KERNEL); + if (!i2cslv_dev) + return -ENOMEM; + + i2cslv_dev->dev = &pdev->dev; + i2cslv_dev->base = base; + i2cslv_dev->div_clk = div_clk; + i2cslv_dev->rstc = rstc; + raw_spin_lock_init(&i2cslv_dev->xfer_lock); + + adap = &i2cslv_dev->adap; + adap->algo = &tegra_i2cslv_algo; + adap->class = I2C_CLASS_DEPRECATED; + adap->dev.parent = &pdev->dev; + adap->dev.of_node = pdev->dev.of_node; + i2c_set_adapdata(adap, i2cslv_dev); + platform_set_drvdata(pdev, i2cslv_dev); + strlcpy(adap->name, pdev->name, sizeof(adap->name)); + + ret = clk_prepare(i2cslv_dev->div_clk); + if (ret < 0) { + dev_err(i2cslv_dev->dev, "clock prepare failed %d\n", ret); + return ret; + } + + ret = devm_request_irq(&pdev->dev, irq, tegra_i2cslv_isr, + 0, dev_name(&pdev->dev), i2cslv_dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register ISR for IRQ %d\n", irq); + return ret; + } + + ret = i2c_add_adapter(&i2cslv_dev->adap); + if (ret) { + dev_err(&pdev->dev, "Failed to add I2C adapter\n"); + return ret; + } + + return 0; +} + +static int tegra_i2cslv_remove(struct platform_device *pdev) +{ + struct tegra_i2cslv_dev *i2cslv_dev = platform_get_drvdata(pdev); + + i2c_del_adapter(&i2cslv_dev->adap); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_i2cslv_suspend(struct device *dev) +{ + struct tegra_i2cslv_dev *i2cslv_dev = dev_get_drvdata(dev); + unsigned long flags; + + raw_spin_lock_irqsave(&i2cslv_dev->xfer_lock, flags); + + tegra_i2cslv_deinit(i2cslv_dev); + + raw_spin_unlock_irqrestore(&i2cslv_dev->xfer_lock, flags); + + return 0; +} + +static int tegra_i2cslv_resume(struct device *dev) +{ + struct tegra_i2cslv_dev *i2cslv_dev = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + raw_spin_lock_irqsave(&i2cslv_dev->xfer_lock, flags); + + ret = clk_enable(i2cslv_dev->div_clk); + if (ret < 0) { + dev_err(i2cslv_dev->dev, "Enable div-clk failed: %d\n", ret); + return ret; + } + ret = tegra_i2cslv_init(i2cslv_dev); + if (ret) + return ret; + + raw_spin_unlock_irqrestore(&i2cslv_dev->xfer_lock, flags); + + return 0; +} +#endif + +static const struct dev_pm_ops tegra_i2cslv_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tegra_i2cslv_suspend, tegra_i2cslv_resume) +}; + +static const struct of_device_id tegra_i2cslv_of_match[] = { + {.compatible = "nvidia,tegra210-i2c-slave",}, + {.compatible = "nvidia,tegra-i2c-slave-byte",}, + {} +}; + +MODULE_DEVICE_TABLE(of, tegra_i2cslv_of_match); + +static struct platform_driver tegra_i2cslv_driver = { + .probe = tegra_i2cslv_probe, + .remove = tegra_i2cslv_remove, + .driver = { + .name = "tegra-i2cslv", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_i2cslv_of_match), + .pm = &tegra_i2cslv_pm_ops, + }, +}; + +module_platform_driver(tegra_i2cslv_driver); + +MODULE_AUTHOR("Shardar Shariff Md "); +MODULE_DESCRIPTION("NVIDIA Tegra I2C slave driver"); +MODULE_LICENSE("GPL v2");