]> git.baikalelectronics.ru Git - uboot.git/commitdiff
i2c: octeon_i2c: Add I2C controller driver for Octeon
authorSuneel Garapati <sgarapati@marvell.com>
Tue, 26 May 2020 12:13:07 +0000 (14:13 +0200)
committerHeiko Schocher <hs@denx.de>
Thu, 9 Jul 2020 04:02:44 +0000 (06:02 +0200)
Add support for I2C controllers found on Octeon II/III and Octeon TX
TX2 SoC platforms.

Signed-off-by: Aaron Williams <awilliams@marvell.com>
Signed-off-by: Suneel Garapati <sgarapati@marvell.com>
Signed-off-by: Stefan Roese <sr@denx.de>
Cc: Heiko Schocher <hs@denx.de>
Cc: Simon Glass <sjg@chromium.org>
Cc: Daniel Schwierzeck <daniel.schwierzeck@gmail.com>
Cc: Aaron Williams <awilliams@marvell.com>
Cc: Chandrakala Chavva <cchavva@marvell.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Heiko Schocher <hs@denx.de>
Reviewed-by: Rayagonda Kokatanur <rayagonda.kokatanur@broadcom.com>
doc/device-tree-bindings/i2c/octeon-i2c.txt [new file with mode: 0644]
drivers/i2c/Kconfig
drivers/i2c/Makefile
drivers/i2c/octeon_i2c.c [new file with mode: 0644]

diff --git a/doc/device-tree-bindings/i2c/octeon-i2c.txt b/doc/device-tree-bindings/i2c/octeon-i2c.txt
new file mode 100644 (file)
index 0000000..9c1908e
--- /dev/null
@@ -0,0 +1,24 @@
+* I2C controller embedded in Marvell Octeon platforms
+
+Required properties :
+- compatible : Must be "cavium,octeon-7890-twsi" or a compatible string
+- reg : Offset and length of the register set for the device
+- clocks: Must contain the input clock of the I2C instance
+- #address-cells = <1>;
+- #size-cells = <0>;
+
+Optional properties :
+- clock-frequency : Desired I2C bus clock frequency in Hz. If not specified,
+  the default 100 kHz frequency will be used. As only Normal, Fast and Fast+
+  modes are implemented, possible values are 100000, 400000 and 1000000.
+
+Example :
+
+       i2c0: i2c@1180000001000 {
+               #address-cells = <1>;
+               #size-cells = <0>;
+               compatible = "cavium,octeon-7890-twsi";
+               reg = <0x11800 0x00001000 0x0 0x200>;
+               clock-frequency = <100000>;
+               clocks = <&sclk>;
+       };
index f8b18de8f3b0034783404667e2ae01310fa9094c..363b899e9cdb256c1be726e11a900785f287c520 100644 (file)
@@ -374,6 +374,16 @@ config SYS_I2C_SANDBOX
          bus. Devices can be attached to the bus using the device tree
          which specifies the driver to use.  See sandbox.dts as an example.
 
+config SYS_I2C_OCTEON
+       bool "Octeon II/III/TX/TX2 I2C driver"
+       depends on (ARCH_OCTEON || ARCH_OCTEONTX || ARCH_OCTEONTX2) && DM_I2C
+       default y
+       help
+         Add support for the Marvell Octeon I2C driver. This is used with
+         various Octeon parts such as Octeon II/III and OcteonTX/TX2. All
+         chips have several I2C ports and all are provided, controlled by
+         the device tree.
+
 config SYS_I2C_S3C24X0
        bool "Samsung I2C driver"
        depends on ARCH_EXYNOS4 && DM_I2C
index 62935b7ebcb51dbab03da6d3726032be20314987..2b58aae892f3c22ad0f694a90f347b767f5a418c 100644 (file)
@@ -27,6 +27,7 @@ obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o
 obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o
 obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o
 obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o
+obj-$(CONFIG_SYS_I2C_OCTEON) += octeon_i2c.o
 obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o
 obj-$(CONFIG_SYS_I2C_RCAR_I2C) += rcar_i2c.o
 obj-$(CONFIG_SYS_I2C_RCAR_IIC) += rcar_iic.o
diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c
new file mode 100644 (file)
index 0000000..c11d6ff
--- /dev/null
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier:    GPL-2.0
+/*
+ * Copyright (C) 2018 Marvell International Ltd.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <i2c.h>
+#include <pci_ids.h>
+#include <asm/io.h>
+#include <linux/bitfield.h>
+#include <linux/compat.h>
+#include <linux/delay.h>
+
+#define TWSI_SW_TWSI           0x00
+#define TWSI_TWSI_SW           0x08
+#define TWSI_INT               0x10
+#define TWSI_SW_TWSI_EXT       0x18
+
+#define TWSI_SW_DATA_MASK      GENMASK_ULL(31, 0)
+#define TWSI_SW_EOP_IA_MASK    GENMASK_ULL(34, 32)
+#define TWSI_SW_IA_MASK                GENMASK_ULL(39, 35)
+#define TWSI_SW_ADDR_MASK      GENMASK_ULL(49, 40)
+#define TWSI_SW_SCR_MASK       GENMASK_ULL(51, 50)
+#define TWSI_SW_SIZE_MASK      GENMASK_ULL(54, 52)
+#define TWSI_SW_SOVR           BIT_ULL(55)
+#define TWSI_SW_R              BIT_ULL(56)
+#define TWSI_SW_OP_MASK                GENMASK_ULL(60, 57)
+#define TWSI_SW_EIA            GENMASK_ULL(61)
+#define TWSI_SW_SLONLY         BIT_ULL(62)
+#define TWSI_SW_V              BIT_ULL(63)
+
+#define TWSI_INT_SDA_OVR       BIT_ULL(8)
+#define TWSI_INT_SCL_OVR       BIT_ULL(9)
+#define TWSI_INT_SDA           BIT_ULL(10)
+#define TWSI_INT_SCL           BIT_ULL(11)
+
+enum {
+       TWSI_OP_WRITE   = 0,
+       TWSI_OP_READ    = 1,
+};
+
+enum {
+       TWSI_EOP_SLAVE_ADDR = 0,
+       TWSI_EOP_CLK_CTL = 3,
+       TWSI_SW_EOP_IA   = 6,
+};
+
+enum {
+       TWSI_SLAVEADD     = 0,
+       TWSI_DATA         = 1,
+       TWSI_CTL          = 2,
+       TWSI_CLKCTL       = 3,
+       TWSI_STAT         = 3,
+       TWSI_SLAVEADD_EXT = 4,
+       TWSI_RST          = 7,
+};
+
+enum {
+       TWSI_CTL_AAK    = BIT(2),
+       TWSI_CTL_IFLG   = BIT(3),
+       TWSI_CTL_STP    = BIT(4),
+       TWSI_CTL_STA    = BIT(5),
+       TWSI_CTL_ENAB   = BIT(6),
+       TWSI_CTL_CE     = BIT(7),
+};
+
+/*
+ * Internal errors. When debugging is enabled, the driver will report the
+ * error number and the user / developer can check the table below for the
+ * detailed error description.
+ */
+enum {
+       /** Bus error */
+       TWSI_STAT_BUS_ERROR             = 0x00,
+       /** Start condition transmitted */
+       TWSI_STAT_START                 = 0x08,
+       /** Repeat start condition transmitted */
+       TWSI_STAT_RSTART                = 0x10,
+       /** Address + write bit transmitted, ACK received */
+       TWSI_STAT_TXADDR_ACK            = 0x18,
+       /** Address + write bit transmitted, /ACK received */
+       TWSI_STAT_TXADDR_NAK            = 0x20,
+       /** Data byte transmitted in master mode, ACK received */
+       TWSI_STAT_TXDATA_ACK            = 0x28,
+       /** Data byte transmitted in master mode, ACK received */
+       TWSI_STAT_TXDATA_NAK            = 0x30,
+       /** Arbitration lost in address or data byte */
+       TWSI_STAT_TX_ARB_LOST           = 0x38,
+       /** Address + read bit transmitted, ACK received */
+       TWSI_STAT_RXADDR_ACK            = 0x40,
+       /** Address + read bit transmitted, /ACK received */
+       TWSI_STAT_RXADDR_NAK            = 0x48,
+       /** Data byte received in master mode, ACK transmitted */
+       TWSI_STAT_RXDATA_ACK_SENT       = 0x50,
+       /** Data byte received, NACK transmitted */
+       TWSI_STAT_RXDATA_NAK_SENT       = 0x58,
+       /** Slave address received, sent ACK */
+       TWSI_STAT_SLAVE_RXADDR_ACK      = 0x60,
+       /**
+        * Arbitration lost in address as master, slave address + write bit
+        * received, ACK transmitted
+        */
+       TWSI_STAT_TX_ACK_ARB_LOST       = 0x68,
+       /** General call address received, ACK transmitted */
+       TWSI_STAT_RX_GEN_ADDR_ACK       = 0x70,
+       /**
+        * Arbitration lost in address as master, general call address
+        * received, ACK transmitted
+        */
+       TWSI_STAT_RX_GEN_ADDR_ARB_LOST  = 0x78,
+       /** Data byte received after slave address received, ACK transmitted */
+       TWSI_STAT_SLAVE_RXDATA_ACK      = 0x80,
+       /** Data byte received after slave address received, /ACK transmitted */
+       TWSI_STAT_SLAVE_RXDATA_NAK      = 0x88,
+       /**
+        * Data byte received after general call address received, ACK
+        * transmitted
+        */
+       TWSI_STAT_GEN_RXADDR_ACK        = 0x90,
+       /**
+        * Data byte received after general call address received, /ACK
+        * transmitted
+        */
+       TWSI_STAT_GEN_RXADDR_NAK        = 0x98,
+       /** STOP or repeated START condition received in slave mode */
+       TWSI_STAT_STOP_MULTI_START      = 0xa0,
+       /** Slave address + read bit received, ACK transmitted */
+       TWSI_STAT_SLAVE_RXADDR2_ACK     = 0xa8,
+       /**
+        * Arbitration lost in address as master, slave address + read bit
+        * received, ACK transmitted
+        */
+       TWSI_STAT_RXDATA_ACK_ARB_LOST   = 0xb0,
+       /** Data byte transmitted in slave mode, ACK received */
+       TWSI_STAT_SLAVE_TXDATA_ACK      = 0xb8,
+       /** Data byte transmitted in slave mode, /ACK received */
+       TWSI_STAT_SLAVE_TXDATA_NAK      = 0xc0,
+       /** Last byte transmitted in slave mode, ACK received */
+       TWSI_STAT_SLAVE_TXDATA_END_ACK  = 0xc8,
+       /** Second address byte + write bit transmitted, ACK received */
+       TWSI_STAT_TXADDR2DATA_ACK       = 0xd0,
+       /** Second address byte + write bit transmitted, /ACK received */
+       TWSI_STAT_TXADDR2DATA_NAK       = 0xd8,
+       /** No relevant status information */
+       TWSI_STAT_IDLE                  = 0xf8
+};
+
+#define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR       0x77
+
+enum {
+       PROBE_PCI = 0,          /* PCI based probing */
+       PROBE_DT,               /* DT based probing */
+};
+
+enum {
+       CLK_METHOD_OCTEON = 0,
+       CLK_METHOD_OCTEONTX2,
+};
+
+/**
+ * struct octeon_i2c_data - SoC specific data of this driver
+ *
+ * @probe:     Probing of this SoC (DT vs PCI)
+ * @reg_offs:  Register offset
+ * @thp:       THP define for divider calculation
+ * @clk_method:        Clock calculation method
+ */
+struct octeon_i2c_data {
+       int probe;
+       u32 reg_offs;
+       int thp;
+       int clk_method;
+};
+
+/**
+ * struct octeon_twsi - Private data of this driver
+ *
+ * @base:      Base address of i2c registers
+ * @data:      Pointer to SoC specific data struct
+ */
+struct octeon_twsi {
+       void __iomem *base;
+       const struct octeon_i2c_data *data;
+       struct clk clk;
+};
+
+static void twsi_unblock(void *base);
+static int twsi_stop(void *base);
+
+/**
+ * Returns true if we lost arbitration
+ *
+ * @code       status code
+ * @final_read true if this is the final read operation
+ * @return     true if arbitration has been lost, false if it hasn't been lost.
+ */
+static int twsi_i2c_lost_arb(u8 code, int final_read)
+{
+       switch (code) {
+       case TWSI_STAT_TX_ARB_LOST:
+       case TWSI_STAT_TX_ACK_ARB_LOST:
+       case TWSI_STAT_RX_GEN_ADDR_ARB_LOST:
+       case TWSI_STAT_RXDATA_ACK_ARB_LOST:
+               /* Arbitration lost */
+               return -EAGAIN;
+
+       case TWSI_STAT_SLAVE_RXADDR_ACK:
+       case TWSI_STAT_RX_GEN_ADDR_ACK:
+       case TWSI_STAT_GEN_RXADDR_ACK:
+       case TWSI_STAT_GEN_RXADDR_NAK:
+               /* Being addressed as slave, should back off and listen */
+               return -EIO;
+
+       case TWSI_STAT_SLAVE_RXDATA_ACK:
+       case TWSI_STAT_SLAVE_RXDATA_NAK:
+       case TWSI_STAT_STOP_MULTI_START:
+       case TWSI_STAT_SLAVE_RXADDR2_ACK:
+       case TWSI_STAT_SLAVE_TXDATA_ACK:
+       case TWSI_STAT_SLAVE_TXDATA_NAK:
+       case TWSI_STAT_SLAVE_TXDATA_END_ACK:
+               /* Core busy as slave */
+               return  -EIO;
+
+       case TWSI_STAT_RXDATA_ACK_SENT:
+               /* Ack allowed on pre-terminal bytes only */
+               if (!final_read)
+                       return 0;
+               return -EAGAIN;
+
+       case TWSI_STAT_RXDATA_NAK_SENT:
+               /* NAK allowed on terminal byte only */
+               if (!final_read)
+                       return 0;
+               return -EAGAIN;
+
+       case TWSI_STAT_TXDATA_NAK:
+       case TWSI_STAT_TXADDR_NAK:
+       case TWSI_STAT_RXADDR_NAK:
+       case TWSI_STAT_TXADDR2DATA_NAK:
+               return -EAGAIN;
+       }
+
+       return 0;
+}
+
+/**
+ * Writes to the MIO_TWS(0..5)_SW_TWSI register
+ *
+ * @base       Base address of i2c registers
+ * @val                value to write
+ * @return     0 for success, otherwise error
+ */
+static u64 twsi_write_sw(void __iomem *base, u64 val)
+{
+       unsigned long start = get_timer(0);
+
+       val &= ~TWSI_SW_R;
+       val |= TWSI_SW_V;
+
+       debug("%s(%p, 0x%llx)\n", __func__, base, val);
+       writeq(val, base + TWSI_SW_TWSI);
+       do {
+               val = readq(base + TWSI_SW_TWSI);
+       } while ((val & TWSI_SW_V) && (get_timer(start) < 50));
+
+       if (val & TWSI_SW_V)
+               debug("%s: timed out\n", __func__);
+       return val;
+}
+
+/**
+ * Reads the MIO_TWS(0..5)_SW_TWSI register
+ *
+ * @base       Base address of i2c registers
+ * @val                value for eia and op, etc. to read
+ * @return     value of the register
+ */
+static u64 twsi_read_sw(void __iomem *base, u64 val)
+{
+       unsigned long start = get_timer(0);
+
+       val |= TWSI_SW_R | TWSI_SW_V;
+
+       debug("%s(%p, 0x%llx)\n", __func__, base, val);
+       writeq(val, base + TWSI_SW_TWSI);
+
+       do {
+               val = readq(base + TWSI_SW_TWSI);
+       } while ((val & TWSI_SW_V) && (get_timer(start) < 50));
+
+       if (val & TWSI_SW_V)
+               debug("%s: Error writing 0x%llx\n", __func__, val);
+
+       debug("%s: Returning 0x%llx\n", __func__, val);
+       return val;
+}
+
+/**
+ * Write control register
+ *
+ * @base       Base address for i2c registers
+ * @data       data to write
+ */
+static void twsi_write_ctl(void __iomem *base, u8 data)
+{
+       u64 val;
+
+       debug("%s(%p, 0x%x)\n", __func__, base, data);
+       val = data | FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
+       twsi_write_sw(base, val);
+}
+
+/**
+ * Reads the TWSI Control Register
+ *
+ * @base       Base address for i2c
+ * @return     8-bit TWSI control register
+ */
+static u8 twsi_read_ctl(void __iomem *base)
+{
+       u64 val;
+
+       val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
+       val = twsi_read_sw(base, val);
+
+       debug("%s(%p): 0x%x\n", __func__, base, (u8)val);
+       return (u8)val;
+}
+
+/**
+ * Read i2c status register
+ *
+ * @base       Base address of i2c registers
+ * @return     value of status register
+ */
+static u8 twsi_read_status(void __iomem *base)
+{
+       u64 val;
+
+       val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_STAT) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
+
+       return twsi_read_sw(base, val);
+}
+
+/**
+ * Waits for an i2c operation to complete
+ *
+ * @param      base    Base address of registers
+ * @return     0 for success, 1 if timeout
+ */
+static int twsi_wait(void __iomem *base)
+{
+       unsigned long start = get_timer(0);
+       u8 twsi_ctl;
+
+       debug("%s(%p)\n", __func__, base);
+       do {
+               twsi_ctl = twsi_read_ctl(base);
+               twsi_ctl &= TWSI_CTL_IFLG;
+       } while (!twsi_ctl && get_timer(start) < 50);
+
+       debug("  return: %u\n", !twsi_ctl);
+       return !twsi_ctl;
+}
+
+/**
+ * Unsticks the i2c bus
+ *
+ * @base       base address of registers
+ */
+static int twsi_start_unstick(void __iomem *base)
+{
+       twsi_stop(base);
+       twsi_unblock(base);
+
+       return 0;
+}
+
+/**
+ * Sends an i2c start condition
+ *
+ * @base       base address of registers
+ * @return     0 for success, otherwise error
+ */
+static int twsi_start(void __iomem *base)
+{
+       int ret;
+       u8 stat;
+
+       debug("%s(%p)\n", __func__, base);
+       twsi_write_ctl(base, TWSI_CTL_STA | TWSI_CTL_ENAB);
+       ret = twsi_wait(base);
+       if (ret) {
+               stat = twsi_read_status(base);
+               debug("%s: ret: 0x%x, status: 0x%x\n", __func__, ret, stat);
+               switch (stat) {
+               case TWSI_STAT_START:
+               case TWSI_STAT_RSTART:
+                       return 0;
+               case TWSI_STAT_RXADDR_ACK:
+               default:
+                       return twsi_start_unstick(base);
+               }
+       }
+
+       debug("%s: success\n", __func__);
+       return 0;
+}
+
+/**
+ * Sends an i2c stop condition
+ *
+ * @base       register base address
+ * @return     0 for success, -1 if error
+ */
+static int twsi_stop(void __iomem *base)
+{
+       u8 stat;
+
+       twsi_write_ctl(base, TWSI_CTL_STP | TWSI_CTL_ENAB);
+
+       stat = twsi_read_status(base);
+       if (stat != TWSI_STAT_IDLE) {
+               debug("%s: Bad status on bus@%p\n", __func__, base);
+               return -1;
+       }
+
+       return 0;
+}
+
+/**
+ * Writes data to the i2c bus
+ *
+ * @base       register base address
+ * @slave_addr address of slave to write to
+ * @buffer     Pointer to buffer to write
+ * @length     Number of bytes in buffer to write
+ * @return     0 for success, otherwise error
+ */
+static int twsi_write_data(void __iomem *base, u8  slave_addr,
+                          u8 *buffer, unsigned int length)
+{
+       unsigned int curr = 0;
+       u64 val;
+       int ret;
+
+       debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, base, slave_addr,
+             buffer, length);
+       ret = twsi_start(base);
+       if (ret) {
+               debug("%s: Could not start BUS transaction\n", __func__);
+               return -1;
+       }
+
+       ret = twsi_wait(base);
+       if (ret) {
+               debug("%s: wait failed\n", __func__);
+               return ret;
+       }
+
+       val = (u32)(slave_addr << 1) | TWSI_OP_WRITE |
+               FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
+       twsi_write_sw(base, val);
+       twsi_write_ctl(base, TWSI_CTL_ENAB);
+
+       debug("%s: Waiting\n", __func__);
+       ret = twsi_wait(base);
+       if (ret) {
+               debug("%s: Timed out writing slave address 0x%x to target\n",
+                     __func__, slave_addr);
+               return ret;
+       }
+
+       ret = twsi_read_status(base);
+       debug("%s: status: 0x%x\n", __func__, ret);
+       if (ret != TWSI_STAT_TXADDR_ACK) {
+               debug("%s: status: 0x%x\n", __func__, ret);
+               twsi_stop(base);
+               return twsi_i2c_lost_arb(ret, 0);
+       }
+
+       while (curr < length) {
+               val = buffer[curr++] |
+                       FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) |
+                       FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
+               twsi_write_sw(base, val);
+               twsi_write_ctl(base, TWSI_CTL_ENAB);
+
+               debug("%s: Writing 0x%llx\n", __func__, val);
+
+               ret = twsi_wait(base);
+               if (ret) {
+                       debug("%s: Timed out writing data to 0x%x\n",
+                             __func__, slave_addr);
+                       return ret;
+               }
+               ret = twsi_read_status(base);
+               debug("%s: status: 0x%x\n", __func__, ret);
+       }
+
+       debug("%s: Stopping\n", __func__);
+       return twsi_stop(base);
+}
+
+/**
+ * Manually clear the I2C bus and send a stop
+ *
+ * @base       register base address
+ */
+static void twsi_unblock(void __iomem *base)
+{
+       int i;
+
+       for (i = 0; i < 9; i++) {
+               writeq(0, base + TWSI_INT);
+               udelay(5);
+               writeq(TWSI_INT_SCL_OVR, base + TWSI_INT);
+               udelay(5);
+       }
+       writeq(TWSI_INT_SCL_OVR | TWSI_INT_SDA_OVR, base + TWSI_INT);
+       udelay(5);
+       writeq(TWSI_INT_SDA_OVR, base + TWSI_INT);
+       udelay(5);
+       writeq(0, base + TWSI_INT);
+       udelay(5);
+}
+
+/**
+ * Performs a read transaction on the i2c bus
+ *
+ * @base       Base address of twsi registers
+ * @slave_addr i2c bus address to read from
+ * @buffer     buffer to read into
+ * @length     number of bytes to read
+ * @return     0 for success, otherwise error
+ */
+static int twsi_read_data(void __iomem *base, u8 slave_addr,
+                         u8 *buffer, unsigned int length)
+{
+       unsigned int curr = 0;
+       u64 val;
+       int ret;
+
+       debug("%s(%p, 0x%x, %p, %u)\n", __func__, base, slave_addr,
+             buffer, length);
+       ret = twsi_start(base);
+       if (ret) {
+               debug("%s: start failed\n", __func__);
+               return ret;
+       }
+
+       ret = twsi_wait(base);
+       if (ret) {
+               debug("%s: wait failed\n", __func__);
+               return ret;
+       }
+
+       val = (u32)(slave_addr << 1) | TWSI_OP_READ |
+               FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
+       twsi_write_sw(base, val);
+       twsi_write_ctl(base, TWSI_CTL_ENAB);
+
+       ret = twsi_wait(base);
+       if (ret) {
+               debug("%s: waiting for sending addr failed\n", __func__);
+               return ret;
+       }
+
+       ret = twsi_read_status(base);
+       debug("%s: status: 0x%x\n", __func__, ret);
+       if (ret != TWSI_STAT_RXADDR_ACK) {
+               debug("%s: status: 0x%x\n", __func__, ret);
+               twsi_stop(base);
+               return twsi_i2c_lost_arb(ret, 0);
+       }
+
+       while (curr < length) {
+               twsi_write_ctl(base, TWSI_CTL_ENAB |
+                              ((curr < length - 1) ? TWSI_CTL_AAK : 0));
+
+               ret = twsi_wait(base);
+               if (ret) {
+                       debug("%s: waiting for data failed\n", __func__);
+                       return ret;
+               }
+
+               val = twsi_read_sw(base, val);
+               buffer[curr++] = (u8)val;
+       }
+
+       twsi_stop(base);
+
+       return 0;
+}
+
+/**
+ * Calculate the divisor values
+ *
+ * @speed      Speed to set
+ * @m_div      Pointer to M divisor
+ * @n_div      Pointer to N divisor
+ * @return     0 for success, otherwise error
+ */
+static void twsi_calc_div(struct udevice *bus, ulong sclk, unsigned int speed,
+                         int *m_div, int *n_div)
+{
+       struct octeon_twsi *twsi = dev_get_priv(bus);
+       int thp = twsi->data->thp;
+       int tclk, fsamp;
+       int ndiv, mdiv;
+
+       if (twsi->data->clk_method == CLK_METHOD_OCTEON) {
+               tclk = sclk / (2 * (thp + 1));
+       } else {
+               /* Refclk src in mode register defaults to 100MHz clock */
+               sclk = 100000000; /* 100 Mhz */
+               tclk = sclk / (thp + 2);
+       }
+       debug("%s( io_clock %lu tclk %u)\n", __func__, sclk, tclk);
+
+       /*
+        * Compute the clocks M divider:
+        *
+        * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N)
+        * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1
+        *
+        * For OcteonTX2 -
+        * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N)
+        * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1
+        */
+       for (ndiv = 0; ndiv < 8; ndiv++) {
+               fsamp = tclk / (1 << ndiv);
+               mdiv = fsamp / speed / 10;
+               mdiv -= 1;
+               if (mdiv < 16)
+                       break;
+       }
+
+       *m_div = mdiv;
+       *n_div = ndiv;
+}
+
+/**
+ * Init I2C controller
+ *
+ * @base       Base address of twsi registers
+ * @slave_addr I2C slave address to configure this controller to
+ * @return     0 for success, otherwise error
+ */
+static int twsi_init(void __iomem *base, int slaveaddr)
+{
+       u64 val;
+
+       debug("%s (%p, 0x%x)\n", __func__, base, slaveaddr);
+
+       val = slaveaddr << 1 |
+               FIELD_PREP(TWSI_SW_EOP_IA_MASK, 0) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) |
+               TWSI_SW_V;
+       twsi_write_sw(base, val);
+
+       /* Set slave address */
+       val = slaveaddr |
+               FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_EOP_SLAVE_ADDR) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) |
+               TWSI_SW_V;
+       twsi_write_sw(base, val);
+
+       return 0;
+}
+
+/**
+ * Transfers data over the i2c bus
+ *
+ * @bus                i2c bus to transfer data over
+ * @msg                Array of i2c messages
+ * @nmsgs      Number of messages to send/receive
+ * @return     0 for success, otherwise error
+ */
+static int octeon_i2c_xfer(struct udevice *bus, struct i2c_msg *msg,
+                          int nmsgs)
+{
+       struct octeon_twsi *twsi = dev_get_priv(bus);
+       int ret;
+       int i;
+
+       debug("%s: %d messages\n", __func__, nmsgs);
+       for (i = 0; i < nmsgs; i++, msg++) {
+               debug("%s: chip=0x%x, len=0x%x\n", __func__, msg->addr,
+                     msg->len);
+
+               if (msg->flags & I2C_M_RD) {
+                       debug("%s: Reading data\n", __func__);
+                       ret = twsi_read_data(twsi->base, msg->addr,
+                                            msg->buf, msg->len);
+               } else {
+                       debug("%s: Writing data\n", __func__);
+                       ret = twsi_write_data(twsi->base, msg->addr,
+                                             msg->buf, msg->len);
+               }
+               if (ret) {
+                       debug("%s: error sending\n", __func__);
+                       return -EREMOTEIO;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * Set I2C bus speed
+ *
+ * @bus                i2c bus to transfer data over
+ * @speed      Speed in Hz to set
+ * @return     0 for success, otherwise error
+ */
+static int octeon_i2c_set_bus_speed(struct udevice *bus, unsigned int speed)
+{
+       struct octeon_twsi *twsi = dev_get_priv(bus);
+       int m_div, n_div;
+       ulong clk_rate;
+       u64 val;
+
+       debug("%s(%p, %u)\n", __func__, bus, speed);
+
+       clk_rate = clk_get_rate(&twsi->clk);
+       if (IS_ERR_VALUE(clk_rate))
+               return -EINVAL;
+
+       twsi_calc_div(bus, clk_rate, speed, &m_div, &n_div);
+       if (m_div >= 16)
+               return -1;
+
+       val = (u32)(((m_div & 0xf) << 3) | ((n_div & 0x7) << 0)) |
+               FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CLKCTL) |
+               FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) |
+               TWSI_SW_V;
+       /* Only init non-slave ports */
+       writeq(val, twsi->base + TWSI_SW_TWSI);
+
+       debug("%s: Wrote 0x%llx to sw_twsi\n", __func__, val);
+       return 0;
+}
+
+/**
+ * Driver probe function
+ *
+ * @dev                I2C device to probe
+ * @return     0 for success, otherwise error
+ */
+static int octeon_i2c_probe(struct udevice *dev)
+{
+       struct octeon_twsi *twsi = dev_get_priv(dev);
+       u32 i2c_slave_addr;
+       int ret;
+
+       twsi->data = (const struct octeon_i2c_data *)dev_get_driver_data(dev);
+
+       if (twsi->data->probe == PROBE_PCI) {
+               pci_dev_t bdf = dm_pci_get_bdf(dev);
+
+               debug("TWSI PCI device: %x\n", bdf);
+               dev->req_seq = PCI_FUNC(bdf);
+
+               twsi->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0,
+                                           PCI_REGION_MEM);
+       } else {
+               twsi->base = dev_remap_addr(dev);
+       }
+       twsi->base += twsi->data->reg_offs;
+
+       i2c_slave_addr = dev_read_u32_default(dev, "i2c-sda-hold-time-ns",
+                                             CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR);
+
+       ret = clk_get_by_index(dev, 0, &twsi->clk);
+       if (ret < 0)
+               return ret;
+
+       ret = clk_enable(&twsi->clk);
+       if (ret)
+               return ret;
+
+       debug("TWSI bus %d at %p\n", dev->seq, twsi->base);
+
+       /* Start with standard speed, real speed set via DT or cmd */
+       return twsi_init(twsi->base, i2c_slave_addr);
+}
+
+static const struct dm_i2c_ops octeon_i2c_ops = {
+       .xfer           = octeon_i2c_xfer,
+       .set_bus_speed  = octeon_i2c_set_bus_speed,
+};
+
+static const struct octeon_i2c_data i2c_octeon_data = {
+       .probe = PROBE_DT,
+       .reg_offs = 0x0000,
+       .thp = 3,
+       .clk_method = CLK_METHOD_OCTEON,
+};
+
+static const struct octeon_i2c_data i2c_octeontx_data = {
+       .probe = PROBE_PCI,
+       .reg_offs = 0x8000,
+       .thp = 3,
+       .clk_method = CLK_METHOD_OCTEON,
+};
+
+static const struct octeon_i2c_data i2c_octeontx2_data = {
+       .probe = PROBE_PCI,
+       .reg_offs = 0x8000,
+       .thp = 24,
+       .clk_method = CLK_METHOD_OCTEONTX2,
+};
+
+static const struct udevice_id octeon_i2c_ids[] = {
+       { .compatible = "cavium,octeon-7890-twsi",
+         .data = (ulong)&i2c_octeon_data },
+       { .compatible = "cavium,thunder-8890-twsi",
+         .data = (ulong)&i2c_octeontx_data },
+       { .compatible = "cavium,thunder2-99xx-twsi",
+         .data = (ulong)&i2c_octeontx2_data },
+       { }
+};
+
+U_BOOT_DRIVER(octeon_pci_twsi) = {
+       .name   = "i2c_octeon",
+       .id     = UCLASS_I2C,
+       .of_match = octeon_i2c_ids,
+       .probe  = octeon_i2c_probe,
+       .priv_auto_alloc_size = sizeof(struct octeon_twsi),
+       .ops    = &octeon_i2c_ops,
+};
+
+static struct pci_device_id octeon_twsi_supported[] = {
+       { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI),
+         .driver_data = (ulong)&i2c_octeontx2_data },
+       { },
+};
+
+U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported);