[PATCH v4 0/7] Add support for PMICs used on Tegra 3 devices

Patch set adds basic support for PMICs used in many Tegra 3 devices. All PMIC drivers are based on datasheets provided by vendors. Implemented API include regulator set/get voltage enable/disable and basic PMIC r/w capabilities.
Drivers were tested by me on LG P895 (max77663), HTC One X (TPS80032) and ASUS TF300T (tps65911). Calculations and behavior were correct, predictable and reproducible.
Poweroff functional will be added after this basic drivers are merged.
--- Changes from v3: - expanded descriptions in Kconfigs for pmics and regulators - replaced ret with other variable name if it is not used for return - dropped prefixed with pmic name - dropped () in ~(var) type expressions - return dm_i2c_* output in pmic_read/write functions - switched to log_debug in some places - added descryptions for some "non obvious" functions - fixed tps80031 spms voltage calculation in non linear region - adjsuted tps65911 vio voltage calculation
Changes from v2: - removed all previously made poweroff API in favour to use sysreset after this patchset is accepted
Changes from v1: - created poweroff API for PMIC and integrated it call into do_poweroff - added poweroff function to submitted max77663, tps80032 and tps65911 drivers - fixed registers asignments for regulators with non-numeric name (ldoln, ldousb, vddctrl) ---
Svyatoslav Ryhel (7): power: pmic: palmas: support TI TPS65913 PMIC power: pmic: add the base MAX77663 PMIC support power: regulator: max77663: add regulator support power: pmic: add the base TPS80031 PMIC support power: regulator: tps80031: add regulator support power: pmic: tps65910: add TPS65911 PMIC support power: regulator: tps65911: add regulator support
doc/device-tree-bindings/pmic/max77663.txt | 84 ++++ doc/device-tree-bindings/pmic/tps65911.txt | 78 ++++ doc/device-tree-bindings/pmic/tps80031.txt | 76 ++++ drivers/power/pmic/Kconfig | 20 + drivers/power/pmic/Makefile | 2 + drivers/power/pmic/max77663.c | 86 ++++ drivers/power/pmic/palmas.c | 1 + drivers/power/pmic/pmic_tps65910_dm.c | 26 +- drivers/power/pmic/tps80031.c | 87 ++++ drivers/power/regulator/Kconfig | 28 ++ drivers/power/regulator/Makefile | 3 + drivers/power/regulator/max77663_regulator.c | 378 ++++++++++++++++++ drivers/power/regulator/tps65911_regulator.c | 397 +++++++++++++++++++ drivers/power/regulator/tps80031_regulator.c | 343 ++++++++++++++++ include/power/max77663.h | 42 ++ include/power/tps65910_pmic.h | 47 +++ include/power/tps80031.h | 45 +++ 17 files changed, 1740 insertions(+), 3 deletions(-) create mode 100644 doc/device-tree-bindings/pmic/max77663.txt create mode 100644 doc/device-tree-bindings/pmic/tps65911.txt create mode 100644 doc/device-tree-bindings/pmic/tps80031.txt create mode 100644 drivers/power/pmic/max77663.c create mode 100644 drivers/power/pmic/tps80031.c create mode 100644 drivers/power/regulator/max77663_regulator.c create mode 100644 drivers/power/regulator/tps65911_regulator.c create mode 100644 drivers/power/regulator/tps80031_regulator.c create mode 100644 include/power/max77663.h create mode 100644 include/power/tps80031.h

Existing PALMAS PMIC driver is fully compatible with TI TPS65913 PMIC found in many Tegra 4 devices, like Tegra Note 7 and ASUS TF701T. TPS65913 shares same structure of regulators like TPS659038 so data can be reused.
Tested-by: Svyatoslav Ryhel clamor95@gmail.com # NVIDIA Tegratab Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- drivers/power/pmic/palmas.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/power/pmic/palmas.c b/drivers/power/pmic/palmas.c index 6080cbff0b..d6eb9cb433 100644 --- a/drivers/power/pmic/palmas.c +++ b/drivers/power/pmic/palmas.c @@ -87,6 +87,7 @@ static struct dm_pmic_ops palmas_ops = {
static const struct udevice_id palmas_ids[] = { { .compatible = "ti,tps659038", .data = TPS659038 }, + { .compatible = "ti,tps65913" , .data = TPS659038 }, { .compatible = "ti,tps65917" , .data = TPS65917 }, { } };

