diff options
author | Tom Rini <trini@konsulko.com> | 2020-07-09 08:22:44 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2020-07-09 08:22:44 -0400 |
commit | d9107930af63d88c2d84560db19e65f1a51c4cbd (patch) | |
tree | d35deb928f896c3179d65765edeebca54e901ee5 /drivers | |
parent | 5fb70639cc5eea60e37e5eaaa7cc145e29527658 (diff) | |
parent | 7239a610b796b0bb8f85c5c21798596c2768cb50 (diff) | |
download | u-boot-d9107930af63d88c2d84560db19e65f1a51c4cbd.tar.gz u-boot-d9107930af63d88c2d84560db19e65f1a51c4cbd.tar.bz2 u-boot-d9107930af63d88c2d84560db19e65f1a51c4cbd.zip |
Merge tag 'for-v2020.10' of https://gitlab.denx.de/u-boot/custodians/u-boot-i2c
i2c changes for v2020.10
- Add support for I2C controllers found on Octeon II/III and Octeon TX
TX2 SoC platforms.
- Add I2C controller support for Cortina Access CAxxxx SoCs
- new rtc methods, rtc command, and tests
- imx_lpi2c: Improve the codes to use private data
- stm32f7_i2c.c: Add new compatible "st,stm32mp15-i2c"
- stm32f7_i2c.c: Add Fast Mode Plus support
- pwm: Add PWM driver for SiFive SoC
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/Kconfig | 18 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 2 | ||||
-rw-r--r-- | drivers/i2c/i2c-cortina.c | 347 | ||||
-rw-r--r-- | drivers/i2c/i2c-cortina.h | 87 | ||||
-rw-r--r-- | drivers/i2c/imx_lpi2c.c | 22 | ||||
-rw-r--r-- | drivers/i2c/octeon_i2c.c | 847 | ||||
-rw-r--r-- | drivers/i2c/stm32f7_i2c.c | 73 | ||||
-rw-r--r-- | drivers/pwm/Kconfig | 6 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-sifive.c | 172 | ||||
-rw-r--r-- | drivers/rtc/i2c_rtc_emul.c | 3 | ||||
-rw-r--r-- | drivers/rtc/pcf2127.c | 13 | ||||
-rw-r--r-- | drivers/rtc/rtc-uclass.c | 59 | ||||
-rw-r--r-- | drivers/rtc/sandbox_rtc.c | 65 |
14 files changed, 1655 insertions, 60 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index f8b18de8f3..87d11b663c 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) @@ -374,6 +382,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..174081e252 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 @@ -27,6 +28,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/i2c-cortina.c b/drivers/i2c/i2c-cortina.c new file mode 100644 index 0000000000..036fc4282b --- /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 <common.h> +#include <i2c.h> +#include <log.h> +#include <asm/io.h> +#include <dm.h> +#include <mapmem.h> +#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 0000000000..7e406b580e --- /dev/null +++ b/drivers/i2c/i2c-cortina.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2019 + * Cortina Access, <www.cortina-access.com> + */ + +#ifndef __CA_I2C_H_ +#define __CA_I2C_H_ + +#include <linux/bitops.h> +#include <linux/delay.h> + +#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_ */ diff --git a/drivers/i2c/imx_lpi2c.c b/drivers/i2c/imx_lpi2c.c index c8e42e05f5..b7b2aafc7f 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); diff --git a/drivers/i2c/octeon_i2c.c b/drivers/i2c/octeon_i2c.c new file mode 100644 index 0000000000..c11d6ff93d --- /dev/null +++ b/drivers/i2c/octeon_i2c.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell International Ltd. + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <i2c.h> +#include <pci_ids.h> +#include <asm/io.h> +#include <linux/bitfield.h> +#include <linux/compat.h> +#include <linux/delay.h> + +#define TWSI_SW_TWSI 0x00 +#define TWSI_TWSI_SW 0x08 +#define TWSI_INT 0x10 +#define TWSI_SW_TWSI_EXT 0x18 + +#define TWSI_SW_DATA_MASK GENMASK_ULL(31, 0) +#define TWSI_SW_EOP_IA_MASK GENMASK_ULL(34, 32) +#define TWSI_SW_IA_MASK GENMASK_ULL(39, 35) +#define TWSI_SW_ADDR_MASK GENMASK_ULL(49, 40) +#define TWSI_SW_SCR_MASK GENMASK_ULL(51, 50) +#define TWSI_SW_SIZE_MASK GENMASK_ULL(54, 52) +#define TWSI_SW_SOVR BIT_ULL(55) +#define TWSI_SW_R BIT_ULL(56) +#define TWSI_SW_OP_MASK GENMASK_ULL(60, 57) +#define TWSI_SW_EIA GENMASK_ULL(61) +#define TWSI_SW_SLONLY BIT_ULL(62) +#define TWSI_SW_V BIT_ULL(63) + +#define TWSI_INT_SDA_OVR BIT_ULL(8) +#define TWSI_INT_SCL_OVR BIT_ULL(9) +#define TWSI_INT_SDA BIT_ULL(10) +#define TWSI_INT_SCL BIT_ULL(11) + +enum { + TWSI_OP_WRITE = 0, + TWSI_OP_READ = 1, +}; + +enum { + TWSI_EOP_SLAVE_ADDR = 0, + TWSI_EOP_CLK_CTL = 3, + TWSI_SW_EOP_IA = 6, +}; + +enum { + TWSI_SLAVEADD = 0, + TWSI_DATA = 1, + TWSI_CTL = 2, + TWSI_CLKCTL = 3, + TWSI_STAT = 3, + TWSI_SLAVEADD_EXT = 4, + TWSI_RST = 7, +}; + +enum { + TWSI_CTL_AAK = BIT(2), + TWSI_CTL_IFLG = BIT(3), + TWSI_CTL_STP = BIT(4), + TWSI_CTL_STA = BIT(5), + TWSI_CTL_ENAB = BIT(6), + TWSI_CTL_CE = BIT(7), +}; + +/* + * Internal errors. When debugging is enabled, the driver will report the + * error number and the user / developer can check the table below for the + * detailed error description. + */ +enum { + /** Bus error */ + TWSI_STAT_BUS_ERROR = 0x00, + /** Start condition transmitted */ + TWSI_STAT_START = 0x08, + /** Repeat start condition transmitted */ + TWSI_STAT_RSTART = 0x10, + /** Address + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR_ACK = 0x18, + /** Address + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR_NAK = 0x20, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_ACK = 0x28, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_NAK = 0x30, + /** Arbitration lost in address or data byte */ + TWSI_STAT_TX_ARB_LOST = 0x38, + /** Address + read bit transmitted, ACK received */ + TWSI_STAT_RXADDR_ACK = 0x40, + /** Address + read bit transmitted, /ACK received */ + TWSI_STAT_RXADDR_NAK = 0x48, + /** Data byte received in master mode, ACK transmitted */ + TWSI_STAT_RXDATA_ACK_SENT = 0x50, + /** Data byte received, NACK transmitted */ + TWSI_STAT_RXDATA_NAK_SENT = 0x58, + /** Slave address received, sent ACK */ + TWSI_STAT_SLAVE_RXADDR_ACK = 0x60, + /** + * Arbitration lost in address as master, slave address + write bit + * received, ACK transmitted + */ + TWSI_STAT_TX_ACK_ARB_LOST = 0x68, + /** General call address received, ACK transmitted */ + TWSI_STAT_RX_GEN_ADDR_ACK = 0x70, + /** + * Arbitration lost in address as master, general call address + * received, ACK transmitted + */ + TWSI_STAT_RX_GEN_ADDR_ARB_LOST = 0x78, + /** Data byte received after slave address received, ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_ACK = 0x80, + /** Data byte received after slave address received, /ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_NAK = 0x88, + /** + * Data byte received after general call address received, ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_ACK = 0x90, + /** + * Data byte received after general call address received, /ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_NAK = 0x98, + /** STOP or repeated START condition received in slave mode */ + TWSI_STAT_STOP_MULTI_START = 0xa0, + /** Slave address + read bit received, ACK transmitted */ + TWSI_STAT_SLAVE_RXADDR2_ACK = 0xa8, + /** + * Arbitration lost in address as master, slave address + read bit + * received, ACK transmitted + */ + TWSI_STAT_RXDATA_ACK_ARB_LOST = 0xb0, + /** Data byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_ACK = 0xb8, + /** Data byte transmitted in slave mode, /ACK received */ + TWSI_STAT_SLAVE_TXDATA_NAK = 0xc0, + /** Last byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_END_ACK = 0xc8, + /** Second address byte + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR2DATA_ACK = 0xd0, + /** Second address byte + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR2DATA_NAK = 0xd8, + /** No relevant status information */ + TWSI_STAT_IDLE = 0xf8 +}; + +#define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR 0x77 + +enum { + PROBE_PCI = 0, /* PCI based probing */ + PROBE_DT, /* DT based probing */ +}; + +enum { + CLK_METHOD_OCTEON = 0, + CLK_METHOD_OCTEONTX2, +}; + +/** + * struct octeon_i2c_data - SoC specific data of this driver + * + * @probe: Probing of this SoC (DT vs PCI) + * @reg_offs: Register offset + * @thp: THP define for divider calculation + * @clk_method: Clock calculation method + */ +struct octeon_i2c_data { + int probe; + u32 reg_offs; + int thp; + int clk_method; +}; + +/** + * struct octeon_twsi - Private data of this driver + * + * @base: Base address of i2c registers + * @data: Pointer to SoC specific data struct + */ +struct octeon_twsi { + void __iomem *base; + const struct octeon_i2c_data *data; + struct clk clk; +}; + +static void twsi_unblock(void *base); +static int twsi_stop(void *base); + +/** + * Returns true if we lost arbitration + * + * @code status code + * @final_read true if this is the final read operation + * @return true if arbitration has been lost, false if it hasn't been lost. + */ +static int twsi_i2c_lost_arb(u8 code, int final_read) +{ + switch (code) { + case TWSI_STAT_TX_ARB_LOST: + case TWSI_STAT_TX_ACK_ARB_LOST: + case TWSI_STAT_RX_GEN_ADDR_ARB_LOST: + case TWSI_STAT_RXDATA_ACK_ARB_LOST: + /* Arbitration lost */ + return -EAGAIN; + + case TWSI_STAT_SLAVE_RXADDR_ACK: + case TWSI_STAT_RX_GEN_ADDR_ACK: + case TWSI_STAT_GEN_RXADDR_ACK: + case TWSI_STAT_GEN_RXADDR_NAK: + /* Being addressed as slave, should back off and listen */ + return -EIO; + + case TWSI_STAT_SLAVE_RXDATA_ACK: + case TWSI_STAT_SLAVE_RXDATA_NAK: + case TWSI_STAT_STOP_MULTI_START: + case TWSI_STAT_SLAVE_RXADDR2_ACK: + case TWSI_STAT_SLAVE_TXDATA_ACK: + case TWSI_STAT_SLAVE_TXDATA_NAK: + case TWSI_STAT_SLAVE_TXDATA_END_ACK: + /* Core busy as slave */ + return -EIO; + + case TWSI_STAT_RXDATA_ACK_SENT: + /* Ack allowed on pre-terminal bytes only */ + if (!final_read) + return 0; + return -EAGAIN; + + case TWSI_STAT_RXDATA_NAK_SENT: + /* NAK allowed on terminal byte only */ + if (!final_read) + return 0; + return -EAGAIN; + + case TWSI_STAT_TXDATA_NAK: + case TWSI_STAT_TXADDR_NAK: + case TWSI_STAT_RXADDR_NAK: + case TWSI_STAT_TXADDR2DATA_NAK: + return -EAGAIN; + } + + return 0; +} + +/** + * Writes to the MIO_TWS(0..5)_SW_TWSI register + * + * @base Base address of i2c registers + * @val value to write + * @return 0 for success, otherwise error + */ +static u64 twsi_write_sw(void __iomem *base, u64 val) +{ + unsigned long start = get_timer(0); + + val &= ~TWSI_SW_R; + val |= TWSI_SW_V; + + debug("%s(%p, 0x%llx)\n", __func__, base, val); + writeq(val, base + TWSI_SW_TWSI); + do { + val = readq(base + TWSI_SW_TWSI); + } while ((val & TWSI_SW_V) && (get_timer(start) < 50)); + + if (val & TWSI_SW_V) + debug("%s: timed out\n", __func__); + return val; +} + +/** + * Reads the MIO_TWS(0..5)_SW_TWSI register + * + * @base Base address of i2c registers + * @val value for eia and op, etc. to read + * @return value of the register + */ +static u64 twsi_read_sw(void __iomem *base, u64 val) +{ + unsigned long start = get_timer(0); + + val |= TWSI_SW_R | TWSI_SW_V; + + debug("%s(%p, 0x%llx)\n", __func__, base, val); + writeq(val, base + TWSI_SW_TWSI); + + do { + val = readq(base + TWSI_SW_TWSI); + } while ((val & TWSI_SW_V) && (get_timer(start) < 50)); + + if (val & TWSI_SW_V) + debug("%s: Error writing 0x%llx\n", __func__, val); + + debug("%s: Returning 0x%llx\n", __func__, val); + return val; +} + +/** + * Write control register + * + * @base Base address for i2c registers + * @data data to write + */ +static void twsi_write_ctl(void __iomem *base, u8 data) +{ + u64 val; + + debug("%s(%p, 0x%x)\n", __func__, base, data); + val = data | FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); +} + +/** + * Reads the TWSI Control Register + * + * @base Base address for i2c + * @return 8-bit TWSI control register + */ +static u8 twsi_read_ctl(void __iomem *base) +{ + u64 val; + + val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + val = twsi_read_sw(base, val); + + debug("%s(%p): 0x%x\n", __func__, base, (u8)val); + return (u8)val; +} + +/** + * Read i2c status register + * + * @base Base address of i2c registers + * @return value of status register + */ +static u8 twsi_read_status(void __iomem *base) +{ + u64 val; + + val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_STAT) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + + return twsi_read_sw(base, val); +} + +/** + * Waits for an i2c operation to complete + * + * @param base Base address of registers + * @return 0 for success, 1 if timeout + */ +static int twsi_wait(void __iomem *base) +{ + unsigned long start = get_timer(0); + u8 twsi_ctl; + + debug("%s(%p)\n", __func__, base); + do { + twsi_ctl = twsi_read_ctl(base); + twsi_ctl &= TWSI_CTL_IFLG; + } while (!twsi_ctl && get_timer(start) < 50); + + debug(" return: %u\n", !twsi_ctl); + return !twsi_ctl; +} + +/** + * Unsticks the i2c bus + * + * @base base address of registers + */ +static int twsi_start_unstick(void __iomem *base) +{ + twsi_stop(base); + twsi_unblock(base); + + return 0; +} + +/** + * Sends an i2c start condition + * + * @base base address of registers + * @return 0 for success, otherwise error + */ +static int twsi_start(void __iomem *base) +{ + int ret; + u8 stat; + + debug("%s(%p)\n", __func__, base); + twsi_write_ctl(base, TWSI_CTL_STA | TWSI_CTL_ENAB); + ret = twsi_wait(base); + if (ret) { + stat = twsi_read_status(base); + debug("%s: ret: 0x%x, status: 0x%x\n", __func__, ret, stat); + switch (stat) { + case TWSI_STAT_START: + case TWSI_STAT_RSTART: + return 0; + case TWSI_STAT_RXADDR_ACK: + default: + return twsi_start_unstick(base); + } + } + + debug("%s: success\n", __func__); + return 0; +} + +/** + * Sends an i2c stop condition + * + * @base register base address + * @return 0 for success, -1 if error + */ +static int twsi_stop(void __iomem *base) +{ + u8 stat; + + twsi_write_ctl(base, TWSI_CTL_STP | TWSI_CTL_ENAB); + + stat = twsi_read_status(base); + if (stat != TWSI_STAT_IDLE) { + debug("%s: Bad status on bus@%p\n", __func__, base); + return -1; + } + + return 0; +} + +/** + * Writes data to the i2c bus + * + * @base register base address + * @slave_addr address of slave to write to + * @buffer Pointer to buffer to write + * @length Number of bytes in buffer to write + * @return 0 for success, otherwise error + */ +static int twsi_write_data(void __iomem *base, u8 slave_addr, + u8 *buffer, unsigned int length) +{ + unsigned int curr = 0; + u64 val; + int ret; + + debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, base, slave_addr, + buffer, length); + ret = twsi_start(base); + if (ret) { + debug("%s: Could not start BUS transaction\n", __func__); + return -1; + } + + ret = twsi_wait(base); + if (ret) { + debug("%s: wait failed\n", __func__); + return ret; + } + + val = (u32)(slave_addr << 1) | TWSI_OP_WRITE | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + debug("%s: Waiting\n", __func__); + ret = twsi_wait(base); + if (ret) { + debug("%s: Timed out writing slave address 0x%x to target\n", + __func__, slave_addr); + return ret; + } + + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + if (ret != TWSI_STAT_TXADDR_ACK) { + debug("%s: status: 0x%x\n", __func__, ret); + twsi_stop(base); + return twsi_i2c_lost_arb(ret, 0); + } + + while (curr < length) { + val = buffer[curr++] | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + debug("%s: Writing 0x%llx\n", __func__, val); + + ret = twsi_wait(base); + if (ret) { + debug("%s: Timed out writing data to 0x%x\n", + __func__, slave_addr); + return ret; + } + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + } + + debug("%s: Stopping\n", __func__); + return twsi_stop(base); +} + +/** + * Manually clear the I2C bus and send a stop + * + * @base register base address + */ +static void twsi_unblock(void __iomem *base) +{ + int i; + + for (i = 0; i < 9; i++) { + writeq(0, base + TWSI_INT); + udelay(5); + writeq(TWSI_INT_SCL_OVR, base + TWSI_INT); + udelay(5); + } + writeq(TWSI_INT_SCL_OVR | TWSI_INT_SDA_OVR, base + TWSI_INT); + udelay(5); + writeq(TWSI_INT_SDA_OVR, base + TWSI_INT); + udelay(5); + writeq(0, base + TWSI_INT); + udelay(5); +} + +/** + * Performs a read transaction on the i2c bus + * + * @base Base address of twsi registers + * @slave_addr i2c bus address to read from + * @buffer buffer to read into + * @length number of bytes to read + * @return 0 for success, otherwise error + */ +static int twsi_read_data(void __iomem *base, u8 slave_addr, + u8 *buffer, unsigned int length) +{ + unsigned int curr = 0; + u64 val; + int ret; + + debug("%s(%p, 0x%x, %p, %u)\n", __func__, base, slave_addr, + buffer, length); + ret = twsi_start(base); + if (ret) { + debug("%s: start failed\n", __func__); + return ret; + } + + ret = twsi_wait(base); + if (ret) { + debug("%s: wait failed\n", __func__); + return ret; + } + + val = (u32)(slave_addr << 1) | TWSI_OP_READ | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA); + twsi_write_sw(base, val); + twsi_write_ctl(base, TWSI_CTL_ENAB); + + ret = twsi_wait(base); + if (ret) { + debug("%s: waiting for sending addr failed\n", __func__); + return ret; + } + + ret = twsi_read_status(base); + debug("%s: status: 0x%x\n", __func__, ret); + if (ret != TWSI_STAT_RXADDR_ACK) { + debug("%s: status: 0x%x\n", __func__, ret); + twsi_stop(base); + return twsi_i2c_lost_arb(ret, 0); + } + + while (curr < length) { + twsi_write_ctl(base, TWSI_CTL_ENAB | + ((curr < length - 1) ? TWSI_CTL_AAK : 0)); + + ret = twsi_wait(base); + if (ret) { + debug("%s: waiting for data failed\n", __func__); + return ret; + } + + val = twsi_read_sw(base, val); + buffer[curr++] = (u8)val; + } + + twsi_stop(base); + + return 0; +} + +/** + * Calculate the divisor values + * + * @speed Speed to set + * @m_div Pointer to M divisor + * @n_div Pointer to N divisor + * @return 0 for success, otherwise error + */ +static void twsi_calc_div(struct udevice *bus, ulong sclk, unsigned int speed, + int *m_div, int *n_div) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int thp = twsi->data->thp; + int tclk, fsamp; + int ndiv, mdiv; + + if (twsi->data->clk_method == CLK_METHOD_OCTEON) { + tclk = sclk / (2 * (thp + 1)); + } else { + /* Refclk src in mode register defaults to 100MHz clock */ + sclk = 100000000; /* 100 Mhz */ + tclk = sclk / (thp + 2); + } + debug("%s( io_clock %lu tclk %u)\n", __func__, sclk, tclk); + + /* + * Compute the clocks M divider: + * + * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N) + * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1 + * + * For OcteonTX2 - + * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N) + * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1 + */ + for (ndiv = 0; ndiv < 8; ndiv++) { + fsamp = tclk / (1 << ndiv); + mdiv = fsamp / speed / 10; + mdiv -= 1; + if (mdiv < 16) + break; + } + + *m_div = mdiv; + *n_div = ndiv; +} + +/** + * Init I2C controller + * + * @base Base address of twsi registers + * @slave_addr I2C slave address to configure this controller to + * @return 0 for success, otherwise error + */ +static int twsi_init(void __iomem *base, int slaveaddr) +{ + u64 val; + + debug("%s (%p, 0x%x)\n", __func__, base, slaveaddr); + + val = slaveaddr << 1 | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, 0) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + twsi_write_sw(base, val); + + /* Set slave address */ + val = slaveaddr | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_EOP_SLAVE_ADDR) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + twsi_write_sw(base, val); + + return 0; +} + +/** + * Transfers data over the i2c bus + * + * @bus i2c bus to transfer data over + * @msg Array of i2c messages + * @nmsgs Number of messages to send/receive + * @return 0 for success, otherwise error + */ +static int octeon_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, + int nmsgs) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int ret; + int i; + + debug("%s: %d messages\n", __func__, nmsgs); + for (i = 0; i < nmsgs; i++, msg++) { + debug("%s: chip=0x%x, len=0x%x\n", __func__, msg->addr, + msg->len); + + if (msg->flags & I2C_M_RD) { + debug("%s: Reading data\n", __func__); + ret = twsi_read_data(twsi->base, msg->addr, + msg->buf, msg->len); + } else { + debug("%s: Writing data\n", __func__); + ret = twsi_write_data(twsi->base, msg->addr, + msg->buf, msg->len); + } + if (ret) { + debug("%s: error sending\n", __func__); + return -EREMOTEIO; + } + } + + return 0; +} + +/** + * Set I2C bus speed + * + * @bus i2c bus to transfer data over + * @speed Speed in Hz to set + * @return 0 for success, otherwise error + */ +static int octeon_i2c_set_bus_speed(struct udevice *bus, unsigned int speed) +{ + struct octeon_twsi *twsi = dev_get_priv(bus); + int m_div, n_div; + ulong clk_rate; + u64 val; + + debug("%s(%p, %u)\n", __func__, bus, speed); + + clk_rate = clk_get_rate(&twsi->clk); + if (IS_ERR_VALUE(clk_rate)) + return -EINVAL; + + twsi_calc_div(bus, clk_rate, speed, &m_div, &n_div); + if (m_div >= 16) + return -1; + + val = (u32)(((m_div & 0xf) << 3) | ((n_div & 0x7) << 0)) | + FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CLKCTL) | + FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) | + TWSI_SW_V; + /* Only init non-slave ports */ + writeq(val, twsi->base + TWSI_SW_TWSI); + + debug("%s: Wrote 0x%llx to sw_twsi\n", __func__, val); + return 0; +} + +/** + * Driver probe function + * + * @dev I2C device to probe + * @return 0 for success, otherwise error + */ +static int octeon_i2c_probe(struct udevice *dev) +{ + struct octeon_twsi *twsi = dev_get_priv(dev); + u32 i2c_slave_addr; + int ret; + + twsi->data = (const struct octeon_i2c_data *)dev_get_driver_data(dev); + + if (twsi->data->probe == PROBE_PCI) { + pci_dev_t bdf = dm_pci_get_bdf(dev); + + debug("TWSI PCI device: %x\n", bdf); + dev->req_seq = PCI_FUNC(bdf); + + twsi->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, + PCI_REGION_MEM); + } else { + twsi->base = dev_remap_addr(dev); + } + twsi->base += twsi->data->reg_offs; + + i2c_slave_addr = dev_read_u32_default(dev, "i2c-sda-hold-time-ns", + CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR); + + ret = clk_get_by_index(dev, 0, &twsi->clk); + if (ret < 0) + return ret; + + ret = clk_enable(&twsi->clk); + if (ret) + return ret; + + debug("TWSI bus %d at %p\n", dev->seq, twsi->base); + + /* Start with standard speed, real speed set via DT or cmd */ + return twsi_init(twsi->base, i2c_slave_addr); +} + +static const struct dm_i2c_ops octeon_i2c_ops = { + .xfer = octeon_i2c_xfer, + .set_bus_speed = octeon_i2c_set_bus_speed, +}; + +static const struct octeon_i2c_data i2c_octeon_data = { + .probe = PROBE_DT, + .reg_offs = 0x0000, + .thp = 3, + .clk_method = CLK_METHOD_OCTEON, +}; + +static const struct octeon_i2c_data i2c_octeontx_data = { + .probe = PROBE_PCI, + .reg_offs = 0x8000, + .thp = 3, + .clk_method = CLK_METHOD_OCTEON, +}; + +static const struct octeon_i2c_data i2c_octeontx2_data = { + .probe = PROBE_PCI, + .reg_offs = 0x8000, + .thp = 24, + .clk_method = CLK_METHOD_OCTEONTX2, +}; + +static const struct udevice_id octeon_i2c_ids[] = { + { .compatible = "cavium,octeon-7890-twsi", + .data = (ulong)&i2c_octeon_data }, + { .compatible = "cavium,thunder-8890-twsi", + .data = (ulong)&i2c_octeontx_data }, + { .compatible = "cavium,thunder2-99xx-twsi", + .data = (ulong)&i2c_octeontx2_data }, + { } +}; + +U_BOOT_DRIVER(octeon_pci_twsi) = { + .name = "i2c_octeon", + .id = UCLASS_I2C, + .of_match = octeon_i2c_ids, + .probe = octeon_i2c_probe, + .priv_auto_alloc_size = sizeof(struct octeon_twsi), + .ops = &octeon_i2c_ops, +}; + +static struct pci_device_id octeon_twsi_supported[] = { + { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_TWSI), + .driver_data = (ulong)&i2c_octeontx2_data }, + { }, +}; + +U_BOOT_PCI_DEVICE(octeon_pci_twsi, octeon_twsi_supported); diff --git a/drivers/i2c/stm32f7_i2c.c b/drivers/i2c/stm32f7_i2c.c index ada8f4095e..2f60911549 100644 --- a/drivers/i2c/stm32f7_i2c.c +++ b/drivers/i2c/stm32f7_i2c.c @@ -8,7 +8,9 @@ #include <dm.h> #include <i2c.h> #include <log.h> +#include <regmap.h> #include <reset.h> +#include <syscon.h> #include <linux/bitops.h> #include <linux/delay.h> @@ -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,6 +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)&stm32mp15_setup }, {} }; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index edb3f0f538..61eb468cde 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 2c3a069006..0f4e84b04d 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 0000000000..77bc659fef --- /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 <common.h> +#include <clk.h> +#include <div64.h> +#include <dm.h> +#include <pwm.h> +#include <regmap.h> +#include <linux/io.h> +#include <linux/log2.h> +#include <linux/bitfield.h> + +/* 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), +}; diff --git a/drivers/rtc/i2c_rtc_emul.c b/drivers/rtc/i2c_rtc_emul.c index a010af411b..7f78ff83cb 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); } } diff --git a/drivers/rtc/pcf2127.c b/drivers/rtc/pcf2127.c index c423960b34..88ff8c52c3 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; @@ -44,6 +43,12 @@ static int pcf2127_read_reg(struct udevice *dev, uint offset, 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}; @@ -73,7 +78,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 +115,8 @@ static const struct rtc_ops pcf2127_rtc_ops = { .get = pcf2127_rtc_get, .set = pcf2127_rtc_set, .reset = pcf2127_rtc_reset, + .read = pcf2127_rtc_read, + .write = pcf2127_rtc_write, }; static const struct udevice_id pcf2127_rtc_ids[] = { diff --git a/drivers/rtc/rtc-uclass.c b/drivers/rtc/rtc-uclass.c index 926cca234e..8035f7fe9c 100644 --- a/drivers/rtc/rtc-uclass.c +++ b/drivers/rtc/rtc-uclass.c @@ -40,24 +40,75 @@ int dm_rtc_reset(struct udevice *dev) return ops->reset(dev); } -int rtc_read8(struct udevice *dev, unsigned int reg) +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; - return ops->read8(dev, reg); + while (len--) { + int ret = ops->read8(dev, reg++); + + if (ret < 0) + return ret; + *buf++ = ret; + } + return 0; } -int rtc_write8(struct udevice *dev, unsigned int reg, int val) +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; - return ops->write8(dev, reg, val); + 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); + + assert(ops); + 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) +{ + struct rtc_ops *ops = rtc_get_ops(dev); + + assert(ops); + 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) diff --git a/drivers/rtc/sandbox_rtc.c b/drivers/rtc/sandbox_rtc.c index b08d758a74..77065e49c7 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; |