| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/i2c.h> |
| #include <linux/regmap.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| #include <linux/err.h> |
| #include <linux/irq.h> |
| #include <linux/interrupt.h> |
| #include <linux/mfd/core.h> |
| #include <linux/of.h> |
| #include <linux/regulator/of_regulator.h> |
| #include <linux/gpio/consumer.h> |
| |
| #include <linux/amlogic/pmic/meson_pmic6.h> |
| |
| #define PMIC6_IRQ_MASK0_OFFSET 0 |
| #define PMIC6_IRQ_MASK1_OFFSET 1 |
| #define PMIC6_IRQ_MASK2_OFFSET 2 |
| #define PMIC6_IRQ_MASK3_OFFSET 3 |
| |
| static struct resource meson_pmic_regulators_resources[] = { |
| { |
| .name = "LDO1_OV", |
| .start = PMIC6_IRQ_LDO1_OV, |
| .end = PMIC6_IRQ_LDO1_OV, |
| .flags = IORESOURCE_IRQ, |
| }, |
| { |
| .name = "LDO1_UV", |
| .start = PMIC6_IRQ_LDO1_UV, |
| .end = PMIC6_IRQ_LDO1_UV, |
| .flags = IORESOURCE_IRQ, |
| } |
| }; |
| |
| static struct resource meson_pmic_onkey_resources[] = { |
| { |
| .name = "PWRKEY", |
| .start = PMIC6_IRQ_PWR_KEY, |
| .end = PMIC6_IRQ_PWR_KEY, |
| .flags = IORESOURCE_IRQ, |
| }, |
| }; |
| |
| static const struct mfd_cell meson_pmic_common_devs[] = { |
| { |
| .name = PMIC6_DRVNAME_REGULATORS, |
| .num_resources = ARRAY_SIZE(meson_pmic_regulators_resources), |
| .resources = meson_pmic_regulators_resources, |
| }, |
| { |
| .name = PMIC6_DRVNAME_LEDS, |
| }, |
| { |
| .name = PMIC6_DRVNAME_WATCHDOG, |
| .of_compatible = "amlogic,pmic6-watchdog", |
| }, |
| { |
| .name = PMIC6_DRVNAME_GPIO, |
| .of_compatible = "amlogic,pmic6-gpio", |
| }, |
| { |
| .name = PMIC6_DRVNAME_ONKEY, |
| .num_resources = ARRAY_SIZE(meson_pmic_onkey_resources), |
| .resources = meson_pmic_onkey_resources, |
| .of_compatible = "amlogic,pmic6-pwrkey", |
| }, |
| { |
| .name = PMIC6_DRVNAME_BATTERY, |
| .of_compatible = "amlogic,pmic6-battery", |
| }, |
| }; |
| |
| static const struct regmap_range meson_pmic_readable_ranges[] = { |
| regmap_reg_range(PMIC6_POWER_UP_SEL0, PMIC6_REG_MAX), |
| }; |
| |
| static const struct regmap_range meson_pmic_writeable_ranges[] = { |
| regmap_reg_range(PMIC6_POWER_UP_SEL0, PMIC6_REG_MAX), |
| }; |
| |
| static const struct regmap_range meson_pmic_volatile_ranges[] = { |
| regmap_reg_range(PMIC6_POWER_UP_SEL0, PMIC6_REG_MAX), |
| }; |
| |
| static const struct regmap_access_table meson_pmic_readable_table = { |
| .yes_ranges = meson_pmic_readable_ranges, |
| .n_yes_ranges = ARRAY_SIZE(meson_pmic_readable_ranges), |
| }; |
| |
| static const struct regmap_access_table meson_pmic_writeable_table = { |
| .yes_ranges = meson_pmic_writeable_ranges, |
| .n_yes_ranges = ARRAY_SIZE(meson_pmic_writeable_ranges), |
| }; |
| |
| static const struct regmap_access_table meson_pmic_volatile_table = { |
| .yes_ranges = meson_pmic_volatile_ranges, |
| .n_yes_ranges = ARRAY_SIZE(meson_pmic_volatile_ranges), |
| }; |
| |
| static bool meson_pmic_volatile_reg(struct device *dev, unsigned int reg) |
| { |
| return true; |
| } |
| |
| static struct regmap_config meson_pmic_regmap_config = { |
| .reg_bits = 16, |
| .val_bits = 8, |
| .volatile_reg = meson_pmic_volatile_reg, |
| .max_register = PMIC6_REG_MAX, |
| .reg_format_endian = REGMAP_ENDIAN_LITTLE, |
| |
| .cache_type = REGCACHE_RBTREE, |
| }; |
| |
| static const struct regmap_irq meson_pmic_irqs[] = { |
| /* PMIC6 IRQ status 0 register */ |
| REGMAP_IRQ_REG(PMIC6_IRQ_NTC_UNT, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(0)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_NTC_OVT, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(1)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_DCIN_UV_LV, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(2)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_DCIN_OV_LV, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(3)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_OTP, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(4)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_OCP, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(5)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_OVP, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(6)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_DCIN_OK, |
| PMIC6_IRQ_MASK0_OFFSET, BIT(7)), |
| /* PMIC6 IRQ status 1 register */ |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_TIMEOUT, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(0)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_CHG_CHGEND, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(1)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_DC1_UV, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(2)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_DC1_OC, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(3)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_DC2_UV, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(4)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_DC2_OC, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(5)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_DC3_UV, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(6)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_DC3_OC, |
| PMIC6_IRQ_MASK1_OFFSET, BIT(7)), |
| /* PMIC6 IRQ status 2 register */ |
| REGMAP_IRQ_REG(PMIC6_IRQ_VSYS_UVLO, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(1)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO1_OV, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(2)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO1_UV, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(3)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO1_OC, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(4)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO2_OV, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(5)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO2_UV, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(6)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO2_OC, |
| PMIC6_IRQ_MASK2_OFFSET, BIT(7)), |
| /* PMIC6 IRQ status 3 register */ |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO3_OV, |
| PMIC6_IRQ_MASK3_OFFSET, BIT(0)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO3_UV, |
| PMIC6_IRQ_MASK3_OFFSET, BIT(1)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_LDO3_OC, |
| PMIC6_IRQ_MASK3_OFFSET, BIT(2)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_PWR_KEY, |
| PMIC6_IRQ_MASK3_OFFSET, BIT(6)), |
| REGMAP_IRQ_REG(PMIC6_IRQ_SAR, |
| PMIC6_IRQ_MASK3_OFFSET, BIT(7)), |
| }; |
| |
| static const struct regmap_irq_chip meson_pmic_irq_chip = { |
| .name = "meson_pmic-irq", |
| .irqs = meson_pmic_irqs, |
| .num_irqs = ARRAY_SIZE(meson_pmic_irqs), |
| .num_regs = 4, |
| .status_base = PMIC6_IRQ_STATUS_CLR0, |
| .mask_base = PMIC6_IRQ_MASK0, |
| .ack_base = PMIC6_IRQ_STATUS_CLR0, |
| .init_ack_masked = true, |
| .mask_invert = true, |
| .ack_invert = true, |
| }; |
| |
| int meson_pmic_irq_init(struct meson_pmic *meson_pmic) |
| { |
| const struct regmap_irq_chip *irq_chip; |
| int ret; |
| |
| if (!meson_pmic->chip_irq) { |
| dev_err(meson_pmic->dev, "No IRQ configured\n"); |
| return -EINVAL; |
| } |
| |
| irq_chip = &meson_pmic_irq_chip; |
| |
| ret = devm_regmap_add_irq_chip(meson_pmic->dev, meson_pmic->regmap, |
| meson_pmic->chip_irq, IRQF_TRIGGER_HIGH | |
| IRQF_ONESHOT | IRQF_SHARED, |
| meson_pmic->irq_base, irq_chip, |
| &meson_pmic->regmap_irq); |
| if (ret) { |
| dev_err(meson_pmic->dev, "Failed to reguest IRQ %d: %d\n", |
| meson_pmic->chip_irq, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int meson_pmic_device_init(struct meson_pmic *meson_pmic, unsigned int irq) |
| { |
| struct pmic6_pdata *pdata = meson_pmic->dev->platform_data; |
| int ret; |
| |
| if (pdata) { |
| meson_pmic->flags = pdata->flags; |
| meson_pmic->irq_base = pdata->irq_base; |
| } else { |
| meson_pmic->flags = 0; |
| meson_pmic->irq_base = -1; |
| } |
| meson_pmic->chip_irq = irq; |
| |
| if (pdata && pdata->init) { |
| ret = pdata->init(meson_pmic); |
| if (ret != 0) { |
| dev_err(meson_pmic->dev, |
| "Platform initialization failed.\n"); |
| return ret; |
| } |
| } |
| |
| ret = meson_pmic_irq_init(meson_pmic); |
| if (ret) { |
| dev_err(meson_pmic->dev, "Cannot initialize interrupts.\n"); |
| return ret; |
| } |
| |
| meson_pmic->irq_base = regmap_irq_chip_get_base(meson_pmic->regmap_irq); |
| |
| ret = devm_mfd_add_devices(meson_pmic->dev, PLATFORM_DEVID_NONE, |
| meson_pmic_common_devs, |
| ARRAY_SIZE(meson_pmic_common_devs), |
| NULL, meson_pmic->irq_base, NULL); |
| if (ret) { |
| dev_err(meson_pmic->dev, "Failed to add child devices\n"); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static const struct of_device_id meson_pmic_dt_ids[] = { |
| { .compatible = "amlogic,pmic6", }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, meson_pmic_dt_ids); |
| |
| static ssize_t read_reg_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, |
| dev); |
| int ret; |
| int reg = 0; |
| u32 val; |
| struct meson_pmic *pmic = i2c_get_clientdata(client); |
| |
| ret = kstrtoint(buf, 16, ®); |
| if (ret) { |
| pr_err("%s: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_read(pmic->regmap, reg, &val); |
| if (ret) |
| pr_err("%s: %d\n", __func__, __LINE__); |
| |
| pr_info("[0x%x] = 0x%x\n", reg, val); |
| |
| return strlen(buf); |
| } |
| |
| static ssize_t write_reg_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct i2c_client *client = container_of(dev, struct i2c_client, |
| dev); |
| int ret, i = 0, j = 0; |
| struct meson_pmic *pmic = i2c_get_clientdata(client); |
| int reg = 0, val = 0; |
| char tone[20]; |
| |
| while (buf[i] != '\0') { |
| if (buf[i] == 's') { |
| tone[j] = '\0'; |
| ret = kstrtoint(tone, 16, ®); |
| if (ret) { |
| pr_err("%s: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| break; |
| } |
| tone[j] = buf[i]; |
| i++; |
| j++; |
| } |
| ret = kstrtoint(&buf[i + 1], 16, &val); |
| if (ret) { |
| pr_err("%s: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_write(pmic->regmap, reg, val); |
| if (ret) |
| pr_err("%s: %d\n", __func__, __LINE__); |
| |
| pr_info("[0x%x] = 0x%x\n", reg, val); |
| |
| return strlen(buf); |
| } |
| |
| static DEVICE_ATTR_WO(read_reg); |
| static DEVICE_ATTR_WO(write_reg); |
| |
| static struct attribute *pmic_attrs[] = { |
| &dev_attr_read_reg.attr, |
| &dev_attr_write_reg.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group pmic_attr_group = { |
| .attrs = pmic_attrs, |
| }; |
| |
| static int meson_pmic_probe(struct i2c_client *i2c, |
| const struct i2c_device_id *id) |
| { |
| struct meson_pmic *meson_pmic; |
| struct gpio_desc *pmic_irq; |
| int ret; |
| |
| meson_pmic = devm_kzalloc(&i2c->dev, |
| sizeof(struct meson_pmic), |
| GFP_KERNEL); |
| if (!meson_pmic) |
| return -ENOMEM; |
| |
| i2c_set_clientdata(i2c, meson_pmic); |
| meson_pmic->dev = &i2c->dev; |
| |
| pmic_irq = gpiod_get(&i2c->dev, "pmic_irq", GPIOD_IN); |
| if (IS_ERR(pmic_irq)) { |
| dev_err(meson_pmic->dev, "Failed to claim gpio\n"); |
| return PTR_ERR(pmic_irq); |
| } |
| meson_pmic->chip_irq = gpiod_to_irq(pmic_irq); |
| |
| meson_pmic_regmap_config.rd_table = &meson_pmic_readable_table; |
| meson_pmic_regmap_config.wr_table = &meson_pmic_writeable_table; |
| meson_pmic_regmap_config.volatile_table = &meson_pmic_volatile_table; |
| |
| meson_pmic->regmap = devm_regmap_init_i2c(i2c, |
| &meson_pmic_regmap_config); |
| if (IS_ERR(meson_pmic->regmap)) { |
| ret = PTR_ERR(meson_pmic->regmap); |
| dev_err(meson_pmic->dev, "Failed to allocate register map: %d\n", |
| ret); |
| return ret; |
| } |
| |
| ret = sysfs_create_group(&meson_pmic->dev->kobj, &pmic_attr_group); |
| if (ret) { |
| dev_err(meson_pmic->dev, |
| "pmic sysfs group creation failed: %d\n", ret); |
| } |
| return meson_pmic_device_init(meson_pmic, meson_pmic->chip_irq); |
| } |
| |
| static const struct i2c_device_id meson_pmic_i2c_id[] = { |
| { "meson_pmic",}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, meson_pmic_i2c_id); |
| |
| static struct i2c_driver meson_pmic_driver = { |
| .driver = { |
| .name = "meson_pmic", |
| .of_match_table = of_match_ptr(meson_pmic_dt_ids), |
| }, |
| .probe = meson_pmic_probe, |
| .id_table = meson_pmic_i2c_id, |
| }; |
| |
| static int __init meson_pmic_i2c_init(void) |
| { |
| return i2c_add_driver(&meson_pmic_driver); |
| } |
| |
| static void __exit meson_pmic_i2c_exit(void) |
| { |
| return i2c_del_driver(&meson_pmic_driver); |
| } |
| |
| postcore_initcall(meson_pmic_i2c_init); |
| module_exit(meson_pmic_i2c_exit); |
| |
| MODULE_DESCRIPTION("PMIC mfd driver for Amlogic"); |
| MODULE_AUTHOR("Amlogic"); |
| MODULE_LICENSE("GPL v2"); |