diff mbox series

[v1] i2c: octeon_i2c: Add I2C controller driver for Octeon

Message ID 20200514072311.28286-1-sr@denx.de
State New
Headers show
Series [v1] i2c: octeon_i2c: Add I2C controller driver for Octeon | expand

Commit Message

Stefan Roese May 14, 2020, 7:23 a.m. UTC
From: Suneel Garapati <sgarapati at marvell.com>

Add support for I2C controllers found on Octeon II/III and Octeon TX
TX2 SoC platforms.

Signed-off-by: Aaron Williams <awilliams at marvell.com>
Signed-off-by: Suneel Garapati <sgarapati at marvell.com>
Signed-off-by: Stefan Roese <sr at denx.de>
Cc: Heiko Schocher <hs at denx.de>
Cc: Simon Glass <sjg at chromium.org>
Cc: Daniel Schwierzeck <daniel.schwierzeck at gmail.com>
Cc: Aaron Williams <awilliams at marvell.com>
Cc: Chandrakala Chavva <cchavva at marvell.com>
---
RFC -> v1 (Stefan):
- Separated this patch from the OcteonTX/TX2 RFC patch series into a
  single patch. This is useful, as the upcoming MIPS Octeon support will
  use this I2C driver.
- Added MIPS Octeon II/III support (big endian). Rename driver and its
  function names from "octeontx" to "octeon" to better match all Octeon
  platforms.
- Moved from union to defines / bitmasks as suggested by Simon. This makes
  the driver usage on little- and big-endian platforms much easier.
- Enhanced Kconfig text
- Removed all clock macros (use values from DT)
- Removed long driver debug strings. This is only available when a debug
  version of this driver is built. The user / developer can lookup the
  descriptive error messages in the driver in this case anyway.
- Removed static "last_id"
- Dropped misc blank lines. Misc reformatting.
- Dropped "!= 0"
- Added missing function comments
- Added missing strut comments
- Changed comment style
- Renames "result" to "ret"
- Hex numbers uppercase
- Minor other changes
- Reword commit text and subject

 drivers/i2c/Kconfig      |  10 +
 drivers/i2c/Makefile     |   1 +
 drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 814 insertions(+)
 create mode 100644 drivers/i2c/octeon_i2c.c

Comments

Heiko Schocher May 19, 2020, 6:07 a.m. UTC | #1
Hello Stefan,

Am 14.05.2020 um 09:23 schrieb Stefan Roese:
> From: Suneel Garapati <sgarapati at marvell.com>
> 
> Add support for I2C controllers found on Octeon II/III and Octeon TX
> TX2 SoC platforms.
> 
> Signed-off-by: Aaron Williams <awilliams at marvell.com>
> Signed-off-by: Suneel Garapati <sgarapati at marvell.com>
> Signed-off-by: Stefan Roese <sr at denx.de>
> Cc: Heiko Schocher <hs at denx.de>
> Cc: Simon Glass <sjg at chromium.org>
> Cc: Daniel Schwierzeck <daniel.schwierzeck at gmail.com>
> Cc: Aaron Williams <awilliams at marvell.com>
> Cc: Chandrakala Chavva <cchavva at marvell.com>
> ---
> RFC -> v1 (Stefan):
> - Separated this patch from the OcteonTX/TX2 RFC patch series into a
>    single patch. This is useful, as the upcoming MIPS Octeon support will
>    use this I2C driver.
> - Added MIPS Octeon II/III support (big endian). Rename driver and its
>    function names from "octeontx" to "octeon" to better match all Octeon
>    platforms.
> - Moved from union to defines / bitmasks as suggested by Simon. This makes
>    the driver usage on little- and big-endian platforms much easier.
> - Enhanced Kconfig text
> - Removed all clock macros (use values from DT)
> - Removed long driver debug strings. This is only available when a debug
>    version of this driver is built. The user / developer can lookup the
>    descriptive error messages in the driver in this case anyway.
> - Removed static "last_id"
> - Dropped misc blank lines. Misc reformatting.
> - Dropped "!= 0"
> - Added missing function comments
> - Added missing strut comments
> - Changed comment style
> - Renames "result" to "ret"
> - Hex numbers uppercase
> - Minor other changes
> - Reword commit text and subject
> 
>   drivers/i2c/Kconfig      |  10 +
>   drivers/i2c/Makefile     |   1 +
>   drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++
>   3 files changed, 814 insertions(+)
>   create mode 100644 drivers/i2c/octeon_i2c.c

