[PATCH 1/3] power: pmic: Add driver for ST-Ericsson AB8500 via PRCMU

Jaehoon Chung jh80.chung at samsung.com
Tue Jul 6 03:08:03 CEST 2021


On 7/6/21 1:28 AM, Stephan Gerhold wrote:
> All devices based on ST-Ericsson Ux500 use a PMIC similar to AB8500
> (Analog Baseband). There is AB8500, AB8505, AB9540 and AB8540
> although in practice only AB8500 and AB8505 are relevant since the
> platforms with AB9540 and AB8540 were cancelled and never used in
> production.
> 
> In general, the AB8500 PMIC uses I2C as control interface, where the
> different register banks are represented as separate I2C devices.
> However, in practice AB8500 is always connected to a special I2C bus
> on the DB8500 SoC that is controlled by the power/reset/clock
> management unit (PRCMU) firmware.
> 
> Add a simple driver that allows reading/writing registers of the
> AB8500 PMIC. The driver directly accesses registers from the PRCMU
> parent device (represented by syscon in U-Boot). Abstracting it
> further (e.g. with the i2c uclass) would not provide any advantage
> because the PRCMU I2C bus is always just connected to AB8500 and
> vice-versa.
> 
> The ab8500.h header is mostly taken as-is from Linux (with some
> minor adjustments) to allow using similar code in both Linux and
> U-Boot.
> 
> Cc: Linus Walleij <linus.walleij at linaro.org>
> Signed-off-by: Stephan Gerhold <stephan at gerhold.net>
> ---
> 
>  drivers/power/pmic/Kconfig  |  10 ++
>  drivers/power/pmic/Makefile |   1 +
>  drivers/power/pmic/ab8500.c | 258 ++++++++++++++++++++++++++++++++++++
>  include/power/ab8500.h      | 125 +++++++++++++++++
>  4 files changed, 394 insertions(+)
>  create mode 100644 drivers/power/pmic/ab8500.c
>  create mode 100644 include/power/ab8500.h
> 
> diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig
> index 583fd3ddcd..fd6648b313 100644
> --- a/drivers/power/pmic/Kconfig
> +++ b/drivers/power/pmic/Kconfig
> @@ -31,6 +31,16 @@ config SPL_PMIC_CHILDREN
>  	to call your regulator code (e.g. see rk8xx.c for direct functions
>  	for use in SPL).
>  
> +config PMIC_AB8500
> +	bool "Enable driver for ST-Ericsson AB8500 PMIC via PRCMU"
> +	depends on DM_PMIC
> +	select REGMAP
> +	select SYSCON
> +	help
> +	  Enable support for the ST-Ericsson AB8500 (Analog Baseband) PMIC.
> +	  It connects with the ST-Ericsson DB8500 SoC via an I2C bus managed by
> +	  the power/reset/clock management unit (PRCMU) firmware.
> +
>  config PMIC_ACT8846
>  	bool "Enable support for the active-semi 8846 PMIC"
>  	depends on DM_PMIC && DM_I2C
> diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile
> index 89099fde57..5d1a97e5f6 100644
> --- a/drivers/power/pmic/Makefile
> +++ b/drivers/power/pmic/Makefile
> @@ -15,6 +15,7 @@ obj-$(CONFIG_$(SPL_)DM_PMIC_PFUZE100) += pfuze100.o
>  obj-$(CONFIG_$(SPL_)DM_PMIC_PCA9450) += pca9450.o
>  obj-$(CONFIG_PMIC_S2MPS11) += s2mps11.o
>  obj-$(CONFIG_DM_PMIC_SANDBOX) += sandbox.o i2c_pmic_emul.o
> +obj-$(CONFIG_PMIC_AB8500) += ab8500.o
>  obj-$(CONFIG_PMIC_ACT8846) += act8846.o
>  obj-$(CONFIG_PMIC_AS3722) += as3722.o as3722_gpio.o
>  obj-$(CONFIG_PMIC_MAX8997) += max8997.o
> diff --git a/drivers/power/pmic/ab8500.c b/drivers/power/pmic/ab8500.c
> new file mode 100644
> index 0000000000..a87a3b497c
> --- /dev/null
> +++ b/drivers/power/pmic/ab8500.c
> @@ -0,0 +1,258 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 Stephan Gerhold
> + *
> + * Adapted from old U-Boot and Linux kernel implementation:
> + * Copyright (C) STMicroelectronics 2009
> + * Copyright (C) ST-Ericsson SA 2010
> + */
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <regmap.h>
> +#include <syscon.h>
> +#include <linux/bitops.h>
> +#include <linux/err.h>
> +#include <power/ab8500.h>
> +#include <power/pmic.h>
> +
> +/* CPU mailbox registers */
> +#define PRCM_MBOX_CPU_VAL		0x0fc
> +#define PRCM_MBOX_CPU_SET		0x100
> +#define PRCM_MBOX_CPU_CLR		0x104
> +
> +#define PRCM_ARM_IT1_CLR		0x48C
> +#define PRCM_ARM_IT1_VAL		0x494
> +
> +#define PRCM_TCDM_RANGE			2
> +#define PRCM_REQ_MB5			0xE44
> +#define PRCM_ACK_MB5			0xDF4
> +#define _PRCM_MBOX_HEADER		0xFE8
> +#define PRCM_MBOX_HEADER_REQ_MB5	(_PRCM_MBOX_HEADER + 0x5)
> +#define PRCMU_I2C_MBOX_BIT		BIT(5)
> +
> +/* Mailbox 5 Requests */
> +#define PRCM_REQ_MB5_I2C_SLAVE_OP	(PRCM_REQ_MB5 + 0x0)
> +#define PRCM_REQ_MB5_I2C_HW_BITS	(PRCM_REQ_MB5 + 0x1)
> +#define PRCM_REQ_MB5_I2C_REG		(PRCM_REQ_MB5 + 0x2)
> +#define PRCM_REQ_MB5_I2C_VAL		(PRCM_REQ_MB5 + 0x3)
> +#define PRCMU_I2C(bank)			(((bank) << 1) | BIT(6))
> +#define PRCMU_I2C_WRITE			0
> +#define PRCMU_I2C_READ			1
> +#define PRCMU_I2C_STOP_EN		BIT(3)
> +
> +/* Mailbox 5 ACKs */
> +#define PRCM_ACK_MB5_I2C_STATUS		(PRCM_ACK_MB5 + 0x1)
> +#define PRCM_ACK_MB5_I2C_VAL		(PRCM_ACK_MB5 + 0x3)
> +#define PRCMU_I2C_WR_OK			0x1
> +#define PRCMU_I2C_RD_OK			0x2
> +
> +/* AB8500 version registers */
> +#define AB8500_MISC_REV_REG		AB8500_MISC(0x80)
> +#define AB8500_MISC_IC_NAME_REG		AB8500_MISC(0x82)
> +
> +struct ab8500_priv {
> +	struct ab8500 ab8500;
> +	struct regmap *regmap;
> +};
> +
> +static inline int prcmu_tcdm_readb(struct regmap *map, uint offset, u8 *valp)
> +{
> +	return regmap_raw_read_range(map, PRCM_TCDM_RANGE, offset,
> +				     valp, sizeof(*valp));
> +}
> +
> +static inline int prcmu_tcdm_writeb(struct regmap *map, uint offset, u8 val)
> +{
> +	return regmap_raw_write_range(map, PRCM_TCDM_RANGE, offset,
> +				      &val, sizeof(val));
> +}
> +
> +static int prcmu_wait_i2c_mbx_ready(struct ab8500_priv *priv)
> +{
> +	uint val;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, PRCM_ARM_IT1_VAL, &val);

