[PATCH 0/3] Add support for USB on ST-Ericsson Ux500

This patch series adds support for USB on ST-Ericsson Ux500 by: - Adding a driver to talk to the AB8500 PMIC - Adding a driver to enable the USB PHY - Adding a simple Ux500 glue driver for the musb-new driver
This was tested on the u8500 "stemmy" board that is already present in U-Boot.
Stephan Gerhold (3): power: pmic: Add driver for ST-Ericsson AB8500 via PRCMU phy: Add driver for ST-Ericsson AB8500 USB PHY usb: musb-new: Add glue driver for ST-Ericsson Ux500
drivers/phy/Kconfig | 6 + drivers/phy/Makefile | 1 + drivers/phy/phy-ab8500-usb.c | 54 +++++++ drivers/power/pmic/Kconfig | 10 ++ drivers/power/pmic/Makefile | 1 + drivers/power/pmic/ab8500.c | 258 +++++++++++++++++++++++++++++++ drivers/usb/musb-new/Kconfig | 11 +- drivers/usb/musb-new/Makefile | 1 + drivers/usb/musb-new/musb_core.c | 2 +- drivers/usb/musb-new/ux500.c | 179 +++++++++++++++++++++ include/power/ab8500.h | 125 +++++++++++++++ 11 files changed, 646 insertions(+), 2 deletions(-) create mode 100644 drivers/phy/phy-ab8500-usb.c create mode 100644 drivers/power/pmic/ab8500.c create mode 100644 drivers/usb/musb-new/ux500.c create mode 100644 include/power/ab8500.h

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

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@linaro.org Signed-off-by: Stephan Gerhold stephan@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@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

On Tue, Jul 06, 2021 at 10:08:03AM +0900, Jaehoon Chung wrote:
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@linaro.org Signed-off-by: Stephan Gerhold stephan@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()?
Good point, it's probably better to return in case of errors here.
[...]
- /* clear mailbox 5 ack irq */
- regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT);
Ditto.
But here it doesn't really make sense: The read/write is already done. Even if we don't manage to clear the IRQ the read/write will still have succeeded. I will add a comment to clarify this.
- 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;
+};> +
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?
At the moment I don't have any drivers myself that make use of this. However, it would certainly be needed if more advanced drivers for AB8500 are written. I don't know if this will happen anytime soon.
I can remove these if you would prefer to have them added once they are actually needed. What do you think?
Thanks! Stephan
+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

On 7/6/21 5:52 PM, Stephan Gerhold wrote:
On Tue, Jul 06, 2021 at 10:08:03AM +0900, Jaehoon Chung wrote:
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@linaro.org Signed-off-by: Stephan Gerhold stephan@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()?
Good point, it's probably better to return in case of errors here.
[...]
- /* clear mailbox 5 ack irq */
- regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT);
Ditto.
But here it doesn't really make sense: The read/write is already done. Even if we don't manage to clear the IRQ the read/write will still have succeeded. I will add a comment to clarify this.
- 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;
+};> +
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?
At the moment I don't have any drivers myself that make use of this. However, it would certainly be needed if more advanced drivers for AB8500 are written. I don't know if this will happen anytime soon.
I can remove these if you would prefer to have them added once they are actually needed. What do you think?
If you want to maintain them, not need to remove them. :)
Best Regards, Jaehoon Chung
Thanks! Stephan
+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

On Wed, Jul 07, 2021 at 06:25:37AM +0900, Jaehoon Chung wrote:
On 7/6/21 5:52 PM, Stephan Gerhold wrote:
On Tue, Jul 06, 2021 at 10:08:03AM +0900, Jaehoon Chung wrote:
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@linaro.org Signed-off-by: Stephan Gerhold stephan@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()?
Good point, it's probably better to return in case of errors here.
[...]
- /* clear mailbox 5 ack irq */
- regmap_write(priv->regmap, PRCM_ARM_IT1_CLR, PRCMU_I2C_MBOX_BIT);
Ditto.
But here it doesn't really make sense: The read/write is already done. Even if we don't manage to clear the IRQ the read/write will still have succeeded. I will add a comment to clarify this.
- 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;
+};> +
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?
At the moment I don't have any drivers myself that make use of this. However, it would certainly be needed if more advanced drivers for AB8500 are written. I don't know if this will happen anytime soon.
I can remove these if you would prefer to have them added once they are actually needed. What do you think?
If you want to maintain them, not need to remove them. :)
Yes, I think they are fine. Will send a v2 with the other suggested changes soon.
Thanks! Stephan

