aboutsummaryrefslogtreecommitdiff
path: root/drivers/power/pmic/ab8500.c
diff options
context:
space:
mode:
authorStephan Gerhold <stephan@gerhold.net>2021-07-08 20:33:48 +0200
committerTom Rini <trini@konsulko.com>2021-07-14 16:48:14 -0400
commit3f6e4ec7c3d0a25c3e3d69d22f11b41f11684dcb (patch)
treedbad32d26dda4cf0a59a374a185038f043de24dd /drivers/power/pmic/ab8500.c
parente2f82f93f885dd3dd3ad74e98182add8ccd863e7 (diff)
downloadu-boot-3f6e4ec7c3d0a25c3e3d69d22f11b41f11684dcb.zip
u-boot-3f6e4ec7c3d0a25c3e3d69d22f11b41f11684dcb.tar.gz
u-boot-3f6e4ec7c3d0a25c3e3d69d22f11b41f11684dcb.tar.bz2
power: pmic: Add driver for ST-Ericsson AB8500 via PRCMU
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>
Diffstat (limited to 'drivers/power/pmic/ab8500.c')
-rw-r--r--drivers/power/pmic/ab8500.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/drivers/power/pmic/ab8500.c b/drivers/power/pmic/ab8500.c
new file mode 100644
index 0000000..1f64f21
--- /dev/null
+++ b/drivers/power/pmic/ab8500.c
@@ -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),
+};