Signed-off-by: Tim Harvey <thar...@gateworks.com>
---
 configs/thunderx_81xx_defconfig |   2 +
 drivers/i2c/Kconfig             |   7 +
 drivers/i2c/Makefile            |   1 +
 drivers/i2c/thunderx_i2c.c      | 878 ++++++++++++++++++++++++++++++++
 include/configs/thunderx_81xx.h |   2 +
 5 files changed, 890 insertions(+)
 create mode 100644 drivers/i2c/thunderx_i2c.c

diff --git a/configs/thunderx_81xx_defconfig b/configs/thunderx_81xx_defconfig
index d07b0e5804..e43aa9750d 100644
--- a/configs/thunderx_81xx_defconfig
+++ b/configs/thunderx_81xx_defconfig
@@ -22,11 +22,13 @@ CONFIG_SYS_PROMPT="ThunderX_81XX> "
 # CONFIG_CMD_ENV_EXISTS is not set
 # CONFIG_CMD_FLASH is not set
 CONFIG_CMD_GPIO=y
+CONFIG_CMD_I2C=y
 CONFIG_CMD_PCI=y
 # CONFIG_CMD_NET is not set
 CONFIG_DEFAULT_DEVICE_TREE="thunderx-81xx"
 CONFIG_DM=y
 CONFIG_DM_GPIO=y
+CONFIG_DM_I2C=y
 CONFIG_LED=y
 CONFIG_LED_GPIO=y
 # CONFIG_MMC is not set
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 1ef22e6bcd..0424b54eb4 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -374,6 +374,13 @@ 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_THUNDERX
+       bool "ThunderX i2c driver"
+       depends on ARCH_THUNDERX && DM_I2C
+       default y
+       help
+         Enable I2C support for Cavium ThunderX line of processors.
+
 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 d3637bcd8d..27087f1814 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_SYS_I2C_SH) += sh_i2c.o
 obj-$(CONFIG_SYS_I2C_SOFT) += soft_i2c.o
 obj-$(CONFIG_SYS_I2C_STM32F7) += stm32f7_i2c.o
 obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o
+obj-$(CONFIG_SYS_I2C_THUNDERX) += thunderx_i2c.o
 obj-$(CONFIG_SYS_I2C_UNIPHIER) += i2c-uniphier.o
 obj-$(CONFIG_SYS_I2C_UNIPHIER_F) += i2c-uniphier-f.o
 obj-$(CONFIG_SYS_I2C_VERSATILE) += i2c-versatile.o
