new file mode 100644
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/88pm88x.h>
+
+#define PM88X_REG_INT_STATUS1 0x05
+
+#define PM88X_REG_INT_ENA_1 0x0a
+#define PM88X_INT_ENA1_ONKEY BIT(0)
+
+#define PM88X_REGMAP_CONF_REG_BITS 8
+#define PM88X_REGMAP_CONF_VAL_BITS 8
+#define PM88X_REGMAP_CONF_MAX_REG 0xfe
+
+enum pm88x_irq_number {
+ PM88X_IRQ_ONKEY,
+
+ PM88X_MAX_IRQ
+};
+
+static struct regmap_irq pm88x_regmap_irqs[] = {
+ REGMAP_IRQ_REG(PM88X_IRQ_ONKEY, 0, PM88X_INT_ENA1_ONKEY),
+};
+
+static struct regmap_irq_chip pm88x_regmap_irq_chip = {
+ .name = "88pm88x",
+ .irqs = pm88x_regmap_irqs,
+ .num_irqs = ARRAY_SIZE(pm88x_regmap_irqs),
+ .num_regs = 4,
+ .status_base = PM88X_REG_INT_STATUS1,
+ .ack_base = PM88X_REG_INT_STATUS1,
+ .unmask_base = PM88X_REG_INT_ENA_1,
+};
+
+static struct resource pm88x_onkey_resources[] = {
+ DEFINE_RES_IRQ_NAMED(PM88X_IRQ_ONKEY, "88pm88x-onkey"),
+};
+
+static struct mfd_cell pm886_devs[] = {
+ {
+ .name = "88pm88x-onkey",
+ .of_compatible = "marvell,88pm88x-onkey",
+ .num_resources = ARRAY_SIZE(pm88x_onkey_resources),
+ .resources = pm88x_onkey_resources,
+ },
+ {
+ .name = "88pm88x-regulator",
+ .id = PM88X_REGULATOR_ID_LDO2,
+ },
+ {
+ .name = "88pm88x-regulator",
+ .id = PM88X_REGULATOR_ID_LDO15,
+ },
+ {
+ .name = "88pm88x-regulator",
+ .id = PM886_REGULATOR_ID_BUCK2,
+ },
+};
+
+static const struct regmap_config pm88x_i2c_regmap = {
+ .reg_bits = PM88X_REGMAP_CONF_REG_BITS,
+ .val_bits = PM88X_REGMAP_CONF_VAL_BITS,
+ .max_register = PM88X_REGMAP_CONF_MAX_REG,
+};
+
+static int pm88x_power_off_handler(struct sys_off_data *sys_off_data)
+{
+ struct pm88x_chip *chip = sys_off_data->cb_data;
+ struct regmap *regmap = chip->regmaps[PM88X_REGMAP_BASE];
+ struct device *dev = &chip->client->dev;
+ int err;
+
+ err = regmap_update_bits(regmap, PM88X_REG_MISC_CONFIG1, PM88X_SW_PDOWN,
+ PM88X_SW_PDOWN);
+ if (err) {
+ dev_err(dev, "Failed to power off the device: %d\n", err);
+ return NOTIFY_BAD;
+ }
+ return NOTIFY_DONE;
+}
+
+static int pm88x_initialize_subregmaps(struct pm88x_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+ struct i2c_client *page;
+ struct regmap *regmap;
+ int err;
+
+ /* LDO page */
+ page = devm_i2c_new_dummy_device(dev, chip->client->adapter,
+ chip->client->addr + PM88X_PAGE_OFFSET_LDO);
+ if (IS_ERR(page)) {
+ err = PTR_ERR(page);
+ dev_err(dev, "Failed to initialize LDO client: %d\n", err);
+ return err;
+ }
+ regmap = devm_regmap_init_i2c(page, &pm88x_i2c_regmap);
+ if (IS_ERR(regmap)) {
+ err = PTR_ERR(regmap);
+ dev_err(dev, "Failed to initialize LDO regmap: %d\n", err);
+ return err;
+ }
+ chip->regmaps[PM88X_REGMAP_LDO] = regmap;
+
+ return 0;
+}
+
+static int pm88x_setup_irq(struct pm88x_chip *chip,
+ struct regmap_irq_chip_data **irq_data)
+{
+ struct regmap *regmap = chip->regmaps[PM88X_REGMAP_BASE];
+ struct device *dev = &chip->client->dev;
+ int err;
+
+ /* Set interrupt clearing mode to clear on write. */
+ err = regmap_update_bits(regmap, PM88X_REG_MISC_CONFIG2,
+ PM88X_INT_INV | PM88X_INT_CLEAR | PM88X_INT_MASK_MODE,
+ PM88X_INT_WC);
+ if (err) {
+ dev_err(dev, "Failed to set interrupt clearing mode: %d\n", err);
+ return err;
+ }
+
+ err = devm_regmap_add_irq_chip(dev, regmap, chip->client->irq,
+ IRQF_ONESHOT, -1, &pm88x_regmap_irq_chip,
+ irq_data);
+ if (err) {
+ dev_err(dev, "Failed to request IRQ: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int pm88x_probe(struct i2c_client *client)
+{
+ struct regmap_irq_chip_data *irq_data;
+ struct device *dev = &client->dev;
+ struct pm88x_chip *chip;
+ struct regmap *regmap;
+ unsigned int chip_id;
+ int err;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->whoami = (uintptr_t)device_get_match_data(dev);
+ i2c_set_clientdata(client, chip);
+
+ regmap = devm_regmap_init_i2c(client, &pm88x_i2c_regmap);
+ if (IS_ERR(regmap)) {
+ err = PTR_ERR(regmap);
+ return dev_err_probe(dev, err, "Failed to initialize regmap\n");
+ }
+ chip->regmaps[PM88X_REGMAP_BASE] = regmap;
+
+ err = regmap_read(regmap, PM88X_REG_ID, &chip_id);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to read chip ID\n");
+ if (chip->whoami != chip_id)
+ return dev_err_probe(dev, -EINVAL, "Device reported wrong chip ID: %u\n",
+ chip_id);
+
+ err = pm88x_initialize_subregmaps(chip);
+ if (err)
+ return err;
+
+ err = pm88x_setup_irq(chip, &irq_data);
+ if (err)
+ return err;
+
+ err = devm_mfd_add_devices(dev, 0, pm886_devs, ARRAY_SIZE(pm886_devs),
+ NULL, 0, regmap_irq_get_domain(irq_data));
+ if (err)
+ return dev_err_probe(dev, err, "Failed to add devices\n");
+
+ err = devm_register_power_off_handler(dev, pm88x_power_off_handler, chip);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to register power off handler\n");
+
+ device_init_wakeup(dev, device_property_read_bool(dev, "wakeup-source"));
+
+ return 0;
+}
+
+const struct of_device_id pm88x_of_match[] = {
+ { .compatible = "marvell,88pm886-a1", .data = (void *)PM886_A1_CHIP_ID },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pm88x_of_match);
+
+static struct i2c_driver pm88x_i2c_driver = {
+ .driver = {
+ .name = "88pm88x",
+ .of_match_table = pm88x_of_match,
+ },
+ .probe = pm88x_probe,
+};
+module_i2c_driver(pm88x_i2c_driver);
+
+MODULE_DESCRIPTION("Marvell 88PM88X PMIC driver");
+MODULE_AUTHOR("Karel Balej <balejk@matfyz.cz>");
+MODULE_LICENSE("GPL");
@@ -794,6 +794,18 @@ config MFD_88PM860X
select individual components like voltage regulators, RTC and
battery-charger under the corresponding menus.
+config MFD_88PM88X_PMIC
+ bool "Marvell 88PM886 PMIC"
+ depends on I2C=y
+ depends on OF
+ select REGMAP_I2C
+ select REGMAP_IRQ
+ select MFD_CORE
+ help
+ This enables support for Marvell 88PM886 Power Management IC.
+ This includes the I2C driver and the core APIs _only_, you have to
+ select individual components like onkey under the corresponding menus.
+
config MFD_MAX14577
tristate "Maxim Semiconductor MAX14577/77836 MUIC + Charger Support"
depends on I2C
@@ -7,6 +7,7 @@
obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o
obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o
obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
+obj-$(CONFIG_MFD_88PM88X_PMIC) += 88pm88x.o
obj-$(CONFIG_MFD_ACT8945A) += act8945a.o
obj-$(CONFIG_MFD_SM501) += sm501.o
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
new file mode 100644
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __MFD_88PM88X_H
+#define __MFD_88PM88X_H
+
+#include <linux/mfd/core.h>
+
+#define PM886_A1_CHIP_ID 0xa1
+
+#define PM88X_REG_ID 0x00
+
+#define PM88X_REG_STATUS1 0x01
+#define PM88X_ONKEY_STS1 BIT(0)
+
+#define PM88X_REG_MISC_CONFIG1 0x14
+#define PM88X_SW_PDOWN BIT(5)
+
+#define PM88X_REG_MISC_CONFIG2 0x15
+#define PM88X_INT_INV BIT(0)
+#define PM88X_INT_CLEAR BIT(1)
+#define PM88X_INT_RC 0x00
+#define PM88X_INT_WC BIT(1)
+#define PM88X_INT_MASK_MODE BIT(2)
+
+#define PM88X_PAGE_OFFSET_LDO 1
+
+enum pm88x_regulator_id {
+ PM88X_REGULATOR_ID_LDO2,
+ PM88X_REGULATOR_ID_LDO15,
+ PM886_REGULATOR_ID_BUCK2,
+
+ PM88X_REGULATOR_ID_SENTINEL
+};
+
+enum pm88x_regmap_index {
+ PM88X_REGMAP_BASE,
+ PM88X_REGMAP_LDO,
+
+ PM88X_REGMAP_NR
+};
+
+struct pm88x_chip {
+ struct i2c_client *client;
+ unsigned int whoami;
+ struct regmap *regmaps[PM88X_REGMAP_NR];
+};
+#endif /* __MFD_88PM88X_H */