Add support to bind the regulators/child nodes with the pmic. Also adds the pmic i2c based read/write functions to access pmic registers.
Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- doc/device-tree-bindings/pmic/max77663.txt | 84 +++++++++++++++++++++ drivers/power/pmic/Kconfig | 9 +++ drivers/power/pmic/Makefile | 1 + drivers/power/pmic/max77663.c | 86 ++++++++++++++++++++++ include/power/max77663.h | 42 +++++++++++ 5 files changed, 222 insertions(+) create mode 100644 doc/device-tree-bindings/pmic/max77663.txt create mode 100644 drivers/power/pmic/max77663.c create mode 100644 include/power/max77663.h
diff --git a/doc/device-tree-bindings/pmic/max77663.txt b/doc/device-tree-bindings/pmic/max77663.txt new file mode 100644 index 0000000000..ddb7d3eb14 --- /dev/null +++ b/doc/device-tree-bindings/pmic/max77663.txt @@ -0,0 +1,84 @@ +MAXIM, MAX77663 PMIC + +This device uses two drivers: +- drivers/power/pmic/max77663.c (for parent device) +- drivers/power/regulator/max77663_regulator.c (for child regulators) + +This chapter describes the binding info for the PMIC driver and regulators. + +Required properties for PMIC: +- compatible: "maxim,max77663" +- reg: usually 0x1c or 0x3c + +With those two properties, the pmic device can be used for read/write only. +To bind each regulator, the optional regulators subnode should exists. + +Optional subnode: +- name: regulators (subnode list of each device's regulator) + +Regulators subnode contains set on supported regulators. + +Required properties: +- regulator-name: used for regulator uclass platform data '.name', + +List of supported regulator nodes names for max77663: +- sd0, sd1, sd2, sd3, ldo0, ldo1, ldo2, ldo3, ldo4, ldo5, ldo6, ldo7, ldo8 + +Optional: +- regulator-min-microvolt: minimum allowed Voltage to set +- regulator-max-microvolt: minimum allowed Voltage to set +- regulator-always-on: regulator should be never disabled +- regulator-boot-on: regulator should be enabled by the bootloader + +Linux driver binding for this driver is compatible. + +Example: + +max77663@1c { + compatible = "maxim,max77663"; + reg = <0x1c>; + + regulators { + sd0 { + regulator-name = "vdd_cpu"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1250000>; + regulator-always-on; + regulator-boot-on; + }; + + ... + + ldo0 { + regulator-name = "avdd_pll"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + ... + + ldo2 { + regulator-name = "avdd_usb"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + + ldo3 { + regulator-name = "vdd_sdmmc3"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + regulator-always-on; + regulator-boot-on; + }; + + ... + + ldo8 { + regulator-name = "avdd_dsi_csi"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + }; +}; diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index 176fb07c65..b2a5e71a87 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -184,6 +184,15 @@ config SPL_DM_PMIC_PFUZE100 This config enables implementation of driver-model pmic uclass features for PMIC PFUZE100 in SPL. The driver implements read/write operations.
+config DM_PMIC_MAX77663 + bool "Enable Driver Model for PMIC MAX77663" + ---help--- + This config enables implementation of driver-model pmic uclass features + for PMIC MAX77663. The driver implements read/write operations. + This is a Power Management IC with a decent set of peripherals from which + 4 DC-to-DC Step-Down (SD) Regulators, 9 Low-Dropout Linear (LDO) Regulators, + 8 GPIOs, Real-Time Clock (RTC) and more with I2C Compatible Interface. + config DM_PMIC_MAX77686 bool "Enable Driver Model for PMIC MAX77686" ---help--- diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 0b3b3d62d0..414a9d8225 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_$(SPL_TPL_)DM_PMIC) += pmic-uclass.o obj-$(CONFIG_$(SPL_)DM_PMIC_FAN53555) += fan53555.o obj-$(CONFIG_$(SPL_)DM_PMIC_DA9063) += da9063.o +obj-$(CONFIG_$(SPL_)DM_PMIC_MAX77663) += max77663.o obj-$(CONFIG_DM_PMIC_MAX77686) += max77686.o obj-$(CONFIG_DM_PMIC_MAX8998) += max8998.o obj-$(CONFIG_DM_PMIC_MC34708) += mc34708.o diff --git a/drivers/power/pmic/max77663.c b/drivers/power/pmic/max77663.c new file mode 100644 index 0000000000..4070235d3c --- /dev/null +++ b/drivers/power/pmic/max77663.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <i2c.h> +#include <log.h> +#include <power/pmic.h> +#include <power/regulator.h> +#include <power/max77663.h> + +static const struct pmic_child_info pmic_children_info[] = { + { .prefix = "ldo", .driver = MAX77663_LDO_DRIVER }, + { .prefix = "sd", .driver = MAX77663_SD_DRIVER }, + { }, +}; + +static int max77663_write(struct udevice *dev, uint reg, const uint8_t *buff, + int len) +{ + int ret; + + ret = dm_i2c_write(dev, reg, buff, len); + if (ret) { + log_debug("write error to device: %p register: %#x!\n", dev, reg); + return ret; + } + + return 0; +} + +static int max77663_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{ + int ret; + + ret = dm_i2c_read(dev, reg, buff, len); + if (ret) { + log_debug("read error from device: %p register: %#x!\n", dev, reg); + return ret; + } + + return 0; +} + +static int max77663_bind(struct udevice *dev) +{ + ofnode regulators_node; + int children; + + regulators_node = dev_read_subnode(dev, "regulators"); + if (!ofnode_valid(regulators_node)) { + log_err("%s regulators subnode not found!\n", dev->name); + return -ENXIO; + } + + debug("%s: '%s' - found regulators subnode\n", __func__, dev->name); + + children = pmic_bind_children(dev, regulators_node, pmic_children_info); + if (!children) + log_err("%s - no child found\n", dev->name); + + /* Always return success for this device */ + return 0; +} + +static struct dm_pmic_ops max77663_ops = { + .read = max77663_read, + .write = max77663_write, +}; + +static const struct udevice_id max77663_ids[] = { + { .compatible = "maxim,max77663" }, + { } +}; + +U_BOOT_DRIVER(pmic_max77663) = { + .name = "max77663_pmic", + .id = UCLASS_PMIC, + .of_match = max77663_ids, + .bind = max77663_bind, + .ops = &max77663_ops, +}; diff --git a/include/power/max77663.h b/include/power/max77663.h new file mode 100644 index 0000000000..0f764bcbcc --- /dev/null +++ b/include/power/max77663.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#ifndef _MAX77663_H_ +#define _MAX77663_H_ + +#define MAX77663_LDO_NUM 9 +#define MAX77663_SD_NUM 5 + +/* Drivers name */ +#define MAX77663_LDO_DRIVER "max77663_ldo" +#define MAX77663_SD_DRIVER "max77663_sd" + +/* Step-Down (SD) Regulator calculations */ +#define SD_STATUS_MASK 0x30 + +#define SD0_VOLT_MAX_HEX 0x40 +#define SD1_VOLT_MAX_HEX 0x4c +#define SD_VOLT_MAX_HEX 0xff +#define SD_VOLT_MIN_HEX 0x02 + +#define SD0_VOLT_MAX 1400000 +#define SD1_VOLT_MAX 1550000 +#define SD_VOLT_MAX 3787500 +#define SD_VOLT_MIN 625000 + +#define SD_VOLT_BASE 600000 + +/* Low-Dropout Linear (LDO) Regulator calculations */ +#define LDO_STATUS_MASK 0xc0 +#define LDO_VOLT_MASK 0x3f +#define LDO_VOLT_MAX_HEX 0x3f + +#define LDO01_VOLT_MAX 2375000 +#define LDO4_VOLT_MAX 1587500 +#define LDO_VOLT_MAX 3950000 + +#define LDO_VOLT_BASE 800000 + +#endif /* _MAX77663_H_ */