nitpick only ...

Please add a documentation of the device tree bindings.

[...]
> diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c
> new file mode 100644
> index 0000000000..210f98655e
> --- /dev/null
> +++ b/drivers/i2c/octeon_i2c.c
> @@ -0,0 +1,803 @@
> +// SPDX-License-Identifier:    GPL-2.0
> +/*
> + * Copyright (C) 2018 Marvell International Ltd.
> + */
> +
> +#include <common.h>
> +#include <i2c.h>
> +#include <dm.h>
> +#include <pci_ids.h>
> +#include <asm/io.h>
> +#include <asm/arch/clock.h>
> +#include <linux/bitfield.h>
> +
> +/*
> + * Octeon II/III (MIPS) have different register offsets than the ARM based
> + * Octeon TX/TX2 SoCs
> + */
> +#if defined(CONFIG_ARCH_OCTEON)
> +#define REG_OFFS		0x0000
> +#else
> +#define REG_OFFS		0x1000
> +#endif
> +
> +#define TWSI_SW_TWSI		(REG_OFFS + 0x00)
> +#define TWSI_TWSI_SW		(REG_OFFS + 0x08)

Is there no clearer name ?

Just wonderung about "TWSI" .. we already have an i2c driver with TWSI defines:

https://gitlab.denx.de/u-boot/u-boot/-/blob/master/drivers/i2c/mvtwsi.c

But it seems they have no common parts.

[...]
> +#if defined(CONFIG_ARCH_OCTEON)
> +static int get_io_clock(void)
> +{
> +	return octeon_get_io_clock();
> +}
> +#else
> +static int get_io_clock(void)
> +{
> +	return octeontx_get_io_clock();
> +}
> +#endif

Here would be good to have the clk framework...

> +
> +static int twsi_thp(void)
> +{
> +	if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX))
> +		return 24;
> +	else
> +		return 3;
> +}

May you can add here some comments for this magic numbers?

[...]
> +#define RST_BOOT_PNR_MUL(val)  (((val) >> 33) & 0x1F)

not used define, please remove.

[...]

bye,
Heiko
Simon Glass May 19, 2020, 12:32 p.m. UTC | #2
Hi,