diff --git a/drivers/i2c/thunderx_i2c.c b/drivers/i2c/thunderx_i2c.c
new file mode 100644
index 0000000000..bac442d3fa
--- /dev/null
+++ b/drivers/i2c/thunderx_i2c.c
@@ -0,0 +1,878 @@
+// SPDX-License-Identifier:    GPL-2.0+
+/*
+ * Copyright (C) 2018, Cavium Inc.
+ */
+
+#include <common.h>
+#include <i2c.h>
+#include <dm.h>
+#include <asm/io.h>
+#include <asm/arch-thunderx/thunderx.h>
+
+/*
+ * Slave address to use for Thunder when accessed by another master
+ */
+#ifndef CONFIG_SYS_I2C_THUNDERX_SLAVE_ADDR
+# define CONFIG_SYS_I2C_THUNDERX_SLAVE_ADDR    0x77
+#endif
+
+#define TWSI_THP               24
+
+#define TWSI_SW_TWSI           0x1000
+#define TWSI_TWSI_SW           0x1008
+#define TWSI_INT               0x1010
+#define TWSI_SW_TWSI_EXT       0x1018
+
+union twsx_sw_twsi {
+       u64 u;
+       struct {
+               u64 data:32;
+               u64 eop_ia:3;
+               u64 ia:5;
+               u64 addr:10;
+               u64 scr:2;
+               u64 size:3;
+               u64 sovr:1;
+               u64 r:1;
+               u64 op:4;
+               u64 eia:1;
+               u64 slonly:1;
+               u64 v:1;
+       } s;
+};
+
+union twsx_sw_twsi_ext {
+       u64 u;
+       struct {
+               u64     data:32;
+               u64     ia:8;
+               u64     :24;
+       } s;
+};
+
+union twsx_int {
+       u64 u;
+       struct {
+               u64 st_int:1;   /** TWSX_SW_TWSI register update int */
+               u64 ts_int:1;   /** TWSX_TWSI_SW register update int */
+               u64 core_int:1; /** TWSI core interrupt, ignored for HLC */
+               u64 :5;         /** Reserved */
+               u64 sda_ovr:1;  /** SDA testing override */
+               u64 scl_ovr:1;  /** SCL testing override */
+               u64 sda:1;      /** SDA signal */
+               u64 scl:1;      /** SCL signal */
+               u64 :52;        /** Reserved */
+       } s;
+};
+
+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),
+};
+
+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
+};
+
+struct thunderx_twsi {
+       int                     id;
+       int                     speed;
+       void                    *baseaddr;
+};
+
+/** Last i2c id assigned */
+static int last_id = 0;
+
+static void twsi_unblock(void *baseaddr);
+static int twsi_stop(void *baseaddr);
+
+
+/**
+ * Converts the i2c status to a meaningful string
+ *
+ * @param      status  status to convert
+ *
+ * @return     string representation of the status
+ */
+static const char *twsi_i2c_status_str(uint8_t status)
+{
+       switch (status) {
+       case TWSI_STAT_BUS_ERROR:
+               return "Bus error";
+       case TWSI_STAT_START:
+               return "START condition transmitted";
+       case TWSI_STAT_RSTART:
+               return "Repeated START condition transmitted";
+       case TWSI_STAT_TXADDR_ACK:
+               return "Address + write bit transmitted, ACK received";
+       case TWSI_STAT_TXADDR_NAK:
+               return "Address + write bit transmitted, NAK received";
+       case TWSI_STAT_TXDATA_ACK:
+               return "Data byte transmitted in master mode, ACK received";
+       case TWSI_STAT_TXDATA_NAK:
+               return "Data byte transmitted in master mode, NAK received";
+       case TWSI_STAT_TX_ARB_LOST:
+               return "Arbitration lost in address or data byte";
+       case TWSI_STAT_RXADDR_ACK:
+               return "Address + read bit transmitted, ACK received";
+       case TWSI_STAT_RXADDR_NAK:
+               return "Address + read bit transmitted, NAK received";
+       case TWSI_STAT_RXDATA_ACK_SENT:
+               return "Data byte received in master mode, ACK transmitted";
+       case TWSI_STAT_RXDATA_NAK_SENT:
+               return "Data byte received in master mode, NAK transmitted";
+       case TWSI_STAT_SLAVE_RXADDR_ACK:
+               return "Slave address + write bit received, ACK transmitted";
+       case TWSI_STAT_TX_ACK_ARB_LOST:
+               return "Arbitration lost in address as master, slave address + 
write bit received, ACK transmitted";
+       case TWSI_STAT_RX_GEN_ADDR_ACK:
+               return "General call address received, ACK transmitted";
+       case TWSI_STAT_RX_GEN_ADDR_ARB_LOST:
+               return "Arbitration lost in address as master, general call 
address received, ACK transmitted";
+       case TWSI_STAT_SLAVE_RXDATA_ACK:
+               return "Data byte received after slave address received, ACK 
transmitted";
+       case TWSI_STAT_SLAVE_RXDATA_NAK:
+               return "Data byte received after slave address received, NAK 
transmitted";
+       case TWSI_STAT_GEN_RXADDR_ACK:
+               return "Data byte received after general call address received, 
ACK transmitted";
+       case TWSI_STAT_GEN_RXADDR_NAK:
+               return "Data byte received after general call address received, 
NAK transmitted";
+       case TWSI_STAT_STOP_MULTI_START:
+               return "STOP or repeated START condition received in slave 
mode";
+       case TWSI_STAT_SLAVE_RXADDR2_ACK:
+               return "Slave address + read bit received, ACK transmitted";
+       case TWSI_STAT_RXDATA_ACK_ARB_LOST:
+               return "Arbitration lost in address as master, slave address + 
read bit received, ACK transmitted";
+       case TWSI_STAT_SLAVE_TXDATA_ACK:
+               return "Data byte transmitted in slave mode, ACK received";
+       case TWSI_STAT_SLAVE_TXDATA_NAK:
+               return "Data byte transmitted in slave mode, NAK received";
+       case TWSI_STAT_SLAVE_TXDATA_END_ACK:
+               return "Last byte transmitted in slave mode, ACK received";
+       case TWSI_STAT_TXADDR2DATA_ACK:
+               return "Second address byte + write bit transmitted, ACK 
received";
+       case TWSI_STAT_TXADDR2DATA_NAK:
+               return "Second address byte + write bit transmitted, NAK 
received";
+       case TWSI_STAT_IDLE:
+               return "Idle";
+       default:
+               return "Unknown status code";
+       }
+}
+
+/**
+ * Returns true if we lost arbitration
+ *
+ * @param      code            status code
+ * @param      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) {
+       /* Arbitration lost */
+       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:
+               return -EAGAIN;
+
+       /* Being addressed as slave, should back off and listen */
+       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:
+               return -EIO;
+
+       /* Core busy as slave */
+       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:
+               return  -EIO;
+
+       /* Ack allowed on pre-terminal bytes only */
+       case TWSI_STAT_RXDATA_ACK_SENT:
+               if (!final_read)
+                       return 0;
+               return -EAGAIN;
+
+       /* NAK allowed on terminal byte only */
+       case TWSI_STAT_RXDATA_NAK_SENT:
+               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
+ *
+ * @param      baseaddr        Base address of i2c registers
+ * @param      sw_twsi         value to write
+ *
+ * @return     0 for success, otherwise error
+ */
+static u64 twsi_write_sw(void *baseaddr, union twsx_sw_twsi sw_twsi)
+{
+       unsigned long start = get_timer(0);
+
+       sw_twsi.s.r = 0;
+       sw_twsi.s.v = 1;
+
+       debug("%s(%p, 0x%llx)\n", __func__, baseaddr, sw_twsi.u);
+       writeq(sw_twsi.u, baseaddr + TWSI_SW_TWSI);
+       do {
+               sw_twsi.u = readq(baseaddr + TWSI_SW_TWSI);
+       } while (sw_twsi.s.v != 0 && get_timer(start) < 50);
+
+       if (sw_twsi.s.v)
+               debug("%s: timed out\n", __func__);
+       return sw_twsi.u;
+}
+
+/**
+ * Reads the MIO_TWS(0..5)_SW_TWSI register
+ *
+ * @param      baseaddr        Base address of i2c registers
+ * @param      sw_twsi         value for eia and op, etc. to read
+ *
+ * @return     value of the register
+ */
+static u64 twsi_read_sw(void *baseaddr, union twsx_sw_twsi sw_twsi)
+{
+       unsigned long start = get_timer(0);
+       sw_twsi.s.r = 1;
+       sw_twsi.s.v = 1;
+
+       debug("%s(%p, 0x%llx)\n", __func__, baseaddr, sw_twsi.u);
+       writeq(sw_twsi.u, baseaddr + TWSI_SW_TWSI);
+
+       do {
+               sw_twsi.u = readq(baseaddr + TWSI_SW_TWSI);
+       } while (sw_twsi.s.v != 0 && get_timer(start) < 50);
+
+       if (sw_twsi.s.v)
+               debug("%s: Error writing 0x%llx\n", __func__, sw_twsi.u);
+
+       debug("%s: Returning 0x%llx\n", __func__, sw_twsi.u);
+       return sw_twsi.u;
+}
+
+/**
+ * Write control register
+ *
+ * @param      baseaddr        Base address for i2c registers
+ * @param      data            data to write
+ */
+static void twsi_write_ctl(void *baseaddr, u8 data)
+{
+       union twsx_sw_twsi twsi_sw;
+
+       debug("%s(%p, 0x%x)\n", __func__, baseaddr, data);
+       twsi_sw.u = 0;
+
+       twsi_sw.s.op     = TWSI_SW_EOP_IA;
+       twsi_sw.s.eop_ia = TWSI_CTL;
+       twsi_sw.s.data   = data;
+
+       twsi_write_sw(baseaddr, twsi_sw);
+}
+
+/**
+ * Reads the TWSI Control Register
+ *
+ * @param[in]  baseaddr        Base address for i2c
+ *
+ * @return     8-bit TWSI control register
+ */
+static u32 twsi_read_ctl(void *baseaddr)
+{
+       union twsx_sw_twsi sw_twsi;
+
+       sw_twsi.u        = 0;
+       sw_twsi.s.op     = TWSI_SW_EOP_IA;
+       sw_twsi.s.eop_ia = TWSI_CTL;
+
+       sw_twsi.u = twsi_read_sw(baseaddr, sw_twsi);
+       debug("%s(%p): 0x%x\n", __func__, baseaddr, sw_twsi.s.data);
+       return sw_twsi.s.data;
+}
+
+/**
+ * Read i2c status register
+ *
+ * @param      baseaddr        Base address of i2c registers
+ *
+ * @return     value of status register
+ */
+static u8 twsi_read_status(void *baseaddr)
+{
+       union twsx_sw_twsi twsi_sw;
+
+       twsi_sw.u       = 0;
+       twsi_sw.s.op    = TWSI_SW_EOP_IA;
+       twsi_sw.s.eop_ia = TWSI_STAT;
+
+       return twsi_read_sw(baseaddr, twsi_sw);
+}
+
+/**
+ * Waits for an i2c operation to complete
+ *
+ * @param      baseaddr        Base address of registers
+ *
+ * @return     0 for success, 1 if timeout
+ */
+static int twsi_wait(void *baseaddr)
+{
+       unsigned long start = get_timer(0);
+       u8 twsi_ctl;
+
+       debug("%s(%p)\n", __func__, baseaddr);
+       do {
+               twsi_ctl = twsi_read_ctl(baseaddr);
+               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
+ *
+ * @param      baseaddr        base address of registers
+ */
+static int twsi_start_unstick(void *baseaddr)
+{
+       twsi_stop(baseaddr);
+
+       twsi_unblock(baseaddr);
+
+       return 0;
+}
+
+/**
+ * Sends an i2c start condition
+ *
+ * @param      baseaddr        base address of registers
+ *
+ * @return     0 for success, otherwise error
+ */
+static int twsi_start(void *baseaddr)
+{
+       int result;
+       u8 stat;
+
+       debug("%s(%p)\n", __func__, baseaddr);
+       twsi_write_ctl(baseaddr, TWSI_CTL_STA | TWSI_CTL_ENAB);
+       result = twsi_wait(baseaddr);
+       if (result) {
+               stat = twsi_read_status(baseaddr);
+               debug("%s: result: 0x%x, status: 0x%x\n", __func__,
+                     result, stat);
+               switch (stat) {
+               case TWSI_STAT_START:
+               case TWSI_STAT_RSTART:
+                       return 0;
+               case TWSI_STAT_RXADDR_ACK:
+               default:
+                       return twsi_start_unstick(baseaddr);
+               }
+       }
+       debug("%s: success\n", __func__);
+       return 0;
+}
+
+/**
+ * Sends an i2c stop condition
+ *
+ * @param      baseaddr        register base address
+ *
+ * @return     0 for success, -1 if error
+ */
+static int twsi_stop(void *baseaddr)
+{
+       u8 stat;
+       twsi_write_ctl(baseaddr, TWSI_CTL_STP | TWSI_CTL_ENAB);
+
+       stat = twsi_read_status(baseaddr);
+       if (stat != TWSI_STAT_IDLE) {
+               debug("%s: Bad status on bus@%p\n", __func__, baseaddr);
+               return -1;
+       }
+       return 0;
+}
+
+/**
+ * Writes data to the i2c bus
+ *
+ * @param      baseraddr       register base address
+ * @param      slave_addr      address of slave to write to
+ * @param      buffer          Pointer to buffer to write
+ * @param      length          Number of bytes in buffer to write
+ *
+ * @return     0 for success, otherwise error
+ */
+static int twsi_write_data(void *baseaddr, u8  slave_addr,
+                          u8 *buffer, unsigned int length)
+{
+       union twsx_sw_twsi twsi_sw;
+       unsigned int curr = 0;
+       int result;
+
+       debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, baseaddr, slave_addr,
+             buffer, length);
+       result = twsi_start(baseaddr);
+       if (result) {
+               debug("%s: Could not start BUS transaction\n", __func__);
+               return -1;
+       }
+
+       result = twsi_wait(baseaddr);
+       if (result) {
+               debug("%s: wait failed\n", __func__);
+               return result;
+       }
+
+       twsi_sw.u        = 0;
+       twsi_sw.s.op     = TWSI_SW_EOP_IA;
+       twsi_sw.s.eop_ia = TWSI_DATA;
+       twsi_sw.s.data   = (u32) (slave_addr << 1) | TWSI_OP_WRITE;
+
+       twsi_write_sw(baseaddr, twsi_sw);
+       twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);
+
+       debug("%s: Waiting\n", __func__);
+       result = twsi_wait(baseaddr);
+       if (result) {
+               debug("%s: Timed out writing slave address 0x%x to target\n",
+                     __func__, slave_addr);
+               return result;
+       }
+       result = twsi_read_status(baseaddr);
+       debug("%s: status: (%d) %s\n", __func__, result,
+             twsi_i2c_status_str(result));
+       if ((result = twsi_read_status(baseaddr)) != TWSI_STAT_TXADDR_ACK) {
+               debug("%s: status: (%d) %s\n", __func__, result,
+                     twsi_i2c_status_str(result));
+               twsi_stop(baseaddr);
+               return twsi_i2c_lost_arb(result, 0);
+       }
+
+       while (curr < length) {
+               twsi_sw.u        = 0;
+               twsi_sw.s.op     = TWSI_SW_EOP_IA;
+               twsi_sw.s.eop_ia = TWSI_DATA;
+               twsi_sw.s.data   = buffer[curr++];
+
+               twsi_write_sw(baseaddr, twsi_sw);
+               twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);
+
+               debug("%s: Writing 0x%x\n", __func__, twsi_sw.s.data);
+
+               result = twsi_wait(baseaddr);
+               if (result) {
+                       debug("%s: Timed out writing data to 0x%x\n",
+                             __func__, slave_addr);
+                       return result;
+               }
+               result = twsi_read_status(baseaddr);
+               debug("%s: status: (%d) %s\n", __func__, result,
+                     twsi_i2c_status_str(result));
+       }
+
+       debug("%s: Stopping\n", __func__);
+       return twsi_stop(baseaddr);
+}
+
+/**
+ * Manually clear the I2C bus and send a stop
+ */
+static void twsi_unblock(void *baseaddr)
+{
+       int i;
+       union twsx_int  int_reg;
+
+       int_reg.u = 0;
+       for (i = 0; i < 9; i++) {
+               int_reg.s.scl_ovr = 0;
+               writeq(int_reg.u, baseaddr + TWSI_INT);
+               udelay(5);
+               int_reg.s.scl_ovr = 1;
+               writeq(int_reg.u, baseaddr + TWSI_INT);
+               udelay(5);
+       }
+       int_reg.s.sda_ovr = 1;
+       writeq(int_reg.u, baseaddr + TWSI_INT);
+       udelay(5);
+       int_reg.s.scl_ovr = 0;
+       writeq(int_reg.u, baseaddr + TWSI_INT);
+       udelay(5);
+       int_reg.u = 0;
+       writeq(int_reg.u, baseaddr + TWSI_INT);
+       udelay(5);
+}
+
+/**
+ * Performs a read transaction on the i2c bus
+ *
+ * @param      baseaddr        Base address of twsi registers
+ * @param      slave_addr      i2c bus address to read from
+ * @param      buffer          buffer to read into
+ * @param      length          number of bytes to read
+ *
+ * @return     0 for success, otherwise error
+ */
+static int twsi_read_data(void *baseaddr, u8 slave_addr,
+                         u8 *buffer, unsigned int length)
+{
+       union twsx_sw_twsi twsi_sw;
+       unsigned int curr = 0;
+       int result;
+
+       debug("%s(%p, 0x%x, %p, %u)\n", __func__, baseaddr, slave_addr,
+             buffer, length);
+       result = twsi_start(baseaddr);
+       if (result) {
+               debug("%s: start failed\n", __func__);
+               return result;
+       }
+
+       result = twsi_wait(baseaddr);
+       if (result) {
+               debug("%s: wait failed\n", __func__);
+               return result;
+       }
+
+       twsi_sw.u        = 0;
+       twsi_sw.s.op     = TWSI_SW_EOP_IA;
+       twsi_sw.s.eop_ia = TWSI_DATA;
+
+       twsi_sw.s.data  = (u32) (slave_addr << 1) | TWSI_OP_READ;
+
+       twsi_write_sw(baseaddr, twsi_sw);
+       twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);
+
+       result = twsi_wait(baseaddr);
+       if (result) {
+               debug("%s: waiting for sending addr failed\n", __func__);
+               return result;
+       }
+
+       result = twsi_read_status(baseaddr);
+       debug("%s: status: (%d) %s\n", __func__, result,
+             twsi_i2c_status_str(result));
+       if (result != TWSI_STAT_RXADDR_ACK) {
+               debug("%s: status: (%d) %s\n", __func__, result,
+                     twsi_i2c_status_str(result));
+               twsi_stop(baseaddr);
+               return twsi_i2c_lost_arb(result, 0);
+       }
+
+       while (curr < length) {
+               twsi_write_ctl(baseaddr, TWSI_CTL_ENAB |
+                               ((curr < length - 1) ? TWSI_CTL_AAK : 0));
+
+               result = twsi_wait(baseaddr);
+               if (result) {
+                       debug("%s: waiting for data failed\n", __func__);
+                       return result;
+               }
+
+               twsi_sw.u = twsi_read_sw(baseaddr, twsi_sw);
+               buffer[curr++] = twsi_sw.s.data;
+       }
+
+       twsi_stop(baseaddr);
+
+       return 0;
+}
+
+static int twsi_init(void *baseaddr, unsigned int speed, int slaveaddr)
+{
+       int io_clock_hz;
+       int n_div;
+       int m_div;
+       union twsx_sw_twsi sw_twsi;
+
+       debug("%s(%p, %u, 0x%x)\n", __func__, baseaddr, speed, slaveaddr);
+       io_clock_hz = thunderx_get_io_clock();
+
+       /* Set the TWSI clock to a conservative TWSI_BUS_FREQ.  Compute the
+        * clocks M divider based on the SCLK.
+        * TWSI freq = (core freq) / (20 x (M+1) x (thp+1) x 2^N)
+        * M = ((core freq) / (20 x (TWSI freq) x (thp+1) x 2^N)) - 1
+        */
+       for (n_div = 0; n_div < 8; n_div++) {
+               m_div = io_clock_hz / (20 * speed * (TWSI_THP + 1));
+               m_div /= 1 << n_div;
+               m_div -= 1;
+               if (m_div < 16)
+                       break;
+       }
+       if (m_div >= 16)
+               return -1;
+
+       sw_twsi.u = 0;
+       sw_twsi.s.v = 1;        /* Clear valid bit */
+       sw_twsi.s.op = 0x6;     /* See EOP field */
+       sw_twsi.s.r = 0;        /* Select CLKCTL when R = 0 */
+       sw_twsi.s.eop_ia = 3;   /* R=0 selects CLKCTL, R=1 selects STAT */
+       sw_twsi.s.data = ((m_div & 0xf) << 3) | ((n_div & 0x7) << 0);
+
+       twsi_write_sw(baseaddr, sw_twsi);
+       /* Only init non-slave ports */
+       debug("%s: Writing 0x%llx to sw_twsi, m_div: 0x%x, n_div: 0x%x\n",
+             __func__, sw_twsi.u, m_div, n_div);
+
+
+       sw_twsi.u = 0;
+       sw_twsi.s.v = 1;
+       sw_twsi.s.op = TWSI_SW_EOP_IA;
+       sw_twsi.s.r = 0;
+       sw_twsi.s.eop_ia = 0;
+       sw_twsi.s.data = slaveaddr << 1;
+
+       twsi_write_sw(baseaddr, sw_twsi);
+
+       /* Set slave address */
+       sw_twsi.u = 0;
+       sw_twsi.s.v = 1;
+       sw_twsi.s.op = TWSI_SW_EOP_IA;
+       sw_twsi.s.r = 0;
+       sw_twsi.s.eop_ia = TWSI_EOP_SLAVE_ADDR;
+       sw_twsi.s.data = slaveaddr;
+       twsi_write_sw(baseaddr, sw_twsi);
+
+       return 0;
+}
+
+/**
+ * Transfers data over the i2c bus
+ *
+ * @param      bus     i2c bus to transfer data over
+ * @param      msg     Array of i2c messages
+ * @param      nmsgs   Number of messages to send/receive
+ *
+ * @return     0 for success, otherwise error
+ */
+static int thunderx_i2c_xfer(struct udevice *bus, struct i2c_msg *msg,
+                            int nmsgs)
+{
+       struct thunderx_twsi *twsi = dev_get_priv(bus);
+       int result;
+
+       debug("thunderx_i2c_xfer: %d messages\n", nmsgs);
+       for (; nmsgs > 0; nmsgs--, msg++) {
+               debug("thunderx_i2c_xfer: chip=0x%x, len=0x%x\n",
+                     msg->addr, msg->len);
+               if (msg->flags & I2C_M_RD) {
+                       debug("%s: Reading data\n", __func__);
+                       result = twsi_read_data(twsi->baseaddr, msg->addr,
+                                            msg->buf, msg->len);
+               } else {
+                       debug("%s: Writing data\n", __func__);
+                       result = twsi_write_data(twsi->baseaddr, msg->addr,
+                                             msg->buf, msg->len);
+               }
+               if (result) {
+                       debug("thunderx_i2c_xfer: error sending\n");
+                       return -EREMOTEIO;
+               }
+       }
+
+       return 0;
+}
+
+static int thunderx_i2c_set_bus_speed(struct udevice *bus, unsigned int speed)
+{
+       struct thunderx_twsi *twsi = dev_get_priv(bus);
+       int m_div, n_div;
+       unsigned io_clock_hz;
+       union twsx_sw_twsi sw_twsi;
+       void *baseaddr = twsi->baseaddr;
+
+       io_clock_hz = thunderx_get_io_clock();
+       debug("%s(%p, %u) io clock: %u\n", __func__, bus, speed, io_clock_hz);
+
+       /* Set the TWSI clock to a conservative TWSI_BUS_FREQ.  Compute the
+        * clocks M divider based on the SCLK.
+        * TWSI freq = (core freq) / (20 x (M+1) x (thp+1) x 2^N)
+        * M = ((core freq) / (20 x (TWSI freq) x (thp+1) x 2^N)) - 1 */
+       for (n_div = 0; n_div < 8; n_div++) {
+               m_div = io_clock_hz / (20 * speed * (TWSI_THP + 1));
+               m_div /= 1 << n_div;
+               m_div -= 1;
+               if (m_div < 16)
+                       break;
+       }
+       if (m_div >= 16)
+               return -1;
+
+       sw_twsi.u = 0;
+       sw_twsi.s.v = 1;                /* Clear valid bit */
+       sw_twsi.s.op = TWSI_SW_EOP_IA;  /* See EOP field */
+       sw_twsi.s.r = 0;                /* Select CLKCTL when R = 0 */
+       sw_twsi.s.eop_ia = TWSI_CLKCTL; /* R=0 selects CLKCTL, R=1 selects STAT 
*/
+       sw_twsi.s.data = ((m_div & 0xf) << 3) | ((n_div & 0x7) << 0);
+
+       /* Only init non-slave ports */
+       writeq(sw_twsi.u, baseaddr + TWSI_SW_TWSI);
+
+       debug("%s: Wrote 0x%llx to sw_twsi\n", __func__, sw_twsi.u);
+       return 0;
+}
+
+static int thunderx_pci_i2c_probe(struct udevice *dev)
+{
+       struct thunderx_twsi *twsi = dev_get_priv(dev);
+       size_t size;
+       pci_dev_t bdf = dm_pci_get_bdf(dev);
+
+       debug("TWSI PCI device: %x\n", bdf);
+       dev->req_seq = PCI_FUNC(bdf);
+
+       twsi->baseaddr = dm_pci_map_bar(dev, 0, &size, PCI_REGION_MEM);
+       twsi->id = last_id++;
+
+       debug("TWSI bus %d at %p\n",dev->seq, twsi->baseaddr);
+
+       return twsi_init(twsi->baseaddr, CONFIG_SYS_I2C_SPEED,
+                        CONFIG_SYS_I2C_THUNDERX_SLAVE_ADDR);
+}
+
+static const struct dm_i2c_ops thunderx_i2c_ops = {
+       .xfer           = thunderx_i2c_xfer,
+       .set_bus_speed  = thunderx_i2c_set_bus_speed,
+};
+
+static const struct udevice_id thunderx_i2c_ids[] = {
+       { .compatible = "cavium,thunder-8890-twsi" },
+       { }
+};
+
+U_BOOT_DRIVER(thunderx_pci_twsi) = {
+       .name   = "i2c_thunderx",
+       .id     = UCLASS_I2C,
+       .of_match = thunderx_i2c_ids,
+       .probe  = thunderx_pci_i2c_probe,
+       .priv_auto_alloc_size = sizeof(struct thunderx_twsi),
+       .ops    = &thunderx_i2c_ops,
+};
+
+static struct pci_device_id thunderx_pci_twsi_supported[] = {
+       { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_THUNDERX_TWSI) },
+       { },
+};
+
+U_BOOT_PCI_DEVICE(thunderx_pci_twsi, thunderx_pci_twsi_supported);
diff --git a/include/configs/thunderx_81xx.h b/include/configs/thunderx_81xx.h
index 10c4a89232..7a2c0adde2 100644
--- a/include/configs/thunderx_81xx.h
+++ b/include/configs/thunderx_81xx.h
@@ -68,4 +68,6 @@
 #define PLL_REF_CLK                    50000000        /* 50 MHz */
 #define NS_PER_REF_CLK_TICK            (1000000000/PLL_REF_CLK)
 
+#define CONFIG_SYS_I2C_SPEED           100000
+
 #endif /* __THUNDERX_81XX_H__ */
-- 
2.17.1

_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
https://lists.denx.de/listinfo/u-boot

Reply via email to