The driver provides regulator set/get voltage enable/disable functions for MAXIM MAX77663 PMICs.
Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- drivers/power/regulator/Kconfig | 9 + drivers/power/regulator/Makefile | 1 + drivers/power/regulator/max77663_regulator.c | 378 +++++++++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 drivers/power/regulator/max77663_regulator.c
diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index eb5aa38c1c..581816294c 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -141,6 +141,15 @@ config SPL_REGULATOR_PWM This config enables implementation of driver-model regulator uclass features for PWM regulators in SPL.
+config DM_REGULATOR_MAX77663 + bool "Enable Driver Model for REGULATOR MAX77663" + depends on DM_REGULATOR && DM_PMIC_MAX77663 + ---help--- + This config enables implementation of driver-model regulator uclass + features for REGULATOR MAX77663. The driver supports both DC-to-DC + Step-Down (SD) Regulators and Low-Dropout Linear (LDO) Regulators + found in MAX77663 PMIC and implements get/set api for value and enable. + config DM_REGULATOR_MAX77686 bool "Enable Driver Model for REGULATOR MAX77686" depends on DM_REGULATOR && DM_PMIC_MAX77686 diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index d9e0cd5949..8d73169b50 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_REGULATOR_AS3722) += as3722_regulator.o obj-$(CONFIG_$(SPL_)REGULATOR_AXP) += axp_regulator.o obj-$(CONFIG_$(SPL_)REGULATOR_AXP_USB_POWER) += axp_usb_power.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_DA9063) += da9063.o +obj-$(CONFIG_$(SPL_)DM_REGULATOR_MAX77663) += max77663_regulator.o obj-$(CONFIG_DM_REGULATOR_MAX77686) += max77686.o obj-$(CONFIG_DM_REGULATOR_NPCM8XX) += npcm8xx_regulator.o obj-$(CONFIG_$(SPL_)DM_PMIC_PFUZE100) += pfuze100.o diff --git a/drivers/power/regulator/max77663_regulator.c b/drivers/power/regulator/max77663_regulator.c new file mode 100644 index 0000000000..bccfb4ccf9 --- /dev/null +++ b/drivers/power/regulator/max77663_regulator.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <power/pmic.h> +#include <power/regulator.h> +#include <power/max77663.h> + +/* fist row is control registers, second is voltage registers */ +static const char max77663_sd_reg[][MAX77663_SD_NUM] = { + { 0x1d, 0x1e, 0x1f, 0x20, 0x21 }, + { 0x16, 0x17, 0x18, 0x19, 0x2a }, +}; + +static const char max77663_ldo_reg[MAX77663_LDO_NUM] = { + 0x23, 0x25, 0x27, 0x29, 0x2b, 0x2d, 0x2f, 0x31, 0x33 +}; + +static int max77663_sd_enable(struct udevice *dev, int op, bool *enable) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->ctrl_reg; + int val, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + if (val & SD_STATUS_MASK) + *enable = true; + else + *enable = false; + + return 0; + } else if (op == PMIC_OP_SET) { + val &= ~SD_STATUS_MASK; + + if (*enable) + val |= SD_STATUS_MASK; + + ret = pmic_reg_write(dev->parent, adr, val); + if (ret) + return ret; + } + + return 0; +} + +/** + * max77663_*_volt2hex() - convert voltage in uV into + * applicable to register hex value + * + * @idx: regulator index + * @uV: voltage in uV + * + * Return: voltage in hex on success, -ve on failure + */ +static int max77663_sd_volt2hex(int idx, int uV) +{ + switch (idx) { + case 0: + /* SD0 has max voltage 1.4V */ + if (uV > SD0_VOLT_MAX) + return -EINVAL; + break; + case 1: + /* SD1 has max voltage 1.55V */ + if (uV > SD1_VOLT_MAX) + return -EINVAL; + break; + default: + /* SD2 and SD3 have max voltage 3.79V */ + if (uV > SD_VOLT_MAX) + return -EINVAL; + break; + }; + + if (uV < SD_VOLT_MIN) + uV = SD_VOLT_MIN; + + return (uV - SD_VOLT_BASE) / 12500; +} + +/** + * max77663_*_hex2volt() - convert register hex value into + * actual voltage in uV + * + * @idx: regulator index + * @hex: hex value of register + * + * Return: voltage in uV on success, -ve on failure + */ +static int max77663_sd_hex2volt(int idx, int hex) +{ + switch (idx) { + case 0: + /* SD0 has max voltage 1.4V */ + if (hex > SD0_VOLT_MAX_HEX) + return -EINVAL; + break; + case 1: + /* SD1 has max voltage 1.55V */ + if (hex > SD1_VOLT_MAX_HEX) + return -EINVAL; + break; + default: + /* SD2 and SD3 have max voltage 3.79V */ + if (hex > SD_VOLT_MAX_HEX) + return -EINVAL; + break; + }; + + if (hex < SD_VOLT_MIN_HEX) + hex = SD_VOLT_MIN_HEX; + + return SD_VOLT_BASE + hex * 12500; +} + +static int max77663_sd_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->volt_reg; + int idx = dev->driver_data; + int hex, ret; + + if (op == PMIC_OP_GET) { + hex = pmic_reg_read(dev->parent, adr); + if (hex < 0) + return hex; + + *uV = 0; + + ret = max77663_sd_hex2volt(idx, hex); + if (ret < 0) + return ret; + *uV = ret; + + return 0; + } + + /* SD regulators use entire register for voltage */ + hex = max77663_sd_volt2hex(idx, *uV); + if (hex < 0) + return hex; + + return pmic_reg_write(dev->parent, adr, hex); +} + +static int max77663_sd_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + int idx = dev->driver_data; + + uc_pdata->type = REGULATOR_TYPE_BUCK; + uc_pdata->ctrl_reg = max77663_sd_reg[0][idx]; + uc_pdata->volt_reg = max77663_sd_reg[1][idx]; + + return 0; +} + +static int sd_get_value(struct udevice *dev) +{ + int uV; + int ret; + + ret = max77663_sd_val(dev, PMIC_OP_GET, &uV); + if (ret) + return ret; + + return uV; +} + +static int sd_set_value(struct udevice *dev, int uV) +{ + return max77663_sd_val(dev, PMIC_OP_SET, &uV); +} + +static int sd_get_enable(struct udevice *dev) +{ + bool enable = false; + int ret; + + ret = max77663_sd_enable(dev, PMIC_OP_GET, &enable); + if (ret) + return ret; + + return enable; +} + +static int sd_set_enable(struct udevice *dev, bool enable) +{ + return max77663_sd_enable(dev, PMIC_OP_SET, &enable); +} + +static const struct dm_regulator_ops max77663_sd_ops = { + .get_value = sd_get_value, + .set_value = sd_set_value, + .get_enable = sd_get_enable, + .set_enable = sd_set_enable, +}; + +U_BOOT_DRIVER(max77663_sd) = { + .name = MAX77663_SD_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &max77663_sd_ops, + .probe = max77663_sd_probe, +}; + +static int max77663_ldo_enable(struct udevice *dev, int op, bool *enable) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->ctrl_reg; + int val, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + if (val & LDO_STATUS_MASK) + *enable = true; + else + *enable = false; + + return 0; + } else if (op == PMIC_OP_SET) { + val &= ~LDO_STATUS_MASK; + + if (*enable) + val |= LDO_STATUS_MASK; + + ret = pmic_reg_write(dev->parent, adr, val); + if (ret) + return ret; + } + + return 0; +} + +static int max77663_ldo_volt2hex(int idx, int uV) +{ + switch (idx) { + case 0: + case 1: + if (uV > LDO01_VOLT_MAX) + return -EINVAL; + + return (uV - LDO_VOLT_BASE) / 25000; + case 4: + if (uV > LDO4_VOLT_MAX) + return -EINVAL; + + return (uV - LDO_VOLT_BASE) / 12500; + default: + if (uV > LDO_VOLT_MAX) + return -EINVAL; + + return (uV - LDO_VOLT_BASE) / 50000; + }; +} + +static int max77663_ldo_hex2volt(int idx, int hex) +{ + if (hex > LDO_VOLT_MAX_HEX) + return -EINVAL; + + switch (idx) { + case 0: + case 1: + return (hex * 25000) + LDO_VOLT_BASE; + case 4: + return (hex * 12500) + LDO_VOLT_BASE; + default: + return (hex * 50000) + LDO_VOLT_BASE; + }; +} + +static int max77663_ldo_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->ctrl_reg; + int idx = dev->driver_data; + int hex, val, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + *uV = 0; + + ret = max77663_ldo_hex2volt(idx, val & LDO_VOLT_MASK); + if (ret < 0) + return ret; + + *uV = ret; + return 0; + } + + hex = max77663_ldo_volt2hex(idx, *uV); + if (hex < 0) + return hex; + + val &= ~LDO_VOLT_MASK; + + return pmic_reg_write(dev->parent, adr, val | hex); +} + +static int max77663_ldo_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + int idx = dev->driver_data; + + uc_pdata->type = REGULATOR_TYPE_LDO; + uc_pdata->ctrl_reg = max77663_ldo_reg[idx]; + + return 0; +} + +static int ldo_get_value(struct udevice *dev) +{ + int uV; + int ret; + + ret = max77663_ldo_val(dev, PMIC_OP_GET, &uV); + if (ret) + return ret; + + return uV; +} + +static int ldo_set_value(struct udevice *dev, int uV) +{ + return max77663_ldo_val(dev, PMIC_OP_SET, &uV); +} + +static int ldo_get_enable(struct udevice *dev) +{ + bool enable = false; + int ret; + + ret = max77663_ldo_enable(dev, PMIC_OP_GET, &enable); + if (ret) + return ret; + + return enable; +} + +static int ldo_set_enable(struct udevice *dev, bool enable) +{ + return max77663_ldo_enable(dev, PMIC_OP_SET, &enable); +} + +static const struct dm_regulator_ops max77663_ldo_ops = { + .get_value = ldo_get_value, + .set_value = ldo_set_value, + .get_enable = ldo_get_enable, + .set_enable = ldo_set_enable, +}; + +U_BOOT_DRIVER(max77663_ldo) = { + .name = MAX77663_LDO_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &max77663_ldo_ops, + .probe = max77663_ldo_probe, +};

