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

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
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@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);

Hi Stefan,
On Tue, 26 May 2020 at 06:13, 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: Simon Glass sjg@chromium.org

Hello Stefan,
Am 26.05.2020 um 14:13 schrieb Stefan Roese:
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: Heiko Schocher hs@denx.de
bye, Heiko

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@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

Hello Stefan,
Am 26.05.2020 um 14:13 schrieb Stefan Roese:
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
Applied to u-boot-i2c.git master
Thanks!
bye, Heiko
participants (4)
-
Heiko Schocher
-
Rayagonda Kokatanur
-
Simon Glass
-
Stefan Roese