* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon @ 2020-05-14 7:23 Stefan Roese 2020-05-19 6:07 ` Heiko Schocher ` (2 more replies) 0 siblings, 3 replies; 9+ messages in thread From: Stefan Roese @ 2020-05-14 7:23 UTC (permalink / raw) To: u-boot 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> --- RFC -> v1 (Stefan): - Separated this patch from the OcteonTX/TX2 RFC patch series into a single patch. This is useful, as the upcoming MIPS Octeon support will use this I2C driver. - Added MIPS Octeon II/III support (big endian). Rename driver and its function names from "octeontx" to "octeon" to better match all Octeon platforms. - Moved from union to defines / bitmasks as suggested by Simon. This makes the driver usage on little- and big-endian platforms much easier. - Enhanced Kconfig text - Removed all clock macros (use values from DT) - Removed long driver debug strings. This is only available when a debug version of this driver is built. The user / developer can lookup the descriptive error messages in the driver in this case anyway. - Removed static "last_id" - Dropped misc blank lines. Misc reformatting. - Dropped "!= 0" - Added missing function comments - Added missing strut comments - Changed comment style - Renames "result" to "ret" - Hex numbers uppercase - Minor other changes - Reword commit text and subject drivers/i2c/Kconfig | 10 + drivers/i2c/Makefile | 1 + drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 814 insertions(+) create mode 100644 drivers/i2c/octeon_i2c.c diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index e42b6516bf..1330b36698 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -374,6 +374,16 @@ config SYS_I2C_SANDBOX bus. Devices can be attached to the bus using the device tree which specifies the driver to use. See sandbox.dts as an example. +config SYS_I2C_OCTEON + bool "Octeon II/III/TX/TX2 I2C driver" + depends on (ARCH_OCTEON || ARCH_OCTEONTX || ARCH_OCTEONTX2) && DM_I2C + default y + help + Add support for the Marvell Octeon I2C driver. This is used with + various Octeon parts such as Octeon II/III and OcteonTX/TX2. All + chips have several I2C ports and all are provided, controlled by + the device tree. + config SYS_I2C_S3C24X0 bool "Samsung I2C driver" depends on ARCH_EXYNOS4 && DM_I2C diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 62935b7ebc..2b58aae892 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o +obj-$(CONFIG_SYS_I2C_OCTEON) += octeon_i2c.o obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o obj-$(CONFIG_SYS_I2C_RCAR_I2C) += rcar_i2c.o obj-$(CONFIG_SYS_I2C_RCAR_IIC) += rcar_iic.o diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c new file mode 100644 index 0000000000..210f98655e --- /dev/null +++ b/drivers/i2c/octeon_i2c.c @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell International Ltd. + */ + +#include <common.h> +#include <i2c.h> +#include <dm.h> +#include <pci_ids.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <linux/bitfield.h> + +/* + * Octeon II/III (MIPS) have different register offsets than the ARM based + * Octeon TX/TX2 SoCs + */ +#if defined(CONFIG_ARCH_OCTEON) +#define REG_OFFS 0x0000 +#else +#define REG_OFFS 0x1000 +#endif + +#define TWSI_SW_TWSI (REG_OFFS + 0x00) +#define TWSI_TWSI_SW (REG_OFFS + 0x08) +#define TWSI_INT (REG_OFFS + 0x10) +#define TWSI_SW_TWSI_EXT (REG_OFFS + 0x18) + +#define TWSI_SW_DATA_MASK GENMASK_ULL(31, 0) +#define TWSI_SW_EOP_IA_MASK GENMASK_ULL(34, 32) +#define TWSI_SW_IA_MASK GENMASK_ULL(39, 35) +#define TWSI_SW_ADDR_MASK GENMASK_ULL(49, 40) +#define TWSI_SW_SCR_MASK GENMASK_ULL(51, 50) +#define TWSI_SW_SIZE_MASK GENMASK_ULL(54, 52) +#define TWSI_SW_SOVR BIT_ULL(55) +#define TWSI_SW_R BIT_ULL(56) +#define TWSI_SW_OP_MASK GENMASK_ULL(60, 57) +#define TWSI_SW_EIA GENMASK_ULL(61) +#define TWSI_SW_SLONLY BIT_ULL(62) +#define TWSI_SW_V BIT_ULL(63) + +#define TWSI_INT_SDA_OVR BIT_ULL(8) +#define TWSI_INT_SCL_OVR BIT_ULL(9) +#define TWSI_INT_SDA BIT_ULL(10) +#define TWSI_INT_SCL BIT_ULL(11) + +enum { + TWSI_OP_WRITE = 0, + TWSI_OP_READ = 1, +}; + +enum { + TWSI_EOP_SLAVE_ADDR = 0, + TWSI_EOP_CLK_CTL = 3, + TWSI_SW_EOP_IA = 6, +}; + +enum { + TWSI_SLAVEADD = 0, + TWSI_DATA = 1, + TWSI_CTL = 2, + TWSI_CLKCTL = 3, + TWSI_STAT = 3, + TWSI_SLAVEADD_EXT = 4, + TWSI_RST = 7, +}; + +enum { + TWSI_CTL_AAK = BIT(2), + TWSI_CTL_IFLG = BIT(3), + TWSI_CTL_STP = BIT(4), + TWSI_CTL_STA = BIT(5), + TWSI_CTL_ENAB = BIT(6), + TWSI_CTL_CE = BIT(7), +}; + +/* + * Internal errors. When debugging is enabled, the driver will report the + * error number and the user / developer can check the table below for the + * detailed error description. + */ +enum { + /** Bus error */ + TWSI_STAT_BUS_ERROR = 0x00, + /** Start condition transmitted */ + TWSI_STAT_START = 0x08, + /** Repeat start condition transmitted */ + TWSI_STAT_RSTART = 0x10, + /** Address + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR_ACK = 0x18, + /** Address + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR_NAK = 0x20, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_ACK = 0x28, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_NAK = 0x30, + /** Arbitration lost in address or data byte */ + TWSI_STAT_TX_ARB_LOST = 0x38, + /** Address + read bit transmitted, ACK received */ + TWSI_STAT_RXADDR_ACK = 0x40, + /** Address + read bit transmitted, /ACK received */ + TWSI_STAT_RXADDR_NAK = 0x48, + /** Data byte received in master mode, ACK transmitted */ + TWSI_STAT_RXDATA_ACK_SENT = 0x50, + /** Data byte received, NACK transmitted */ + TWSI_STAT_RXDATA_NAK_SENT = 0x58, + /** Slave address received, sent ACK */ + TWSI_STAT_SLAVE_RXADDR_ACK = 0x60, + /** + * Arbitration lost in address as master, slave address + write bit + * received, ACK transmitted + */ + TWSI_STAT_TX_ACK_ARB_LOST = 0x68, + /** General call address received, ACK transmitted */ + TWSI_STAT_RX_GEN_ADDR_ACK = 0x70, + /** + * Arbitration lost in address as master, general call address + * received, ACK transmitted + */ + TWSI_STAT_RX_GEN_ADDR_ARB_LOST = 0x78, + /** Data byte received after slave address received, ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_ACK = 0x80, + /** Data byte received after slave address received, /ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_NAK = 0x88, + /** + * Data byte received after general call address received, ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_ACK = 0x90, + /** + * Data byte received after general call address received, /ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_NAK = 0x98, + /** STOP or repeated START condition received in slave mode */ + TWSI_STAT_STOP_MULTI_START = 0xa0, + /** Slave address + read bit received, ACK transmitted */ + TWSI_STAT_SLAVE_RXADDR2_ACK = 0xa8, + /** + * Arbitration lost in address as master, slave address + read bit + * received, ACK transmitted + */ + TWSI_STAT_RXDATA_ACK_ARB_LOST = 0xb0, + /** Data byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_ACK = 0xb8, + /** Data byte transmitted in slave mode, /ACK received */ + TWSI_STAT_SLAVE_TXDATA_NAK = 0xc0, + /** Last byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_END_ACK = 0xc8, + /** Second address byte + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR2DATA_ACK = 0xd0, + /** Second address byte + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR2DATA_NAK = 0xd8, + /** No relevant status information */ + TWSI_STAT_IDLE = 0xf8 +}; + +#ifndef CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR +# define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR 0x77 +#endif + +/** + * struct octeon_twsi - Private data of this driver + * + * @base: Base address of i2c registers + */ +struct octeon_twsi { + void __iomem *base; +}; + +static void twsi_unblock(void *base); +static int twsi_stop(void *base); + +#if defined(CONFIG_ARCH_OCTEON) +static int get_io_clock(void) +{ + return octeon_get_io_clock(); +} +#else +static int get_io_clock(void) +{ + return octeontx_get_io_clock(); +} +#endif + +static int twsi_thp(void) +{ + if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX)) + return 24; + else + return 3; +} + +/** + * Returns true if we lost arbitration + * + * @code status code + * @final_read true if this is the final read operation + * @return true if arbitration has been lost, false if it hasn't been lost. + */ +static int twsi_i2c_lost_arb(u8 code, int final_read) +{ + switch (code) { + case TWSI_STAT_TX_ARB_LOST: + case TWSI_STAT_TX_ACK_ARB_LOST: + case TWSI_STAT_RX_GEN_ADDR_ARB_LOST: + case TWSI_STAT_RXDATA_ACK_ARB_LOST: + /* Arbitration lost */ + return -EAGAIN; + + case TWSI_STAT_SLAVE_RXADDR_ACK: + case TWSI_STAT_RX_GEN_ADDR_ACK: + case TWSI_STAT_GEN_RXADDR_ACK: + case TWSI_STAT_GEN_RXADDR_NAK: + /* Being addressed as slave, should back off and listen */ + return -EIO; + + case TWSI_STAT_SLAVE_RXDATA_ACK: + case TWSI_STAT_SLAVE_RXDATA_NAK: + case TWSI_STAT_STOP_MULTI_START: + case TWSI_STAT_SLAVE_RXADDR2_ACK: + case TWSI_STAT_SLAVE_TXDATA_ACK: + case TWSI_STAT_SLAVE_TXDATA_NAK: + case TWSI_STAT_SLAVE_TXDATA_END_ACK: + /* Core busy as slave */ + return -EIO; + + case TWSI_STAT_RXDATA_ACK_SENT: + /* Ack allowed on pre-terminal bytes only */ + if (!final_read) + return 0; + return -EAGAIN; + + case TWSI_STAT_RXDATA_NAK_SENT: + /* NAK allowed on terminal byte only */ + if (!final_read) + return 0; + return -EAGAIN; + + case TWSI_STAT_TXDATA_NAK: + case TWSI_STAT_TXADDR_NAK: + case TWSI_STAT_RXADDR_NAK: + case TWSI_STAT_TXADDR2DATA_NAK: + return -EAGAIN; + } + + return 0; +} + +#define RST_BOOT_PNR_MUL(val) (((val) >> 33) & 0x1F) + +/** + * Writes to the MIO_TWS(0..5)_SW_TWSI register + * + * @base Base address of i2c registers + * @val value to write + * @return 0 for success, otherwise error + */ +static u64 twsi_write_sw(void __iomem *base, u64 val) +{ + unsigned long start = get_timer(0); + + val &= ~TWSI_SW_R; + val |= TWSI_SW_V; + + debug("%s(%p, 0x%llx)\n", __func__, base, val); + writeq(val, base + TWSI_SW_TWSI); + do { + val = readq(base + TWSI_SW_TWSI); + } while ((val & TWSI_SW_V) && (get_timer(start) < 50)); + + if (val & TWSI_SW_V) + debug("%s: timed out\n", __func__); + return val; +} + +/** + * Reads the MIO_TWS(0..5)_SW_TWSI register + * + * @base Base address of i2c registers + * @val value for eia and op, etc. to read + * @return value of the register + */ +static u64 twsi_read_sw(void __iomem *base, u64 val) +{ + unsigned long start = get_timer(0); + + val |= TWSI_SW_R | TWSI_SW_V; + + debug("%s(%p, 0x%llx)\n", __func__, base, val); + writeq(val, base + TWSI_SW_TWSI); + + do { + val = readq(base + TWSI_SW_TWSI); + } while ((val & TWSI_SW_V) && (get_timer(start) < 50)); + + if (val & TWSI_SW_V) + debug("%s: Error writing 0x%llx\n", __func__, val); + + debug("%s: Returning 0x%llx\n", __func__, val); + return val; +} + +/** + * Write control register + * + * @base Base address for i2c registers + * @data data to write + */ +static void twsi_write_ctl(void __iomem *base, u8 data) +{ + u64 val; + + debug("%s(%p, 0x%x)\n", __func__, base, data); + val = data | FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); +} + +/** + * Reads the TWSI Control Register + * + * @base Base address for i2c + * @return 8-bit TWSI control register + */ +static u8 twsi_read_ctl(void __iomem *base) +{ + u64 val; + + val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + val = twsi_read_sw(base, val); + + debug("%s(%p): 0x%x\n", __func__, base, (u8)val); + return (u8)val; +} + +/** + * Read i2c status register + * + * @base Base address of i2c registers + * @return value of status register + */ +static u8 twsi_read_status(void __iomem *base) +{ + u64 val; + + val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_STAT) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + + return twsi_read_sw(base, val); +} + +/** + * Waits for an i2c operation to complete + * + * @param base Base address of registers + * @return 0 for success, 1 if timeout + */ +static int twsi_wait(void __iomem *base) +{ + unsigned long start = get_timer(0); + u8 twsi_ctl; + + debug("%s(%p)\n", __func__, base); + do { + twsi_ctl = twsi_read_ctl(base); + twsi_ctl &= TWSI_CTL_IFLG; + } while (!twsi_ctl && get_timer(start) < 50); + + debug(" return: %u\n", !twsi_ctl); + return !twsi_ctl; +} + +/** + * Unsticks the i2c bus + * + * @base base address of registers + */ +static int twsi_start_unstick(void __iomem *base) +{ + twsi_stop(base); + twsi_unblock(base); + + return 0; +} + +/** + * Sends an i2c start condition + * + * @base base address of registers + * @return 0 for success, otherwise error + */ +static int twsi_start(void __iomem *base) +{ + int ret; + u8 stat; + + debug("%s(%p)\n", __func__, base); + twsi_write_ctl(base, TWSI_CTL_STA | TWSI_CTL_ENAB); + ret = twsi_wait(base); + if (ret) { + stat = twsi_read_status(base); + debug("%s: ret: 0x%x, status: 0x%x\n", __func__, ret, stat); + switch (stat) { + case TWSI_STAT_START: + case TWSI_STAT_RSTART: + return 0; + case TWSI_STAT_RXADDR_ACK: + default: + return twsi_start_unstick(base); + } + } + + debug("%s: success\n", __func__); + return 0; +} + +/** + * Sends an i2c stop condition + * + * @base register base address + * @return 0 for success, -1 if error + */ +static int twsi_stop(void __iomem *base) +{ + u8 stat; + + twsi_write_ctl(base, TWSI_CTL_STP | TWSI_CTL_ENAB); + + stat = twsi_read_status(base); + if (stat != TWSI_STAT_IDLE) { + debug("%s: Bad status on bus@%p\n", __func__, base); + return -1; + } + + return 0; +} + +/** + * Writes data to the i2c bus + * + * @base register base address + * @slave_addr address of slave to write to + * @buffer Pointer to buffer to write + * @length Number of bytes in buffer to write + * @return 0 for success, otherwise error + */ +static int twsi_write_data(void __iomem *base, u8 slave_addr, + u8 *buffer, unsigned int length) +{ + unsigned int curr = 0; + u64 val; + int ret; + + debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, base, slave_addr, + buffer, length); + ret = twsi_start(base); + if (ret) { + debug("%s: Could not start BUS transaction\n", __func__); + return -1; + } + + ret = twsi_wait(base); + if (ret) { + debug("%s: wait failed\n", __func__); + return ret; + } + + val = (u32)(slave_addr << 1) | TWSI_OP_WRITE | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + debug("%s: Waiting\n", __func__); + ret = twsi_wait(base); + if (ret) { + debug("%s: Timed out writing slave address 0x%x to target\n", + __func__, slave_addr); + return ret; + } + + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + if (ret != TWSI_STAT_TXADDR_ACK) { + debug("%s: status: 0x%x\n", __func__, ret); + twsi_stop(base); + return twsi_i2c_lost_arb(ret, 0); + } + + while (curr < length) { + val = buffer[curr++] | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + debug("%s: Writing 0x%llx\n", __func__, val); + + ret = twsi_wait(base); + if (ret) { + debug("%s: Timed out writing data to 0x%x\n", + __func__, slave_addr); + return ret; + } + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + } + + debug("%s: Stopping\n", __func__); + return twsi_stop(base); +} + +/** + * Manually clear the I2C bus and send a stop + * + * @base register base address + */ +static void twsi_unblock(void __iomem *base) +{ + int i; + + for (i = 0; i < 9; i++) { + writeq(0, base + TWSI_INT); + udelay(5); + writeq(TWSI_INT_SCL_OVR, base + TWSI_INT); + udelay(5); + } + writeq(TWSI_INT_SCL_OVR | TWSI_INT_SDA_OVR, base + TWSI_INT); + udelay(5); + writeq(TWSI_INT_SDA_OVR, base + TWSI_INT); + udelay(5); + writeq(0, base + TWSI_INT); + udelay(5); +} + +/** + * Performs a read transaction on the i2c bus + * + * @base Base address of twsi registers + * @slave_addr i2c bus address to read from + * @buffer buffer to read into + * @length number of bytes to read + * @return 0 for success, otherwise error + */ +static int twsi_read_data(void __iomem *base, u8 slave_addr, + u8 *buffer, unsigned int length) +{ + unsigned int curr = 0; + u64 val; + int ret; + + debug("%s(%p, 0x%x, %p, %u)\n", __func__, base, slave_addr, + buffer, length); + ret = twsi_start(base); + if (ret) { + debug("%s: start failed\n", __func__); + return ret; + } + + ret = twsi_wait(base); + if (ret) { + debug("%s: wait failed\n", __func__); + return ret; + } + + val = (u32)(slave_addr << 1) | TWSI_OP_READ | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + ret = twsi_wait(base); + if (ret) { + debug("%s: waiting for sending addr failed\n", __func__); + return ret; + } + + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + if (ret != TWSI_STAT_RXADDR_ACK) { + debug("%s: status: 0x%x\n", __func__, ret); + twsi_stop(base); + return twsi_i2c_lost_arb(ret, 0); + } + + while (curr < length) { + twsi_write_ctl(base, TWSI_CTL_ENAB | + ((curr < length - 1) ? TWSI_CTL_AAK : 0)); + + ret = twsi_wait(base); + if (ret) { + debug("%s: waiting for data failed\n", __func__); + return ret; + } + + val = twsi_read_sw(base, val); + buffer[curr++] = (u8)val; + } + + twsi_stop(base); + + return 0; +} + +/** + * Calculate the divisor values + * + * @speed Speed to set + * @m_div Pointer to M divisor + * @n_div Pointer to N divisor + * @return 0 for success, otherwise error + */ +static void twsi_calc_div(unsigned int speed, int *m_div, int *n_div) +{ + int io_clock_hz; + int tclk, fsamp; + int ndiv, mdiv; + +#if defined(CONFIG_ARCH_OCTEON) || defined(CONFIG_ARCH_OCTEONTX) + io_clock_hz = get_io_clock(); + tclk = io_clock_hz / (2 * (twsi_thp() + 1)); +#elif defined(CONFIG_ARCH_OCTEONTX2) + /* Refclk src in mode register defaults to 100MHz clock */ + io_clock_hz = 100000000; /* 100 Mhz */ + tclk = io_clock_hz / (twsi_thp() + 2); +#endif + debug("%s( io_clock %u tclk %u)\n", __func__, io_clock_hz, tclk); + + /* + * Compute the clocks M divider: + * + * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N) + * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1 + * + * For OcteonTX2 - + * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N) + * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1 + */ + for (ndiv = 0; ndiv < 8; ndiv++) { + fsamp = tclk / (1 << ndiv); + mdiv = fsamp / speed / 10; + mdiv -= 1; + if (mdiv < 16) + break; + } + + *m_div = mdiv; + *n_div = ndiv; +} + +/** + * Init I2C controller + * + * @base Base address of twsi registers + * @slave_addr I2C slave address to configure this controller to + * @return 0 for success, otherwise error + */ +static int twsi_init(void __iomem *base, int slaveaddr) +{ + u64 val; + + debug("%s (%p, 0x%x)\n", __func__, base, slaveaddr); + + val = slaveaddr << 1 | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, 0) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + twsi_write_sw(base, val); + + /* Set slave address */ + val = slaveaddr | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_EOP_SLAVE_ADDR) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + twsi_write_sw(base, val); + + return 0; +} + +/** + * Transfers data over the i2c bus + * + * @bus i2c bus to transfer data over + * @msg Array of i2c messages + * @nmsgs Number of messages to send/receive + * @return 0 for success, otherwise error + */ +static int octeon_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, + int nmsgs) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int ret; + int i; + + debug("%s: %d messages\n", __func__, nmsgs); + for (i = 0; i < nmsgs; i++, msg++) { + debug("%s: chip=0x%x, len=0x%x\n", __func__, msg->addr, + msg->len); + + if (msg->flags & I2C_M_RD) { + debug("%s: Reading data\n", __func__); + ret = twsi_read_data(twsi->base, msg->addr, + msg->buf, msg->len); + } else { + debug("%s: Writing data\n", __func__); + ret = twsi_write_data(twsi->base, msg->addr, + msg->buf, msg->len); + } + if (ret) { + debug("%s: error sending\n", __func__); + return -EREMOTEIO; + } + } + + return 0; +} + +/** + * Set I2C bus speed + * + * @bus i2c bus to transfer data over + * @speed Speed in Hz to set + * @return 0 for success, otherwise error + */ +static int octeon_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int m_div, n_div; + u64 val; + + debug("%s(%p, %u)\n", __func__, bus, speed); + + twsi_calc_div(speed, &m_div, &n_div); + if (m_div >= 16) + return -1; + + val = (u32)(((m_div & 0xf) << 3) | ((n_div & 0x7) << 0)) | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CLKCTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + /* Only init non-slave ports */ + writeq(val, twsi->base + TWSI_SW_TWSI); + + debug("%s: Wrote 0x%llx to sw_twsi\n", __func__, val); + return 0; +} + +/** + * Driver probe function + * + * @dev I2C device to probe + * @return 0 for success, otherwise error + */ +static int octeon_pci_i2c_probe(struct udevice *dev) +{ + struct octeon_twsi *twsi = dev_get_priv(dev); +#if !defined(CONFIG_ARCH_OCTEON) + pci_dev_t bdf = dm_pci_get_bdf(dev); + + debug("TWSI PCI device: %x\n", bdf); + dev->req_seq = PCI_FUNC(bdf); + + twsi->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, + PCI_REGION_MEM); +#else + twsi->base = dev_remap_addr(dev); +#endif + debug("TWSI bus %d at %p\n", dev->seq, twsi->base); + + /* Start with standard speed, real speed set via DT or cmd */ + return twsi_init(twsi->base, CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR); +} + +static const struct dm_i2c_ops octeon_i2c_ops = { + .xfer = octeon_i2c_xfer, + .set_bus_speed = octeon_i2c_set_bus_speed, +}; + +static const struct udevice_id octeon_i2c_ids[] = { + { .compatible = "cavium,thunder-8890-twsi" }, + { .compatible = "cavium,octeon-7890-twsi" }, + { } +}; + +U_BOOT_DRIVER(octeon_pci_twsi) = { + .name = "i2c_octeon", + .id = UCLASS_I2C, + .of_match = octeon_i2c_ids, + .probe = octeon_pci_i2c_probe, + .priv_auto_alloc_size = sizeof(struct octeon_twsi), + .ops = &octeon_i2c_ops, +}; + +#if !defined(CONFIG_ARCH_OCTEON) +static struct pci_device_id octeon_twsi_supported[] = { + { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI) }, + { }, +}; + +U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported); +#endif -- 2.26.2 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-05-14 7:23 [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon Stefan Roese @ 2020-05-19 6:07 ` Heiko Schocher 2020-05-26 6:49 ` Stefan Roese 2020-05-19 12:32 ` Simon Glass 2020-06-03 5:15 ` Rayagonda Kokatanur 2 siblings, 1 reply; 9+ messages in thread From: Heiko Schocher @ 2020-05-19 6:07 UTC (permalink / raw) To: u-boot Hello Stefan, Am 14.05.2020 um 09:23 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> > --- > RFC -> v1 (Stefan): > - Separated this patch from the OcteonTX/TX2 RFC patch series into a > single patch. This is useful, as the upcoming MIPS Octeon support will > use this I2C driver. > - Added MIPS Octeon II/III support (big endian). Rename driver and its > function names from "octeontx" to "octeon" to better match all Octeon > platforms. > - Moved from union to defines / bitmasks as suggested by Simon. This makes > the driver usage on little- and big-endian platforms much easier. > - Enhanced Kconfig text > - Removed all clock macros (use values from DT) > - Removed long driver debug strings. This is only available when a debug > version of this driver is built. The user / developer can lookup the > descriptive error messages in the driver in this case anyway. > - Removed static "last_id" > - Dropped misc blank lines. Misc reformatting. > - Dropped "!= 0" > - Added missing function comments > - Added missing strut comments > - Changed comment style > - Renames "result" to "ret" > - Hex numbers uppercase > - Minor other changes > - Reword commit text and subject > > drivers/i2c/Kconfig | 10 + > drivers/i2c/Makefile | 1 + > drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 814 insertions(+) > create mode 100644 drivers/i2c/octeon_i2c.c nitpick only ... Please add a documentation of the device tree bindings. [...] > diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c > new file mode 100644 > index 0000000000..210f98655e > --- /dev/null > +++ b/drivers/i2c/octeon_i2c.c > @@ -0,0 +1,803 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Marvell International Ltd. > + */ > + > +#include <common.h> > +#include <i2c.h> > +#include <dm.h> > +#include <pci_ids.h> > +#include <asm/io.h> > +#include <asm/arch/clock.h> > +#include <linux/bitfield.h> > + > +/* > + * Octeon II/III (MIPS) have different register offsets than the ARM based > + * Octeon TX/TX2 SoCs > + */ > +#if defined(CONFIG_ARCH_OCTEON) > +#define REG_OFFS 0x0000 > +#else > +#define REG_OFFS 0x1000 > +#endif > + > +#define TWSI_SW_TWSI (REG_OFFS + 0x00) > +#define TWSI_TWSI_SW (REG_OFFS + 0x08) Is there no clearer name ? Just wonderung about "TWSI" .. we already have an i2c driver with TWSI defines: https://gitlab.denx.de/u-boot/u-boot/-/blob/master/drivers/i2c/mvtwsi.c But it seems they have no common parts. [...] > +#if defined(CONFIG_ARCH_OCTEON) > +static int get_io_clock(void) > +{ > + return octeon_get_io_clock(); > +} > +#else > +static int get_io_clock(void) > +{ > + return octeontx_get_io_clock(); > +} > +#endif Here would be good to have the clk framework... > + > +static int twsi_thp(void) > +{ > + if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX)) > + return 24; > + else > + return 3; > +} May you can add here some comments for this magic numbers? [...] > +#define RST_BOOT_PNR_MUL(val) (((val) >> 33) & 0x1F) not used define, please remove. [...] bye, Heiko -- DENX Software Engineering GmbH, Managing Director: Wolfgang Denk HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany Phone: +49-8142-66989-52 Fax: +49-8142-66989-80 Email: hs at denx.de ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-05-19 6:07 ` Heiko Schocher @ 2020-05-26 6:49 ` Stefan Roese 0 siblings, 0 replies; 9+ messages in thread From: Stefan Roese @ 2020-05-26 6:49 UTC (permalink / raw) To: u-boot Hi Heiko, On 19.05.20 08:07, Heiko Schocher wrote: > Hello Stefan, > > Am 14.05.2020 um 09:23 schrieb Stefan Roese: >> From: Suneel Garapati <sgarapati@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> >> --- >> RFC -> v1 (Stefan): >> - Separated this patch from the OcteonTX/TX2 RFC patch series into a >> ?? single patch. This is useful, as the upcoming MIPS Octeon support will >> ?? use this I2C driver. >> - Added MIPS Octeon II/III support (big endian). Rename driver and its >> ?? function names from "octeontx" to "octeon" to better match all Octeon >> ?? platforms. >> - Moved from union to defines / bitmasks as suggested by Simon. This >> makes >> ?? the driver usage on little- and big-endian platforms much easier. >> - Enhanced Kconfig text >> - Removed all clock macros (use values from DT) >> - Removed long driver debug strings. This is only available when a debug >> ?? version of this driver is built. The user / developer can lookup the >> ?? descriptive error messages in the driver in this case anyway. >> - Removed static "last_id" >> - Dropped misc blank lines. Misc reformatting. >> - Dropped "!= 0" >> - Added missing function comments >> - Added missing strut comments >> - Changed comment style >> - Renames "result" to "ret" >> - Hex numbers uppercase >> - Minor other changes >> - Reword commit text and subject >> >> ? drivers/i2c/Kconfig????? |? 10 + >> ? drivers/i2c/Makefile???? |?? 1 + >> ? drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++ >> ? 3 files changed, 814 insertions(+) >> ? create mode 100644 drivers/i2c/octeon_i2c.c > > nitpick only ... > > Please add a documentation of the device tree bindings. Okay. > [...] >> diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c >> new file mode 100644 >> index 0000000000..210f98655e >> --- /dev/null >> +++ b/drivers/i2c/octeon_i2c.c >> @@ -0,0 +1,803 @@ >> +// SPDX-License-Identifier:??? GPL-2.0 >> +/* >> + * Copyright (C) 2018 Marvell International Ltd. >> + */ >> + >> +#include <common.h> >> +#include <i2c.h> >> +#include <dm.h> >> +#include <pci_ids.h> >> +#include <asm/io.h> >> +#include <asm/arch/clock.h> >> +#include <linux/bitfield.h> >> + >> +/* >> + * Octeon II/III (MIPS) have different register offsets than the ARM >> based >> + * Octeon TX/TX2 SoCs >> + */ >> +#if defined(CONFIG_ARCH_OCTEON) >> +#define REG_OFFS??????? 0x0000 >> +#else >> +#define REG_OFFS??????? 0x1000 >> +#endif >> + >> +#define TWSI_SW_TWSI??????? (REG_OFFS + 0x00) >> +#define TWSI_TWSI_SW??????? (REG_OFFS + 0x08) > > Is there no clearer name ? > > Just wonderung about "TWSI" .. we already have an i2c driver with TWSI > defines: > > https://gitlab.denx.de/u-boot/u-boot/-/blob/master/drivers/i2c/mvtwsi.c > > But it seems they have no common parts. TWSI is another abbreviation for I2C: Two Wire Serial Interface. I would like to keep these macros as they reflect the register names in the Cavium / Marvell manuals. > [...] >> +#if defined(CONFIG_ARCH_OCTEON) >> +static int get_io_clock(void) >> +{ >> +??? return octeon_get_io_clock(); >> +} >> +#else >> +static int get_io_clock(void) >> +{ >> +??? return octeontx_get_io_clock(); >> +} >> +#endif > > Here would be good to have the clk framework... Yes, it would be great. I'm thinking about adding a minimal clk driver for Octeon - not sure, if I get it done shortly though. >> + >> +static int twsi_thp(void) >> +{ >> +??? if (IS_ENABLED(CONFIG_ARCH_OCTEON) || >> IS_ENABLED(CONFIG_ARCH_OCTEONTX)) >> +??????? return 24; >> +??? else >> +??????? return 3; >> +} > > May you can add here some comments for this magic numbers? I didn't write the initial version of this driver, so its hard for me to come up with details here. But I'll try. > [...] >> +#define RST_BOOT_PNR_MUL(val)? (((val) >> 33) & 0x1F) > > not used define, please remove. Ah, thanks for spotting. Thanks, Stefan ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-05-14 7:23 [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon Stefan Roese 2020-05-19 6:07 ` Heiko Schocher @ 2020-05-19 12:32 ` Simon Glass 2020-05-26 10:55 ` Stefan Roese 2020-06-03 5:15 ` Rayagonda Kokatanur 2 siblings, 1 reply; 9+ messages in thread From: Simon Glass @ 2020-05-19 12:32 UTC (permalink / raw) To: u-boot Hi, On Thu, 14 May 2020 at 01:23, 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> > --- > RFC -> v1 (Stefan): > - Separated this patch from the OcteonTX/TX2 RFC patch series into a > single patch. This is useful, as the upcoming MIPS Octeon support will > use this I2C driver. > - Added MIPS Octeon II/III support (big endian). Rename driver and its > function names from "octeontx" to "octeon" to better match all Octeon > platforms. > - Moved from union to defines / bitmasks as suggested by Simon. This makes > the driver usage on little- and big-endian platforms much easier. > - Enhanced Kconfig text > - Removed all clock macros (use values from DT) > - Removed long driver debug strings. This is only available when a debug > version of this driver is built. The user / developer can lookup the > descriptive error messages in the driver in this case anyway. > - Removed static "last_id" > - Dropped misc blank lines. Misc reformatting. > - Dropped "!= 0" > - Added missing function comments > - Added missing strut comments > - Changed comment style > - Renames "result" to "ret" > - Hex numbers uppercase > - Minor other changes > - Reword commit text and subject > > drivers/i2c/Kconfig | 10 + > drivers/i2c/Makefile | 1 + > drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 814 insertions(+) > create mode 100644 drivers/i2c/octeon_i2c.c > > diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig > index e42b6516bf..1330b36698 100644 > --- a/drivers/i2c/Kconfig > +++ b/drivers/i2c/Kconfig > @@ -374,6 +374,16 @@ config SYS_I2C_SANDBOX > bus. Devices can be attached to the bus using the device tree > which specifies the driver to use. See sandbox.dts as an example. > > +config SYS_I2C_OCTEON > + bool "Octeon II/III/TX/TX2 I2C driver" > + depends on (ARCH_OCTEON || ARCH_OCTEONTX || ARCH_OCTEONTX2) && DM_I2C > + default y > + help > + Add support for the Marvell Octeon I2C driver. This is used with > + various Octeon parts such as Octeon II/III and OcteonTX/TX2. All > + chips have several I2C ports and all are provided, controlled by > + the device tree. > + > config SYS_I2C_S3C24X0 > bool "Samsung I2C driver" > depends on ARCH_EXYNOS4 && DM_I2C > diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile > index 62935b7ebc..2b58aae892 100644 > --- a/drivers/i2c/Makefile > +++ b/drivers/i2c/Makefile > @@ -27,6 +27,7 @@ obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o > obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o > obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o > obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o > +obj-$(CONFIG_SYS_I2C_OCTEON) += octeon_i2c.o > obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o > obj-$(CONFIG_SYS_I2C_RCAR_I2C) += rcar_i2c.o > obj-$(CONFIG_SYS_I2C_RCAR_IIC) += rcar_iic.o > diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c > new file mode 100644 > index 0000000000..210f98655e > --- /dev/null > +++ b/drivers/i2c/octeon_i2c.c > @@ -0,0 +1,803 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Marvell International Ltd. > + */ > + > +#include <common.h> > +#include <i2c.h> > +#include <dm.h> > +#include <pci_ids.h> > +#include <asm/io.h> > +#include <asm/arch/clock.h> > +#include <linux/bitfield.h> > + > +/* > + * Octeon II/III (MIPS) have different register offsets than the ARM based > + * Octeon TX/TX2 SoCs > + */ > +#if defined(CONFIG_ARCH_OCTEON) > +#define REG_OFFS 0x0000 > +#else > +#define REG_OFFS 0x1000 > +#endif > + > +#define TWSI_SW_TWSI (REG_OFFS + 0x00) > +#define TWSI_TWSI_SW (REG_OFFS + 0x08) > +#define TWSI_INT (REG_OFFS + 0x10) > +#define TWSI_SW_TWSI_EXT (REG_OFFS + 0x18) > + > +#define TWSI_SW_DATA_MASK GENMASK_ULL(31, 0) > +#define TWSI_SW_EOP_IA_MASK GENMASK_ULL(34, 32) > +#define TWSI_SW_IA_MASK GENMASK_ULL(39, 35) > +#define TWSI_SW_ADDR_MASK GENMASK_ULL(49, 40) > +#define TWSI_SW_SCR_MASK GENMASK_ULL(51, 50) > +#define TWSI_SW_SIZE_MASK GENMASK_ULL(54, 52) > +#define TWSI_SW_SOVR BIT_ULL(55) > +#define TWSI_SW_R BIT_ULL(56) > +#define TWSI_SW_OP_MASK GENMASK_ULL(60, 57) > +#define TWSI_SW_EIA GENMASK_ULL(61) > +#define TWSI_SW_SLONLY BIT_ULL(62) > +#define TWSI_SW_V BIT_ULL(63) > + > +#define TWSI_INT_SDA_OVR BIT_ULL(8) > +#define TWSI_INT_SCL_OVR BIT_ULL(9) > +#define TWSI_INT_SDA BIT_ULL(10) > +#define TWSI_INT_SCL BIT_ULL(11) > + > +enum { > + TWSI_OP_WRITE = 0, > + TWSI_OP_READ = 1, > +}; > + > +enum { > + TWSI_EOP_SLAVE_ADDR = 0, > + TWSI_EOP_CLK_CTL = 3, > + TWSI_SW_EOP_IA = 6, > +}; > + > +enum { > + TWSI_SLAVEADD = 0, > + TWSI_DATA = 1, > + TWSI_CTL = 2, > + TWSI_CLKCTL = 3, > + TWSI_STAT = 3, > + TWSI_SLAVEADD_EXT = 4, > + TWSI_RST = 7, > +}; > + > +enum { > + TWSI_CTL_AAK = BIT(2), > + TWSI_CTL_IFLG = BIT(3), > + TWSI_CTL_STP = BIT(4), > + TWSI_CTL_STA = BIT(5), > + TWSI_CTL_ENAB = BIT(6), > + TWSI_CTL_CE = BIT(7), > +}; > + > +/* > + * Internal errors. When debugging is enabled, the driver will report the > + * error number and the user / developer can check the table below for the > + * detailed error description. > + */ > +enum { > + /** Bus error */ > + TWSI_STAT_BUS_ERROR = 0x00, > + /** Start condition transmitted */ > + TWSI_STAT_START = 0x08, > + /** Repeat start condition transmitted */ > + TWSI_STAT_RSTART = 0x10, > + /** Address + write bit transmitted, ACK received */ > + TWSI_STAT_TXADDR_ACK = 0x18, > + /** Address + write bit transmitted, /ACK received */ > + TWSI_STAT_TXADDR_NAK = 0x20, > + /** Data byte transmitted in master mode, ACK received */ > + TWSI_STAT_TXDATA_ACK = 0x28, > + /** Data byte transmitted in master mode, ACK received */ > + TWSI_STAT_TXDATA_NAK = 0x30, > + /** Arbitration lost in address or data byte */ > + TWSI_STAT_TX_ARB_LOST = 0x38, > + /** Address + read bit transmitted, ACK received */ > + TWSI_STAT_RXADDR_ACK = 0x40, > + /** Address + read bit transmitted, /ACK received */ > + TWSI_STAT_RXADDR_NAK = 0x48, > + /** Data byte received in master mode, ACK transmitted */ > + TWSI_STAT_RXDATA_ACK_SENT = 0x50, > + /** Data byte received, NACK transmitted */ > + TWSI_STAT_RXDATA_NAK_SENT = 0x58, > + /** Slave address received, sent ACK */ > + TWSI_STAT_SLAVE_RXADDR_ACK = 0x60, > + /** > + * Arbitration lost in address as master, slave address + write bit > + * received, ACK transmitted > + */ > + TWSI_STAT_TX_ACK_ARB_LOST = 0x68, > + /** General call address received, ACK transmitted */ > + TWSI_STAT_RX_GEN_ADDR_ACK = 0x70, > + /** > + * Arbitration lost in address as master, general call address > + * received, ACK transmitted > + */ > + TWSI_STAT_RX_GEN_ADDR_ARB_LOST = 0x78, > + /** Data byte received after slave address received, ACK transmitted */ > + TWSI_STAT_SLAVE_RXDATA_ACK = 0x80, > + /** Data byte received after slave address received, /ACK transmitted */ > + TWSI_STAT_SLAVE_RXDATA_NAK = 0x88, > + /** > + * Data byte received after general call address received, ACK > + * transmitted > + */ > + TWSI_STAT_GEN_RXADDR_ACK = 0x90, > + /** > + * Data byte received after general call address received, /ACK > + * transmitted > + */ > + TWSI_STAT_GEN_RXADDR_NAK = 0x98, > + /** STOP or repeated START condition received in slave mode */ > + TWSI_STAT_STOP_MULTI_START = 0xa0, > + /** Slave address + read bit received, ACK transmitted */ > + TWSI_STAT_SLAVE_RXADDR2_ACK = 0xa8, > + /** > + * Arbitration lost in address as master, slave address + read bit > + * received, ACK transmitted > + */ > + TWSI_STAT_RXDATA_ACK_ARB_LOST = 0xb0, > + /** Data byte transmitted in slave mode, ACK received */ > + TWSI_STAT_SLAVE_TXDATA_ACK = 0xb8, > + /** Data byte transmitted in slave mode, /ACK received */ > + TWSI_STAT_SLAVE_TXDATA_NAK = 0xc0, > + /** Last byte transmitted in slave mode, ACK received */ > + TWSI_STAT_SLAVE_TXDATA_END_ACK = 0xc8, > + /** Second address byte + write bit transmitted, ACK received */ > + TWSI_STAT_TXADDR2DATA_ACK = 0xd0, > + /** Second address byte + write bit transmitted, /ACK received */ > + TWSI_STAT_TXADDR2DATA_NAK = 0xd8, > + /** No relevant status information */ > + TWSI_STAT_IDLE = 0xf8 > +}; > + > +#ifndef CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR > +# define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR 0x77 This should be a device-tree property. > +#endif > + > +/** > + * struct octeon_twsi - Private data of this driver > + * > + * @base: Base address of i2c registers > + */ > +struct octeon_twsi { > + void __iomem *base; > +}; > + > +static void twsi_unblock(void *base); > +static int twsi_stop(void *base); > + > +#if defined(CONFIG_ARCH_OCTEON) Needs a clock driver. > +static int get_io_clock(void) > +{ > + return octeon_get_io_clock(); > +} > +#else > +static int get_io_clock(void) > +{ > + return octeontx_get_io_clock(); > +} > +#endif > + > +static int twsi_thp(void) > +{ > + if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX)) > + return 24; > + else > + return 3; > +} > + [..] > +/** > + * Calculate the divisor values > + * > + * @speed Speed to set > + * @m_div Pointer to M divisor > + * @n_div Pointer to N divisor > + * @return 0 for success, otherwise error > + */ > +static void twsi_calc_div(unsigned int speed, int *m_div, int *n_div) > +{ > + int io_clock_hz; > + int tclk, fsamp; > + int ndiv, mdiv; > + > +#if defined(CONFIG_ARCH_OCTEON) || defined(CONFIG_ARCH_OCTEONTX) > + io_clock_hz = get_io_clock(); > + tclk = io_clock_hz / (2 * (twsi_thp() + 1)); > +#elif defined(CONFIG_ARCH_OCTEONTX2) > + /* Refclk src in mode register defaults to 100MHz clock */ > + io_clock_hz = 100000000; /* 100 Mhz */ > + tclk = io_clock_hz / (twsi_thp() + 2); > +#endif Needs a clock driver. If different logic is needed it should use the compatible string / driver data rather than #ifdefs. > + debug("%s( io_clock %u tclk %u)\n", __func__, io_clock_hz, tclk); > + > + /* > + * Compute the clocks M divider: > + * > + * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N) > + * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1 > + * > + * For OcteonTX2 - > + * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N) > + * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1 > + */ > + for (ndiv = 0; ndiv < 8; ndiv++) { > + fsamp = tclk / (1 << ndiv); > + mdiv = fsamp / speed / 10; > + mdiv -= 1; > + if (mdiv < 16) > + break; > + } > + > + *m_div = mdiv; > + *n_div = ndiv; > +} > + > +/** [..] > +/** > + * Driver probe function > + * > + * @dev I2C device to probe > + * @return 0 for success, otherwise error > + */ > +static int octeon_pci_i2c_probe(struct udevice *dev) > +{ > + struct octeon_twsi *twsi = dev_get_priv(dev); > +#if !defined(CONFIG_ARCH_OCTEON) Again should use dev_get_driver_data() > + pci_dev_t bdf = dm_pci_get_bdf(dev); > + > + debug("TWSI PCI device: %x\n", bdf); > + dev->req_seq = PCI_FUNC(bdf); > + > + twsi->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, > + PCI_REGION_MEM); > +#else > + twsi->base = dev_remap_addr(dev); > +#endif > + debug("TWSI bus %d at %p\n", dev->seq, twsi->base); > + > + /* Start with standard speed, real speed set via DT or cmd */ > + return twsi_init(twsi->base, CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR); > +} > + > +static const struct dm_i2c_ops octeon_i2c_ops = { > + .xfer = octeon_i2c_xfer, > + .set_bus_speed = octeon_i2c_set_bus_speed, > +}; > + > +static const struct udevice_id octeon_i2c_ids[] = { > + { .compatible = "cavium,thunder-8890-twsi" }, > + { .compatible = "cavium,octeon-7890-twsi" }, > + { } > +}; > + > +U_BOOT_DRIVER(octeon_pci_twsi) = { > + .name = "i2c_octeon", > + .id = UCLASS_I2C, > + .of_match = octeon_i2c_ids, > + .probe = octeon_pci_i2c_probe, > + .priv_auto_alloc_size = sizeof(struct octeon_twsi), > + .ops = &octeon_i2c_ops, > +}; > + > +#if !defined(CONFIG_ARCH_OCTEON) > +static struct pci_device_id octeon_twsi_supported[] = { > + { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI) }, > + { }, > +}; > + > +U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported); > +#endif > -- > 2.26.2 > Regards, Simon ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-05-19 12:32 ` Simon Glass @ 2020-05-26 10:55 ` Stefan Roese 0 siblings, 0 replies; 9+ messages in thread From: Stefan Roese @ 2020-05-26 10:55 UTC (permalink / raw) To: u-boot Hi Simon, On 19.05.20 14:32, Simon Glass wrote: > Hi, > > On Thu, 14 May 2020 at 01:23, 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> >> --- >> RFC -> v1 (Stefan): >> - Separated this patch from the OcteonTX/TX2 RFC patch series into a >> single patch. This is useful, as the upcoming MIPS Octeon support will >> use this I2C driver. >> - Added MIPS Octeon II/III support (big endian). Rename driver and its >> function names from "octeontx" to "octeon" to better match all Octeon >> platforms. >> - Moved from union to defines / bitmasks as suggested by Simon. This makes >> the driver usage on little- and big-endian platforms much easier. >> - Enhanced Kconfig text >> - Removed all clock macros (use values from DT) >> - Removed long driver debug strings. This is only available when a debug >> version of this driver is built. The user / developer can lookup the >> descriptive error messages in the driver in this case anyway. >> - Removed static "last_id" >> - Dropped misc blank lines. Misc reformatting. >> - Dropped "!= 0" >> - Added missing function comments >> - Added missing strut comments >> - Changed comment style >> - Renames "result" to "ret" >> - Hex numbers uppercase >> - Minor other changes >> - Reword commit text and subject >> >> drivers/i2c/Kconfig | 10 + >> drivers/i2c/Makefile | 1 + >> drivers/i2c/octeon_i2c.c | 803 +++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 814 insertions(+) >> create mode 100644 drivers/i2c/octeon_i2c.c >> >> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig >> index e42b6516bf..1330b36698 100644 >> --- a/drivers/i2c/Kconfig >> +++ b/drivers/i2c/Kconfig >> @@ -374,6 +374,16 @@ config SYS_I2C_SANDBOX >> bus. Devices can be attached to the bus using the device tree >> which specifies the driver to use. See sandbox.dts as an example. >> >> +config SYS_I2C_OCTEON >> + bool "Octeon II/III/TX/TX2 I2C driver" >> + depends on (ARCH_OCTEON || ARCH_OCTEONTX || ARCH_OCTEONTX2) && DM_I2C >> + default y >> + help >> + Add support for the Marvell Octeon I2C driver. This is used with >> + various Octeon parts such as Octeon II/III and OcteonTX/TX2. All >> + chips have several I2C ports and all are provided, controlled by >> + the device tree. >> + >> config SYS_I2C_S3C24X0 >> bool "Samsung I2C driver" >> depends on ARCH_EXYNOS4 && DM_I2C >> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile >> index 62935b7ebc..2b58aae892 100644 >> --- a/drivers/i2c/Makefile >> +++ b/drivers/i2c/Makefile >> @@ -27,6 +27,7 @@ obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o >> obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o >> obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o >> obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o >> +obj-$(CONFIG_SYS_I2C_OCTEON) += octeon_i2c.o >> obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o >> obj-$(CONFIG_SYS_I2C_RCAR_I2C) += rcar_i2c.o >> obj-$(CONFIG_SYS_I2C_RCAR_IIC) += rcar_iic.o >> diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c >> new file mode 100644 >> index 0000000000..210f98655e >> --- /dev/null >> +++ b/drivers/i2c/octeon_i2c.c >> @@ -0,0 +1,803 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * Copyright (C) 2018 Marvell International Ltd. >> + */ >> + >> +#include <common.h> >> +#include <i2c.h> >> +#include <dm.h> >> +#include <pci_ids.h> >> +#include <asm/io.h> >> +#include <asm/arch/clock.h> >> +#include <linux/bitfield.h> >> + >> +/* >> + * Octeon II/III (MIPS) have different register offsets than the ARM based >> + * Octeon TX/TX2 SoCs >> + */ >> +#if defined(CONFIG_ARCH_OCTEON) >> +#define REG_OFFS 0x0000 >> +#else >> +#define REG_OFFS 0x1000 >> +#endif >> + >> +#define TWSI_SW_TWSI (REG_OFFS + 0x00) >> +#define TWSI_TWSI_SW (REG_OFFS + 0x08) >> +#define TWSI_INT (REG_OFFS + 0x10) >> +#define TWSI_SW_TWSI_EXT (REG_OFFS + 0x18) >> + >> +#define TWSI_SW_DATA_MASK GENMASK_ULL(31, 0) >> +#define TWSI_SW_EOP_IA_MASK GENMASK_ULL(34, 32) >> +#define TWSI_SW_IA_MASK GENMASK_ULL(39, 35) >> +#define TWSI_SW_ADDR_MASK GENMASK_ULL(49, 40) >> +#define TWSI_SW_SCR_MASK GENMASK_ULL(51, 50) >> +#define TWSI_SW_SIZE_MASK GENMASK_ULL(54, 52) >> +#define TWSI_SW_SOVR BIT_ULL(55) >> +#define TWSI_SW_R BIT_ULL(56) >> +#define TWSI_SW_OP_MASK GENMASK_ULL(60, 57) >> +#define TWSI_SW_EIA GENMASK_ULL(61) >> +#define TWSI_SW_SLONLY BIT_ULL(62) >> +#define TWSI_SW_V BIT_ULL(63) >> + >> +#define TWSI_INT_SDA_OVR BIT_ULL(8) >> +#define TWSI_INT_SCL_OVR BIT_ULL(9) >> +#define TWSI_INT_SDA BIT_ULL(10) >> +#define TWSI_INT_SCL BIT_ULL(11) >> + >> +enum { >> + TWSI_OP_WRITE = 0, >> + TWSI_OP_READ = 1, >> +}; >> + >> +enum { >> + TWSI_EOP_SLAVE_ADDR = 0, >> + TWSI_EOP_CLK_CTL = 3, >> + TWSI_SW_EOP_IA = 6, >> +}; >> + >> +enum { >> + TWSI_SLAVEADD = 0, >> + TWSI_DATA = 1, >> + TWSI_CTL = 2, >> + TWSI_CLKCTL = 3, >> + TWSI_STAT = 3, >> + TWSI_SLAVEADD_EXT = 4, >> + TWSI_RST = 7, >> +}; >> + >> +enum { >> + TWSI_CTL_AAK = BIT(2), >> + TWSI_CTL_IFLG = BIT(3), >> + TWSI_CTL_STP = BIT(4), >> + TWSI_CTL_STA = BIT(5), >> + TWSI_CTL_ENAB = BIT(6), >> + TWSI_CTL_CE = BIT(7), >> +}; >> + >> +/* >> + * Internal errors. When debugging is enabled, the driver will report the >> + * error number and the user / developer can check the table below for the >> + * detailed error description. >> + */ >> +enum { >> + /** Bus error */ >> + TWSI_STAT_BUS_ERROR = 0x00, >> + /** Start condition transmitted */ >> + TWSI_STAT_START = 0x08, >> + /** Repeat start condition transmitted */ >> + TWSI_STAT_RSTART = 0x10, >> + /** Address + write bit transmitted, ACK received */ >> + TWSI_STAT_TXADDR_ACK = 0x18, >> + /** Address + write bit transmitted, /ACK received */ >> + TWSI_STAT_TXADDR_NAK = 0x20, >> + /** Data byte transmitted in master mode, ACK received */ >> + TWSI_STAT_TXDATA_ACK = 0x28, >> + /** Data byte transmitted in master mode, ACK received */ >> + TWSI_STAT_TXDATA_NAK = 0x30, >> + /** Arbitration lost in address or data byte */ >> + TWSI_STAT_TX_ARB_LOST = 0x38, >> + /** Address + read bit transmitted, ACK received */ >> + TWSI_STAT_RXADDR_ACK = 0x40, >> + /** Address + read bit transmitted, /ACK received */ >> + TWSI_STAT_RXADDR_NAK = 0x48, >> + /** Data byte received in master mode, ACK transmitted */ >> + TWSI_STAT_RXDATA_ACK_SENT = 0x50, >> + /** Data byte received, NACK transmitted */ >> + TWSI_STAT_RXDATA_NAK_SENT = 0x58, >> + /** Slave address received, sent ACK */ >> + TWSI_STAT_SLAVE_RXADDR_ACK = 0x60, >> + /** >> + * Arbitration lost in address as master, slave address + write bit >> + * received, ACK transmitted >> + */ >> + TWSI_STAT_TX_ACK_ARB_LOST = 0x68, >> + /** General call address received, ACK transmitted */ >> + TWSI_STAT_RX_GEN_ADDR_ACK = 0x70, >> + /** >> + * Arbitration lost in address as master, general call address >> + * received, ACK transmitted >> + */ >> + TWSI_STAT_RX_GEN_ADDR_ARB_LOST = 0x78, >> + /** Data byte received after slave address received, ACK transmitted */ >> + TWSI_STAT_SLAVE_RXDATA_ACK = 0x80, >> + /** Data byte received after slave address received, /ACK transmitted */ >> + TWSI_STAT_SLAVE_RXDATA_NAK = 0x88, >> + /** >> + * Data byte received after general call address received, ACK >> + * transmitted >> + */ >> + TWSI_STAT_GEN_RXADDR_ACK = 0x90, >> + /** >> + * Data byte received after general call address received, /ACK >> + * transmitted >> + */ >> + TWSI_STAT_GEN_RXADDR_NAK = 0x98, >> + /** STOP or repeated START condition received in slave mode */ >> + TWSI_STAT_STOP_MULTI_START = 0xa0, >> + /** Slave address + read bit received, ACK transmitted */ >> + TWSI_STAT_SLAVE_RXADDR2_ACK = 0xa8, >> + /** >> + * Arbitration lost in address as master, slave address + read bit >> + * received, ACK transmitted >> + */ >> + TWSI_STAT_RXDATA_ACK_ARB_LOST = 0xb0, >> + /** Data byte transmitted in slave mode, ACK received */ >> + TWSI_STAT_SLAVE_TXDATA_ACK = 0xb8, >> + /** Data byte transmitted in slave mode, /ACK received */ >> + TWSI_STAT_SLAVE_TXDATA_NAK = 0xc0, >> + /** Last byte transmitted in slave mode, ACK received */ >> + TWSI_STAT_SLAVE_TXDATA_END_ACK = 0xc8, >> + /** Second address byte + write bit transmitted, ACK received */ >> + TWSI_STAT_TXADDR2DATA_ACK = 0xd0, >> + /** Second address byte + write bit transmitted, /ACK received */ >> + TWSI_STAT_TXADDR2DATA_NAK = 0xd8, >> + /** No relevant status information */ >> + TWSI_STAT_IDLE = 0xf8 >> +}; >> + >> +#ifndef CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR >> +# define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR 0x77 > > This should be a device-tree property. Okay, will move to DT. >> +#endif >> + >> +/** >> + * struct octeon_twsi - Private data of this driver >> + * >> + * @base: Base address of i2c registers >> + */ >> +struct octeon_twsi { >> + void __iomem *base; >> +}; >> + >> +static void twsi_unblock(void *base); >> +static int twsi_stop(void *base); >> + >> +#if defined(CONFIG_ARCH_OCTEON) > > Needs a clock driver. Right. I'll try to add clock driver support with the first Octeon base port. >> +static int get_io_clock(void) >> +{ >> + return octeon_get_io_clock(); >> +} >> +#else >> +static int get_io_clock(void) >> +{ >> + return octeontx_get_io_clock(); >> +} >> +#endif >> + >> +static int twsi_thp(void) >> +{ >> + if (IS_ENABLED(CONFIG_ARCH_OCTEON) || IS_ENABLED(CONFIG_ARCH_OCTEONTX)) >> + return 24; >> + else >> + return 3; >> +} >> + > > [..] > >> +/** >> + * Calculate the divisor values >> + * >> + * @speed Speed to set >> + * @m_div Pointer to M divisor >> + * @n_div Pointer to N divisor >> + * @return 0 for success, otherwise error >> + */ >> +static void twsi_calc_div(unsigned int speed, int *m_div, int *n_div) >> +{ >> + int io_clock_hz; >> + int tclk, fsamp; >> + int ndiv, mdiv; >> + >> +#if defined(CONFIG_ARCH_OCTEON) || defined(CONFIG_ARCH_OCTEONTX) >> + io_clock_hz = get_io_clock(); >> + tclk = io_clock_hz / (2 * (twsi_thp() + 1)); >> +#elif defined(CONFIG_ARCH_OCTEONTX2) >> + /* Refclk src in mode register defaults to 100MHz clock */ >> + io_clock_hz = 100000000; /* 100 Mhz */ >> + tclk = io_clock_hz / (twsi_thp() + 2); >> +#endif > > Needs a clock driver. If different logic is needed it should use the > compatible string / driver data rather than #ifdefs. Yes, will update. >> + debug("%s( io_clock %u tclk %u)\n", __func__, io_clock_hz, tclk); >> + >> + /* >> + * Compute the clocks M divider: >> + * >> + * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N) >> + * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1 >> + * >> + * For OcteonTX2 - >> + * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N) >> + * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1 >> + */ >> + for (ndiv = 0; ndiv < 8; ndiv++) { >> + fsamp = tclk / (1 << ndiv); >> + mdiv = fsamp / speed / 10; >> + mdiv -= 1; >> + if (mdiv < 16) >> + break; >> + } >> + >> + *m_div = mdiv; >> + *n_div = ndiv; >> +} >> + >> +/** > > [..] > >> +/** >> + * Driver probe function >> + * >> + * @dev I2C device to probe >> + * @return 0 for success, otherwise error >> + */ >> +static int octeon_pci_i2c_probe(struct udevice *dev) >> +{ >> + struct octeon_twsi *twsi = dev_get_priv(dev); >> +#if !defined(CONFIG_ARCH_OCTEON) > > Again should use dev_get_driver_data() Yes, will update in v2. Thanks, Stefan ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-05-14 7:23 [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon Stefan Roese 2020-05-19 6:07 ` Heiko Schocher 2020-05-19 12:32 ` Simon Glass @ 2020-06-03 5:15 ` Rayagonda Kokatanur 2020-06-03 5:20 ` Stefan Roese 2 siblings, 1 reply; 9+ messages in thread From: Rayagonda Kokatanur @ 2020-06-03 5:15 UTC (permalink / raw) To: u-boot Hi Stefan, Few minor comments, On Thu, May 14, 2020 at 12:53 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> > --- > 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 > ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-06-03 5:15 ` Rayagonda Kokatanur @ 2020-06-03 5:20 ` Stefan Roese 2020-06-03 5:37 ` Rayagonda Kokatanur 0 siblings, 1 reply; 9+ messages in thread From: Stefan Roese @ 2020-06-03 5:20 UTC (permalink / raw) To: u-boot Hi Rayagonda, v1 is superseeded. Please review the latest version, v2: https://patchwork.ozlabs.org/project/uboot/patch/20200526121307.2735-1-sr at denx.de/ 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@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> >> --- >> 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 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-06-03 5:20 ` Stefan Roese @ 2020-06-03 5:37 ` Rayagonda Kokatanur 2020-06-03 5:39 ` Stefan Roese 0 siblings, 1 reply; 9+ messages in thread From: Rayagonda Kokatanur @ 2020-06-03 5:37 UTC (permalink / raw) To: u-boot On Wed, Jun 3, 2020 at 10:50 AM Stefan Roese <sr@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 at denx.de/ Thank you, I missed that v2. Anyway please ignore my comments, all of them are fixed in v2. 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@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> > >> --- > >> 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 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon 2020-06-03 5:37 ` Rayagonda Kokatanur @ 2020-06-03 5:39 ` Stefan Roese 0 siblings, 0 replies; 9+ messages in thread From: Stefan Roese @ 2020-06-03 5:39 UTC (permalink / raw) To: u-boot On 03.06.20 07:37, Rayagonda Kokatanur wrote: > On Wed, Jun 3, 2020 at 10:50 AM Stefan Roese <sr@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 at 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@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> >>>> --- >>>> 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 ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2020-06-03 5:39 UTC | newest] Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-05-14 7:23 [PATCH v1] i2c: octeon_i2c: Add I2C controller driver for Octeon Stefan Roese 2020-05-19 6:07 ` Heiko Schocher 2020-05-26 6:49 ` Stefan Roese 2020-05-19 12:32 ` Simon Glass 2020-05-26 10:55 ` Stefan Roese 2020-06-03 5:15 ` Rayagonda Kokatanur 2020-06-03 5:20 ` Stefan Roese 2020-06-03 5:37 ` Rayagonda Kokatanur 2020-06-03 5:39 ` Stefan Roese
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.