u-boot/drivers/i2c/sun6i_p2wi.c
Samuel Holland 48457f7ab0 i2c: sun6i_p2wi: Initialize chips in .child_pre_probe
Chips attached to the P2WI bus require an initialization command before
they can be used. (Specifically, this switches the chip from I2C mode
to P2WI mode.) The driver does this in its .probe_chip hook, under the
assumption that .probe_chip is called during child probe. This is not
the case; .probe_chip is only called by dm_i2c_probe, which is intended
for use by board-level code, not for chips with OF nodes.

Since this initialization command must be run before a child chip can be
used, do it before probing each child.

Signed-off-by: Samuel Holland <samuel@sholland.org>
Acked-by: Andre Przywara <andre.przywara@arm.com>
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
2022-04-04 23:24:17 +01:00

221 lines
5.4 KiB
C

// 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)
{
/* 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;
/* 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);
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);
struct udevice *bus = child->parent;
/* Ensure each transfer is for a single register. */
chip->flags |= DM_I2C_CHIP_RD_ADDRESS | DM_I2C_CHIP_WR_ADDRESS;
return sun6i_p2wi_probe_chip(bus, chip->chip_addr, 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) */