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

Stefan Roese sr at denx.de
Wed Jun 3 07:39:35 CEST 2020


On 03.06.20 07:37, Rayagonda Kokatanur wrote:
> On Wed, Jun 3, 2020 at 10:50 AM Stefan Roese <sr at denx.de> wrote:
>>
>> Hi Rayagonda,
>>
>> v1 is superseeded. Please review the latest version, v2:
>>
>> https://patchwork.ozlabs.org/project/uboot/patch/20200526121307.2735-1-sr@denx.de/
> 
> Thank you, I missed that v2.
> Anyway please ignore my comments, all of them are fixed in v2.

Good. Could you please send a Reviewed-by tag then?

Thanks,
Stefan

> Thanks,
> Rayagonda
>>
>> Thanks,
>> Stefan
>>
>> On 03.06.20 07:15, Rayagonda Kokatanur wrote:
>>> Hi Stefan,
>>>
>>> Few minor comments,
>>>
>>> On Thu, May 14, 2020 at 12:53 PM 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.
>>>
>>> Should it  be 2020 ?
>>>> + */
>>>> +
>>>> +#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
>>>
>>> How about getting this offset from dt ?
>>>> +
>>>> +#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.
>>>
>>> Instead of true/false, better to mention 0 on success and -ve on failure.
>>>> + */
>>>> +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)
>>>
>>> Better to move this up where all macros are defined.
>>>
>>>> +
>>>> +/**
>>>> + * 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;
>>>
>>> newline before return, please fix globally.
>>>> +}
>>>> +
>>>> +/**
>>>> + * 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)
>>>
>>> This should be properly aligned.
>>>
>>>> +{
>>>> +       unsigned int curr = 0;
>>>> +       u64 val;
>>>> +       int ret;
>>>> +
>>>> +       debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, base, slave_addr,
>>>> +             buffer, length);
>>>
>>> same alignment.
>>>
>>>> +       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
>>>> --
>>>> 2.26.2
>>>>
>>
>>
>> Viele Grüße,
>> Stefan
>>
>> --
>> DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
>> HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
>> Phone: (+49)-8142-66989-51 Fax: (+49)-8142-66989-80 Email: sr at denx.de


Viele Grüße,
Stefan

-- 
DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-51 Fax: (+49)-8142-66989-80 Email: sr at denx.de


More information about the U-Boot mailing list