On Thu, 14 May 2020 at 01:23, Stefan Roese <sr at denx.de> wrote:
>
> From: Suneel Garapati <sgarapati at marvell.com>
>
> Add support for I2C controllers found on Octeon II/III and Octeon TX
> TX2 SoC platforms.
>
> Signed-off-by: Aaron Williams <awilliams at marvell.com>
> Signed-off-by: Suneel Garapati <sgarapati at marvell.com>
> Signed-off-by: Stefan Roese <sr at denx.de>
> Cc: Heiko Schocher <hs at denx.de>
> Cc: Simon Glass <sjg at chromium.org>
> Cc: Daniel Schwierzeck <daniel.schwierzeck at gmail.com>
> Cc: Aaron Williams <awilliams at marvell.com>
> Cc: Chandrakala Chavva <cchavva at marvell.com>
> ---
> RFC -> v1 (Stefan):
> - Separated this patch from the OcteonTX/TX2 RFC patch series into a
>   single patch. This is useful, as the upcoming MIPS Octeon support will
>   use this I2C driver.
> - Added MIPS Octeon II/III support (big endian). Rename driver and its
>   function names from "octeontx" to "octeon" to better match all Octeon
>   platforms.
> - Moved from union to defines / bitmasks as suggested by Simon. This makes
>   the driver usage on little- and big-endian platforms much easier.
> - Enhanced Kconfig text
> - Removed all clock macros (use values from DT)
> - Removed long driver debug strings. This is only available when a debug
>   version of this driver is built. The user / developer can lookup the
>   descriptive error messages in the driver in this case anyway.
> - Removed static "last_id"
> - Dropped misc blank lines. Misc reformatting.
> - Dropped "!= 0"
> - Added missing function comments
> - Added missing strut comments
> - Changed comment style
> - Renames "result" to "ret"
> - Hex numbers uppercase
> - Minor other changes
> - Reword commit text and subject
>
>  drivers/i2c/Kconfig      |  10 +
>  drivers/i2c/Makefile     |   1 +
>  drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++
>  3 files changed, 814 insertions(+)
>  create mode 100644 drivers/i2c/octeon_i2c.c
>
> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
> index e42b6516bf..1330b36698 100644
> --- a/drivers/i2c/Kconfig
> +++ b/drivers/i2c/Kconfig
> @@ -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
> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
> index 62935b7ebc..2b58aae892 100644
> --- a/drivers/i2c/Makefile
> +++ b/drivers/i2c/Makefile
> @@ -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
> index 0000000000..210f98655e
> --- /dev/null
> +++ b/drivers/i2c/octeon_i2c.c
> @@ -0,0 +1,803 @@
> +// SPDX-License-Identifier:    GPL-2.0
> +/*
> + * Copyright (C) 2018 Marvell International Ltd.
> + */
> +
> +#include <common.h>
> +#include <i2c.h>
> +#include <dm.h>
> +#include <pci_ids.h>
> +#include <asm/io.h>
> +#include <asm/arch/clock.h>
> +#include <linux/bitfield.h>
> +
> +/*
> + * Octeon II/III (MIPS) have different register offsets than the ARM based
> + * Octeon TX/TX2 SoCs
> + */
> +#if defined(CONFIG_ARCH_OCTEON)
> +#define REG_OFFS               0x0000
> +#else
> +#define REG_OFFS               0x1000
> +#endif
> +
> +#define TWSI_SW_TWSI           (REG_OFFS + 0x00)
> +#define TWSI_TWSI_SW           (REG_OFFS + 0x08)
> +#define TWSI_INT               (REG_OFFS + 0x10)
> +#define TWSI_SW_TWSI_EXT       (REG_OFFS + 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
> +};
> +
> +#ifndef CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR
> +# define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR      0x77

This should be a device-tree property.

> +#endif
> +
> +/**
> + * struct octeon_twsi - Private data of this driver
> + *
> + * @base:      Base address of i2c registers
> + */
> +struct octeon_twsi {
> +       void __iomem *base;
> +};
> +
> +static void twsi_unblock(void *base);
> +static int twsi_stop(void *base);
> +
> +#if defined(CONFIG_ARCH_OCTEON)

Needs a clock driver.

> +static int get_io_clock(void)
> +{
> +       return octeon_get_io_clock();
> +}
> +#else
> +static int get_io_clock(void)
> +{
> +       return octeontx_get_io_clock();
> +}
> +#endif
> +
> +static int twsi_thp(void)
> +{
> +       if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX))
> +               return 24;
> +       else
> +               return 3;
> +}
> +

[..]

> +/**
> + * 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(unsigned int speed, int *m_div, int *n_div)
> +{
> +       int io_clock_hz;
> +       int tclk, fsamp;
> +       int ndiv, mdiv;
> +
> +#if defined(CONFIG_ARCH_OCTEON) || defined(CONFIG_ARCH_OCTEONTX)
> +       io_clock_hz = get_io_clock();
> +       tclk = io_clock_hz / (2 * (twsi_thp() + 1));
> +#elif defined(CONFIG_ARCH_OCTEONTX2)
> +       /* Refclk src in mode register defaults to 100MHz clock */
> +       io_clock_hz = 100000000; /* 100 Mhz */
> +       tclk = io_clock_hz / (twsi_thp() + 2);
> +#endif

Needs a clock driver. If different logic is needed it should use the
compatible string / driver data rather than #ifdefs.

