|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * Core support for ATC260x PMICs | 
|  | * | 
|  | * Copyright (C) 2019 Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> | 
|  | * Copyright (C) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/mfd/atc260x/core.h> | 
|  | #include <linux/mfd/core.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_device.h> | 
|  | #include <linux/regmap.h> | 
|  |  | 
|  | #define ATC260X_CHIP_REV_MAX	31 | 
|  |  | 
|  | struct atc260x_init_regs { | 
|  | unsigned int cmu_devrst; | 
|  | unsigned int cmu_devrst_ints; | 
|  | unsigned int ints_msk; | 
|  | unsigned int pad_en; | 
|  | unsigned int pad_en_extirq; | 
|  | }; | 
|  |  | 
|  | static void regmap_lock_mutex(void *__mutex) | 
|  | { | 
|  | struct mutex *mutex = __mutex; | 
|  |  | 
|  | /* | 
|  | * Using regmap within an atomic context (e.g. accessing a PMIC when | 
|  | * powering system down) is normally allowed only if the regmap type | 
|  | * is MMIO and the regcache type is either REGCACHE_NONE or | 
|  | * REGCACHE_FLAT. For slow buses like I2C and SPI, the regmap is | 
|  | * internally protected by a mutex which is acquired non-atomically. | 
|  | * | 
|  | * Let's improve this by using a customized locking scheme inspired | 
|  | * from I2C atomic transfer. See i2c_in_atomic_xfer_mode() for a | 
|  | * starting point. | 
|  | */ | 
|  | if (system_state > SYSTEM_RUNNING && irqs_disabled()) | 
|  | mutex_trylock(mutex); | 
|  | else | 
|  | mutex_lock(mutex); | 
|  | } | 
|  |  | 
|  | static void regmap_unlock_mutex(void *__mutex) | 
|  | { | 
|  | struct mutex *mutex = __mutex; | 
|  |  | 
|  | mutex_unlock(mutex); | 
|  | } | 
|  |  | 
|  | static const struct regmap_config atc2603c_regmap_config = { | 
|  | .reg_bits = 8, | 
|  | .val_bits = 16, | 
|  | .max_register = ATC2603C_SADDR, | 
|  | .cache_type = REGCACHE_NONE, | 
|  | }; | 
|  |  | 
|  | static const struct regmap_config atc2609a_regmap_config = { | 
|  | .reg_bits = 8, | 
|  | .val_bits = 16, | 
|  | .max_register = ATC2609A_SADDR, | 
|  | .cache_type = REGCACHE_NONE, | 
|  | }; | 
|  |  | 
|  | static const struct regmap_irq atc2603c_regmap_irqs[] = { | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_AUDIO,	0, ATC2603C_INTS_MSK_AUDIO), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_OV,		0, ATC2603C_INTS_MSK_OV), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_OC,		0, ATC2603C_INTS_MSK_OC), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_OT,		0, ATC2603C_INTS_MSK_OT), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_UV,		0, ATC2603C_INTS_MSK_UV), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_ALARM,	0, ATC2603C_INTS_MSK_ALARM), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_ONOFF,	0, ATC2603C_INTS_MSK_ONOFF), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_SGPIO,	0, ATC2603C_INTS_MSK_SGPIO), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_IR,		0, ATC2603C_INTS_MSK_IR), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_REMCON,	0, ATC2603C_INTS_MSK_REMCON), | 
|  | REGMAP_IRQ_REG(ATC2603C_IRQ_POWER_IN,	0, ATC2603C_INTS_MSK_POWERIN), | 
|  | }; | 
|  |  | 
|  | static const struct regmap_irq atc2609a_regmap_irqs[] = { | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_AUDIO,	0, ATC2609A_INTS_MSK_AUDIO), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_OV,		0, ATC2609A_INTS_MSK_OV), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_OC,		0, ATC2609A_INTS_MSK_OC), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_OT,		0, ATC2609A_INTS_MSK_OT), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_UV,		0, ATC2609A_INTS_MSK_UV), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_ALARM,	0, ATC2609A_INTS_MSK_ALARM), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_ONOFF,	0, ATC2609A_INTS_MSK_ONOFF), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_WKUP,	0, ATC2609A_INTS_MSK_WKUP), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_IR,		0, ATC2609A_INTS_MSK_IR), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_REMCON,	0, ATC2609A_INTS_MSK_REMCON), | 
|  | REGMAP_IRQ_REG(ATC2609A_IRQ_POWER_IN,	0, ATC2609A_INTS_MSK_POWERIN), | 
|  | }; | 
|  |  | 
|  | static const struct regmap_irq_chip atc2603c_regmap_irq_chip = { | 
|  | .name = "atc2603c", | 
|  | .irqs = atc2603c_regmap_irqs, | 
|  | .num_irqs = ARRAY_SIZE(atc2603c_regmap_irqs), | 
|  | .num_regs = 1, | 
|  | .status_base = ATC2603C_INTS_PD, | 
|  | .unmask_base = ATC2603C_INTS_MSK, | 
|  | }; | 
|  |  | 
|  | static const struct regmap_irq_chip atc2609a_regmap_irq_chip = { | 
|  | .name = "atc2609a", | 
|  | .irqs = atc2609a_regmap_irqs, | 
|  | .num_irqs = ARRAY_SIZE(atc2609a_regmap_irqs), | 
|  | .num_regs = 1, | 
|  | .status_base = ATC2609A_INTS_PD, | 
|  | .unmask_base = ATC2609A_INTS_MSK, | 
|  | }; | 
|  |  | 
|  | static const struct resource atc2603c_onkey_resources[] = { | 
|  | DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF), | 
|  | }; | 
|  |  | 
|  | static const struct resource atc2609a_onkey_resources[] = { | 
|  | DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF), | 
|  | }; | 
|  |  | 
|  | static const struct mfd_cell atc2603c_mfd_cells[] = { | 
|  | { .name = "atc260x-regulator" }, | 
|  | { .name = "atc260x-pwrc" }, | 
|  | { | 
|  | .name = "atc260x-onkey", | 
|  | .num_resources = ARRAY_SIZE(atc2603c_onkey_resources), | 
|  | .resources = atc2603c_onkey_resources, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct mfd_cell atc2609a_mfd_cells[] = { | 
|  | { .name = "atc260x-regulator" }, | 
|  | { .name = "atc260x-pwrc" }, | 
|  | { | 
|  | .name = "atc260x-onkey", | 
|  | .num_resources = ARRAY_SIZE(atc2609a_onkey_resources), | 
|  | .resources = atc2609a_onkey_resources, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct atc260x_init_regs atc2603c_init_regs = { | 
|  | .cmu_devrst = ATC2603C_CMU_DEVRST, | 
|  | .cmu_devrst_ints = ATC2603C_CMU_DEVRST_INTS, | 
|  | .ints_msk = ATC2603C_INTS_MSK, | 
|  | .pad_en = ATC2603C_PAD_EN, | 
|  | .pad_en_extirq = ATC2603C_PAD_EN_EXTIRQ, | 
|  | }; | 
|  |  | 
|  | static const struct atc260x_init_regs atc2609a_init_regs = { | 
|  | .cmu_devrst = ATC2609A_CMU_DEVRST, | 
|  | .cmu_devrst_ints = ATC2609A_CMU_DEVRST_INTS, | 
|  | .ints_msk = ATC2609A_INTS_MSK, | 
|  | .pad_en = ATC2609A_PAD_EN, | 
|  | .pad_en_extirq = ATC2609A_PAD_EN_EXTIRQ, | 
|  | }; | 
|  |  | 
|  | static void atc260x_cmu_reset(struct atc260x *atc260x) | 
|  | { | 
|  | const struct atc260x_init_regs *regs = atc260x->init_regs; | 
|  |  | 
|  | /* Assert reset */ | 
|  | regmap_update_bits(atc260x->regmap, regs->cmu_devrst, | 
|  | regs->cmu_devrst_ints, ~regs->cmu_devrst_ints); | 
|  |  | 
|  | /* De-assert reset */ | 
|  | regmap_update_bits(atc260x->regmap, regs->cmu_devrst, | 
|  | regs->cmu_devrst_ints, regs->cmu_devrst_ints); | 
|  | } | 
|  |  | 
|  | static void atc260x_dev_init(struct atc260x *atc260x) | 
|  | { | 
|  | const struct atc260x_init_regs *regs = atc260x->init_regs; | 
|  |  | 
|  | /* Initialize interrupt block */ | 
|  | atc260x_cmu_reset(atc260x); | 
|  |  | 
|  | /* Disable all interrupt sources */ | 
|  | regmap_write(atc260x->regmap, regs->ints_msk, 0); | 
|  |  | 
|  | /* Enable EXTIRQ pad */ | 
|  | regmap_update_bits(atc260x->regmap, regs->pad_en, | 
|  | regs->pad_en_extirq, regs->pad_en_extirq); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * atc260x_match_device(): Setup ATC260x variant related fields | 
|  | * | 
|  | * @atc260x: ATC260x device to setup (.dev field must be set) | 
|  | * @regmap_cfg: regmap config associated with this ATC260x device | 
|  | * | 
|  | * This lets the ATC260x core configure the MFD cells and register maps | 
|  | * for later use. | 
|  | */ | 
|  | int atc260x_match_device(struct atc260x *atc260x, struct regmap_config *regmap_cfg) | 
|  | { | 
|  | struct device *dev = atc260x->dev; | 
|  | const void *of_data; | 
|  |  | 
|  | of_data = of_device_get_match_data(dev); | 
|  | if (!of_data) | 
|  | return -ENODEV; | 
|  |  | 
|  | atc260x->ic_type = (unsigned long)of_data; | 
|  |  | 
|  | switch (atc260x->ic_type) { | 
|  | case ATC2603C: | 
|  | *regmap_cfg = atc2603c_regmap_config; | 
|  | atc260x->regmap_irq_chip = &atc2603c_regmap_irq_chip; | 
|  | atc260x->cells = atc2603c_mfd_cells; | 
|  | atc260x->nr_cells = ARRAY_SIZE(atc2603c_mfd_cells); | 
|  | atc260x->type_name = "atc2603c"; | 
|  | atc260x->rev_reg = ATC2603C_CHIP_VER; | 
|  | atc260x->init_regs = &atc2603c_init_regs; | 
|  | break; | 
|  | case ATC2609A: | 
|  | *regmap_cfg = atc2609a_regmap_config; | 
|  | atc260x->regmap_irq_chip = &atc2609a_regmap_irq_chip; | 
|  | atc260x->cells = atc2609a_mfd_cells; | 
|  | atc260x->nr_cells = ARRAY_SIZE(atc2609a_mfd_cells); | 
|  | atc260x->type_name = "atc2609a"; | 
|  | atc260x->rev_reg = ATC2609A_CHIP_VER; | 
|  | atc260x->init_regs = &atc2609a_init_regs; | 
|  | break; | 
|  | default: | 
|  | dev_err(dev, "Unsupported ATC260x device type: %u\n", | 
|  | atc260x->ic_type); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | atc260x->regmap_mutex = devm_kzalloc(dev, sizeof(*atc260x->regmap_mutex), | 
|  | GFP_KERNEL); | 
|  | if (!atc260x->regmap_mutex) | 
|  | return -ENOMEM; | 
|  |  | 
|  | mutex_init(atc260x->regmap_mutex); | 
|  |  | 
|  | regmap_cfg->lock = regmap_lock_mutex, | 
|  | regmap_cfg->unlock = regmap_unlock_mutex, | 
|  | regmap_cfg->lock_arg = atc260x->regmap_mutex; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(atc260x_match_device); | 
|  |  | 
|  | /** | 
|  | * atc260x_device_probe(): Probe a configured ATC260x device | 
|  | * | 
|  | * @atc260x: ATC260x device to probe (must be configured) | 
|  | * | 
|  | * This function lets the ATC260x core register the ATC260x MFD devices | 
|  | * and IRQCHIP. The ATC260x device passed in must be fully configured | 
|  | * with atc260x_match_device, its IRQ set, and regmap created. | 
|  | */ | 
|  | int atc260x_device_probe(struct atc260x *atc260x) | 
|  | { | 
|  | struct device *dev = atc260x->dev; | 
|  | unsigned int chip_rev; | 
|  | int ret; | 
|  |  | 
|  | if (!atc260x->irq) { | 
|  | dev_err(dev, "No interrupt support\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Initialize the hardware */ | 
|  | atc260x_dev_init(atc260x); | 
|  |  | 
|  | ret = regmap_read(atc260x->regmap, atc260x->rev_reg, &chip_rev); | 
|  | if (ret) { | 
|  | dev_err(dev, "Failed to get chip revision\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (chip_rev > ATC260X_CHIP_REV_MAX) { | 
|  | dev_err(dev, "Unknown chip revision: %u\n", chip_rev); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | atc260x->ic_ver = __ffs(chip_rev + 1U); | 
|  |  | 
|  | dev_info(dev, "Detected chip type %s rev.%c\n", | 
|  | atc260x->type_name, 'A' + atc260x->ic_ver); | 
|  |  | 
|  | ret = devm_regmap_add_irq_chip(dev, atc260x->regmap, atc260x->irq, IRQF_ONESHOT, | 
|  | -1, atc260x->regmap_irq_chip, &atc260x->irq_data); | 
|  | if (ret) { | 
|  | dev_err(dev, "Failed to add IRQ chip: %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | 
|  | atc260x->cells, atc260x->nr_cells, NULL, 0, | 
|  | regmap_irq_get_domain(atc260x->irq_data)); | 
|  | if (ret) { | 
|  | dev_err(dev, "Failed to add child devices: %d\n", ret); | 
|  | regmap_del_irq_chip(atc260x->irq, atc260x->irq_data); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(atc260x_device_probe); | 
|  |  | 
|  | MODULE_DESCRIPTION("ATC260x PMICs Core support"); | 
|  | MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); | 
|  | MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>"); | 
|  | MODULE_LICENSE("GPL"); |