All of lore.kernel.org
 help / color / mirror / Atom feed
From: Rayagonda Kokatanur <rayagonda.kokatanur@broadcom.com>
To: u-boot@lists.denx.de
Subject: [PATCH v2] i2c: octeon_i2c: Add I2C controller driver for Octeon
Date: Wed, 3 Jun 2020 11:13:37 +0530	[thread overview]
Message-ID: <CAHO=5PEcjdxQRd7T2c9iu85psExUZ=wD6pxytALe_jrLN3M7Dg@mail.gmail.com> (raw)
In-Reply-To: <20200526121307.2735-1-sr@denx.de>

On Tue, May 26, 2020 at 5:43 PM Stefan Roese <sr@denx.de> wrote:
>
> From: Suneel Garapati <sgarapati@marvell.com>
>
> 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>
> ---
> v2 (Stefan):
> - Added clk framework support and dropped ad-hoc clock code
> - Removed #ifdef's for Octeon vs OcteonTX/TX2 completely
>   The differentiation is now made via driver data / compatible
>   string
> - Added device-tree bindings documentation
> - Removed unused macro
>
> 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
>
>  doc/device-tree-bindings/i2c/octeon-i2c.txt |  24 +
>  drivers/i2c/Kconfig                         |  10 +
>  drivers/i2c/Makefile                        |   1 +
>  drivers/i2c/octeon_i2c.c                    | 847 ++++++++++++++++++++
>  4 files changed, 882 insertions(+)
>  create mode 100644 doc/device-tree-bindings/i2c/octeon-i2c.txt
>  create mode 100644 drivers/i2c/octeon_i2c.c

Reviewed-by: Rayagonda Kokatanur <rayagonda.kokatanur@broadcom.com>

>
> diff --git a/doc/device-tree-bindings/i2c/octeon-i2c.txt b/doc/device-tree-bindings/i2c/octeon-i2c.txt
> new file mode 100644
> index 0000000000..9c1908ec2c
> --- /dev/null
> +++ b/doc/device-tree-bindings/i2c/octeon-i2c.txt
> @@ -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 at 1180000001000 {
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +               compatible = "cavium,octeon-7890-twsi";
> +               reg = <0x11800 0x00001000 0x0 0x200>;
> +               clock-frequency = <100000>;
> +               clocks = <&sclk>;
> +       };
> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
> index f8b18de8f3..363b899e9c 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..c11d6ff93d
> --- /dev/null
> +++ b/drivers/i2c/octeon_i2c.c
> @@ -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);
> --
> 2.26.2
>

  parent reply	other threads:[~2020-06-03  5:43 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-05-26 12:13 [PATCH v2] i2c: octeon_i2c: Add I2C controller driver for Octeon Stefan Roese
2020-05-31 14:08 ` Simon Glass
2020-06-03  4:14 ` Heiko Schocher
2020-06-03  5:43 ` Rayagonda Kokatanur [this message]
2020-07-09  8:29 ` Heiko Schocher

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAHO=5PEcjdxQRd7T2c9iu85psExUZ=wD6pxytALe_jrLN3M7Dg@mail.gmail.com' \
    --to=rayagonda.kokatanur@broadcom.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.