linux-i2c.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] i2c: add i2c bus driver for amd navi gpu
@ 2021-03-09 13:31 Sanket Goswami
  2021-03-09 14:23 ` kernel test robot
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Sanket Goswami @ 2021-03-09 13:31 UTC (permalink / raw)
  To: jarkko.nikula, andriy.shevchenko, mika.westerberg
  Cc: linux-i2c, linux-kernel, Sanket Goswami, Shyam Sundar S K,
	Nehal Bakulchandra Shah

Latest AMDGPU NAVI cards have USB Type-C interface which can be accessed
over I2C. The Type-C controller has integrated designware i2c which is
exposed as a PCI device to the AMD platform.

Also there exists couple of notable IP problems that are dealt as a
workaround:
- I2C transactions work on a polling mode as IP does not generate
interrupt.

- I2C reads commands are sent twice to address the IP issues.

Reviewed-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
Co-developed-by: Nehal Bakulchandra Shah <Nehal-Bakulchandra.shah@amd.com>
Signed-off-by: Nehal Bakulchandra Shah <Nehal-Bakulchandra.shah@amd.com>
Signed-off-by: Sanket Goswami <Sanket.Goswami@amd.com>
---
 drivers/i2c/busses/i2c-designware-common.c |   3 +
 drivers/i2c/busses/i2c-designware-core.h   |   3 +
 drivers/i2c/busses/i2c-designware-master.c | 175 +++++++++++++++++++++
 drivers/i2c/busses/i2c-designware-pcidrv.c |  56 +++++++
 4 files changed, 237 insertions(+)

diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index 3c19aada4b30..50883a70b482 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -150,6 +150,9 @@ int i2c_dw_init_regmap(struct dw_i2c_dev *dev)
 	reg = readl(dev->base + DW_IC_COMP_TYPE);
 	i2c_dw_release_lock(dev);
 
+	if (dev->flags & AMD_NON_INTR_MODE)
+		map_cfg.max_register = AMD_UCSI_INTR_REG;
+
 	if (reg == swab32(DW_IC_COMP_TYPE_VALUE)) {
 		map_cfg.reg_read = dw_reg_read_swab;
 		map_cfg.reg_write = dw_reg_write_swab;
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 85307cfa7109..0cce4144b9ac 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -291,9 +291,12 @@ struct dw_i2c_dev {
 #define ACCESS_INTR_MASK	BIT(0)
 #define ACCESS_NO_IRQ_SUSPEND	BIT(1)
 
+#define AMD_NON_INTR_MODE	BIT(2)
 #define MODEL_MSCC_OCELOT	BIT(8)
 #define MODEL_BAIKAL_BT1	BIT(9)
 #define MODEL_MASK		GENMASK(11, 8)
+#define AMD_UCSI_INTR_EN	0xD
+#define AMD_UCSI_INTR_REG	0x474
 
 int i2c_dw_init_regmap(struct dw_i2c_dev *dev);
 u32 i2c_dw_scl_hcnt(u32 ic_clk, u32 tSYMBOL, u32 tf, int cond, int offset);
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index d6425ad6e6a3..35c894a01381 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -22,6 +22,10 @@
 #include <linux/reset.h>
 
 #include "i2c-designware-core.h"
+#define AMD_TIMEOUT_MSEC_MIN	10000
+#define AMD_TIMEOUT_MSEC_MAX	11000
+#define AMD_FIFO_SIZE		0x20
+#define AMD_MASTERCFG_MASK	GENMASK(15, 0)
 
 static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev)
 {
@@ -33,6 +37,154 @@ static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev)
 	regmap_write(dev->map, DW_IC_CON, dev->master_cfg);
 }
 
+static void i2c_dw_configure_bus(struct dw_i2c_dev *i2cd)
+{
+	int master_cfg;
+	u16 icon;
+	u32 reg;
+
+	/* First disable the controller */
+	__i2c_dw_disable_nowait(i2cd);
+	master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_SPEED_STD;
+
+	/* Clear all the interrupts */
+	regmap_read(i2cd->map, DW_IC_CLR_INTR, &reg);
+	regmap_write(i2cd->map, DW_IC_INTR_MASK, STATUS_IDLE);
+
+	icon = master_cfg & AMD_MASTERCFG_MASK;
+	icon &= ~DW_IC_CON_10BITADDR_MASTER;
+	icon = icon | DW_IC_CON_SPEED_STD;
+
+	/* Configure the master */
+	regmap_write(i2cd->map, DW_IC_CON, icon);
+	/* Configure the FIFO */
+	regmap_write(i2cd->map, DW_IC_TX_TL, AMD_FIFO_SIZE);
+	regmap_write(i2cd->map, DW_IC_RX_TL, AMD_FIFO_SIZE);
+
+	/* Setup 100k Speed */
+	regmap_write(i2cd->map, DW_IC_SS_SCL_HCNT, i2cd->ss_hcnt);
+	regmap_write(i2cd->map, DW_IC_SS_SCL_LCNT, i2cd->ss_lcnt);
+	regmap_write(i2cd->map, DW_IC_TAR, 0x08);
+
+	/* Now enable the controller */
+	__i2c_dw_enable(i2cd);
+}
+
+static int i2c_dw_check_stopbit(struct dw_i2c_dev *i2cd)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_read_poll_timeout(i2cd->map, DW_IC_INTR_STAT, val,
+				       !(val & DW_IC_INTR_STOP_DET),
+					1100, 20000);
+	if (ret) {
+		dev_err(i2cd->dev, "i2c timeout error %d\n", ret);
+		return -ETIMEDOUT;
+	}
+
+	regmap_read(i2cd->map, DW_IC_CLR_INTR, &val);
+	if (val & DW_IC_INTR_STOP_DET)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int i2c_dw_status(struct dw_i2c_dev *i2cd)
+{
+	int status;
+
+	status = i2c_dw_wait_bus_not_busy(i2cd);
+	if (status)
+		return -ETIMEDOUT;
+
+	status = i2c_dw_check_stopbit(i2cd);
+	if (status)
+		return -ETIMEDOUT;
+
+	return status;
+}
+
+/* Initiate and continue master read/write transaction with polling
+ * based transfer routine and write messages into the tx buffer.
+ */
+static int amd_i2c_dw_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num_msgs)
+{
+	struct dw_i2c_dev *i2cd = i2c_get_adapdata(adap);
+	int msg_wrt_idx, msg_itr_lmt, buf_len, data_idx;
+	int cmd = 0, ret, status;
+	u8 *tx_buf;
+	u32 val;
+
+	ret = i2c_dw_acquire_lock(i2cd);
+	if (ret) {
+		dev_err(i2cd->dev, "Failed to get bus ownership\n");
+		return ret;
+	}
+
+	i2c_dw_configure_bus(i2cd);
+
+	/* Initiate messages read/write transaction */
+	for (msg_wrt_idx = 0; msg_wrt_idx < num_msgs; msg_wrt_idx++) {
+		tx_buf = msgs[msg_wrt_idx].buf;
+		buf_len = msgs[msg_wrt_idx].len;
+
+		if (!(msgs[msg_wrt_idx].flags & I2C_M_RD))
+			regmap_write(i2cd->map, DW_IC_TX_TL, buf_len - 1);
+
+		/* Initiate the i2c read/write transition of buffer length,
+		 * and poll for bus busy status. For the last message transfer,
+		 * update the command with stopbit enable.
+		 */
+		for (msg_itr_lmt = buf_len; msg_itr_lmt > 0; msg_itr_lmt--) {
+			if (msg_wrt_idx == num_msgs - 1 && msg_itr_lmt == 1)
+				cmd |= BIT(9);
+
+			if (msgs[msg_wrt_idx].flags & I2C_M_RD) {
+				/* Due to hardware bug, need to write command two times. */
+				regmap_write(i2cd->map, DW_IC_DATA_CMD, 0x100);
+				regmap_write(i2cd->map, DW_IC_DATA_CMD, 0x100 | cmd);
+				if (cmd) {
+					regmap_write(i2cd->map, DW_IC_TX_TL, 2 * (buf_len - 1));
+					regmap_write(i2cd->map, DW_IC_RX_TL, 2 * (buf_len - 1));
+
+					/* Need to check the stop bit. However, it cannot be
+					 * detected from the registers so we check it always
+					 * when read/write the last byte.
+					 */
+					status = i2c_dw_status(i2cd);
+					if (status) {
+						ret = -ETIMEDOUT;
+						goto lock_release;
+					}
+					for (data_idx = 0; data_idx < buf_len; data_idx++) {
+						regmap_read(i2cd->map, DW_IC_DATA_CMD, &val);
+						tx_buf[data_idx] = val;
+					}
+					status = i2c_dw_check_stopbit(i2cd);
+					if (status) {
+						ret = -ETIMEDOUT;
+						goto lock_release;
+					}
+				}
+			} else {
+				regmap_write(i2cd->map, DW_IC_DATA_CMD, *tx_buf++ | cmd);
+				usleep_range(AMD_TIMEOUT_MSEC_MIN, AMD_TIMEOUT_MSEC_MAX);
+			}
+		}
+		status = i2c_dw_check_stopbit(i2cd);
+		if (status) {
+			ret = -ETIMEDOUT;
+			goto lock_release;
+		}
+	}
+
+lock_release:
+	i2c_dw_release_lock(i2cd);
+
+	return ret;
+}
+
 static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 {
 	const char *mode_str, *fp_str = "";
@@ -208,6 +360,9 @@ static int i2c_dw_init_master(struct dw_i2c_dev *dev)
 	if (dev->sda_hold_time)
 		regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
 
+	/* Enable ucsi interrupt */
+	if (dev->flags & AMD_NON_INTR_MODE)
+		regmap_write(dev->map, AMD_UCSI_INTR_REG, AMD_UCSI_INTR_EN);
 	i2c_dw_configure_fifo_master(dev);
 	i2c_dw_release_lock(dev);
 
@@ -462,6 +617,9 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 
 	pm_runtime_get_sync(dev->dev);
 
+	if (dev->flags & AMD_NON_INTR_MODE)
+		return amd_i2c_dw_master_xfer(adap, msgs, num);
+
 	if (dev_WARN_ONCE(dev->dev, dev->suspended, "Transfer while suspended\n")) {
 		ret = -ESHUTDOWN;
 		goto done_nolock;
@@ -738,6 +896,19 @@ static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev)
 	return 0;
 }
 
+int amd_i2c_adap_quirk(struct dw_i2c_dev *amdev)
+{
+	struct i2c_adapter *amdap = &amdev->adapter;
+	int ret;
+
+	if (!(amdev->flags & AMD_NON_INTR_MODE))
+		return -ENODEV;
+
+	return i2c_add_numbered_adapter(amdap);
+
+	return ret;
+}
+
 int i2c_dw_probe_master(struct dw_i2c_dev *dev)
 {
 	struct i2c_adapter *adap = &dev->adapter;
@@ -774,6 +945,10 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
 	adap->dev.parent = dev->dev;
 	i2c_set_adapdata(adap, dev);
 
+	ret = amd_i2c_adap_quirk(dev);
+	if (ret != -ENODEV)
+		return ret;
+
 	if (dev->flags & ACCESS_NO_IRQ_SUSPEND) {
 		irq_flags = IRQF_NO_SUSPEND;
 	} else {
diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c
index 55c83a7a24f3..69ea6f206ab2 100644
--- a/drivers/i2c/busses/i2c-designware-pcidrv.c
+++ b/drivers/i2c/busses/i2c-designware-pcidrv.c
@@ -26,6 +26,7 @@
 #include "i2c-designware-core.h"
 
 #define DRIVER_NAME "i2c-designware-pci"
+#define AMD_CLK_RATE	100000
 
 enum dw_pci_ctl_id_t {
 	medfield,
@@ -34,6 +35,7 @@ enum dw_pci_ctl_id_t {
 	cherrytrail,
 	haswell,
 	elkhartlake,
+	navi_amd,
 };
 
 struct dw_scl_sda_cfg {
@@ -78,11 +80,23 @@ static struct dw_scl_sda_cfg hsw_config = {
 	.sda_hold = 0x9,
 };
 
+/* NAVI-AMD HCNT/LCNT/SDA hold time */
+static struct dw_scl_sda_cfg navi_amd_config = {
+	.ss_hcnt = 0x1ae,
+	.ss_lcnt = 0x23a,
+	.sda_hold = 0x9,
+};
+
 static u32 mfld_get_clk_rate_khz(struct dw_i2c_dev *dev)
 {
 	return 25000;
 }
 
+static u32 navi_amd_get_clk_rate_khz(struct dw_i2c_dev *dev)
+{
+	return AMD_CLK_RATE;
+}
+
 static int mfld_setup(struct pci_dev *pdev, struct dw_pci_controller *c)
 {
 	struct dw_i2c_dev *dev = dev_get_drvdata(&pdev->dev);
@@ -104,6 +118,35 @@ static int mfld_setup(struct pci_dev *pdev, struct dw_pci_controller *c)
 	return -ENODEV;
 }
 
+static int i2c_dw_populate_client(struct dw_i2c_dev *i2cd)
+{
+	struct i2c_board_info	*i2c_dw_ccgx_ucsi;
+	struct i2c_client	*ccgx_client;
+
+	i2c_dw_ccgx_ucsi = devm_kzalloc(i2cd->dev, sizeof(*i2c_dw_ccgx_ucsi), GFP_KERNEL);
+	if (!i2c_dw_ccgx_ucsi)
+		return -ENOMEM;
+
+	strscpy(i2c_dw_ccgx_ucsi->type, "ccgx-ucsi", sizeof(i2c_dw_ccgx_ucsi->type));
+	i2c_dw_ccgx_ucsi->addr = 0x08;
+	i2c_dw_ccgx_ucsi->irq = i2cd->irq;
+
+	ccgx_client = i2c_new_client_device(&i2cd->adapter, i2c_dw_ccgx_ucsi);
+	if (!ccgx_client)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int navi_amd_setup(struct pci_dev *pdev, struct dw_pci_controller *c)
+{
+	struct dw_i2c_dev *dev = dev_get_drvdata(&pdev->dev);
+
+	dev->flags |= AMD_NON_INTR_MODE;
+	dev->timings.bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ;
+	return 0;
+}
+
 static int mrfld_setup(struct pci_dev *pdev, struct dw_pci_controller *c)
 {
 	/*
@@ -155,6 +198,12 @@ static struct dw_pci_controller dw_pci_controllers[] = {
 		.bus_num = -1,
 		.get_clk_rate_khz = ehl_get_clk_rate_khz,
 	},
+	[navi_amd] = {
+		.bus_num = -1,
+		.scl_sda_cfg = &navi_amd_config,
+		.setup =  navi_amd_setup,
+		.get_clk_rate_khz = navi_amd_get_clk_rate_khz,
+	},
 };
 
 #ifdef CONFIG_PM
@@ -274,6 +323,9 @@ static int i2c_dw_pci_probe(struct pci_dev *pdev,
 		return r;
 	}
 
+	if (dev->flags & AMD_NON_INTR_MODE)
+		i2c_dw_populate_client(dev);
+
 	pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
 	pm_runtime_use_autosuspend(&pdev->dev);
 	pm_runtime_put_autosuspend(&pdev->dev);
@@ -337,6 +389,10 @@ static const struct pci_device_id i2_designware_pci_ids[] = {
 	{ PCI_VDEVICE(INTEL, 0x4bbe), elkhartlake },
 	{ PCI_VDEVICE(INTEL, 0x4bbf), elkhartlake },
 	{ PCI_VDEVICE(INTEL, 0x4bc0), elkhartlake },
+	{ PCI_VDEVICE(ATI,  0x7314), navi_amd },
+	{ PCI_VDEVICE(ATI,  0x73a4), navi_amd },
+	{ PCI_VDEVICE(ATI,  0x73e4), navi_amd },
+	{ PCI_VDEVICE(ATI,  0x73c4), navi_amd },
 	{ 0,}
 };
 MODULE_DEVICE_TABLE(pci, i2_designware_pci_ids);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread
* [PATCH] i2c: add i2c bus driver for AMD NAVI GPU
@ 2020-11-27 19:30 Sanjay R Mehta
  2020-11-30 11:19 ` Andy Shevchenko
  0 siblings, 1 reply; 14+ messages in thread
