diff options
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 37 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 4 | ||||
-rw-r--r-- | drivers/mfd/altera-sysmgr.c | 211 | ||||
-rw-r--r-- | drivers/mfd/max77650.c | 232 | ||||
-rw-r--r-- | drivers/mfd/mfd-core.c | 13 | ||||
-rw-r--r-- | drivers/mfd/stmfx.c | 545 | ||||
-rw-r--r-- | drivers/mfd/ti-lmu.c | 11 |
7 files changed, 1041 insertions, 12 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 26ad6468d13a..9d53480352a6 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -29,6 +29,16 @@ config MFD_ALTERA_A10SR accessing the external gpio extender (LEDs & buttons) and power supply alarms (hwmon). +config MFD_ALTERA_SYSMGR + bool "Altera SOCFPGA System Manager" + depends on (ARCH_SOCFPGA || ARCH_STRATIX10) && OF + select MFD_SYSCON + help + Select this to get System Manager support for all Altera branded + SOCFPGAs. The SOCFPGA System Manager handles all SOCFPGAs by + using regmap_mmio accesses for ARM32 parts and SMC calls to + EL3 for ARM64 parts. + config MFD_ACT8945A tristate "Active-semi ACT8945A" select MFD_CORE @@ -733,6 +743,20 @@ config MFD_MAX77620 provides common support for accessing the device; additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX77650 + tristate "Maxim MAX77650/77651 PMIC Support" + depends on I2C + depends on OF || COMPILE_TEST + select MFD_CORE + select REGMAP_I2C + help + Say Y here to add support for Maxim Semiconductor MAX77650 and + MAX77651 Power Management ICs. This is the core multifunction + driver for interacting with the device. The module name is + 'max77650'. Additional drivers can be enabled in order to use + the following functionalities of the device: GPIO, regulator, + charger, LED, onkey. + config MFD_MAX77686 tristate "Maxim Semiconductor MAX77686/802 PMIC Support" depends on I2C @@ -1907,6 +1931,19 @@ config MFD_STPMIC1 To compile this driver as a module, choose M here: the module will be called stpmic1. +config MFD_STMFX + tristate "Support for STMicroelectronics Multi-Function eXpander (STMFX)" + depends on I2C + depends on OF || COMPILE_TEST + select MFD_CORE + select REGMAP_I2C + help + Support for the STMicroelectronics Multi-Function eXpander. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device. + menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b4569ed7f3f3..52b1a90ff515 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -155,6 +155,7 @@ obj-$(CONFIG_MFD_DA9150) += da9150-core.o obj-$(CONFIG_MFD_MAX14577) += max14577.o obj-$(CONFIG_MFD_MAX77620) += max77620.o +obj-$(CONFIG_MFD_MAX77650) += max77650.o obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX77843) += max77843.o @@ -237,6 +238,7 @@ obj-$(CONFIG_INTEL_SOC_PMIC_CHTDC_TI) += intel_soc_pmic_chtdc_ti.o obj-$(CONFIG_MFD_MT6397) += mt6397-core.o obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o +obj-$(CONFIG_MFD_ALTERA_SYSMGR) += altera-sysmgr.o obj-$(CONFIG_MFD_STPMIC1) += stpmic1.o obj-$(CONFIG_MFD_SUN4I_GPADC) += sun4i-gpadc.o @@ -246,4 +248,4 @@ obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o - +obj-$(CONFIG_MFD_STMFX) += stmfx.o diff --git a/drivers/mfd/altera-sysmgr.c b/drivers/mfd/altera-sysmgr.c new file mode 100644 index 000000000000..8976f82785bb --- /dev/null +++ b/drivers/mfd/altera-sysmgr.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019, Intel Corporation. + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Linaro Ltd. + * + * Based on syscon driver. + */ + +#include <linux/arm-smccc.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mfd/altera-sysmgr.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/** + * struct altr_sysmgr - Altera SOCFPGA System Manager + * @regmap: the regmap used for System Manager accesses. + * @base : the base address for the System Manager + */ +struct altr_sysmgr { + struct regmap *regmap; + resource_size_t *base; +}; + +static struct platform_driver altr_sysmgr_driver; + +/** + * s10_protected_reg_write + * Write to a protected SMC register. + * @base: Base address of System Manager + * @reg: Address offset of register + * @val: Value to write + * Return: INTEL_SIP_SMC_STATUS_OK (0) on success + * INTEL_SIP_SMC_REG_ERROR on error + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION if not supported + */ +static int s10_protected_reg_write(void *base, + unsigned int reg, unsigned int val) +{ + struct arm_smccc_res result; + unsigned long sysmgr_base = (unsigned long)base; + + arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE, sysmgr_base + reg, + val, 0, 0, 0, 0, 0, &result); + + return (int)result.a0; +} + +/** + * s10_protected_reg_read + * Read the status of a protected SMC register + * @base: Base address of System Manager. + * @reg: Address of register + * @val: Value read. + * Return: INTEL_SIP_SMC_STATUS_OK (0) on success + * INTEL_SIP_SMC_REG_ERROR on error + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION if not supported + */ +static int s10_protected_reg_read(void *base, + unsigned int reg, unsigned int *val) +{ + struct arm_smccc_res result; + unsigned long sysmgr_base = (unsigned long)base; + + arm_smccc_smc(INTEL_SIP_SMC_REG_READ, sysmgr_base + reg, + 0, 0, 0, 0, 0, 0, &result); + + *val = (unsigned int)result.a1; + + return (int)result.a0; +} + +static struct regmap_config altr_sysmgr_regmap_cfg = { + .name = "altr_sysmgr", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .use_single_read = true, + .use_single_write = true, +}; + +/** + * sysmgr_match_phandle + * Matching function used by driver_find_device(). + * Return: True if match is found, otherwise false. + */ +static int sysmgr_match_phandle(struct device *dev, void *data) +{ + return dev->of_node == (struct device_node *)data; +} + +/** + * altr_sysmgr_regmap_lookup_by_phandle + * Find the sysmgr previous configured in probe() and return regmap property. + * Return: regmap if found or error if not found. + */ +struct regmap *altr_sysmgr_regmap_lookup_by_phandle(struct device_node *np, + const char *property) +{ + struct device *dev; + struct altr_sysmgr *sysmgr; + struct device_node *sysmgr_np; + + if (property) + sysmgr_np = of_parse_phandle(np, property, 0); + else + sysmgr_np = np; + + if (!sysmgr_np) + return ERR_PTR(-ENODEV); + + dev = driver_find_device(&altr_sysmgr_driver.driver, NULL, + (void *)sysmgr_np, sysmgr_match_phandle); + of_node_put(sysmgr_np); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); + + sysmgr = dev_get_drvdata(dev); + + return sysmgr->regmap; +} +EXPORT_SYMBOL_GPL(altr_sysmgr_regmap_lookup_by_phandle); + +static int sysmgr_probe(struct platform_device *pdev) +{ + struct altr_sysmgr *sysmgr; + struct regmap *regmap; + struct resource *res; + struct regmap_config sysmgr_config = altr_sysmgr_regmap_cfg; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + sysmgr = devm_kzalloc(dev, sizeof(*sysmgr), GFP_KERNEL); + if (!sysmgr) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + sysmgr_config.max_register = resource_size(res) - + sysmgr_config.reg_stride; + if (of_device_is_compatible(np, "altr,sys-mgr-s10")) { + /* Need physical address for SMCC call */ + sysmgr->base = (resource_size_t *)res->start; + sysmgr_config.reg_read = s10_protected_reg_read; + sysmgr_config.reg_write = s10_protected_reg_write; + + regmap = devm_regmap_init(dev, NULL, sysmgr->base, + &sysmgr_config); + } else { + sysmgr->base = devm_ioremap(dev, res->start, + resource_size(res)); + if (!sysmgr->base) + return -ENOMEM; + + sysmgr_config.max_register = res->end - res->start - 3; + regmap = devm_regmap_init_mmio(dev, sysmgr->base, + &sysmgr_config); + } + + if (IS_ERR(regmap)) { + pr_err("regmap init failed\n"); + return PTR_ERR(regmap); + } + + sysmgr->regmap = regmap; + + platform_set_drvdata(pdev, sysmgr); + + return 0; +} + +static const struct of_device_id altr_sysmgr_of_match[] = { + { .compatible = "altr,sys-mgr" }, + { .compatible = "altr,sys-mgr-s10" }, + {}, +}; +MODULE_DEVICE_TABLE(of, altr_sysmgr_of_match); + +static struct platform_driver altr_sysmgr_driver = { + .probe = sysmgr_probe, + .driver = { + .name = "altr,system_manager", + .of_match_table = altr_sysmgr_of_match, + }, +}; + +static int __init altr_sysmgr_init(void) +{ + return platform_driver_register(&altr_sysmgr_driver); +} +core_initcall(altr_sysmgr_init); + +static void __exit altr_sysmgr_exit(void) +{ + platform_driver_unregister(&altr_sysmgr_driver); +} +module_exit(altr_sysmgr_exit); + +MODULE_AUTHOR("Thor Thayer <>"); +MODULE_DESCRIPTION("SOCFPGA System Manager driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/max77650.c b/drivers/mfd/max77650.c new file mode 100644 index 000000000000..60e07aca6ae5 --- /dev/null +++ b/drivers/mfd/max77650.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 BayLibre SAS +// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> +// +// Core MFD driver for MAXIM 77650/77651 charger/power-supply. +// Programming manual: https://pdfserv.maximintegrated.com/en/an/AN6428.pdf + +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77650.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#define MAX77650_INT_GPI_F_MSK BIT(0) +#define MAX77650_INT_GPI_R_MSK BIT(1) +#define MAX77650_INT_GPI_MSK \ + (MAX77650_INT_GPI_F_MSK | MAX77650_INT_GPI_R_MSK) +#define MAX77650_INT_nEN_F_MSK BIT(2) +#define MAX77650_INT_nEN_R_MSK BIT(3) +#define MAX77650_INT_TJAL1_R_MSK BIT(4) +#define MAX77650_INT_TJAL2_R_MSK BIT(5) +#define MAX77650_INT_DOD_R_MSK BIT(6) + +#define MAX77650_INT_THM_MSK BIT(0) +#define MAX77650_INT_CHG_MSK BIT(1) +#define MAX77650_INT_CHGIN_MSK BIT(2) +#define MAX77650_INT_TJ_REG_MSK BIT(3) +#define MAX77650_INT_CHGIN_CTRL_MSK BIT(4) +#define MAX77650_INT_SYS_CTRL_MSK BIT(5) +#define MAX77650_INT_SYS_CNFG_MSK BIT(6) + +#define MAX77650_INT_GLBL_OFFSET 0 +#define MAX77650_INT_CHG_OFFSET 1 + +#define MAX77650_SBIA_LPM_MASK BIT(5) +#define MAX77650_SBIA_LPM_DISABLED 0x00 + +enum { + MAX77650_INT_GPI, + MAX77650_INT_nEN_F, + MAX77650_INT_nEN_R, + MAX77650_INT_TJAL1_R, + MAX77650_INT_TJAL2_R, + MAX77650_INT_DOD_R, + MAX77650_INT_THM, + MAX77650_INT_CHG, + MAX77650_INT_CHGIN, + MAX77650_INT_TJ_REG, + MAX77650_INT_CHGIN_CTRL, + MAX77650_INT_SYS_CTRL, + MAX77650_INT_SYS_CNFG, +}; + +static const struct resource max77650_charger_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHG, "CHG"), + DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHGIN, "CHGIN"), +}; + +static const struct resource max77650_gpio_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77650_INT_GPI, "GPI"), +}; + +static const struct resource max77650_onkey_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_F, "nEN_F"), + DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_R, "nEN_R"), +}; + +static const struct mfd_cell max77650_cells[] = { + { + .name = "max77650-regulator", + .of_compatible = "maxim,max77650-regulator", + }, { + .name = "max77650-charger", + .of_compatible = "maxim,max77650-charger", + .resources = max77650_charger_resources, + .num_resources = ARRAY_SIZE(max77650_charger_resources), + }, { + .name = "max77650-gpio", + .of_compatible = "maxim,max77650-gpio", + .resources = max77650_gpio_resources, + .num_resources = ARRAY_SIZE(max77650_gpio_resources), + }, { + .name = "max77650-led", + .of_compatible = "maxim,max77650-led", + }, { + .name = "max77650-onkey", + .of_compatible = "maxim,max77650-onkey", + .resources = max77650_onkey_resources, + .num_resources = ARRAY_SIZE(max77650_onkey_resources), + }, +}; + +static const struct regmap_irq max77650_irqs[] = { + [MAX77650_INT_GPI] = { + .reg_offset = MAX77650_INT_GLBL_OFFSET, + .mask = MAX77650_INT_GPI_MSK, + .type = { + .type_falling_val = MAX77650_INT_GPI_F_MSK, + .type_rising_val = MAX77650_INT_GPI_R_MSK, + .types_supported = IRQ_TYPE_EDGE_BOTH, + }, + }, + REGMAP_IRQ_REG(MAX77650_INT_nEN_F, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_F_MSK), + REGMAP_IRQ_REG(MAX77650_INT_nEN_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_TJAL1_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL1_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_TJAL2_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL2_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_DOD_R, + MAX77650_INT_GLBL_OFFSET, MAX77650_INT_DOD_R_MSK), + REGMAP_IRQ_REG(MAX77650_INT_THM, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_THM_MSK), + REGMAP_IRQ_REG(MAX77650_INT_CHG, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHG_MSK), + REGMAP_IRQ_REG(MAX77650_INT_CHGIN, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_MSK), + REGMAP_IRQ_REG(MAX77650_INT_TJ_REG, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_TJ_REG_MSK), + REGMAP_IRQ_REG(MAX77650_INT_CHGIN_CTRL, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_CTRL_MSK), + REGMAP_IRQ_REG(MAX77650_INT_SYS_CTRL, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CTRL_MSK), + REGMAP_IRQ_REG(MAX77650_INT_SYS_CNFG, + MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CNFG_MSK), +}; + +static const struct regmap_irq_chip max77650_irq_chip = { + .name = "max77650-irq", + .irqs = max77650_irqs, + .num_irqs = ARRAY_SIZE(max77650_irqs), + .num_regs = 2, + .status_base = MAX77650_REG_INT_GLBL, + .mask_base = MAX77650_REG_INTM_GLBL, + .type_in_mask = true, + .type_invert = true, + .init_ack_masked = true, + .clear_on_unmask = true, +}; + +static const struct regmap_config max77650_regmap_config = { + .name = "max77650", + .reg_bits = 8, + .val_bits = 8, +}; + +static int max77650_i2c_probe(struct i2c_client *i2c) +{ + struct regmap_irq_chip_data *irq_data; + struct device *dev = &i2c->dev; + struct irq_domain *domain; + struct regmap *map; + unsigned int val; + int rv, id; + + map = devm_regmap_init_i2c(i2c, &max77650_regmap_config); + if (IS_ERR(map)) { + dev_err(dev, "Unable to initialise I2C Regmap\n"); + return PTR_ERR(map); + } + + rv = regmap_read(map, MAX77650_REG_CID, &val); + if (rv) { + dev_err(dev, "Unable to read Chip ID\n"); + return rv; + } + + id = MAX77650_CID_BITS(val); + switch (id) { + case MAX77650_CID_77650A: + case MAX77650_CID_77650C: + case MAX77650_CID_77651A: + case MAX77650_CID_77651B: + break; + default: + dev_err(dev, "Chip not supported - ID: 0x%02x\n", id); + return -ENODEV; + } + + /* + * This IC has a low-power mode which reduces the quiescent current + * consumption to ~5.6uA but is only suitable for systems consuming + * less than ~2mA. Since this is not likely the case even on + * linux-based wearables - keep the chip in normal power mode. + */ + rv = regmap_update_bits(map, + MAX77650_REG_CNFG_GLBL, + MAX77650_SBIA_LPM_MASK, + MAX77650_SBIA_LPM_DISABLED); + if (rv) { + dev_err(dev, "Unable to change the power mode\n"); + return rv; + } + + rv = devm_regmap_add_irq_chip(dev, map, i2c->irq, + IRQF_ONESHOT | IRQF_SHARED, 0, + &max77650_irq_chip, &irq_data); + if (rv) { + dev_err(dev, "Unable to add Regmap IRQ chip\n"); + return rv; + } + + domain = regmap_irq_get_domain(irq_data); + + return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + max77650_cells, ARRAY_SIZE(max77650_cells), + NULL, 0, domain); +} + +static const struct of_device_id max77650_of_match[] = { + { .compatible = "maxim,max77650" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77650_of_match); + +static struct i2c_driver max77650_i2c_driver = { + .driver = { + .name = "max77650", + .of_match_table = of_match_ptr(max77650_of_match), + }, + .probe_new = max77650_i2c_probe, +}; +module_i2c_driver(max77650_i2c_driver); + +MODULE_DESCRIPTION("MAXIM 77650/77651 multi-function core driver"); +MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 94e3f32ce935..1ade4c8cc91f 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -269,6 +269,19 @@ fail_alloc: return ret; } +/** + * mfd_add_devices - register child devices + * + * @parent: Pointer to parent device. + * @id: Can be PLATFORM_DEVID_AUTO to let the Platform API take care + * of device numbering, or will be added to a device's cell_id. + * @cells: Array of (struct mfd_cell)s describing child devices. + * @n_devs: Number of child devices to register. + * @mem_base: Parent register range resource for child devices. + * @irq_base: Base of the range of virtual interrupt numbers allocated for + * this MFD device. Unused if @domain is specified. + * @domain: Interrupt domain to create mappings for hardware interrupts. + */ int mfd_add_devices(struct device *parent, int id, const struct mfd_cell *cells, int n_devs, struct resource *mem_base, diff --git a/drivers/mfd/stmfx.c b/drivers/mfd/stmfx.c new file mode 100644 index 000000000000..fe8efba2d45f --- /dev/null +++ b/drivers/mfd/stmfx.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STMicroelectronics Multi-Function eXpander (STMFX) core + * + * Copyright (C) 2019 STMicroelectronics + * Author(s): Amelie Delaunay <amelie.delaunay@st.com>. + */ +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/mfd/stmfx.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> + +static bool stmfx_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STMFX_REG_SYS_CTRL: + case STMFX_REG_IRQ_SRC_EN: + case STMFX_REG_IRQ_PENDING: + case STMFX_REG_IRQ_GPI_PENDING1: + case STMFX_REG_IRQ_GPI_PENDING2: + case STMFX_REG_IRQ_GPI_PENDING3: + case STMFX_REG_GPIO_STATE1: + case STMFX_REG_GPIO_STATE2: + case STMFX_REG_GPIO_STATE3: + case STMFX_REG_IRQ_GPI_SRC1: + case STMFX_REG_IRQ_GPI_SRC2: + case STMFX_REG_IRQ_GPI_SRC3: + case STMFX_REG_GPO_SET1: + case STMFX_REG_GPO_SET2: + case STMFX_REG_GPO_SET3: + case STMFX_REG_GPO_CLR1: + case STMFX_REG_GPO_CLR2: + case STMFX_REG_GPO_CLR3: + return true; + default: + return false; + } +} + +static bool stmfx_reg_writeable(struct device *dev, unsigned int reg) +{ + return (reg >= STMFX_REG_SYS_CTRL); +} + +static const struct regmap_config stmfx_regmap_config = { + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = STMFX_REG_MAX, + .volatile_reg = stmfx_reg_volatile, + .writeable_reg = stmfx_reg_writeable, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct resource stmfx_pinctrl_resources[] = { + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_GPIO), +}; + +static const struct resource stmfx_idd_resources[] = { + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_IDD), + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_ERROR), +}; + +static const struct resource stmfx_ts_resources[] = { + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_DET), + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_NE), + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_TH), + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_FULL), + DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_OVF), +}; + +static struct mfd_cell stmfx_cells[] = { + { + .of_compatible = "st,stmfx-0300-pinctrl", + .name = "stmfx-pinctrl", + .resources = stmfx_pinctrl_resources, + .num_resources = ARRAY_SIZE(stmfx_pinctrl_resources), + }, + { + .of_compatible = "st,stmfx-0300-idd", + .name = "stmfx-idd", + .resources = stmfx_idd_resources, + .num_resources = ARRAY_SIZE(stmfx_idd_resources), + }, + { + .of_compatible = "st,stmfx-0300-ts", + .name = "stmfx-ts", + .resources = stmfx_ts_resources, + .num_resources = ARRAY_SIZE(stmfx_ts_resources), + }, +}; + +static u8 stmfx_func_to_mask(u32 func) +{ + u8 mask = 0; + + if (func & STMFX_FUNC_GPIO) + mask |= STMFX_REG_SYS_CTRL_GPIO_EN; + + if ((func & STMFX_FUNC_ALTGPIO_LOW) || (func & STMFX_FUNC_ALTGPIO_HIGH)) + mask |= STMFX_REG_SYS_CTRL_ALTGPIO_EN; + + if (func & STMFX_FUNC_TS) + mask |= STMFX_REG_SYS_CTRL_TS_EN; + + if (func & STMFX_FUNC_IDD) + mask |= STMFX_REG_SYS_CTRL_IDD_EN; + + return mask; +} + +int stmfx_function_enable(struct stmfx *stmfx, u32 func) +{ + u32 sys_ctrl; + u8 mask; + int ret; + + ret = regmap_read(stmfx->map, STMFX_REG_SYS_CTRL, &sys_ctrl); + if (ret) + return ret; + + /* + * IDD and TS have priority in STMFX FW, so if IDD and TS are enabled, + * ALTGPIO function is disabled by STMFX FW. If IDD or TS is enabled, + * the number of aGPIO available decreases. To avoid GPIO management + * disturbance, abort IDD or TS function enable in this case. + */ + if (((func & STMFX_FUNC_IDD) || (func & STMFX_FUNC_TS)) && + (sys_ctrl & STMFX_REG_SYS_CTRL_ALTGPIO_EN)) { + dev_err(stmfx->dev, "ALTGPIO function already enabled\n"); + return -EBUSY; + } + + /* If TS is enabled, aGPIO[3:0] cannot be used */ + if ((func & STMFX_FUNC_ALTGPIO_LOW) && + (sys_ctrl & STMFX_REG_SYS_CTRL_TS_EN)) { + dev_err(stmfx->dev, "TS in use, aGPIO[3:0] unavailable\n"); + return -EBUSY; + } + + /* If IDD is enabled, aGPIO[7:4] cannot be used */ + if ((func & STMFX_FUNC_ALTGPIO_HIGH) && + (sys_ctrl & STMFX_REG_SYS_CTRL_IDD_EN)) { + dev_err(stmfx->dev, "IDD in use, aGPIO[7:4] unavailable\n"); + return -EBUSY; + } + + mask = stmfx_func_to_mask(func); + + return regmap_update_bits(stmfx->map, STMFX_REG_SYS_CTRL, mask, mask); +} +EXPORT_SYMBOL_GPL(stmfx_function_enable); + +int stmfx_function_disable(struct stmfx *stmfx, u32 func) +{ + u8 mask = stmfx_func_to_mask(func); + + return regmap_update_bits(stmfx->map, STMFX_REG_SYS_CTRL, mask, 0); +} +EXPORT_SYMBOL_GPL(stmfx_function_disable); + +static void stmfx_irq_bus_lock(struct irq_data *data) +{ + struct stmfx *stmfx = irq_data_get_irq_chip_data(data); + + mutex_lock(&stmfx->lock); +} + +static void stmfx_irq_bus_sync_unlock(struct irq_data *data) +{ + struct stmfx *stmfx = irq_data_get_irq_chip_data(data); + + regmap_write(stmfx->map, STMFX_REG_IRQ_SRC_EN, stmfx->irq_src); + + mutex_unlock(&stmfx->lock); +} + +static void stmfx_irq_mask(struct irq_data *data) +{ + struct stmfx *stmfx = irq_data_get_irq_chip_data(data); + + stmfx->irq_src &= ~BIT(data->hwirq % 8); +} + +static void stmfx_irq_unmask(struct irq_data *data) +{ + struct stmfx *stmfx = irq_data_get_irq_chip_data(data); + + stmfx->irq_src |= BIT(data->hwirq % 8); +} + +static struct irq_chip stmfx_irq_chip = { + .name = "stmfx-core", + .irq_bus_lock = stmfx_irq_bus_lock, + .irq_bus_sync_unlock = stmfx_irq_bus_sync_unlock, + .irq_mask = stmfx_irq_mask, + .irq_unmask = stmfx_irq_unmask, +}; + +static irqreturn_t stmfx_irq_handler(int irq, void *data) +{ + struct stmfx *stmfx = data; + unsigned long n, pending; + u32 ack; + int ret; + + ret = regmap_read(stmfx->map, STMFX_REG_IRQ_PENDING, + (u32 *)&pending); + if (ret) + return IRQ_NONE; + + /* + * There is no ACK for GPIO, MFX_REG_IRQ_PENDING_GPIO is a logical OR + * of MFX_REG_IRQ_GPI _PENDING1/_PENDING2/_PENDING3 + */ + ack = pending & ~BIT(STMFX_REG_IRQ_SRC_EN_GPIO); + if (ack) { + ret = regmap_write(stmfx->map, STMFX_REG_IRQ_ACK, ack); + if (ret) + return IRQ_NONE; + } + + for_each_set_bit(n, &pending, STMFX_REG_IRQ_SRC_MAX) + handle_nested_irq(irq_find_mapping(stmfx->irq_domain, n)); + + return IRQ_HANDLED; +} + +static int stmfx_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + irq_set_chip_data(virq, d->host_data); + irq_set_chip_and_handler(virq, &stmfx_irq_chip, handle_simple_irq); + irq_set_nested_thread(virq, 1); + irq_set_noprobe(virq); + + return 0; +} + +static void stmfx_irq_unmap(struct irq_domain *d, unsigned int virq) +{ + irq_set_chip_and_handler(virq, NULL, NULL); + irq_set_chip_data(virq, NULL); +} + +static const struct irq_domain_ops stmfx_irq_ops = { + .map = stmfx_irq_map, + .unmap = stmfx_irq_unmap, +}; + +static void stmfx_irq_exit(struct i2c_client *client) +{ + struct stmfx *stmfx = i2c_get_clientdata(client); + int hwirq; + + for (hwirq = 0; hwirq < STMFX_REG_IRQ_SRC_MAX; hwirq++) + irq_dispose_mapping(irq_find_mapping(stmfx->irq_domain, hwirq)); + + irq_domain_remove(stmfx->irq_domain); +} + +static int stmfx_irq_init(struct i2c_client *client) +{ + struct stmfx *stmfx = i2c_get_clientdata(client); + u32 irqoutpin = 0, irqtrigger; + int ret; + + stmfx->irq_domain = irq_domain_add_simple(stmfx->dev->of_node, + STMFX_REG_IRQ_SRC_MAX, 0, + &stmfx_irq_ops, stmfx); + if (!stmfx->irq_domain) { + dev_err(stmfx->dev, "Failed to create IRQ domain\n"); + return -EINVAL; + } + + if (!of_property_read_bool(stmfx->dev->of_node, "drive-open-drain")) + irqoutpin |= STMFX_REG_IRQ_OUT_PIN_TYPE; + + irqtrigger = irq_get_trigger_type(client->irq); + if ((irqtrigger & IRQ_TYPE_EDGE_RISING) || + (irqtrigger & IRQ_TYPE_LEVEL_HIGH)) + irqoutpin |= STMFX_REG_IRQ_OUT_PIN_POL; + + ret = regmap_write(stmfx->map, STMFX_REG_IRQ_OUT_PIN, irqoutpin); + if (ret) + return ret; + + ret = devm_request_threaded_irq(stmfx->dev, client->irq, + NULL, stmfx_irq_handler, + irqtrigger | IRQF_ONESHOT, + "stmfx", stmfx); + if (ret) + stmfx_irq_exit(client); + + return ret; +} + +static int stmfx_chip_reset(struct stmfx *stmfx) +{ + int ret; + + ret = regmap_write(stmfx->map, STMFX_REG_SYS_CTRL, + STMFX_REG_SYS_CTRL_SWRST); + if (ret) + return ret; + + msleep(STMFX_BOOT_TIME_MS); + + return ret; +} + +static int stmfx_chip_init(struct i2c_client *client) +{ + struct stmfx *stmfx = i2c_get_clientdata(client); + u32 id; + u8 version[2]; + int ret; + + stmfx->vdd = devm_regulator_get_optional(&client->dev, "vdd"); + ret = PTR_ERR_OR_ZERO(stmfx->vdd); + if (ret == -ENODEV) { + stmfx->vdd = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else if (ret) { + dev_err(&client->dev, "Failed to get VDD regulator: %d\n", ret); + return ret; + } + + if (stmfx->vdd) { + ret = regulator_enable(stmfx->vdd); + if (ret) { + dev_err(&client->dev, "VDD enable failed: %d\n", ret); + return ret; + } + } + + ret = regmap_read(stmfx->map, STMFX_REG_CHIP_ID, &id); + if (ret) { + dev_err(&client->dev, "Error reading chip ID: %d\n", ret); + goto err; + } + + /* + * Check that ID is the complement of the I2C address: + * STMFX I2C address follows the 7-bit format (MSB), that's why + * client->addr is shifted. + * + * STMFX_I2C_ADDR| STMFX | Linux + * input pin | I2C device address | I2C device address + *--------------------------------------------------------- + * 0 | b: 1000 010x h:0x84 | 0x42 + * 1 | b: 1000 011x h:0x86 | 0x43 + */ + if (FIELD_GET(STMFX_REG_CHIP_ID_MASK, ~id) != (client->addr << 1)) { + dev_err(&client->dev, "Unknown chip ID: %#x\n", id); + ret = -EINVAL; + goto err; + } + + ret = regmap_bulk_read(stmfx->map, STMFX_REG_FW_VERSION_MSB, + version, ARRAY_SIZE(version)); + if (ret) { + dev_err(&client->dev, "Error reading FW version: %d\n", ret); + goto err; + } + + dev_info(&client->dev, "STMFX id: %#x, fw version: %x.%02x\n", + id, version[0], version[1]); + + ret = stmfx_chip_reset(stmfx); + if (ret) { + dev_err(&client->dev, "Failed to reset chip: %d\n", ret); + goto err; + } + + return 0; + +err: + if (stmfx->vdd) + return regulator_disable(stmfx->vdd); + + return ret; +} + +static int stmfx_chip_exit(struct i2c_client *client) +{ + struct stmfx *stmfx = i2c_get_clientdata(client); + + regmap_write(stmfx->map, STMFX_REG_IRQ_SRC_EN, 0); + regmap_write(stmfx->map, STMFX_REG_SYS_CTRL, 0); + + if (stmfx->vdd) + return regulator_disable(stmfx->vdd); + + return 0; +} + +static int stmfx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct stmfx *stmfx; + int ret; + + stmfx = devm_kzalloc(dev, sizeof(*stmfx), GFP_KERNEL); + if (!stmfx) + return -ENOMEM; + + i2c_set_clientdata(client, stmfx); + + stmfx->dev = dev; + + stmfx->map = devm_regmap_init_i2c(client, &stmfx_regmap_config); + if (IS_ERR(stmfx->map)) { + ret = PTR_ERR(stmfx->map); + dev_err(dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + mutex_init(&stmfx->lock); + + ret = stmfx_chip_init(client); + if (ret) { + if (ret == -ETIMEDOUT) + return -EPROBE_DEFER; + return ret; + } + + if (client->irq < 0) { + dev_err(dev, "Failed to get IRQ: %d\n", client->irq); + ret = client->irq; + goto err_chip_exit; + } + + ret = stmfx_irq_init(client); + if (ret) + goto err_chip_exit; + + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + stmfx_cells, ARRAY_SIZE(stmfx_cells), NULL, + 0, stmfx->irq_domain); + if (ret) + goto err_irq_exit; + + return 0; + +err_irq_exit: + stmfx_irq_exit(client); +err_chip_exit: + stmfx_chip_exit(client); + + return ret; +} + +static int stmfx_remove(struct i2c_client *client) +{ + stmfx_irq_exit(client); + + return stmfx_chip_exit(client); +} + +#ifdef CONFIG_PM_SLEEP +static int stmfx_suspend(struct device *dev) +{ + struct stmfx *stmfx = dev_get_drvdata(dev); + int ret; + + ret = regmap_raw_read(stmfx->map, STMFX_REG_SYS_CTRL, + &stmfx->bkp_sysctrl, sizeof(stmfx->bkp_sysctrl)); + if (ret) + return ret; + + ret = regmap_raw_read(stmfx->map, STMFX_REG_IRQ_OUT_PIN, + &stmfx->bkp_irqoutpin, + sizeof(stmfx->bkp_irqoutpin)); + if (ret) + return ret; + + if (stmfx->vdd) + return regulator_disable(stmfx->vdd); + + return 0; +} + +static int stmfx_resume(struct device *dev) +{ + struct stmfx *stmfx = dev_get_drvdata(dev); + int ret; + + if (stmfx->vdd) { + ret = regulator_enable(stmfx->vdd); + if (ret) { + dev_err(stmfx->dev, + "VDD enable failed: %d\n", ret); + return ret; + } + } + + ret = regmap_raw_write(stmfx->map, STMFX_REG_SYS_CTRL, + &stmfx->bkp_sysctrl, sizeof(stmfx->bkp_sysctrl)); + if (ret) + return ret; + + ret = regmap_raw_write(stmfx->map, STMFX_REG_IRQ_OUT_PIN, + &stmfx->bkp_irqoutpin, + sizeof(stmfx->bkp_irqoutpin)); + if (ret) + return ret; + + ret = regmap_raw_write(stmfx->map, STMFX_REG_IRQ_SRC_EN, + &stmfx->irq_src, sizeof(stmfx->irq_src)); + if (ret) + return ret; + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(stmfx_dev_pm_ops, stmfx_suspend, stmfx_resume); + +static const struct of_device_id stmfx_of_match[] = { + { .compatible = "st,stmfx-0300", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stmfx_of_match); + +static struct i2c_driver stmfx_driver = { + .driver = { + .name = "stmfx-core", + .of_match_table = of_match_ptr(stmfx_of_match), + .pm = &stmfx_dev_pm_ops, + }, + .probe = stmfx_probe, + .remove = stmfx_remove, +}; +module_i2c_driver(stmfx_driver); + +MODULE_DESCRIPTION("STMFX core driver"); +MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/ti-lmu.c b/drivers/mfd/ti-lmu.c index 37d0bdb291c3..b06cb908d1aa 100644 --- a/drivers/mfd/ti-lmu.c +++ b/drivers/mfd/ti-lmu.c @@ -54,14 +54,6 @@ static void ti_lmu_disable_hw(void *data) gpiod_set_value(lmu->en_gpio, 0); } -static const struct mfd_cell lm3532_devices[] = { - { - .name = "ti-lmu-backlight", - .id = LM3532, - .of_compatible = "ti,lm3532-backlight", - }, -}; - #define LM363X_REGULATOR(_id) \ { \ .name = "lm363x-regulator", \ @@ -141,7 +133,6 @@ static const struct ti_lmu_data chip##_data = \ .max_register = max_reg, \ } \ -TI_LMU_DATA(lm3532, LM3532_MAX_REG); TI_LMU_DATA(lm3631, LM3631_MAX_REG); TI_LMU_DATA(lm3632, LM3632_MAX_REG); TI_LMU_DATA(lm3633, LM3633_MAX_REG); @@ -211,7 +202,6 @@ static int ti_lmu_probe(struct i2c_client *cl, const struct i2c_device_id *id) } static const struct of_device_id ti_lmu_of_match[] = { - { .compatible = "ti,lm3532", .data = &lm3532_data }, { .compatible = "ti,lm3631", .data = &lm3631_data }, { .compatible = "ti,lm3632", .data = &lm3632_data }, { .compatible = "ti,lm3633", .data = &lm3633_data }, @@ -222,7 +212,6 @@ static const struct of_device_id ti_lmu_of_match[] = { MODULE_DEVICE_TABLE(of, ti_lmu_of_match); static const struct i2c_device_id ti_lmu_ids[] = { - { "lm3532", LM3532 }, { "lm3631", LM3631 }, { "lm3632", LM3632 }, { "lm3633", LM3633 }, |