Doesn't need to check about returned to error?

> +	if (ret == 0 && val & PRCMU_I2C_MBOX_BIT) {
> +		printf("ab8500: warning: PRCMU i2c mailbox was not acked\n");
> +		/* clear mailbox 5 ack irq */
> +		regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT);

Not need to check the return value about regmap_write()?

> +	}
> +
> +	/* wait for on-going transaction, use 1s timeout */
> +	return regmap_read_poll_timeout(priv->regmap, PRCM_MBOX_CPU_VAL, val,
> +					!(val & PRCMU_I2C_MBOX_BIT), 0, 1000);
> +}
> +
> +static int prcmu_wait_i2c_mbx_done(struct ab8500_priv *priv)
> +{
> +	uint val;
> +	int ret;
> +
> +	/* set interrupt to XP70 */
> +	ret = regmap_write(priv->regmap, PRCM_MBOX_CPU_SET, PRCMU_I2C_MBOX_BIT);
> +	if (ret)
> +		return ret;
> +
> +	/* wait for mailbox 5 (i2c) ack, use 1s timeout */
> +	return regmap_read_poll_timeout(priv->regmap, PRCM_ARM_IT1_VAL, val,
> +					(val & PRCMU_I2C_MBOX_BIT), 0, 1000);
> +}
> +
> +static int ab8500_transfer(struct udevice *dev, uint bank_reg, u8 *val,
> +			   u8 op, u8 expected_status)
> +{
> +	struct ab8500_priv *priv = dev_get_priv(dev);
> +	u8 reg = bank_reg & 0xff;
> +	u8 bank = bank_reg >> 8;
> +	u8 status;
> +	int ret;
> +
> +	ret = prcmu_wait_i2c_mbx_ready(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = prcmu_tcdm_writeb(priv->regmap, PRCM_MBOX_HEADER_REQ_MB5, 0);
> +	if (ret)
> +		return ret;
> +	ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_SLAVE_OP,
> +				PRCMU_I2C(bank) | op);
> +	if (ret)
> +		return ret;
> +	ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_HW_BITS,
> +				PRCMU_I2C_STOP_EN);
> +	if (ret)
> +		return ret;
> +	ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_REG, reg);
> +	if (ret)
> +		return ret;
> +	ret = prcmu_tcdm_writeb(priv->regmap, PRCM_REQ_MB5_I2C_VAL, *val);
> +	if (ret)
> +		return ret;
> +
> +	ret = prcmu_wait_i2c_mbx_done(priv);
> +	if (ret) {
> +		printf("%s: mailbox request timed out\n", __func__);
> +		return ret;
> +	}
> +
> +	/* read transfer result */
> +	ret = prcmu_tcdm_readb(priv->regmap, PRCM_ACK_MB5_I2C_STATUS, &status);
> +	if (ret)
> +		return ret;
> +	ret = prcmu_tcdm_readb(priv->regmap, PRCM_ACK_MB5_I2C_VAL, val);
> +	if (ret)
> +		return ret;
> +
> +	/* clear mailbox 5 ack irq */
> +	regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT);