From: Sanjay R Mehta @ 2020-11-27 19:30 UTC (permalink / raw)
  To: wsa+renesas, jarkko.nikula, andriy.shevchenko, jdelvare,
	Sergey.Semin, krzk, kblaiech, loic.poulain, rppt,
	bjorn.andersson, linux, vadimp, tali.perry1
  Cc: linux-i2c, Nehal Bakulchandra Shah, Sanjay R Mehta

From: Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@amd.com>

Latest AMD GPU card has USB Type-C interface. There is a
Type-C controller which can be accessed over I2C.

This driver adds I2C bus driver to communicate with Type-C controller.
I2C client driver will be part of USB Type-C UCSI driver.

Signed-off-by: Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@amd.com>
Co-developed-by: Sanjay R Mehta <Sanju.Mehta@amd.com>
Signed-off-by: Sanjay R Mehta <Sanju.Mehta@amd.com>
---
 MAINTAINERS                          |   7 +
 drivers/i2c/busses/Kconfig           |   9 +
 drivers/i2c/busses/Makefile          |   1 +
 drivers/i2c/busses/i2c-amdgpu-navi.c | 334 +++++++++++++++++++++++++++++++++++
 4 files changed, 351 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-amdgpu-navi.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a008b70..59923c7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8187,6 +8187,13 @@ S:	Maintained
 F:	Documentation/i2c/busses/i2c-nvidia-gpu.rst
 F:	drivers/i2c/busses/i2c-nvidia-gpu.c
 