The AB8500 PMIC contains an USB PHY that needs to be set up in device or host mode to make USB work properly. Add a simple driver for the generic PHY uclass that allows enabling it.
The if (CONFIG_IS_ENABLED(USB_MUSB_HOST)) might be a bit strange. The USB PHY must be configured in either host or device mode and somehow the USB PHY driver must be made aware of the mode.
Actually, the MUSB driver used together with this PHY does not support dynamic selection of host/device mode in U-Boot at the moment. Therefore, one very simple approach that works fine is to select the mode to configure at compile time. When the MUSB driver is configured in host mode the PHY is configured in host mode, and similarly when the MUSB driver is configured in device/gadget mode.
Cc: Linus Walleij linus.walleij@linaro.org Signed-off-by: Stephan Gerhold stephan@gerhold.net ---
Better suggestions to make the USB PHY driver aware of the mode are very welcome. :) I'm not sure it's worth it though, the compile time selection is not ideal but it does the job just fine for now. --- drivers/phy/Kconfig | 6 ++++ drivers/phy/Makefile | 1 + drivers/phy/phy-ab8500-usb.c | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 drivers/phy/phy-ab8500-usb.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 008186a10d..92c74b9d0b 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -64,6 +64,12 @@ config MIPI_DPHY_HELPERS help Provides a number of helpers a core functions for MIPI D-PHY drivers.
+config AB8500_USB_PHY + bool "AB8500 USB PHY Driver" + depends on PHY && PMIC_AB8500 + help + Support for the USB OTG PHY in ST-Ericsson AB8500. + config BCM6318_USBH_PHY bool "BCM6318 USBH PHY support" depends on PHY && ARCH_BMIPS diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 3c4a673a83..bf03d05d9b 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_$(SPL_)PHY) += phy-uclass.o obj-$(CONFIG_$(SPL_)NOP_PHY) += nop-phy.o obj-$(CONFIG_MIPI_DPHY_HELPERS) += phy-core-mipi-dphy.o +obj-$(CONFIG_AB8500_USB_PHY) += phy-ab8500-usb.o obj-$(CONFIG_BCM6318_USBH_PHY) += bcm6318-usbh-phy.o obj-$(CONFIG_BCM6348_USBH_PHY) += bcm6348-usbh-phy.o obj-$(CONFIG_BCM6358_USBH_PHY) += bcm6358-usbh-phy.o diff --git a/drivers/phy/phy-ab8500-usb.c b/drivers/phy/phy-ab8500-usb.c new file mode 100644 index 0000000000..4ecb03a9cb --- /dev/null +++ b/drivers/phy/phy-ab8500-usb.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2019 Stephan Gerhold */ + +#include <common.h> +#include <dm.h> +#include <generic-phy.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <power/ab8500.h> + +#define AB8500_USB_PHY_CTRL_REG AB8500_USB(0x8A) +#define AB8500_BIT_PHY_CTRL_HOST_EN BIT(0) +#define AB8500_BIT_PHY_CTRL_DEVICE_EN BIT(1) +#define AB8500_USB_PHY_CTRL_MASK (AB8500_BIT_PHY_CTRL_HOST_EN |\ + AB8500_BIT_PHY_CTRL_DEVICE_EN) + +static int ab8500_usb_phy_power_on(struct phy *phy) +{ + struct udevice *dev = phy->dev; + uint set; + + if (CONFIG_IS_ENABLED(USB_MUSB_HOST)) + set = AB8500_BIT_PHY_CTRL_HOST_EN; + else + set = AB8500_BIT_PHY_CTRL_DEVICE_EN; + + return pmic_clrsetbits(dev->parent, AB8500_USB_PHY_CTRL_REG, + AB8500_USB_PHY_CTRL_MASK, set); +} + +static int ab8500_usb_phy_power_off(struct phy *phy) +{ + struct udevice *dev = phy->dev; + + return pmic_clrsetbits(dev->parent, AB8500_USB_PHY_CTRL_REG, + AB8500_USB_PHY_CTRL_MASK, 0); +} + +struct phy_ops ab8500_usb_phy_ops = { + .power_on = ab8500_usb_phy_power_on, + .power_off = ab8500_usb_phy_power_off, +}; + +static const struct udevice_id ab8500_usb_phy_ids[] = { + { .compatible = "stericsson,ab8500-usb" }, + { } +}; + +U_BOOT_DRIVER(ab8500_usb_phy) = { + .name = "ab8500_usb_phy", + .id = UCLASS_PHY, + .of_match = ab8500_usb_phy_ids, + .ops = &ab8500_usb_phy_ops, +};