Ditto.

> +
> +	if (status != expected_status) {
> +		/*
> +		 * AB8500 does not have the AB8500_MISC_IC_NAME_REG register,
> +		 * but we need to try reading it to detect AB8505.
> +		 * In case of an error, assume that we have AB8500.
> +		 */
> +		if (op == PRCMU_I2C_READ && bank_reg == AB8500_MISC_IC_NAME_REG) {
> +			*val = AB8500_VERSION_AB8500;
> +			return 0;
> +		}
> +
> +		printf("%s: return status %d\n", __func__, status);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ab8500_reg_count(struct udevice *dev)
> +{
> +	return AB8500_NUM_REGISTERS;
> +}
> +
> +static int ab8500_read(struct udevice *dev, uint reg, uint8_t *buf, int len)
> +{
> +	int ret;
> +
> +	if (len != 1)
> +		return -EINVAL;
> +
> +	*buf = 0;
> +	ret = ab8500_transfer(dev, reg, buf, PRCMU_I2C_READ, PRCMU_I2C_RD_OK);
> +	if (ret) {
> +		printf("%s failed: %d\n", __func__, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ab8500_write(struct udevice *dev, uint reg, const uint8_t *buf, int len)
> +{
> +	int ret;
> +	u8 val;
> +
> +	if (len != 1)
> +		return -EINVAL;
> +
> +	val = *buf;
> +	ret = ab8500_transfer(dev, reg, &val, PRCMU_I2C_WRITE, PRCMU_I2C_WR_OK);
> +	if (ret) {
> +		printf("%s failed: %d\n", __func__, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct dm_pmic_ops ab8500_ops = {
> +	.reg_count = ab8500_reg_count,
> +	.read = ab8500_read,
> +	.write = ab8500_write,
> +};
> +
> +static int ab8500_probe(struct udevice *dev)
> +{
> +	struct ab8500_priv *priv = dev_get_priv(dev);
> +	int ret;
> +
> +	/* get regmap from the PRCMU parent device (syscon in U-Boot) */
> +	priv->regmap = syscon_get_regmap(dev->parent);
> +	if (IS_ERR(priv->regmap))
> +		return PTR_ERR(priv->regmap);
> +
> +	ret = pmic_reg_read(dev, AB8500_MISC_IC_NAME_REG);
> +	if (ret < 0) {
> +		printf("ab8500: failed to read chip version: %d\n", ret);
> +		return ret;
> +	}
> +	priv->ab8500.version = ret;
> +
> +	ret = pmic_reg_read(dev, AB8500_MISC_REV_REG);
> +	if (ret < 0) {
> +		printf("ab8500: failed to read chip id: %d\n", ret);
> +		return ret;
> +	}
> +	priv->ab8500.chip_id = ret;
> +
> +	debug("ab8500: version: %#x, chip id: %#x\n",
> +	      priv->ab8500.version, priv->ab8500.chip_id);
> +
> +	return 0;
> +}
> +
> +static const struct udevice_id ab8500_ids[] = {
> +	{ .compatible = "stericsson,ab8500" },
> +	{ }
> +};
> +
> +U_BOOT_DRIVER(pmic_ab8500) = {
> +	.name		= "pmic_ab8500",
> +	.id		= UCLASS_PMIC,
> +	.of_match	= ab8500_ids,
> +	.bind		= dm_scan_fdt_dev,
> +	.probe		= ab8500_probe,
> +	.ops		= &ab8500_ops,
> +	.priv_auto	= sizeof(struct ab8500_priv),
> +};
> diff --git a/include/power/ab8500.h b/include/power/ab8500.h
> new file mode 100644
> index 0000000000..157eb4a5b1
> --- /dev/null
> +++ b/include/power/ab8500.h
> @@ -0,0 +1,125 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Based on include/linux/mfd/abx500/ab8500.h from Linux
> + * Copyright (C) ST-Ericsson SA 2010
> + * Author: Srinidhi Kasagar <srinidhi.kasagar at stericsson.com>
> + */
> +
> +#ifndef _PMIC_AB8500_H_
> +#define _PMIC_AB8500_H_
> +
> +/*
> + * AB IC versions
> + *
> + * AB8500_VERSION_AB8500 should be 0xFF but will never be read as need a
> + * non-supported multi-byte I2C access via PRCMU. Set to 0x00 to ease the
> + * print of version string.
> + */
> +enum ab8500_version {
> +	AB8500_VERSION_AB8500 = 0x0,
> +	AB8500_VERSION_AB8505 = 0x1,
> +	AB8500_VERSION_AB9540 = 0x2,
> +	AB8500_VERSION_AB8540 = 0x4,
> +	AB8500_VERSION_UNDEFINED,
> +};
> +
> +/* AB8500 CIDs*/
> +#define AB8500_CUTEARLY	0x00
> +#define AB8500_CUT1P0	0x10
> +#define AB8500_CUT1P1	0x11
> +#define AB8500_CUT1P2	0x12 /* Only valid for AB8540 */
> +#define AB8500_CUT2P0	0x20
> +#define AB8500_CUT3P0	0x30
> +#define AB8500_CUT3P3	0x33
> +
> +/*
> + * AB8500 bank addresses
> + */
> +#define AB8500_BANK(bank, reg)		(((bank) << 8) | (reg))
> +#define AB8500_M_FSM_RANK(reg)		AB8500_BANK(0x0, reg)
> +#define AB8500_SYS_CTRL1_BLOCK(reg)	AB8500_BANK(0x1, reg)
> +#define AB8500_SYS_CTRL2_BLOCK(reg)	AB8500_BANK(0x2, reg)
> +#define AB8500_REGU_CTRL1(reg)		AB8500_BANK(0x3, reg)
> +#define AB8500_REGU_CTRL2(reg)		AB8500_BANK(0x4, reg)
> +#define AB8500_USB(reg)			AB8500_BANK(0x5, reg)
> +#define AB8500_TVOUT(reg)		AB8500_BANK(0x6, reg)
> +#define AB8500_DBI(reg)			AB8500_BANK(0x7, reg)
> +#define AB8500_ECI_AV_ACC(reg)		AB8500_BANK(0x8, reg)
> +#define AB8500_RESERVED(reg)		AB8500_BANK(0x9, reg)
> +#define AB8500_GPADC(reg)		AB8500_BANK(0xA, reg)
> +#define AB8500_CHARGER(reg)		AB8500_BANK(0xB, reg)
> +#define AB8500_GAS_GAUGE(reg)		AB8500_BANK(0xC, reg)
> +#define AB8500_AUDIO(reg)		AB8500_BANK(0xD, reg)
> +#define AB8500_INTERRUPT(reg)		AB8500_BANK(0xE, reg)
> +#define AB8500_RTC(reg)			AB8500_BANK(0xF, reg)
> +#define AB8500_GPIO(reg)		AB8500_BANK(0x10, reg)
> +#define AB8500_MISC(reg)		AB8500_BANK(0x10, reg)
> +#define AB8500_DEVELOPMENT(reg)		AB8500_BANK(0x11, reg)
> +#define AB8500_DEBUG(reg)		AB8500_BANK(0x12, reg)
> +#define AB8500_PROD_TEST(reg)		AB8500_BANK(0x13, reg)
> +#define AB8500_STE_TEST(reg)		AB8500_BANK(0x14, reg)
> +#define AB8500_OTP_EMUL(reg)		AB8500_BANK(0x15, reg)
> +
> +#define AB8500_NUM_BANKS		0x16
> +#define AB8500_NUM_REGISTERS		AB8500_BANK(AB8500_NUM_BANKS, 0)
> +
> +struct ab8500 {
> +	enum ab8500_version version;
> +	u8 chip_id;
> +};> +

I have checked that below codes are existed in linux kernel. 
But I didn't find where the below codes is used in U-boot.
Will it be used in future? 

Best Regards,
Jaehoon Chung

> +static inline int is_ab8500(struct ab8500 *ab)
> +{
> +	return ab->version == AB8500_VERSION_AB8500;
> +}
> +
> +static inline int is_ab8505(struct ab8500 *ab)
> +{
> +	return ab->version == AB8500_VERSION_AB8505;
> +}
> +
> +/* exclude also ab8505, ab9540... */
> +static inline int is_ab8500_1p0_or_earlier(struct ab8500 *ab)
> +{
> +	return (is_ab8500(ab) && (ab->chip_id <= AB8500_CUT1P0));
> +}
> +
> +/* exclude also ab8505, ab9540... */
> +static inline int is_ab8500_1p1_or_earlier(struct ab8500 *ab)
> +{
> +	return (is_ab8500(ab) && (ab->chip_id <= AB8500_CUT1P1));
> +}
> +
> +/* exclude also ab8505, ab9540... */
> +static inline int is_ab8500_2p0_or_earlier(struct ab8500 *ab)
> +{
> +	return (is_ab8500(ab) && (ab->chip_id <= AB8500_CUT2P0));
> +}
> +
> +static inline int is_ab8500_3p3_or_earlier(struct ab8500 *ab)
> +{
> +	return (is_ab8500(ab) && (ab->chip_id <= AB8500_CUT3P3));
> +}
> +
> +/* exclude also ab8505, ab9540... */
> +static inline int is_ab8500_2p0(struct ab8500 *ab)
> +{
> +	return (is_ab8500(ab) && (ab->chip_id == AB8500_CUT2P0));
> +}
> +
> +static inline int is_ab8505_1p0_or_earlier(struct ab8500 *ab)
> +{
> +	return (is_ab8505(ab) && (ab->chip_id <= AB8500_CUT1P0));
> +}
> +
> +static inline int is_ab8505_2p0(struct ab8500 *ab)
> +{
> +	return (is_ab8505(ab) && (ab->chip_id == AB8500_CUT2P0));
> +}
> +
> +static inline int is_ab8505_2p0_earlier(struct ab8500 *ab)
> +{
> +	return (is_ab8505(ab) && (ab->chip_id < AB8500_CUT2P0));
> +}
> +
> +#endif
> 



More information about the U-Boot mailing list