--- /dev/null
+// 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),
+};
--- /dev/null
+/* 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