From 5c2c3e8b44e7fa9bc81e6e367797b3ee2b424168 Mon Sep 17 00:00:00 2001 From: Suneel Garapati Date: Tue, 26 May 2020 14:13:07 +0200 Subject: [PATCH 01/19] i2c: octeon_i2c: Add I2C controller driver for Octeon Add support for I2C controllers found on Octeon II/III and Octeon TX TX2 SoC platforms. Signed-off-by: Aaron Williams Signed-off-by: Suneel Garapati Signed-off-by: Stefan Roese Cc: Heiko Schocher Cc: Simon Glass Cc: Daniel Schwierzeck Cc: Aaron Williams Cc: Chandrakala Chavva Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Reviewed-by: Rayagonda Kokatanur --- doc/device-tree-bindings/i2c/octeon-i2c.txt | 24 + drivers/i2c/Kconfig | 10 + drivers/i2c/Makefile | 1 + drivers/i2c/octeon_i2c.c | 847 ++++++++++++++++++++ 4 files changed, 882 insertions(+) create mode 100644 doc/device-tree-bindings/i2c/octeon-i2c.txt create mode 100644 drivers/i2c/octeon_i2c.c diff --git a/doc/device-tree-bindings/i2c/octeon-i2c.txt b/doc/device-tree-bindings/i2c/octeon-i2c.txt new file mode 100644 index 00000000000..9c1908ec2cc --- /dev/null +++ b/doc/device-tree-bindings/i2c/octeon-i2c.txt @@ -0,0 +1,24 @@ +* I2C controller embedded in Marvell Octeon platforms + +Required properties : +- compatible : Must be "cavium,octeon-7890-twsi" or a compatible string +- reg : Offset and length of the register set for the device +- clocks: Must contain the input clock of the I2C instance +- #address-cells = <1>; +- #size-cells = <0>; + +Optional properties : +- clock-frequency : Desired I2C bus clock frequency in Hz. If not specified, + the default 100 kHz frequency will be used. As only Normal, Fast and Fast+ + modes are implemented, possible values are 100000, 400000 and 1000000. + +Example : + + i2c0: i2c@1180000001000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "cavium,octeon-7890-twsi"; + reg = <0x11800 0x00001000 0x0 0x200>; + clock-frequency = <100000>; + clocks = <&sclk>; + }; diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index f8b18de8f3b..363b899e9cd 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 62935b7ebcb..2b58aae892f 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 00000000000..c11d6ff93d1 --- /dev/null +++ b/drivers/i2c/octeon_i2c.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell International Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TWSI_SW_TWSI 0x00 +#define TWSI_TWSI_SW 0x08 +#define TWSI_INT 0x10 +#define TWSI_SW_TWSI_EXT 0x18 + +#define TWSI_SW_DATA_MASK GENMASK_ULL(31, 0) +#define TWSI_SW_EOP_IA_MASK GENMASK_ULL(34, 32) +#define TWSI_SW_IA_MASK GENMASK_ULL(39, 35) +#define TWSI_SW_ADDR_MASK GENMASK_ULL(49, 40) +#define TWSI_SW_SCR_MASK GENMASK_ULL(51, 50) +#define TWSI_SW_SIZE_MASK GENMASK_ULL(54, 52) +#define TWSI_SW_SOVR BIT_ULL(55) +#define TWSI_SW_R BIT_ULL(56) +#define TWSI_SW_OP_MASK GENMASK_ULL(60, 57) +#define TWSI_SW_EIA GENMASK_ULL(61) +#define TWSI_SW_SLONLY BIT_ULL(62) +#define TWSI_SW_V BIT_ULL(63) + +#define TWSI_INT_SDA_OVR BIT_ULL(8) +#define TWSI_INT_SCL_OVR BIT_ULL(9) +#define TWSI_INT_SDA BIT_ULL(10) +#define TWSI_INT_SCL BIT_ULL(11) + +enum { + TWSI_OP_WRITE = 0, + TWSI_OP_READ = 1, +}; + +enum { + TWSI_EOP_SLAVE_ADDR = 0, + TWSI_EOP_CLK_CTL = 3, + TWSI_SW_EOP_IA = 6, +}; + +enum { + TWSI_SLAVEADD = 0, + TWSI_DATA = 1, + TWSI_CTL = 2, + TWSI_CLKCTL = 3, + TWSI_STAT = 3, + TWSI_SLAVEADD_EXT = 4, + TWSI_RST = 7, +}; + +enum { + TWSI_CTL_AAK = BIT(2), + TWSI_CTL_IFLG = BIT(3), + TWSI_CTL_STP = BIT(4), + TWSI_CTL_STA = BIT(5), + TWSI_CTL_ENAB = BIT(6), + TWSI_CTL_CE = BIT(7), +}; + +/* + * Internal errors. When debugging is enabled, the driver will report the + * error number and the user / developer can check the table below for the + * detailed error description. + */ +enum { + /** Bus error */ + TWSI_STAT_BUS_ERROR = 0x00, + /** Start condition transmitted */ + TWSI_STAT_START = 0x08, + /** Repeat start condition transmitted */ + TWSI_STAT_RSTART = 0x10, + /** Address + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR_ACK = 0x18, + /** Address + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR_NAK = 0x20, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_ACK = 0x28, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_NAK = 0x30, + /** Arbitration lost in address or data byte */ + TWSI_STAT_TX_ARB_LOST = 0x38, + /** Address + read bit transmitted, ACK received */ + TWSI_STAT_RXADDR_ACK = 0x40, + /** Address + read bit transmitted, /ACK received */ + TWSI_STAT_RXADDR_NAK = 0x48, + /** Data byte received in master mode, ACK transmitted */ + TWSI_STAT_RXDATA_ACK_SENT = 0x50, + /** Data byte received, NACK transmitted */ + TWSI_STAT_RXDATA_NAK_SENT = 0x58, + /** Slave address received, sent ACK */ + TWSI_STAT_SLAVE_RXADDR_ACK = 0x60, + /** + * Arbitration lost in address as master, slave address + write bit + * received, ACK transmitted + */ + TWSI_STAT_TX_ACK_ARB_LOST = 0x68, + /** General call address received, ACK transmitted */ + TWSI_STAT_RX_GEN_ADDR_ACK = 0x70, + /** + * Arbitration lost in address as master, general call address + * received, ACK transmitted + */ + TWSI_STAT_RX_GEN_ADDR_ARB_LOST = 0x78, + /** Data byte received after slave address received, ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_ACK = 0x80, + /** Data byte received after slave address received, /ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_NAK = 0x88, + /** + * Data byte received after general call address received, ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_ACK = 0x90, + /** + * Data byte received after general call address received, /ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_NAK = 0x98, + /** STOP or repeated START condition received in slave mode */ + TWSI_STAT_STOP_MULTI_START = 0xa0, + /** Slave address + read bit received, ACK transmitted */ + TWSI_STAT_SLAVE_RXADDR2_ACK = 0xa8, + /** + * Arbitration lost in address as master, slave address + read bit + * received, ACK transmitted + */ + TWSI_STAT_RXDATA_ACK_ARB_LOST = 0xb0, + /** Data byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_ACK = 0xb8, + /** Data byte transmitted in slave mode, /ACK received */ + TWSI_STAT_SLAVE_TXDATA_NAK = 0xc0, + /** Last byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_END_ACK = 0xc8, + /** Second address byte + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR2DATA_ACK = 0xd0, + /** Second address byte + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR2DATA_NAK = 0xd8, + /** No relevant status information */ + TWSI_STAT_IDLE = 0xf8 +}; + +#define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR 0x77 + +enum { + PROBE_PCI = 0, /* PCI based probing */ + PROBE_DT, /* DT based probing */ +}; + +enum { + CLK_METHOD_OCTEON = 0, + CLK_METHOD_OCTEONTX2, +}; + +/** + * struct octeon_i2c_data - SoC specific data of this driver + * + * @probe: Probing of this SoC (DT vs PCI) + * @reg_offs: Register offset + * @thp: THP define for divider calculation + * @clk_method: Clock calculation method + */ +struct octeon_i2c_data { + int probe; + u32 reg_offs; + int thp; + int clk_method; +}; + +/** + * struct octeon_twsi - Private data of this driver + * + * @base: Base address of i2c registers + * @data: Pointer to SoC specific data struct + */ +struct octeon_twsi { + void __iomem *base; + const struct octeon_i2c_data *data; + struct clk clk; +}; + +static void twsi_unblock(void *base); +static int twsi_stop(void *base); + +/** + * Returns true if we lost arbitration + * + * @code status code + * @final_read true if this is the final read operation + * @return true if arbitration has been lost, false if it hasn't been lost. + */ +static int twsi_i2c_lost_arb(u8 code, int final_read) +{ + switch (code) { + case TWSI_STAT_TX_ARB_LOST: + case TWSI_STAT_TX_ACK_ARB_LOST: + case TWSI_STAT_RX_GEN_ADDR_ARB_LOST: + case TWSI_STAT_RXDATA_ACK_ARB_LOST: + /* Arbitration lost */ + return -EAGAIN; + + case TWSI_STAT_SLAVE_RXADDR_ACK: + case TWSI_STAT_RX_GEN_ADDR_ACK: + case TWSI_STAT_GEN_RXADDR_ACK: + case TWSI_STAT_GEN_RXADDR_NAK: + /* Being addressed as slave, should back off and listen */ + return -EIO; + + case TWSI_STAT_SLAVE_RXDATA_ACK: + case TWSI_STAT_SLAVE_RXDATA_NAK: + case TWSI_STAT_STOP_MULTI_START: + case TWSI_STAT_SLAVE_RXADDR2_ACK: + case TWSI_STAT_SLAVE_TXDATA_ACK: + case TWSI_STAT_SLAVE_TXDATA_NAK: + case TWSI_STAT_SLAVE_TXDATA_END_ACK: + /* Core busy as slave */ + return -EIO; + + case TWSI_STAT_RXDATA_ACK_SENT: + /* Ack allowed on pre-terminal bytes only */ + if (!final_read) + return 0; + return -EAGAIN; + + case TWSI_STAT_RXDATA_NAK_SENT: + /* NAK allowed on terminal byte only */ + if (!final_read) + return 0; + return -EAGAIN; + + case TWSI_STAT_TXDATA_NAK: + case TWSI_STAT_TXADDR_NAK: + case TWSI_STAT_RXADDR_NAK: + case TWSI_STAT_TXADDR2DATA_NAK: + return -EAGAIN; + } + + return 0; +} + +/** + * Writes to the MIO_TWS(0..5)_SW_TWSI register + * + * @base Base address of i2c registers + * @val value to write + * @return 0 for success, otherwise error + */ +static u64 twsi_write_sw(void __iomem *base, u64 val) +{ + unsigned long start = get_timer(0); + + val &= ~TWSI_SW_R; + val |= TWSI_SW_V; + + debug("%s(%p, 0x%llx)\n", __func__, base, val); + writeq(val, base + TWSI_SW_TWSI); + do { + val = readq(base + TWSI_SW_TWSI); + } while ((val & TWSI_SW_V) && (get_timer(start) < 50)); + + if (val & TWSI_SW_V) + debug("%s: timed out\n", __func__); + return val; +} + +/** + * Reads the MIO_TWS(0..5)_SW_TWSI register + * + * @base Base address of i2c registers + * @val value for eia and op, etc. to read + * @return value of the register + */ +static u64 twsi_read_sw(void __iomem *base, u64 val) +{ + unsigned long start = get_timer(0); + + val |= TWSI_SW_R | TWSI_SW_V; + + debug("%s(%p, 0x%llx)\n", __func__, base, val); + writeq(val, base + TWSI_SW_TWSI); + + do { + val = readq(base + TWSI_SW_TWSI); + } while ((val & TWSI_SW_V) && (get_timer(start) < 50)); + + if (val & TWSI_SW_V) + debug("%s: Error writing 0x%llx\n", __func__, val); + + debug("%s: Returning 0x%llx\n", __func__, val); + return val; +} + +/** + * Write control register + * + * @base Base address for i2c registers + * @data data to write + */ +static void twsi_write_ctl(void __iomem *base, u8 data) +{ + u64 val; + + debug("%s(%p, 0x%x)\n", __func__, base, data); + val = data | FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); +} + +/** + * Reads the TWSI Control Register + * + * @base Base address for i2c + * @return 8-bit TWSI control register + */ +static u8 twsi_read_ctl(void __iomem *base) +{ + u64 val; + + val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + val = twsi_read_sw(base, val); + + debug("%s(%p): 0x%x\n", __func__, base, (u8)val); + return (u8)val; +} + +/** + * Read i2c status register + * + * @base Base address of i2c registers + * @return value of status register + */ +static u8 twsi_read_status(void __iomem *base) +{ + u64 val; + + val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_STAT) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + + return twsi_read_sw(base, val); +} + +/** + * Waits for an i2c operation to complete + * + * @param base Base address of registers + * @return 0 for success, 1 if timeout + */ +static int twsi_wait(void __iomem *base) +{ + unsigned long start = get_timer(0); + u8 twsi_ctl; + + debug("%s(%p)\n", __func__, base); + do { + twsi_ctl = twsi_read_ctl(base); + twsi_ctl &= TWSI_CTL_IFLG; + } while (!twsi_ctl && get_timer(start) < 50); + + debug(" return: %u\n", !twsi_ctl); + return !twsi_ctl; +} + +/** + * Unsticks the i2c bus + * + * @base base address of registers + */ +static int twsi_start_unstick(void __iomem *base) +{ + twsi_stop(base); + twsi_unblock(base); + + return 0; +} + +/** + * Sends an i2c start condition + * + * @base base address of registers + * @return 0 for success, otherwise error + */ +static int twsi_start(void __iomem *base) +{ + int ret; + u8 stat; + + debug("%s(%p)\n", __func__, base); + twsi_write_ctl(base, TWSI_CTL_STA | TWSI_CTL_ENAB); + ret = twsi_wait(base); + if (ret) { + stat = twsi_read_status(base); + debug("%s: ret: 0x%x, status: 0x%x\n", __func__, ret, stat); + switch (stat) { + case TWSI_STAT_START: + case TWSI_STAT_RSTART: + return 0; + case TWSI_STAT_RXADDR_ACK: + default: + return twsi_start_unstick(base); + } + } + + debug("%s: success\n", __func__); + return 0; +} + +/** + * Sends an i2c stop condition + * + * @base register base address + * @return 0 for success, -1 if error + */ +static int twsi_stop(void __iomem *base) +{ + u8 stat; + + twsi_write_ctl(base, TWSI_CTL_STP | TWSI_CTL_ENAB); + + stat = twsi_read_status(base); + if (stat != TWSI_STAT_IDLE) { + debug("%s: Bad status on bus@%p\n", __func__, base); + return -1; + } + + return 0; +} + +/** + * Writes data to the i2c bus + * + * @base register base address + * @slave_addr address of slave to write to + * @buffer Pointer to buffer to write + * @length Number of bytes in buffer to write + * @return 0 for success, otherwise error + */ +static int twsi_write_data(void __iomem *base, u8 slave_addr, + u8 *buffer, unsigned int length) +{ + unsigned int curr = 0; + u64 val; + int ret; + + debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, base, slave_addr, + buffer, length); + ret = twsi_start(base); + if (ret) { + debug("%s: Could not start BUS transaction\n", __func__); + return -1; + } + + ret = twsi_wait(base); + if (ret) { + debug("%s: wait failed\n", __func__); + return ret; + } + + val = (u32)(slave_addr << 1) | TWSI_OP_WRITE | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + debug("%s: Waiting\n", __func__); + ret = twsi_wait(base); + if (ret) { + debug("%s: Timed out writing slave address 0x%x to target\n", + __func__, slave_addr); + return ret; + } + + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + if (ret != TWSI_STAT_TXADDR_ACK) { + debug("%s: status: 0x%x\n", __func__, ret); + twsi_stop(base); + return twsi_i2c_lost_arb(ret, 0); + } + + while (curr < length) { + val = buffer[curr++] | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + debug("%s: Writing 0x%llx\n", __func__, val); + + ret = twsi_wait(base); + if (ret) { + debug("%s: Timed out writing data to 0x%x\n", + __func__, slave_addr); + return ret; + } + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + } + + debug("%s: Stopping\n", __func__); + return twsi_stop(base); +} + +/** + * Manually clear the I2C bus and send a stop + * + * @base register base address + */ +static void twsi_unblock(void __iomem *base) +{ + int i; + + for (i = 0; i < 9; i++) { + writeq(0, base + TWSI_INT); + udelay(5); + writeq(TWSI_INT_SCL_OVR, base + TWSI_INT); + udelay(5); + } + writeq(TWSI_INT_SCL_OVR | TWSI_INT_SDA_OVR, base + TWSI_INT); + udelay(5); + writeq(TWSI_INT_SDA_OVR, base + TWSI_INT); + udelay(5); + writeq(0, base + TWSI_INT); + udelay(5); +} + +/** + * Performs a read transaction on the i2c bus + * + * @base Base address of twsi registers + * @slave_addr i2c bus address to read from + * @buffer buffer to read into + * @length number of bytes to read + * @return 0 for success, otherwise error + */ +static int twsi_read_data(void __iomem *base, u8 slave_addr, + u8 *buffer, unsigned int length) +{ + unsigned int curr = 0; + u64 val; + int ret; + + debug("%s(%p, 0x%x, %p, %u)\n", __func__, base, slave_addr, + buffer, length); + ret = twsi_start(base); + if (ret) { + debug("%s: start failed\n", __func__); + return ret; + } + + ret = twsi_wait(base); + if (ret) { + debug("%s: wait failed\n", __func__); + return ret; + } + + val = (u32)(slave_addr << 1) | TWSI_OP_READ | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + ret = twsi_wait(base); + if (ret) { + debug("%s: waiting for sending addr failed\n", __func__); + return ret; + } + + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + if (ret != TWSI_STAT_RXADDR_ACK) { + debug("%s: status: 0x%x\n", __func__, ret); + twsi_stop(base); + return twsi_i2c_lost_arb(ret, 0); + } + + while (curr < length) { + twsi_write_ctl(base, TWSI_CTL_ENAB | + ((curr < length - 1) ? TWSI_CTL_AAK : 0)); + + ret = twsi_wait(base); + if (ret) { + debug("%s: waiting for data failed\n", __func__); + return ret; + } + + val = twsi_read_sw(base, val); + buffer[curr++] = (u8)val; + } + + twsi_stop(base); + + return 0; +} + +/** + * Calculate the divisor values + * + * @speed Speed to set + * @m_div Pointer to M divisor + * @n_div Pointer to N divisor + * @return 0 for success, otherwise error + */ +static void twsi_calc_div(struct udevice *bus, ulong sclk, unsigned int speed, + int *m_div, int *n_div) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int thp = twsi->data->thp; + int tclk, fsamp; + int ndiv, mdiv; + + if (twsi->data->clk_method == CLK_METHOD_OCTEON) { + tclk = sclk / (2 * (thp + 1)); + } else { + /* Refclk src in mode register defaults to 100MHz clock */ + sclk = 100000000; /* 100 Mhz */ + tclk = sclk / (thp + 2); + } + debug("%s( io_clock %lu tclk %u)\n", __func__, sclk, tclk); + + /* + * Compute the clocks M divider: + * + * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N) + * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1 + * + * For OcteonTX2 - + * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N) + * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1 + */ + for (ndiv = 0; ndiv < 8; ndiv++) { + fsamp = tclk / (1 << ndiv); + mdiv = fsamp / speed / 10; + mdiv -= 1; + if (mdiv < 16) + break; + } + + *m_div = mdiv; + *n_div = ndiv; +} + +/** + * Init I2C controller + * + * @base Base address of twsi registers + * @slave_addr I2C slave address to configure this controller to + * @return 0 for success, otherwise error + */ +static int twsi_init(void __iomem *base, int slaveaddr) +{ + u64 val; + + debug("%s (%p, 0x%x)\n", __func__, base, slaveaddr); + + val = slaveaddr << 1 | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, 0) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + twsi_write_sw(base, val); + + /* Set slave address */ + val = slaveaddr | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_EOP_SLAVE_ADDR) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + twsi_write_sw(base, val); + + return 0; +} + +/** + * Transfers data over the i2c bus + * + * @bus i2c bus to transfer data over + * @msg Array of i2c messages + * @nmsgs Number of messages to send/receive + * @return 0 for success, otherwise error + */ +static int octeon_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, + int nmsgs) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int ret; + int i; + + debug("%s: %d messages\n", __func__, nmsgs); + for (i = 0; i < nmsgs; i++, msg++) { + debug("%s: chip=0x%x, len=0x%x\n", __func__, msg->addr, + msg->len); + + if (msg->flags & I2C_M_RD) { + debug("%s: Reading data\n", __func__); + ret = twsi_read_data(twsi->base, msg->addr, + msg->buf, msg->len); + } else { + debug("%s: Writing data\n", __func__); + ret = twsi_write_data(twsi->base, msg->addr, + msg->buf, msg->len); + } + if (ret) { + debug("%s: error sending\n", __func__); + return -EREMOTEIO; + } + } + + return 0; +} + +/** + * Set I2C bus speed + * + * @bus i2c bus to transfer data over + * @speed Speed in Hz to set + * @return 0 for success, otherwise error + */ +static int octeon_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int m_div, n_div; + ulong clk_rate; + u64 val; + + debug("%s(%p, %u)\n", __func__, bus, speed); + + clk_rate = clk_get_rate(&twsi->clk); + if (IS_ERR_VALUE(clk_rate)) + return -EINVAL; + + twsi_calc_div(bus, clk_rate, speed, &m_div, &n_div); + if (m_div >= 16) + return -1; + + val = (u32)(((m_div & 0xf) << 3) | ((n_div & 0x7) << 0)) | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CLKCTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + /* Only init non-slave ports */ + writeq(val, twsi->base + TWSI_SW_TWSI); + + debug("%s: Wrote 0x%llx to sw_twsi\n", __func__, val); + return 0; +} + +/** + * Driver probe function + * + * @dev I2C device to probe + * @return 0 for success, otherwise error + */ +static int octeon_i2c_probe(struct udevice *dev) +{ + struct octeon_twsi *twsi = dev_get_priv(dev); + u32 i2c_slave_addr; + int ret; + + twsi->data = (const struct octeon_i2c_data *)dev_get_driver_data(dev); + + if (twsi->data->probe == PROBE_PCI) { + pci_dev_t bdf = dm_pci_get_bdf(dev); + + debug("TWSI PCI device: %x\n", bdf); + dev->req_seq = PCI_FUNC(bdf); + + twsi->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, + PCI_REGION_MEM); + } else { + twsi->base = dev_remap_addr(dev); + } + twsi->base += twsi->data->reg_offs; + + i2c_slave_addr = dev_read_u32_default(dev, "i2c-sda-hold-time-ns", + CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR); + + ret = clk_get_by_index(dev, 0, &twsi->clk); + if (ret < 0) + return ret; + + ret = clk_enable(&twsi->clk); + if (ret) + return ret; + + debug("TWSI bus %d at %p\n", dev->seq, twsi->base); + + /* Start with standard speed, real speed set via DT or cmd */ + return twsi_init(twsi->base, i2c_slave_addr); +} + +static const struct dm_i2c_ops octeon_i2c_ops = { + .xfer = octeon_i2c_xfer, + .set_bus_speed = octeon_i2c_set_bus_speed, +}; + +static const struct octeon_i2c_data i2c_octeon_data = { + .probe = PROBE_DT, + .reg_offs = 0x0000, + .thp = 3, + .clk_method = CLK_METHOD_OCTEON, +}; + +static const struct octeon_i2c_data i2c_octeontx_data = { + .probe = PROBE_PCI, + .reg_offs = 0x8000, + .thp = 3, + .clk_method = CLK_METHOD_OCTEON, +}; + +static const struct octeon_i2c_data i2c_octeontx2_data = { + .probe = PROBE_PCI, + .reg_offs = 0x8000, + .thp = 24, + .clk_method = CLK_METHOD_OCTEONTX2, +}; + +static const struct udevice_id octeon_i2c_ids[] = { + { .compatible = "cavium,octeon-7890-twsi", + .data = (ulong)&i2c_octeon_data }, + { .compatible = "cavium,thunder-8890-twsi", + .data = (ulong)&i2c_octeontx_data }, + { .compatible = "cavium,thunder2-99xx-twsi", + .data = (ulong)&i2c_octeontx2_data }, + { } +}; + +U_BOOT_DRIVER(octeon_pci_twsi) = { + .name = "i2c_octeon", + .id = UCLASS_I2C, + .of_match = octeon_i2c_ids, + .probe = octeon_i2c_probe, + .priv_auto_alloc_size = sizeof(struct octeon_twsi), + .ops = &octeon_i2c_ops, +}; + +static struct pci_device_id octeon_twsi_supported[] = { + { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI), + .driver_data = (ulong)&i2c_octeontx2_data }, + { }, +}; + +U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported); From 7f5ea25062001589f301813416119b5353caca16 Mon Sep 17 00:00:00 2001 From: Arthur Li Date: Mon, 1 Jun 2020 12:56:31 -0700 Subject: [PATCH 02/19] i2c: i2c-cortina: added CAxxxx I2C support Add I2C controller support for Cortina Access CAxxxx SoCs Signed-off-by: Arthur Li Signed-off-by: Alex Nemirovsky CC: Heiko Schocher Reviewed-by: Heiko Schocher hs: fixed build error, add include log.h --- MAINTAINERS | 4 + doc/device-tree-bindings/i2c/i2c-cortina.txt | 18 + drivers/i2c/Kconfig | 8 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-cortina.c | 347 +++++++++++++++++++ drivers/i2c/i2c-cortina.h | 87 +++++ 6 files changed, 465 insertions(+) create mode 100644 doc/device-tree-bindings/i2c/i2c-cortina.txt create mode 100644 drivers/i2c/i2c-cortina.c create mode 100644 drivers/i2c/i2c-cortina.h diff --git a/MAINTAINERS b/MAINTAINERS index e3f3e5f0d85..2a281a9a0f3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -182,6 +182,8 @@ F: drivers/gpio/cortina_gpio.c F: drivers/watchdog/cortina_wdt.c F: drivers/serial/serial_cortina.c F: drivers/mmc/ca_dw_mmc.c +F: drivers/i2c/i2c-cortina.c +F: drivers/i2c/i2c-cortina.h ARM/CZ.NIC TURRIS MOX SUPPORT M: Marek Behun @@ -740,6 +742,8 @@ F: drivers/gpio/cortina_gpio.c F: drivers/watchdog/cortina_wdt.c F: drivers/serial/serial_cortina.c F: drivers/mmc/ca_dw_mmc.c +F: drivers/i2c/i2c-cortina.c +F: drivers/i2c/i2c-cortina.h MIPS MSCC M: Gregory CLEMENT diff --git a/doc/device-tree-bindings/i2c/i2c-cortina.txt b/doc/device-tree-bindings/i2c/i2c-cortina.txt new file mode 100644 index 00000000000..59d523582a4 --- /dev/null +++ b/doc/device-tree-bindings/i2c/i2c-cortina.txt @@ -0,0 +1,18 @@ +* I2C for Cortina platforms + +Required properties : +- compatible : Must be "cortina,ca-i2c" +- reg : Offset and length of the register set for the device + +Recommended properties : +- clock-frequency : desired I2C bus clock frequency in Hz. If not specified, + default value is 100000. Possible values are 100000, + 400000 and 1000000. + +Examples : + + i2c: i2c@f4329120 { + compatible = "cortina,ca-i2c"; + reg = <0x0 0xf4329120 0x28>; + clock-frequency = <400000>; + }; diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 363b899e9cd..87d11b663c3 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -93,6 +93,14 @@ config SYS_I2C_CADENCE Say yes here to select Cadence I2C Host Controller. This controller is e.g. used by Xilinx Zynq. +config SYS_I2C_CA + tristate "Cortina-Access I2C Controller" + depends on DM_I2C && CORTINA_PLATFORM + default n + help + Add support for the Cortina Access I2C host controller. + Say yes here to select Cortina-Access I2C Host Controller. + config SYS_I2C_DAVINCI bool "Davinci I2C Controller" depends on (ARCH_KEYSTONE || ARCH_DAVINCI) diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 2b58aae892f..174081e2529 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SYS_I2C) += i2c_core.o obj-$(CONFIG_SYS_I2C_ASPEED) += ast_i2c.o obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o obj-$(CONFIG_SYS_I2C_CADENCE) += i2c-cdns.o +obj-$(CONFIG_SYS_I2C_CA) += i2c-cortina.o obj-$(CONFIG_SYS_I2C_DAVINCI) += davinci_i2c.o obj-$(CONFIG_SYS_I2C_DW) += designware_i2c.o ifdef CONFIG_DM_PCI diff --git a/drivers/i2c/i2c-cortina.c b/drivers/i2c/i2c-cortina.c new file mode 100644 index 00000000000..036fc4282b5 --- /dev/null +++ b/drivers/i2c/i2c-cortina.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2020 + * Arthur Li, Cortina Access, arthur.li@cortina-access.com. + */ + +#include +#include +#include +#include +#include +#include +#include "i2c-cortina.h" + +static void set_speed(struct i2c_regs *regs, int i2c_spd) +{ + union ca_biw_cfg i2c_cfg; + + i2c_cfg.wrd = readl(®s->i2c_cfg); + i2c_cfg.bf.core_en = 0; + writel(i2c_cfg.wrd, ®s->i2c_cfg); + + switch (i2c_spd) { + case IC_SPEED_MODE_FAST_PLUS: + i2c_cfg.bf.prer = CORTINA_PER_IO_FREQ / + (5 * I2C_SPEED_FAST_PLUS_RATE) - 1; + break; + + case IC_SPEED_MODE_STANDARD: + i2c_cfg.bf.prer = CORTINA_PER_IO_FREQ / + (5 * I2C_SPEED_STANDARD_RATE) - 1; + break; + + case IC_SPEED_MODE_FAST: + default: + i2c_cfg.bf.prer = CORTINA_PER_IO_FREQ / + (5 * I2C_SPEED_FAST_RATE) - 1; + break; + } + + i2c_cfg.bf.core_en = 1; + writel(i2c_cfg.wrd, ®s->i2c_cfg); +} + +static int ca_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ + struct ca_i2c *priv = dev_get_priv(bus); + int i2c_spd; + + if (speed >= I2C_SPEED_FAST_PLUS_RATE) { + i2c_spd = IC_SPEED_MODE_FAST_PLUS; + priv->speed = I2C_SPEED_FAST_PLUS_RATE; + } else if (speed >= I2C_SPEED_FAST_RATE) { + i2c_spd = IC_SPEED_MODE_FAST; + priv->speed = I2C_SPEED_FAST_RATE; + } else { + i2c_spd = IC_SPEED_MODE_STANDARD; + priv->speed = I2C_SPEED_STANDARD_RATE; + } + + set_speed(priv->regs, i2c_spd); + + return 0; +} + +static int ca_i2c_get_bus_speed(struct udevice *bus) +{ + struct ca_i2c *priv = dev_get_priv(bus); + + return priv->speed; +} + +static void ca_i2c_init(struct i2c_regs *regs) +{ + union ca_biw_cfg i2c_cfg; + + i2c_cfg.wrd = readl(®s->i2c_cfg); + i2c_cfg.bf.core_en = 0; + i2c_cfg.bf.biw_soft_reset = 1; + writel(i2c_cfg.wrd, ®s->i2c_cfg); + mdelay(10); + i2c_cfg.bf.biw_soft_reset = 0; + writel(i2c_cfg.wrd, ®s->i2c_cfg); + + set_speed(regs, IC_SPEED_MODE_STANDARD); + + i2c_cfg.wrd = readl(®s->i2c_cfg); + i2c_cfg.bf.core_en = 1; + writel(i2c_cfg.wrd, ®s->i2c_cfg); +} + +static int i2c_wait_complete(struct i2c_regs *regs) +{ + union ca_biw_ctrl i2c_ctrl; + unsigned long start_time_bb = get_timer(0); + + i2c_ctrl.wrd = readl(®s->i2c_ctrl); + + while (i2c_ctrl.bf.biwdone == 0) { + i2c_ctrl.wrd = readl(®s->i2c_ctrl); + + if (get_timer(start_time_bb) > + (unsigned long)(I2C_BYTE_TO_BB)) { + printf("%s not done!!!\n", __func__); + return -ETIMEDOUT; + } + } + + /* Clear done bit */ + writel(i2c_ctrl.wrd, ®s->i2c_ctrl); + + return 0; +} + +static void i2c_setaddress(struct i2c_regs *regs, unsigned int i2c_addr, + int write_read) +{ + writel(i2c_addr | write_read, ®s->i2c_txr); + + writel(BIW_CTRL_START | BIW_CTRL_WRITE, + ®s->i2c_ctrl); + + i2c_wait_complete(regs); +} + +static int i2c_wait_for_bus_busy(struct i2c_regs *regs) +{ + union ca_biw_ack i2c_ack; + unsigned long start_time_bb = get_timer(0); + + i2c_ack.wrd = readl(®s->i2c_ack); + + while (i2c_ack.bf.biw_busy) { + i2c_ack.wrd = readl(®s->i2c_ack); + + if (get_timer(start_time_bb) > + (unsigned long)(I2C_BYTE_TO_BB)) { + printf("%s: timeout!\n", __func__); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int i2c_xfer_init(struct i2c_regs *regs, uint8_t chip, uint addr, + int alen, int write_read) +{ + int addr_len = alen; + + if (i2c_wait_for_bus_busy(regs)) + return 1; + + /* First cycle must write addr + offset */ + chip = ((chip & 0x7F) << 1); + if (alen == 0 && write_read == I2C_CMD_RD) + i2c_setaddress(regs, chip, I2C_CMD_RD); + else + i2c_setaddress(regs, chip, I2C_CMD_WT); + + while (alen) { + alen--; + writel(addr, ®s->i2c_txr); + if (write_read == I2C_CMD_RD) + writel(BIW_CTRL_WRITE | BIW_CTRL_STOP, + ®s->i2c_ctrl); + else + writel(BIW_CTRL_WRITE, ®s->i2c_ctrl); + i2c_wait_complete(regs); + } + + /* Send address again with Read flag if it's read command */ + if (write_read == I2C_CMD_RD && addr_len > 0) + i2c_setaddress(regs, chip, I2C_CMD_RD); + + return 0; +} + +static int i2c_xfer_finish(struct i2c_regs *regs) +{ + /* Dummy read makes bus free */ + writel(BIW_CTRL_READ | BIW_CTRL_STOP, ®s->i2c_ctrl); + i2c_wait_complete(regs); + + if (i2c_wait_for_bus_busy(regs)) { + printf("Timed out waiting for bus\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int ca_i2c_read(struct i2c_regs *regs, uint8_t chip, uint addr, + int alen, uint8_t *buffer, int len) +{ + unsigned long start_time_rx; + int rc = 0; + + rc = i2c_xfer_init(regs, chip, addr, alen, I2C_CMD_RD); + if (rc) + return rc; + + start_time_rx = get_timer(0); + while (len) { + /* ACK_IN is ack value to send during read. + * ack high only on the very last byte! + */ + if (len == 1) + writel(BIW_CTRL_READ | BIW_CTRL_ACK_IN | BIW_CTRL_STOP, + ®s->i2c_ctrl); + else + writel(BIW_CTRL_READ, ®s->i2c_ctrl); + + rc = i2c_wait_complete(regs); + udelay(1); + + if (rc == 0) { + *buffer++ = + (uchar) readl(®s->i2c_rxr); + len--; + start_time_rx = get_timer(0); + + } else if (get_timer(start_time_rx) > I2C_BYTE_TO) { + return -ETIMEDOUT; + } + } + i2c_xfer_finish(regs); + return rc; +} + +static int ca_i2c_write(struct i2c_regs *regs, uint8_t chip, uint addr, + int alen, uint8_t *buffer, int len) +{ + int rc, nb = len; + unsigned long start_time_tx; + + rc = i2c_xfer_init(regs, chip, addr, alen, I2C_CMD_WT); + if (rc) + return rc; + + start_time_tx = get_timer(0); + while (len) { + writel(*buffer, ®s->i2c_txr); + if (len == 1) + writel(BIW_CTRL_WRITE | BIW_CTRL_STOP, + ®s->i2c_ctrl); + else + writel(BIW_CTRL_WRITE, ®s->i2c_ctrl); + + rc = i2c_wait_complete(regs); + + if (rc == 0) { + len--; + buffer++; + start_time_tx = get_timer(0); + } else if (get_timer(start_time_tx) > (nb * I2C_BYTE_TO)) { + return -ETIMEDOUT; + } + } + + return 0; +} + +static int ca_i2c_probe_chip(struct udevice *bus, uint chip_addr, + uint chip_flags) +{ + struct ca_i2c *priv = dev_get_priv(bus); + int ret; + u32 tmp; + + /* Try to read the first location of the chip */ + ret = ca_i2c_read(priv->regs, chip_addr, 0, 1, (uchar *)&tmp, 1); + if (ret) + ca_i2c_init(priv->regs); + + return ret; +} + +static int ca_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs) +{ + struct ca_i2c *priv = dev_get_priv(bus); + int ret; + + debug("i2c_xfer: %d messages\n", nmsgs); + for (; nmsgs > 0; nmsgs--, msg++) { + debug("i2c_xfer: chip=0x%x, len=0x%x\n", msg->addr, msg->len); + if (msg->flags & I2C_M_RD) + ret = ca_i2c_read(priv->regs, msg->addr, 0, 0, + msg->buf, msg->len); + else + ret = ca_i2c_write(priv->regs, msg->addr, 0, 0, + msg->buf, msg->len); + + if (ret) { + printf("i2c_xfer: %s error\n", + msg->flags & I2C_M_RD ? "read" : "write"); + return ret; + } + } + + return 0; +} + +static const struct dm_i2c_ops ca_i2c_ops = { + .xfer = ca_i2c_xfer, + .probe_chip = ca_i2c_probe_chip, + .set_bus_speed = ca_i2c_set_bus_speed, + .get_bus_speed = ca_i2c_get_bus_speed, +}; + +static const struct udevice_id ca_i2c_ids[] = { + { .compatible = "cortina,ca-i2c", }, + { } +}; + +static int ca_i2c_probe(struct udevice *bus) +{ + struct ca_i2c *priv = dev_get_priv(bus); + + ca_i2c_init(priv->regs); + + return 0; +} + +static int ca_i2c_ofdata_to_platdata(struct udevice *bus) +{ + struct ca_i2c *priv = dev_get_priv(bus); + + priv->regs = map_sysmem(dev_read_addr(bus), sizeof(struct i2c_regs)); + if (!priv->regs) { + printf("I2C: base address is invalid\n"); + return -EINVAL; + } + + return 0; +} + +U_BOOT_DRIVER(i2c_cortina) = { + .name = "i2c_cortina", + .id = UCLASS_I2C, + .of_match = ca_i2c_ids, + .ofdata_to_platdata = ca_i2c_ofdata_to_platdata, + .probe = ca_i2c_probe, + .priv_auto_alloc_size = sizeof(struct ca_i2c), + .ops = &ca_i2c_ops, + .flags = DM_FLAG_PRE_RELOC, +}; diff --git a/drivers/i2c/i2c-cortina.h b/drivers/i2c/i2c-cortina.h new file mode 100644 index 00000000000..7e406b580ea --- /dev/null +++ b/drivers/i2c/i2c-cortina.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2019 + * Cortina Access, + */ + +#ifndef __CA_I2C_H_ +#define __CA_I2C_H_ + +#include +#include + +#if !defined(__ASSEMBLER__) && !defined(__ASSEMBLY__) +struct i2c_regs { + u32 i2c_cfg; + u32 i2c_ctrl; + u32 i2c_txr; + u32 i2c_rxr; + u32 i2c_ack; + u32 i2c_ie0; + u32 i2c_int0; + u32 i2c_ie1; + u32 i2c_int1; + u32 i2c_stat; +}; + +union ca_biw_cfg { + struct biw_cfg { + u32 core_en : 1; + u32 biw_soft_reset : 1; + u32 busywait_en : 1; + u32 stretch_en : 1; + u32 arb_en : 1; + u32 clksync_en : 1; + u32 rsrvd1 : 2; + u32 spike_cnt : 4; + u32 rsrvd2 : 4; + u32 prer : 16; + } bf; + unsigned int wrd; +}; + +union ca_biw_ctrl { + struct biw_ctrl { + u32 biwdone : 1; + u32 rsrvd1 : 2; + u32 ack_in : 1; + u32 write : 1; + u32 read : 1; + u32 stop : 1; + u32 start : 1; + u32 rsrvd2 : 24; + } bf; + unsigned int wrd; +}; + +union ca_biw_ack { + struct biw_ack { + u32 al :1; + u32 biw_busy :1; + u32 ack_out :1; + u32 rsrvd1 :29; + } bf; + unsigned int wrd; +}; +#endif /* !__ASSEMBLER__*/ + +struct ca_i2c { + struct i2c_regs *regs; + unsigned int speed; +}; + +#define I2C_CMD_WT 0 +#define I2C_CMD_RD 1 + +#define BIW_CTRL_DONE BIT(0) +#define BIW_CTRL_ACK_IN BIT(3) +#define BIW_CTRL_WRITE BIT(4) +#define BIW_CTRL_READ BIT(5) +#define BIW_CTRL_STOP BIT(6) +#define BIW_CTRL_START BIT(7) + +#define I2C_BYTE_TO (CONFIG_SYS_HZ / 500) +#define I2C_STOPDET_TO (CONFIG_SYS_HZ / 500) +#define I2C_BYTE_TO_BB (10) + +#endif /* __CA_I2C_H_ */ From 44ad59e695e6c7fd87e9f1a8590f746090d3afe4 Mon Sep 17 00:00:00 2001 From: Alex Nemirovsky Date: Mon, 1 Jun 2020 12:56:32 -0700 Subject: [PATCH 03/19] board: presidio-asic: Add I2C support Add I2C board support for Cortina Access Presidio Engineering Board Signed-off-by: Alex Nemirovsky CC: Heiko Schocher Reviewed-by: Heiko Schocher --- configs/cortina_presidio-asic-emmc_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/cortina_presidio-asic-emmc_defconfig b/configs/cortina_presidio-asic-emmc_defconfig index e10008a2b7a..e45e23c2a01 100644 --- a/configs/cortina_presidio-asic-emmc_defconfig +++ b/configs/cortina_presidio-asic-emmc_defconfig @@ -10,6 +10,7 @@ CONFIG_SHOW_BOOT_PROGRESS=y CONFIG_BOOTDELAY=3 CONFIG_BOARD_EARLY_INIT_R=y CONFIG_SYS_PROMPT="G3#" +CONFIG_CMD_I2C=y CONFIG_CMD_MMC=y CONFIG_CMD_PART=y CONFIG_CMD_WDT=y @@ -24,6 +25,8 @@ CONFIG_DEFAULT_DEVICE_TREE="ca-presidio-engboard" # CONFIG_NET is not set CONFIG_DM=y CONFIG_CORTINA_GPIO=y +CONFIG_DM_I2C=y +CONFIG_SYS_I2C_CA=y CONFIG_DM_MMC=y CONFIG_MMC_DW=y CONFIG_MMC_DW_CORTINA=y From 35d3982e23d3449d93bcc4f1990e929998d69936 Mon Sep 17 00:00:00 2001 From: Ye Li Date: Tue, 9 Jun 2020 20:29:50 -0700 Subject: [PATCH 04/19] i2c: imx_lpi2c: Improve the codes to use private data Current driver calls the devfdt_get_addr to get the base address of lpi2c controller in each sub-functions. Since the devfdt_get_addr accesses the DTB and translate the address, it introduces much overhead. Improve the codes to use private variable which has recorded the base address from probe. Signed-off-by: Ye Li Reviewed-by: Peng Fan --- drivers/i2c/imx_lpi2c.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/i2c/imx_lpi2c.c b/drivers/i2c/imx_lpi2c.c index c8e42e05f5d..b7b2aafc7f6 100644 --- a/drivers/i2c/imx_lpi2c.c +++ b/drivers/i2c/imx_lpi2c.c @@ -97,7 +97,8 @@ static int bus_i2c_wait_for_tx_ready(struct imx_lpi2c_reg *regs) static int bus_i2c_send(struct udevice *bus, u8 *txbuf, int len) { - struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)devfdt_get_addr(bus); + struct imx_lpi2c_bus *i2c_bus = dev_get_priv(bus); + struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)(i2c_bus->base); lpi2c_status_t result = LPI2C_SUCESS; /* empty tx */ @@ -118,7 +119,8 @@ static int bus_i2c_send(struct udevice *bus, u8 *txbuf, int len) static int bus_i2c_receive(struct udevice *bus, u8 *rxbuf, int len) { - struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)devfdt_get_addr(bus); + struct imx_lpi2c_bus *i2c_bus = dev_get_priv(bus); + struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)(i2c_bus->base); lpi2c_status_t result = LPI2C_SUCESS; u32 val; ulong start_time = get_timer(0); @@ -162,8 +164,8 @@ static int bus_i2c_receive(struct udevice *bus, u8 *rxbuf, int len) static int bus_i2c_start(struct udevice *bus, u8 addr, u8 dir) { lpi2c_status_t result; - struct imx_lpi2c_reg *regs = - (struct imx_lpi2c_reg *)devfdt_get_addr(bus); + struct imx_lpi2c_bus *i2c_bus = dev_get_priv(bus); + struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)(i2c_bus->base); u32 val; result = imx_lpci2c_check_busy_bus(regs); @@ -199,8 +201,8 @@ static int bus_i2c_start(struct udevice *bus, u8 addr, u8 dir) static int bus_i2c_stop(struct udevice *bus) { lpi2c_status_t result; - struct imx_lpi2c_reg *regs = - (struct imx_lpi2c_reg *)devfdt_get_addr(bus); + struct imx_lpi2c_bus *i2c_bus = dev_get_priv(bus); + struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)(i2c_bus->base); u32 status; ulong start_time; @@ -271,7 +273,7 @@ u32 __weak imx_get_i2cclk(u32 i2c_num) static int bus_i2c_set_bus_speed(struct udevice *bus, int speed) { struct imx_lpi2c_bus *i2c_bus = dev_get_priv(bus); - struct imx_lpi2c_reg *regs; + struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)(i2c_bus->base); u32 val; u32 preescale = 0, best_pre = 0, clkhi = 0; u32 best_clkhi = 0, abs_error = 0, rate; @@ -280,8 +282,6 @@ static int bus_i2c_set_bus_speed(struct udevice *bus, int speed) bool mode; int i; - regs = (struct imx_lpi2c_reg *)devfdt_get_addr(bus); - if (IS_ENABLED(CONFIG_CLK)) { clock_rate = clk_get_rate(&i2c_bus->per_clk); if (clock_rate <= 0) { @@ -348,11 +348,11 @@ static int bus_i2c_set_bus_speed(struct udevice *bus, int speed) static int bus_i2c_init(struct udevice *bus, int speed) { - struct imx_lpi2c_reg *regs; u32 val; int ret; - regs = (struct imx_lpi2c_reg *)devfdt_get_addr(bus); + struct imx_lpi2c_bus *i2c_bus = dev_get_priv(bus); + struct imx_lpi2c_reg *regs = (struct imx_lpi2c_reg *)(i2c_bus->base); /* reset peripheral */ writel(LPI2C_MCR_RST_MASK, ®s->mcr); writel(0x0, ®s->mcr); From d8be08805bc03d53b0dd6953792fba543a4f3890 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:10 +0200 Subject: [PATCH 05/19] rtc: add dm_rtc_read helper and ->read method Some users may want to read multiple consecutive 8-bit registers. Instead of each caller having to implement the loop, provide a dm_rtc_read() helper. Also, allow a driver to provide a ->read method, which can be more efficient than reading one register at a time. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/rtc-uclass.c | 19 +++++++++++++++++++ include/rtc.h | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/drivers/rtc/rtc-uclass.c b/drivers/rtc/rtc-uclass.c index 926cca234e8..4a0e3c5643d 100644 --- a/drivers/rtc/rtc-uclass.c +++ b/drivers/rtc/rtc-uclass.c @@ -40,6 +40,25 @@ int dm_rtc_reset(struct udevice *dev) return ops->reset(dev); } +int dm_rtc_read(struct udevice *dev, unsigned int reg, u8 *buf, unsigned int len) +{ + struct rtc_ops *ops = rtc_get_ops(dev); + + assert(ops); + if (ops->read) + return ops->read(dev, reg, buf, len); + if (!ops->read8) + return -ENOSYS; + while (len--) { + int ret = ops->read8(dev, reg++); + + if (ret < 0) + return ret; + *buf++ = ret; + } + return 0; +} + int rtc_read8(struct udevice *dev, unsigned int reg) { struct rtc_ops *ops = rtc_get_ops(dev); diff --git a/include/rtc.h b/include/rtc.h index 8aabfc1162a..f30e908221a 100644 --- a/include/rtc.h +++ b/include/rtc.h @@ -55,6 +55,18 @@ struct rtc_ops { */ int (*reset)(struct udevice *dev); + /** + * read() - Read multiple 8-bit registers + * + * @dev: Device to read from + * @reg: First register to read + * @buf: Output buffer + * @len: Number of registers to read + * @return 0 if OK, -ve on error + */ + int (*read)(struct udevice *dev, unsigned int reg, + u8 *buf, unsigned int len); + /** * read8() - Read an 8-bit register * @@ -109,6 +121,17 @@ int dm_rtc_set(struct udevice *dev, struct rtc_time *time); */ int dm_rtc_reset(struct udevice *dev); +/** + * dm_rtc_read() - Read multiple 8-bit registers + * + * @dev: Device to read from + * @reg: First register to read + * @buf: Output buffer + * @len: Number of registers to read + * @return 0 if OK, -ve on error + */ +int dm_rtc_read(struct udevice *dev, unsigned int reg, u8 *buf, unsigned int len); + /** * rtc_read8() - Read an 8-bit register * From 09381829a231f5d2b95568f3178c2bd125a93fac Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:11 +0200 Subject: [PATCH 06/19] rtc: add dm_rtc_write() helper Similar to dm_rtc_read(), introduce a helper that allows the caller to write multiple consecutive 8-bit registers with one call. If the driver provides the ->write method, use that, otherwise loop using ->write8. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/rtc-uclass.c | 19 +++++++++++++++++++ include/rtc.h | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/drivers/rtc/rtc-uclass.c b/drivers/rtc/rtc-uclass.c index 4a0e3c5643d..44da500c038 100644 --- a/drivers/rtc/rtc-uclass.c +++ b/drivers/rtc/rtc-uclass.c @@ -59,6 +59,25 @@ int dm_rtc_read(struct udevice *dev, unsigned int reg, u8 *buf, unsigned int len return 0; } +int dm_rtc_write(struct udevice *dev, unsigned int reg, + const u8 *buf, unsigned int len) +{ + struct rtc_ops *ops = rtc_get_ops(dev); + + assert(ops); + if (ops->write) + return ops->write(dev, reg, buf, len); + if (!ops->write8) + return -ENOSYS; + while (len--) { + int ret = ops->write8(dev, reg++, *buf++); + + if (ret < 0) + return ret; + } + return 0; +} + int rtc_read8(struct udevice *dev, unsigned int reg) { struct rtc_ops *ops = rtc_get_ops(dev); diff --git a/include/rtc.h b/include/rtc.h index f30e908221a..1efc0db3de6 100644 --- a/include/rtc.h +++ b/include/rtc.h @@ -67,6 +67,18 @@ struct rtc_ops { int (*read)(struct udevice *dev, unsigned int reg, u8 *buf, unsigned int len); + /** + * write() - Write multiple 8-bit registers + * + * @dev: Device to write to + * @reg: First register to write + * @buf: Input buffer + * @len: Number of registers to write + * @return 0 if OK, -ve on error + */ + int (*write)(struct udevice *dev, unsigned int reg, + const u8 *buf, unsigned int len); + /** * read8() - Read an 8-bit register * @@ -132,6 +144,18 @@ int dm_rtc_reset(struct udevice *dev); */ int dm_rtc_read(struct udevice *dev, unsigned int reg, u8 *buf, unsigned int len); +/** + * dm_rtc_write() - Write multiple 8-bit registers + * + * @dev: Device to write to + * @reg: First register to write + * @buf: Input buffer + * @len: Number of registers to write + * @return 0 if OK, -ve on error + */ +int dm_rtc_write(struct udevice *dev, unsigned int reg, + const u8 *buf, unsigned int len); + /** * rtc_read8() - Read an 8-bit register * From 5113f0b65a6a1ce79f4befa4e99f3e8e4a1eb9b0 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:12 +0200 Subject: [PATCH 07/19] rtc: fall back to ->{read, write} if ->{read, write}8 are not provided Similar to how the dm_rtc_{read,write} functions fall back to using the {read,write}8 methods, do the opposite in the rtc_{read,write}8 functions. This way, each driver only needs to provide either ->read8 or ->read to make both rtc_read8() and dm_rtc_read() work - without this, a driver that provides ->read() would most likely just duplicate the logic here for implementing a ->read8() method in term of its ->read() method. The same remarks of course apply to the write case. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/rtc-uclass.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-uclass.c b/drivers/rtc/rtc-uclass.c index 44da500c038..8035f7fe9cc 100644 --- a/drivers/rtc/rtc-uclass.c +++ b/drivers/rtc/rtc-uclass.c @@ -83,9 +83,17 @@ int rtc_read8(struct udevice *dev, unsigned int reg) struct rtc_ops *ops = rtc_get_ops(dev); assert(ops); - if (!ops->read8) - return -ENOSYS; - return ops->read8(dev, reg); + if (ops->read8) + return ops->read8(dev, reg); + if (ops->read) { + u8 buf[1]; + int ret = ops->read(dev, reg, buf, 1); + + if (ret < 0) + return ret; + return buf[0]; + } + return -ENOSYS; } int rtc_write8(struct udevice *dev, unsigned int reg, int val) @@ -93,9 +101,14 @@ int rtc_write8(struct udevice *dev, unsigned int reg, int val) struct rtc_ops *ops = rtc_get_ops(dev); assert(ops); - if (!ops->write8) - return -ENOSYS; - return ops->write8(dev, reg, val); + if (ops->write8) + return ops->write8(dev, reg, val); + if (ops->write) { + u8 buf[1] = { val }; + + return ops->write(dev, reg, buf, 1); + } + return -ENOSYS; } int rtc_read16(struct udevice *dev, unsigned int reg, u16 *valuep) From a518dd6336ff511c2a3d38e4410281a90b4e16ea Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:13 +0200 Subject: [PATCH 08/19] rtc: pcf2127: provide ->read method This simply consists of renaming the existing pcf2127_read_reg() helper to follow the naming of the other methods (i.e. pcf2127_rtc_) and changing the type of its "len" parameter. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/pcf2127.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/pcf2127.c b/drivers/rtc/pcf2127.c index c423960b343..eea72ad5226 100644 --- a/drivers/rtc/pcf2127.c +++ b/drivers/rtc/pcf2127.c @@ -23,8 +23,7 @@ #define PCF2127_REG_MO 0x08 #define PCF2127_REG_YR 0x09 -static int pcf2127_read_reg(struct udevice *dev, uint offset, - u8 *buffer, int len) +static int pcf2127_rtc_read(struct udevice *dev, uint offset, u8 *buffer, uint len) { struct dm_i2c_chip *chip = dev_get_parent_platdata(dev); struct i2c_msg msg; @@ -73,7 +72,7 @@ static int pcf2127_rtc_get(struct udevice *dev, struct rtc_time *tm) int ret = 0; uchar buf[10] = { PCF2127_REG_CTRL1 }; - ret = pcf2127_read_reg(dev, PCF2127_REG_CTRL1, buf, sizeof(buf)); + ret = pcf2127_rtc_read(dev, PCF2127_REG_CTRL1, buf, sizeof(buf)); if (ret < 0) return ret; @@ -110,6 +109,7 @@ static const struct rtc_ops pcf2127_rtc_ops = { .get = pcf2127_rtc_get, .set = pcf2127_rtc_set, .reset = pcf2127_rtc_reset, + .read = pcf2127_rtc_read, }; static const struct udevice_id pcf2127_rtc_ids[] = { From 29f965a7ec2bc32be3282eac0645b6e5ea2112ab Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:14 +0200 Subject: [PATCH 09/19] rtc: pcf2127: provide ->write method Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/pcf2127.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/rtc/pcf2127.c b/drivers/rtc/pcf2127.c index eea72ad5226..88ff8c52c35 100644 --- a/drivers/rtc/pcf2127.c +++ b/drivers/rtc/pcf2127.c @@ -43,6 +43,12 @@ static int pcf2127_rtc_read(struct udevice *dev, uint offset, u8 *buffer, uint l return dm_i2c_xfer(dev, &msg, 1); } +static int pcf2127_rtc_write(struct udevice *dev, uint offset, + const u8 *buffer, uint len) +{ + return dm_i2c_write(dev, offset, buffer, len); +} + static int pcf2127_rtc_set(struct udevice *dev, const struct rtc_time *tm) { uchar buf[7] = {0}; @@ -110,6 +116,7 @@ static const struct rtc_ops pcf2127_rtc_ops = { .set = pcf2127_rtc_set, .reset = pcf2127_rtc_reset, .read = pcf2127_rtc_read, + .write = pcf2127_rtc_write, }; static const struct udevice_id pcf2127_rtc_ids[] = { From 803a859884b73dbc10826ed26fbf62d487ddc5c7 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:15 +0200 Subject: [PATCH 10/19] rtc: add rtc command Mostly as an aid for debugging RTC drivers, provide a command that can be used to read/write arbitrary registers (assuming the driver provides the read/write methods or their single-register-at-a-time variants). Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- cmd/Kconfig | 6 ++ cmd/Makefile | 1 + cmd/rtc.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 cmd/rtc.c diff --git a/cmd/Kconfig b/cmd/Kconfig index 2b823dd2608..846c905c9cc 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1739,6 +1739,12 @@ config CMD_DATE Enable the 'date' command for getting/setting the time/date in RTC devices. +config CMD_RTC + bool "rtc" + depends on DM_RTC + help + Enable the 'rtc' command for low-level access to RTC devices. + config CMD_TIME bool "time" help diff --git a/cmd/Makefile b/cmd/Makefile index 7008dd42dc1..dc412d1106d 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -122,6 +122,7 @@ obj-$(CONFIG_CMD_REISER) += reiser.o obj-$(CONFIG_CMD_REMOTEPROC) += remoteproc.o obj-$(CONFIG_CMD_RNG) += rng.o obj-$(CONFIG_CMD_ROCKUSB) += rockusb.o +obj-$(CONFIG_CMD_RTC) += rtc.o obj-$(CONFIG_SANDBOX) += host.o obj-$(CONFIG_CMD_SATA) += sata.o obj-$(CONFIG_CMD_NVME) += nvme.o diff --git a/cmd/rtc.c b/cmd/rtc.c new file mode 100644 index 00000000000..b4f61b2e835 --- /dev/null +++ b/cmd/rtc.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_RTC_BYTES 32 + +static int do_rtc_read(struct udevice *dev, int argc, char * const argv[]) +{ + u8 buf[MAX_RTC_BYTES]; + int reg, len, ret, r; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + reg = simple_strtoul(argv[0], NULL, 16); + len = simple_strtoul(argv[1], NULL, 16); + + if (argc == 3) { + u8 *addr; + + addr = map_sysmem(simple_strtoul(argv[2], NULL, 16), len); + ret = dm_rtc_read(dev, reg, addr, len); + unmap_sysmem(addr); + if (ret) { + printf("dm_rtc_read() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + + while (len) { + r = min_t(int, len, sizeof(buf)); + ret = dm_rtc_read(dev, reg, buf, r); + if (ret) { + printf("dm_rtc_read() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + print_buffer(reg, buf, 1, r, 0); + len -= r; + reg += r; + } + + return CMD_RET_SUCCESS; +} + +static int do_rtc_write(struct udevice *dev, int argc, char * const argv[]) +{ + u8 buf[MAX_RTC_BYTES]; + int reg, len, ret; + const char *s; + int slen; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + reg = simple_strtoul(argv[0], NULL, 16); + + if (argc == 3) { + u8 *addr; + + len = simple_strtoul(argv[1], NULL, 16); + addr = map_sysmem(simple_strtoul(argv[2], NULL, 16), len); + ret = dm_rtc_write(dev, reg, addr, len); + unmap_sysmem(addr); + if (ret) { + printf("dm_rtc_write() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + + s = argv[1]; + slen = strlen(s); + + if (slen % 2) { + printf("invalid hex string\n"); + return CMD_RET_FAILURE; + } + + while (slen) { + len = min_t(int, slen / 2, sizeof(buf)); + if (hex2bin(buf, s, len)) { + printf("invalid hex string\n"); + return CMD_RET_FAILURE; + } + + ret = dm_rtc_write(dev, reg, buf, len); + if (ret) { + printf("dm_rtc_write() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + s += 2 * len; + slen -= 2 * len; + } + + return CMD_RET_SUCCESS; +} + +int do_rtc(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + static int curr_rtc; + struct udevice *dev; + int ret, idx; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; + argv++; + + if (!strcmp(argv[0], "list")) { + struct uclass *uc; + + idx = 0; + uclass_id_foreach_dev(UCLASS_RTC, dev, uc) { + printf("RTC #%d - %s\n", idx++, dev->name); + } + if (!idx) { + printf("*** no RTC devices available ***\n"); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + + idx = curr_rtc; + if (!strcmp(argv[0], "dev") && argc >= 2) + idx = simple_strtoul(argv[1], NULL, 10); + + ret = uclass_get_device(UCLASS_RTC, idx, &dev); + if (ret) { + printf("Cannot find RTC #%d: err=%d\n", idx, ret); + return CMD_RET_FAILURE; + } + + if (!strcmp(argv[0], "dev")) { + /* Show the existing or newly selected RTC */ + if (argc >= 2) + curr_rtc = idx; + printf("RTC #%d - %s\n", idx, dev->name); + return CMD_RET_SUCCESS; + } + + if (!strcmp(argv[0], "read")) + return do_rtc_read(dev, argc - 1, argv + 1); + + if (!strcmp(argv[0], "write")) + return do_rtc_write(dev, argc - 1, argv + 1); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + rtc, 5, 0, do_rtc, + "RTC subsystem", + "list - show available rtc devices\n" + "rtc dev [n] - show or set current rtc device\n" + "rtc read - read and display 8-bit registers starting at \n" + "rtc read - read 8-bit registers starting at to memory \n" + "rtc write - write 8-bit registers starting at \n" + "rtc write - write from memory to 8-bit registers starting at \n" +); From 9fb6a41cdaa743c98b007c6769970b68c9521cd7 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:16 +0200 Subject: [PATCH 11/19] rtc: sandbox-rtc: fix set method The current set method is broken; a simple test case is to first set the date to something in April, then change the date to 31st May: => date 040412122020.34 Date: 2020-04-04 (Saturday) Time: 12:12:34 => date 053112122020.34 Date: 2020-05-01 (Friday) Time: 12:12:34 or via the amending of the existing rtc_set_get test case similarly: $ ./u-boot -T -v => ut dm rtc_set_get Test: dm_test_rtc_set_get: rtc.c expected: 31/08/2004 18:18:00 actual: 01/08/2004 18:18:00 The problem is that after each register write, sandbox_i2c_rtc_complete_write() gets called and sets the internal time from the current set of registers. However, when we get to writing 31 to mday, the registers are in an inconsistent state (mon is still 4), so the mktime machinery ends up translating April 31st to May 1st. Upon the next register write, the registers are populated by sandbox_i2c_rtc_prepare_read(), so the 31 we just wrote to mday gets overwritten by a 1. Fix it by writing all registers at once, and for consistency, update the get method to retrieve them all with one "i2c transfer". Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/sandbox_rtc.c | 65 +++++++++++++++------------------------ test/dm/rtc.c | 15 ++++++++- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/drivers/rtc/sandbox_rtc.c b/drivers/rtc/sandbox_rtc.c index b08d758a74e..77065e49c76 100644 --- a/drivers/rtc/sandbox_rtc.c +++ b/drivers/rtc/sandbox_rtc.c @@ -14,55 +14,38 @@ static int sandbox_rtc_get(struct udevice *dev, struct rtc_time *time) { - time->tm_sec = dm_i2c_reg_read(dev, REG_SEC); - if (time->tm_sec < 0) - return time->tm_sec; - time->tm_min = dm_i2c_reg_read(dev, REG_MIN); - if (time->tm_min < 0) - return time->tm_min; - time->tm_hour = dm_i2c_reg_read(dev, REG_HOUR); - if (time->tm_hour < 0) - return time->tm_hour; - time->tm_mday = dm_i2c_reg_read(dev, REG_MDAY); - if (time->tm_mday < 0) - return time->tm_mday; - time->tm_mon = dm_i2c_reg_read(dev, REG_MON); - if (time->tm_mon < 0) - return time->tm_mon; - time->tm_year = dm_i2c_reg_read(dev, REG_YEAR); - if (time->tm_year < 0) - return time->tm_year; - time->tm_year += 1900; - time->tm_wday = dm_i2c_reg_read(dev, REG_WDAY); - if (time->tm_wday < 0) - return time->tm_wday; + u8 buf[7]; + int ret; + + ret = dm_i2c_read(dev, REG_SEC, buf, sizeof(buf)); + if (ret < 0) + return ret; + + time->tm_sec = buf[REG_SEC - REG_SEC]; + time->tm_min = buf[REG_MIN - REG_SEC]; + time->tm_hour = buf[REG_HOUR - REG_SEC]; + time->tm_mday = buf[REG_MDAY - REG_SEC]; + time->tm_mon = buf[REG_MON - REG_SEC]; + time->tm_year = buf[REG_YEAR - REG_SEC] + 1900; + time->tm_wday = buf[REG_WDAY - REG_SEC]; return 0; } static int sandbox_rtc_set(struct udevice *dev, const struct rtc_time *time) { + u8 buf[7]; int ret; - ret = dm_i2c_reg_write(dev, REG_SEC, time->tm_sec); - if (ret < 0) - return ret; - ret = dm_i2c_reg_write(dev, REG_MIN, time->tm_min); - if (ret < 0) - return ret; - ret = dm_i2c_reg_write(dev, REG_HOUR, time->tm_hour); - if (ret < 0) - return ret; - ret = dm_i2c_reg_write(dev, REG_MDAY, time->tm_mday); - if (ret < 0) - return ret; - ret = dm_i2c_reg_write(dev, REG_MON, time->tm_mon); - if (ret < 0) - return ret; - ret = dm_i2c_reg_write(dev, REG_YEAR, time->tm_year - 1900); - if (ret < 0) - return ret; - ret = dm_i2c_reg_write(dev, REG_WDAY, time->tm_wday); + buf[REG_SEC - REG_SEC] = time->tm_sec; + buf[REG_MIN - REG_SEC] = time->tm_min; + buf[REG_HOUR - REG_SEC] = time->tm_hour; + buf[REG_MDAY - REG_SEC] = time->tm_mday; + buf[REG_MON - REG_SEC] = time->tm_mon; + buf[REG_YEAR - REG_SEC] = time->tm_year - 1900; + buf[REG_WDAY - REG_SEC] = time->tm_wday; + + ret = dm_i2c_write(dev, REG_SEC, buf, sizeof(buf)); if (ret < 0) return ret; diff --git a/test/dm/rtc.c b/test/dm/rtc.c index 88f86581cce..e072fd618b0 100644 --- a/test/dm/rtc.c +++ b/test/dm/rtc.c @@ -70,7 +70,20 @@ static int dm_test_rtc_set_get(struct unit_test_state *uts) old_base_time = sandbox_i2c_rtc_get_set_base_time(emul, -1); memset(&time, '\0', sizeof(time)); - time.tm_mday = 25; + time.tm_mday = 3; + time.tm_mon = 6; + time.tm_year = 2004; + time.tm_sec = 0; + time.tm_min = 18; + time.tm_hour = 18; + ut_assertok(dm_rtc_set(dev, &time)); + + memset(&cmp, '\0', sizeof(cmp)); + ut_assertok(dm_rtc_get(dev, &cmp)); + ut_assertok(cmp_times(&time, &cmp, true)); + + memset(&time, '\0', sizeof(time)); + time.tm_mday = 31; time.tm_mon = 8; time.tm_year = 2004; time.tm_sec = 0; From a3e36525a9b853c09d5f5726eff3641cd0cb5619 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:17 +0200 Subject: [PATCH 12/19] rtc: i2c_rtc_emul: catch any write to the "reset" register It's more natural that any write that happens to touch the reset register should cause a reset, rather than just a write that starts at that offset. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- drivers/rtc/i2c_rtc_emul.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/i2c_rtc_emul.c b/drivers/rtc/i2c_rtc_emul.c index a010af411bb..7f78ff83cb0 100644 --- a/drivers/rtc/i2c_rtc_emul.c +++ b/drivers/rtc/i2c_rtc_emul.c @@ -197,7 +197,8 @@ static int sandbox_i2c_rtc_xfer(struct udevice *emul, struct i2c_msg *msg, /* Write the register */ memcpy(plat->reg + offset, ptr, len); - if (offset == REG_RESET) + /* If the reset register was written to, do reset. */ + if (offset <= REG_RESET && REG_RESET < offset + len) reset_time(emul); } } From baed77913831325bf25dcc27ff72e1cd742a7cc3 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:18 +0200 Subject: [PATCH 13/19] test: dm: rtc: add test of dm_rtc_read, dm_rtc_write Define a few aux registers and check that they can be read/written individually. Also check that one can access the time-keeping registers directly and get the expected results. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- arch/sandbox/include/asm/rtc.h | 5 ++++ test/dm/rtc.c | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/arch/sandbox/include/asm/rtc.h b/arch/sandbox/include/asm/rtc.h index 1fbfea79998..5bb032f59f2 100644 --- a/arch/sandbox/include/asm/rtc.h +++ b/arch/sandbox/include/asm/rtc.h @@ -21,6 +21,11 @@ enum { REG_RESET = 0x20, + REG_AUX0 = 0x30, + REG_AUX1, + REG_AUX2, + REG_AUX3, + REG_COUNT = 0x80, }; diff --git a/test/dm/rtc.c b/test/dm/rtc.c index e072fd618b0..ff1bc7b7f66 100644 --- a/test/dm/rtc.c +++ b/test/dm/rtc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,50 @@ static int dm_test_rtc_set_get(struct unit_test_state *uts) } DM_TEST(dm_test_rtc_set_get, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); +static int dm_test_rtc_read_write(struct unit_test_state *uts) +{ + struct rtc_time time; + struct udevice *dev, *emul; + long old_offset; + u8 buf[4], reg; + + ut_assertok(uclass_get_device(UCLASS_RTC, 0, &dev)); + + memcpy(buf, "car", 4); + ut_assertok(dm_rtc_write(dev, REG_AUX0, buf, 4)); + memset(buf, '\0', sizeof(buf)); + ut_assertok(dm_rtc_read(dev, REG_AUX0, buf, 4)); + ut_asserteq(memcmp(buf, "car", 4), 0); + + reg = 'b'; + ut_assertok(dm_rtc_write(dev, REG_AUX0, ®, 1)); + memset(buf, '\0', sizeof(buf)); + ut_assertok(dm_rtc_read(dev, REG_AUX0, buf, 4)); + ut_asserteq(memcmp(buf, "bar", 4), 0); + + reg = 't'; + ut_assertok(dm_rtc_write(dev, REG_AUX2, ®, 1)); + memset(buf, '\0', sizeof(buf)); + ut_assertok(dm_rtc_read(dev, REG_AUX1, buf, 3)); + ut_asserteq(memcmp(buf, "at", 3), 0); + + ut_assertok(i2c_emul_find(dev, &emul)); + ut_assert(emul != NULL); + + old_offset = sandbox_i2c_rtc_set_offset(emul, false, 0); + ut_assertok(dm_rtc_get(dev, &time)); + + ut_assertok(dm_rtc_read(dev, REG_SEC, ®, 1)); + ut_asserteq(time.tm_sec, reg); + ut_assertok(dm_rtc_read(dev, REG_MDAY, ®, 1)); + ut_asserteq(time.tm_mday, reg); + + sandbox_i2c_rtc_set_offset(emul, true, old_offset); + + return 0; +} +DM_TEST(dm_test_rtc_read_write, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + /* Reset the time */ static int dm_test_rtc_reset(struct unit_test_state *uts) { From 8ce16be94bee4c2dfc544ae2488b71f9fd740054 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:19 +0200 Subject: [PATCH 14/19] sandbox: add rtc command to defconfigs In order to allow adding unit tests of the rtc command, add it to the various sandbox defconfigs. Signed-off-by: Rasmus Villemoes --- configs/sandbox64_defconfig | 1 + configs/sandbox_defconfig | 1 + configs/sandbox_flattree_defconfig | 1 + 3 files changed, 3 insertions(+) diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index ac604b57b64..dcf2f44b584 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -60,6 +60,7 @@ CONFIG_CMD_LINK_LOCAL=y CONFIG_CMD_ETHSW=y CONFIG_CMD_BMP=y CONFIG_CMD_EFIDEBUG=y +CONFIG_CMD_RTC=y CONFIG_CMD_TIME=y CONFIG_CMD_TIMER=y CONFIG_CMD_SOUND=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 8cf2ad5fe41..e0dffa36ea9 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -69,6 +69,7 @@ CONFIG_CMD_ETHSW=y CONFIG_CMD_BMP=y CONFIG_CMD_BOOTCOUNT=y CONFIG_CMD_EFIDEBUG=y +CONFIG_CMD_RTC=y CONFIG_CMD_TIME=y CONFIG_CMD_TIMER=y CONFIG_CMD_SOUND=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index dfcc43532ab..4158b9b86d4 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -49,6 +49,7 @@ CONFIG_CMD_SNTP=y CONFIG_CMD_DNS=y CONFIG_CMD_LINK_LOCAL=y CONFIG_CMD_EFIDEBUG=y +CONFIG_CMD_RTC=y CONFIG_CMD_TIME=y CONFIG_CMD_TIMER=y CONFIG_CMD_SOUND=y From c203ba44f1df393c196646f64f894481dacea5e3 Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 6 Jul 2020 22:01:20 +0200 Subject: [PATCH 15/19] test: dm: rtc: add tests of rtc shell command Add tests of the "list", "read" and "write" subcommands of the rtc shell command. Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher Signed-off-by: Rasmus Villemoes --- test/dm/rtc.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/dm/rtc.c b/test/dm/rtc.c index ff1bc7b7f66..dd037a6e17a 100644 --- a/test/dm/rtc.c +++ b/test/dm/rtc.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -175,6 +176,63 @@ static int dm_test_rtc_read_write(struct unit_test_state *uts) } DM_TEST(dm_test_rtc_read_write, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); +/* Test 'rtc list' command */ +static int dm_test_rtc_cmd_list(struct unit_test_state *uts) +{ + console_record_reset(); + + run_command("rtc list", 0); + ut_assert_nextline("RTC #0 - rtc@43"); + ut_assert_nextline("RTC #1 - rtc@61"); + ut_assert_console_end(); + + return 0; +} +DM_TEST(dm_test_rtc_cmd_list, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test 'rtc read' and 'rtc write' commands */ +static int dm_test_rtc_cmd_rw(struct unit_test_state *uts) +{ + console_record_reset(); + + run_command("rtc dev 0", 0); + ut_assert_nextline("RTC #0 - rtc@43"); + ut_assert_console_end(); + + run_command("rtc write 0x30 aabb", 0); + ut_assert_console_end(); + + run_command("rtc read 0x30 2", 0); + ut_assert_nextline("00000030: aa bb .."); + ut_assert_console_end(); + + run_command("rtc dev 1", 0); + ut_assert_nextline("RTC #1 - rtc@61"); + ut_assert_console_end(); + + run_command("rtc write 0x30 ccdd", 0); + ut_assert_console_end(); + + run_command("rtc read 0x30 2", 0); + ut_assert_nextline("00000030: cc dd .."); + ut_assert_console_end(); + + /* + * Switch back to device #0, check that its aux registers + * still have the same values. + */ + run_command("rtc dev 0", 0); + ut_assert_nextline("RTC #0 - rtc@43"); + ut_assert_console_end(); + + run_command("rtc read 0x30 2", 0); + ut_assert_nextline("00000030: aa bb .."); + ut_assert_console_end(); + + return 0; +} +DM_TEST(dm_test_rtc_cmd_rw, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + /* Reset the time */ static int dm_test_rtc_reset(struct unit_test_state *uts) { From fb3388cd85c43e6b262d7ea3c897bed42ac3dff8 Mon Sep 17 00:00:00 2001 From: Patrick Delaunay Date: Mon, 6 Jul 2020 13:26:52 +0200 Subject: [PATCH 16/19] i2c: stm32f7: add stm32mp15 compatible Add a new compatible "st,stm32mp15-i2c" introduced in Linux kernel v5.8 Signed-off-by: Patrick Delaunay Reviewed-by: Heiko Schocher --- drivers/i2c/stm32f7_i2c.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/stm32f7_i2c.c b/drivers/i2c/stm32f7_i2c.c index ada8f4095ed..593f713d6bb 100644 --- a/drivers/i2c/stm32f7_i2c.c +++ b/drivers/i2c/stm32f7_i2c.c @@ -873,6 +873,7 @@ static const struct dm_i2c_ops stm32_i2c_ops = { static const struct udevice_id stm32_i2c_of_match[] = { { .compatible = "st,stm32f7-i2c", .data = (ulong)&stm32f7_setup }, + { .compatible = "st,stm32mp15-i2c", .data = (ulong)&stm32f7_setup }, {} }; From 7ce87dcff4e87b2d067f911beee23ad221f3b467 Mon Sep 17 00:00:00 2001 From: Patrick Delaunay Date: Mon, 6 Jul 2020 13:31:35 +0200 Subject: [PATCH 17/19] i2c: stm32f7: SYSCFG Fast Mode Plus support for I2C STM32F7 Read SYSCFG bindings to set Fast Mode Plus bits if Fast Mode Plus speed is selected. Handle the stm32mp15 specific compatible to handle FastMode+ registers handling which is different on the stm32mp15 compared to the stm32f7 or stm32h7. Indeed, on the stm32mp15, the FastMode+ set and clear registers are separated while on the other platforms (F7 or H7) the control is done in a unique register. Signed-off-by: Patrick Delaunay Reviewed-by: Heiko Schocher --- drivers/i2c/stm32f7_i2c.c | 74 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/stm32f7_i2c.c b/drivers/i2c/stm32f7_i2c.c index 593f713d6bb..2f609115494 100644 --- a/drivers/i2c/stm32f7_i2c.c +++ b/drivers/i2c/stm32f7_i2c.c @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include @@ -154,6 +156,7 @@ struct stm32_i2c_spec { * @fall_time: Fall time (ns) * @dnf: Digital filter coefficient (0-16) * @analog_filter: Analog filter delay (On/Off) + * @fmp_clr_offset: Fast Mode Plus clear register offset from set register */ struct stm32_i2c_setup { u32 speed_freq; @@ -162,6 +165,7 @@ struct stm32_i2c_setup { u32 fall_time; u8 dnf; bool analog_filter; + u32 fmp_clr_offset; }; /** @@ -181,11 +185,26 @@ struct stm32_i2c_timings { u8 scll; }; +/** + * struct stm32_i2c_priv - private data of the controller + * @regs: I2C registers address + * @clk: hw i2c clock + * @setup: I2C timing setup parameters + * @speed: I2C clock frequency of the controller. Standard, Fast or Fast+ + * @regmap: holds SYSCFG phandle for Fast Mode Plus bit + * @regmap_sreg: register address for setting Fast Mode Plus bits + * @regmap_creg: register address for clearing Fast Mode Plus bits + * @regmap_mask: mask for Fast Mode Plus bits + */ struct stm32_i2c_priv { struct stm32_i2c_regs *regs; struct clk clk; struct stm32_i2c_setup *setup; u32 speed; + struct regmap *regmap; + u32 regmap_sreg; + u32 regmap_creg; + u32 regmap_mask; }; static const struct stm32_i2c_spec i2c_specs[] = { @@ -237,6 +256,14 @@ static const struct stm32_i2c_setup stm32f7_setup = { .analog_filter = STM32_I2C_ANALOG_FILTER_ENABLE, }; +static const struct stm32_i2c_setup stm32mp15_setup = { + .rise_time = STM32_I2C_RISE_TIME_DEFAULT, + .fall_time = STM32_I2C_FALL_TIME_DEFAULT, + .dnf = STM32_I2C_DNF_DEFAULT, + .analog_filter = STM32_I2C_ANALOG_FILTER_ENABLE, + .fmp_clr_offset = 0x40, +}; + static int stm32_i2c_check_device_busy(struct stm32_i2c_priv *i2c_priv) { struct stm32_i2c_regs *regs = i2c_priv->regs; @@ -761,6 +788,29 @@ static int stm32_i2c_setup_timing(struct stm32_i2c_priv *i2c_priv, return 0; } +static int stm32_i2c_write_fm_plus_bits(struct stm32_i2c_priv *i2c_priv) +{ + int ret; + bool enable = i2c_priv->speed > I2C_SPEED_FAST_RATE; + + /* Optional */ + if (IS_ERR_OR_NULL(i2c_priv->regmap)) + return 0; + + if (i2c_priv->regmap_sreg == i2c_priv->regmap_creg) + ret = regmap_update_bits(i2c_priv->regmap, + i2c_priv->regmap_sreg, + i2c_priv->regmap_mask, + enable ? i2c_priv->regmap_mask : 0); + else + ret = regmap_write(i2c_priv->regmap, + enable ? i2c_priv->regmap_sreg : + i2c_priv->regmap_creg, + i2c_priv->regmap_mask); + + return ret; +} + static int stm32_i2c_hw_config(struct stm32_i2c_priv *i2c_priv) { struct stm32_i2c_regs *regs = i2c_priv->regs; @@ -775,6 +825,11 @@ static int stm32_i2c_hw_config(struct stm32_i2c_priv *i2c_priv) /* Disable I2C */ clrbits_le32(®s->cr1, STM32_I2C_CR1_PE); + /* Setup Fast mode plus if necessary */ + ret = stm32_i2c_write_fm_plus_bits(i2c_priv); + if (ret) + return ret; + /* Timing settings */ timing |= STM32_I2C_TIMINGR_PRESC(t.presc); timing |= STM32_I2C_TIMINGR_SCLDEL(t.scldel); @@ -850,6 +905,7 @@ static int stm32_ofdata_to_platdata(struct udevice *dev) { struct stm32_i2c_priv *i2c_priv = dev_get_priv(dev); u32 rise_time, fall_time; + int ret; i2c_priv->setup = (struct stm32_i2c_setup *)dev_get_driver_data(dev); if (!i2c_priv->setup) @@ -863,6 +919,22 @@ static int stm32_ofdata_to_platdata(struct udevice *dev) if (fall_time) i2c_priv->setup->fall_time = fall_time; + /* Optional */ + i2c_priv->regmap = syscon_regmap_lookup_by_phandle(dev, + "st,syscfg-fmp"); + if (!IS_ERR(i2c_priv->regmap)) { + u32 fmp[3]; + + ret = dev_read_u32_array(dev, "st,syscfg-fmp", fmp, 3); + if (ret) + return ret; + + i2c_priv->regmap_sreg = fmp[1]; + i2c_priv->regmap_creg = fmp[1] + + i2c_priv->setup->fmp_clr_offset; + i2c_priv->regmap_mask = fmp[2]; + } + return 0; } @@ -873,7 +945,7 @@ static const struct dm_i2c_ops stm32_i2c_ops = { static const struct udevice_id stm32_i2c_of_match[] = { { .compatible = "st,stm32f7-i2c", .data = (ulong)&stm32f7_setup }, - { .compatible = "st,stm32mp15-i2c", .data = (ulong)&stm32f7_setup }, + { .compatible = "st,stm32mp15-i2c", .data = (ulong)&stm32mp15_setup }, {} }; From 0dae9e24ea71d6cc21cb5c2804efeb3aedcbdc96 Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Thu, 23 Apr 2020 16:57:15 +0530 Subject: [PATCH 18/19] pwm: Add DT documentation for SiFive PWM Controller DT documentation for PWM controller added from Linux v5.6 commit: daa78cc3408e ("pwm: sifive: Add DT documentation for SiFive PWM Controller") Signed-off-by: Yash Shah Reviewed-by: Heiko Schocher --- doc/device-tree-bindings/pwm/pwm-sifive.txt | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 doc/device-tree-bindings/pwm/pwm-sifive.txt diff --git a/doc/device-tree-bindings/pwm/pwm-sifive.txt b/doc/device-tree-bindings/pwm/pwm-sifive.txt new file mode 100644 index 00000000000..9a988372c44 --- /dev/null +++ b/doc/device-tree-bindings/pwm/pwm-sifive.txt @@ -0,0 +1,31 @@ +SiFive PWM controller + +Unlike most other PWM controllers, the SiFive PWM controller currently only +supports one period for all channels in the PWM. All PWMs need to run at +the same period. The period also has significant restrictions on the values +it can achieve, which the driver rounds to the nearest achievable period. +PWM RTL that corresponds to the IP block version numbers can be found +here: + +https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/pwm + +Required properties: +- compatible: Should be "sifive,-pwm" and "sifive,pwm". + Supported compatible strings are: "sifive,fu540-c000-pwm" for the SiFive + PWM v0 as integrated onto the SiFive FU540 chip, and "sifive,pwm0" for the + SiFive PWM v0 IP block with no chip integration tweaks. +- reg: physical base address and length of the controller's registers +- clocks: Should contain a clock identifier for the PWM's parent clock. +- #pwm-cells: Should be 3. +- interrupts: one interrupt per PWM channel + +Examples: + +pwm: pwm@10020000 { + compatible = "sifive,fu540-c000-pwm", "sifive,pwm0"; + reg = <0x0 0x10020000 0x0 0x1000>; + clocks = <&tlclk>; + interrupt-parent = <&plic>; + interrupts = <42 43 44 45>; + #pwm-cells = <3>; +}; From 7239a610b796b0bb8f85c5c21798596c2768cb50 Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Thu, 23 Apr 2020 16:57:16 +0530 Subject: [PATCH 19/19] pwm: Add PWM driver for SiFive SoC Adds a PWM driver for PWM chip present in SiFive's HiFive Unleashed SoC This driver is simple port of Linux pwm sifive driver from Linux v5.6 commit: 9e37a53eb051 ("pwm: sifive: Add a driver for SiFive SoC PWM") Signed-off-by: Yash Shah Reviewed-by: Heiko Schocher --- drivers/pwm/Kconfig | 6 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-sifive.c | 172 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 drivers/pwm/pwm-sifive.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index edb3f0f538e..61eb468cdeb 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -47,6 +47,12 @@ config PWM_SANDBOX useful. The PWM can be enabled but is not connected to any outputs so this is not very useful. +config PWM_SIFIVE + bool "Enable support for SiFive PWM" + depends on DM_PWM + help + This PWM is found SiFive's FU540 and other SoCs. + config PWM_TEGRA bool "Enable support for the Tegra PWM" depends on DM_PWM diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 2c3a069006a..0f4e84b04de 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -15,5 +15,6 @@ obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o obj-$(CONFIG_PWM_MTK) += pwm-mtk.o obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o +obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c new file mode 100644 index 00000000000..77bc659fefe --- /dev/null +++ b/drivers/pwm/pwm-sifive.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 SiFive, Inc + * For SiFive's PWM IP block documentation please refer Chapter 14 of + * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf + * + * Limitations: + * - When changing both duty cycle and period, we cannot prevent in + * software that the output might produce a period with mixed + * settings (new period length and old duty cycle). + * - The hardware cannot generate a 100% duty cycle. + * - The hardware generates only inverted output. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PWMCFG fields */ +#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0) +#define PWM_SIFIVE_PWMCFG_STICKY BIT(8) +#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9) +#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10) +#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12) +#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13) +#define PWM_SIFIVE_PWMCFG_CENTER BIT(16) +#define PWM_SIFIVE_PWMCFG_GANG BIT(24) +#define PWM_SIFIVE_PWMCFG_IP BIT(28) + +/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */ +#define PWM_SIFIVE_SIZE_PWMCMP 4 +#define PWM_SIFIVE_CMPWIDTH 16 + +DECLARE_GLOBAL_DATA_PTR; + +struct pwm_sifive_regs { + unsigned long cfg; + unsigned long cnt; + unsigned long pwms; + unsigned long cmp0; +}; + +struct pwm_sifive_data { + struct pwm_sifive_regs regs; +}; + +struct pwm_sifive_priv { + void __iomem *base; + ulong freq; + const struct pwm_sifive_data *data; +}; + +static int pwm_sifive_set_config(struct udevice *dev, uint channel, + uint period_ns, uint duty_ns) +{ + struct pwm_sifive_priv *priv = dev_get_priv(dev); + const struct pwm_sifive_regs *regs = &priv->data->regs; + unsigned long scale_pow; + unsigned long long num; + u32 scale, val = 0, frac; + + debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns); + + /* + * The PWM unit is used with pwmzerocmp=0, so the only way to modify the + * period length is using pwmscale which provides the number of bits the + * counter is shifted before being feed to the comparators. A period + * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks. + * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period + */ + scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000); + scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf); + val |= FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale); + + /* + * The problem of output producing mixed setting as mentioned at top, + * occurs here. To minimize the window for this problem, we are + * calculating the register values first and then writing them + * consecutively + */ + num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH); + frac = DIV_ROUND_CLOSEST_ULL(num, period_ns); + frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); + + writel(val, priv->base + regs->cfg); + writel(frac, priv->base + regs->cmp0 + channel * + PWM_SIFIVE_SIZE_PWMCMP); + + return 0; +} + +static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable) +{ + struct pwm_sifive_priv *priv = dev_get_priv(dev); + const struct pwm_sifive_regs *regs = &priv->data->regs; + u32 val; + + debug("%s: Enable '%s'\n", __func__, dev->name); + + if (enable) { + val = readl(priv->base + regs->cfg); + val |= PWM_SIFIVE_PWMCFG_EN_ALWAYS; + writel(val, priv->base + regs->cfg); + } else { + writel(0, priv->base + regs->cmp0 + channel * + PWM_SIFIVE_SIZE_PWMCMP); + } + + return 0; +} + +static int pwm_sifive_ofdata_to_platdata(struct udevice *dev) +{ + struct pwm_sifive_priv *priv = dev_get_priv(dev); + + priv->base = dev_read_addr_ptr(dev); + + return 0; +} + +static int pwm_sifive_probe(struct udevice *dev) +{ + struct pwm_sifive_priv *priv = dev_get_priv(dev); + struct clk clk; + int ret = 0; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret < 0) { + debug("%s get clock fail!\n", __func__); + return -EINVAL; + } + + priv->freq = clk_get_rate(&clk); + priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev); + + return 0; +} + +static const struct pwm_ops pwm_sifive_ops = { + .set_config = pwm_sifive_set_config, + .set_enable = pwm_sifive_set_enable, +}; + +static const struct pwm_sifive_data pwm_data = { + .regs = { + .cfg = 0x00, + .cnt = 0x08, + .pwms = 0x10, + .cmp0 = 0x20, + }, +}; + +static const struct udevice_id pwm_sifive_ids[] = { + { .compatible = "sifive,pwm0", .data = (ulong)&pwm_data}, + { } +}; + +U_BOOT_DRIVER(pwm_sifive) = { + .name = "pwm_sifive", + .id = UCLASS_PWM, + .of_match = pwm_sifive_ids, + .ops = &pwm_sifive_ops, + .ofdata_to_platdata = pwm_sifive_ofdata_to_platdata, + .probe = pwm_sifive_probe, + .priv_auto_alloc_size = sizeof(struct pwm_sifive_priv), +};