Add the core MFD driver for max77705 PMIC. We define five sub-devices
for which the drivers will be added in subsequent patches.

Signed-off-by: Dzmitry Sankouski <dsankou...@gmail.com>
Co-authored-by: Rose Hudson <r...@krx.sh>
---
 MAINTAINERS                          |   2 +
 drivers/mfd/Kconfig                  |  12 ++
 drivers/mfd/Makefile                 |   3 +
 drivers/mfd/max77705-core.c          | 278 ++++++++++++++++++++++++++++++++
 drivers/mfd/max77705-irq.c           | 299 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/max77705-private.h | 281 ++++++++++++++++++++++++++++++++
 include/linux/mfd/max77705.h         |  20 +++
 7 files changed, 895 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index fae3b8ea9ce4..f66f08825db9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13690,6 +13690,7 @@ F:      drivers/*/*max77843.c
 F:     drivers/*/max14577*.c
 F:     drivers/*/max77686*.c
 F:     drivers/*/max77693*.c
+F:     drivers/*/max77705*.c
 F:     drivers/clk/clk-max77686.c
 F:     drivers/extcon/extcon-max14577.c
 F:     drivers/extcon/extcon-max77693.c
@@ -13697,6 +13698,7 @@ F:      drivers/rtc/rtc-max77686.c
 F:     include/linux/mfd/max14577*.h
 F:     include/linux/mfd/max77686*.h
 F:     include/linux/mfd/max77693*.h
+F:     include/linux/mfd/max77705*.h
 
 MAXIRADIO FM RADIO RECEIVER DRIVER
 M:     Hans Verkuil <hverk...@xs4all.nl>
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 266b4f54af60..c4eb8ff2dcad 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -880,6 +880,18 @@ config MFD_MAX77693
          additional drivers must be enabled in order to use the functionality
          of the device.
 
+config MFD_MAX77705
+       tristate "Maxim Semiconductor MAX77705 PMIC Support"
+       depends on I2C
+       select MFD_CORE
+       help
+         Say yes here to add support for Maxim Semiconductor MAX77705.
+         This is a Power Management IC with Charger, safe LDOs, Flash, Haptic
+         and MUIC controls on chip.
+         This driver provides common support for accessing the device;
+         additional drivers must be enabled in order to use the functionality
+         of the device.
+
 config MFD_MAX77714
        tristate "Maxim Semiconductor MAX77714 PMIC Support"
        depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index db1ba39de3b5..bf65cc36d59c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -162,6 +162,8 @@ obj-$(CONFIG_MFD_MAX77620)  += max77620.o
 obj-$(CONFIG_MFD_MAX77650)     += max77650.o
 obj-$(CONFIG_MFD_MAX77686)     += max77686.o
 obj-$(CONFIG_MFD_MAX77693)     += max77693.o
+max77705-objs := max77705-core.o max77705-irq.o
+obj-$(CONFIG_MFD_MAX77705)     += max77705.o
 obj-$(CONFIG_MFD_MAX77714)     += max77714.o
 obj-$(CONFIG_MFD_MAX77843)     += max77843.o
 obj-$(CONFIG_MFD_MAX8907)      += max8907.o
@@ -226,6 +228,7 @@ obj-$(CONFIG_MFD_RK8XX_I2C) += rk8xx-i2c.o
 obj-$(CONFIG_MFD_RK8XX_SPI)    += rk8xx-spi.o
 obj-$(CONFIG_MFD_RN5T618)      += rn5t618.o
 obj-$(CONFIG_MFD_SEC_CORE)     += sec-core.o sec-irq.o
+obj-$(CONFIG_MFD_S2DOS05)      += s2dos05.o
 obj-$(CONFIG_MFD_SYSCON)       += syscon.o
 obj-$(CONFIG_MFD_LM3533)       += lm3533-core.o lm3533-ctrlbank.o
 obj-$(CONFIG_MFD_VEXPRESS_SYSREG)      += vexpress-sysreg.o
diff --git a/drivers/mfd/max77705-core.c b/drivers/mfd/max77705-core.c
new file mode 100644
index 000000000000..7cb71a0a9688
--- /dev/null
+++ b/drivers/mfd/max77705-core.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * max77705.c - mfd core driver for the Maxim 77705
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (C) 2024 Dzmitry Sankouski <dsankou...@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max77705.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/debugfs.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+
+#define I2C_ADDR_PMIC  (0xCC >> 1)     /* Top sys, Haptic */
+#define I2C_ADDR_MUIC  (0x4A >> 1)
+#define I2C_ADDR_CHG    (0xD2 >> 1)
+#define I2C_ADDR_FG     (0x6C >> 1)
+#define I2C_ADDR_DEBUG  (0xC4 >> 1)
+
+static struct dentry *debugfs_file;
+
+static int max77705_debugfs_show(struct seq_file *s, void *data)
+{
+       struct max77705_dev *max77705 = s->private;
+       struct regmap *regmap = max77705->regmap;
+       unsigned int i, reg, reg_data, pmic_id, pmic_rev;
+       int regs[] = {
+               MAX77705_PMIC_REG_MAINCTRL1,
+               MAX77705_PMIC_REG_MCONFIG,
+               MAX77705_PMIC_REG_MCONFIG2,
+               MAX77705_PMIC_REG_INTSRC,
+               MAX77705_PMIC_REG_INTSRC_MASK,
+               MAX77705_PMIC_REG_SYSTEM_INT,
+               MAX77705_PMIC_REG_SYSTEM_INT_MASK,
+               MAX77705_RGBLED_REG_LEDEN,
+               MAX77705_RGBLED_REG_LED0BRT,
+               MAX77705_RGBLED_REG_LED1BRT,
+               MAX77705_RGBLED_REG_LED2BRT,
+               MAX77705_RGBLED_REG_LED3BRT,
+               MAX77705_RGBLED_REG_LEDBLNK
+       };
+
+       regmap_read(regmap, MAX77705_PMIC_REG_PMICID1, &pmic_id);
+       regmap_read(regmap, MAX77705_PMIC_REG_PMICREV, &pmic_rev);
+       seq_printf(s, "MAX77705, pmic id: %d, pmic rev: %d\n",
+                  pmic_id, pmic_rev);
+       seq_puts(s, "===================\n");
+       for (i = 0; i < ARRAY_SIZE(regs); i++) {
+               reg = regs[i];
+               regmap_read(regmap, reg, &reg_data);
+               seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data);
+       }
+
+       seq_puts(s, "\n");
+       return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(max77705_debugfs);
+
+static const struct regmap_config max77705_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = MAX77705_PMIC_REG_END,
+};
+
+static const struct regmap_config max77705_leds_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = MAX77705_LED_REG_END,
+};
+
+static const struct regmap_config max77705_fg_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = MAX77705_FG_END,
+};
+
+static struct mfd_cell max77705_devs[] = {
+       {
+               .name = "leds-max77705-rgb",
+               .of_compatible = "maxim,max77705-led",
+       },
+       {
+               .name = "max77705-fuelgauge",
+               .of_compatible = "maxim,max77705-fg",
+       },
+       {
+               .name = "max77705-charger",
+               .of_compatible = "maxim,max77705-charger",
+       },
+       {
+               .name = "max77705-haptic",
+               .of_compatible = "maxim,max77705-haptic",
+       },
+};
+
+static int max77705_i2c_probe(struct i2c_client *i2c)
+{
+       struct max77705_dev *max77705;
+       struct max77705_platform_data *pdata = i2c->dev.platform_data;
+
+       unsigned int reg_data;
+       int ret = 0;
+
+       max77705 = kzalloc(sizeof(struct max77705_dev), GFP_KERNEL);
+       if (!max77705)
+               return -ENOMEM;
+
+       max77705->pdata = pdata;
+       max77705->dev = &i2c->dev;
+       max77705->i2c = i2c;
+       max77705->irq = i2c->irq;
+
+       max77705->regmap = devm_regmap_init_i2c(max77705->i2c, 
&max77705_regmap_config);
+       if (IS_ERR(max77705->regmap)) {
+               ret = PTR_ERR(max77705->regmap);
+               dev_err(max77705->dev, "failed to allocate register map: %d\n",
+                               ret);
+               return ret;
+       }
+
+       max77705->regmap_leds = devm_regmap_init_i2c(max77705->i2c, 
&max77705_leds_regmap_config);
+       if (IS_ERR(max77705->regmap_leds)) {
+               ret = PTR_ERR(max77705->regmap_leds);
+               dev_err(max77705->dev, "failed to allocate register map: %d\n",
+                               ret);
+               return ret;
+       }
+
+       i2c_set_clientdata(i2c, max77705);
+
+       if (regmap_read(max77705->regmap, MAX77705_PMIC_REG_PMICREV, &reg_data) 
< 0) {
+               dev_err(max77705->dev,
+                       "device not found on this channel (this is not an 
error)\n");
+               ret = -ENODEV;
+               goto err;
+       } else {
+               /* print rev */
+               max77705->pmic_rev = (reg_data & MAX77705_REVISION_MASK);
+               max77705->pmic_ver = ((reg_data & MAX77705_VERSION_MASK) >> 
MAX77705_VERSION_SHIFT);
+               dev_info(max77705->dev, "%s device found: rev.0x%x, ver.0x%x\n",
+                               __func__, max77705->pmic_rev, 
max77705->pmic_ver);
+       }
+
+       max77705->charger = devm_i2c_new_dummy_device(max77705->dev, 
i2c->adapter, I2C_ADDR_CHG);
+       i2c_set_clientdata(max77705->charger, max77705);
+       max77705->regmap_charger = devm_regmap_init_i2c(max77705->charger, 
&max77705_regmap_config);
+       if (IS_ERR(max77705->regmap)) {
+               ret = PTR_ERR(max77705->regmap);
+               dev_err(max77705->dev, "failed to allocate register map: %d\n",
+                               ret);
+               return ret;
+       }
+
+       max77705->fuelgauge = devm_i2c_new_dummy_device(max77705->dev, 
i2c->adapter, I2C_ADDR_FG);
+       i2c_set_clientdata(max77705->fuelgauge, max77705);
+       max77705->regmap_fg = devm_regmap_init_i2c(max77705->fuelgauge, 
&max77705_fg_regmap_config);
+       if (IS_ERR(max77705->regmap_fg)) {
+               ret = PTR_ERR(max77705->regmap_fg);
+               dev_err(max77705->dev, "failed to allocate register map: %d\n",
+                               ret);
+               return ret;
+       }
+
+       if (likely(i2c->irq > 0))
+               max77705->irq = i2c->irq;
+       else {
+               dev_err(max77705->dev, "failed to get irq number\n");
+               return -EINVAL;
+       }
+
+       max77705->irq_base = irq_alloc_descs(-1, 0, MAX77705_IRQ_NR, -1);
+       if (unlikely(max77705->irq_base < 0)) {
+               dev_err(max77705->dev, "irq_alloc_descs fail: %d\n", 
max77705->irq_base);
+               ret = -EINVAL;
+               goto err;
+       }
+
+       disable_irq(max77705->irq);
+       ret = max77705_irq_init(max77705);
+       if (ret) {
+               dev_err(max77705->dev, "failed to init irq system: %d\n", ret);
+               return ret;
+       }
+
+       ret = mfd_add_devices(max77705->dev, -1, max77705_devs,
+                       ARRAY_SIZE(max77705_devs), NULL, 0, NULL);
+       if (ret < 0)
+               goto err_mfd;
+
+       debugfs_file = debugfs_create_file("max77705-regs",
+                               0664, NULL, (void *)max77705,
+                                 &max77705_debugfs_fops);
+       if (!debugfs_file)
+               dev_err(max77705->dev, "Failed to create debugfs file\n");
+
+       device_init_wakeup(max77705->dev, true);
+
+       return ret;
+
+err_mfd:
+       mfd_remove_devices(max77705->dev);
+err:
+       kfree(max77705);
+       return ret;
+}
+
+static void max77705_i2c_remove(struct i2c_client *i2c)
+{
+       struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
+
+       if (debugfs_file)
+               debugfs_remove(debugfs_file);
+
+       device_init_wakeup(max77705->dev, 0);
+       mfd_remove_devices(max77705->dev);
+}
+
+static int __maybe_unused max77705_suspend(struct device *dev)
+{
+       struct i2c_client *i2c = to_i2c_client(dev);
+       struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
+
+       disable_irq(max77705->irq);
+       if (device_may_wakeup(dev))
+               enable_irq_wake(max77705->irq);
+
+       return 0;
+}
+
+static int __maybe_unused max77705_resume(struct device *dev)
+{
+       struct i2c_client *i2c = to_i2c_client(dev);
+       struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
+
+       if (device_may_wakeup(dev))
+               disable_irq_wake(max77705->irq);
+       enable_irq(max77705->irq);
+
+       return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max77705_pm, max77705_suspend, max77705_resume);
+
+static const struct of_device_id max77705_i2c_dt_ids[] = {
+       { .compatible = "maxim,max77705" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, max77705_i2c_dt_ids);
+
+static struct i2c_driver max77705_i2c_driver = {
+       .driver         = {
+               .name   = MFD_DEV_NAME,
+               .pm = &max77705_pm,
+               .of_match_table = max77705_i2c_dt_ids,
+               .suppress_bind_attrs = true,
+       },
+       .probe          = max77705_i2c_probe,
+       .remove         = max77705_i2c_remove,
+};
+module_i2c_driver(max77705_i2c_driver);
+
+MODULE_DESCRIPTION("MAXIM 77705 multi-function core driver");
+MODULE_AUTHOR("Dzmitry Sankouski <dsankou...@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/max77705-irq.c b/drivers/mfd/max77705-irq.c
new file mode 100644
index 000000000000..0da17fc95e18
--- /dev/null
+++ b/drivers/mfd/max77705-irq.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * max77705-irq.c - Interrupt controller support for MAX77705
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (C) 2024 Dzmitry Sankouski <dsankou...@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max77705.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/regmap.h>
+
+static const u8 max77705_mask_reg[] = {
+       [SYS_INT] = MAX77705_PMIC_REG_SYSTEM_INT_MASK,
+       [CHG_INT] = MAX77705_CHG_REG_INT_MASK,
+       [FUEL_INT] = MAX77705_REG_INVALID,
+};
+
+static struct regmap *get_i2c(struct max77705_dev *max77705,
+                               enum max77705_irq_source src)
+{
+       switch (src) {
+       case SYS_INT:
+               return max77705->regmap;
+       case FUEL_INT:
+               return max77705->regmap_fg;
+       case CHG_INT:
+               return max77705->regmap_charger;
+       default:
+               return ERR_PTR(-EINVAL);
+       }
+}
+
+struct max77705_irq_data {
+       int mask;
+       enum max77705_irq_source group;
+};
+
+static const struct max77705_irq_data max77705_irqs[] = {
+       [MAX77705_SYSTEM_IRQ_BSTEN_INT] = { .group = SYS_INT, .mask = BIT(3) },
+       [MAX77705_SYSTEM_IRQ_SYSUVLO_INT] = { .group = SYS_INT, .mask = BIT(4) 
},
+       [MAX77705_SYSTEM_IRQ_SYSOVLO_INT] = { .group = SYS_INT, .mask = BIT(5) 
},
+       [MAX77705_SYSTEM_IRQ_TSHDN_INT] = { .group = SYS_INT, .mask = BIT(6) },
+       [MAX77705_SYSTEM_IRQ_TM_INT] = { .group = SYS_INT, .mask = BIT(7) },
+
+       [MAX77705_CHG_IRQ_BYP_I] = { .group = CHG_INT, .mask = BIT(0) },
+       [MAX77705_CHG_IRQ_BAT_I] = { .group = CHG_INT, .mask = BIT(3) },
+       [MAX77705_CHG_IRQ_CHG_I] = { .group = CHG_INT, .mask = BIT(4) },
+       [MAX77705_CHG_IRQ_WCIN_I] = { .group = CHG_INT, .mask = BIT(5) },
+       [MAX77705_CHG_IRQ_CHGIN_I] = { .group = CHG_INT, .mask = BIT(6) },
+       [MAX77705_CHG_IRQ_AICL_I] = { .group = CHG_INT, .mask = BIT(7) },
+
+       [MAX77705_FG_IRQ_ALERT] = { .group = FUEL_INT, .mask = BIT(1) },
+};
+
+static void max77705_irq_lock(struct irq_data *data)
+{
+       struct max77705_dev *max77705 = irq_get_chip_data(data->irq);
+
+       mutex_lock(&max77705->irqlock);
+}
+
+static void max77705_irq_sync_unlock(struct irq_data *data)
+{
+       struct max77705_dev *max77705 = irq_get_chip_data(data->irq);
+       int i;
+
+       for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) {
+               u8 mask_reg = max77705_mask_reg[i];
+               struct regmap *i2c = get_i2c(max77705, i);
+
+               if (mask_reg == MAX77705_REG_INVALID ||
+                               IS_ERR_OR_NULL(i2c))
+                       continue;
+               max77705->irq_masks_cache[i] = max77705->irq_masks_cur[i];
+
+               regmap_write(i2c, max77705_mask_reg[i],
+                               max77705->irq_masks_cur[i]);
+       }
+
+       mutex_unlock(&max77705->irqlock);
+}
+
+static inline void max77705_read_irq_reg(struct regmap *regmap, unsigned int 
pmic_rev,
+                                       unsigned int reg, unsigned int 
*irq_src) {
+       u8 dummy[2] = {0, }; /* for pass1 intr reg clear issue */
+
+       switch (pmic_rev) {
+       case MAX77705_PASS1:
+               regmap_noinc_read(regmap, reg - 1,
+                               dummy, sizeof(dummy));
+               *irq_src = (unsigned int) dummy[1];
+               break;
+       case MAX77705_PASS2:
+       case MAX77705_PASS3:
+               regmap_read(regmap, reg,
+                               irq_src);
+               break;
+       default:
+               pr_err("%s: PMIC_REVISION(SRC_CHG) isn't valid\n", __func__);
+               break;
+       }
+}
+
+static inline const struct max77705_irq_data *
+irq_to_max77705_irq(struct max77705_dev *max77705, int irq)
+{
+       return &max77705_irqs[irq - max77705->irq_base];
+}
+
+static void max77705_irq_mask(struct irq_data *data)
+{
+       struct max77705_dev *max77705 = irq_get_chip_data(data->irq);
+       const struct max77705_irq_data *irq_data =
+           irq_to_max77705_irq(max77705, data->irq);
+
+       if (irq_data->group >= MAX77705_IRQ_GROUP_NR)
+               return;
+
+       max77705->irq_masks_cur[irq_data->group] |= irq_data->mask;
+}
+
+static void max77705_irq_unmask(struct irq_data *data)
+{
+       struct max77705_dev *max77705 = irq_get_chip_data(data->irq);
+       const struct max77705_irq_data *irq_data =
+           irq_to_max77705_irq(max77705, data->irq);
+
+       if (irq_data->group >= MAX77705_IRQ_GROUP_NR)
+               return;
+
+       max77705->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
+}
+
+inline int max77705_irq_mask_subdevice(struct max77705_dev *max77705, unsigned 
int mask)
+{
+       int ret;
+       unsigned int data;
+
+       ret = regmap_read(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK,
+                         &data);
+       if (ret) {
+               dev_err(max77705->dev, "fail to read 
MAX77705_PMIC_REG_INTSRC_MASK reg\n");
+               return ret;
+       }
+       data |= mask;
+
+       regmap_write(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK,
+                          data);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(max77705_irq_mask_subdevice);
+
+inline int max77705_irq_unmask_subdevice(struct max77705_dev *max77705, 
unsigned int mask)
+{
+       int ret;
+       unsigned int data;
+
+       ret = regmap_read(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK,
+                         &data);
+       if (ret) {
+               dev_err(max77705->dev, "fail to read 
MAX77705_PMIC_REG_INTSRC_MASK reg\n");
+               return ret;
+       }
+       data &= ~(mask);
+
+       regmap_write(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK,
+                          data);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(max77705_irq_unmask_subdevice);
+
+static void max77705_irq_disable(struct irq_data *data)
+{
+       max77705_irq_mask(data);
+}
+
+static struct irq_chip max77705_irq_chip = {
+       .name                   = MFD_DEV_NAME,
+       .irq_bus_lock           = max77705_irq_lock,
+       .irq_bus_sync_unlock    = max77705_irq_sync_unlock,
+       .irq_mask               = max77705_irq_mask,
+       .irq_unmask             = max77705_irq_unmask,
+       .irq_disable            = max77705_irq_disable,
+};
+
+static irqreturn_t max77705_irq_thread(int irq, void *data)
+{
+       struct max77705_dev *max77705 = data;
+       unsigned int irq_reg[MAX77705_IRQ_GROUP_NR] = {0};
+       unsigned int irq_src;
+       int i, ret;
+       u8 pmic_rev = max77705->pmic_rev;
+
+       max77705->doing_irq = 1;
+
+       ret = regmap_read(max77705->regmap,
+                                       MAX77705_PMIC_REG_INTSRC, &irq_src);
+       if (ret) {
+               pr_err("%s:%s Failed to read interrupt source: %d\n",
+                       MFD_DEV_NAME, __func__, ret);
+
+               max77705->doing_irq = 0;
+               return IRQ_NONE;
+       }
+
+       if (irq_src & MAX77705_IRQSRC_CHG) {
+               max77705_read_irq_reg(max77705->regmap_charger, pmic_rev,
+                                       MAX77705_CHG_REG_INT, 
&irq_reg[CHG_INT]);
+               pr_info("%s: charger interrupt(0x%02x)\n",
+                               __func__, irq_reg[CHG_INT]);
+       }
+
+       /* Apply masking */
+       for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++)
+               irq_reg[i] &= ~max77705->irq_masks_cur[i];
+
+       /* Report */
+       for (i = 0; i < MAX77705_IRQ_NR; i++) {
+               if (irq_reg[max77705_irqs[i].group] & max77705_irqs[i].mask)
+                       handle_nested_irq(max77705->irq_base + i);
+       }
+
+       max77705->doing_irq = 0;
+
+       return IRQ_HANDLED;
+}
+
+int max77705_irq_init(struct max77705_dev *max77705)
+{
+       int i;
+       int ret = 0;
+       int cur_irq;
+
+       if (!max77705->irq_base) {
+               dev_err(max77705->dev, "No interrupt base specified.\n");
+               return 0;
+       }
+
+       mutex_init(&max77705->irqlock);
+
+       /* Mask individual interrupt sources */
+       for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) {
+               struct regmap *i2c;
+               /* MUIC IRQ  0:MASK 1:NOT MASK => NOT USE */
+               /* Other IRQ 1:MASK 0:NOT MASK */
+               max77705->irq_masks_cur[i] = 0xff;
+               max77705->irq_masks_cache[i] = 0xff;
+
+               i2c = get_i2c(max77705, i);
+
+               if (IS_ERR_OR_NULL(i2c))
+                       continue;
+               if (max77705_mask_reg[i] == MAX77705_REG_INVALID)
+                       continue;
+               regmap_write(i2c, max77705_mask_reg[i], 0xff);
+       }
+
+       /* Register with genirq */
+       for (i = 0; i < MAX77705_IRQ_NR; i++) {
+               cur_irq = i + max77705->irq_base;
+               irq_set_chip_data(cur_irq, max77705);
+               irq_set_chip_and_handler(cur_irq, &max77705_irq_chip,
+                                        handle_level_irq);
+               irq_set_nested_thread(cur_irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(cur_irq, IRQF_VALID);
+#else
+               irq_set_noprobe(cur_irq);
+#endif
+       }
+
+       ret = max77705_irq_mask_subdevice(max77705, MAX77705_IRQSRC_CHG | 
MAX77705_IRQSRC_TOP |
+                                         MAX77705_IRQSRC_FG | 
MAX77705_IRQSRC_USBC);
+       if (ret) {
+               dev_err(max77705->dev, "Failed to mask subdevice irqs\n");
+               return ret;
+       }
+
+       ret = devm_request_threaded_irq(max77705->dev, max77705->irq, NULL, 
max77705_irq_thread,
+                                       IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                                       "max77705-irq", max77705);
+       if (ret) {
+               dev_err(max77705->dev, "Failed to request IRQ %d: %d\n",
+                               max77705->irq, ret);
+       }
+
+       return ret;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max77705-private.h 
b/include/linux/mfd/max77705-private.h
new file mode 100644
index 000000000000..bd6ab9c582b8
--- /dev/null
+++ b/include/linux/mfd/max77705-private.h
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * max77705-private.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_MFD_MAX77705_PRIV_H
+#define __LINUX_MFD_MAX77705_PRIV_H
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#define MAX77705_REG_INVALID           (0xff)
+#define MAX77705_IRQSRC_CHG                    BIT(0)
+#define MAX77705_IRQSRC_TOP                    BIT(1)
+#define MAX77705_IRQSRC_FG                     BIT(2)
+#define MAX77705_IRQSRC_USBC           BIT(3)
+
+/* STATUS_REG */
+#define MAX77705_BAT_ABSENT_MASK       BIT(3)
+/* MAX77705 MAINCTRL1 register */
+#define MAX77705_MAINCTRL1_BIASEN_SHIFT                7
+#define MAX77705_MAINCTRL1_BIASEN_MASK         
BIT(MAX77705_MAINCTRL1_BIASEN_SHIFT)
+
+/* max77705-haptic configuration register */
+#define MAX77705_CONFIG2_MEN_SHIFT             6
+#define MAX77705_CONFIG2_MODE_SHIFT            7
+#define MAX77705_CONFIG2_HTYP_SHIFT            5
+
+#define MAX77705_REVISION_MASK 7
+#define MAX77705_VERSION_MASK  0xF8
+#define MAX77705_VERSION_SHIFT 3
+
+enum max77705_hw_rev {
+       MAX77705_PASS1 = 1,
+       MAX77705_PASS2,
+       MAX77705_PASS3,
+};
+
+enum max77705_reg {
+       /* Slave addr = 0xCC */
+       /* PMIC Top-Level Registers */
+       MAX77705_PMIC_REG_PMICID1               = 0x00,
+       MAX77705_PMIC_REG_PMICREV               = 0x01,
+       MAX77705_PMIC_REG_MAINCTRL1             = 0x02,
+       MAX77705_PMIC_REG_INTSRC                = 0x22,
+       MAX77705_PMIC_REG_INTSRC_MASK           = 0x23,
+       MAX77705_PMIC_REG_SYSTEM_INT            = 0x24,
+       MAX77705_PMIC_REG_RESERVED_25           = 0x25,
+       MAX77705_PMIC_REG_SYSTEM_INT_MASK       = 0x26,
+       MAX77705_PMIC_REG_RESERVED_27           = 0x27,
+       MAX77705_PMIC_REG_RESERVED_28           = 0x28,
+       MAX77705_PMIC_REG_RESERVED_29           = 0x29,
+       MAX77705_PMIC_REG_BOOSTCONTROL1         = 0x4C,
+       MAX77705_PMIC_REG_BSTOUT_MASK           = 0x03,
+       MAX77705_PMIC_REG_BOOSTCONTROL2         = 0x4F,
+       MAX77705_PMIC_REG_FORCE_EN_MASK         = 0x08,
+       MAX77705_PMIC_REG_SW_RESET              = 0x50,
+       MAX77705_PMIC_REG_USBC_RESET            = 0x51,
+
+       /* Haptic motor driver Registers */
+       MAX77705_PMIC_REG_MCONFIG               = 0x10,
+       MAX77705_PMIC_REG_MCONFIG2              = 0x11,
+
+       MAX77705_CHG_REG_INT                    = 0xB0,
+       MAX77705_CHG_REG_INT_MASK               = 0xB1,
+       MAX77705_CHG_REG_INT_OK                 = 0xB2,
+       MAX77705_CHG_REG_DETAILS_00             = 0xB3,
+       MAX77705_CHG_REG_DETAILS_01             = 0xB4,
+       MAX77705_CHG_REG_DETAILS_02             = 0xB5,
+       MAX77705_CHG_REG_DTLS_03                = 0xB6,
+       MAX77705_CHG_REG_CNFG_00                = 0xB7,
+       MAX77705_CHG_REG_CNFG_01                = 0xB8,
+       MAX77705_CHG_REG_CNFG_02                = 0xB9,
+       MAX77705_CHG_REG_CNFG_03                = 0xBA,
+       MAX77705_CHG_REG_CNFG_04                = 0xBB,
+       MAX77705_CHG_REG_CNFG_05                = 0xBC,
+       MAX77705_CHG_REG_CNFG_06                = 0xBD,
+       MAX77705_CHG_REG_CNFG_07                = 0xBE,
+       MAX77705_CHG_REG_CNFG_08                = 0xBF,
+       MAX77705_CHG_REG_CNFG_09                = 0xC0,
+       MAX77705_CHG_REG_CNFG_10                = 0xC1,
+       MAX77705_CHG_REG_CNFG_11                = 0xC2,
+       MAX77705_CHG_REG_CNFG_12                = 0xC3,
+       MAX77705_CHG_REG_CNFG_13                = 0xC4,
+       MAX77705_CHG_REG_CNFG_14                = 0xC5,
+       MAX77705_CHG_REG_SAFEOUT_CTRL           = 0xC6,
+
+       MAX77705_PMIC_REG_END,
+};
+
+enum max77705_charger_battery_state {
+       MAX77705_BATTERY_NOBAT,
+       MAX77705_BATTERY_PREQUALIFICATION,
+       MAX77705_BATTERY_DEAD,
+       MAX77705_BATTERY_GOOD,
+       MAX77705_BATTERY_LOWVOLTAGE,
+       MAX77705_BATTERY_OVERVOLTAGE,
+       MAX77705_BATTERY_RESERVED,
+};
+
+enum max77705_charger_charge_type {
+       MAX77705_CHARGER_CONSTANT_CURRENT       = 1,
+       MAX77705_CHARGER_CONSTANT_VOLTAGE,
+       MAX77705_CHARGER_END_OF_CHARGE,
+       MAX77705_CHARGER_DONE,
+};
+
+/* Slave addr = 0x6C : Fuelgauge */
+enum max77705_fuelgauge_reg {
+       STATUS_REG                              = 0x00,
+       VALRT_THRESHOLD_REG                     = 0x01,
+       TALRT_THRESHOLD_REG                     = 0x02,
+       SALRT_THRESHOLD_REG                     = 0x03,
+       REMCAP_REP_REG                          = 0x05,
+       SOCREP_REG                              = 0x06,
+       TEMPERATURE_REG                         = 0x08,
+       VCELL_REG                               = 0x09,
+       TIME_TO_EMPTY_REG                       = 0x11,
+       FULLSOCTHR_REG                          = 0x13,
+       CURRENT_REG                             = 0x0A,
+       AVG_CURRENT_REG                         = 0x0B,
+       SOCMIX_REG                              = 0x0D,
+       SOCAV_REG                               = 0x0E,
+       REMCAP_MIX_REG                          = 0x0F,
+       FULLCAP_REG                             = 0x10,
+       RFAST_REG                               = 0x15,
+       AVR_TEMPERATURE_REG                     = 0x16,
+       CYCLES_REG                              = 0x17,
+       DESIGNCAP_REG                           = 0x18,
+       AVR_VCELL_REG                           = 0x19,
+       TIME_TO_FULL_REG                        = 0x20,
+       CONFIG_REG                              = 0x1D,
+       ICHGTERM_REG                            = 0x1E,
+       REMCAP_AV_REG                           = 0x1F,
+       FULLCAP_NOM_REG                         = 0x23,
+       LEARN_CFG_REG                           = 0x28,
+       FILTER_CFG_REG                          = 0x29,
+       MISCCFG_REG                             = 0x2B,
+       QRTABLE20_REG                           = 0x32,
+       FULLCAP_REP_REG                         = 0x35,
+       RCOMP_REG                               = 0x38,
+       VEMPTY_REG                              = 0x3A,
+       FSTAT_REG                               = 0x3D,
+       DISCHARGE_THRESHOLD_REG                 = 0x40,
+       QRTABLE30_REG                           = 0x42,
+       ISYS_REG                                = 0x43,
+       DQACC_REG                               = 0x45,
+       DPACC_REG                               = 0x46,
+       AVGISYS_REG                             = 0x4B,
+       QH_REG                                  = 0x4D,
+       VSYS_REG                                = 0xB1,
+       TALRTTH2_REG                            = 0xB2,
+       /* "not used REG(0xB2)" is for checking fuelgague init result. */
+       FG_INIT_RESULT_REG                      = TALRTTH2_REG,
+       VBYP_REG                                = 0xB3,
+       CONFIG2_REG                             = 0xBB,
+       IIN_REG                                 = 0xD0,
+       OCV_REG                                 = 0xEE,
+       VFOCV_REG                               = 0xFB,
+       VFSOC_REG                               = 0xFF,
+
+       MAX77705_FG_END,
+};
+
+enum max77705_irq_source {
+       SYS_INT = 0,
+       CHG_INT,
+       FUEL_INT,
+       MAX77705_IRQ_GROUP_NR,
+};
+
+
+#define MAX77705_REG_MAINCTRL1_BIASEN          BIT(7)
+
+/* Slave addr = 0x94: RGB LED */
+enum max77705_led_reg {
+       MAX77705_RGBLED_REG_LEDEN                       = 0x30,
+       MAX77705_RGBLED_REG_LED0BRT                     = 0x31,
+       MAX77705_RGBLED_REG_LED1BRT                     = 0x32,
+       MAX77705_RGBLED_REG_LED2BRT                     = 0x33,
+       MAX77705_RGBLED_REG_LED3BRT                     = 0x34,
+       MAX77705_RGBLED_REG_LEDRMP                      = 0x36,
+       MAX77705_RGBLED_REG_LEDBLNK                     = 0x38,
+       MAX77705_LED_REG_END,
+};
+
+enum max77705_irq {
+       /* PMIC; TOPSYS */
+       MAX77705_SYSTEM_IRQ_BSTEN_INT,
+       MAX77705_SYSTEM_IRQ_SYSUVLO_INT,
+       MAX77705_SYSTEM_IRQ_SYSOVLO_INT,
+       MAX77705_SYSTEM_IRQ_TSHDN_INT,
+       MAX77705_SYSTEM_IRQ_TM_INT,
+
+       /* PMIC; Charger */
+       MAX77705_CHG_IRQ_BYP_I,
+       MAX77705_CHG_IRQ_BAT_I,
+       MAX77705_CHG_IRQ_CHG_I,
+       MAX77705_CHG_IRQ_WCIN_I,
+       MAX77705_CHG_IRQ_CHGIN_I,
+       MAX77705_CHG_IRQ_AICL_I,
+
+       /* Fuelgauge */
+       MAX77705_FG_IRQ_ALERT,
+
+       MAX77705_IRQ_NR,
+};
+
+struct max77705_dev {
+       struct device *dev;
+       struct i2c_client *i2c; /* 0xCC; Haptic, PMIC */
+       struct i2c_client *charger; /* 0xD2; Charger */
+       struct i2c_client *fuelgauge; /* 0x6C; Fuelgauge */
+       struct i2c_client *muic; /* 0x4A; MUIC */
+       struct i2c_client *debug; /* 0xC4; Debug */
+       struct mutex i2c_lock;
+
+       struct regmap *regmap;
+       struct regmap *regmap_fg;
+       struct regmap *regmap_charger;
+       struct regmap *regmap_leds;
+
+       int type;
+
+       int irq;
+       int irq_base;
+       int irq_masks_cur[MAX77705_IRQ_GROUP_NR];
+       int irq_masks_cache[MAX77705_IRQ_GROUP_NR];
+       bool wakeup;
+       struct mutex irqlock;
+
+#ifdef CONFIG_HIBERNATION
+       /* For hibernation */
+       u8 reg_pmic_dump[MAX77705_PMIC_REG_END];
+       u8 reg_muic_dump[MAX77705_USBC_REG_END];
+       u8 reg_led_dump[MAX77705_LED_REG_END];
+#endif
+
+       /* pmic VER/REV register */
+       u8 pmic_rev;    /* pmic Rev */
+       u8 pmic_ver;    /* pmic version */
+
+       u8 cc_booting_complete;
+
+       wait_queue_head_t queue_empty_wait_q;
+       int doing_irq;
+       int is_usbc_queue;
+
+       struct max77705_platform_data *pdata;
+};
+
+enum max77705_types {
+       TYPE_MAX77705,
+};
+
+
+/**
+ * Unmask sub device interrupts on device level
+ *
+ * @param max77705 - device structure
+ * @param mask - sub device interrupts to unmask
+ */
+extern inline int max77705_irq_unmask_subdevice(struct max77705_dev *max77705, 
unsigned int mask);
+
+/**
+ * Same as max77705_irq_unmask_device, but for masking.
+ */
+extern inline int max77705_irq_mask_subdevice(struct max77705_dev *max77705, 
unsigned int mask);
+
+
+extern int max77705_irq_init(struct max77705_dev *max77705);
+
+#endif /* __LINUX_MFD_MAX77705_PRIV_H */
diff --git a/include/linux/mfd/max77705.h b/include/linux/mfd/max77705.h
new file mode 100644
index 000000000000..e2d757c77973
--- /dev/null
+++ b/include/linux/mfd/max77705.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * max77705.h - Driver for the Maxim 77705
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __MAX77705_H__
+#define __MAX77705_H__
+
+#define MFD_DEV_NAME "max77705"
+
+struct max77705_platform_data {
+       struct power_supply_battery_info *bat_info;
+};
+
+#endif /* __MAX77705_H__ */
+

-- 
2.39.2


Reply via email to