From 4094040b1a133206ed893da2cf5e17cc22f7ca7c Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Apr 2025 11:45:59 +0300 Subject: mfd: rohm-bd96801: Support ROHM BD96802 The ROHM BD96802 PMIC looks from software point of view a lot like ROHM BD96801 PMIC. Just with reduced number of voltage rails. Both PMICs provide two physical IRQ lines referred as INTB and ERRB and contain blocks implementing regulator controls and a weatchdog. Hence it makes sense to use same MFD core for both PMICs. Add support for ROHM BD96802 scalable companion PMIC to the BD96801 core driver. Signed-off-by: Matti Vaittinen Acked-by: Conor Dooley Link: https://lore.kernel.org/r/05957d194425a79a4f35f287695c3d9ca2ed1ae2.1744090658.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- include/linux/mfd/rohm-bd96801.h | 2 ++ include/linux/mfd/rohm-bd96802.h | 74 ++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/rohm-generic.h | 1 + 3 files changed, 77 insertions(+) create mode 100644 include/linux/mfd/rohm-bd96802.h (limited to 'include/linux') diff --git a/include/linux/mfd/rohm-bd96801.h b/include/linux/mfd/rohm-bd96801.h index e2d9e10b6364..68c8ac8ad409 100644 --- a/include/linux/mfd/rohm-bd96801.h +++ b/include/linux/mfd/rohm-bd96801.h @@ -40,7 +40,9 @@ * INTB status registers are at range 0x5c ... 0x63 */ #define BD96801_REG_INT_SYS_ERRB1 0x52 +#define BD96801_REG_INT_BUCK2_ERRB 0x56 #define BD96801_REG_INT_SYS_INTB 0x5c +#define BD96801_REG_INT_BUCK2_INTB 0x5e #define BD96801_REG_INT_LDO7_INTB 0x63 /* MASK registers */ diff --git a/include/linux/mfd/rohm-bd96802.h b/include/linux/mfd/rohm-bd96802.h new file mode 100644 index 000000000000..bf4b77944edf --- /dev/null +++ b/include/linux/mfd/rohm-bd96802.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025 ROHM Semiconductors + * + * The digital interface of trhe BD96802 PMIC is a reduced version of the + * BD96801. Hence the BD96801 definitions are used for registers and masks + * while this header only holds the IRQ definitions - mainly to avoid gaps in + * IRQ numbers caused by the lack of some BUCKs / LDOs and their respective + * IRQs. + */ + +#ifndef __LINUX_MFD_BD96802_H__ +#define __LINUX_MFD_BD96802_H__ + +/* ERRB IRQs */ +enum { + /* Reg 0x52, 0x53, 0x54 - ERRB system IRQs */ + BD96802_OTP_ERR_STAT, + BD96802_DBIST_ERR_STAT, + BD96802_EEP_ERR_STAT, + BD96802_ABIST_ERR_STAT, + BD96802_PRSTB_ERR_STAT, + BD96802_DRMOS1_ERR_STAT, + BD96802_DRMOS2_ERR_STAT, + BD96802_SLAVE_ERR_STAT, + BD96802_VREF_ERR_STAT, + BD96802_TSD_ERR_STAT, + BD96802_UVLO_ERR_STAT, + BD96802_OVLO_ERR_STAT, + BD96802_OSC_ERR_STAT, + BD96802_PON_ERR_STAT, + BD96802_POFF_ERR_STAT, + BD96802_CMD_SHDN_ERR_STAT, + BD96802_INT_SHDN_ERR_STAT, + + /* Reg 0x55 BUCK1 ERR IRQs */ + BD96802_BUCK1_PVIN_ERR_STAT, + BD96802_BUCK1_OVP_ERR_STAT, + BD96802_BUCK1_UVP_ERR_STAT, + BD96802_BUCK1_SHDN_ERR_STAT, + + /* Reg 0x56 BUCK2 ERR IRQs */ + BD96802_BUCK2_PVIN_ERR_STAT, + BD96802_BUCK2_OVP_ERR_STAT, + BD96802_BUCK2_UVP_ERR_STAT, + BD96802_BUCK2_SHDN_ERR_STAT, +}; + +/* INTB IRQs */ +enum { + /* Reg 0x5c (System INTB) */ + BD96802_TW_STAT, + BD96802_WDT_ERR_STAT, + BD96802_I2C_ERR_STAT, + BD96802_CHIP_IF_ERR_STAT, + + /* Reg 0x5d (BUCK1 INTB) */ + BD96802_BUCK1_OCPH_STAT, + BD96802_BUCK1_OCPL_STAT, + BD96802_BUCK1_OCPN_STAT, + BD96802_BUCK1_OVD_STAT, + BD96802_BUCK1_UVD_STAT, + BD96802_BUCK1_TW_CH_STAT, + + /* Reg 0x5e (BUCK2 INTB) */ + BD96802_BUCK2_OCPH_STAT, + BD96802_BUCK2_OCPL_STAT, + BD96802_BUCK2_OCPN_STAT, + BD96802_BUCK2_OVD_STAT, + BD96802_BUCK2_UVD_STAT, + BD96802_BUCK2_TW_CH_STAT, +}; + +#endif diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h index e7d4e6afe388..11b86f9129e3 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -17,6 +17,7 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD71837, ROHM_CHIP_TYPE_BD71847, ROHM_CHIP_TYPE_BD96801, + ROHM_CHIP_TYPE_BD96802, ROHM_CHIP_TYPE_AMOUNT }; -- cgit v1.2.3 From 6a309b489215f705c20cb4ed7f85d9c16f768e55 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Apr 2025 11:46:36 +0300 Subject: mfd: bd96801: Support ROHM BD96805 The ROHM BD96805 is from the software perspective almost identical to the ROHM BD96801. The main difference is different voltage tuning ranges. Add support differentiating these PMICs based on the compatible, and invoking the regulator driver with correct IC type. Signed-off-by: Matti Vaittinen Acked-by: Conor Dooley Link: https://lore.kernel.org/r/8680097dc083f191bea56d3ac7c6fe5c005644ec.1744090658.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/rohm-bd96801.c | 22 ++++++++++++++++++++++ include/linux/mfd/rohm-generic.h | 1 + 2 files changed, 23 insertions(+) (limited to 'include/linux') diff --git a/drivers/mfd/rohm-bd96801.c b/drivers/mfd/rohm-bd96801.c index e5ee5f556a55..dd0be7d675b8 100644 --- a/drivers/mfd/rohm-bd96801.c +++ b/drivers/mfd/rohm-bd96801.c @@ -220,6 +220,10 @@ static struct mfd_cell bd96802_cells[] = { [WDG_CELL] = { .name = "bd96801-wdt", }, [REGULATOR_CELL] = { .name = "bd96802-regulator", }, }; +static struct mfd_cell bd96805_cells[] = { + [WDG_CELL] = { .name = "bd96801-wdt", }, + [REGULATOR_CELL] = { .name = "bd96805-regulator", }, +}; static const struct regmap_range bd96801_volatile_ranges[] = { /* Status registers */ @@ -599,6 +603,20 @@ static const struct bd968xx bd96802_data = { .unlock_val = BD96801_UNLOCK, }; +static const struct bd968xx bd96805_data = { + .errb_irqs = bd96801_reg_errb_irqs, + .intb_irqs = bd96801_reg_intb_irqs, + .num_errb_irqs = ARRAY_SIZE(bd96801_reg_errb_irqs), + .num_intb_irqs = ARRAY_SIZE(bd96801_reg_intb_irqs), + .errb_irq_chip = &bd96801_irq_chip_errb, + .intb_irq_chip = &bd96801_irq_chip_intb, + .regmap_config = &bd96801_regmap_config, + .cells = bd96805_cells, + .num_cells = ARRAY_SIZE(bd96805_cells), + .unlock_reg = BD96801_LOCK_REG, + .unlock_val = BD96801_UNLOCK, +}; + static int bd96801_i2c_probe(struct i2c_client *i2c) { struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data; @@ -621,6 +639,9 @@ static int bd96801_i2c_probe(struct i2c_client *i2c) case ROHM_CHIP_TYPE_BD96802: ddata = &bd96802_data; break; + case ROHM_CHIP_TYPE_BD96805: + ddata = &bd96805_data; + break; default: dev_err(&i2c->dev, "Unknown IC\n"); return -EINVAL; @@ -717,6 +738,7 @@ skip_errb: static const struct of_device_id bd96801_of_match[] = { { .compatible = "rohm,bd96801", .data = (void *)ROHM_CHIP_TYPE_BD96801 }, { .compatible = "rohm,bd96802", .data = (void *)ROHM_CHIP_TYPE_BD96802 }, + { .compatible = "rohm,bd96805", .data = (void *)ROHM_CHIP_TYPE_BD96805 }, { } }; MODULE_DEVICE_TABLE(of, bd96801_of_match); diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h index 11b86f9129e3..867acf5baefc 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -18,6 +18,7 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD71847, ROHM_CHIP_TYPE_BD96801, ROHM_CHIP_TYPE_BD96802, + ROHM_CHIP_TYPE_BD96805, ROHM_CHIP_TYPE_AMOUNT }; -- cgit v1.2.3 From fecc18a9f59ce9c36eb3622ae77bff5fa5c6d976 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Apr 2025 11:47:10 +0300 Subject: mfd: bd96801: Support ROHM BD96806 The ROHM BD96806 is from the software perspective almost identical to the ROHM BD96802. The main difference is different voltage tuning ranges. Add support differentiating these PMICs based on the compatible, and invoking the regulator driver with correct IC type. Signed-off-by: Matti Vaittinen Acked-by: Conor Dooley Link: https://lore.kernel.org/r/ccc95ae33613648fdcba08915777d945412ac5c4.1744090658.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/rohm-bd96801.c | 23 +++++++++++++++++++++++ include/linux/mfd/rohm-generic.h | 1 + 2 files changed, 24 insertions(+) (limited to 'include/linux') diff --git a/drivers/mfd/rohm-bd96801.c b/drivers/mfd/rohm-bd96801.c index dd0be7d675b8..66fa017ad568 100644 --- a/drivers/mfd/rohm-bd96801.c +++ b/drivers/mfd/rohm-bd96801.c @@ -225,6 +225,11 @@ static struct mfd_cell bd96805_cells[] = { [REGULATOR_CELL] = { .name = "bd96805-regulator", }, }; +static struct mfd_cell bd96806_cells[] = { + [WDG_CELL] = { .name = "bd96806-wdt", }, + [REGULATOR_CELL] = { .name = "bd96806-regulator", }, +}; + static const struct regmap_range bd96801_volatile_ranges[] = { /* Status registers */ regmap_reg_range(BD96801_REG_WD_FEED, BD96801_REG_WD_FAILCOUNT), @@ -617,6 +622,20 @@ static const struct bd968xx bd96805_data = { .unlock_val = BD96801_UNLOCK, }; +static struct bd968xx bd96806_data = { + .errb_irqs = bd96802_reg_errb_irqs, + .intb_irqs = bd96802_reg_intb_irqs, + .num_errb_irqs = ARRAY_SIZE(bd96802_reg_errb_irqs), + .num_intb_irqs = ARRAY_SIZE(bd96802_reg_intb_irqs), + .errb_irq_chip = &bd96802_irq_chip_errb, + .intb_irq_chip = &bd96802_irq_chip_intb, + .regmap_config = &bd96802_regmap_config, + .cells = bd96806_cells, + .num_cells = ARRAY_SIZE(bd96806_cells), + .unlock_reg = BD96801_LOCK_REG, + .unlock_val = BD96801_UNLOCK, +}; + static int bd96801_i2c_probe(struct i2c_client *i2c) { struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data; @@ -642,6 +661,9 @@ static int bd96801_i2c_probe(struct i2c_client *i2c) case ROHM_CHIP_TYPE_BD96805: ddata = &bd96805_data; break; + case ROHM_CHIP_TYPE_BD96806: + ddata = &bd96806_data; + break; default: dev_err(&i2c->dev, "Unknown IC\n"); return -EINVAL; @@ -739,6 +761,7 @@ static const struct of_device_id bd96801_of_match[] = { { .compatible = "rohm,bd96801", .data = (void *)ROHM_CHIP_TYPE_BD96801 }, { .compatible = "rohm,bd96802", .data = (void *)ROHM_CHIP_TYPE_BD96802 }, { .compatible = "rohm,bd96805", .data = (void *)ROHM_CHIP_TYPE_BD96805 }, + { .compatible = "rohm,bd96806", .data = (void *)ROHM_CHIP_TYPE_BD96806 }, { } }; MODULE_DEVICE_TABLE(of, bd96801_of_match); diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h index 867acf5baefc..579e8dcfcca4 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -19,6 +19,7 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD96801, ROHM_CHIP_TYPE_BD96802, ROHM_CHIP_TYPE_BD96805, + ROHM_CHIP_TYPE_BD96806, ROHM_CHIP_TYPE_AMOUNT }; -- cgit v1.2.3 From bb71e40db1b7a7822f434d211b4a94bab9996cc6 Mon Sep 17 00:00:00 2001 From: André Draszik Date: Fri, 9 May 2025 14:22:39 +0100 Subject: mfd: max77759: Add Maxim MAX77759 core driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. Fuel Gauge and TCPC have separate and independent I2C addresses, register maps, and interrupt lines and are therefore excluded from the MFD core device driver here. The GPIO and NVMEM interfaces are accessed via specific commands to the built-in microprocessor. This driver implements an API that client drivers can use for accessing those. Signed-off-by: André Draszik Acked-by: Peter Griffin Link: https://lore.kernel.org/r/20250509-max77759-mfd-v10-1-962ac15ee3ef@linaro.org Signed-off-by: Lee Jones --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 20 ++ drivers/mfd/Makefile | 1 + drivers/mfd/max77759.c | 690 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/max77759.h | 165 +++++++++++ 5 files changed, 878 insertions(+) create mode 100644 drivers/mfd/max77759.c create mode 100644 include/linux/mfd/max77759.h (limited to 'include/linux') diff --git a/MAINTAINERS b/MAINTAINERS index 8954f2e63c79..8b9994301ffd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14580,6 +14580,8 @@ M: André Draszik L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml +F: drivers/mfd/max77759.c +F: include/linux/mfd/max77759.h MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER M: Javier Martinez Canillas diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 22b936310039..96992af22565 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -943,6 +943,26 @@ config MFD_MAX77714 drivers must be enabled in order to use each functionality of the device. +config MFD_MAX77759 + tristate "Maxim Integrated MAX77759 PMIC" + depends on I2C + depends on OF + select IRQ_DOMAIN + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to add support for Maxim Integrated MAX77759. + This is a companion Power Management IC for USB Type-C applications + with Battery Charger, Fuel Gauge, temperature sensors, USB Type-C + Port Controller (TCPC), NVMEM, and additional GPIO interfaces. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + + To compile this driver as a module, choose M here: the module will be + called max77759. + config MFD_MAX77843 bool "Maxim Semiconductor MAX77843 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 948cbdf42a18..5e5cc279af60 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -169,6 +169,7 @@ obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX77705) += max77705.o obj-$(CONFIG_MFD_MAX77714) += max77714.o +obj-$(CONFIG_MFD_MAX77759) += max77759.o obj-$(CONFIG_MFD_MAX77843) += max77843.o obj-$(CONFIG_MFD_MAX8907) += max8907.o max8925-objs := max8925-core.o max8925-i2c.o diff --git a/drivers/mfd/max77759.c b/drivers/mfd/max77759.c new file mode 100644 index 000000000000..6cf6306c4a3b --- /dev/null +++ b/drivers/mfd/max77759.c @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2020 Google Inc + * Copyright 2025 Linaro Ltd. + * + * Core driver for Maxim MAX77759 companion PMIC for USB Type-C + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Chip ID as per MAX77759_PMIC_REG_PMIC_ID */ +enum { + MAX77759_CHIP_ID = 59, +}; + +enum max77759_i2c_subdev_id { + /* + * These are arbitrary and simply used to match struct + * max77759_i2c_subdev entries to the regmap pointers in struct + * max77759 during probe(). + */ + MAX77759_I2C_SUBDEV_ID_MAXQ, + MAX77759_I2C_SUBDEV_ID_CHARGER, +}; + +struct max77759_i2c_subdev { + enum max77759_i2c_subdev_id id; + const struct regmap_config *cfg; + u16 i2c_address; +}; + +static const struct regmap_range max77759_top_registers[] = { + regmap_reg_range(0x00, 0x02), /* PMIC_ID / PMIC_REVISION / OTP_REVISION */ + regmap_reg_range(0x22, 0x24), /* INTSRC / INTSRCMASK / TOPSYS_INT */ + regmap_reg_range(0x26, 0x26), /* TOPSYS_INT_MASK */ + regmap_reg_range(0x40, 0x40), /* I2C_CNFG */ + regmap_reg_range(0x50, 0x51), /* SWRESET / CONTROL_FG */ +}; + +static const struct regmap_range max77759_top_ro_registers[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x22, 0x22), +}; + +static const struct regmap_range max77759_top_volatile_registers[] = { + regmap_reg_range(0x22, 0x22), + regmap_reg_range(0x24, 0x24), +}; + +static const struct regmap_access_table max77759_top_wr_table = { + .yes_ranges = max77759_top_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_top_registers), + .no_ranges = max77759_top_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_top_ro_registers), +}; + +static const struct regmap_access_table max77759_top_rd_table = { + .yes_ranges = max77759_top_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_top_registers), +}; + +static const struct regmap_access_table max77759_top_volatile_table = { + .yes_ranges = max77759_top_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_top_volatile_registers), +}; + +static const struct regmap_config max77759_regmap_config_top = { + .name = "top", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77759_PMIC_REG_CONTROL_FG, + .wr_table = &max77759_top_wr_table, + .rd_table = &max77759_top_rd_table, + .volatile_table = &max77759_top_volatile_table, + .num_reg_defaults_raw = MAX77759_PMIC_REG_CONTROL_FG + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range max77759_maxq_registers[] = { + regmap_reg_range(0x60, 0x73), /* Device ID, Rev, INTx, STATUSx, MASKx */ + regmap_reg_range(0x81, 0xa1), /* AP_DATAOUTx */ + regmap_reg_range(0xb1, 0xd1), /* AP_DATAINx */ + regmap_reg_range(0xe0, 0xe0), /* UIC_SWRST */ +}; + +static const struct regmap_range max77759_maxq_ro_registers[] = { + regmap_reg_range(0x60, 0x63), /* Device ID, Rev */ + regmap_reg_range(0x68, 0x6f), /* STATUSx */ + regmap_reg_range(0xb1, 0xd1), +}; + +static const struct regmap_range max77759_maxq_volatile_registers[] = { + regmap_reg_range(0x64, 0x6f), /* INTx, STATUSx */ + regmap_reg_range(0xb1, 0xd1), + regmap_reg_range(0xe0, 0xe0), +}; + +static const struct regmap_access_table max77759_maxq_wr_table = { + .yes_ranges = max77759_maxq_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers), + .no_ranges = max77759_maxq_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_maxq_ro_registers), +}; + +static const struct regmap_access_table max77759_maxq_rd_table = { + .yes_ranges = max77759_maxq_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers), +}; + +static const struct regmap_access_table max77759_maxq_volatile_table = { + .yes_ranges = max77759_maxq_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_maxq_volatile_registers), +}; + +static const struct regmap_config max77759_regmap_config_maxq = { + .name = "maxq", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77759_MAXQ_REG_UIC_SWRST, + .wr_table = &max77759_maxq_wr_table, + .rd_table = &max77759_maxq_rd_table, + .volatile_table = &max77759_maxq_volatile_table, + .num_reg_defaults_raw = MAX77759_MAXQ_REG_UIC_SWRST + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range max77759_charger_registers[] = { + regmap_reg_range(0xb0, 0xcc), +}; + +static const struct regmap_range max77759_charger_ro_registers[] = { + regmap_reg_range(0xb4, 0xb8), /* INT_OK, DETAILS_0x */ +}; + +static const struct regmap_range max77759_charger_volatile_registers[] = { + regmap_reg_range(0xb0, 0xb1), /* INTx */ + regmap_reg_range(0xb4, 0xb8), +}; + +static const struct regmap_access_table max77759_charger_wr_table = { + .yes_ranges = max77759_charger_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_charger_registers), + .no_ranges = max77759_charger_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_charger_ro_registers), +}; + +static const struct regmap_access_table max77759_charger_rd_table = { + .yes_ranges = max77759_charger_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_charger_registers), +}; + +static const struct regmap_access_table max77759_charger_volatile_table = { + .yes_ranges = max77759_charger_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_charger_volatile_registers), +}; + +static const struct regmap_config max77759_regmap_config_charger = { + .name = "charger", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77759_CHGR_REG_CHG_CNFG_19, + .wr_table = &max77759_charger_wr_table, + .rd_table = &max77759_charger_rd_table, + .volatile_table = &max77759_charger_volatile_table, + .num_reg_defaults_raw = MAX77759_CHGR_REG_CHG_CNFG_19 + 1, + .cache_type = REGCACHE_FLAT, +}; + +/* + * Interrupts - with the following interrupt hierarchy: + * pmic IRQs (INTSRC) + * - MAXQ_INT: MaxQ IRQs + * - UIC_INT1 + * - APCmdResI + * - SysMsgI + * - GPIOxI + * - TOPSYS_INT: topsys + * - TOPSYS_INT + * - TSHDN_INT + * - SYSOVLO_INT + * - SYSUVLO_INT + * - FSHIP_NOT_RD + * - CHGR_INT: charger + * - CHG_INT + * - CHG_INT2 + */ +enum { + MAX77759_INT_MAXQ, + MAX77759_INT_TOPSYS, + MAX77759_INT_CHGR, +}; + +enum { + MAX77759_TOPSYS_INT_TSHDN, + MAX77759_TOPSYS_INT_SYSOVLO, + MAX77759_TOPSYS_INT_SYSUVLO, + MAX77759_TOPSYS_INT_FSHIP_NOT_RD, +}; + +enum { + MAX77759_MAXQ_INT_APCMDRESI, + MAX77759_MAXQ_INT_SYSMSGI, + MAX77759_MAXQ_INT_GPIO, + MAX77759_MAXQ_INT_UIC1, + MAX77759_MAXQ_INT_UIC2, + MAX77759_MAXQ_INT_UIC3, + MAX77759_MAXQ_INT_UIC4, +}; + +enum { + MAX77759_CHARGER_INT_1, + MAX77759_CHARGER_INT_2, +}; + +static const struct regmap_irq max77759_pmic_irqs[] = { + REGMAP_IRQ_REG(MAX77759_INT_MAXQ, 0, MAX77759_PMIC_REG_INTSRC_MAXQ), + REGMAP_IRQ_REG(MAX77759_INT_TOPSYS, 0, MAX77759_PMIC_REG_INTSRC_TOPSYS), + REGMAP_IRQ_REG(MAX77759_INT_CHGR, 0, MAX77759_PMIC_REG_INTSRC_CHGR), +}; + +static const struct regmap_irq max77759_maxq_irqs[] = { + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_APCMDRESI, 0, MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_SYSMSGI, 0, MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_GPIO, 0, GENMASK(1, 0)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC1, 0, GENMASK(5, 2)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC2, 1, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC3, 2, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC4, 3, GENMASK(7, 0)), +}; + +static const struct regmap_irq max77759_topsys_irqs[] = { + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_TSHDN, 0, MAX77759_PMIC_REG_TOPSYS_INT_TSHDN), + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSOVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO), + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSUVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO), + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_FSHIP_NOT_RD, 0, MAX77759_PMIC_REG_TOPSYS_INT_FSHIP), +}; + +static const struct regmap_irq max77759_chgr_irqs[] = { + REGMAP_IRQ_REG(MAX77759_CHARGER_INT_1, 0, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)), +}; + +static const struct regmap_irq_chip max77759_pmic_irq_chip = { + .name = "max77759-pmic", + /* INTSRC is read-only and doesn't require clearing */ + .status_base = MAX77759_PMIC_REG_INTSRC, + .mask_base = MAX77759_PMIC_REG_INTSRCMASK, + .num_regs = 1, + .irqs = max77759_pmic_irqs, + .num_irqs = ARRAY_SIZE(max77759_pmic_irqs), +}; + +/* + * We can let regmap-irq auto-ack the topsys interrupt bits as required, but + * for all others the individual drivers need to know which interrupt bit + * exactly is set inside their interrupt handlers, and therefore we can not set + * .ack_base for those. + */ +static const struct regmap_irq_chip max77759_maxq_irq_chip = { + .name = "max77759-maxq", + .domain_suffix = "MAXQ", + .status_base = MAX77759_MAXQ_REG_UIC_INT1, + .mask_base = MAX77759_MAXQ_REG_UIC_INT1_M, + .num_regs = 4, + .irqs = max77759_maxq_irqs, + .num_irqs = ARRAY_SIZE(max77759_maxq_irqs), +}; + +static const struct regmap_irq_chip max77759_topsys_irq_chip = { + .name = "max77759-topsys", + .domain_suffix = "TOPSYS", + .status_base = MAX77759_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77759_PMIC_REG_TOPSYS_INT_MASK, + .ack_base = MAX77759_PMIC_REG_TOPSYS_INT, + .num_regs = 1, + .irqs = max77759_topsys_irqs, + .num_irqs = ARRAY_SIZE(max77759_topsys_irqs), +}; + +static const struct regmap_irq_chip max77759_chrg_irq_chip = { + .name = "max77759-chgr", + .domain_suffix = "CHGR", + .status_base = MAX77759_CHGR_REG_CHG_INT, + .mask_base = MAX77759_CHGR_REG_CHG_INT_MASK, + .num_regs = 2, + .irqs = max77759_chgr_irqs, + .num_irqs = ARRAY_SIZE(max77759_chgr_irqs), +}; + +static const struct max77759_i2c_subdev max77759_i2c_subdevs[] = { + { + .id = MAX77759_I2C_SUBDEV_ID_MAXQ, + .cfg = &max77759_regmap_config_maxq, + /* I2C address is same as for sub-block 'top' */ + }, + { + .id = MAX77759_I2C_SUBDEV_ID_CHARGER, + .cfg = &max77759_regmap_config_charger, + .i2c_address = 0x69, + }, +}; + +static const struct resource max77759_gpio_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77759_MAXQ_INT_GPIO, "GPI"), +}; + +static const struct resource max77759_charger_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_1, "INT1"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"), +}; + +static const struct mfd_cell max77759_cells[] = { + MFD_CELL_OF("max77759-nvmem", NULL, NULL, 0, 0, + "maxim,max77759-nvmem"), +}; + +static const struct mfd_cell max77759_maxq_cells[] = { + MFD_CELL_OF("max77759-gpio", max77759_gpio_resources, NULL, 0, 0, + "maxim,max77759-gpio"), +}; + +static const struct mfd_cell max77759_charger_cells[] = { + MFD_CELL_RES("max77759-charger", max77759_charger_resources), +}; + +int max77759_maxq_command(struct max77759 *max77759, + const struct max77759_maxq_command *cmd, + struct max77759_maxq_response *rsp) +{ + DEFINE_FLEX(struct max77759_maxq_response, _rsp, rsp, length, 1); + struct device *dev = regmap_get_device(max77759->regmap_maxq); + static const unsigned int timeout_ms = 200; + int ret; + + if (cmd->length > MAX77759_MAXQ_OPCODE_MAXLENGTH) + return -EINVAL; + + /* + * As a convenience for API users when issuing simple commands, rsp is + * allowed to be NULL. In that case we need a temporary here to write + * the response to, as we need to verify that the command was indeed + * completed correctly. + */ + if (!rsp) + rsp = _rsp; + + if (!rsp->length || rsp->length > MAX77759_MAXQ_OPCODE_MAXLENGTH) + return -EINVAL; + + guard(mutex)(&max77759->maxq_lock); + + reinit_completion(&max77759->cmd_done); + + /* + * MaxQ latches the message when the DATAOUT32 register is written. If + * cmd->length is shorter we still need to write 0 to it. + */ + ret = regmap_bulk_write(max77759->regmap_maxq, + MAX77759_MAXQ_REG_AP_DATAOUT0, cmd->cmd, + cmd->length); + if (!ret && cmd->length < MAX77759_MAXQ_OPCODE_MAXLENGTH) + ret = regmap_write(max77759->regmap_maxq, + MAX77759_MAXQ_REG_AP_DATAOUT32, 0); + if (ret) { + dev_err(dev, "writing command failed: %d\n", ret); + return ret; + } + + /* Wait for response from MaxQ */ + if (!wait_for_completion_timeout(&max77759->cmd_done, + msecs_to_jiffies(timeout_ms))) { + dev_err(dev, "timed out waiting for response\n"); + return -ETIMEDOUT; + } + + ret = regmap_bulk_read(max77759->regmap_maxq, + MAX77759_MAXQ_REG_AP_DATAIN0, + rsp->rsp, rsp->length); + if (ret) { + dev_err(dev, "reading response failed: %d\n", ret); + return ret; + } + + /* + * As per the protocol, the first byte of the reply will match the + * request. + */ + if (cmd->cmd[0] != rsp->rsp[0]) { + dev_err(dev, "unexpected opcode response for %#.2x: %*ph\n", + cmd->cmd[0], (int)rsp->length, rsp->rsp); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(max77759_maxq_command); + +static irqreturn_t apcmdres_irq_handler(int irq, void *irq_data) +{ + struct max77759 *max77759 = irq_data; + + regmap_write(max77759->regmap_maxq, MAX77759_MAXQ_REG_UIC_INT1, + MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI); + + complete(&max77759->cmd_done); + + return IRQ_HANDLED; +} + +static int max77759_create_i2c_subdev(struct i2c_client *client, + struct max77759 *max77759, + const struct max77759_i2c_subdev *sd) +{ + struct i2c_client *sub; + struct regmap *regmap; + int ret; + + /* + * If 'sd' has an I2C address, 'sub' will be assigned a new 'dummy' + * device, otherwise use it as-is. + */ + sub = client; + if (sd->i2c_address) { + sub = devm_i2c_new_dummy_device(&client->dev, + client->adapter, + sd->i2c_address); + + if (IS_ERR(sub)) + return dev_err_probe(&client->dev, PTR_ERR(sub), + "failed to claim I2C device %s\n", + sd->cfg->name); + } + + regmap = devm_regmap_init_i2c(sub, sd->cfg); + if (IS_ERR(regmap)) + return dev_err_probe(&sub->dev, PTR_ERR(regmap), + "regmap init for '%s' failed\n", + sd->cfg->name); + + ret = regmap_attach_dev(&client->dev, regmap, sd->cfg); + if (ret) + return dev_err_probe(&client->dev, ret, + "regmap attach of '%s' failed\n", + sd->cfg->name); + + if (sd->id == MAX77759_I2C_SUBDEV_ID_MAXQ) + max77759->regmap_maxq = regmap; + else if (sd->id == MAX77759_I2C_SUBDEV_ID_CHARGER) + max77759->regmap_charger = regmap; + + return 0; +} + +static int max77759_add_chained_irq_chip(struct device *dev, + struct regmap *regmap, + int pirq, + struct regmap_irq_chip_data *parent, + const struct regmap_irq_chip *chip, + struct regmap_irq_chip_data **data) +{ + int irq, ret; + + irq = regmap_irq_get_virq(parent, pirq); + if (irq < 0) + return dev_err_probe(dev, irq, + "failed to get parent vIRQ(%d) for chip %s\n", + pirq, chip->name); + + ret = devm_regmap_add_irq_chip(dev, regmap, irq, + IRQF_ONESHOT | IRQF_SHARED, 0, chip, + data); + if (ret) + return dev_err_probe(dev, ret, "failed to add %s IRQ chip\n", + chip->name); + + return 0; +} + +static int max77759_add_chained_maxq(struct i2c_client *client, + struct max77759 *max77759, + struct regmap_irq_chip_data *parent) +{ + struct regmap_irq_chip_data *irq_chip_data; + int apcmdres_irq; + int ret; + + ret = max77759_add_chained_irq_chip(&client->dev, + max77759->regmap_maxq, + MAX77759_INT_MAXQ, + parent, + &max77759_maxq_irq_chip, + &irq_chip_data); + if (ret) + return ret; + + init_completion(&max77759->cmd_done); + apcmdres_irq = regmap_irq_get_virq(irq_chip_data, + MAX77759_MAXQ_INT_APCMDRESI); + + ret = devm_request_threaded_irq(&client->dev, apcmdres_irq, + NULL, apcmdres_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(&client->dev), max77759); + if (ret) + return dev_err_probe(&client->dev, ret, + "MAX77759_MAXQ_INT_APCMDRESI failed\n"); + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + max77759_maxq_cells, + ARRAY_SIZE(max77759_maxq_cells), + NULL, 0, + regmap_irq_get_domain(irq_chip_data)); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to add child devices (MaxQ)\n"); + + return 0; +} + +static int max77759_add_chained_topsys(struct i2c_client *client, + struct max77759 *max77759, + struct regmap_irq_chip_data *parent) +{ + struct regmap_irq_chip_data *irq_chip_data; + int ret; + + ret = max77759_add_chained_irq_chip(&client->dev, + max77759->regmap_top, + MAX77759_INT_TOPSYS, + parent, + &max77759_topsys_irq_chip, + &irq_chip_data); + if (ret) + return ret; + + return 0; +} + +static int max77759_add_chained_charger(struct i2c_client *client, + struct max77759 *max77759, + struct regmap_irq_chip_data *parent) +{ + struct regmap_irq_chip_data *irq_chip_data; + int ret; + + ret = max77759_add_chained_irq_chip(&client->dev, + max77759->regmap_charger, + MAX77759_INT_CHGR, + parent, + &max77759_chrg_irq_chip, + &irq_chip_data); + if (ret) + return ret; + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + max77759_charger_cells, + ARRAY_SIZE(max77759_charger_cells), + NULL, 0, + regmap_irq_get_domain(irq_chip_data)); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to add child devices (charger)\n"); + + return 0; +} + +static int max77759_probe(struct i2c_client *client) +{ + struct regmap_irq_chip_data *irq_chip_data_pmic; + struct irq_data *irq_data; + struct max77759 *max77759; + unsigned long irq_flags; + unsigned int pmic_id; + int ret; + + max77759 = devm_kzalloc(&client->dev, sizeof(*max77759), GFP_KERNEL); + if (!max77759) + return -ENOMEM; + + i2c_set_clientdata(client, max77759); + + max77759->regmap_top = devm_regmap_init_i2c(client, + &max77759_regmap_config_top); + if (IS_ERR(max77759->regmap_top)) + return dev_err_probe(&client->dev, PTR_ERR(max77759->regmap_top), + "regmap init for '%s' failed\n", + max77759_regmap_config_top.name); + + ret = regmap_read(max77759->regmap_top, + MAX77759_PMIC_REG_PMIC_ID, &pmic_id); + if (ret) + return dev_err_probe(&client->dev, ret, + "unable to read device ID\n"); + + if (pmic_id != MAX77759_CHIP_ID) + return dev_err_probe(&client->dev, -ENODEV, + "unsupported device ID %#.2x (%d)\n", + pmic_id, pmic_id); + + ret = devm_mutex_init(&client->dev, &max77759->maxq_lock); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(max77759_i2c_subdevs); i++) { + ret = max77759_create_i2c_subdev(client, max77759, + &max77759_i2c_subdevs[i]); + if (ret) + return ret; + } + + irq_data = irq_get_irq_data(client->irq); + if (!irq_data) + return dev_err_probe(&client->dev, -EINVAL, + "invalid IRQ: %d\n", client->irq); + + irq_flags = IRQF_ONESHOT | IRQF_SHARED; + irq_flags |= irqd_get_trigger_type(irq_data); + + ret = devm_regmap_add_irq_chip(&client->dev, max77759->regmap_top, + client->irq, irq_flags, 0, + &max77759_pmic_irq_chip, + &irq_chip_data_pmic); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to add IRQ chip '%s'\n", + max77759_pmic_irq_chip.name); + + ret = max77759_add_chained_maxq(client, max77759, irq_chip_data_pmic); + if (ret) + return ret; + + ret = max77759_add_chained_topsys(client, max77759, irq_chip_data_pmic); + if (ret) + return ret; + + ret = max77759_add_chained_charger(client, max77759, irq_chip_data_pmic); + if (ret) + return ret; + + return devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + max77759_cells, ARRAY_SIZE(max77759_cells), + NULL, 0, + regmap_irq_get_domain(irq_chip_data_pmic)); +} + +static const struct i2c_device_id max77759_i2c_id[] = { + { "max77759" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77759_i2c_id); + +static const struct of_device_id max77759_of_id[] = { + { .compatible = "maxim,max77759", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77759_of_id); + +static struct i2c_driver max77759_i2c_driver = { + .driver = { + .name = "max77759", + .of_match_table = max77759_of_id, + }, + .probe = max77759_probe, + .id_table = max77759_i2c_id, +}; +module_i2c_driver(max77759_i2c_driver); + +MODULE_AUTHOR("André Draszik "); +MODULE_DESCRIPTION("Maxim MAX77759 core driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max77759.h b/include/linux/mfd/max77759.h new file mode 100644 index 000000000000..c6face34e385 --- /dev/null +++ b/include/linux/mfd/max77759.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2020 Google Inc. + * Copyright 2025 Linaro Ltd. + * + * Maxim MAX77759 core driver + */ + +#ifndef __LINUX_MFD_MAX77759_H +#define __LINUX_MFD_MAX77759_H + +#include +#include +#include + +#define MAX77759_PMIC_REG_PMIC_ID 0x00 +#define MAX77759_PMIC_REG_PMIC_REVISION 0x01 +#define MAX77759_PMIC_REG_OTP_REVISION 0x02 +#define MAX77759_PMIC_REG_INTSRC 0x22 +#define MAX77759_PMIC_REG_INTSRCMASK 0x23 +#define MAX77759_PMIC_REG_INTSRC_MAXQ BIT(3) +#define MAX77759_PMIC_REG_INTSRC_TOPSYS BIT(1) +#define MAX77759_PMIC_REG_INTSRC_CHGR BIT(0) +#define MAX77759_PMIC_REG_TOPSYS_INT 0x24 +#define MAX77759_PMIC_REG_TOPSYS_INT_MASK 0x26 +#define MAX77759_PMIC_REG_TOPSYS_INT_TSHDN BIT(6) +#define MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO BIT(5) +#define MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO BIT(4) +#define MAX77759_PMIC_REG_TOPSYS_INT_FSHIP BIT(0) +#define MAX77759_PMIC_REG_I2C_CNFG 0x40 +#define MAX77759_PMIC_REG_SWRESET 0x50 +#define MAX77759_PMIC_REG_CONTROL_FG 0x51 + +#define MAX77759_MAXQ_REG_UIC_INT1 0x64 +#define MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI BIT(7) +#define MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI BIT(6) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIO6I BIT(1) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIO5I BIT(0) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, en) (((en) & 1) << (offs)) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(offs) \ + MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, ~0) +#define MAX77759_MAXQ_REG_UIC_INT2 0x65 +#define MAX77759_MAXQ_REG_UIC_INT3 0x66 +#define MAX77759_MAXQ_REG_UIC_INT4 0x67 +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS1 0x68 +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS2 0x69 +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS3 0x6a +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS4 0x6b +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS5 0x6c +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS6 0x6d +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS7 0x6f +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS8 0x6f +#define MAX77759_MAXQ_REG_UIC_INT1_M 0x70 +#define MAX77759_MAXQ_REG_UIC_INT2_M 0x71 +#define MAX77759_MAXQ_REG_UIC_INT3_M 0x72 +#define MAX77759_MAXQ_REG_UIC_INT4_M 0x73 +#define MAX77759_MAXQ_REG_AP_DATAOUT0 0x81 +#define MAX77759_MAXQ_REG_AP_DATAOUT32 0xa1 +#define MAX77759_MAXQ_REG_AP_DATAIN0 0xb1 +#define MAX77759_MAXQ_REG_UIC_SWRST 0xe0 + +#define MAX77759_CHGR_REG_CHG_INT 0xb0 +#define MAX77759_CHGR_REG_CHG_INT2 0xb1 +#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2 +#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3 +#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4 +#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5 +#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6 +#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7 +#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8 +#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9 +#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba +#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb +#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc +#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd +#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe +#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf +#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0 +#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1 +#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2 +#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3 +#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4 +#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5 +#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6 +#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7 +#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8 +#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9 +#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca +#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb +#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc + +/* MaxQ opcodes for max77759_maxq_command() */ +#define MAX77759_MAXQ_OPCODE_MAXLENGTH (MAX77759_MAXQ_REG_AP_DATAOUT32 - \ + MAX77759_MAXQ_REG_AP_DATAOUT0 + \ + 1) + +#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ 0x21 +#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE 0x22 +#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ 0x23 +#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE 0x24 +#define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81 +#define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82 + +/** + * struct max77759 - core max77759 internal data structure + * + * @regmap_top: Regmap for accessing TOP registers + * @maxq_lock: Lock for serializing access to MaxQ + * @regmap_maxq: Regmap for accessing MaxQ registers + * @cmd_done: Used to signal completion of a MaxQ command + * @regmap_charger: Regmap for accessing charger registers + * + * The MAX77759 comprises several sub-blocks, namely TOP, MaxQ, Charger, + * Fuel Gauge, and TCPCI. + */ +struct max77759 { + struct regmap *regmap_top; + + /* This protects MaxQ commands - only one can be active */ + struct mutex maxq_lock; + struct regmap *regmap_maxq; + struct completion cmd_done; + + struct regmap *regmap_charger; +}; + +/** + * struct max77759_maxq_command - structure containing the MaxQ command to + * send + * + * @length: The number of bytes to send. + * @cmd: The data to send. + */ +struct max77759_maxq_command { + u8 length; + u8 cmd[] __counted_by(length); +}; + +/** + * struct max77759_maxq_response - structure containing the MaxQ response + * + * @length: The number of bytes to receive. + * @rsp: The data received. Must have at least @length bytes space. + */ +struct max77759_maxq_response { + u8 length; + u8 rsp[] __counted_by(length); +}; + +/** + * max77759_maxq_command() - issue a MaxQ command and wait for the response + * and associated data + * + * @max77759: The core max77759 device handle. + * @cmd: The command to be sent. + * @rsp: Any response data associated with the command will be copied here; + * can be %NULL if the command has no response (other than ACK). + * + * Return: 0 on success, a negative error number otherwise. + */ +int max77759_maxq_command(struct max77759 *max77759, + const struct max77759_maxq_command *cmd, + struct max77759_maxq_response *rsp); + +#endif /* __LINUX_MFD_MAX77759_H */ -- cgit v1.2.3 From 4f8ceb0302b36c5f78bcc8d0e7cfa2372fba134c Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Tue, 29 Apr 2025 14:51:28 +0200 Subject: mfd: stm32-lptimer: Add support for stm32mp25 Add support for STM32MP25 SoC. A new hardware configuration register (HWCFGR2) has been added, to gather number of capture/compare channels, autonomous mode and input capture capability. The full feature set is implemented in LPTIM1/2/3/4. LPTIM5 supports a smaller set of features. This can now be read from HWCFGR registers. Add new registers to the stm32-lptimer.h: CCMR1, CCR2, HWCFGR1/2 and VERR. Update the stm32_lptimer data struct so signal the number of capture/compare channels to the child devices. Also Remove some unused bit masks (CMPOK_ARROK / CMPOKCF_ARROKCF). Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20250429125133.1574167-3-fabrice.gasnier@foss.st.com Signed-off-by: Lee Jones --- drivers/mfd/stm32-lptimer.c | 33 ++++++++++++++++++++++++++++++++- include/linux/mfd/stm32-lptimer.h | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/mfd/stm32-lptimer.c b/drivers/mfd/stm32-lptimer.c index b2704a9809c7..09073dbc9c80 100644 --- a/drivers/mfd/stm32-lptimer.c +++ b/drivers/mfd/stm32-lptimer.c @@ -6,6 +6,7 @@ * Inspired by Benjamin Gaignard's stm32-timers driver */ +#include #include #include #include @@ -49,6 +50,36 @@ static int stm32_lptimer_detect_encoder(struct stm32_lptimer *ddata) return 0; } +static int stm32_lptimer_detect_hwcfgr(struct stm32_lptimer *ddata) +{ + u32 val; + int ret; + + ret = regmap_read(ddata->regmap, STM32_LPTIM_VERR, &ddata->version); + if (ret) + return ret; + + /* Try to guess parameters from HWCFGR: e.g. encoder mode (STM32MP15) */ + ret = regmap_read(ddata->regmap, STM32_LPTIM_HWCFGR1, &val); + if (ret) + return ret; + + /* Fallback to legacy init if HWCFGR isn't present */ + if (!val) + return stm32_lptimer_detect_encoder(ddata); + + ddata->has_encoder = FIELD_GET(STM32_LPTIM_HWCFGR1_ENCODER, val); + + ret = regmap_read(ddata->regmap, STM32_LPTIM_HWCFGR2, &val); + if (ret) + return ret; + + /* Number of capture/compare channels */ + ddata->num_cc_chans = FIELD_GET(STM32_LPTIM_HWCFGR2_CHAN_NUM, val); + + return 0; +} + static int stm32_lptimer_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -73,7 +104,7 @@ static int stm32_lptimer_probe(struct platform_device *pdev) if (IS_ERR(ddata->clk)) return PTR_ERR(ddata->clk); - ret = stm32_lptimer_detect_encoder(ddata); + ret = stm32_lptimer_detect_hwcfgr(ddata); if (ret) return ret; diff --git a/include/linux/mfd/stm32-lptimer.h b/include/linux/mfd/stm32-lptimer.h index 06d3f11dc3c9..a592c8dc716d 100644 --- a/include/linux/mfd/stm32-lptimer.h +++ b/include/linux/mfd/stm32-lptimer.h @@ -17,20 +17,30 @@ #define STM32_LPTIM_IER 0x08 /* Interrupt Enable Reg */ #define STM32_LPTIM_CFGR 0x0C /* Configuration Reg */ #define STM32_LPTIM_CR 0x10 /* Control Reg */ -#define STM32_LPTIM_CMP 0x14 /* Compare Reg */ +#define STM32_LPTIM_CMP 0x14 /* Compare Reg (MP25 CCR1) */ #define STM32_LPTIM_ARR 0x18 /* Autoreload Reg */ #define STM32_LPTIM_CNT 0x1C /* Counter Reg */ +#define STM32_LPTIM_CCMR1 0x2C /* Capture/Compare Mode MP25 */ +#define STM32_LPTIM_CCR2 0x34 /* Compare Reg2 MP25 */ + +#define STM32_LPTIM_HWCFGR2 0x3EC /* Hardware configuration register 2 - MP25 */ +#define STM32_LPTIM_HWCFGR1 0x3F0 /* Hardware configuration register 1 - MP15 */ +#define STM32_LPTIM_VERR 0x3F4 /* Version identification register - MP15 */ /* STM32_LPTIM_ISR - bit fields */ +#define STM32_LPTIM_DIEROK_ARROK (BIT(24) | BIT(4)) /* MP25 */ +#define STM32_LPTIM_CMP2_ARROK (BIT(19) | BIT(4)) #define STM32_LPTIM_CMPOK_ARROK GENMASK(4, 3) #define STM32_LPTIM_ARROK BIT(4) #define STM32_LPTIM_CMPOK BIT(3) /* STM32_LPTIM_ICR - bit fields */ -#define STM32_LPTIM_ARRMCF BIT(1) +#define STM32_LPTIM_DIEROKCF_ARROKCF (BIT(24) | BIT(4)) /* MP25 */ +#define STM32_LPTIM_CMP2OKCF_ARROKCF (BIT(19) | BIT(4)) #define STM32_LPTIM_CMPOKCF_ARROKCF GENMASK(4, 3) +#define STM32_LPTIM_ARRMCF BIT(1) -/* STM32_LPTIM_IER - bit flieds */ +/* STM32_LPTIM_IER - bit fields */ #define STM32_LPTIM_ARRMIE BIT(1) /* STM32_LPTIM_CR - bit fields */ @@ -53,16 +63,37 @@ /* STM32_LPTIM_ARR */ #define STM32_LPTIM_MAX_ARR 0xFFFF +/* STM32_LPTIM_CCMR1 */ +#define STM32_LPTIM_CC2P GENMASK(19, 18) +#define STM32_LPTIM_CC2E BIT(17) +#define STM32_LPTIM_CC2SEL BIT(16) +#define STM32_LPTIM_CC1P GENMASK(3, 2) +#define STM32_LPTIM_CC1E BIT(1) +#define STM32_LPTIM_CC1SEL BIT(0) + +/* STM32_LPTIM_HWCFGR1 */ +#define STM32_LPTIM_HWCFGR1_ENCODER BIT(16) + +/* STM32_LPTIM_HWCFGR2 */ +#define STM32_LPTIM_HWCFGR2_CHAN_NUM GENMASK(3, 0) + +/* STM32_LPTIM_VERR */ +#define STM32_LPTIM_VERR_23 0x23 /* STM32MP25 */ + /** * struct stm32_lptimer - STM32 Low-Power Timer data assigned by parent device * @clk: clock reference for this instance * @regmap: register map reference for this instance * @has_encoder: indicates this Low-Power Timer supports encoder mode + * @num_cc_chans: indicates the number of capture/compare channels + * @version: indicates the major and minor revision of the controller */ struct stm32_lptimer { struct clk *clk; struct regmap *regmap; bool has_encoder; + unsigned int num_cc_chans; + u32 version; }; #endif -- cgit v1.2.3 From 6adf48a3aa316ce360e02dd10222e96da9a0eff5 Mon Sep 17 00:00:00 2001 From: Artur Weber Date: Thu, 15 May 2025 16:16:30 +0200 Subject: mfd: bcm590xx: Add support for multiple device types + BCM59054 compatible The BCM59054 is another chip from the BCM590xx line of PMUs, commonly used on devices with the BCM21664/BCM23550 chipsets. Prepare the BCM590xx driver for supporting other devices by adding the PMUID register values for supported chip types and store them in the MFD data struct as "pmu_id". (These will be checked against the actual PMUID register values in a later commit.) Then, add a DT compatible for the BCM59054, and provide the PMU ID as OF match data. Signed-off-by: Artur Weber Reviewed-by: Stanislav Jakubek Link: https://lore.kernel.org/r/20250515-bcm59054-v9-3-14ba0ea2ea5b@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/bcm590xx.c | 12 +++++++++++- include/linux/mfd/bcm590xx.h | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/mfd/bcm590xx.c b/drivers/mfd/bcm590xx.c index 8b56786d85d0..4620eed0066f 100644 --- a/drivers/mfd/bcm590xx.c +++ b/drivers/mfd/bcm590xx.c @@ -50,6 +50,8 @@ static int bcm590xx_i2c_probe(struct i2c_client *i2c_pri) bcm590xx->dev = &i2c_pri->dev; bcm590xx->i2c_pri = i2c_pri; + bcm590xx->pmu_id = (uintptr_t) of_device_get_match_data(bcm590xx->dev); + bcm590xx->regmap_pri = devm_regmap_init_i2c(i2c_pri, &bcm590xx_regmap_config_pri); if (IS_ERR(bcm590xx->regmap_pri)) { @@ -91,12 +93,20 @@ err: } static const struct of_device_id bcm590xx_of_match[] = { - { .compatible = "brcm,bcm59056" }, + { + .compatible = "brcm,bcm59054", + .data = (void *)BCM590XX_PMUID_BCM59054, + }, + { + .compatible = "brcm,bcm59056", + .data = (void *)BCM590XX_PMUID_BCM59056, + }, { } }; MODULE_DEVICE_TABLE(of, bcm590xx_of_match); static const struct i2c_device_id bcm590xx_i2c_id[] = { + { "bcm59054" }, { "bcm59056" }, { } }; diff --git a/include/linux/mfd/bcm590xx.h b/include/linux/mfd/bcm590xx.h index 6b8791da6119..76c30e629333 100644 --- a/include/linux/mfd/bcm590xx.h +++ b/include/linux/mfd/bcm590xx.h @@ -13,6 +13,10 @@ #include #include +/* PMU ID register values; also used as device type */ +#define BCM590XX_PMUID_BCM59054 0x54 +#define BCM590XX_PMUID_BCM59056 0x56 + /* max register address */ #define BCM590XX_MAX_REGISTER_PRI 0xe7 #define BCM590XX_MAX_REGISTER_SEC 0xf0 @@ -24,6 +28,9 @@ struct bcm590xx { struct regmap *regmap_pri; struct regmap *regmap_sec; unsigned int id; + + /* PMU ID value; also used as device type */ + u8 pmu_id; }; #endif /* __LINUX_MFD_BCM590XX_H */ -- cgit v1.2.3 From d310cdbb4ee6285f374d4dfc32173c35f8a2273e Mon Sep 17 00:00:00 2001 From: Artur Weber Date: Thu, 15 May 2025 16:16:31 +0200 Subject: mfd: bcm590xx: Add PMU ID/revision parsing function The BCM590xx PMUs have two I2C registers for reading the PMU ID and revision. The revision is useful for subdevice drivers, since different revisions may have slight differences in behavior (for example - BCM59054 has different regulator configurations for revision A0 and A1). Check the PMU ID register and make sure it matches the DT compatible. Fetch the digital and analog revision from the PMUREV register so that it can be used in subdevice drivers. Also add some known revision values to bcm590xx.h, for convenience when writing subdevice drivers. Signed-off-by: Artur Weber Reviewed-by: Stanislav Jakubek Link: https://lore.kernel.org/r/20250515-bcm59054-v9-4-14ba0ea2ea5b@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/bcm590xx.c | 54 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/bcm590xx.h | 14 ++++++++++++ 2 files changed, 68 insertions(+) (limited to 'include/linux') diff --git a/drivers/mfd/bcm590xx.c b/drivers/mfd/bcm590xx.c index 4620eed0066f..5a8456bbd63f 100644 --- a/drivers/mfd/bcm590xx.c +++ b/drivers/mfd/bcm590xx.c @@ -17,6 +17,15 @@ #include #include +/* Under primary I2C address: */ +#define BCM590XX_REG_PMUID 0x1e + +#define BCM590XX_REG_PMUREV 0x1f +#define BCM590XX_PMUREV_DIG_MASK 0xF +#define BCM590XX_PMUREV_DIG_SHIFT 0 +#define BCM590XX_PMUREV_ANA_MASK 0xF0 +#define BCM590XX_PMUREV_ANA_SHIFT 4 + static const struct mfd_cell bcm590xx_devs[] = { { .name = "bcm590xx-vregs", @@ -37,6 +46,47 @@ static const struct regmap_config bcm590xx_regmap_config_sec = { .cache_type = REGCACHE_MAPLE, }; +/* Map PMU ID value to model name string */ +static const char * const bcm590xx_names[] = { + [BCM590XX_PMUID_BCM59054] = "BCM59054", + [BCM590XX_PMUID_BCM59056] = "BCM59056", +}; + +static int bcm590xx_parse_version(struct bcm590xx *bcm590xx) +{ + unsigned int id, rev; + int ret; + + /* Get PMU ID and verify that it matches compatible */ + ret = regmap_read(bcm590xx->regmap_pri, BCM590XX_REG_PMUID, &id); + if (ret) { + dev_err(bcm590xx->dev, "failed to read PMU ID: %d\n", ret); + return ret; + } + + if (id != bcm590xx->pmu_id) { + dev_err(bcm590xx->dev, "Incorrect ID for %s: expected %x, got %x.\n", + bcm590xx_names[bcm590xx->pmu_id], bcm590xx->pmu_id, id); + return -ENODEV; + } + + /* Get PMU revision and store it in the info struct */ + ret = regmap_read(bcm590xx->regmap_pri, BCM590XX_REG_PMUREV, &rev); + if (ret) { + dev_err(bcm590xx->dev, "failed to read PMU revision: %d\n", ret); + return ret; + } + + bcm590xx->rev_digital = (rev & BCM590XX_PMUREV_DIG_MASK) >> BCM590XX_PMUREV_DIG_SHIFT; + + bcm590xx->rev_analog = (rev & BCM590XX_PMUREV_ANA_MASK) >> BCM590XX_PMUREV_ANA_SHIFT; + + dev_dbg(bcm590xx->dev, "PMU ID 0x%x (%s), revision: digital %d, analog %d", + id, bcm590xx_names[id], bcm590xx->rev_digital, bcm590xx->rev_analog); + + return 0; +} + static int bcm590xx_i2c_probe(struct i2c_client *i2c_pri) { struct bcm590xx *bcm590xx; @@ -78,6 +128,10 @@ static int bcm590xx_i2c_probe(struct i2c_client *i2c_pri) goto err; } + ret = bcm590xx_parse_version(bcm590xx); + if (ret) + goto err; + ret = devm_mfd_add_devices(&i2c_pri->dev, -1, bcm590xx_devs, ARRAY_SIZE(bcm590xx_devs), NULL, 0, NULL); if (ret < 0) { diff --git a/include/linux/mfd/bcm590xx.h b/include/linux/mfd/bcm590xx.h index 76c30e629333..a54c50b2d2c8 100644 --- a/include/linux/mfd/bcm590xx.h +++ b/include/linux/mfd/bcm590xx.h @@ -17,6 +17,16 @@ #define BCM590XX_PMUID_BCM59054 0x54 #define BCM590XX_PMUID_BCM59056 0x56 +/* Known chip revision IDs */ +#define BCM59054_REV_DIGITAL_A1 1 +#define BCM59054_REV_ANALOG_A1 2 + +#define BCM59056_REV_DIGITAL_A0 1 +#define BCM59056_REV_ANALOG_A0 1 + +#define BCM59056_REV_DIGITAL_B0 2 +#define BCM59056_REV_ANALOG_B0 2 + /* max register address */ #define BCM590XX_MAX_REGISTER_PRI 0xe7 #define BCM590XX_MAX_REGISTER_SEC 0xf0 @@ -31,6 +41,10 @@ struct bcm590xx { /* PMU ID value; also used as device type */ u8 pmu_id; + + /* Chip revision, read from PMUREV reg */ + u8 rev_digital; + u8 rev_analog; }; #endif /* __LINUX_MFD_BCM590XX_H */ -- cgit v1.2.3 From 75dc12b4450269821fca4c8634f5185d28cf2117 Mon Sep 17 00:00:00 2001 From: Artur Weber Date: Thu, 15 May 2025 16:16:33 +0200 Subject: regulator: bcm590xx: Store regulator descriptions in table Instead of filling in the regulator description programatically, store the data in a struct. This will make it a bit nicer to introduce support for other BCM590xx chips besides the BCM59056. To do this, add a new struct type, bcm590xx_reg_data, to store all of the necessary information. Drop the old IS_LDO, IS_GPLDO... macros in favor of the "type" field in this struct. Adapt the old bcm590xx_reg struct to the new types. Signed-off-by: Artur Weber Reviewed-by: Stanislav Jakubek Reviewed-by: Mark Brown Link: https://lore.kernel.org/r/20250515-bcm59054-v9-6-14ba0ea2ea5b@gmail.com Signed-off-by: Lee Jones --- drivers/regulator/bcm590xx-regulator.c | 473 ++++++++++++++++++++++----------- include/linux/mfd/bcm590xx.h | 6 + 2 files changed, 322 insertions(+), 157 deletions(-) (limited to 'include/linux') diff --git a/drivers/regulator/bcm590xx-regulator.c b/drivers/regulator/bcm590xx-regulator.c index a17311fa30e4..c2427ea16662 100644 --- a/drivers/regulator/bcm590xx-regulator.c +++ b/drivers/regulator/bcm590xx-regulator.c @@ -20,6 +20,18 @@ /* I2C slave 0 registers */ #define BCM590XX_RFLDOPMCTRL1 0x60 +#define BCM590XX_CAMLDO1PMCTRL1 0x62 +#define BCM590XX_CAMLDO2PMCTRL1 0x64 +#define BCM590XX_SIMLDO1PMCTRL1 0x66 +#define BCM590XX_SIMLDO2PMCTRL1 0x68 +#define BCM590XX_SDLDOPMCTRL1 0x6a +#define BCM590XX_SDXLDOPMCTRL1 0x6c +#define BCM590XX_MMCLDO1PMCTRL1 0x6e +#define BCM590XX_MMCLDO2PMCTRL1 0x70 +#define BCM590XX_AUDLDOPMCTRL1 0x72 +#define BCM590XX_MICLDOPMCTRL1 0x74 +#define BCM590XX_USBLDOPMCTRL1 0x76 +#define BCM590XX_VIBLDOPMCTRL1 0x78 #define BCM590XX_IOSR1PMCTRL1 0x7a #define BCM590XX_IOSR2PMCTRL1 0x7c #define BCM590XX_CSRPMCTRL1 0x7e @@ -28,7 +40,25 @@ #define BCM590XX_MSRPMCTRL1 0x8a #define BCM590XX_VSRPMCTRL1 0x8e #define BCM590XX_RFLDOCTRL 0x96 +#define BCM590XX_CAMLDO1CTRL 0x97 +#define BCM590XX_CAMLDO2CTRL 0x98 +#define BCM590XX_SIMLDO1CTRL 0x99 +#define BCM590XX_SIMLDO2CTRL 0x9a +#define BCM590XX_SDLDOCTRL 0x9b +#define BCM590XX_SDXLDOCTRL 0x9c +#define BCM590XX_MMCLDO1CTRL 0x9d +#define BCM590XX_MMCLDO2CTRL 0x9e +#define BCM590XX_AUDLDOCTRL 0x9f +#define BCM590XX_MICLDOCTRL 0xa0 +#define BCM590XX_USBLDOCTRL 0xa1 +#define BCM590XX_VIBLDOCTRL 0xa2 #define BCM590XX_CSRVOUT1 0xc0 +#define BCM590XX_IOSR1VOUT1 0xc3 +#define BCM590XX_IOSR2VOUT1 0xc6 +#define BCM590XX_MSRVOUT1 0xc9 +#define BCM590XX_SDSR1VOUT1 0xcc +#define BCM590XX_SDSR2VOUT1 0xcf +#define BCM590XX_VSRVOUT1 0xd2 /* I2C slave 1 registers */ #define BCM590XX_GPLDO5PMCTRL1 0x16 @@ -94,11 +124,6 @@ #define BCM590XX_NUM_REGS 27 -#define BCM590XX_REG_IS_LDO(n) (n < BCM590XX_REG_CSR) -#define BCM590XX_REG_IS_GPLDO(n) \ - ((n > BCM590XX_REG_VSR) && (n < BCM590XX_REG_VBUS)) -#define BCM590XX_REG_IS_VBUS(n) (n == BCM590XX_REG_VBUS) - /* LDO group A: supported voltages in microvolts */ static const unsigned int ldo_a_table[] = { 1200000, 1800000, 2500000, 2700000, 2800000, @@ -111,10 +136,6 @@ static const unsigned int ldo_c_table[] = { 2900000, 3000000, 3300000, }; -static const unsigned int ldo_vbus[] = { - 5000000, -}; - /* DCDC group CSR: supported voltages in microvolts */ static const struct linear_range dcdc_csr_ranges[] = { REGULATOR_LINEAR_RANGE(860000, 2, 50, 10000), @@ -137,115 +158,25 @@ static const struct linear_range dcdc_sdsr1_ranges[] = { REGULATOR_LINEAR_RANGE(900000, 52, 63, 0), }; -struct bcm590xx_info { - const char *name; - const char *vin_name; - u8 n_voltages; - const unsigned int *volt_table; - u8 n_linear_ranges; - const struct linear_range *linear_ranges; +enum bcm590xx_reg_type { + BCM590XX_REG_TYPE_LDO, + BCM590XX_REG_TYPE_GPLDO, + BCM590XX_REG_TYPE_SR, + BCM590XX_REG_TYPE_VBUS }; -#define BCM590XX_REG_TABLE(_name, _table) \ - { \ - .name = #_name, \ - .n_voltages = ARRAY_SIZE(_table), \ - .volt_table = _table, \ - } - -#define BCM590XX_REG_RANGES(_name, _ranges) \ - { \ - .name = #_name, \ - .n_voltages = 64, \ - .n_linear_ranges = ARRAY_SIZE(_ranges), \ - .linear_ranges = _ranges, \ - } - -static struct bcm590xx_info bcm590xx_regs[] = { - BCM590XX_REG_TABLE(rfldo, ldo_a_table), - BCM590XX_REG_TABLE(camldo1, ldo_c_table), - BCM590XX_REG_TABLE(camldo2, ldo_c_table), - BCM590XX_REG_TABLE(simldo1, ldo_a_table), - BCM590XX_REG_TABLE(simldo2, ldo_a_table), - BCM590XX_REG_TABLE(sdldo, ldo_c_table), - BCM590XX_REG_TABLE(sdxldo, ldo_a_table), - BCM590XX_REG_TABLE(mmcldo1, ldo_a_table), - BCM590XX_REG_TABLE(mmcldo2, ldo_a_table), - BCM590XX_REG_TABLE(audldo, ldo_a_table), - BCM590XX_REG_TABLE(micldo, ldo_a_table), - BCM590XX_REG_TABLE(usbldo, ldo_a_table), - BCM590XX_REG_TABLE(vibldo, ldo_c_table), - BCM590XX_REG_RANGES(csr, dcdc_csr_ranges), - BCM590XX_REG_RANGES(iosr1, dcdc_iosr1_ranges), - BCM590XX_REG_RANGES(iosr2, dcdc_iosr1_ranges), - BCM590XX_REG_RANGES(msr, dcdc_iosr1_ranges), - BCM590XX_REG_RANGES(sdsr1, dcdc_sdsr1_ranges), - BCM590XX_REG_RANGES(sdsr2, dcdc_iosr1_ranges), - BCM590XX_REG_RANGES(vsr, dcdc_iosr1_ranges), - BCM590XX_REG_TABLE(gpldo1, ldo_a_table), - BCM590XX_REG_TABLE(gpldo2, ldo_a_table), - BCM590XX_REG_TABLE(gpldo3, ldo_a_table), - BCM590XX_REG_TABLE(gpldo4, ldo_a_table), - BCM590XX_REG_TABLE(gpldo5, ldo_a_table), - BCM590XX_REG_TABLE(gpldo6, ldo_a_table), - BCM590XX_REG_TABLE(vbus, ldo_vbus), +struct bcm590xx_reg_data { + enum bcm590xx_reg_type type; + enum bcm590xx_regmap_type regmap; + const struct regulator_desc desc; }; struct bcm590xx_reg { - struct regulator_desc *desc; struct bcm590xx *mfd; + unsigned int n_regulators; + const struct bcm590xx_reg_data *regs; }; -static int bcm590xx_get_vsel_register(int id) -{ - if (BCM590XX_REG_IS_LDO(id)) - return BCM590XX_RFLDOCTRL + id; - else if (BCM590XX_REG_IS_GPLDO(id)) - return BCM590XX_GPLDO1CTRL + id; - else - return BCM590XX_CSRVOUT1 + (id - BCM590XX_REG_CSR) * 3; -} - -static int bcm590xx_get_enable_register(int id) -{ - int reg = 0; - - if (BCM590XX_REG_IS_LDO(id)) - reg = BCM590XX_RFLDOPMCTRL1 + id * 2; - else if (BCM590XX_REG_IS_GPLDO(id)) - reg = BCM590XX_GPLDO1PMCTRL1 + id * 2; - else - switch (id) { - case BCM590XX_REG_CSR: - reg = BCM590XX_CSRPMCTRL1; - break; - case BCM590XX_REG_IOSR1: - reg = BCM590XX_IOSR1PMCTRL1; - break; - case BCM590XX_REG_IOSR2: - reg = BCM590XX_IOSR2PMCTRL1; - break; - case BCM590XX_REG_MSR: - reg = BCM590XX_MSRPMCTRL1; - break; - case BCM590XX_REG_SDSR1: - reg = BCM590XX_SDSR1PMCTRL1; - break; - case BCM590XX_REG_SDSR2: - reg = BCM590XX_SDSR2PMCTRL1; - break; - case BCM590XX_REG_VSR: - reg = BCM590XX_VSRPMCTRL1; - break; - case BCM590XX_REG_VBUS: - reg = BCM590XX_OTG_CTRL; - break; - } - - - return reg; -} - static const struct regulator_ops bcm590xx_ops_ldo = { .is_enabled = regulator_is_enabled_regmap, .enable = regulator_enable_regmap, @@ -272,73 +203,301 @@ static const struct regulator_ops bcm590xx_ops_vbus = { .disable = regulator_disable_regmap, }; +#define BCM590XX_REG_DESC(_name, _name_lower) \ + .id = BCM590XX_REG_##_name, \ + .name = #_name_lower, \ + .of_match = of_match_ptr(#_name_lower), \ + .regulators_node = of_match_ptr("regulators"), \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE \ + +#define BCM590XX_LDO_DESC(_name, _name_lower, _table) \ + BCM590XX_REG_DESC(_name, _name_lower), \ + .ops = &bcm590xx_ops_ldo, \ + .n_voltages = ARRAY_SIZE(_table), \ + .volt_table = _table, \ + .vsel_reg = BCM590XX_##_name##CTRL, \ + .vsel_mask = BCM590XX_LDO_VSEL_MASK, \ + .enable_reg = BCM590XX_##_name##PMCTRL1, \ + .enable_mask = BCM590XX_REG_ENABLE, \ + .enable_is_inverted = true + +#define BCM590XX_SR_DESC(_name, _name_lower, _ranges) \ + BCM590XX_REG_DESC(_name, _name_lower), \ + .ops = &bcm590xx_ops_dcdc, \ + .n_voltages = 64, \ + .linear_ranges = _ranges, \ + .n_linear_ranges = ARRAY_SIZE(_ranges), \ + .vsel_reg = BCM590XX_##_name##VOUT1, \ + .vsel_mask = BCM590XX_SR_VSEL_MASK, \ + .enable_reg = BCM590XX_##_name##PMCTRL1, \ + .enable_mask = BCM590XX_REG_ENABLE, \ + .enable_is_inverted = true + +static const struct bcm590xx_reg_data bcm590xx_regs[BCM590XX_NUM_REGS] = { + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(RFLDO, rfldo, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(CAMLDO1, camldo1, ldo_c_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(CAMLDO2, camldo2, ldo_c_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(SIMLDO1, simldo1, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(SIMLDO2, simldo2, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(SDLDO, sdldo, ldo_c_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(SDXLDO, sdxldo, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(MMCLDO1, mmcldo1, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(MMCLDO2, mmcldo2, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(AUDLDO, audldo, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(MICLDO, micldo, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(USBLDO, usbldo, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_LDO, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_LDO_DESC(VIBLDO, vibldo, ldo_c_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(CSR, csr, dcdc_csr_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(IOSR1, iosr1, dcdc_iosr1_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(IOSR2, iosr2, dcdc_iosr1_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(MSR, msr, dcdc_iosr1_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(SDSR1, sdsr1, dcdc_sdsr1_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(SDSR2, sdsr2, dcdc_iosr1_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_SR, + .regmap = BCM590XX_REGMAP_PRI, + .desc = { + BCM590XX_SR_DESC(VSR, vsr, dcdc_iosr1_ranges), + }, + }, + + { + .type = BCM590XX_REG_TYPE_GPLDO, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_LDO_DESC(GPLDO1, gpldo1, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_GPLDO, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_LDO_DESC(GPLDO2, gpldo2, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_GPLDO, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_LDO_DESC(GPLDO3, gpldo3, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_GPLDO, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_LDO_DESC(GPLDO4, gpldo4, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_GPLDO, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_LDO_DESC(GPLDO5, gpldo5, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_GPLDO, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_LDO_DESC(GPLDO6, gpldo6, ldo_a_table), + }, + }, + + { + .type = BCM590XX_REG_TYPE_VBUS, + .regmap = BCM590XX_REGMAP_SEC, + .desc = { + BCM590XX_REG_DESC(VBUS, vbus), + .ops = &bcm590xx_ops_vbus, + .n_voltages = 1, + .fixed_uV = 5000000, + .enable_reg = BCM590XX_OTG_CTRL, + .enable_mask = BCM590XX_VBUS_ENABLE, + }, + }, +}; + static int bcm590xx_probe(struct platform_device *pdev) { struct bcm590xx *bcm590xx = dev_get_drvdata(pdev->dev.parent); struct bcm590xx_reg *pmu; + const struct bcm590xx_reg_data *info; struct regulator_config config = { }; - struct bcm590xx_info *info; struct regulator_dev *rdev; - int i; + unsigned int i; pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL); if (!pmu) return -ENOMEM; pmu->mfd = bcm590xx; + pmu->n_regulators = BCM590XX_NUM_REGS; + pmu->regs = bcm590xx_regs; platform_set_drvdata(pdev, pmu); - pmu->desc = devm_kcalloc(&pdev->dev, - BCM590XX_NUM_REGS, - sizeof(struct regulator_desc), - GFP_KERNEL); - if (!pmu->desc) - return -ENOMEM; - - info = bcm590xx_regs; - - for (i = 0; i < BCM590XX_NUM_REGS; i++, info++) { - /* Register the regulators */ - pmu->desc[i].name = info->name; - pmu->desc[i].of_match = of_match_ptr(info->name); - pmu->desc[i].regulators_node = of_match_ptr("regulators"); - pmu->desc[i].supply_name = info->vin_name; - pmu->desc[i].id = i; - pmu->desc[i].volt_table = info->volt_table; - pmu->desc[i].n_voltages = info->n_voltages; - pmu->desc[i].linear_ranges = info->linear_ranges; - pmu->desc[i].n_linear_ranges = info->n_linear_ranges; - - if ((BCM590XX_REG_IS_LDO(i)) || (BCM590XX_REG_IS_GPLDO(i))) { - pmu->desc[i].ops = &bcm590xx_ops_ldo; - pmu->desc[i].vsel_mask = BCM590XX_LDO_VSEL_MASK; - } else if (BCM590XX_REG_IS_VBUS(i)) - pmu->desc[i].ops = &bcm590xx_ops_vbus; - else { - pmu->desc[i].ops = &bcm590xx_ops_dcdc; - pmu->desc[i].vsel_mask = BCM590XX_SR_VSEL_MASK; - } - - if (BCM590XX_REG_IS_VBUS(i)) - pmu->desc[i].enable_mask = BCM590XX_VBUS_ENABLE; - else { - pmu->desc[i].vsel_reg = bcm590xx_get_vsel_register(i); - pmu->desc[i].enable_is_inverted = true; - pmu->desc[i].enable_mask = BCM590XX_REG_ENABLE; - } - pmu->desc[i].enable_reg = bcm590xx_get_enable_register(i); - pmu->desc[i].type = REGULATOR_VOLTAGE; - pmu->desc[i].owner = THIS_MODULE; + /* Register the regulators */ + for (i = 0; i < pmu->n_regulators; i++) { + info = &pmu->regs[i]; config.dev = bcm590xx->dev; config.driver_data = pmu; - if (BCM590XX_REG_IS_GPLDO(i) || BCM590XX_REG_IS_VBUS(i)) - config.regmap = bcm590xx->regmap_sec; - else + + switch (info->regmap) { + case BCM590XX_REGMAP_PRI: config.regmap = bcm590xx->regmap_pri; + break; + case BCM590XX_REGMAP_SEC: + config.regmap = bcm590xx->regmap_sec; + break; + default: + dev_err(bcm590xx->dev, + "invalid regmap for %s regulator; this is a driver bug\n", + pdev->name); + return -EINVAL; + } - rdev = devm_regulator_register(&pdev->dev, &pmu->desc[i], + rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); if (IS_ERR(rdev)) return dev_err_probe(bcm590xx->dev, PTR_ERR(rdev), diff --git a/include/linux/mfd/bcm590xx.h b/include/linux/mfd/bcm590xx.h index a54c50b2d2c8..09d4ae054242 100644 --- a/include/linux/mfd/bcm590xx.h +++ b/include/linux/mfd/bcm590xx.h @@ -27,6 +27,12 @@ #define BCM59056_REV_DIGITAL_B0 2 #define BCM59056_REV_ANALOG_B0 2 +/* regmap types */ +enum bcm590xx_regmap_type { + BCM590XX_REGMAP_PRI, + BCM590XX_REGMAP_SEC, +}; + /* max register address */ #define BCM590XX_MAX_REGISTER_PRI 0xe7 #define BCM590XX_MAX_REGISTER_SEC 0xf0 -- cgit v1.2.3