]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
power: pmic: Add driver for ST-Ericsson AB8500 via PRCMU
authorStephan Gerhold <stephan@gerhold.net>
Thu, 8 Jul 2021 18:33:48 +0000 (20:33 +0200)
committerTom Rini <trini@konsulko.com>
Wed, 14 Jul 2021 20:48:14 +0000 (16:48 -0400)
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@linaro.org>
Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
Acked-by: Jaehoon Chung <jh80.chung@samsung.com>
drivers/power/pmic/Kconfig
drivers/power/pmic/Makefile
drivers/power/pmic/ab8500.c [new file with mode: 0644]
include/power/ab8500.h [new file with mode: 0644]

index 583fd3ddcde7e315e6ca8ea05d4009cf91d72772..fd6648b313ee14e3ff2605655a472bca3c5cc969 100644 (file)
@@ -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
index 89099fde57381ba13ca488ba309fd7dd66278d11..5d1a97e5f6f5c2804178c0652731927fea7e200c 100644 (file)
@@ -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 (file)
index 0000000..1f64f21
--- /dev/null
@@ -0,0 +1,268 @@
+// 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);
+       if (ret)
+               return ret;
+
+       if (val & PRCMU_I2C_MBOX_BIT) {
+               printf("ab8500: warning: PRCMU i2c mailbox was not acked\n");
+               /* clear mailbox 5 ack irq */
+               ret = regmap_write(priv->regmap, PRCM_ARM_IT1_CLR,
+                                  PRCMU_I2C_MBOX_BIT);
+               if (ret)
+                       return ret;
+       }
+
+       /* 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. Note that the transfer is already complete
+        * here so checking for errors does not make sense. Clearing the irq
+        * will be retried in prcmu_wait_i2c_mbx_ready() on the next transfer.
+        */
+       regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT);
+
+       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 (file)
index 0000000..157eb4a
--- /dev/null
@@ -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@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;
+};
+
+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