Hi Stephan,
On 7/6/21 1:28 AM, Stephan Gerhold wrote:
The AB8500 PMIC contains an USB PHY that needs to be set up in device or host mode to make USB work properly. Add a simple driver for the generic PHY uclass that allows enabling it.
The if (CONFIG_IS_ENABLED(USB_MUSB_HOST)) might be a bit strange. The USB PHY must be configured in either host or device mode and somehow the USB PHY driver must be made aware of the mode.
Actually, the MUSB driver used together with this PHY does not support dynamic selection of host/device mode in U-Boot at the moment. Therefore, one very simple approach that works fine is to select the mode to configure at compile time. When the MUSB driver is configured in host mode the PHY is configured in host mode, and similarly when the MUSB driver is configured in device/gadget mode.
Cc: Linus Walleij linus.walleij@linaro.org Signed-off-by: Stephan Gerhold stephan@gerhold.net
Better suggestions to make the USB PHY driver aware of the mode are very welcome. :) I'm not sure it's worth it though, the compile time selection is not ideal but it does the job just fine for now.
drivers/phy/Kconfig | 6 ++++ drivers/phy/Makefile | 1 + drivers/phy/phy-ab8500-usb.c | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 drivers/phy/phy-ab8500-usb.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 008186a10d..92c74b9d0b 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -64,6 +64,12 @@ config MIPI_DPHY_HELPERS help Provides a number of helpers a core functions for MIPI D-PHY drivers.
+config AB8500_USB_PHY
- bool "AB8500 USB PHY Driver"
- depends on PHY && PMIC_AB8500
- help
Support for the USB OTG PHY in ST-Ericsson AB8500.
config BCM6318_USBH_PHY bool "BCM6318 USBH PHY support" depends on PHY && ARCH_BMIPS diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 3c4a673a83..bf03d05d9b 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_$(SPL_)PHY) += phy-uclass.o obj-$(CONFIG_$(SPL_)NOP_PHY) += nop-phy.o obj-$(CONFIG_MIPI_DPHY_HELPERS) += phy-core-mipi-dphy.o +obj-$(CONFIG_AB8500_USB_PHY) += phy-ab8500-usb.o obj-$(CONFIG_BCM6318_USBH_PHY) += bcm6318-usbh-phy.o obj-$(CONFIG_BCM6348_USBH_PHY) += bcm6348-usbh-phy.o obj-$(CONFIG_BCM6358_USBH_PHY) += bcm6358-usbh-phy.o diff --git a/drivers/phy/phy-ab8500-usb.c b/drivers/phy/phy-ab8500-usb.c new file mode 100644 index 0000000000..4ecb03a9cb --- /dev/null +++ b/drivers/phy/phy-ab8500-usb.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2019 Stephan Gerhold */
+#include <common.h> +#include <dm.h> +#include <generic-phy.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <power/ab8500.h>
+#define AB8500_USB_PHY_CTRL_REG AB8500_USB(0x8A) +#define AB8500_BIT_PHY_CTRL_HOST_EN BIT(0) +#define AB8500_BIT_PHY_CTRL_DEVICE_EN BIT(1) +#define AB8500_USB_PHY_CTRL_MASK (AB8500_BIT_PHY_CTRL_HOST_EN |\
AB8500_BIT_PHY_CTRL_DEVICE_EN)
+static int ab8500_usb_phy_power_on(struct phy *phy) +{
- struct udevice *dev = phy->dev;
- uint set;
unit set = AB8500_BIT_PHY_CTRL_DEVICE_EN;
if (CONFIG_IS_ENABLED(USB)MUSB_HOST)) set = ...;
how about that?
- if (CONFIG_IS_ENABLED(USB_MUSB_HOST))
set = AB8500_BIT_PHY_CTRL_HOST_EN;
- else
set = AB8500_BIT_PHY_CTRL_DEVICE_EN;
- return pmic_clrsetbits(dev->parent, AB8500_USB_PHY_CTRL_REG,
AB8500_USB_PHY_CTRL_MASK, set);
+}
+static int ab8500_usb_phy_power_off(struct phy *phy) +{
- struct udevice *dev = phy->dev;
- return pmic_clrsetbits(dev->parent, AB8500_USB_PHY_CTRL_REG,
AB8500_USB_PHY_CTRL_MASK, 0);
+}
+struct phy_ops ab8500_usb_phy_ops = {
- .power_on = ab8500_usb_phy_power_on,
- .power_off = ab8500_usb_phy_power_off,
+};
+static const struct udevice_id ab8500_usb_phy_ids[] = {
- { .compatible = "stericsson,ab8500-usb" },
- { }
+};
+U_BOOT_DRIVER(ab8500_usb_phy) = {
- .name = "ab8500_usb_phy",
- .id = UCLASS_PHY,
- .of_match = ab8500_usb_phy_ids,
- .ops = &ab8500_usb_phy_ops,
+};