+I2C CONTROLLER DRIVER FOR AMD NAVI GPU
+M:	Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@amd.com>
+M:	Sanjay R Mehta <sanju.mehta@amd.com>
+L:	linux-i2c@vger.kernel.org
+S:	Maintained
+F:	drivers/i2c/busses/i2c-amdgpu-navi.c
+
 I2C MUXES
 M:	Peter Rosin <peda@axentia.se>
 L:	linux-i2c@vger.kernel.org
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a97a9d0..7e268ef 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -322,6 +322,15 @@ config I2C_VIAPRO
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-viapro.
 
+config I2C_AMDGPU_NAVI
+	tristate "AMDGPU NAVI I2C controller"
+	depends on PCI
+	help
+	  If you say yes to this option, support will be included for the
+	  NAVI I2C controller which is used to communicate with the GPU's
+	  Type-C controller. This driver can also be built as a module called
+	  i2c-amd-gpu.
+
 if ACPI
 
 comment "ACPI drivers"
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 683c49f..5818f2d 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_I2C_SIS630)	+= i2c-sis630.o
 obj-$(CONFIG_I2C_SIS96X)	+= i2c-sis96x.o
 obj-$(CONFIG_I2C_VIA)		+= i2c-via.o
 obj-$(CONFIG_I2C_VIAPRO)	+= i2c-viapro.o
+obj-$(CONFIG_I2C_AMDGPU_NAVI)	+= i2c-amdgpu-navi.o
 
 # Mac SMBus host controller drivers
 obj-$(CONFIG_I2C_HYDRA)		+= i2c-hydra.o
diff --git a/drivers/i2c/busses/i2c-amdgpu-navi.c b/drivers/i2c/busses/i2c-amdgpu-navi.c
new file mode 100644
index 0000000..3067899
--- /dev/null
+++ b/drivers/i2c/busses/i2c-amdgpu-navi.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+//
+// AMD I2C Controller Driver for Navi GPU's
+//
+// Copyright (c) 2020, Advanced Micro Devices, Inc.
+//
+// Author: Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@amd.com>
+//	   Sanjay R Mehta <Sanju.Mehta@amd.com>
+
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+#include "i2c-designware-core.h"
+
+#define DRIVER_DESC "AMD I2C Controller Driver for Navi"
+#define AMD_UCSI_INTR_REG 0x474
+#define AMD_UCSI_INTR_EN 0xD
+#define AMD_MASTERCFG_MASK GENMASK_ULL(15, 0)
+
+struct amdgpu_i2c_dev {
+	void __iomem *regs;
+	struct device *dev;
+	u32 master_cfg;
+	u32 slave_adr;
+	u32			tx_fifo_depth;
+	u32			rx_fifo_depth;
+	u32			sda_hold_time;
+	u16			ss_hcnt;
+	u16			ss_lcnt;
+	u16			fs_hcnt;
+	u16			fs_lcnt;
+	u16			fp_hcnt;
+	u16			fp_lcnt;
+	u16			hs_hcnt;
+	u16			hs_lcnt;
+	struct i2c_adapter adapter;
+	struct i2c_board_info *gpu_ccgx_ucsi;
+	struct i2c_client *ccgx_client;
+};
+
+static void amdgpu_configure_i2c_bus(struct amdgpu_i2c_dev *i2cd)
+{
+	u16 icon;
+
+	/* First disable the controller */
+	writel(0, i2cd->regs + DW_IC_ENABLE);
+	i2cd->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_RESTART_EN
+			   | DW_IC_CON_SPEED_STD;
+
+	/* clear all the interrupt */
+	readl(i2cd->regs +  DW_IC_CLR_INTR);
+	writel(0, i2cd->regs + DW_IC_INTR_MASK);
+
+	icon = i2cd->master_cfg & AMD_MASTERCFG_MASK;
+	icon &= ~BIT(3);
+	icon &= ~DW_IC_CON_10BITADDR_MASTER;
+	icon = icon | DW_IC_CON_SPEED_STD;
+	/* configure the master */
+	writel(icon, i2cd->regs + DW_IC_CON);
+	/*configure the FIFO */
+	i2cd->tx_fifo_depth = 32;
+	i2cd->rx_fifo_depth = 32;
+	writel(i2cd->tx_fifo_depth, i2cd->regs +  DW_IC_TX_TL);
+	writel(i2cd->rx_fifo_depth, i2cd->regs +  DW_IC_RX_TL);
+
+	/* setup 100k Speed */
+	i2cd->ss_hcnt = 430;
+	i2cd->ss_lcnt = 570;
+	writel(i2cd->ss_hcnt, i2cd->regs + DW_IC_SS_SCL_HCNT);
+	writel(i2cd->ss_lcnt, i2cd->regs + DW_IC_SS_SCL_LCNT);
+	/* setup the slave address */
+	i2cd->slave_adr = 0x08;
+	writel(i2cd->slave_adr, i2cd->regs + DW_IC_TAR);
+
+	/* Now Enable the*/
+	writel(1, i2cd->regs + DW_IC_ENABLE);
+}
+
+static int amdgpu_i2c_check_activity(struct amdgpu_i2c_dev *i2cd)
+{
+	int timeout = ETIMEDOUT;
+
+	while (readl(i2cd->regs + DW_IC_STATUS) & DW_IC_STATUS_ACTIVITY) {
+		if (timeout <= 0) {
+			dev_dbg(i2cd->dev, "timeout waiting for bus ready\n");
+			if (readl(i2cd->regs + DW_IC_STATUS) & DW_IC_STATUS_ACTIVITY)
+				return -ETIMEDOUT;
+			return 0;
+		}
+		timeout--;
+		usleep_range(1000, 1100);
+	}
+
+	return 0;
+}
+
+static int amdgpu_i2c_check_stopbit(struct amdgpu_i2c_dev *i2cd)
+{
+	int timeout = ETIMEDOUT;
+
+	while (readl(i2cd->regs + DW_IC_INTR_STAT) & DW_IC_INTR_STOP_DET) {
+		if (timeout <= 0) {
+			dev_dbg(i2cd->dev, "timeout waiting for STOP BIT ready\n");
+			if (readl(i2cd->regs + DW_IC_INTR_STAT) & DW_IC_INTR_STOP_DET)
+				return -ETIMEDOUT;
+			return 0;
+		}
+		timeout--;
+		usleep_range(1000, 1100);
+	}
+
+	return 0;
+}
+
+static int amdgpu_i2c_status(struct amdgpu_i2c_dev *i2cd)
+{
+	int status;
+
+	status = amdgpu_i2c_check_activity(i2cd);
+	if (status)
+		return -ETIMEDOUT;
+
+	status = amdgpu_i2c_check_stopbit(i2cd);
+	if (status)
+		return -ETIMEDOUT;
+
+	return status;
+}
+
+/* Polling based xfer routine */
+static int amdgpu_i2c_master_xfer(struct i2c_adapter *adap,
+				  struct i2c_msg *msgs, int num)
+{
+	struct amdgpu_i2c_dev *i2cd = i2c_get_adapdata(adap);
+	int i, j, len, k;
+	int cmd = 0;
+	int status;
+	u8 *buf;
+
+	pm_runtime_get_sync(i2cd->dev);
+	amdgpu_configure_i2c_bus(i2cd);
+
+	for (i = 0; i < num; i++) {
+		buf = msgs[i].buf;
+		len = msgs[i].len;
+
+		if (!msgs[i].flags & I2C_M_RD)
+			writel(len - 1, i2cd->regs +  DW_IC_TX_TL);
+
+		for (j = len; j > 0; j--) {
+			if (i == num - 1 && j == 1)
+				cmd |= BIT(9);
+
+			if (msgs[i].flags & I2C_M_RD) {
+				writel(0x100, i2cd->regs + DW_IC_DATA_CMD);
+				writel(0x100 | cmd, i2cd->regs + DW_IC_DATA_CMD);
+				if (cmd) {
+					writel(2 * (len - 1), i2cd->regs +  DW_IC_TX_TL);
+					writel(2 * (len - 1), i2cd->regs +  DW_IC_RX_TL);
+					status = amdgpu_i2c_status(i2cd);
+					if (status)
+						return -ETIMEDOUT;
+
+					for (k = 0; k < len; k++)
+						buf[k] = readl(i2cd->regs + DW_IC_DATA_CMD);
+					status = amdgpu_i2c_check_stopbit(i2cd);
+					if (status)
+						return -ETIMEDOUT;
+				}
+			} else {
+				writel(*buf++ | cmd, i2cd->regs + DW_IC_DATA_CMD);
+				usleep_range(10000, 11000);
+			}
+		}
+		status = amdgpu_i2c_check_stopbit(i2cd);
+		if (status)
+			return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static u32 amdgpu_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm amdgpu_i2c_algorithm = {
+	.master_xfer	= amdgpu_i2c_master_xfer,
+	.functionality	= amdgpu_i2c_functionality,
+};
+
+#define PCI_DEVICE_ID_UCSI_AMD	0x73a4
+static const struct pci_device_id amdgpu_i2c_ids[] = {
+		{PCI_VDEVICE(ATI, PCI_DEVICE_ID_UCSI_AMD)},
+		{}
+};
+MODULE_DEVICE_TABLE(pci, amdgpu_i2c_ids);
+
+static int amdgpu_populate_client(struct amdgpu_i2c_dev *i2cd, int irq)
+{
+	i2cd->gpu_ccgx_ucsi = devm_kzalloc(i2cd->dev,
+					   sizeof(*i2cd->gpu_ccgx_ucsi),
+					   GFP_KERNEL);
+	if (!i2cd->gpu_ccgx_ucsi)
+		return -ENOMEM;
+
+	strlcpy(i2cd->gpu_ccgx_ucsi->type, "ccgx-ucsi",
+		sizeof(i2cd->gpu_ccgx_ucsi->type));
+	i2cd->gpu_ccgx_ucsi->addr = 0x8;
+	i2cd->gpu_ccgx_ucsi->irq = irq;
+	i2cd->ccgx_client = i2c_new_client_device(&i2cd->adapter, i2cd->gpu_ccgx_ucsi);
+	if (!i2cd->ccgx_client)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int amdgpu_i2c_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct amdgpu_i2c_dev *i2cd;
+	int status;
+	int irq;
+
+	i2cd = devm_kzalloc(&pdev->dev, sizeof(*i2cd), GFP_KERNEL);
+	if (!i2cd)
+		return -ENOMEM;
+
+	i2cd->dev = &pdev->dev;
+	dev_set_drvdata(&pdev->dev, i2cd);
+	status = pcim_enable_device(pdev);
+	if (status < 0) {
+		dev_err(&pdev->dev, "pcim_enable_device failed %d\n", status);
+		return status;
+	}
+
+	pci_set_master(pdev);
+
+	i2cd->regs = pcim_iomap(pdev, 0, 0);
+	if (!i2cd->regs) {
+		dev_err(&pdev->dev, "pcim_iomap failed\n");
+		return -ENOMEM;
+	}
+
+	/* Enable ucsi interrupt */
+	writel(AMD_UCSI_INTR_EN, i2cd->regs + AMD_UCSI_INTR_REG);
+
+	status = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+	if (status < 0) {
+		dev_err(&pdev->dev, "pci_alloc_irq_vectors err %d\n", status);
+		return status;
+	}
+
+	irq = pci_irq_vector(pdev, 0);
+	amdgpu_configure_i2c_bus(i2cd);
+	i2c_set_adapdata(&i2cd->adapter, i2cd);
+	i2cd->adapter.owner = THIS_MODULE;
+	strlcpy(i2cd->adapter.name, "AMDGPU NAVI I2C adapter",
+		sizeof(i2cd->adapter.name));
+	i2cd->adapter.algo = &amdgpu_i2c_algorithm;
+	i2cd->adapter.dev.parent = &pdev->dev;
+	status = i2c_add_adapter(&i2cd->adapter);
+	if (status < 0)
+		goto free_irq_vectors;
+
+	status = amdgpu_populate_client(i2cd, irq);
+	if (status < 0) {
+		dev_err(&pdev->dev, "amdgpu_populate_client failed %d\n", status);
+		goto del_adapter;
+	}
+
+	pm_runtime_set_autosuspend_delay(&pdev->dev, 3000);
+	pm_runtime_use_autosuspend(&pdev->dev);
+	pm_runtime_put_autosuspend(&pdev->dev);
+	pm_runtime_allow(&pdev->dev);
+
+	return 0;
+
+del_adapter:
+	i2c_del_adapter(&i2cd->adapter);
+free_irq_vectors:
+	pci_free_irq_vectors(pdev);
+	return status;
+}
+
+static void amdgpu_i2c_remove(struct pci_dev *pdev)
+{
+	struct amdgpu_i2c_dev *i2cd = dev_get_drvdata(&pdev->dev);
+
+	writel(0, i2cd->regs + AMD_UCSI_INTR_REG);
+	pm_runtime_get_noresume(i2cd->dev);
+	i2c_del_adapter(&i2cd->adapter);
+	pci_free_irq_vectors(pdev);
+}
+
+static __maybe_unused int amdgpu_i2c_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static __maybe_unused int amdgpu_i2c_resume(struct device *dev)
+{
+	struct amdgpu_i2c_dev *i2cd = dev_get_drvdata(dev);
+
+	amdgpu_configure_i2c_bus(i2cd);
+	pm_request_resume(&i2cd->ccgx_client->dev);
+	return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(amdgpu_i2c_driver_pm, amdgpu_i2c_suspend, amdgpu_i2c_resume,
+			    NULL);
+
+static struct pci_driver amdgpu_i2c_driver = {
+	.name		= "amdgpu-ucsi-i2c",
+	.id_table	= amdgpu_i2c_ids,
+	.probe		= amdgpu_i2c_probe,
+	.remove		= amdgpu_i2c_remove,
+	.driver		= {
+		.pm	= &amdgpu_i2c_driver_pm,
+	},
+};
+module_pci_driver(amdgpu_i2c_driver);
+
+MODULE_AUTHOR("Nehal Bakulchandra Shah <Nehal-bakulchandra.Shah@amd.com>");
+MODULE_AUTHOR("Sanjay R Mehta <Sanju.Mehta@amd.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2021-03-29 12:37 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-09 13:31 [PATCH] i2c: add i2c bus driver for amd navi gpu Sanket Goswami
2021-03-09 14:23 ` kernel test robot
2021-03-09 14:26 ` Andy Shevchenko
2021-03-22 16:56   ` Goswami, Sanket
2021-03-25 17:05     ` Andy Shevchenko
2021-03-26 10:23       ` Goswami, Sanket
2021-03-26 10:40         ` Andy Shevchenko
2021-03-29  5:55           ` Goswami, Sanket
2021-03-29 12:36             ` Andy Shevchenko
2021-03-09 15:35 ` kernel test robot
2021-03-09 15:36 ` kernel test robot
  -- strict thread matches above, loose matches on Subject: below --
2020-11-27 19:30 [PATCH] i2c: add i2c bus driver for AMD NAVI GPU Sanjay R Mehta
2020-11-30 11:19 ` Andy Shevchenko
2020-12-02  4:44   ` Sanjay R Mehta

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).