Add support to bind the regulators/child nodes with the pmic. Also adds the pmic i2c based read/write functions to access pmic registers.
Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- doc/device-tree-bindings/pmic/tps80031.txt | 76 +++++++++++++++++++ drivers/power/pmic/Kconfig | 11 +++ drivers/power/pmic/Makefile | 1 + drivers/power/pmic/tps80031.c | 87 ++++++++++++++++++++++ include/power/tps80031.h | 45 +++++++++++ 5 files changed, 220 insertions(+) create mode 100644 doc/device-tree-bindings/pmic/tps80031.txt create mode 100644 drivers/power/pmic/tps80031.c create mode 100644 include/power/tps80031.h
diff --git a/doc/device-tree-bindings/pmic/tps80031.txt b/doc/device-tree-bindings/pmic/tps80031.txt new file mode 100644 index 0000000000..577e6de1c1 --- /dev/null +++ b/doc/device-tree-bindings/pmic/tps80031.txt @@ -0,0 +1,76 @@ +Texas Instruments, TPS80031/TPS80032 PMIC + +This device uses two drivers: +- drivers/power/pmic/tps80031.c (for parent device) +- drivers/power/regulator/tps80031_regulator.c (for child regulators) + +This chapter describes the binding info for the PMIC driver and regulators. + +Required properties for PMIC: +- compatible: "ti,tps80031" or "ti,tps80032" +- reg: 0x48 + +With those two properties, the pmic device can be used for read/write only. +To bind each regulator, the optional regulators subnode should exists. + +Optional subnode: +- name: regulators (subnode list of each device's regulator) + +Regulators subnode contains set on supported regulators. + +Required properties: +- regulator-name: used for regulator uclass platform data '.name', + +List of supported regulator nodes names for tps80031/tps80032: +- smps1, smps2, smps3, smps4, smps5 +- ldo1, ldo2, ldo3, ldo4, ldo5, ldo6, ldo7, ldoln, ldousb + +SMPS5 in Linux 3.1.10 is referred as vio, but datasheet clearly names it SMPS5. + +Optional: +- regulator-min-microvolt: minimum allowed Voltage to set +- regulator-max-microvolt: minimum allowed Voltage to set +- regulator-always-on: regulator should be never disabled +- regulator-boot-on: regulator should be enabled by the bootloader + +Example: + +tps80032@48 { + compatible = "ti,tps80032"; + reg = <0x48>; + + regulators { + smps1 { + regulator-name = "vdd_cpu"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1250000>; + regulator-always-on; + regulator-boot-on; + }; + + ... + + smps5 { + regulator-name = "vdd_1v8_gen"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + + ldo1 { + regulator-name = "avdd_dsi_csi"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-boot-on; + }; + + ... + + ldousb { + regulator-name = "avdd_usb"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + }; +}; diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index b2a5e71a87..df42f2fa2f 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -350,6 +350,17 @@ config DM_PMIC_TPS65910 DC-DC converter, 8 LDOs and a RTC. This driver binds the SMPS and LDO pmic children.
+config DM_PMIC_TPS80031 + bool "Enable driver for Texas Instruments TPS80031/TPS80032 PMIC" + ---help--- + This config enables implementation of driver-model pmic uclass features + for TPS80031/TPS80032 PMICs. The driver implements read/write operations. + This is a Power Management IC with a decent set of peripherals from which + 5 Buck Converters refered as Switched-mode power supply (SMPS), 11 General- + Purpose Low-Dropout Voltage Regulators (LDO), USB OTG Module, Real-Time + Clock (RTC) with Timer and Alarm Wake-Up, Two Digital PWM Outputs and more + with I2C Compatible Interface. PMIC occupies 4 I2C addresses. + config PMIC_STPMIC1 bool "Enable support for STMicroelectronics STPMIC1 PMIC" depends on DM_I2C diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 414a9d8225..55ee614364 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_$(SPL_)PMIC_RN5T567) += rn5t567.o obj-$(CONFIG_PMIC_TPS65090) += tps65090.o obj-$(CONFIG_PMIC_S5M8767) += s5m8767.o obj-$(CONFIG_DM_PMIC_TPS65910) += pmic_tps65910_dm.o +obj-$(CONFIG_$(SPL_)DM_PMIC_TPS80031) += tps80031.o obj-$(CONFIG_$(SPL_)PMIC_PALMAS) += palmas.o obj-$(CONFIG_$(SPL_)PMIC_LP873X) += lp873x.o obj-$(CONFIG_$(SPL_)PMIC_LP87565) += lp87565.o diff --git a/drivers/power/pmic/tps80031.c b/drivers/power/pmic/tps80031.c new file mode 100644 index 0000000000..c2b04fffdf --- /dev/null +++ b/drivers/power/pmic/tps80031.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <i2c.h> +#include <log.h> +#include <power/pmic.h> +#include <power/regulator.h> +#include <power/tps80031.h> + +static const struct pmic_child_info pmic_children_info[] = { + { .prefix = "ldo", .driver = TPS80031_LDO_DRIVER }, + { .prefix = "smps", .driver = TPS80031_SMPS_DRIVER }, + { }, +}; + +static int tps80031_write(struct udevice *dev, uint reg, const uint8_t *buff, + int len) +{ + int ret; + + ret = dm_i2c_write(dev, reg, buff, len); + if (ret) { + log_debug("write error to device: %p register: %#x!\n", dev, reg); + return ret; + } + + return 0; +} + +static int tps80031_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{ + int ret; + + ret = dm_i2c_read(dev, reg, buff, len); + if (ret) { + log_debug("read error from device: %p register: %#x!\n", dev, reg); + return ret; + } + + return 0; +} + +static int tps80031_bind(struct udevice *dev) +{ + ofnode regulators_node; + int children; + + regulators_node = dev_read_subnode(dev, "regulators"); + if (!ofnode_valid(regulators_node)) { + log_err("%s regulators subnode not found!\n", dev->name); + return -ENXIO; + } + + debug("%s: '%s' - found regulators subnode\n", __func__, dev->name); + + children = pmic_bind_children(dev, regulators_node, pmic_children_info); + if (!children) + log_err("%s - no child found\n", dev->name); + + /* Always return success for this device */ + return 0; +} + +static struct dm_pmic_ops tps80031_ops = { + .read = tps80031_read, + .write = tps80031_write, +}; + +static const struct udevice_id tps80031_ids[] = { + { .compatible = "ti,tps80031" }, + { .compatible = "ti,tps80032" }, + { } +}; + +U_BOOT_DRIVER(pmic_tps80031) = { + .name = "tps80031_pmic", + .id = UCLASS_PMIC, + .of_match = tps80031_ids, + .bind = tps80031_bind, + .ops = &tps80031_ops, +}; diff --git a/include/power/tps80031.h b/include/power/tps80031.h new file mode 100644 index 0000000000..f6664e4719 --- /dev/null +++ b/include/power/tps80031.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#ifndef _TPS80031_H_ +#define _TPS80031_H_ + +#define TPS80031_LDO_NUM 9 +#define TPS80031_SMPS_NUM 5 + +/* Drivers name */ +#define TPS80031_LDO_DRIVER "tps80031_ldo" +#define TPS80031_SMPS_DRIVER "tps80031_smps" + +#define TPS80031_SMPS_OFFSET 0xe0 +#define TPS80031_OFFSET_FLAG BIT(3) + +#define REGULATOR_STATUS_MASK 0x3 +#define REGULATOR_MODE_ON 0x1 + +/* Switched-Mode Power Supply Regulator calculations */ +#define SMPS_VOLT_MASK 0x3f +#define SMPS_VOLT_LINEAR_HEX 0x39 +#define SMPS_VOLT_NLINEAR_HEX 0x3a +#define SMPS_VOLT_LINEAR 1300000 +#define SMPS_VOLT_BASE 600000 +#define SMPS_VOLT_BASE_OFFSET 700000 + +/* Low-Dropout Linear (LDO) Regulator calculations */ +#define LDO_VOLT_MASK 0x3f +#define LDO_VOLT_MAX_HEX 0x18 +#define LDO_VOLT_MIN_HEX 0x01 +#define LDO_VOLT_MAX 3360000 +#define LDO_VOLT_MIN 1018000 +#define LDO_VOLT_BASE 916000 + +/* register groups */ +enum { + CTRL, + VOLT, + OFFSET, +}; + +#endif /* _TPS80031_H_ */