On Tue, Jul 06, 2021 at 10:10:54AM +0900, Jaehoon Chung wrote:
Hi Stephan,
On 7/6/21 1:28 AM, Stephan Gerhold wrote:
The AB8500 PMIC contains an USB PHY that needs to be set up in device or host mode to make USB work properly. Add a simple driver for the generic PHY uclass that allows enabling it.
The if (CONFIG_IS_ENABLED(USB_MUSB_HOST)) might be a bit strange. The USB PHY must be configured in either host or device mode and somehow the USB PHY driver must be made aware of the mode.
Actually, the MUSB driver used together with this PHY does not support dynamic selection of host/device mode in U-Boot at the moment. Therefore, one very simple approach that works fine is to select the mode to configure at compile time. When the MUSB driver is configured in host mode the PHY is configured in host mode, and similarly when the MUSB driver is configured in device/gadget mode.
Cc: Linus Walleij linus.walleij@linaro.org Signed-off-by: Stephan Gerhold stephan@gerhold.net
Better suggestions to make the USB PHY driver aware of the mode are very welcome. :) I'm not sure it's worth it though, the compile time selection is not ideal but it does the job just fine for now.
drivers/phy/Kconfig | 6 ++++ drivers/phy/Makefile | 1 + drivers/phy/phy-ab8500-usb.c | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 drivers/phy/phy-ab8500-usb.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 008186a10d..92c74b9d0b 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -64,6 +64,12 @@ config MIPI_DPHY_HELPERS help Provides a number of helpers a core functions for MIPI D-PHY drivers.
+config AB8500_USB_PHY
- bool "AB8500 USB PHY Driver"
- depends on PHY && PMIC_AB8500
- help
Support for the USB OTG PHY in ST-Ericsson AB8500.
config BCM6318_USBH_PHY bool "BCM6318 USBH PHY support" depends on PHY && ARCH_BMIPS diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 3c4a673a83..bf03d05d9b 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_$(SPL_)PHY) += phy-uclass.o obj-$(CONFIG_$(SPL_)NOP_PHY) += nop-phy.o obj-$(CONFIG_MIPI_DPHY_HELPERS) += phy-core-mipi-dphy.o +obj-$(CONFIG_AB8500_USB_PHY) += phy-ab8500-usb.o obj-$(CONFIG_BCM6318_USBH_PHY) += bcm6318-usbh-phy.o obj-$(CONFIG_BCM6348_USBH_PHY) += bcm6348-usbh-phy.o obj-$(CONFIG_BCM6358_USBH_PHY) += bcm6358-usbh-phy.o diff --git a/drivers/phy/phy-ab8500-usb.c b/drivers/phy/phy-ab8500-usb.c new file mode 100644 index 0000000000..4ecb03a9cb --- /dev/null +++ b/drivers/phy/phy-ab8500-usb.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2019 Stephan Gerhold */
+#include <common.h> +#include <dm.h> +#include <generic-phy.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <power/ab8500.h>
+#define AB8500_USB_PHY_CTRL_REG AB8500_USB(0x8A) +#define AB8500_BIT_PHY_CTRL_HOST_EN BIT(0) +#define AB8500_BIT_PHY_CTRL_DEVICE_EN BIT(1) +#define AB8500_USB_PHY_CTRL_MASK (AB8500_BIT_PHY_CTRL_HOST_EN |\
AB8500_BIT_PHY_CTRL_DEVICE_EN)
+static int ab8500_usb_phy_power_on(struct phy *phy) +{
- struct udevice *dev = phy->dev;
- uint set;
unit set = AB8500_BIT_PHY_CTRL_DEVICE_EN;
if (CONFIG_IS_ENABLED(USB)MUSB_HOST)) set = ...;
how about that?
Both looks fine to me, I can change it in v2.
- if (CONFIG_IS_ENABLED(USB_MUSB_HOST))
set = AB8500_BIT_PHY_CTRL_HOST_EN;
- else
set = AB8500_BIT_PHY_CTRL_DEVICE_EN;
- return pmic_clrsetbits(dev->parent, AB8500_USB_PHY_CTRL_REG,
AB8500_USB_PHY_CTRL_MASK, set);
+}
Thanks for the review! Stephan

