diff options
author | Samuel Holland | 2021-10-08 00:17:20 -0500 |
---|---|---|
committer | Andre Przywara | 2021-10-12 09:50:56 +0100 |
commit | 104950a7feae7926e40676f27cfbd279a43b4bc3 (patch) | |
tree | ce45115b87c8d40491d8b1e3e44fcb1aaf050901 /drivers/i2c | |
parent | 4ab39e74b640799748dfdef49f099b416a5ae497 (diff) |
i2c: Add a DM_I2C driver for the sun6i P2WI controller
This bus controller is used to communicate with an X-Powers AXP PMIC.
Currently, various drivers access PMIC registers through a platform-
specific non-DM "pmic_bus" interface, which depends on the legacy I2C
framework. In order to convert those drivers to use DM_PMIC, this bus
needs a DM_I2C driver.
Refactor the p2wi functions to take the base address as a parameter,
and implement both the existing interface (which is still needed in
SPL) and the DM_I2C interface on top of them.
The register for switching between I2C/P2WI/RSB mode is the same across
all PMIC variants. Move that to the common header, so it can be used by
both interface implementations.
Signed-off-by: Samuel Holland <samuel@sholland.org>
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/Kconfig | 8 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/sun6i_p2wi.c | 220 |
3 files changed, 229 insertions, 0 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 57cac4483f0..0adf143abfd 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -575,6 +575,14 @@ config SYS_I2C_STM32F7 _ Optional clock stretching _ Software reset +config SYS_I2C_SUN6I_P2WI + bool "Allwinner sun6i P2WI controller" + depends on ARCH_SUNXI + help + Support for the P2WI (Push/Pull 2 Wire Interface) controller embedded + in the Allwinner A31 and A31s SOCs. This interface is used to connect + to specific devices like the X-Powers AXP221 PMIC. + config SYS_I2C_SYNQUACER bool "Socionext SynQuacer I2C controller" depends on ARCH_SYNQUACER && DM_I2C diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 67841bf3e02..0a2cc641930 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SYS_I2C_SANDBOX) += sandbox_i2c.o i2c-emul-uclass.o obj-$(CONFIG_SYS_I2C_SH) += sh_i2c.o obj-$(CONFIG_SYS_I2C_SOFT) += soft_i2c.o obj-$(CONFIG_SYS_I2C_STM32F7) += stm32f7_i2c.o +obj-$(CONFIG_SYS_I2C_SUN6I_P2WI) += sun6i_p2wi.o obj-$(CONFIG_SYS_I2C_SYNQUACER) += synquacer_i2c.o obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o obj-$(CONFIG_SYS_I2C_UNIPHIER) += i2c-uniphier.o diff --git a/drivers/i2c/sun6i_p2wi.c b/drivers/i2c/sun6i_p2wi.c new file mode 100644 index 00000000000..c9e1b3fcd5f --- /dev/null +++ b/drivers/i2c/sun6i_p2wi.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Sunxi A31 Power Management Unit + * + * (C) Copyright 2013 Oliver Schinagl <oliver@schinagl.nl> + * http://linux-sunxi.org + * + * Based on sun6i sources and earlier U-Boot Allwinner A10 SPL work + * + * (C) Copyright 2006-2013 + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> + * Berg Xing <bergxing@allwinnertech.com> + * Tom Cubie <tangliang@allwinnertech.com> + */ + +#include <axp_pmic.h> +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <i2c.h> +#include <time.h> +#include <asm/io.h> +#include <asm/arch/cpu.h> +#include <asm/arch/gpio.h> +#include <asm/arch/p2wi.h> +#include <asm/arch/prcm.h> +#include <asm/arch/sys_proto.h> + +static int sun6i_p2wi_await_trans(struct sunxi_p2wi_reg *base) +{ + unsigned long tmo = timer_get_us() + 1000000; + int ret; + u8 reg; + + while (1) { + reg = readl(&base->status); + if (reg & P2WI_STAT_TRANS_ERR) { + ret = -EIO; + break; + } + if (reg & P2WI_STAT_TRANS_DONE) { + ret = 0; + break; + } + if (timer_get_us() > tmo) { + ret = -ETIME; + break; + } + } + writel(reg, &base->status); /* Clear status bits */ + + return ret; +} + +static int sun6i_p2wi_read(struct sunxi_p2wi_reg *base, const u8 addr, u8 *data) +{ + int ret; + + writel(P2WI_DATADDR_BYTE_1(addr), &base->dataddr0); + writel(P2WI_DATA_NUM_BYTES(1) | + P2WI_DATA_NUM_BYTES_READ, &base->numbytes); + writel(P2WI_STAT_TRANS_DONE, &base->status); + writel(P2WI_CTRL_TRANS_START, &base->ctrl); + + ret = sun6i_p2wi_await_trans(base); + + *data = readl(&base->data0) & P2WI_DATA_BYTE_1_MASK; + + return ret; +} + +static int sun6i_p2wi_write(struct sunxi_p2wi_reg *base, const u8 addr, u8 data) +{ + writel(P2WI_DATADDR_BYTE_1(addr), &base->dataddr0); + writel(P2WI_DATA_BYTE_1(data), &base->data0); + writel(P2WI_DATA_NUM_BYTES(1), &base->numbytes); + writel(P2WI_STAT_TRANS_DONE, &base->status); + writel(P2WI_CTRL_TRANS_START, &base->ctrl); + + return sun6i_p2wi_await_trans(base); +} + +static int sun6i_p2wi_change_to_p2wi_mode(struct sunxi_p2wi_reg *base, + u8 slave_addr, u8 ctrl_reg, + u8 init_data) +{ + unsigned long tmo = timer_get_us() + 1000000; + + writel(P2WI_PM_DEV_ADDR(slave_addr) | + P2WI_PM_CTRL_ADDR(ctrl_reg) | + P2WI_PM_INIT_DATA(init_data) | + P2WI_PM_INIT_SEND, + &base->pm); + + while ((readl(&base->pm) & P2WI_PM_INIT_SEND)) { + if (timer_get_us() > tmo) + return -ETIME; + } + + return 0; +} + +static void sun6i_p2wi_init(struct sunxi_p2wi_reg *base) +{ + /* Enable p2wi and PIO clk, and de-assert their resets */ + prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_P2WI); + + sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN6I_GPL0_R_P2WI_SCK); + sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN6I_GPL1_R_P2WI_SDA); + + /* Reset p2wi controller and set clock to CLKIN(12)/8 = 1.5 MHz */ + writel(P2WI_CTRL_RESET, &base->ctrl); + sdelay(0x100); + writel(P2WI_CC_SDA_OUT_DELAY(1) | P2WI_CC_CLK_DIV(8), + &base->cc); +} + +#if IS_ENABLED(CONFIG_AXP_PMIC_BUS) +int p2wi_read(const u8 addr, u8 *data) +{ + struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE; + + return sun6i_p2wi_read(base, addr, data); +} + +int p2wi_write(const u8 addr, u8 data) +{ + struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE; + + return sun6i_p2wi_write(base, addr, data); +} + +int p2wi_change_to_p2wi_mode(u8 slave_addr, u8 ctrl_reg, u8 init_data) +{ + struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE; + + return sun6i_p2wi_change_to_p2wi_mode(base, slave_addr, ctrl_reg, + init_data); +} + +void p2wi_init(void) +{ + struct sunxi_p2wi_reg *base = (struct sunxi_p2wi_reg *)SUN6I_P2WI_BASE; + + sun6i_p2wi_init(base); +} +#endif + +#if CONFIG_IS_ENABLED(DM_I2C) +struct sun6i_p2wi_priv { + struct sunxi_p2wi_reg *base; +}; + +static int sun6i_p2wi_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs) +{ + struct sun6i_p2wi_priv *priv = dev_get_priv(bus); + + /* The hardware only supports SMBus-style transfers. */ + if (nmsgs == 2 && msg[1].flags == I2C_M_RD && msg[1].len == 1) + return sun6i_p2wi_read(priv->base, + msg[0].buf[0], &msg[1].buf[0]); + + if (nmsgs == 1 && msg[0].len == 2) + return sun6i_p2wi_write(priv->base, + msg[0].buf[0], msg[0].buf[1]); + + return -EINVAL; +} + +static int sun6i_p2wi_probe_chip(struct udevice *bus, uint chip_addr, + uint chip_flags) +{ + struct sun6i_p2wi_priv *priv = dev_get_priv(bus); + + return sun6i_p2wi_change_to_p2wi_mode(priv->base, chip_addr, + AXP_PMIC_MODE_REG, + AXP_PMIC_MODE_P2WI); +} + +static int sun6i_p2wi_probe(struct udevice *bus) +{ + struct sun6i_p2wi_priv *priv = dev_get_priv(bus); + + priv->base = dev_read_addr_ptr(bus); + + sun6i_p2wi_init(priv->base); + + return 0; +} + +static int sun6i_p2wi_child_pre_probe(struct udevice *child) +{ + struct dm_i2c_chip *chip = dev_get_parent_plat(child); + + /* Ensure each transfer is for a single register. */ + chip->flags |= DM_I2C_CHIP_RD_ADDRESS | DM_I2C_CHIP_WR_ADDRESS; + + return 0; +} + +static const struct dm_i2c_ops sun6i_p2wi_ops = { + .xfer = sun6i_p2wi_xfer, + .probe_chip = sun6i_p2wi_probe_chip, +}; + +static const struct udevice_id sun6i_p2wi_ids[] = { + { .compatible = "allwinner,sun6i-a31-p2wi" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sun6i_p2wi) = { + .name = "sun6i_p2wi", + .id = UCLASS_I2C, + .of_match = sun6i_p2wi_ids, + .probe = sun6i_p2wi_probe, + .child_pre_probe = sun6i_p2wi_child_pre_probe, + .priv_auto = sizeof(struct sun6i_p2wi_priv), + .ops = &sun6i_p2wi_ops, +}; +#endif /* CONFIG_IS_ENABLED(DM_I2C) */ |