diff options
-rw-r--r-- | drivers/power/pmic/Kconfig | 10 | ||||
-rw-r--r-- | drivers/power/pmic/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/pmic/ab8500.c | 268 | ||||
-rw-r--r-- | include/power/ab8500.h | 125 |
4 files changed, 404 insertions, 0 deletions
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..1f64f217c3 --- /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), +}; 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@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 |