On Mon, Jul 5, 2021 at 6:28 PM Stephan Gerhold stephan@gerhold.net wrote:
The AB8500 PMIC contains an USB PHY that needs to be set up in device or host mode to make USB work properly. Add a simple driver for the generic PHY uclass that allows enabling it.
The if (CONFIG_IS_ENABLED(USB_MUSB_HOST)) might be a bit strange. The USB PHY must be configured in either host or device mode and somehow the USB PHY driver must be made aware of the mode.
Actually, the MUSB driver used together with this PHY does not support dynamic selection of host/device mode in U-Boot at the moment. Therefore, one very simple approach that works fine is to select the mode to configure at compile time. When the MUSB driver is configured in host mode the PHY is configured in host mode, and similarly when the MUSB driver is configured in device/gadget mode.
Cc: Linus Walleij linus.walleij@linaro.org Signed-off-by: Stephan Gerhold stephan@gerhold.net
FWIW: Reviewed-by: Linus Walleij linus.walleij@linaro.org
Yours, Linus Walleij

The ST-Ericsson DB8500 SoC contains a MUSB OTG controller which supports both host and gadget mode. For some reason there is nothing special about it - add a simple glue driver for Ux500 that literally just sets up MUSB together with a generic PHY. There are no SoC-specific registers etc needed to make USB work.
The new Ux500 glue driver is only tested to work with DM_USB and DM_USB_GADGET. Both host and gadget mode work fine on the u8500 "stemmy" board that is already present in U-Boot.
Cc: Linus Walleij linus.walleij@linaro.org Signed-off-by: Stephan Gerhold stephan@gerhold.net ---
This patch could be applied separately from the first two if needed. There is a functional runtime dependency but not a compile time dependency with the PMIC changes in the first two patches.
--- drivers/usb/musb-new/Kconfig | 11 +- drivers/usb/musb-new/Makefile | 1 + drivers/usb/musb-new/musb_core.c | 2 +- drivers/usb/musb-new/ux500.c | 179 +++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 drivers/usb/musb-new/ux500.c
diff --git a/drivers/usb/musb-new/Kconfig b/drivers/usb/musb-new/Kconfig index fd6f4109b0..81ceea9740 100644 --- a/drivers/usb/musb-new/Kconfig +++ b/drivers/usb/musb-new/Kconfig @@ -72,6 +72,15 @@ config USB_MUSB_SUNXI Say y here to enable support for the sunxi OTG / DRC USB controller used on almost all sunxi boards.
+config USB_MUSB_UX500 + bool "Enable ST-Ericsson Ux500 USB controller" + depends on DM_USB && DM_USB_GADGET && ARCH_U8500 + default y + help + Say y to enable support for the MUSB OTG USB controller used in + ST-Ericsson Ux500. The driver supports either gadget or host mode + based on the selection of CONFIG_USB_MUSB_HOST. + config USB_MUSB_DISABLE_BULK_COMBINE_SPLIT bool "Disable MUSB bulk split/combine" default y @@ -85,7 +94,7 @@ endif
config USB_MUSB_PIO_ONLY bool "Disable DMA (always use PIO)" - default y if USB_MUSB_AM35X || USB_MUSB_PIC32 || USB_MUSB_OMAP2PLUS || USB_MUSB_DSPS || USB_MUSB_SUNXI || USB_MUSB_MT85XX + default y if USB_MUSB_AM35X || USB_MUSB_PIC32 || USB_MUSB_OMAP2PLUS || USB_MUSB_DSPS || USB_MUSB_SUNXI || USB_MUSB_MT85XX || USB_MUSB_UX500 help All data is copied between memory and FIFO by the CPU. DMA controllers are ignored. diff --git a/drivers/usb/musb-new/Makefile b/drivers/usb/musb-new/Makefile index 6355eb12dd..396ff02654 100644 --- a/drivers/usb/musb-new/Makefile +++ b/drivers/usb/musb-new/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_USB_MUSB_OMAP2PLUS) += omap2430.o obj-$(CONFIG_USB_MUSB_PIC32) += pic32.o obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o obj-$(CONFIG_USB_MUSB_TI) += ti-musb.o +obj-$(CONFIG_USB_MUSB_UX500) += ux500.o
ccflags-y := $(call cc-option,-Wno-unused-variable) \ $(call cc-option,-Wno-unused-but-set-variable) \ diff --git a/drivers/usb/musb-new/musb_core.c b/drivers/usb/musb-new/musb_core.c index 22811a5efb..18d9bc805f 100644 --- a/drivers/usb/musb-new/musb_core.c +++ b/drivers/usb/musb-new/musb_core.c @@ -1526,7 +1526,7 @@ static int __devinit musb_core_init(u16 musb_type, struct musb *musb) /*-------------------------------------------------------------------------*/
#if defined(CONFIG_SOC_OMAP2430) || defined(CONFIG_SOC_OMAP3430) || \ - defined(CONFIG_ARCH_OMAP4) + defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_ARCH_U8500)
static irqreturn_t generic_interrupt(int irq, void *__hci) { diff --git a/drivers/usb/musb-new/ux500.c b/drivers/usb/musb-new/ux500.c new file mode 100644 index 0000000000..57c7d5630d --- /dev/null +++ b/drivers/usb/musb-new/ux500.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2019 Stephan Gerhold */ + +#include <common.h> +#include <dm.h> +#include <generic-phy.h> +#include <dm/device_compat.h> +#include "musb_uboot.h" + +static struct musb_hdrc_config ux500_musb_hdrc_config = { + .multipoint = true, + .dyn_fifo = true, + .num_eps = 16, + .ram_bits = 16, +}; + +struct ux500_glue { + struct musb_host_data mdata; + struct device dev; + struct phy phy; + bool enabled; +}; +#define to_ux500_glue(d) container_of(d, struct ux500_glue, dev) + +static int ux500_musb_enable(struct musb *musb) +{ + struct ux500_glue *glue = to_ux500_glue(musb->controller); + int ret; + + if (glue->enabled) + return 0; + + ret = generic_phy_power_on(&glue->phy); + if (ret) { + printf("%s: failed to power on USB PHY\n", __func__); + return ret; + } + + glue->enabled = true; + return 0; +} + +static void ux500_musb_disable(struct musb *musb) +{ + struct ux500_glue *glue = to_ux500_glue(musb->controller); + int ret; + + if (!glue->enabled) + return; + + ret = generic_phy_power_off(&glue->phy); + if (ret) { + printf("%s: failed to power off USB PHY\n", __func__); + return; + } + + glue->enabled = false; +} + +static int ux500_musb_init(struct musb *musb) +{ + struct ux500_glue *glue = to_ux500_glue(musb->controller); + int ret; + + ret = generic_phy_init(&glue->phy); + if (ret) { + printf("%s: failed to init USB PHY\n", __func__); + return ret; + } + + return 0; +} + +static int ux500_musb_exit(struct musb *musb) +{ + struct ux500_glue *glue = to_ux500_glue(musb->controller); + int ret; + + ret = generic_phy_exit(&glue->phy); + if (ret) { + printf("%s: failed to exit USB PHY\n", __func__); + return ret; + } + + return 0; +} + +static const struct musb_platform_ops ux500_musb_ops = { + .init = ux500_musb_init, + .exit = ux500_musb_exit, + .enable = ux500_musb_enable, + .disable = ux500_musb_disable, +}; + +int dm_usb_gadget_handle_interrupts(struct udevice *dev) +{ + struct ux500_glue *glue = dev_get_priv(dev); + + glue->mdata.host->isr(0, glue->mdata.host); + return 0; +} + +static int ux500_musb_probe(struct udevice *dev) +{ +#ifdef CONFIG_USB_MUSB_HOST + struct usb_bus_priv *priv = dev_get_uclass_priv(dev); +#endif + struct ux500_glue *glue = dev_get_priv(dev); + struct musb_host_data *host = &glue->mdata; + struct musb_hdrc_platform_data pdata; + void *base = dev_read_addr_ptr(dev); + int ret; + + if (!base) + return -EINVAL; + + ret = generic_phy_get_by_name(dev, "usb", &glue->phy); + if (ret) { + dev_err(dev, "failed to get USB PHY: %d\n", ret); + return ret; + } + + memset(&pdata, 0, sizeof(pdata)); + pdata.platform_ops = &ux500_musb_ops; + pdata.config = &ux500_musb_hdrc_config; + +#ifdef CONFIG_USB_MUSB_HOST + priv->desc_before_addr = true; + pdata.mode = MUSB_HOST; + + host->host = musb_init_controller(&pdata, &glue->dev, base); + if (!host->host) + return -EIO; + + return musb_lowlevel_init(host); +#else + pdata.mode = MUSB_PERIPHERAL; + host->host = musb_init_controller(&pdata, &glue->dev, base); + if (!host->host) + return -EIO; + + return usb_add_gadget_udc(&glue->dev, &host->host->g); +#endif +} + +static int ux500_musb_remove(struct udevice *dev) +{ + struct ux500_glue *glue = dev_get_priv(dev); + struct musb_host_data *host = &glue->mdata; + + usb_del_gadget_udc(&host->host->g); + musb_stop(host->host); + free(host->host); + host->host = NULL; + + return 0; +} + +static const struct udevice_id ux500_musb_ids[] = { + { .compatible = "stericsson,db8500-musb" }, + { } +}; + +U_BOOT_DRIVER(ux500_musb) = { + .name = "ux500-musb", +#ifdef CONFIG_USB_MUSB_HOST + .id = UCLASS_USB, +#else + .id = UCLASS_USB_GADGET_GENERIC, +#endif + .of_match = ux500_musb_ids, + .probe = ux500_musb_probe, + .remove = ux500_musb_remove, +#ifdef CONFIG_USB_MUSB_HOST + .ops = &musb_usb_ops, +#endif + .plat_auto = sizeof(struct usb_plat), + .priv_auto = sizeof(struct ux500_glue), +};

On Mon, Jul 5, 2021 at 6:28 PM Stephan Gerhold stephan@gerhold.net wrote:
The ST-Ericsson DB8500 SoC contains a MUSB OTG controller which supports both host and gadget mode. For some reason there is nothing special about it - add a simple glue driver for Ux500 that literally just sets up MUSB together with a generic PHY. There are no SoC-specific registers etc needed to make USB work.
The new Ux500 glue driver is only tested to work with DM_USB and DM_USB_GADGET. Both host and gadget mode work fine on the u8500 "stemmy" board that is already present in U-Boot.
Cc: Linus Walleij linus.walleij@linaro.org Signed-off-by: Stephan Gerhold stephan@gerhold.net
FWIW: Reviewed-by: Linus Walleij linus.walleij@linaro.org
Yours, Linus Walleij
participants (3)
-
Jaehoon Chung
-
Linus Walleij
-
Stephan Gerhold