> +       debug("%s( io_clock %u tclk %u)\n", __func__, io_clock_hz, 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;
> +}
> +
> +/**

[..]

> +/**
> + * Driver probe function
> + *
> + * @dev                I2C device to probe
> + * @return     0 for success, otherwise error
> + */
> +static int octeon_pci_i2c_probe(struct udevice *dev)
> +{
> +       struct octeon_twsi *twsi = dev_get_priv(dev);
> +#if !defined(CONFIG_ARCH_OCTEON)

Again should use dev_get_driver_data()

> +       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);
> +#endif
> +       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, CONFIG_SYS_I2C_OCTEON_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 udevice_id octeon_i2c_ids[] = {
> +       { .compatible = "cavium,thunder-8890-twsi" },
> +       { .compatible = "cavium,octeon-7890-twsi" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(octeon_pci_twsi) = {
> +       .name   = "i2c_octeon",
> +       .id     = UCLASS_I2C,
> +       .of_match = octeon_i2c_ids,
> +       .probe  = octeon_pci_i2c_probe,
> +       .priv_auto_alloc_size = sizeof(struct octeon_twsi),
> +       .ops    = &octeon_i2c_ops,
> +};
> +
> +#if !defined(CONFIG_ARCH_OCTEON)
> +static struct pci_device_id octeon_twsi_supported[] = {
> +       { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI) },
> +       { },
> +};
> +
> +U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported);
> +#endif
> --
> 2.26.2
>

Regards,
Simon
Stefan Roese May 26, 2020, 6:49 a.m. UTC | #3
Hi Heiko,

On 19.05.20 08:07, Heiko Schocher wrote:
> Hello Stefan,
> 
> Am 14.05.2020 um 09:23 schrieb Stefan Roese:
>> From: Suneel Garapati <sgarapati at marvell.com>
>>
>> Add support for I2C controllers found on Octeon II/III and Octeon TX
>> TX2 SoC platforms.
>>
>> Signed-off-by: Aaron Williams <awilliams at marvell.com>
>> Signed-off-by: Suneel Garapati <sgarapati at marvell.com>
>> Signed-off-by: Stefan Roese <sr at denx.de>
>> Cc: Heiko Schocher <hs at denx.de>
>> Cc: Simon Glass <sjg at chromium.org>
>> Cc: Daniel Schwierzeck <daniel.schwierzeck at gmail.com>
>> Cc: Aaron Williams <awilliams at marvell.com>
>> Cc: Chandrakala Chavva <cchavva at marvell.com>
>> ---
>> RFC -> v1 (Stefan):
>> - Separated this patch from the OcteonTX/TX2 RFC patch series into a
>> ?? single patch. This is useful, as the upcoming MIPS Octeon support will
>> ?? use this I2C driver.
>> - Added MIPS Octeon II/III support (big endian). Rename driver and its
>> ?? function names from "octeontx" to "octeon" to better match all Octeon
>> ?? platforms.
>> - Moved from union to defines / bitmasks as suggested by Simon. This 
>> makes
>> ?? the driver usage on little- and big-endian platforms much easier.
>> - Enhanced Kconfig text
>> - Removed all clock macros (use values from DT)
>> - Removed long driver debug strings. This is only available when a debug
>> ?? version of this driver is built. The user / developer can lookup the
>> ?? descriptive error messages in the driver in this case anyway.
>> - Removed static "last_id"
>> - Dropped misc blank lines. Misc reformatting.
>> - Dropped "!= 0"
>> - Added missing function comments
>> - Added missing strut comments
>> - Changed comment style
>> - Renames "result" to "ret"
>> - Hex numbers uppercase
>> - Minor other changes
>> - Reword commit text and subject
>>
>> ? drivers/i2c/Kconfig????? |? 10 +
>> ? drivers/i2c/Makefile???? |?? 1 +
>> ? drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++
>> ? 3 files changed, 814 insertions(+)
>> ? create mode 100644 drivers/i2c/octeon_i2c.c
> 
> nitpick only ...
> 
> Please add a documentation of the device tree bindings.

Okay.

> [...]
>> diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c
>> new file mode 100644
>> index 0000000000..210f98655e
>> --- /dev/null
>> +++ b/drivers/i2c/octeon_i2c.c
>> @@ -0,0 +1,803 @@
>> +// SPDX-License-Identifier:??? GPL-2.0
>> +/*
>> + * Copyright (C) 2018 Marvell International Ltd.
>> + */
>> +
>> +#include <common.h>
>> +#include <i2c.h>
>> +#include <dm.h>
>> +#include <pci_ids.h>
>> +#include <asm/io.h>
>> +#include <asm/arch/clock.h>
>> +#include <linux/bitfield.h>
>> +
>> +/*
>> + * Octeon II/III (MIPS) have different register offsets than the ARM 
>> based
>> + * Octeon TX/TX2 SoCs
>> + */
>> +#if defined(CONFIG_ARCH_OCTEON)
>> +#define REG_OFFS??????? 0x0000
>> +#else
>> +#define REG_OFFS??????? 0x1000
>> +#endif
>> +
>> +#define TWSI_SW_TWSI??????? (REG_OFFS + 0x00)
>> +#define TWSI_TWSI_SW??????? (REG_OFFS + 0x08)
> 
> Is there no clearer name ?
> 
> Just wonderung about "TWSI" .. we already have an i2c driver with TWSI 
> defines:
> 
> https://gitlab.denx.de/u-boot/u-boot/-/blob/master/drivers/i2c/mvtwsi.c
> 
> But it seems they have no common parts.

