[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20220926082642.2578447-1-tharunkumar.pasumarthi@microchip.com>
Date: Mon, 26 Sep 2022 13:56:42 +0530
From: Tharun Kumar P <tharunkumar.pasumarthi@...rochip.com>
To: <linux-i2c@...r.kernel.org>, <linux-kernel@...r.kernel.org>,
<wsa@...nel.org>
CC: <andriy.shevchenko@...ux.intel.com>, <krzk@...nel.org>,
<jarkko.nikula@...ux.intel.com>, <robh@...nel.org>,
<semen.protsenko@...aro.org>, <sven@...npeter.dev>,
<jsd@...ihalf.com>, <rafal@...ecki.pl>, <olof@...om.net>,
<arnd@...db.de>, <UNGLinuxDriver@...rochip.com>
Subject: [PATCH RFC i2c-master] i2c: microchip: pci1xxxx: Load I2C driver for I2C endpoint of pci1xxxx switch
Microchip PCI1XXXX is an unmanaged PCIe3.1a Switch for Consumer,
Industrial and Automotive applications. This switch has multiple
downstream ports. In one of the Switch's Downstream port, there
is a peripheral endpoint which supports I2C functionality. The I2C
function in the endpoint operates at 100KHz, 400KHz and 1 MHz and
has buffer depth of 128 bytes.
This series of patches provides the I2C controller driver for the
I2C endpoint of the switch.
Signed-off-by: Tharun Kumar P <tharunkumar.pasumarthi@...rochip.com>
---
MAINTAINERS | 8 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mchp-pci1xxxx.c | 1123 ++++++++++++++++++++++++
4 files changed, 1142 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-mchp-pci1xxxx.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3cf9842d9233..204885ab5200 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13110,6 +13110,14 @@ S: Supported
F: Documentation/devicetree/bindings/mtd/atmel-nand.txt
F: drivers/mtd/nand/raw/atmel/*
+MICROCHIP PCI1XXXX I2C DRIVER
+M: Tharun Kumar P <tharunkumar.pasumarthi@...rochip.com>
+M: Kumaravel Thiagarajan <kumaravel.thiagarajan@...rochip.com>
+M: Microchip Linux Driver Support <UNGLinuxDriver@...rochip.com>
+L: linux-i2c@...r.kernel.org
+S: Maintained
+F: drivers/i2c/busses/i2c-mchp-pci1xxxx.c
+
MICROCHIP PWM DRIVER
M: Claudiu Beznea <claudiu.beznea@...rochip.com>
L: linux-arm-kernel@...ts.infradead.org (moderated for non-subscribers)
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a1bae59208e3..2baeb1bf05e8 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1290,6 +1290,16 @@ config I2C_VIPERBOARD
River Tech's viperboard.h for detailed meaning
of the module parameters.
+config I2C_PCI1XXXX
+ tristate "PCI1XXXX I2C Host Adapter support"
+ depends on PCI
+ help
+ Say yes here to enable the I2C Host adapter support for the PCI1xxxx card
+ This is a PCI to I2C adapter
+
+ This driver can be built as a module. If so, the module will be
+ called as i2c-mchp-pci1xxxx
+
comment "Other I2C/SMBus bus drivers"
config I2C_ACORN
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 479f60e4ee3d..f5eaa737d8ee 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_ROBOTFUZZ_OSIF) += i2c-robotfuzz-osif.o
obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o
obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o
obj-$(CONFIG_I2C_VIPERBOARD) += i2c-viperboard.o
+obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
# Other I2C/SMBus bus drivers
obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o
diff --git a/drivers/i2c/busses/i2c-mchp-pci1xxxx.c b/drivers/i2c/busses/i2c-mchp-pci1xxxx.c
new file mode 100644
index 000000000000..8168ef2f0bf8
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mchp-pci1xxxx.c
@@ -0,0 +1,1123 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microchip PCI1XXXX I2C adapter driver for PCIe Switch
+ * which has I2C controller in one of its downstream functions
+ *
+ * Copyright (C) 2021 - 2022 Microchip Technology Inc.
+ *
+ * Author: Tharun Kumar P <tharunkumar.pasumarthi@...rochip.com>
+ * Kumaravel Thiagarajan <kumaravel.thiagarajan@...rochip.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/i2c-smbus.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#define SMBUS_MAST_CORE_ADDR_BASE 0x00000
+#define SMBUS_MAST_SYS_REG_ADDR_BASE 0x01000
+
+#define PCI1XXXX_IRQ_FLAGS 0
+
+/*SMB register space*/
+#define SMB_CORE_CTRL_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x00)
+
+#define SMB_CORE_CTRL_ESO 0x40
+#define SMB_CORE_CTRL_FW_ACK 0x10
+
+#define SMB_CORE_CMD_REG_OFF3 (SMBUS_MAST_CORE_ADDR_BASE + 0x0F)
+#define SMB_CORE_CMD_REG_OFF2 (SMBUS_MAST_CORE_ADDR_BASE + 0x0E)
+#define SMB_CORE_CMD_REG_OFF1 (SMBUS_MAST_CORE_ADDR_BASE + 0x0D)
+
+#define SMB_CORE_CMD_READM 0x10
+#define SMB_CORE_CMD_STOP 0x04
+#define SMB_CORE_CMD_START 0x01
+
+#define SMB_CORE_CMD_REG_OFF0 (SMBUS_MAST_CORE_ADDR_BASE + 0x0C)
+
+#define SMB_CORE_CMD_M_PROCEED 0x02
+#define SMB_CORE_CMD_M_RUN 0x01
+
+#define SMB_CORE_SR_HOLD_TIME_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x18)
+
+#define SR_HOLD_TIME_100KHZ 0x85
+#define SR_HOLD_TIME_400KHZ 0x14
+#define SR_HOLD_TIME_1000KHZ 0x0B
+
+#define SMB_CORE_COMPLETION_REG_OFF3 (SMBUS_MAST_CORE_ADDR_BASE + 0x23)
+
+#define COMPLETION_MDONE 0x40
+#define COMPLETION_IDLE 0x20
+#define COMPLETION_MNAKX 0x01
+
+#define SMB_CORE_IDLE_SCALING_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x24)
+
+#define SMB_IDLE_SCALING_100KHZ 0x03E803C9
+#define SMB_IDLE_SCALING_400KHZ 0x01F4009D
+#define SMB_IDLE_SCALING_1000KHZ 0x01F4009D
+
+#define SMB_CORE_CONFIG_REG3 (SMBUS_MAST_CORE_ADDR_BASE + 0x2B)
+
+#define SMB_CONFIG3_ENMI 0x40
+#define SMB_CONFIG3_ENIDI 0x20
+
+#define SMB_CORE_CONFIG_REG2 (SMBUS_MAST_CORE_ADDR_BASE + 0x2A)
+#define SMB_CORE_CONFIG_REG1 (SMBUS_MAST_CORE_ADDR_BASE + 0x29)
+
+#define SMB_CONFIG1_ASR 0x80
+#define SMB_CONFIG1_ENAB 0x04
+#define SMB_CONFIG1_RESET 0x02
+#define SMB_CONFIG1_FEN 0x01
+
+#define SMB_CORE_BUS_CLK_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x2C)
+
+#define BUS_CLK_100KHZ 0x9A9C
+#define BUS_CLK_400KHZ 0x2329
+#define BUS_CLK_1000KHZ 0x0E0F
+
+#define SMB_CORE_CLK_SYNC_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x3C)
+
+#define CLK_SYNC_100KHZ 0x04
+#define CLK_SYNC_400KHZ 0x04
+#define CLK_SYNC_1000KHZ 0x04
+
+#define SMB_CORE_DATA_TIMING_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x40)
+
+#define DATA_TIMING_100KHZ 0x169D9D01
+#define DATA_TIMING_400KHZ 0x10141401
+#define DATA_TIMING_1000KHZ 0x060C0C01
+
+#define SMB_CORE_TO_SCALING_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x44)
+
+#define TO_SCALING_100KHZ 0xA79FC7CC
+#define TO_SCALING_400KHZ 0x8B9FC7CC
+#define TO_SCALING_1000KHZ 0x859FC7CC
+
+#define I2C_SCL_PAD_CTRL_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x100)
+#define I2C_SDA_PAD_CTRL_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x101)
+
+#define I2C_FOD_EN 0x10
+#define I2C_PULL_UP_EN 0x08
+#define I2C_PULL_DOWN_EN 0x04
+#define I2C_INPUT_EN 0x02
+#define I2C_OUTPUT_EN 0x01
+
+#define SMBUS_CONTROL_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x200)
+
+#define CTL_RESET_COUNTERS 0x08
+#define CTL_TRANSFER_DIR 0x04
+#define CTL_HOST_FIFO_ENTRY 0x02
+#define CTL_RUN 0x01
+
+#define I2C_DIR_WRITE 0
+#define I2C_DIR_READ 1
+
+#define SMBUS_STATUS_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x204)
+
+#define STA_DMA_TERM 0x80
+#define STA_DMA_REQ 0x40
+#define STA_THRESHOLD 0x04
+#define STA_BUF_FULL 0x02
+#define STA_BUF_EMPTY 0x01
+
+#define SMBUS_INTR_STAT_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x208)
+
+#define INTR_STAT_DMA_TERM 0x80
+#define INTR_STAT_THRESHOLD 0x04
+#define INTR_STAT_BUF_FULL 0x02
+#define INTR_STAT_BUF_EMPTY 0x01
+
+#define SMBUS_INTR_MSK_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x20C)
+
+#define INTR_MSK_DMA_TERM 0x80
+#define INTR_MSK_THRESHOLD 0x04
+#define INTR_MSK_BUF_FULL 0x02
+#define INTR_MSK_BUF_EMPTY 0x01
+
+#define ALL_NW_LAYER_INTERRUPTS (INTR_MSK_DMA_TERM | INTR_MSK_THRESHOLD |\
+ INTR_MSK_BUF_FULL | INTR_MSK_BUF_EMPTY)
+
+#define SMBUS_MCU_COUNTER_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x214)
+
+#define SMBALERT_MST_PAD_CTRL_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x230)
+
+#define SMBALERT_MST_PU 0x01
+
+#define SMBUS_GEN_INT_STAT_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x23C)
+
+#define SMBUS_GEN_INT_MASK_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x240)
+
+#define SMBALERT_INTR_MASK 0x0400
+#define I2C_BUF_MSTR_INTR_MASK 0x0200
+#define I2C_INTR_MASK 0x0100
+#define SMBALERT_WAKE_INTR_MASK 0x0004
+#define I2C_BUF_MSTR_WAKE_INTR_MASK 0x0002
+#define I2C_WAKE_INTR_MASK 0x0001
+
+#define ALL_HIGH_LAYER_INTR (SMBALERT_INTR_MASK | I2C_BUF_MSTR_INTR_MASK | \
+ I2C_INTR_MASK | SMBALERT_WAKE_INTR_MASK | \
+ I2C_BUF_MSTR_WAKE_INTR_MASK | \
+ I2C_WAKE_INTR_MASK)
+
+#define SMBUS_RESET_REG (SMBUS_MAST_CORE_ADDR_BASE + 0x248)
+
+#define PERI_SMBUS_D3_RESET_DIS 0x10000
+
+#define SMBUS_MST_BUF (SMBUS_MAST_CORE_ADDR_BASE + 0x280)
+
+#define SMBUS_MAST_BUF_MAX_SIZE 0x80
+
+#define I2C_FLAGS_DIRECT_MODE 0x80
+#define I2C_FLAGS_POLLING_MODE 0x40
+#define I2C_FLAGS_STOP 0x20
+#define I2C_FLAGS_SMB_BLK_READ 0x10
+
+#define PCI1XXXX_I2C_TIMEOUT 1000
+
+/* General Purpose Register */
+#define SMB_GPR_REG (SMBUS_MAST_CORE_ADDR_BASE + 0x1000 + 0x0c00 + 0x00)
+
+/* Lock Register */
+#define SMB_GPR_LOCK_REG (SMBUS_MAST_CORE_ADDR_BASE + 0x1000 + 0x0000 + 0x00A0)
+
+#define SMBUS_PERI_LOCK BIT(3)
+
+/*
+ * struct pci1xxxx_i2c - private structure for the I2C controller
+ *
+ * @adap: I2C adapter instance
+ * @dev: pointer to device struct
+ * @i2c_base: pci base address of the I2C ctrler
+ * @i2c_xfer_done: used for synchronisation between foreground & isr
+ * @freq: frequency of I2C transfer
+ * @flags: internal flags to store transfer conditions
+ * @irq: irq number
+ */
+
+struct pci1xxxx_i2c {
+ struct completion i2c_xfer_done;
+ bool i2c_xfer_in_progress;
+ struct i2c_adapter adap;
+ void __iomem *i2c_base;
+ u32 freq;
+ u32 flags;
+};
+
+static int set_sys_lock(void __iomem *addr)
+{
+ u8 data;
+
+ writel(SMBUS_PERI_LOCK, addr);
+ data = readl(addr);
+ if (data != SMBUS_PERI_LOCK)
+ return -EPERM;
+
+ return 0;
+}
+
+static int release_sys_lock(void __iomem *addr)
+{
+ u8 data;
+
+ data = readl(addr);
+ if (data != SMBUS_PERI_LOCK)
+ return 0;
+
+ writel(0, addr);
+ data = readl(addr);
+ if (data & SMBUS_PERI_LOCK)
+ return -EPERM;
+
+ return 0;
+}
+
+static void pci1xxxx_ack_high_level_intr(struct pci1xxxx_i2c *i2c, u16 intr_msk)
+{
+ writew(intr_msk, i2c->i2c_base + SMBUS_GEN_INT_STAT_REG_OFF);
+}
+
+static void pci1xxxx_i2c_configure_smbalert_pin(struct pci1xxxx_i2c *i2c,
+ bool enable)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMBALERT_MST_PAD_CTRL_REG_OFF);
+
+ if (enable)
+ regval |= SMBALERT_MST_PU;
+ else
+ regval &= ~SMBALERT_MST_PU;
+
+ writeb(regval, i2c->i2c_base + SMBALERT_MST_PAD_CTRL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_send_start_stop(struct pci1xxxx_i2c *i2c, bool start)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMB_CORE_CMD_REG_OFF1);
+
+ if (start)
+ regval |= SMB_CORE_CMD_START;
+ else
+ regval |= SMB_CORE_CMD_STOP;
+
+ writeb(regval, i2c->i2c_base + SMB_CORE_CMD_REG_OFF1);
+}
+
+/*when accessing the core control reg, we should not do a read modified write
+ * as there are write '1' to clear bits. Hence do a write with the specific
+ * bits that needs to be set
+ */
+static void pci1xxxx_i2c_set_clear_FW_ACK(struct pci1xxxx_i2c *i2c, bool set)
+{
+ u8 regval;
+
+ if (set)
+ regval = SMB_CORE_CTRL_FW_ACK | SMB_CORE_CTRL_ESO;
+ else
+ regval = SMB_CORE_CTRL_ESO;
+
+ writeb(regval, i2c->i2c_base + SMB_CORE_CTRL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_buffer_write(struct pci1xxxx_i2c *i2c, u8 slaveaddr,
+ u8 transferlen, unsigned char *buf)
+{
+ if (slaveaddr) {
+ writeb(slaveaddr, i2c->i2c_base + SMBUS_MST_BUF);
+ if (buf)
+ memcpy_toio((i2c->i2c_base + SMBUS_MST_BUF + 1), buf, transferlen);
+ } else {
+ if (buf)
+ memcpy_toio((i2c->i2c_base + SMBUS_MST_BUF), buf, transferlen);
+ }
+}
+
+/*
+ *when accessing the core control reg, we should not do a read modified write
+ *as there are write '1' to clear bits. Hence do a write with the
+ *specific bits that needs to be set
+ */
+static void pci1xxxx_i2c_enable_ESO(struct pci1xxxx_i2c *i2c)
+{
+ writeb(SMB_CORE_CTRL_ESO, i2c->i2c_base + SMB_CORE_CTRL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_reset_counters(struct pci1xxxx_i2c *i2c)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+ regval |= CTL_RESET_COUNTERS;
+ writeb(regval, i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_transfer_dir(struct pci1xxxx_i2c *i2c, u8 direction)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+ if (direction == I2C_DIR_WRITE)
+ regval &= ~CTL_TRANSFER_DIR;
+ else
+ regval |= CTL_TRANSFER_DIR;
+
+ writeb(regval, i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_mcu_count(struct pci1xxxx_i2c *i2c, u8 count)
+{
+ writeb(count, i2c->i2c_base + SMBUS_MCU_COUNTER_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_read_count(struct pci1xxxx_i2c *i2c, u8 readcount)
+{
+ writeb(readcount, i2c->i2c_base + SMB_CORE_CMD_REG_OFF3);
+}
+
+static void pci1xxxx_i2c_set_write_count(struct pci1xxxx_i2c *i2c, u8 writecount)
+{
+ writeb(writecount, i2c->i2c_base + SMB_CORE_CMD_REG_OFF2);
+}
+
+static void pci1xxxx_i2c_set_DMA_run(struct pci1xxxx_i2c *i2c)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+ regval |= CTL_RUN;
+ writeb(regval, i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_mrun_proceed(struct pci1xxxx_i2c *i2c)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMB_CORE_CMD_REG_OFF0);
+ regval |= SMB_CORE_CMD_M_RUN;
+ regval |= SMB_CORE_CMD_M_PROCEED;
+ writeb(regval, i2c->i2c_base + SMB_CORE_CMD_REG_OFF0);
+}
+
+static void pci1xxxx_i2c_start_DMA(struct pci1xxxx_i2c *i2c)
+{
+ pci1xxxx_i2c_set_DMA_run(i2c);
+ pci1xxxx_i2c_set_mrun_proceed(i2c);
+}
+
+static void pci1xxxx_i2c_config_asr(struct pci1xxxx_i2c *i2c, bool enable)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMB_CORE_CONFIG_REG1);
+ if (enable)
+ regval |= SMB_CONFIG1_ASR;
+ else
+ regval &= ~SMB_CONFIG1_ASR;
+ writeb(regval, i2c->i2c_base + SMB_CORE_CONFIG_REG1);
+}
+
+static irqreturn_t pci1xxxx_i2c_isr(int irq, void *dev)
+{
+ irqreturn_t intr_handled = IRQ_NONE;
+ struct pci1xxxx_i2c *i2c = dev;
+ u16 reg1;
+ u8 reg3;
+
+ /* Read the SMBus interrupt status register to see if the
+ * DMA_TERM interrupt has caused this callback
+ */
+ reg1 = readw(i2c->i2c_base + SMBUS_GEN_INT_STAT_REG_OFF);
+
+ if (reg1 & I2C_BUF_MSTR_INTR_MASK) {
+ reg3 = readb(i2c->i2c_base + SMBUS_INTR_STAT_REG_OFF);
+ if (reg3 & INTR_STAT_DMA_TERM) {
+ complete(&i2c->i2c_xfer_done);
+ intr_handled = IRQ_HANDLED;
+ writeb(INTR_STAT_DMA_TERM,
+ i2c->i2c_base + SMBUS_INTR_STAT_REG_OFF);
+ }
+ pci1xxxx_ack_high_level_intr(i2c, I2C_BUF_MSTR_INTR_MASK);
+ }
+
+ if (reg1 & SMBALERT_INTR_MASK) {
+ intr_handled = IRQ_HANDLED;
+ /*ACK the interrupt*/
+ pci1xxxx_ack_high_level_intr(i2c, SMBALERT_INTR_MASK);
+ }
+
+ return intr_handled;
+}
+
+static void pci1xxxx_i2c_set_count(struct pci1xxxx_i2c *i2c, u8 mcucount,
+ u8 writecount, u8 readcount)
+{
+ pci1xxxx_i2c_set_mcu_count(i2c, mcucount);
+ pci1xxxx_i2c_set_write_count(i2c, writecount);
+ pci1xxxx_i2c_set_read_count(i2c, readcount);
+}
+
+static void pci1xxxx_i2c_set_readm(struct pci1xxxx_i2c *i2c, bool enable)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMB_CORE_CMD_REG_OFF1);
+ if (enable)
+ regval |= SMB_CORE_CMD_READM;
+ else
+ regval &= ~SMB_CORE_CMD_READM;
+
+ writeb(regval, i2c->i2c_base + SMB_CORE_CMD_REG_OFF1);
+}
+
+static void pci1xxxx_ack_nw_layer_intr(struct pci1xxxx_i2c *i2c,
+ u8 ack_intr_msk)
+{
+ writeb(ack_intr_msk, i2c->i2c_base + SMBUS_INTR_STAT_REG_OFF);
+}
+
+static void pci1xxxx_config_nw_layer_intr(struct pci1xxxx_i2c *i2c,
+ u8 intr_msk, bool enable)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMBUS_INTR_MSK_REG_OFF);
+ if (enable)
+ regval &= ~(intr_msk);
+ else
+ regval |= intr_msk;
+
+ writeb(regval, i2c->i2c_base + SMBUS_INTR_MSK_REG_OFF);
+}
+
+static void pci1xxxx_i2c_config_padctrl(struct pci1xxxx_i2c *i2c, bool enable)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + I2C_SCL_PAD_CTRL_REG_OFF);
+ if (enable)
+ regval |= (I2C_INPUT_EN | I2C_OUTPUT_EN);
+ else
+ regval &= ~(I2C_INPUT_EN | I2C_OUTPUT_EN);
+
+ writeb(regval, i2c->i2c_base + I2C_SCL_PAD_CTRL_REG_OFF);
+
+ regval = readb(i2c->i2c_base + I2C_SDA_PAD_CTRL_REG_OFF);
+ if (enable)
+ regval |= (I2C_INPUT_EN | I2C_OUTPUT_EN);
+ else
+ regval &= ~(I2C_INPUT_EN | I2C_OUTPUT_EN);
+
+ writeb(regval, i2c->i2c_base + I2C_SDA_PAD_CTRL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_mode(struct pci1xxxx_i2c *i2c)
+{
+ u8 regval;
+
+ regval = readb(i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+ if (i2c->flags & I2C_FLAGS_DIRECT_MODE)
+ regval &= ~CTL_HOST_FIFO_ENTRY;
+ else
+ regval |= CTL_HOST_FIFO_ENTRY;
+
+ writeb(regval, i2c->i2c_base + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_config_high_level_intr(struct pci1xxxx_i2c *i2c,
+ u16 intr_msk, bool enable)
+{
+ u16 regval;
+
+ regval = readw(i2c->i2c_base + SMBUS_GEN_INT_MASK_REG_OFF);
+ if (enable)
+ regval &= ~(intr_msk);
+ else
+ regval |= intr_msk;
+ writew(regval, i2c->i2c_base + SMBUS_GEN_INT_MASK_REG_OFF);
+}
+
+static void pci1xxxx_i2c_configure_core_reg(struct pci1xxxx_i2c *i2c, bool enable)
+{
+ u8 reg1;
+ u8 reg3;
+
+ reg1 = readb(i2c->i2c_base + SMB_CORE_CONFIG_REG1);
+ reg3 = readb(i2c->i2c_base + SMB_CORE_CONFIG_REG3);
+ if (enable) {
+ reg1 |= (SMB_CONFIG1_ENAB | SMB_CONFIG1_FEN);
+ reg3 |= (SMB_CONFIG3_ENMI | SMB_CONFIG3_ENIDI);
+ } else {
+ reg1 &= ~(SMB_CONFIG1_ENAB | SMB_CONFIG1_FEN);
+ reg3 &= ~(SMB_CONFIG3_ENMI | SMB_CONFIG3_ENIDI);
+ }
+
+ writeb(reg1, i2c->i2c_base + SMB_CORE_CONFIG_REG1);
+ writeb(reg3, i2c->i2c_base + SMB_CORE_CONFIG_REG3);
+}
+
+static void pci1xxxx_i2c_set_freq(struct pci1xxxx_i2c *i2c)
+{
+ /* The SMB core needs specific values to be set in the BUS_CLK register for the
+ * corresponding frequency
+ */
+ switch (i2c->freq) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ writeb(SR_HOLD_TIME_100KHZ,
+ i2c->i2c_base + SMB_CORE_SR_HOLD_TIME_REG_OFF);
+ writel(SMB_IDLE_SCALING_100KHZ,
+ i2c->i2c_base + SMB_CORE_IDLE_SCALING_REG_OFF);
+ writew(BUS_CLK_100KHZ,
+ i2c->i2c_base + SMB_CORE_BUS_CLK_REG_OFF);
+ writel(CLK_SYNC_100KHZ,
+ i2c->i2c_base + SMB_CORE_CLK_SYNC_REG_OFF);
+ writel(DATA_TIMING_100KHZ,
+ i2c->i2c_base + SMB_CORE_DATA_TIMING_REG_OFF);
+ writel(TO_SCALING_100KHZ,
+ i2c->i2c_base + SMB_CORE_TO_SCALING_REG_OFF);
+ break;
+
+ case I2C_MAX_FAST_MODE_PLUS_FREQ:
+ writeb(SR_HOLD_TIME_1000KHZ,
+ i2c->i2c_base + SMB_CORE_SR_HOLD_TIME_REG_OFF);
+ writel(SMB_IDLE_SCALING_1000KHZ,
+ i2c->i2c_base + SMB_CORE_IDLE_SCALING_REG_OFF);
+ writew(BUS_CLK_1000KHZ,
+ i2c->i2c_base + SMB_CORE_BUS_CLK_REG_OFF);
+ writel(CLK_SYNC_1000KHZ,
+ i2c->i2c_base + SMB_CORE_CLK_SYNC_REG_OFF);
+ writel(DATA_TIMING_1000KHZ,
+ i2c->i2c_base + SMB_CORE_DATA_TIMING_REG_OFF);
+ writel(TO_SCALING_1000KHZ,
+ i2c->i2c_base + SMB_CORE_TO_SCALING_REG_OFF);
+ break;
+
+ case I2C_MAX_FAST_MODE_FREQ:
+ default:
+ writeb(SR_HOLD_TIME_400KHZ,
+ i2c->i2c_base + SMB_CORE_SR_HOLD_TIME_REG_OFF);
+ writel(SMB_IDLE_SCALING_400KHZ,
+ i2c->i2c_base + SMB_CORE_IDLE_SCALING_REG_OFF);
+ writew(BUS_CLK_400KHZ,
+ i2c->i2c_base + SMB_CORE_BUS_CLK_REG_OFF);
+ writel(CLK_SYNC_400KHZ,
+ i2c->i2c_base + SMB_CORE_CLK_SYNC_REG_OFF);
+ writel(DATA_TIMING_400KHZ,
+ i2c->i2c_base + SMB_CORE_DATA_TIMING_REG_OFF);
+ writel(TO_SCALING_400KHZ,
+ i2c->i2c_base + SMB_CORE_TO_SCALING_REG_OFF);
+ break;
+ }
+}
+
+static void pci1xxxx_i2c_init(struct pci1xxxx_i2c *i2c)
+{
+ u8 regval;
+ u8 ret;
+
+ ret = set_sys_lock(i2c->i2c_base + SMB_GPR_LOCK_REG);
+ if (ret != -EPERM) {
+ regval = readl(i2c->i2c_base + SMB_GPR_REG);
+ release_sys_lock(i2c->i2c_base + SMB_GPR_LOCK_REG);
+ } else {
+ /*
+ * Configure I2C Fast Mode as default frequency if unable
+ * to acquire sys lock
+ */
+ regval = 0;
+ }
+
+ switch (regval) {
+ case 0:
+ i2c->freq = I2C_MAX_FAST_MODE_FREQ;
+ pci1xxxx_i2c_set_freq(i2c);
+ break;
+ case 1:
+ i2c->freq = I2C_MAX_STANDARD_MODE_FREQ;
+ pci1xxxx_i2c_set_freq(i2c);
+ break;
+ case 2:
+ i2c->freq = I2C_MAX_FAST_MODE_PLUS_FREQ;
+ pci1xxxx_i2c_set_freq(i2c);
+ break;
+ case 3:
+ default:
+ break;
+ }
+
+ pci1xxxx_i2c_config_padctrl(i2c, true);
+ i2c->flags |= I2C_FLAGS_DIRECT_MODE;
+ pci1xxxx_i2c_set_mode(i2c);
+
+ /*added as a precaution since BUF_EMPTY in status register
+ *also trigered an Interrupt
+ */
+ writeb(STA_BUF_EMPTY, i2c->i2c_base + SMBUS_STATUS_REG_OFF);
+
+ /*Configure core I2c control registers*/
+ pci1xxxx_i2c_configure_core_reg(i2c, true);
+
+ /*Enable Pullup for the SMB alert pin which is just used for
+ *wakeup right now
+ */
+ pci1xxxx_i2c_configure_smbalert_pin(i2c, true);
+}
+
+static void pci1xxxx_i2c_clear_flags(struct pci1xxxx_i2c *i2c)
+{
+ u8 regval;
+
+ /*Reset the internal buffer counters*/
+ pci1xxxx_i2c_reset_counters(i2c);
+
+ /*Clear low level interrupts*/
+ regval = (COMPLETION_MNAKX | COMPLETION_IDLE | COMPLETION_MDONE);
+ writeb(regval, i2c->i2c_base + SMB_CORE_COMPLETION_REG_OFF3);
+
+ reinit_completion(&i2c->i2c_xfer_done);
+ pci1xxxx_ack_nw_layer_intr(i2c, ALL_NW_LAYER_INTERRUPTS);
+
+ pci1xxxx_ack_high_level_intr(i2c, ALL_HIGH_LAYER_INTR);
+}
+
+static int pci1xxxx_i2c_read(struct pci1xxxx_i2c *i2c, u8 slaveaddr,
+ unsigned char *buf, u16 total_len)
+{
+ unsigned long time_left;
+ u16 remainingbytes;
+ u8 transferlen;
+ int retval = 0;
+ u8 read_count;
+ u32 regval;
+ u16 count;
+
+ /*Enable I2C host controller by setting the ESO bit in the CONTROL REG*/
+ pci1xxxx_i2c_enable_ESO(i2c);
+
+ pci1xxxx_i2c_clear_flags(i2c);
+
+ pci1xxxx_config_nw_layer_intr(i2c, INTR_MSK_DMA_TERM, true);
+
+ pci1xxxx_i2c_config_high_level_intr(i2c, (I2C_BUF_MSTR_INTR_MASK),
+ true);
+
+ /* The i2c transfer could be for more than 128 bytes. Our Core is
+ * capable of only sending 128 at a time.
+ * As for as the I2C read is concerned, initailly send the
+ * read slave address along with the number of bytes to read in
+ * ReadCount. After sending the slave address the interrupt
+ * is generated. On seeing the ACK for the slave address, reverse the
+ * buffer direction and run the DMA to initiate Read from slave
+ */
+ for (count = 0; count < total_len; count += transferlen) {
+ /*before start of any transaction clear the existing
+ * START/STOP conditions
+ */
+ writeb(0x00, i2c->i2c_base + SMB_CORE_CMD_REG_OFF1);
+
+ remainingbytes = total_len - count;
+
+ transferlen = min(remainingbytes, (u16)(SMBUS_MAST_BUF_MAX_SIZE));
+
+ /* See if this is last chunk in the transaction. IN that case
+ * send a STOP at the end.
+ * Also in case of reads > BUF_SIZE, for the first read,
+ * we should not send NACK for the last byte.
+ * Hence a bit FW_ACK is set for those.
+ * For the last chunk NACK should be sent.
+ * Hence FW_ACK is cleared for that.
+ * Send STOP only when I2C_FLAGS_STOP bit is set in the flags
+ * and only for the last transaction.
+ */
+
+ if ((count + transferlen >= total_len) && (i2c->flags & I2C_FLAGS_STOP)) {
+ pci1xxxx_i2c_set_clear_FW_ACK(i2c, false);
+ pci1xxxx_i2c_send_start_stop(i2c, 0);
+ } else {
+ pci1xxxx_i2c_set_clear_FW_ACK(i2c, true);
+ }
+
+ /*if it is the starting of the transaction send START*/
+ if (count == 0) {
+ pci1xxxx_i2c_set_transfer_dir(i2c, I2C_DIR_WRITE);
+
+ pci1xxxx_i2c_send_start_stop(i2c, 1);
+
+ /*write I2c buffer with just the slave addr*/
+ pci1xxxx_i2c_buffer_write(i2c, slaveaddr, 0, NULL);
+
+ /*Set the count. Readcount is the transfer bytes*/
+ pci1xxxx_i2c_set_count(i2c, 1, 1, transferlen);
+
+ /*Set the Auto_start_read bit so that the HW itself
+ *will take care of the read phase.
+ */
+
+ pci1xxxx_i2c_config_asr(i2c, true);
+
+ if (i2c->flags & I2C_FLAGS_SMB_BLK_READ)
+ pci1xxxx_i2c_set_readm(i2c, true);
+
+ } else {
+ pci1xxxx_i2c_set_count(i2c, 0, 0, transferlen);
+
+ pci1xxxx_i2c_config_asr(i2c, false);
+
+ pci1xxxx_i2c_clear_flags(i2c);
+
+ pci1xxxx_i2c_set_transfer_dir(i2c, I2C_DIR_READ);
+ }
+
+ /*Start the DMA*/
+ pci1xxxx_i2c_start_DMA(i2c);
+
+ /*wait for the DMA_TERM interrupt*/
+ time_left = wait_for_completion_timeout
+ (&i2c->i2c_xfer_done,
+ msecs_to_jiffies(PCI1XXXX_I2C_TIMEOUT));
+ if (time_left == 0) {
+ /*Reset the I2C core to release the bus lock*/
+ pci1xxxx_i2c_init(i2c);
+ retval = -ETIMEDOUT;
+ goto cleanup;
+ }
+
+ /*Read the completion reg to know the reason for DMA_TERM*/
+ regval = readb(i2c->i2c_base + SMB_CORE_COMPLETION_REG_OFF3);
+ /*Slave did not respond*/
+ if (regval & COMPLETION_MNAKX) {
+ writeb(COMPLETION_MNAKX, i2c->i2c_base +
+ SMB_CORE_COMPLETION_REG_OFF3);
+ retval = -ETIMEDOUT;
+ goto cleanup;
+ }
+
+ if (i2c->flags & I2C_FLAGS_SMB_BLK_READ) {
+ buf[0] = readb(i2c->i2c_base +
+ SMBUS_MST_BUF);
+ read_count = buf[0];
+ memcpy_fromio(&buf[1], (i2c->i2c_base +
+ SMBUS_MST_BUF + 1),
+ read_count);
+ } else {
+ memcpy_fromio(&buf[count], (i2c->i2c_base +
+ SMBUS_MST_BUF), transferlen);
+ }
+ }
+
+cleanup:
+ /*Disable all the interrupts*/
+ pci1xxxx_config_nw_layer_intr(i2c, INTR_MSK_DMA_TERM, false);
+ pci1xxxx_i2c_config_high_level_intr(i2c, (I2C_BUF_MSTR_INTR_MASK),
+ false);
+ pci1xxxx_i2c_config_asr(i2c, false);
+ return retval;
+}
+
+static int pci1xxxx_i2c_write(struct pci1xxxx_i2c *i2c, u8 slaveaddr,
+ unsigned char *buf, u16 total_len)
+{
+ unsigned long time_left;
+ u16 remainingbytes;
+ u8 actualwritelen;
+ u8 transferlen;
+ int retval = 0;
+ u32 regval;
+ u16 count;
+
+ /*Enable I2C host controller by setting the ESO bit in the CONTROL REG*/
+ pci1xxxx_i2c_enable_ESO(i2c);
+
+ /*Set the Buffer direction.*/
+ pci1xxxx_i2c_set_transfer_dir(i2c, I2C_DIR_WRITE);
+
+ pci1xxxx_config_nw_layer_intr(i2c, INTR_MSK_DMA_TERM, true);
+
+ pci1xxxx_i2c_config_high_level_intr(i2c, (I2C_BUF_MSTR_INTR_MASK),
+ true);
+
+ /*The i2c transfer could be for more thatn 128 bytes. Our Core is
+ * capable of only sending 128 at a time.
+ */
+ for (count = 0; count < total_len; count += transferlen) {
+ /*before start of any transaction clear the existing
+ *START/STOP conditions
+ */
+ writeb(0x00, i2c->i2c_base + SMB_CORE_CMD_REG_OFF1);
+
+ pci1xxxx_i2c_clear_flags(i2c);
+
+ remainingbytes = total_len - count;
+ /*if it is the starting of the transaction send START*/
+ if (count == 0) {
+ pci1xxxx_i2c_send_start_stop(i2c, 1);
+
+ /* -1 for the slave address.*/
+ transferlen = min((u16)(SMBUS_MAST_BUF_MAX_SIZE - 1),
+ remainingbytes);
+ pci1xxxx_i2c_buffer_write(i2c, slaveaddr,
+ transferlen, &buf[count]);
+
+ /* the actual number of bytes written on the I2C bus
+ * is including the slave address
+ */
+ actualwritelen = transferlen + 1;
+
+ } else {
+ transferlen = min((u16)(SMBUS_MAST_BUF_MAX_SIZE),
+ remainingbytes);
+ pci1xxxx_i2c_buffer_write(i2c, 0x00, transferlen,
+ &buf[count]);
+ actualwritelen = transferlen;
+ }
+
+ pci1xxxx_i2c_set_count(i2c, actualwritelen,
+ actualwritelen, 0x00);
+
+ /*
+ * Send STOP only when I2C_FLAGS_STOP bit is set in the flags and
+ * only for the last transaction.
+ */
+
+ if (remainingbytes <= transferlen && (i2c->flags &
+ I2C_FLAGS_STOP))
+ pci1xxxx_i2c_send_start_stop(i2c, 0);
+
+ pci1xxxx_i2c_start_DMA(i2c);
+
+ /*
+ * wait for the DMA_TERM interrupt and if the timer expires, it means
+ * the transaction has failed due to some bus lock as we dint get
+ * the interrupt
+ */
+ time_left = wait_for_completion_timeout
+ (&i2c->i2c_xfer_done, msecs_to_jiffies(PCI1XXXX_I2C_TIMEOUT));
+
+ if (time_left == 0) {
+ /*Reset the I2C core to release the bus lock */
+ pci1xxxx_i2c_init(i2c);
+ retval = -ETIMEDOUT;
+ goto cleanup;
+ }
+
+ regval = readb(i2c->i2c_base + SMB_CORE_COMPLETION_REG_OFF3);
+ if (regval & COMPLETION_MNAKX) {
+ writeb(COMPLETION_MNAKX, i2c->i2c_base +
+ SMB_CORE_COMPLETION_REG_OFF3);
+ retval = -ETIMEDOUT;
+ goto cleanup;
+ }
+ }
+cleanup:
+ /*Disable all the interrupts*/
+ pci1xxxx_config_nw_layer_intr(i2c, INTR_MSK_DMA_TERM, false);
+ pci1xxxx_i2c_config_high_level_intr(i2c, (I2C_BUF_MSTR_INTR_MASK),
+ false);
+
+ return retval;
+}
+
+static int pci1xxxx_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct pci1xxxx_i2c *i2c = i2c_get_adapdata(adap);
+ u8 slaveaddr;
+ int retval;
+ u32 i;
+
+ i2c->i2c_xfer_in_progress = true;
+ for (i = 0; i < num; i++) {
+ slaveaddr = i2c_8bit_addr_from_msg(&msgs[i]);
+
+ /*Send STOP if it is the Last of the transfer or
+ *if the I2C_M_STOP flag is set
+ */
+ if ((i == (num - 1)) || (msgs[i].flags & I2C_M_STOP))
+ i2c->flags |= I2C_FLAGS_STOP;
+ else
+ i2c->flags &= ~I2C_FLAGS_STOP;
+
+ /*When the command is a block read command*/
+ if (msgs[i].flags & I2C_M_RECV_LEN)
+ i2c->flags |= I2C_FLAGS_SMB_BLK_READ;
+ else
+ i2c->flags &= ~I2C_FLAGS_SMB_BLK_READ;
+
+ if (msgs[i].flags & I2C_M_RD)
+ retval = pci1xxxx_i2c_read(i2c, slaveaddr,
+ msgs[i].buf, msgs[i].len);
+ else
+ retval = pci1xxxx_i2c_write(i2c, slaveaddr,
+ msgs[i].buf, msgs[i].len);
+
+ if (retval < 0)
+ break;
+ }
+ i2c->i2c_xfer_in_progress = false;
+
+ if (retval < 0)
+ return retval;
+
+ return num;
+}
+
+/*
+ * We could have used I2C_FUNC_SMBUS_EMUL but that includes
+ * SMBUS_QUICK as well.We dnt support SMBUS_QUICK hence the
+ * need for a lengthy funcs callback
+ */
+
+static u32 pci1xxxx_i2c_get_funcs(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_PROTOCOL_MANGLING |
+ I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
+ I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE |
+ I2C_FUNC_SMBUS_READ_BYTE_DATA |
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA |
+ I2C_FUNC_SMBUS_WRITE_WORD_DATA |
+ I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_READ_BLOCK_DATA |
+ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm pci1xxxx_i2c_algo = {
+ .master_xfer = pci1xxxx_i2c_xfer,
+ .functionality = pci1xxxx_i2c_get_funcs,
+};
+
+static const struct i2c_adapter pci1xxxx_i2c_ops = {
+ .owner = THIS_MODULE,
+ .name = "Pci1xxxx I2C Adapter",
+ .algo = &pci1xxxx_i2c_algo,
+};
+
+static int pci1xxxx_i2c_suspend(struct device *dev)
+{
+ struct pci1xxxx_i2c *i2c = dev_get_drvdata(dev);
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u32 regval;
+
+ i2c_mark_adapter_suspended(&i2c->adap);
+
+ while ((i2c->i2c_xfer_in_progress))
+ msleep(20);
+
+ pci1xxxx_i2c_config_high_level_intr(i2c,
+ SMBALERT_WAKE_INTR_MASK,
+ true);
+
+ /*Enable the PERST_DIS bit to mask the PERST from
+ *resetting the core regs
+ */
+ regval = readl(i2c->i2c_base + SMBUS_RESET_REG);
+ regval |= PERI_SMBUS_D3_RESET_DIS;
+ writel(regval, i2c->i2c_base + SMBUS_RESET_REG);
+ /*Enable PCI wake in the PMCSR register*/
+ device_set_wakeup_enable(dev, TRUE);
+ pci_wake_from_d3(pdev, TRUE);
+
+ return 0;
+}
+
+static int pci1xxxx_i2c_resume(struct device *dev)
+{
+ struct pci1xxxx_i2c *i2c = dev_get_drvdata(dev);
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u32 regval;
+
+ /* Read the Interrupt register to know if the wakeup is due to the
+ * SMB_ALERT pin being pulled low. If that's the case then handle that.
+ * else this could be a wakeup due to system wide event
+ */
+
+ regval = readw(i2c->i2c_base + SMBUS_GEN_INT_STAT_REG_OFF);
+ writew(regval, i2c->i2c_base + SMBUS_GEN_INT_STAT_REG_OFF);
+ pci1xxxx_i2c_config_high_level_intr(i2c, SMBALERT_WAKE_INTR_MASK,
+ false);
+ regval = readl(i2c->i2c_base + SMBUS_RESET_REG);
+ regval &= ~PERI_SMBUS_D3_RESET_DIS;
+ writel(regval, i2c->i2c_base + SMBUS_RESET_REG);
+
+ i2c_mark_adapter_resumed(&i2c->adap);
+
+ pci_wake_from_d3(pdev, FALSE);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pci1xxxx_i2c_pm_ops, pci1xxxx_i2c_suspend,
+ pci1xxxx_i2c_resume);
+
+static void pci1xxxx_i2c_shutdown(struct pci1xxxx_i2c *i2c)
+{
+ pci1xxxx_i2c_config_padctrl(i2c, false);
+ pci1xxxx_i2c_configure_core_reg(i2c, false);
+}
+
+static int pci1xxxx_i2c_probe_pci(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct pci1xxxx_i2c *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ pci_set_drvdata(pdev, i2c);
+
+ i2c->i2c_xfer_in_progress = false;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ /* we are getting the base address of the SMB core. SMB core uses
+ * BAR0 and 32K is the size here pci_resource_len returns 32K by
+ * reading BAR0
+ */
+
+ ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev));
+ if (ret < 0)
+ return -ENOMEM;
+
+ i2c->i2c_base = pcim_iomap_table(pdev)[0];
+
+ init_completion(&i2c->i2c_xfer_done);
+
+ pci1xxxx_i2c_init(i2c);
+
+ dev_info(&pdev->dev, "i2c clock freq: %d\n", i2c->freq);
+
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0)
+ return ret;
+
+ /*Register the isr. we are not using any isr flags here.*/
+ ret = devm_request_irq(&pdev->dev, pci_irq_vector(pdev, 0),
+ pci1xxxx_i2c_isr, PCI1XXXX_IRQ_FLAGS,
+ pci_name(pdev), i2c);
+ if (ret)
+ goto err_free_region;
+
+ i2c->adap = pci1xxxx_i2c_ops;
+ i2c->adap.class = I2C_CLASS_SPD;
+ i2c->adap.dev.parent = &pdev->dev;
+
+ snprintf(i2c->adap.name, sizeof(i2c->adap.name),
+ "MCHP PCI1xxxx i2c adapter at %s", pci_name(pdev));
+
+ i2c_set_adapdata(&i2c->adap, i2c);
+
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret) {
+ dev_err(&pdev->dev, "i2c add adapter failed = %d\n", ret);
+ pci1xxxx_i2c_shutdown(i2c);
+ goto err_free_region;
+ }
+
+ return 0;
+
+err_free_region:
+ pci_free_irq_vectors(pdev);
+ return ret;
+}
+
+static void pci1xxxx_i2c_remove_pci(struct pci_dev *pdev)
+{
+ struct pci1xxxx_i2c *i2c = pci_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c->adap);
+ pci1xxxx_i2c_shutdown(i2c);
+}
+
+static const struct pci_device_id pci1xxxx_i2c_pci_id_table[] = {
+ { PCI_VDEVICE(EFAR, 0xA003) },
+ { PCI_VDEVICE(EFAR, 0xA013) },
+ { PCI_VDEVICE(EFAR, 0xA023) },
+ { PCI_VDEVICE(EFAR, 0xA033) },
+ { PCI_VDEVICE(EFAR, 0xA043) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, pci1xxxx_i2c_pci_id_table);
+
+static struct pci_driver pci1xxxx_i2c_pci_driver = {
+ .name = "i2c-mchp-pci1xxxx",
+ .id_table = pci1xxxx_i2c_pci_id_table,
+ .probe = pci1xxxx_i2c_probe_pci,
+ .remove = pci1xxxx_i2c_remove_pci,
+ .driver = {
+ .pm = &pci1xxxx_i2c_pm_ops,
+ },
+};
+module_pci_driver(pci1xxxx_i2c_pci_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tharun Kumar P<tharunkumar.pasumarthi@...rochip.com>");
+MODULE_AUTHOR("Kumaravel Thiagarajan <kumaravel.thiagarajan@...rochip.com>");
+MODULE_DESCRIPTION("Microchip Technology Inc. pci1xxxx I2C bus driver");
--
2.25.1
Powered by blists - more mailing lists