The driver provides regulator set/get voltage enable/disable functions for TI TPS80031/TPS80032 PMICs.
Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- drivers/power/regulator/Kconfig | 8 + drivers/power/regulator/Makefile | 1 + drivers/power/regulator/tps80031_regulator.c | 343 +++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 drivers/power/regulator/tps80031_regulator.c
diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index 581816294c..6454c54855 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -356,6 +356,14 @@ config DM_REGULATOR_TPS62360 implements the get/set api for value only, as the power line is always on.
+config DM_REGULATOR_TPS80031 + bool "Enable driver for TPS80031/TPS80032 PMIC regulators" + depends on DM_PMIC_TPS80031 + ---help--- + This enables implementation of driver-model regulator uclass + features for TPS80031/TPS80032 PMICs. The driver implements + get/set api for: value and enable. + config DM_REGULATOR_STPMIC1 bool "Enable driver for STPMIC1 regulators" depends on DM_REGULATOR && PMIC_STPMIC1 diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index 8d73169b50..3ef55dc534 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_$(SPL_)DM_REGULATOR_LP87565) += lp87565_regulator.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o obj-$(CONFIG_DM_REGULATOR_TPS65910) += tps65910_regulator.o obj-$(CONFIG_DM_REGULATOR_TPS62360) += tps62360_regulator.o +obj-$(CONFIG_$(SPL_)DM_REGULATOR_TPS80031) += tps80031_regulator.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_STPMIC1) += stpmic1.o obj-$(CONFIG_DM_REGULATOR_TPS65941) += tps65941_regulator.o obj-$(CONFIG_DM_REGULATOR_SCMI) += scmi_regulator.o diff --git a/drivers/power/regulator/tps80031_regulator.c b/drivers/power/regulator/tps80031_regulator.c new file mode 100644 index 0000000000..fbccd9a6f3 --- /dev/null +++ b/drivers/power/regulator/tps80031_regulator.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <power/pmic.h> +#include <power/regulator.h> +#include <power/tps80031.h> + +static const char tps80031_smps_reg[][TPS80031_SMPS_NUM] = { + { 0x54, 0x5a, 0x66, 0x42, 0x48 }, + { 0x56, 0x5c, 0x68, 0x44, 0x4a }, + { BIT(3), BIT(4), BIT(6), BIT(0), BIT(1) }, +}; + +static const char tps80031_ldo_reg[][TPS80031_LDO_NUM] = { + { 0x9e, 0x86, 0x8e, 0x8a, 0x9a, 0x92, 0xa6, 0x96, 0xa2 }, + { 0x9f, 0x87, 0x8f, 0x8b, 0x9b, 0x93, 0xa7, 0x97, 0xa3 }, +}; + +static int tps80031_regulator_enable(struct udevice *dev, int op, bool *enable) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->ctrl_reg; + int val, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + if (val & REGULATOR_MODE_ON) + *enable = true; + else + *enable = false; + + return 0; + } else if (op == PMIC_OP_SET) { + val &= ~REGULATOR_STATUS_MASK; + + if (*enable) + val |= REGULATOR_MODE_ON; + + ret = pmic_reg_write(dev->parent, adr, val); + if (ret) + return ret; + } + + return 0; +} + +static int tps80031_get_enable(struct udevice *dev) +{ + bool enable = false; + int ret; + + ret = tps80031_regulator_enable(dev, PMIC_OP_GET, &enable); + if (ret) + return ret; + + return enable; +} + +static int tps80031_set_enable(struct udevice *dev, bool enable) +{ + return tps80031_regulator_enable(dev, PMIC_OP_SET, &enable); +} + +/** + * tps80031_ldo_volt2hex() - convert voltage in uV into + * applicable to register hex value + * + * @uV: voltage in uV + * + * Return: voltage in hex on success, -ve on failure + */ +static int tps80031_ldo_volt2hex(int uV) +{ + if (uV > LDO_VOLT_MAX) + return -EINVAL; + + if (uV < LDO_VOLT_MIN) + uV = LDO_VOLT_MIN; + + return DIV_ROUND_UP(uV - LDO_VOLT_BASE, 102000); +} + +/** + * tps80031_ldo_hex2volt() - convert register hex value into + * actual voltage in uV + * + * @hex: hex value of register + * + * Return: voltage in uV on success, -ve on failure + */ +static int tps80031_ldo_hex2volt(int hex) +{ + if (hex > LDO_VOLT_MAX_HEX) + return -EINVAL; + + if (hex < LDO_VOLT_MIN_HEX) + hex = LDO_VOLT_MIN_HEX; + + return LDO_VOLT_BASE + hex * 102000; +} + +static int tps80031_ldo_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->volt_reg; + int val, hex, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + *uV = 0; + + ret = tps80031_ldo_hex2volt(val & LDO_VOLT_MASK); + if (ret < 0) + return ret; + + *uV = ret; + return 0; + } + + hex = tps80031_ldo_volt2hex(*uV); + if (hex < 0) + return hex; + + val &= ~LDO_VOLT_MASK; + + return pmic_reg_write(dev->parent, adr, val | hex); +} + +static int tps80031_ldo_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + + uc_pdata->type = REGULATOR_TYPE_LDO; + + /* check for ldoln and ldousb cases */ + if (!strcmp("ldoln", dev->name)) { + uc_pdata->ctrl_reg = tps80031_ldo_reg[CTRL][7]; + uc_pdata->volt_reg = tps80031_ldo_reg[VOLT][7]; + return 0; + } + + if (!strcmp("ldousb", dev->name)) { + uc_pdata->ctrl_reg = tps80031_ldo_reg[CTRL][8]; + uc_pdata->volt_reg = tps80031_ldo_reg[VOLT][8]; + return 0; + } + + if (dev->driver_data > 0) { + u8 idx = dev->driver_data - 1; + + uc_pdata->ctrl_reg = tps80031_ldo_reg[CTRL][idx]; + uc_pdata->volt_reg = tps80031_ldo_reg[VOLT][idx]; + } + + return 0; +} + +static int ldo_get_value(struct udevice *dev) +{ + int uV; + int ret; + + ret = tps80031_ldo_val(dev, PMIC_OP_GET, &uV); + if (ret) + return ret; + + return uV; +} + +static int ldo_set_value(struct udevice *dev, int uV) +{ + return tps80031_ldo_val(dev, PMIC_OP_SET, &uV); +} + +static const struct dm_regulator_ops tps80031_ldo_ops = { + .get_value = ldo_get_value, + .set_value = ldo_set_value, + .get_enable = tps80031_get_enable, + .set_enable = tps80031_set_enable, +}; + +U_BOOT_DRIVER(tps80031_ldo) = { + .name = TPS80031_LDO_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &tps80031_ldo_ops, + .probe = tps80031_ldo_probe, +}; + +/* DCDC voltages for the selector of 0x39 to 0x3F */ +static int tps80031_dcdc_voltages[5] = { + 1350000, 1500000, 1800000, 1900000, 2100000 +}; + +/** + * tps80031_smps_volt2hex() - convert voltage in uV into + * applicable to register hex value + * + * @base: base voltage in uV + * @uV: voltage in uV + * + * Return: voltage in hex on success, -ve on failure + */ +static int tps80031_smps_volt2hex(u32 base, int uV) +{ + int i; + + if (uV < base) + return 1; + + if (uV > SMPS_VOLT_LINEAR) { + for (i = 0; i < ARRAY_SIZE(tps80031_dcdc_voltages); i++) + if (uV <= tps80031_dcdc_voltages[i]) + break; + + return SMPS_VOLT_NLINEAR_HEX + i; + } + + return DIV_ROUND_UP(uV - base, 12500); +} + +/** + * tps80031_smps_hex2volt() - convert register hex value into + * actual voltage in uV + * + * @base: base voltage in uV + * @hex: hex value of register + * + * Return: voltage in uV on success, -ve on failure + */ +static int tps80031_smps_hex2volt(u32 base, int hex) +{ + if (!hex) + return 0; + + /* if reg value exceeds linear scale use table */ + if (hex > SMPS_VOLT_LINEAR_HEX) + return tps80031_dcdc_voltages[hex - SMPS_VOLT_LINEAR_HEX]; + else + return base + hex * 12500; +} + +static int tps80031_smps_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->volt_reg; + int base, val, hex, ret; + + /* If offset flag was set then base voltage is higher */ + if (uc_pdata->flags & TPS80031_OFFSET_FLAG) + base = SMPS_VOLT_BASE_OFFSET; + else + base = SMPS_VOLT_BASE; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + *uV = 0; + + ret = tps80031_smps_hex2volt(base, val & SMPS_VOLT_MASK); + if (ret < 0) + return ret; + + *uV = ret; + return 0; + } + + hex = tps80031_smps_volt2hex(base, *uV); + if (hex < 0) + return hex; + + val &= ~SMPS_VOLT_MASK; + + return pmic_reg_write(dev->parent, adr, val | hex); +} + +static int tps80031_smps_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + int idx = dev->driver_data - 1; + int val; + + uc_pdata->type = REGULATOR_TYPE_BUCK; + + uc_pdata->ctrl_reg = tps80031_smps_reg[CTRL][idx]; + uc_pdata->volt_reg = tps80031_smps_reg[VOLT][idx]; + + /* Determine if smps regulator uses higher voltage */ + val = pmic_reg_read(dev->parent, TPS80031_SMPS_OFFSET); + if (val & tps80031_smps_reg[OFFSET][idx]) + uc_pdata->flags |= TPS80031_OFFSET_FLAG; + + return 0; +} + +static int smps_get_value(struct udevice *dev) +{ + int uV; + int ret; + + ret = tps80031_smps_val(dev, PMIC_OP_GET, &uV); + if (ret) + return ret; + + return uV; +} + +static int smps_set_value(struct udevice *dev, int uV) +{ + return tps80031_smps_val(dev, PMIC_OP_SET, &uV); +} + +static const struct dm_regulator_ops tps80031_smps_ops = { + .get_value = smps_get_value, + .set_value = smps_set_value, + .get_enable = tps80031_get_enable, + .set_enable = tps80031_set_enable, +}; + +U_BOOT_DRIVER(tps80031_smps) = { + .name = TPS80031_SMPS_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &tps80031_smps_ops, + .probe = tps80031_smps_probe, +};