TWSI is another abbreviation for I2C: Two Wire Serial Interface. I
would like to keep these macros as they reflect the register names in
the Cavium / Marvell manuals.

> [...]
>> +#if defined(CONFIG_ARCH_OCTEON)
>> +static int get_io_clock(void)
>> +{
>> +??? return octeon_get_io_clock();
>> +}
>> +#else
>> +static int get_io_clock(void)
>> +{
>> +??? return octeontx_get_io_clock();
>> +}
>> +#endif
> 
> Here would be good to have the clk framework...

Yes, it would be great. I'm thinking about adding a minimal clk driver
for Octeon - not sure, if I get it done shortly though.

>> +
>> +static int twsi_thp(void)
>> +{
>> +??? if (IS_ENABLED(CONFIG_ARCH_OCTEON) || 
>> IS_ENABLED(CONFIG_ARCH_OCTEONTX))
>> +??????? return 24;
>> +??? else
>> +??????? return 3;
>> +}
> 
> May you can add here some comments for this magic numbers?

I didn't write the initial version of this driver, so its hard for
me to come up with details here. But I'll try.

> [...]
>> +#define RST_BOOT_PNR_MUL(val)? (((val) >> 33) & 0x1F)
> 
> not used define, please remove.

Ah, thanks for spotting.

Thanks,
Stefan
Stefan Roese May 26, 2020, 10:55 a.m. UTC | #4
Hi Simon,

On 19.05.20 14:32, Simon Glass wrote:
> Hi,
> 
> On Thu, 14 May 2020 at 01:23, Stefan Roese <sr at denx.de> wrote:
>>
>> From: Suneel Garapati <sgarapati at marvell.com>
>>
>> Add support for I2C controllers found on Octeon II/III and Octeon TX
>> TX2 SoC platforms.
>>
>> Signed-off-by: Aaron Williams <awilliams at marvell.com>
>> Signed-off-by: Suneel Garapati <sgarapati at marvell.com>
>> Signed-off-by: Stefan Roese <sr at denx.de>
>> Cc: Heiko Schocher <hs at denx.de>
>> Cc: Simon Glass <sjg at chromium.org>
>> Cc: Daniel Schwierzeck <daniel.schwierzeck at gmail.com>
>> Cc: Aaron Williams <awilliams at marvell.com>
>> Cc: Chandrakala Chavva <cchavva at marvell.com>
>> ---
>> RFC -> v1 (Stefan):
>> - Separated this patch from the OcteonTX/TX2 RFC patch series into a
>>    single patch. This is useful, as the upcoming MIPS Octeon support will
>>    use this I2C driver.
>> - Added MIPS Octeon II/III support (big endian). Rename driver and its
>>    function names from "octeontx" to "octeon" to better match all Octeon
>>    platforms.
>> - Moved from union to defines / bitmasks as suggested by Simon. This makes
>>    the driver usage on little- and big-endian platforms much easier.
>> - Enhanced Kconfig text
>> - Removed all clock macros (use values from DT)
>> - Removed long driver debug strings. This is only available when a debug
>>    version of this driver is built. The user / developer can lookup the
>>    descriptive error messages in the driver in this case anyway.
>> - Removed static "last_id"
>> - Dropped misc blank lines. Misc reformatting.
>> - Dropped "!= 0"
>> - Added missing function comments
>> - Added missing strut comments
>> - Changed comment style
>> - Renames "result" to "ret"
>> - Hex numbers uppercase
>> - Minor other changes
>> - Reword commit text and subject
>>
>>   drivers/i2c/Kconfig      |  10 +
>>   drivers/i2c/Makefile     |   1 +
>>   drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 814 insertions(+)
>>   create mode 100644 drivers/i2c/octeon_i2c.c
>>
>> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
>> index e42b6516bf..1330b36698 100644
>> --- a/drivers/i2c/Kconfig
>> +++ b/drivers/i2c/Kconfig
>> @@ -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
>> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
>> index 62935b7ebc..2b58aae892 100644
>> --- a/drivers/i2c/Makefile
>> +++ b/drivers/i2c/Makefile
>> @@ -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
>> index 0000000000..210f98655e
>> --- /dev/null
>> +++ b/drivers/i2c/octeon_i2c.c
>> @@ -0,0 +1,803 @@
>> +// SPDX-License-Identifier:    GPL-2.0
>> +/*
>> + * Copyright (C) 2018 Marvell International Ltd.
>> + */
>> +
>> +#include <common.h>
>> +#include <i2c.h>
>> +#include <dm.h>
>> +#include <pci_ids.h>
>> +#include <asm/io.h>
>> +#include <asm/arch/clock.h>
>> +#include <linux/bitfield.h>
>> +
>> +/*
>> + * Octeon II/III (MIPS) have different register offsets than the ARM based
>> + * Octeon TX/TX2 SoCs
>> + */
>> +#if defined(CONFIG_ARCH_OCTEON)
>> +#define REG_OFFS               0x0000
>> +#else
>> +#define REG_OFFS               0x1000
>> +#endif
>> +
>> +#define TWSI_SW_TWSI           (REG_OFFS + 0x00)
>> +#define TWSI_TWSI_SW           (REG_OFFS + 0x08)
>> +#define TWSI_INT               (REG_OFFS + 0x10)
>> +#define TWSI_SW_TWSI_EXT       (REG_OFFS + 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
>> +};
>> +
>> +#ifndef CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR
>> +# define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR      0x77
> 
> This should be a device-tree property.

