[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20220906102057.679839-1-tharunkumar.pasumarthi@microchip.com>
Date: Tue, 6 Sep 2022 15:50:57 +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 v3 i2c-master] i2c: microchip: pci1xxxx: Add driver for I2C host controller in multifunction 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 multifunction endpoint for peripherals which includes an I2C
host controller. The I2C function in the endpoint operates at 100KHz,
400KHz and 1 MHz and has buffer depth of 128 bytes.
This patch provides the I2C controller driver for the I2C function
of the switch.
Signed-off-by: Tharun Kumar P <tharunkumar.pasumarthi@...rochip.com>
---
V2 -> V3:
1. Replaced SIMPLE_DEV_PM_OPS with DEFINE_SIMPLE_DEV_PM_OPS
2. Used devm_add_action API to avoid mixing devm and non-devm APIs
---
RFC -> V2:
1. Removed pci_free_irq_vectors API in code since pcim_enable_device
is used
2. Added pci1xxxx_i2c_shutdown API in failure case of
pci_alloc_irq_vectors and devm_request_irq
3. Used devm variant of i2c_add_adapter
4. Resolved name collision and fixed styling issues in comments
---
MAINTAINERS | 8 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mchp-pci1xxxx.c | 1227 ++++++++++++++++++++++++
4 files changed, 1246 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..7e967d87dc04 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1244,6 +1244,16 @@ config I2C_PARPORT
This support is also available as a module. If so, the module
will be called i2c-parport.
+config I2C_PCI1XXXX
+ tristate "PCI1XXXX I2C Host Adapter"
+ depends on PCI
+ help
+ If you say yes to this option, support will be included for
+ Microchip PCI1XXXX's I2C interface.
+
+ This driver can also be built as a module. If so, the module will
+ be called i2c-mchp-pci1xxxx.
+
config I2C_ROBOTFUZZ_OSIF
tristate "RobotFuzz Open Source InterFace USB adapter"
depends on USB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 479f60e4ee3d..2372ed91e7ad 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -131,6 +131,7 @@ obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
+obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o
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
diff --git a/drivers/i2c/busses/i2c-mchp-pci1xxxx.c b/drivers/i2c/busses/i2c-mchp-pci1xxxx.c
new file mode 100644
index 000000000000..ef7a25499831
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mchp-pci1xxxx.c
@@ -0,0 +1,1227 @@
+// 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.
+ *
+ * Authors: 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 BIT(6)
+#define SMB_CORE_CTRL_FW_ACK BIT(4)
+
+#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 BIT(4)
+#define SMB_CORE_CMD_STOP BIT(2)
+#define SMB_CORE_CMD_START BIT(0)
+
+#define SMB_CORE_CMD_REG_OFF0 (SMBUS_MAST_CORE_ADDR_BASE + 0x0C)
+
+#define SMB_CORE_CMD_M_PROCEED BIT(1)
+#define SMB_CORE_CMD_M_RUN BIT(0)
+
+#define SMB_CORE_SR_HOLD_TIME_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x18)
+
+/*
+ * SR_HOLD_TIME_XK_TICKS field will indicate the number of ticks of the
+ * baud clock required to program 'Hold Time' at X KHz.
+ */
+#define SR_HOLD_TIME_100K_TICKS 133
+#define SR_HOLD_TIME_400K_TICKS 20
+#define SR_HOLD_TIME_1000K_TICKS 11
+
+#define SMB_CORE_COMPLETION_REG_OFF3 (SMBUS_MAST_CORE_ADDR_BASE + 0x23)
+
+#define COMPLETION_MDONE BIT(6)
+#define COMPLETION_IDLE BIT(5)
+#define COMPLETION_MNAKX BIT(0)
+
+#define SMB_CORE_IDLE_SCALING_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x24)
+
+/*
+ * FAIR_BUS_IDLE_MIN_XK_TICKS field will indicate the number of ticks of
+ * the baud clock required to program 'fair idle delay' at X KHz. Fair idle
+ * delay establishes the MCTP T(IDLE_DELAY) period.
+ */
+#define FAIR_BUS_IDLE_MIN_100K_TICKS 969
+#define FAIR_BUS_IDLE_MIN_400K_TICKS 157
+#define FAIR_BUS_IDLE_MIN_1000K_TICKS 157
+
+/*
+ * FAIR_IDLE_DELAY_XK_TICKS field will indicate the number of ticks of the
+ * baud clock required to satisfy the fairness protocol at X KHz.
+ */
+#define FAIR_IDLE_DELAY_100K_TICKS 1000
+#define FAIR_IDLE_DELAY_400K_TICKS 500
+#define FAIR_IDLE_DELAY_1000K_TICKS 500
+
+#define SMB_IDLE_SCALING_100K \
+ ((FAIR_IDLE_DELAY_100K_TICKS << 16) | FAIR_BUS_IDLE_MIN_100K_TICKS)
+#define SMB_IDLE_SCALING_400K \
+ ((FAIR_IDLE_DELAY_400K_TICKS << 16) | FAIR_BUS_IDLE_MIN_400K_TICKS)
+#define SMB_IDLE_SCALING_1000K \
+ ((FAIR_IDLE_DELAY_1000K_TICKS << 16) | FAIR_BUS_IDLE_MIN_1000K_TICKS)
+
+#define SMB_CORE_CONFIG_REG3 (SMBUS_MAST_CORE_ADDR_BASE + 0x2B)
+
+#define SMB_CONFIG3_ENMI BIT(6)
+#define SMB_CONFIG3_ENIDI BIT(5)
+
+#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 BIT(7)
+#define SMB_CONFIG1_ENAB BIT(2)
+#define SMB_CONFIG1_RESET BIT(1)
+#define SMB_CONFIG1_FEN BIT(0)
+
+#define SMB_CORE_BUS_CLK_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x2C)
+
+/*
+ * BUS_CLK_XK_LOW_PERIOD_TICKS field defines the number of I2C Baud Clock
+ * periods that make up the low phase of the I2C/SMBus bus clock at X KHz.
+ */
+#define BUS_CLK_100K_LOW_PERIOD_TICKS 156
+#define BUS_CLK_400K_LOW_PERIOD_TICKS 41
+#define BUS_CLK_1000K_LOW_PERIOD_TICKS 15
+
+/*
+ * BUS_CLK_XK_HIGH_PERIOD_TICKS field defines the number of I2C Baud Clock
+ * periods that make up the high phase of the I2C/SMBus bus clock at X KHz.
+ */
+#define BUS_CLK_100K_HIGH_PERIOD_TICKS 154
+#define BUS_CLK_400K_HIGH_PERIOD_TICKS 35
+#define BUS_CLK_1000K_HIGH_PERIOD_TICKS 14
+
+#define BUS_CLK_100K \
+ ((BUS_CLK_100K_HIGH_PERIOD_TICKS << 8) | BUS_CLK_100K_LOW_PERIOD_TICKS)
+#define BUS_CLK_400K \
+ ((BUS_CLK_400K_HIGH_PERIOD_TICKS << 8) | BUS_CLK_400K_LOW_PERIOD_TICKS)
+#define BUS_CLK_1000K \
+ ((BUS_CLK_1000K_HIGH_PERIOD_TICKS << 8) | BUS_CLK_1000K_LOW_PERIOD_TICKS)
+
+#define SMB_CORE_CLK_SYNC_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x3C)
+
+/*
+ * CLK_SYNC_XK defines the number of clock cycles to sync up to the external
+ * clock before comparing the internal and external clocks for clock stretching
+ * at X KHz.
+ */
+#define CLK_SYNC_100K 4
+#define CLK_SYNC_400K 4
+#define CLK_SYNC_1000K 4
+
+#define SMB_CORE_DATA_TIMING_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x40)
+
+/*
+ *
+ * FIRST_START_HOLD_XK_TICKS will indicate the number of ticks of the baud
+ * clock required to program 'FIRST_START_HOLD' timer at X KHz. This timer
+ * determines the SCLK hold time following SDAT driven low during the first
+ * START bit in a transfer.
+ */
+#define FIRST_START_HOLD_100K_TICKS 22
+#define FIRST_START_HOLD_400K_TICKS 16
+#define FIRST_START_HOLD_1000K_TICKS 6
+
+/*
+ * STOP_SETUP_XK_TICKS will indicate the number of ticks of the baud clock
+ * required to program 'STOP_SETUP' timer at X KHz. This timer determines the
+ * SDAT setup time from the rising edge of SCLK for a STOP condition.
+ */
+#define STOP_SETUP_100K_TICKS 157
+#define STOP_SETUP_400K_TICKS 20
+#define STOP_SETUP_1000K_TICKS 12
+
+/*
+ * RESTART_SETUP_XK_TICKS will indicate the number of ticks of the baud clock
+ * required to program 'RESTART_SETUP' timer at X KHz. This timer determines the
+ * SDAT setup time from the rising edge of SCLK for a repeated START condition.
+ */
+#define RESTART_SETUP_100K_TICKS 157
+#define RESTART_SETUP_400K_TICKS 20
+#define RESTART_SETUP_1000K_TICKS 12
+
+/*
+ * DATA_HOLD_XK_TICKS will indicate the number of ticks of the baud clock
+ * required to program 'DATA_HOLD' timer at X KHz. This timer determines the
+ * SDAT hold time following SCLK driven low.
+ */
+#define DATA_HOLD_100K_TICKS 2
+#define DATA_HOLD_400K_TICKS 2
+#define DATA_HOLD_1000K_TICKS 2
+
+#define DATA_TIMING_100K \
+ ((FIRST_START_HOLD_100K_TICKS << 24) | (STOP_SETUP_100K_TICKS << 16) | \
+ (RESTART_SETUP_100K_TICKS << 8) | DATA_HOLD_100K_TICKS)
+#define DATA_TIMING_400K \
+ ((FIRST_START_HOLD_400K_TICKS << 24) | (STOP_SETUP_400K_TICKS << 16) | \
+ (RESTART_SETUP_400K_TICKS << 8) | DATA_HOLD_400K_TICKS)
+#define DATA_TIMING_1000K \
+ ((FIRST_START_HOLD_1000K_TICKS << 24) | (STOP_SETUP_1000K_TICKS << 16) | \
+ (RESTART_SETUP_1000K_TICKS << 8) | DATA_HOLD_1000K_TICKS)
+
+#define SMB_CORE_TO_SCALING_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x44)
+
+/*
+ * BUS_IDLE_MIN_XK_TICKS defines Bus Idle Minimum Time.
+ * Bus Idle Minimum time = BUS_IDLE_MIN[7:0] x Baud_Clock_Period x
+ * (BUS_IDLE_MIN_XK_TICKS[7] ? 4,1)
+ */
+#define BUS_IDLE_MIN_100K_TICKS 167
+#define BUS_IDLE_MIN_400K_TICKS 139
+#define BUS_IDLE_MIN_1000K_TICKS 133
+
+/*
+ * CTRL_CUM_TIME_OUT_XK_TICKS defines SMBus Controller Cumulative Time-Out.
+ * SMBus Controller Cumulative Time-Out duration =
+ * CTRL_CUM_TIME_OUT_XK_TICKS[7:0] x Baud_Clock_Period x 2048
+ */
+#define CTRL_CUM_TIME_OUT_100K_TICKS 159
+#define CTRL_CUM_TIME_OUT_400K_TICKS 159
+#define CTRL_CUM_TIME_OUT_1000K_TICKS 159
+
+/*
+ * TARGET_CUM_TIME_OUT_XK_TICKS defines SMBus Target Cumulative Time-Out duration.
+ * SMBus Target Cumulative Time-Out duration = TARGET_CUM_TIME_OUT_XK_TICKS[7:0] x
+ * Baud_Clock_Period x 4096
+ */
+#define TARGET_CUM_TIME_OUT_100K_TICKS 199
+#define TARGET_CUM_TIME_OUT_400K_TICKS 199
+#define TARGET_CUM_TIME_OUT_1000K_TICKS 199
+
+/*
+ * CLOCK_HIGH_TIME_OUT_XK defines Clock High time out period.
+ * Clock High time out period = CLOCK_HIGH_TIME_OUT_XK[7:0] x Baud_Clock_Period x 8
+ */
+#define CLOCK_HIGH_TIME_OUT_100K_TICKS 204
+#define CLOCK_HIGH_TIME_OUT_400K_TICKS 204
+#define CLOCK_HIGH_TIME_OUT_1000K_TICKS 204
+
+#define TO_SCALING_100K \
+ ((BUS_IDLE_MIN_100K_TICKS << 24) | (CTRL_CUM_TIME_OUT_100K_TICKS << 16) | \
+ (TARGET_CUM_TIME_OUT_100K_TICKS << 8) | CLOCK_HIGH_TIME_OUT_100K_TICKS)
+#define TO_SCALING_400K \
+ ((BUS_IDLE_MIN_400K_TICKS << 24) | (CTRL_CUM_TIME_OUT_400K_TICKS << 16) | \
+ (TARGET_CUM_TIME_OUT_400K_TICKS << 8) | CLOCK_HIGH_TIME_OUT_400K_TICKS)
+#define TO_SCALING_1000K \
+ ((BUS_IDLE_MIN_1000K_TICKS << 24) | (CTRL_CUM_TIME_OUT_1000K_TICKS << 16) | \
+ (TARGET_CUM_TIME_OUT_1000K_TICKS << 8) | CLOCK_HIGH_TIME_OUT_1000K_TICKS)
+
+#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 BIT(4)
+#define I2C_PULL_UP_EN BIT(3)
+#define I2C_PULL_DOWN_EN BIT(2)
+#define I2C_INPUT_EN BIT(1)
+#define I2C_OUTPUT_EN BIT(0)
+
+#define SMBUS_CONTROL_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x200)
+
+#define CTL_RESET_COUNTERS BIT(3)
+#define CTL_TRANSFER_DIR BIT(2)
+#define CTL_HOST_FIFO_ENTRY BIT(1)
+#define CTL_RUN BIT(0)
+
+#define I2C_DIRN_WRITE 0
+#define I2C_DIRN_READ 1
+
+#define SMBUS_STATUS_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x204)
+
+#define STA_DMA_TERM BIT(7)
+#define STA_DMA_REQ BIT(6)
+#define STA_THRESHOLD BIT(2)
+#define STA_BUF_FULL BIT(1)
+#define STA_BUF_EMPTY BIT(0)
+
+#define SMBUS_INTR_STAT_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x208)
+
+#define INTR_STAT_DMA_TERM BIT(7)
+#define INTR_STAT_THRESHOLD BIT(2)
+#define INTR_STAT_BUF_FULL BIT(1)
+#define INTR_STAT_BUF_EMPTY BIT(0)
+
+#define SMBUS_INTR_MSK_REG_OFF (SMBUS_MAST_CORE_ADDR_BASE + 0x20C)
+
+#define INTR_MSK_DMA_TERM BIT(7)
+#define INTR_MSK_THRESHOLD BIT(2)
+#define INTR_MSK_BUF_FULL BIT(1)
+#define INTR_MSK_BUF_EMPTY BIT(0)
+
+#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 BIT(0)
+
+#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 BIT(10)
+#define I2C_BUF_MSTR_INTR_MASK BIT(9)
+#define I2C_INTR_MASK BIT(8)
+#define SMBALERT_WAKE_INTR_MASK BIT(2)
+#define I2C_BUF_MSTR_WAKE_INTR_MASK BIT(1)
+#define I2C_WAKE_INTR_MASK BIT(0)
+
+#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 BIT(16)
+
+#define SMBUS_MST_BUF (SMBUS_MAST_CORE_ADDR_BASE + 0x280)
+
+#define SMBUS_BUF_MAX_SIZE 0x80
+
+#define I2C_FLAGS_DIRECT_MODE BIT(7)
+#define I2C_FLAGS_POLLING_MODE BIT(6)
+#define I2C_FLAGS_STOP BIT(5)
+#define I2C_FLAGS_SMB_BLK_READ BIT(4)
+
+#define PCI1XXXX_I2C_TIMEOUT_MS 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 {
+ 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(struct pci1xxxx_i2c *i2c)
+{
+ void __iomem *bp;
+ u8 data;
+
+ bp = i2c->i2c_base;
+ writel(SMBUS_PERI_LOCK, bp + SMB_GPR_LOCK_REG);
+ data = readl(bp + SMB_GPR_LOCK_REG);
+ if (data != SMBUS_PERI_LOCK)
+ return -EPERM;
+
+ return 0;
+}
+
+static int release_sys_lock(struct pci1xxxx_i2c *i2c)
+{
+ void __iomem *bp;
+ u8 data;
+
+ bp = i2c->i2c_base;
+ data = readl(bp + SMB_GPR_LOCK_REG);
+ if (data != SMBUS_PERI_LOCK)
+ return 0;
+
+ writel(0, bp + SMB_GPR_LOCK_REG);
+ data = readl(bp + SMB_GPR_LOCK_REG);
+ 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)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMBALERT_MST_PAD_CTRL_REG_OFF);
+
+ if (enable)
+ regval |= SMBALERT_MST_PU;
+ else
+ regval &= ~SMBALERT_MST_PU;
+
+ writeb(regval, bp + SMBALERT_MST_PAD_CTRL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_send_start_stop(struct pci1xxxx_i2c *i2c, bool start)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMB_CORE_CMD_REG_OFF1);
+
+ if (start)
+ regval |= SMB_CORE_CMD_START;
+ else
+ regval |= SMB_CORE_CMD_STOP;
+
+ writeb(regval, bp + SMB_CORE_CMD_REG_OFF1);
+}
+
+/*
+ * When accessing the core control reg, we should not do a read modified write
+ * as they are write '1' to clear bits. Instead we need to 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)
+{
+ void __iomem *bp;
+
+ bp = i2c->i2c_base;
+ if (slaveaddr) {
+ writeb(slaveaddr, bp + SMBUS_MST_BUF);
+ if (buf)
+ memcpy_toio(bp + SMBUS_MST_BUF + 1, buf, transferlen);
+ } else {
+ if (buf)
+ memcpy_toio(bp + 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. Instead we need to 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)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMBUS_CONTROL_REG_OFF);
+ regval |= CTL_RESET_COUNTERS;
+ writeb(regval, bp + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_transfer_dir(struct pci1xxxx_i2c *i2c, u8 direction)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMBUS_CONTROL_REG_OFF);
+ if (direction == I2C_DIRN_WRITE)
+ regval &= ~CTL_TRANSFER_DIR;
+ else
+ regval |= CTL_TRANSFER_DIR;
+
+ writeb(regval, bp + 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)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMBUS_CONTROL_REG_OFF);
+ regval |= CTL_RUN;
+ writeb(regval, bp + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_mrun_proceed(struct pci1xxxx_i2c *i2c)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMB_CORE_CMD_REG_OFF0);
+ regval |= SMB_CORE_CMD_M_RUN;
+ regval |= SMB_CORE_CMD_M_PROCEED;
+ writeb(regval, bp + 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)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMB_CORE_CONFIG_REG1);
+ if (enable)
+ regval |= SMB_CONFIG1_ASR;
+ else
+ regval &= ~SMB_CONFIG1_ASR;
+ writeb(regval, bp + 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;
+ void __iomem *bp;
+ u16 reg1;
+ u8 reg3;
+
+ bp = i2c->i2c_base;
+
+ /*
+ * Read the SMBus interrupt status register to see if the
+ * DMA_TERM interrupt has caused this callback.
+ */
+ reg1 = readw(bp + SMBUS_GEN_INT_STAT_REG_OFF);
+
+ if (reg1 & I2C_BUF_MSTR_INTR_MASK) {
+ reg3 = readb(bp + 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, bp + 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;
+ 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)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMB_CORE_CMD_REG_OFF1);
+ if (enable)
+ regval |= SMB_CORE_CMD_READM;
+ else
+ regval &= ~SMB_CORE_CMD_READM;
+
+ writeb(regval, bp + 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)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMBUS_INTR_MSK_REG_OFF);
+ if (enable)
+ regval &= ~intr_msk;
+ else
+ regval |= intr_msk;
+
+ writeb(regval, bp + SMBUS_INTR_MSK_REG_OFF);
+}
+
+static void pci1xxxx_i2c_config_padctrl(struct pci1xxxx_i2c *i2c, bool enable)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + 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, bp + I2C_SCL_PAD_CTRL_REG_OFF);
+
+ regval = readb(bp + 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, bp + I2C_SDA_PAD_CTRL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_set_mode(struct pci1xxxx_i2c *i2c)
+{
+ void __iomem *bp;
+ u8 regval;
+
+ bp = i2c->i2c_base;
+ regval = readb(bp + SMBUS_CONTROL_REG_OFF);
+ if (i2c->flags & I2C_FLAGS_DIRECT_MODE)
+ regval &= ~CTL_HOST_FIFO_ENTRY;
+ else
+ regval |= CTL_HOST_FIFO_ENTRY;
+
+ writeb(regval, bp + SMBUS_CONTROL_REG_OFF);
+}
+
+static void pci1xxxx_i2c_config_high_level_intr(struct pci1xxxx_i2c *i2c,
+ u16 intr_msk, bool enable)
+{
+ void __iomem *bp;
+ u16 regval;
+
+ bp = i2c->i2c_base;
+ regval = readw(bp + SMBUS_GEN_INT_MASK_REG_OFF);
+ if (enable)
+ regval &= ~intr_msk;
+ else
+ regval |= intr_msk;
+ writew(regval, bp + SMBUS_GEN_INT_MASK_REG_OFF);
+}
+
+static void pci1xxxx_i2c_configure_core_reg(struct pci1xxxx_i2c *i2c, bool enable)
+{
+ void __iomem *bp;
+ u8 reg1;
+ u8 reg3;
+
+ bp = i2c->i2c_base;
+ reg1 = readb(bp + SMB_CORE_CONFIG_REG1);
+ reg3 = readb(bp + 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, bp + SMB_CORE_CONFIG_REG1);
+ writeb(reg3, bp + SMB_CORE_CONFIG_REG3);
+}
+
+static void pci1xxxx_i2c_set_freq(struct pci1xxxx_i2c *i2c)
+{
+ void __iomem *bp;
+
+ bp = i2c->i2c_base;
+ switch (i2c->freq) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ writeb(SR_HOLD_TIME_100K_TICKS, bp + SMB_CORE_SR_HOLD_TIME_REG_OFF);
+ writel(SMB_IDLE_SCALING_100K, bp + SMB_CORE_IDLE_SCALING_REG_OFF);
+ writew(BUS_CLK_100K, bp + SMB_CORE_BUS_CLK_REG_OFF);
+ writel(CLK_SYNC_100K, bp + SMB_CORE_CLK_SYNC_REG_OFF);
+ writel(DATA_TIMING_100K, bp + SMB_CORE_DATA_TIMING_REG_OFF);
+ writel(TO_SCALING_100K, bp + SMB_CORE_TO_SCALING_REG_OFF);
+ break;
+
+ case I2C_MAX_FAST_MODE_PLUS_FREQ:
+ writeb(SR_HOLD_TIME_1000K_TICKS, bp + SMB_CORE_SR_HOLD_TIME_REG_OFF);
+ writel(SMB_IDLE_SCALING_1000K, bp + SMB_CORE_IDLE_SCALING_REG_OFF);
+ writew(BUS_CLK_1000K, bp + SMB_CORE_BUS_CLK_REG_OFF);
+ writel(CLK_SYNC_1000K, bp + SMB_CORE_CLK_SYNC_REG_OFF);
+ writel(DATA_TIMING_1000K, bp + SMB_CORE_DATA_TIMING_REG_OFF);
+ writel(TO_SCALING_1000K, bp + SMB_CORE_TO_SCALING_REG_OFF);
+ break;
+
+ case I2C_MAX_FAST_MODE_FREQ:
+ default:
+ writeb(SR_HOLD_TIME_400K_TICKS, bp + SMB_CORE_SR_HOLD_TIME_REG_OFF);
+ writel(SMB_IDLE_SCALING_400K, bp + SMB_CORE_IDLE_SCALING_REG_OFF);
+ writew(BUS_CLK_400K, bp + SMB_CORE_BUS_CLK_REG_OFF);
+ writel(CLK_SYNC_400K, bp + SMB_CORE_CLK_SYNC_REG_OFF);
+ writel(DATA_TIMING_400K, bp + SMB_CORE_DATA_TIMING_REG_OFF);
+ writel(TO_SCALING_400K, bp + SMB_CORE_TO_SCALING_REG_OFF);
+ break;
+ }
+}
+
+static void pci1xxxx_i2c_init(struct pci1xxxx_i2c *i2c)
+{
+ void __iomem *bp;
+ u8 regval;
+ u8 ret;
+
+ bp = i2c->i2c_base;
+ ret = set_sys_lock(i2c);
+ if (ret != -EPERM) {
+ regval = readl(bp + SMB_GPR_REG);
+ release_sys_lock(i2c);
+ } 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, bp + 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;
+ void __iomem *bp;
+ u8 transferlen;
+ int retval = 0;
+ u8 read_count;
+ u32 regval;
+ u16 count;
+
+ bp = i2c->i2c_base;
+
+ /* 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 more than 128 bytes. Our Core is
+ * capable of only sending 128 at a time.
+ * As far 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, bp + SMB_CORE_CMD_REG_OFF1);
+ remainingbytes = total_len - count;
+ transferlen = min_t(u16, remainingbytes, (u16)SMBUS_BUF_MAX_SIZE);
+
+ /*
+ * Send STOP bit for the last chunk in the transaction.
+ * For I2C read transaction of more than BUF_SIZE, NACK should
+ * only be sent for the last read.
+ * Hence a bit FW_ACK is set for all the read chunks except for
+ * the last chunk. For the last chunk NACK should be sent and
+ * FW_ACK is cleared 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);
+ }
+
+ /* Send START bit for the first transaction. */
+ if (count == 0) {
+ pci1xxxx_i2c_set_transfer_dir(i2c, I2C_DIRN_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_DIRN_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_MS));
+ 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(bp + SMB_CORE_COMPLETION_REG_OFF3);
+
+ /* Slave did not respond. */
+ if (regval & COMPLETION_MNAKX) {
+ writeb(COMPLETION_MNAKX, bp + SMB_CORE_COMPLETION_REG_OFF3);
+ retval = -ETIMEDOUT;
+ goto cleanup;
+ }
+
+ if (i2c->flags & I2C_FLAGS_SMB_BLK_READ) {
+ buf[0] = readb(bp + SMBUS_MST_BUF);
+ read_count = buf[0];
+ memcpy_fromio(&buf[1], bp + SMBUS_MST_BUF + 1, read_count);
+ } else {
+ memcpy_fromio(&buf[count], bp + 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;
+ void __iomem *bp;
+ u8 transferlen;
+ int retval = 0;
+ u32 regval;
+ u16 count;
+
+ bp = i2c->i2c_base;
+
+ /* 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_DIRN_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 more than 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, bp + 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_t(u16, (u16)SMBUS_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_t(u16, (u16)SMBUS_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.
+ */
+ time_left = wait_for_completion_timeout(&i2c->i2c_xfer_done,
+ msecs_to_jiffies(PCI1XXXX_I2C_TIMEOUT_MS));
+ if (time_left == 0) {
+ /* Reset the I2C core to release the bus lock. */
+ pci1xxxx_i2c_init(i2c);
+ retval = -ETIMEDOUT;
+ goto cleanup;
+ }
+
+ regval = readb(bp + 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 the STOP bit if the transfer is the final one 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;
+
+ 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;
+}
+
+/*
+ * List of supported functions by the driver.
+ */
+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);
+ void __iomem *bp;
+ u32 regval;
+
+ bp = i2c->i2c_base;
+ i2c_mark_adapter_suspended(&i2c->adap);
+
+ /*
+ * If the system is put into 'suspend' state when the I2C transfer is in
+ * progress, wait until the transfer completes.
+ */
+ 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
+ * registers.
+ */
+ regval = readl(bp + SMBUS_RESET_REG);
+ regval |= PERI_SMBUS_D3_RESET_DIS;
+ writel(regval, bp + 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);
+ void __iomem *bp;
+ u32 regval;
+
+ bp = i2c->i2c_base;
+ regval = readw(bp + SMBUS_GEN_INT_STAT_REG_OFF);
+ writew(regval, bp + SMBUS_GEN_INT_STAT_REG_OFF);
+ pci1xxxx_i2c_config_high_level_intr(i2c, SMBALERT_WAKE_INTR_MASK, false);
+ regval = readl(bp + SMBUS_RESET_REG);
+ regval &= ~PERI_SMBUS_D3_RESET_DIS;
+ writel(regval, bp + SMBUS_RESET_REG);
+ i2c_mark_adapter_resumed(&i2c->adap);
+ pci_wake_from_d3(pdev, false);
+ return 0;
+}
+
+static DEFINE_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;
+ struct device *dev;
+ int ret;
+
+ dev = &pdev->dev;
+ i2c = devm_kzalloc(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 size is 32K.
+ */
+ 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);
+
+ ret = devm_add_action(dev, (void (*)(void *))pci1xxxx_i2c_shutdown, i2c);
+ if (ret)
+ return ret;
+
+ 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(dev, pci_irq_vector(pdev, 0), pci1xxxx_i2c_isr,
+ PCI1XXXX_IRQ_FLAGS, pci_name(pdev), i2c);
+ if (ret)
+ return ret;
+
+ i2c->adap = pci1xxxx_i2c_ops;
+ i2c->adap.class = I2C_CLASS_SPD;
+ i2c->adap.dev.parent = 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 = devm_i2c_add_adapter(dev, &i2c->adap);
+ if (ret)
+ return dev_err_probe(dev, ret, "i2c add adapter failed\n");
+
+ return 0;
+}
+
+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,
+ .driver = {
+ .pm = pm_sleep_ptr(&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