Add support to bind the regulators/child nodes with the pmic. Also adds the pmic i2c based read/write functions to access pmic registers.
Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- doc/device-tree-bindings/pmic/tps65911.txt | 78 ++++++++++++++++++++++ drivers/power/pmic/pmic_tps65910_dm.c | 26 +++++++- include/power/tps65910_pmic.h | 47 +++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 doc/device-tree-bindings/pmic/tps65911.txt
diff --git a/doc/device-tree-bindings/pmic/tps65911.txt b/doc/device-tree-bindings/pmic/tps65911.txt new file mode 100644 index 0000000000..29270efbfe --- /dev/null +++ b/doc/device-tree-bindings/pmic/tps65911.txt @@ -0,0 +1,78 @@ +Texas Instruments, TPS65911 PMIC + +This device uses two drivers: +- drivers/power/pmic/tps65910.c (for parent device) +- drivers/power/regulator/tps65911_regulator.c (for child regulators) + +This chapter describes the binding info for the PMIC driver and regulators. + +Required properties for PMIC: +- compatible: "ti,tps65911" +- reg: 0x2d + +With those two properties, the pmic device can be used for read/write only. +To bind each regulator, the optional regulators subnode should exists. + +Optional subnode: +- name: regulators (subnode list of each device's regulator) + +Regulators subnode contains set on supported regulators. + +Required properties: +- regulator-name: used for regulator uclass platform data '.name', + +List of supported regulator nodes names for tps65911: +- vdd1, vdd2, vddctrl, vddio +- ldo1, ldo2, ldo3, ldo4, ldo5, ldo6, ldo7, ldo8 + +vddio in datasheet is referred as vio, but for reduction of code and +unification of smps regulators it is named vddio. + +Optional: +- regulator-min-microvolt: minimum allowed Voltage to set +- regulator-max-microvolt: minimum allowed Voltage to set +- regulator-always-on: regulator should be never disabled +- regulator-boot-on: regulator should be enabled by the bootloader + +Example: + +tps65911@2d { + compatible = "ti,tps65911"; + reg = <0x2d>; + + regulators { + vdd1 { + regulator-name = "vdd_1v2_backlight"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + regulator-boot-on; + }; + + ... + + vddio { + regulator-name = "vdd_1v8_gen"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; + + ldo1 { + regulator-name = "vdd_emmc_core"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + + ... + + ldo8 { + regulator-name = "vdd_ddr_hs"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1000000>; + }; + }; +}; diff --git a/drivers/power/pmic/pmic_tps65910_dm.c b/drivers/power/pmic/pmic_tps65910_dm.c index e03ddc98d7..ff63c502d0 100644 --- a/drivers/power/pmic/pmic_tps65910_dm.c +++ b/drivers/power/pmic/pmic_tps65910_dm.c @@ -11,13 +11,19 @@ #include <power/regulator.h> #include <power/tps65910_pmic.h>
-static const struct pmic_child_info pmic_children_info[] = { +static const struct pmic_child_info tps65910_children_info[] = { { .prefix = "ldo_", .driver = TPS65910_LDO_DRIVER }, { .prefix = "buck_", .driver = TPS65910_BUCK_DRIVER }, { .prefix = "boost_", .driver = TPS65910_BOOST_DRIVER }, { }, };
+static const struct pmic_child_info tps65911_children_info[] = { + { .prefix = "ldo", .driver = TPS65911_LDO_DRIVER }, + { .prefix = "vdd", .driver = TPS65911_VDD_DRIVER }, + { }, +}; + static int pmic_tps65910_reg_count(struct udevice *dev) { return TPS65910_NUM_REGS; @@ -51,6 +57,7 @@ static int pmic_tps65910_bind(struct udevice *dev) { ofnode regulators_node; int children; + int type = dev_get_driver_data(dev);
regulators_node = dev_read_subnode(dev, "regulators"); if (!ofnode_valid(regulators_node)) { @@ -58,7 +65,19 @@ static int pmic_tps65910_bind(struct udevice *dev) return -EINVAL; }
- children = pmic_bind_children(dev, regulators_node, pmic_children_info); + switch (type) { + case TPS65910: + children = pmic_bind_children(dev, regulators_node, + tps65910_children_info); + break; + case TPS65911: + children = pmic_bind_children(dev, regulators_node, + tps65911_children_info); + break; + default: + log_err("unknown PMIC type\n"); + } + if (!children) debug("%s has no children (regulators)\n", dev->name);
@@ -82,7 +101,8 @@ static struct dm_pmic_ops pmic_tps65910_ops = { };
static const struct udevice_id pmic_tps65910_match[] = { - { .compatible = "ti,tps65910" }, + { .compatible = "ti,tps65910", .data = TPS65910 }, + { .compatible = "ti,tps65911", .data = TPS65911 }, { /* sentinel */ } };
diff --git a/include/power/tps65910_pmic.h b/include/power/tps65910_pmic.h index 66214786d3..10fe02b81a 100644 --- a/include/power/tps65910_pmic.h +++ b/include/power/tps65910_pmic.h @@ -6,6 +6,9 @@ #ifndef __TPS65910_PMIC_H_ #define __TPS65910_PMIC_H_
+#define TPS65910 1 +#define TPS65911 2 + #define TPS65910_I2C_SEL_MASK (0x1 << 4) #define TPS65910_VDD_SR_MASK (0x1 << 7) #define TPS65910_GAIN_SEL_MASK (0x3 << 6) @@ -126,4 +129,48 @@ struct tps65910_regulator_pdata { #define TPS65910_BOOST_DRIVER "tps65910_boost" #define TPS65910_LDO_DRIVER "tps65910_ldo"
+/* tps65911 i2c registers */ +enum { + TPS65911_REG_VIO = 0x20, + TPS65911_REG_VDD1, + TPS65911_REG_VDD1_OP, + TPS65911_REG_VDD1_SR, + TPS65911_REG_VDD2, + TPS65911_REG_VDD2_OP, + TPS65911_REG_VDD2_SR, + TPS65911_REG_VDDCTRL, + TPS65911_REG_VDDCTRL_OP, + TPS65911_REG_VDDCTRL_SR, + TPS65911_REG_LDO1 = 0x30, + TPS65911_REG_LDO2, + TPS65911_REG_LDO5, + TPS65911_REG_LDO8, + TPS65911_REG_LDO7, + TPS65911_REG_LDO6, + TPS65911_REG_LDO4, + TPS65911_REG_LDO3, +}; + +#define TPS65911_VDD_NUM 4 +#define TPS65911_LDO_NUM 8 + +#define TPS65911_VDD_VOLT_MAX 1500000 +#define TPS65911_VDD_VOLT_MIN 600000 +#define TPS65911_VDD_VOLT_BASE 562500 + +#define TPS65911_LDO_VOLT_MAX 3300000 +#define TPS65911_LDO_VOLT_BASE 800000 + +#define TPS65911_LDO_SEL_MASK (0x3f << 2) + +#define TPS65911_LDO124_VOLT_MAX_HEX 0x32 +#define TPS65911_LDO358_VOLT_MAX_HEX 0x19 +#define TPS65911_LDO358_VOLT_MIN_HEX 0x02 + +#define TPS65911_LDO124_VOLT_STEP 50000 +#define TPS65911_LDO358_VOLT_STEP 100000 + +#define TPS65911_VDD_DRIVER "tps65911_vdd" +#define TPS65911_LDO_DRIVER "tps65911_ldo" + #endif /* __TPS65910_PMIC_H_ */

The driver provides regulator set/get voltage enable/disable functions for TI TPS5911 PMIC.
Signed-off-by: Svyatoslav Ryhel clamor95@gmail.com Reviewed-by: Simon Glass sjg@chromium.org --- drivers/power/regulator/Kconfig | 11 + drivers/power/regulator/Makefile | 1 + drivers/power/regulator/tps65911_regulator.c | 397 +++++++++++++++++++ 3 files changed, 409 insertions(+) create mode 100644 drivers/power/regulator/tps65911_regulator.c
diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index 6454c54855..102ec7bc5f 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -346,6 +346,17 @@ config DM_REGULATOR_TPS65910 regulator types of the TPS65910 (BUCK, BOOST and LDO). It implements the get/set api for value and enable.
+config DM_REGULATOR_TPS65911 + bool "Enable driver for TPS65911 PMIC regulators" + depends on DM_PMIC_TPS65910 + ---help--- + This config enables implementation of driver-model regulator + uclass features for the TPS65911 PMIC. The driver supports Step-Down + DC-DC Converters for Processor Cores (VDD1 and VDD2), Step-Down DC-DC + Converter for I/O Power (VIO), Controller for External FETs (VDDCtrl) + and LDO Voltage Regulators found in TPS65911 PMIC and implements + get/set api for value and enable. + config DM_REGULATOR_TPS62360 bool "Enable driver for TPS6236x Power Regulator" depends on DM_REGULATOR diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index 3ef55dc534..f79932d833 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_$(SPL_)DM_REGULATOR_LP873X) += lp873x_regulator.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_LP87565) += lp87565_regulator.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o obj-$(CONFIG_DM_REGULATOR_TPS65910) += tps65910_regulator.o +obj-$(CONFIG_$(SPL_)DM_REGULATOR_TPS65911) += tps65911_regulator.o obj-$(CONFIG_DM_REGULATOR_TPS62360) += tps62360_regulator.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_TPS80031) += tps80031_regulator.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_STPMIC1) += stpmic1.o diff --git a/drivers/power/regulator/tps65911_regulator.c b/drivers/power/regulator/tps65911_regulator.c new file mode 100644 index 0000000000..ba80ed7c54 --- /dev/null +++ b/drivers/power/regulator/tps65911_regulator.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright(C) 2023 Svyatoslav Ryhel clamor95@gmail.com + */ + +#include <common.h> +#include <dm.h> +#include <log.h> +#include <power/pmic.h> +#include <power/regulator.h> +#include <power/tps65910_pmic.h> + +/* fist row is control registers, second is voltage registers */ +static const char tps65911_vdd_reg[][TPS65911_VDD_NUM] = { + { TPS65911_REG_VDD1, TPS65911_REG_VDD2, + TPS65911_REG_VDDCTRL, TPS65911_REG_VIO }, + { TPS65911_REG_VDD1_OP, TPS65911_REG_VDD2_OP, + TPS65911_REG_VDDCTRL_OP, 0x00 }, +}; + +static const char tps65911_ldo_reg[TPS65911_LDO_NUM] = { + TPS65911_REG_LDO1, TPS65911_REG_LDO2, TPS65911_REG_LDO3, + TPS65911_REG_LDO4, TPS65911_REG_LDO5, TPS65911_REG_LDO6, + TPS65911_REG_LDO7, TPS65911_REG_LDO8 +}; + +static int tps65911_regulator_enable(struct udevice *dev, int op, bool *enable) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->ctrl_reg; + int val, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + if (val & TPS65910_SUPPLY_STATE_ON) + *enable = true; + else + *enable = false; + + return 0; + } else if (op == PMIC_OP_SET) { + val &= ~TPS65910_SUPPLY_STATE_MASK; + + if (*enable) + val |= TPS65910_SUPPLY_STATE_ON; + + ret = pmic_reg_write(dev->parent, adr, val); + if (ret) + return ret; + } + + return 0; +} + +static int tps65911_get_enable(struct udevice *dev) +{ + bool enable = false; + int ret; + + ret = tps65911_regulator_enable(dev, PMIC_OP_GET, &enable); + if (ret) + return ret; + + return enable; +} + +static int tps65911_set_enable(struct udevice *dev, bool enable) +{ + return tps65911_regulator_enable(dev, PMIC_OP_SET, &enable); +} + +/** + * tps65911_vdd_volt2hex() - convert voltage in uV into + * applicable to register hex value + * + * @uV: voltage in uV + * + * Return: voltage in hex on success, -ve on failure + */ +static int tps65911_vdd_volt2hex(int uV) +{ + if (uV > TPS65911_VDD_VOLT_MAX) + return -EINVAL; + + if (uV < TPS65911_VDD_VOLT_MIN) + uV = TPS65911_VDD_VOLT_MIN; + + return (uV - TPS65911_VDD_VOLT_BASE) / 12500; +} + +/** + * tps65911_vdd_hex2volt() - convert register hex value into + * actual voltage in uV + * + * @hex: hex value of register + * + * Return: voltage in uV on success, -ve on failure + */ +static int tps65911_vdd_hex2volt(int hex) +{ + if (hex > TPS65910_VDD_SEL_MAX) + return -EINVAL; + + if (hex < TPS65910_VDD_SEL_MIN) + hex = TPS65910_VDD_SEL_MIN; + + return TPS65911_VDD_VOLT_BASE + hex * 12500; +} + +static int tps65911_vio_range[4] = { + 1500000, 1800000, 2500000, 3300000 +}; + +static int tps65911_vio_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->volt_reg; + int i, val; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + *uV = 0; + + val &= TPS65910_SEL_MASK; + + *uV = tps65911_vio_range[val >> 2]; + + return 0; + } + + val &= ~TPS65910_SEL_MASK; + + for (i = 0; i < ARRAY_SIZE(tps65911_vio_range); i++) + if (*uV <= tps65911_vio_range[i]) + break; + + return pmic_reg_write(dev->parent, adr, val | i << 2); +} + +static int tps65911_vdd_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->volt_reg; + int val, ret; + + /* in case vdd is vio */ + if (!adr) + return tps65911_vio_val(dev, op, uV); + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + *uV = 0; + + ret = tps65911_vdd_hex2volt(val); + if (ret < 0) + return ret; + + *uV = ret; + return 0; + } + + val = tps65911_vdd_volt2hex(*uV); + if (val < 0) + return val; + + return pmic_reg_write(dev->parent, adr, val); +} + +static int tps65911_vdd_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + + uc_pdata->type = REGULATOR_TYPE_BUCK; + + /* check for vddctrl and vddio cases */ + if (!strcmp("vddctrl", dev->name)) { + uc_pdata->ctrl_reg = tps65911_vdd_reg[0][2]; + uc_pdata->volt_reg = tps65911_vdd_reg[1][2]; + return 0; + } + + if (!strcmp("vddio", dev->name)) { + uc_pdata->ctrl_reg = tps65911_vdd_reg[0][3]; + uc_pdata->volt_reg = tps65911_vdd_reg[1][3]; + return 0; + } + + if (dev->driver_data > 0) { + u8 idx = dev->driver_data - 1; + + uc_pdata->ctrl_reg = tps65911_vdd_reg[0][idx]; + uc_pdata->volt_reg = tps65911_vdd_reg[1][idx]; + } + + return 0; +} + +static int vdd_get_value(struct udevice *dev) +{ + int uV; + int ret; + + ret = tps65911_vdd_val(dev, PMIC_OP_GET, &uV); + if (ret) + return ret; + + return uV; +} + +static int vdd_set_value(struct udevice *dev, int uV) +{ + return tps65911_vdd_val(dev, PMIC_OP_SET, &uV); +} + +static const struct dm_regulator_ops tps65911_vdd_ops = { + .get_value = vdd_get_value, + .set_value = vdd_set_value, + .get_enable = tps65911_get_enable, + .set_enable = tps65911_set_enable, +}; + +U_BOOT_DRIVER(tps65911_vdd) = { + .name = TPS65911_VDD_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &tps65911_vdd_ops, + .probe = tps65911_vdd_probe, +}; + +/** + * tps65911_ldo_volt2hex() - convert voltage in uV into + * applicable to register hex value + * + * @idx: regulator index + * @uV: voltage in uV + * + * Return: voltage in hex on success, -ve on failure + */ +static int tps65911_ldo_volt2hex(int idx, int uV) +{ + int step; + + if (uV > TPS65911_LDO_VOLT_MAX) + return -EINVAL; + + if (uV < TPS65911_LDO_VOLT_BASE) + uV = TPS65911_LDO_VOLT_BASE; + + switch (idx) { + case 1: + case 2: + case 4: + step = TPS65911_LDO124_VOLT_STEP; + break; + case 3: + case 5: + case 6: + case 7: + case 8: + step = TPS65911_LDO358_VOLT_STEP; + break; + default: + return -EINVAL; + }; + + return ((uV - TPS65911_LDO_VOLT_BASE) / step) << 2; +} + +/** + * tps65911_ldo_hex2volt() - convert register hex value into + * actual voltage in uV + * + * @idx: regulator index + * @hex: hex value of register + * + * Return: voltage in uV on success, -ve on failure + */ +static int tps65911_ldo_hex2volt(int idx, int hex) +{ + int step; + + switch (idx) { + case 1: + case 2: + case 4: + if (hex > TPS65911_LDO124_VOLT_MAX_HEX) + return -EINVAL; + + step = TPS65911_LDO124_VOLT_STEP; + break; + case 3: + case 5: + case 6: + case 7: + case 8: + if (hex > TPS65911_LDO358_VOLT_MAX_HEX) + return -EINVAL; + + if (hex < TPS65911_LDO358_VOLT_MIN_HEX) + hex = TPS65911_LDO358_VOLT_MIN_HEX; + + step = TPS65911_LDO358_VOLT_STEP; + break; + default: + return -EINVAL; + }; + + return TPS65911_LDO_VOLT_BASE + hex * step; +} + +static int tps65911_ldo_val(struct udevice *dev, int op, int *uV) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u32 adr = uc_pdata->ctrl_reg; + int idx = dev->driver_data; + int val, hex, ret; + + val = pmic_reg_read(dev->parent, adr); + if (val < 0) + return val; + + if (op == PMIC_OP_GET) { + *uV = 0; + val &= TPS65911_LDO_SEL_MASK; + + ret = tps65911_ldo_hex2volt(idx, val >> 2); + if (ret < 0) + return ret; + + *uV = ret; + return 0; + } + + hex = tps65911_ldo_volt2hex(idx, *uV); + if (hex < 0) + return hex; + + val &= ~TPS65911_LDO_SEL_MASK; + + return pmic_reg_write(dev->parent, adr, val | hex); +} + +static int tps65911_ldo_probe(struct udevice *dev) +{ + struct dm_regulator_uclass_plat *uc_pdata = + dev_get_uclass_plat(dev); + u8 idx = dev->driver_data - 1; + + uc_pdata->type = REGULATOR_TYPE_LDO; + uc_pdata->ctrl_reg = tps65911_ldo_reg[idx]; + + return 0; +} + +static int ldo_get_value(struct udevice *dev) +{ + int uV; + int ret; + + ret = tps65911_ldo_val(dev, PMIC_OP_GET, &uV); + if (ret) + return ret; + + return uV; +} + +static int ldo_set_value(struct udevice *dev, int uV) +{ + return tps65911_ldo_val(dev, PMIC_OP_SET, &uV); +} + +static const struct dm_regulator_ops tps65911_ldo_ops = { + .get_value = ldo_get_value, + .set_value = ldo_set_value, + .get_enable = tps65911_get_enable, + .set_enable = tps65911_set_enable, +}; + +U_BOOT_DRIVER(tps65911_ldo) = { + .name = TPS65911_LDO_DRIVER, + .id = UCLASS_REGULATOR, + .ops = &tps65911_ldo_ops, + .probe = tps65911_ldo_probe, +};
participants (1)
-
Svyatoslav Ryhel