Okay, will move to DT.

>> +#endif
>> +
>> +/**
>> + * struct octeon_twsi - Private data of this driver
>> + *
>> + * @base:      Base address of i2c registers
>> + */
>> +struct octeon_twsi {
>> +       void __iomem *base;
>> +};
>> +
>> +static void twsi_unblock(void *base);
>> +static int twsi_stop(void *base);
>> +
>> +#if defined(CONFIG_ARCH_OCTEON)
> 
> Needs a clock driver.

Right. I'll try to add clock driver support with the first Octeon base
port.

>> +static int get_io_clock(void)
>> +{
>> +       return octeon_get_io_clock();
>> +}
>> +#else
>> +static int get_io_clock(void)
>> +{
>> +       return octeontx_get_io_clock();
>> +}
>> +#endif
>> +
>> +static int twsi_thp(void)
>> +{
>> +       if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX))
>> +               return 24;
>> +       else
>> +               return 3;
>> +}
>> +
> 
> [..]
> 
>> +/**
>> + * 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(unsigned int speed, int *m_div, int *n_div)
>> +{
>> +       int io_clock_hz;
>> +       int tclk, fsamp;
>> +       int ndiv, mdiv;
>> +
>> +#if defined(CONFIG_ARCH_OCTEON) || defined(CONFIG_ARCH_OCTEONTX)
>> +       io_clock_hz = get_io_clock();
>> +       tclk = io_clock_hz / (2 * (twsi_thp() + 1));
>> +#elif defined(CONFIG_ARCH_OCTEONTX2)
>> +       /* Refclk src in mode register defaults to 100MHz clock */
>> +       io_clock_hz = 100000000; /* 100 Mhz */
>> +       tclk = io_clock_hz / (twsi_thp() + 2);
>> +#endif
> 
> Needs a clock driver. If different logic is needed it should use the
> compatible string / driver data rather than #ifdefs.

Yes, will update.

