diff options
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/Kconfig | 22 | ||||
-rw-r--r-- | drivers/i2c/Makefile | 4 | ||||
-rw-r--r-- | drivers/i2c/ocores_i2c.c | 2 | ||||
-rw-r--r-- | drivers/i2c/stm32f7_i2c.c | 91 | ||||
-rw-r--r-- | drivers/i2c/sun6i_p2wi.c | 220 | ||||
-rw-r--r-- | drivers/i2c/sun8i_rsb.c | 281 |
6 files changed, 578 insertions, 42 deletions
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 57cac4483f0..7c447a8aa0a 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -575,6 +575,22 @@ 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_SUN8I_RSB + bool "Allwinner sun8i Reduced Serial Bus controller" + depends on ARCH_SUNXI + help + Support for Allwinner's Reduced Serial Bus (RSB) controller. This + controller is responsible for communicating with various RSB based + devices, such as X-Powers AXPxxx PMICs and AC100/AC200 CODEC ICs. + config SYS_I2C_SYNQUACER bool "Socionext SynQuacer I2C controller" depends on ARCH_SYNQUACER && DM_I2C @@ -611,6 +627,12 @@ config SYS_I2C_VERSATILE Add support for the Arm Ltd Versatile Express I2C driver. The I2C host controller is present in the development boards manufactured by Arm Ltd. +config SYS_I2C_MV + bool "Marvell PXA (Armada 3720) I2C driver" + help + Support for PXA based I2C controller used on Armada 3720 SoC. + In Linux, this driver is called i2c-pxa. + config SYS_I2C_MVTWSI bool "Marvell I2C driver" help diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 67841bf3e02..fca6b157f8a 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_$(SPL_)DM_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_$(SPL_)I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o obj-$(CONFIG_$(SPL_)I2C_CROS_EC_LDO) += cros_ec_ldo.o -obj-$(CONFIG_I2C_MV) += mv_i2c.o obj-$(CONFIG_$(SPL_)SYS_I2C_LEGACY) += i2c_core.o obj-$(CONFIG_SYS_I2C_ASPEED) += ast_i2c.o obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o @@ -29,6 +28,7 @@ obj-$(CONFIG_SYS_I2C_IPROC) += iproc_i2c.o obj-$(CONFIG_SYS_I2C_KONA) += kona_i2c.o obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o +obj-$(CONFIG_SYS_I2C_MV) += mv_i2c.o obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o obj-$(CONFIG_SYS_I2C_NEXELL) += nx_i2c.o @@ -43,6 +43,8 @@ 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_SUN8I_RSB) += sun8i_rsb.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/ocores_i2c.c b/drivers/i2c/ocores_i2c.c index f129ec3818c..3b19ba78fa3 100644 --- a/drivers/i2c/ocores_i2c.c +++ b/drivers/i2c/ocores_i2c.c @@ -516,7 +516,7 @@ static int ocores_i2c_probe(struct udevice *dev) u32 clock_frequency_khz; int ret; - bus->base = (void __iomem *)devfdt_get_addr(dev); + bus->base = dev_read_addr_ptr(dev); if (dev_read_u32(dev, "reg-shift", &bus->reg_shift)) { /* no 'reg-shift', check for deprecated 'regstep' */ diff --git a/drivers/i2c/stm32f7_i2c.c b/drivers/i2c/stm32f7_i2c.c index 7b04a09de0b..c6ae65badb7 100644 --- a/drivers/i2c/stm32f7_i2c.c +++ b/drivers/i2c/stm32f7_i2c.c @@ -45,6 +45,8 @@ struct stm32_i2c_regs { /* STM32 I2C control 1 */ #define STM32_I2C_CR1_ANFOFF BIT(12) +#define STM32_I2C_CR1_DNF_MASK GENMASK(11, 8) +#define STM32_I2C_CR1_DNF(n) (((n) & 0xf) << 8) #define STM32_I2C_CR1_ERRIE BIT(7) #define STM32_I2C_CR1_TCIE BIT(6) #define STM32_I2C_CR1_STOPIE BIT(5) @@ -105,10 +107,8 @@ struct stm32_i2c_regs { #define STM32_I2C_MAX_LEN 0xff -#define STM32_I2C_DNF_DEFAULT 0 -#define STM32_I2C_DNF_MAX 16 +#define STM32_I2C_DNF_MAX 15 -#define STM32_I2C_ANALOG_FILTER_ENABLE 1 #define STM32_I2C_ANALOG_FILTER_DELAY_MIN 50 /* ns */ #define STM32_I2C_ANALOG_FILTER_DELAY_MAX 260 /* ns */ @@ -156,9 +156,8 @@ struct stm32_i2c_spec { * @clock_src: I2C clock source frequency (Hz) * @rise_time: Rise time (ns) * @fall_time: Fall time (ns) - * @dnf: Digital filter coefficient (0-16) + * @dnf: value of digital filter to apply * @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; @@ -167,6 +166,13 @@ struct stm32_i2c_setup { u32 fall_time; u8 dnf; bool analog_filter; +}; + +/** + * struct stm32_i2c_data - driver data for I2C configuration by compatible + * @fmp_clr_offset: Fast Mode Plus clear register offset from set register + */ +struct stm32_i2c_data { u32 fmp_clr_offset; }; @@ -197,16 +203,18 @@ struct stm32_i2c_timings { * @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 + * @dnf_dt: value of digital filter requested via dt */ struct stm32_i2c_priv { struct stm32_i2c_regs *regs; struct clk clk; - struct stm32_i2c_setup *setup; + struct stm32_i2c_setup setup; u32 speed; struct regmap *regmap; u32 regmap_sreg; u32 regmap_creg; u32 regmap_mask; + u32 dnf_dt; }; static const struct stm32_i2c_spec i2c_specs[] = { @@ -251,18 +259,11 @@ static const struct stm32_i2c_spec i2c_specs[] = { }, }; -static const struct stm32_i2c_setup stm32f7_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, +static const struct stm32_i2c_data stm32f7_data = { + .fmp_clr_offset = 0x00, }; -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, +static const struct stm32_i2c_data stm32mp15_data = { .fmp_clr_offset = 0x40, }; @@ -506,14 +507,13 @@ static int stm32_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, return 0; } -static int stm32_i2c_compute_solutions(struct stm32_i2c_setup *setup, +static int stm32_i2c_compute_solutions(u32 i2cclk, + struct stm32_i2c_setup *setup, const struct stm32_i2c_spec *specs, struct list_head *solutions) { struct stm32_i2c_timings *v; u32 p_prev = STM32_PRESC_MAX; - u32 i2cclk = DIV_ROUND_CLOSEST(STM32_NSEC_PER_SEC, - setup->clock_src); u32 af_delay_min, af_delay_max; u16 p, l, a; int sdadel_min, sdadel_max, scldel_min; @@ -581,7 +581,8 @@ static int stm32_i2c_compute_solutions(struct stm32_i2c_setup *setup, return ret; } -static int stm32_i2c_choose_solution(struct stm32_i2c_setup *setup, +static int stm32_i2c_choose_solution(u32 i2cclk, + struct stm32_i2c_setup *setup, const struct stm32_i2c_spec *specs, struct list_head *solutions, struct stm32_i2c_timings *s) @@ -590,8 +591,6 @@ static int stm32_i2c_choose_solution(struct stm32_i2c_setup *setup, u32 i2cbus = DIV_ROUND_CLOSEST(STM32_NSEC_PER_SEC, setup->speed_freq); u32 clk_error_prev = i2cbus; - u32 i2cclk = DIV_ROUND_CLOSEST(STM32_NSEC_PER_SEC, - setup->clock_src); u32 clk_min, clk_max; u32 af_delay_min; u32 dnf_delay; @@ -678,12 +677,13 @@ static const struct stm32_i2c_spec *get_specs(u32 rate) } static int stm32_i2c_compute_timing(struct stm32_i2c_priv *i2c_priv, - struct stm32_i2c_setup *setup, struct stm32_i2c_timings *output) { + struct stm32_i2c_setup *setup = &i2c_priv->setup; const struct stm32_i2c_spec *specs; struct stm32_i2c_timings *v, *_v; struct list_head solutions; + u32 i2cclk = DIV_ROUND_CLOSEST(STM32_NSEC_PER_SEC, setup->clock_src); int ret; specs = get_specs(setup->speed_freq); @@ -701,6 +701,8 @@ static int stm32_i2c_compute_timing(struct stm32_i2c_priv *i2c_priv, return -EINVAL; } + /* Analog and Digital Filters */ + setup->dnf = DIV_ROUND_CLOSEST(i2c_priv->dnf_dt, i2cclk); if (setup->dnf > STM32_I2C_DNF_MAX) { log_err("DNF out of bound %d/%d\n", setup->dnf, STM32_I2C_DNF_MAX); @@ -708,11 +710,11 @@ static int stm32_i2c_compute_timing(struct stm32_i2c_priv *i2c_priv, } INIT_LIST_HEAD(&solutions); - ret = stm32_i2c_compute_solutions(setup, specs, &solutions); + ret = stm32_i2c_compute_solutions(i2cclk, setup, specs, &solutions); if (ret) goto exit; - ret = stm32_i2c_choose_solution(setup, specs, &solutions, output); + ret = stm32_i2c_choose_solution(i2cclk, setup, specs, &solutions, output); if (ret) goto exit; @@ -745,7 +747,7 @@ static u32 get_lower_rate(u32 rate) static int stm32_i2c_setup_timing(struct stm32_i2c_priv *i2c_priv, struct stm32_i2c_timings *timing) { - struct stm32_i2c_setup *setup = i2c_priv->setup; + struct stm32_i2c_setup *setup = &i2c_priv->setup; int ret = 0; setup->speed_freq = i2c_priv->speed; @@ -757,7 +759,7 @@ static int stm32_i2c_setup_timing(struct stm32_i2c_priv *i2c_priv, } do { - ret = stm32_i2c_compute_timing(i2c_priv, setup, timing); + ret = stm32_i2c_compute_timing(i2c_priv, timing); if (ret) { log_debug("failed to compute I2C timings.\n"); if (setup->speed_freq > I2C_SPEED_STANDARD_RATE) { @@ -839,10 +841,15 @@ static int stm32_i2c_hw_config(struct stm32_i2c_priv *i2c_priv) writel(timing, ®s->timingr); /* Enable I2C */ - if (i2c_priv->setup->analog_filter) + if (i2c_priv->setup.analog_filter) clrbits_le32(®s->cr1, STM32_I2C_CR1_ANFOFF); else setbits_le32(®s->cr1, STM32_I2C_CR1_ANFOFF); + + /* Program the Digital Filter */ + clrsetbits_le32(®s->cr1, STM32_I2C_CR1_DNF_MASK, + STM32_I2C_CR1_DNF(i2c_priv->setup.dnf)); + setbits_le32(®s->cr1, STM32_I2C_CR1_PE); return 0; @@ -903,21 +910,26 @@ clk_free: static int stm32_of_to_plat(struct udevice *dev) { + const struct stm32_i2c_data *data; 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) + data = (const struct stm32_i2c_data *)dev_get_driver_data(dev); + if (!data) return -EINVAL; - rise_time = dev_read_u32_default(dev, "i2c-scl-rising-time-ns", 0); - if (rise_time) - i2c_priv->setup->rise_time = rise_time; + rise_time = dev_read_u32_default(dev, "i2c-scl-rising-time-ns", + STM32_I2C_RISE_TIME_DEFAULT); + + fall_time = dev_read_u32_default(dev, "i2c-scl-falling-time-ns", + STM32_I2C_FALL_TIME_DEFAULT); + + i2c_priv->dnf_dt = dev_read_u32_default(dev, "i2c-digital-filter-width-ns", 0); + if (!dev_read_bool(dev, "i2c-digital-filter")) + i2c_priv->dnf_dt = 0; - fall_time = dev_read_u32_default(dev, "i2c-scl-falling-time-ns", 0); - if (fall_time) - i2c_priv->setup->fall_time = fall_time; + i2c_priv->setup.analog_filter = dev_read_bool(dev, "i2c-analog-filter"); /* Optional */ i2c_priv->regmap = syscon_regmap_lookup_by_phandle(dev, @@ -930,8 +942,7 @@ static int stm32_of_to_plat(struct udevice *dev) return ret; i2c_priv->regmap_sreg = fmp[1]; - i2c_priv->regmap_creg = fmp[1] + - i2c_priv->setup->fmp_clr_offset; + i2c_priv->regmap_creg = fmp[1] + data->fmp_clr_offset; i2c_priv->regmap_mask = fmp[2]; } @@ -944,8 +955,8 @@ 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 }, + { .compatible = "st,stm32f7-i2c", .data = (ulong)&stm32f7_data }, + { .compatible = "st,stm32mp15-i2c", .data = (ulong)&stm32mp15_data }, {} }; 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) */ diff --git a/drivers/i2c/sun8i_rsb.c b/drivers/i2c/sun8i_rsb.c new file mode 100644 index 00000000000..716b245a003 --- /dev/null +++ b/drivers/i2c/sun8i_rsb.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2014 Hans de Goede <hdegoede@redhat.com> + * + * Based on allwinner u-boot sources rsb code which is: + * (C) Copyright 2007-2013 + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> + * lixiang <lixiang@allwinnertech.com> + */ + +#include <axp_pmic.h> +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <i2c.h> +#include <time.h> +#include <asm/arch/cpu.h> +#include <asm/arch/gpio.h> +#include <asm/arch/prcm.h> +#include <asm/arch/rsb.h> + +static int sun8i_rsb_await_trans(struct sunxi_rsb_reg *base) +{ + unsigned long tmo = timer_get_us() + 1000000; + u32 stat; + int ret; + + while (1) { + stat = readl(&base->stat); + if (stat & RSB_STAT_LBSY_INT) { + ret = -EBUSY; + break; + } + if (stat & RSB_STAT_TERR_INT) { + ret = -EIO; + break; + } + if (stat & RSB_STAT_TOVER_INT) { + ret = 0; + break; + } + if (timer_get_us() > tmo) { + ret = -ETIME; + break; + } + } + writel(stat, &base->stat); /* Clear status bits */ + + return ret; +} + +static int sun8i_rsb_do_trans(struct sunxi_rsb_reg *base) +{ + setbits_le32(&base->ctrl, RSB_CTRL_START_TRANS); + + return sun8i_rsb_await_trans(base); +} + +static int sun8i_rsb_read(struct sunxi_rsb_reg *base, u16 runtime_addr, + u8 reg_addr, u8 *data) +{ + int ret; + + writel(RSB_DEVADDR_RUNTIME_ADDR(runtime_addr), &base->devaddr); + writel(reg_addr, &base->addr); + writel(RSB_CMD_BYTE_READ, &base->cmd); + + ret = sun8i_rsb_do_trans(base); + if (ret) + return ret; + + *data = readl(&base->data) & 0xff; + + return 0; +} + +static int sun8i_rsb_write(struct sunxi_rsb_reg *base, u16 runtime_addr, + u8 reg_addr, u8 data) +{ + writel(RSB_DEVADDR_RUNTIME_ADDR(runtime_addr), &base->devaddr); + writel(reg_addr, &base->addr); + writel(data, &base->data); + writel(RSB_CMD_BYTE_WRITE, &base->cmd); + + return sun8i_rsb_do_trans(base); +} + +static int sun8i_rsb_set_device_address(struct sunxi_rsb_reg *base, + u16 device_addr, u16 runtime_addr) +{ + writel(RSB_DEVADDR_RUNTIME_ADDR(runtime_addr) | + RSB_DEVADDR_DEVICE_ADDR(device_addr), &base->devaddr); + writel(RSB_CMD_SET_RTSADDR, &base->cmd); + + return sun8i_rsb_do_trans(base); +} + +static void sun8i_rsb_cfg_io(void) +{ +#ifdef CONFIG_MACH_SUN8I + sunxi_gpio_set_cfgpin(SUNXI_GPL(0), SUN8I_GPL_R_RSB); + sunxi_gpio_set_cfgpin(SUNXI_GPL(1), SUN8I_GPL_R_RSB); + sunxi_gpio_set_pull(SUNXI_GPL(0), 1); + sunxi_gpio_set_pull(SUNXI_GPL(1), 1); + sunxi_gpio_set_drv(SUNXI_GPL(0), 2); + sunxi_gpio_set_drv(SUNXI_GPL(1), 2); +#elif defined CONFIG_MACH_SUN9I + sunxi_gpio_set_cfgpin(SUNXI_GPN(0), SUN9I_GPN_R_RSB); + sunxi_gpio_set_cfgpin(SUNXI_GPN(1), SUN9I_GPN_R_RSB); + sunxi_gpio_set_pull(SUNXI_GPN(0), 1); + sunxi_gpio_set_pull(SUNXI_GPN(1), 1); + sunxi_gpio_set_drv(SUNXI_GPN(0), 2); + sunxi_gpio_set_drv(SUNXI_GPN(1), 2); +#else +#error unsupported MACH_SUNXI +#endif +} + +static void sun8i_rsb_set_clk(struct sunxi_rsb_reg *base) +{ + u32 div = 0; + u32 cd_odly = 0; + + /* Source is Hosc24M, set RSB clk to 3Mhz */ + div = 24000000 / 3000000 / 2 - 1; + cd_odly = div >> 1; + if (!cd_odly) + cd_odly = 1; + + writel((cd_odly << 8) | div, &base->ccr); +} + +static int sun8i_rsb_set_device_mode(struct sunxi_rsb_reg *base) +{ + unsigned long tmo = timer_get_us() + 1000000; + + writel(RSB_DMCR_DEVICE_MODE_START | RSB_DMCR_DEVICE_MODE_DATA, + &base->dmcr); + + while (readl(&base->dmcr) & RSB_DMCR_DEVICE_MODE_START) { + if (timer_get_us() > tmo) + return -ETIME; + } + + return sun8i_rsb_await_trans(base); +} + +static int sun8i_rsb_init(struct sunxi_rsb_reg *base) +{ + /* Enable RSB and PIO clk, and de-assert their resets */ + prcm_apb0_enable(PRCM_APB0_GATE_PIO | PRCM_APB0_GATE_RSB); + + /* Setup external pins */ + sun8i_rsb_cfg_io(); + + writel(RSB_CTRL_SOFT_RST, &base->ctrl); + sun8i_rsb_set_clk(base); + + return sun8i_rsb_set_device_mode(base); +} + +#if IS_ENABLED(CONFIG_AXP_PMIC_BUS) +int rsb_read(const u16 runtime_addr, const u8 reg_addr, u8 *data) +{ + struct sunxi_rsb_reg *base = (struct sunxi_rsb_reg *)SUNXI_RSB_BASE; + + return sun8i_rsb_read(base, runtime_addr, reg_addr, data); +} + +int rsb_write(const u16 runtime_addr, const u8 reg_addr, u8 data) +{ + struct sunxi_rsb_reg *base = (struct sunxi_rsb_reg *)SUNXI_RSB_BASE; + + return sun8i_rsb_write(base, runtime_addr, reg_addr, data); +} + +int rsb_set_device_address(u16 device_addr, u16 runtime_addr) +{ + struct sunxi_rsb_reg *base = (struct sunxi_rsb_reg *)SUNXI_RSB_BASE; + + return sun8i_rsb_set_device_address(base, device_addr, runtime_addr); +} + +int rsb_init(void) +{ + struct sunxi_rsb_reg *base = (struct sunxi_rsb_reg *)SUNXI_RSB_BASE; + + return sun8i_rsb_init(base); +} +#endif + +#if CONFIG_IS_ENABLED(DM_I2C) +struct sun8i_rsb_priv { + struct sunxi_rsb_reg *base; +}; + +/* + * The mapping from hardware address to runtime address is fixed, and shared + * among all RSB drivers. See the comment in drivers/bus/sunxi-rsb.c in Linux. + */ +static int sun8i_rsb_get_runtime_address(u16 device_addr) +{ + if (device_addr == AXP_PMIC_PRI_DEVICE_ADDR) + return AXP_PMIC_PRI_RUNTIME_ADDR; + if (device_addr == AXP_PMIC_SEC_DEVICE_ADDR) + return AXP_PMIC_SEC_RUNTIME_ADDR; + + return -ENXIO; +} + +static int sun8i_rsb_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs) +{ + int runtime_addr = sun8i_rsb_get_runtime_address(msg->addr); + struct sun8i_rsb_priv *priv = dev_get_priv(bus); + + if (runtime_addr < 0) + return runtime_addr; + + /* The hardware only supports SMBus-style transfers. */ + if (nmsgs == 2 && msg[1].flags == I2C_M_RD && msg[1].len == 1) + return sun8i_rsb_read(priv->base, runtime_addr, + msg[0].buf[0], &msg[1].buf[0]); + + if (nmsgs == 1 && msg[0].len == 2) + return sun8i_rsb_write(priv->base, runtime_addr, + msg[0].buf[0], msg[0].buf[1]); + + return -EINVAL; +} + +static int sun8i_rsb_probe_chip(struct udevice *bus, uint chip_addr, + uint chip_flags) +{ + int runtime_addr = sun8i_rsb_get_runtime_address(chip_addr); + struct sun8i_rsb_priv *priv = dev_get_priv(bus); + + if (runtime_addr < 0) + return runtime_addr; + + return sun8i_rsb_set_device_address(priv->base, chip_addr, runtime_addr); +} + +static int sun8i_rsb_probe(struct udevice *bus) +{ + struct sun8i_rsb_priv *priv = dev_get_priv(bus); + + priv->base = dev_read_addr_ptr(bus); + + return sun8i_rsb_init(priv->base); +} + +static int sun8i_rsb_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 sun8i_rsb_ops = { + .xfer = sun8i_rsb_xfer, + .probe_chip = sun8i_rsb_probe_chip, +}; + +static const struct udevice_id sun8i_rsb_ids[] = { + { .compatible = "allwinner,sun8i-a23-rsb" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sun8i_rsb) = { + .name = "sun8i_rsb", + .id = UCLASS_I2C, + .of_match = sun8i_rsb_ids, + .probe = sun8i_rsb_probe, + .child_pre_probe = sun8i_rsb_child_pre_probe, + .priv_auto = sizeof(struct sun8i_rsb_priv), + .ops = &sun8i_rsb_ops, +}; +#endif /* CONFIG_IS_ENABLED(DM_I2C) */ |