>> +       debug("%s( io_clock %u tclk %u)\n", __func__, io_clock_hz, 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;
>> +}
>> +
>> +/**
> 
> [..]
> 
>> +/**
>> + * Driver probe function
>> + *
>> + * @dev                I2C device to probe
>> + * @return     0 for success, otherwise error
>> + */
>> +static int octeon_pci_i2c_probe(struct udevice *dev)
>> +{
>> +       struct octeon_twsi *twsi = dev_get_priv(dev);
>> +#if !defined(CONFIG_ARCH_OCTEON)
> 
> Again should use dev_get_driver_data()

Yes, will update in v2.

Thanks,
Stefan
diff mbox series

Patch

diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index e42b6516bf..1330b36698 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -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
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 62935b7ebc..2b58aae892 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -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
index 0000000000..210f98655e
--- /dev/null
+++ b/drivers/i2c/octeon_i2c.c
@@ -0,0 +1,803 @@ 
+// SPDX-License-Identifier:    GPL-2.0
+/*
+ * Copyright (C) 2018 Marvell International Ltd.
+ */
+
+#include <common.h>
+#include <i2c.h>
+#include <dm.h>
+#include <pci_ids.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <linux/bitfield.h>
+
+/*
+ * Octeon II/III (MIPS) have different register offsets than the ARM based
+ * Octeon TX/TX2 SoCs
+ */
+#if defined(CONFIG_ARCH_OCTEON)
+#define REG_OFFS		0x0000
+#else
+#define REG_OFFS		0x1000
+#endif
+
+#define TWSI_SW_TWSI		(REG_OFFS + 0x00)
+#define TWSI_TWSI_SW		(REG_OFFS + 0x08)
+#define TWSI_INT		(REG_OFFS + 0x10)
+#define TWSI_SW_TWSI_EXT	(REG_OFFS + 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
+};
+
+#ifndef CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR
+# define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR	0x77
+#endif
+
+/**
+ * struct octeon_twsi - Private data of this driver
+ *
+ * @base:	Base address of i2c registers
+ */
+struct octeon_twsi {
+	void __iomem *base;
+};
+
+static void twsi_unblock(void *base);
+static int twsi_stop(void *base);
+
+#if defined(CONFIG_ARCH_OCTEON)
+static int get_io_clock(void)
+{
+	return octeon_get_io_clock();
+}
+#else
+static int get_io_clock(void)
+{
+	return octeontx_get_io_clock();
+}
+#endif
+
+static int twsi_thp(void)
+{
+	if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX))
+		return 24;
+	else
+		return 3;
+}
+
+/**
+ * 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;
+}
+
+#define RST_BOOT_PNR_MUL(val)  (((val) >> 33) & 0x1F)
+
+/**
+ * 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(unsigned int speed, int *m_div, int *n_div)
+{
+	int io_clock_hz;
+	int tclk, fsamp;
+	int ndiv, mdiv;
+
+#if defined(CONFIG_ARCH_OCTEON) || defined(CONFIG_ARCH_OCTEONTX)
+	io_clock_hz = get_io_clock();
+	tclk = io_clock_hz / (2 * (twsi_thp() + 1));
+#elif defined(CONFIG_ARCH_OCTEONTX2)
+	/* Refclk src in mode register defaults to 100MHz clock */
+	io_clock_hz = 100000000; /* 100 Mhz */
+	tclk = io_clock_hz / (twsi_thp() + 2);
+#endif
+	debug("%s( io_clock %u tclk %u)\n", __func__, io_clock_hz, 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;
+	u64 val;
+
+	debug("%s(%p, %u)\n", __func__, bus, speed);
+
+	twsi_calc_div(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_pci_i2c_probe(struct udevice *dev)
+{
+	struct octeon_twsi *twsi = dev_get_priv(dev);
+#if !defined(CONFIG_ARCH_OCTEON)
+	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);
+#endif
+	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, CONFIG_SYS_I2C_OCTEON_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 udevice_id octeon_i2c_ids[] = {
+	{ .compatible = "cavium,thunder-8890-twsi" },
+	{ .compatible = "cavium,octeon-7890-twsi" },
+	{ }
+};
+
+U_BOOT_DRIVER(octeon_pci_twsi) = {
+	.name	= "i2c_octeon",
+	.id	= UCLASS_I2C,
+	.of_match = octeon_i2c_ids,
+	.probe	= octeon_pci_i2c_probe,
+	.priv_auto_alloc_size = sizeof(struct octeon_twsi),
+	.ops	= &octeon_i2c_ops,
+};
+
+#if !defined(CONFIG_ARCH_OCTEON)
+static struct pci_device_id octeon_twsi_supported[] = {
+	{ PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI) },
+	{ },
+};
+
+U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported);
+#endif