diff mbox series

[RFC,2/5] mfd: add 88pm88x driver

Message ID 20231217131838.7569-3-karelb@gimli.ms.mff.cuni.cz
State New
Headers show
Series support for Marvell 88PM886 PMIC | expand

Commit Message

Karel Balej Dec. 17, 2023, 1:17 p.m. UTC
From: Karel Balej <balejk@matfyz.cz>

Marvell 88PM880 and 8PM886 are two similar PMICs with mostly matching
register mapping. They provide various functions such as onkey, battery,
charger and regulators.

Add support for 88PM886 found for instance in the samsung,coreprimevelte
smartphone with which this was tested. Support for 88PM880 is not
implemented here but should be straightforward to add.

Implement only the most basic support omitting the currently unused
registers and I2C subclients which should thus be added with the
respective subdevices. However, add support for the onkey already.

Signed-off-by: Karel Balej <balejk@matfyz.cz>
---
 drivers/mfd/88pm88x.c       | 199 ++++++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig         |  11 ++
 drivers/mfd/Makefile        |   1 +
 include/linux/mfd/88pm88x.h |  60 +++++++++++
 4 files changed, 271 insertions(+)
 create mode 100644 drivers/mfd/88pm88x.c
 create mode 100644 include/linux/mfd/88pm88x.h

Comments

Lee Jones Jan. 25, 2024, 12:26 p.m. UTC | #1
On Sun, 17 Dec 2023, Karel Balej wrote:

> From: Karel Balej <balejk@matfyz.cz>
> 
> Marvell 88PM880 and 8PM886 are two similar PMICs with mostly matching
> register mapping. They provide various functions such as onkey, battery,
> charger and regulators.
> 
> Add support for 88PM886 found for instance in the samsung,coreprimevelte
> smartphone with which this was tested. Support for 88PM880 is not
> implemented here but should be straightforward to add.
> 
> Implement only the most basic support omitting the currently unused
> registers and I2C subclients which should thus be added with the
> respective subdevices. However, add support for the onkey already.
> 
> Signed-off-by: Karel Balej <balejk@matfyz.cz>
> ---
>  drivers/mfd/88pm88x.c       | 199 ++++++++++++++++++++++++++++++++++++
>  drivers/mfd/Kconfig         |  11 ++
>  drivers/mfd/Makefile        |   1 +
>  include/linux/mfd/88pm88x.h |  60 +++++++++++
>  4 files changed, 271 insertions(+)
>  create mode 100644 drivers/mfd/88pm88x.c
>  create mode 100644 include/linux/mfd/88pm88x.h
> 
> diff --git a/drivers/mfd/88pm88x.c b/drivers/mfd/88pm88x.c
> new file mode 100644
> index 000000000000..5db6c65b667d
> --- /dev/null
> +++ b/drivers/mfd/88pm88x.c
> @@ -0,0 +1,199 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/core.h>
> +#include <linux/notifier.h>
> +#include <linux/reboot.h>

Alphabetical

> +#include <linux/mfd/88pm88x.h>
> +
> +/* interrupt status registers */

Use correct grammar in comments, including capital letters.

 - Applies throughout

The comment is not required - we can see what they are from the
nomenclature.

> +#define PM88X_REG_INT_STATUS1			0x05
> +
> +#define PM88X_REG_INT_ENA_1			0x0a
> +#define PM88X_INT_ENA1_ONKEY			BIT(0)
> +
> +enum pm88x_irq_number {
> +	PM88X_IRQ_ONKEY,
> +
> +	PM88X_MAX_IRQ
> +};

An enum for a single 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 reg_sequence pm886_presets[] = {
> +	/* disable watchdog */
> +	REG_SEQ0(PM88X_REG_WDOG, 0x01),

Easier to read if you place spaces between them.

> +	/* GPIO1: DVC, GPIO0: input */
> +	REG_SEQ0(PM88X_REG_GPIO_CTRL1, 0x40),

Shouldn't you set these up using Pintrl?

> +	/* GPIO2: input */
> +	REG_SEQ0(PM88X_REG_GPIO_CTRL2, 0x00),
> +	/* DVC2, DVC1 */

Please unify all of the comments.

They all use a different structure.

> +	REG_SEQ0(PM88X_REG_GPIO_CTRL3, 0x44),
> +	/* GPIO5V_1:input, GPIO5V_2: input */
> +	REG_SEQ0(PM88X_REG_GPIO_CTRL4, 0x00),
> +	/* output 32 kHz from XO */
> +	REG_SEQ0(PM88X_REG_AON_CTRL2, 0x2a),
> +	/* OSC_FREERUN = 1, to lock FLL */
> +	REG_SEQ0(PM88X_REG_BK_OSC_CTRL1, 0x0f),
> +	/* XO_LJ = 1, enable low jitter for 32 kHz */
> +	REG_SEQ0(PM88X_REG_LOWPOWER2, 0x20),
> +	/* OV_VSYS and UV_VSYS1 comparators on VSYS disabled, VSYS_OVER_TH : 5.6V */
> +	REG_SEQ0(PM88X_REG_LOWPOWER4, 0xc8),
> +	/* set the duty cycle of charger DC/DC to max */
> +	REG_SEQ0(PM88X_REG_BK_OSC_CTRL3, 0xc0),

These all looks like they should be handled in their respective drivers?

"patch"ing these in seems like a hack.

> +};

Why this instead of 
> +static struct resource onkey_resources[] = {
> +	DEFINE_RES_IRQ_NAMED(PM88X_IRQ_ONKEY, "88pm88x-onkey"),
> +};
> +
> +static struct mfd_cell pm88x_devs[] = {
> +	{
> +		.name = "88pm88x-onkey",
> +		.num_resources = ARRAY_SIZE(onkey_resources),
> +		.resources = onkey_resources,
> +		.id = -1,
> +	},
> +};

It's not an MFD if it only supports a single device.

> +static struct pm88x_data pm886_a1_data = {
> +	.whoami = PM886_A1_WHOAMI,
> +	.presets = pm886_presets,
> +	.num_presets = ARRAY_SIZE(pm886_presets),
> +};

Just pass the device ID through DT's .data, then match on that instead
of passing pointer to random data structures.

> +static const struct regmap_config pm88x_i2c_regmap = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = 0xfe,

Define this please.

> +};
> +
> +static int pm88x_power_off_handler(struct sys_off_data *data)

'data' is a terrible variable name.  Please change throughout.

> +{
> +	struct pm88x_chip *chip = data->cb_data;
> +	int ret;
> +
> +	ret = regmap_update_bits(chip->regmaps[PM88X_REGMAP_BASE], PM88X_REG_MISC_CONFIG1,
> +			PM88X_SW_PDOWN, PM88X_SW_PDOWN);
> +	if (ret) {
> +		dev_err(&chip->client->dev, "Failed to power off the device: %d\n", ret);
> +		return NOTIFY_BAD;
> +	}
> +	return NOTIFY_DONE;
> +}
> +
> +static int pm88x_setup_irq(struct pm88x_chip *chip)
> +{
> +	int ret;
> +
> +	/* set interrupt clearing mode to clear on write */
> +	ret = regmap_update_bits(chip->regmaps[PM88X_REGMAP_BASE], PM88X_REG_MISC_CONFIG2,
> +			PM88X_INT_INV | PM88X_INT_CLEAR | PM88X_INT_MASK_MODE,
> +			PM88X_INT_WC);
> +	if (ret) {
> +		dev_err(&chip->client->dev, "Failed to set interrupt clearing mode: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_regmap_add_irq_chip(&chip->client->dev, chip->regmaps[PM88X_REGMAP_BASE],
> +			chip->client->irq, IRQF_ONESHOT, -1, &pm88x_regmap_irq_chip,
> +			&chip->irq_data);
> +	if (ret) {
> +		dev_err(&chip->client->dev, "Failed to request IRQ: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int pm88x_probe(struct i2c_client *client)
> +{
> +	struct pm88x_chip *chip;
> +	int ret = 0;
> +	unsigned int chip_id;
> +
> +	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return -ENOMEM;
> +
> +	chip->client = client;
> +	chip->data = device_get_match_data(&client->dev);

Now where is this being past to?

What is going to consume this?

> +	i2c_set_clientdata(client, chip);
> +
> +	device_init_wakeup(&client->dev, 1);
> +
> +	chip->regmaps[PM88X_REGMAP_BASE] = devm_regmap_init_i2c(client, &pm88x_i2c_regmap);
> +	if (IS_ERR(chip->regmaps[PM88X_REGMAP_BASE])) {

Just define different regmaps if you really need them.

I only see one being used anyway.

> +		ret = PTR_ERR(chip->regmaps[PM88X_REGMAP_BASE]);
> +		dev_err(&client->dev, "Failed to initialize regmap: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_read(chip->regmaps[PM88X_REGMAP_BASE], PM88X_REG_ID, &chip_id);
> +	if (ret) {
> +		dev_err(&client->dev, "Failed to read chip ID: %d\n", ret);
> +		return ret;
> +	}
> +	if (chip->data->whoami != chip_id) {
> +		dev_err(&client->dev, "Device reported wrong chip ID: %u\n", chip_id);

Use dev_err_probe() throughout.

> +		return -EINVAL;
> +	}
> +
> +	ret = pm88x_setup_irq(chip);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_mfd_add_devices(&client->dev, 0, pm88x_devs, ARRAY_SIZE(pm88x_devs),
> +			NULL, 0, regmap_irq_get_domain(chip->irq_data));
> +	if (ret) {
> +		dev_err(&client->dev, "Failed to add devices: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_register_patch(chip->regmaps[PM88X_REGMAP_BASE], chip->data->presets,
> +			chip->data->num_presets);
> +	if (ret) {
> +		dev_err(&client->dev, "Failed to register regmap patch: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_register_power_off_handler(&client->dev, pm88x_power_off_handler, chip);
> +	if (ret) {
> +		dev_err(&client->dev, "Failed to register power off handler: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +const struct of_device_id pm88x_of_match[] = {
> +	{ .compatible = "marvell,88pm886-a1", .data = &pm886_a1_data },
> +	{ },
> +};
> +
> +static struct i2c_driver pm88x_i2c_driver = {
> +	.driver = {
> +		.name = "88pm88x",
> +		.of_match_table = of_match_ptr(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");
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 90ce58fd629e..c593279fd766 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -794,6 +794,17 @@ config MFD_88PM860X
>  	  select individual components like voltage regulators, RTC and
>  	  battery-charger under the corresponding menus.
>  
> +config MFD_88PM88X

"MFD_88PM88X_PMIC"?

> +	bool "Marvell 88PM886"

"Marvell 88PM886 PMIC"?

> +	depends on I2C=y
> +	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
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index c66f07edcd0e..14e42b045c92 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -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)	+= 88pm88x.o
>  obj-$(CONFIG_MFD_ACT8945A)	+= act8945a.o
>  obj-$(CONFIG_MFD_SM501)		+= sm501.o
>  obj-$(CONFIG_ARCH_BCM2835)	+= bcm2835-pm.o
> diff --git a/include/linux/mfd/88pm88x.h b/include/linux/mfd/88pm88x.h
> new file mode 100644
> index 000000000000..a34c57447827
> --- /dev/null
> +++ b/include/linux/mfd/88pm88x.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __LINUX_MFD_88PM88X_H
> +#define __LINUX_MFD_88PM88X_H

Drop the LINUX part.

> +
> +#include <linux/mfd/core.h>
> +
> +#define PM886_A1_WHOAMI		0xa1

s/WHOAMI/CHIP_ID/

> +#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_REG_WDOG			0x1d
> +
> +#define PM88X_REG_LOWPOWER2		0x21
> +#define PM88X_REG_LOWPOWER4		0x23
> +
> +#define PM88X_REG_GPIO_CTRL1		0x30

These don't really need to be spaced out, do they?

> +#define PM88X_REG_GPIO_CTRL2		0x31
> +
> +#define PM88X_REG_GPIO_CTRL3		0x32
> +
> +#define PM88X_REG_GPIO_CTRL4		0x33
> +
> +#define PM88X_REG_BK_OSC_CTRL1		0x50
> +#define PM88X_REG_BK_OSC_CTRL3		0x52
> +
> +#define PM88X_REG_AON_CTRL2		0xe2
> +
> +enum pm88x_regmap_index {
> +	PM88X_REGMAP_BASE,
> +
> +	PM88X_REGMAP_NR
> +};
> +
> +struct pm88x_data {
> +	unsigned int whoami;
> +	struct reg_sequence *presets;
> +	unsigned int num_presets;
> +};
> +
> +struct pm88x_chip {
> +	struct i2c_client *client;
> +	struct regmap_irq_chip_data *irq_data;

Group this with the other regmap related member(s).

What are you using this for?

> +	const struct pm88x_data *data;

Remove this.

> +	struct regmap *regmaps[PM88X_REGMAP_NR];
> +};
> +#endif /* __LINUX_MFD_88PM88X_H */
> -- 
> 2.43.0
>
diff mbox series

Patch

diff --git a/drivers/mfd/88pm88x.c b/drivers/mfd/88pm88x.c
new file mode 100644
index 000000000000..5db6c65b667d
--- /dev/null
+++ b/drivers/mfd/88pm88x.c
@@ -0,0 +1,199 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+
+#include <linux/mfd/88pm88x.h>
+
+/* interrupt status registers */
+#define PM88X_REG_INT_STATUS1			0x05
+
+#define PM88X_REG_INT_ENA_1			0x0a
+#define PM88X_INT_ENA1_ONKEY			BIT(0)
+
+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 reg_sequence pm886_presets[] = {
+	/* disable watchdog */
+	REG_SEQ0(PM88X_REG_WDOG, 0x01),
+	/* GPIO1: DVC, GPIO0: input */
+	REG_SEQ0(PM88X_REG_GPIO_CTRL1, 0x40),
+	/* GPIO2: input */
+	REG_SEQ0(PM88X_REG_GPIO_CTRL2, 0x00),
+	/* DVC2, DVC1 */
+	REG_SEQ0(PM88X_REG_GPIO_CTRL3, 0x44),
+	/* GPIO5V_1:input, GPIO5V_2: input */
+	REG_SEQ0(PM88X_REG_GPIO_CTRL4, 0x00),
+	/* output 32 kHz from XO */
+	REG_SEQ0(PM88X_REG_AON_CTRL2, 0x2a),
+	/* OSC_FREERUN = 1, to lock FLL */
+	REG_SEQ0(PM88X_REG_BK_OSC_CTRL1, 0x0f),
+	/* XO_LJ = 1, enable low jitter for 32 kHz */
+	REG_SEQ0(PM88X_REG_LOWPOWER2, 0x20),
+	/* OV_VSYS and UV_VSYS1 comparators on VSYS disabled, VSYS_OVER_TH : 5.6V */
+	REG_SEQ0(PM88X_REG_LOWPOWER4, 0xc8),
+	/* set the duty cycle of charger DC/DC to max */
+	REG_SEQ0(PM88X_REG_BK_OSC_CTRL3, 0xc0),
+};
+
+static struct resource onkey_resources[] = {
+	DEFINE_RES_IRQ_NAMED(PM88X_IRQ_ONKEY, "88pm88x-onkey"),
+};
+
+static struct mfd_cell pm88x_devs[] = {
+	{
+		.name = "88pm88x-onkey",
+		.num_resources = ARRAY_SIZE(onkey_resources),
+		.resources = onkey_resources,
+		.id = -1,
+	},
+};
+
+static struct pm88x_data pm886_a1_data = {
+	.whoami = PM886_A1_WHOAMI,
+	.presets = pm886_presets,
+	.num_presets = ARRAY_SIZE(pm886_presets),
+};
+
+static const struct regmap_config pm88x_i2c_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xfe,
+};
+
+static int pm88x_power_off_handler(struct sys_off_data *data)
+{
+	struct pm88x_chip *chip = data->cb_data;
+	int ret;
+
+	ret = regmap_update_bits(chip->regmaps[PM88X_REGMAP_BASE], PM88X_REG_MISC_CONFIG1,
+			PM88X_SW_PDOWN, PM88X_SW_PDOWN);
+	if (ret) {
+		dev_err(&chip->client->dev, "Failed to power off the device: %d\n", ret);
+		return NOTIFY_BAD;
+	}
+	return NOTIFY_DONE;
+}
+
+static int pm88x_setup_irq(struct pm88x_chip *chip)
+{
+	int ret;
+
+	/* set interrupt clearing mode to clear on write */
+	ret = regmap_update_bits(chip->regmaps[PM88X_REGMAP_BASE], PM88X_REG_MISC_CONFIG2,
+			PM88X_INT_INV | PM88X_INT_CLEAR | PM88X_INT_MASK_MODE,
+			PM88X_INT_WC);
+	if (ret) {
+		dev_err(&chip->client->dev, "Failed to set interrupt clearing mode: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_regmap_add_irq_chip(&chip->client->dev, chip->regmaps[PM88X_REGMAP_BASE],
+			chip->client->irq, IRQF_ONESHOT, -1, &pm88x_regmap_irq_chip,
+			&chip->irq_data);
+	if (ret) {
+		dev_err(&chip->client->dev, "Failed to request IRQ: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pm88x_probe(struct i2c_client *client)
+{
+	struct pm88x_chip *chip;
+	int ret = 0;
+	unsigned int chip_id;
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client = client;
+	chip->data = device_get_match_data(&client->dev);
+	i2c_set_clientdata(client, chip);
+
+	device_init_wakeup(&client->dev, 1);
+
+	chip->regmaps[PM88X_REGMAP_BASE] = devm_regmap_init_i2c(client, &pm88x_i2c_regmap);
+	if (IS_ERR(chip->regmaps[PM88X_REGMAP_BASE])) {
+		ret = PTR_ERR(chip->regmaps[PM88X_REGMAP_BASE]);
+		dev_err(&client->dev, "Failed to initialize regmap: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_read(chip->regmaps[PM88X_REGMAP_BASE], PM88X_REG_ID, &chip_id);
+	if (ret) {
+		dev_err(&client->dev, "Failed to read chip ID: %d\n", ret);
+		return ret;
+	}
+	if (chip->data->whoami != chip_id) {
+		dev_err(&client->dev, "Device reported wrong chip ID: %u\n", chip_id);
+		return -EINVAL;
+	}
+
+	ret = pm88x_setup_irq(chip);
+	if (ret)
+		return ret;
+
+	ret = devm_mfd_add_devices(&client->dev, 0, pm88x_devs, ARRAY_SIZE(pm88x_devs),
+			NULL, 0, regmap_irq_get_domain(chip->irq_data));
+	if (ret) {
+		dev_err(&client->dev, "Failed to add devices: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_register_patch(chip->regmaps[PM88X_REGMAP_BASE], chip->data->presets,
+			chip->data->num_presets);
+	if (ret) {
+		dev_err(&client->dev, "Failed to register regmap patch: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_register_power_off_handler(&client->dev, pm88x_power_off_handler, chip);
+	if (ret) {
+		dev_err(&client->dev, "Failed to register power off handler: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+const struct of_device_id pm88x_of_match[] = {
+	{ .compatible = "marvell,88pm886-a1", .data = &pm886_a1_data },
+	{ },
+};
+
+static struct i2c_driver pm88x_i2c_driver = {
+	.driver = {
+		.name = "88pm88x",
+		.of_match_table = of_match_ptr(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");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 90ce58fd629e..c593279fd766 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -794,6 +794,17 @@  config MFD_88PM860X
 	  select individual components like voltage regulators, RTC and
 	  battery-charger under the corresponding menus.
 
+config MFD_88PM88X
+	bool "Marvell 88PM886"
+	depends on I2C=y
+	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
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07edcd0e..14e42b045c92 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -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)	+= 88pm88x.o
 obj-$(CONFIG_MFD_ACT8945A)	+= act8945a.o
 obj-$(CONFIG_MFD_SM501)		+= sm501.o
 obj-$(CONFIG_ARCH_BCM2835)	+= bcm2835-pm.o
diff --git a/include/linux/mfd/88pm88x.h b/include/linux/mfd/88pm88x.h
new file mode 100644
index 000000000000..a34c57447827
--- /dev/null
+++ b/include/linux/mfd/88pm88x.h
@@ -0,0 +1,60 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __LINUX_MFD_88PM88X_H
+#define __LINUX_MFD_88PM88X_H
+
+#include <linux/mfd/core.h>
+
+#define PM886_A1_WHOAMI		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_REG_WDOG			0x1d
+
+#define PM88X_REG_LOWPOWER2		0x21
+#define PM88X_REG_LOWPOWER4		0x23
+
+#define PM88X_REG_GPIO_CTRL1		0x30
+
+#define PM88X_REG_GPIO_CTRL2		0x31
+
+#define PM88X_REG_GPIO_CTRL3		0x32
+
+#define PM88X_REG_GPIO_CTRL4		0x33
+
+#define PM88X_REG_BK_OSC_CTRL1		0x50
+#define PM88X_REG_BK_OSC_CTRL3		0x52
+
+#define PM88X_REG_AON_CTRL2		0xe2
+
+enum pm88x_regmap_index {
+	PM88X_REGMAP_BASE,
+
+	PM88X_REGMAP_NR
+};
+
+struct pm88x_data {
+	unsigned int whoami;
+	struct reg_sequence *presets;
+	unsigned int num_presets;
+};
+
+struct pm88x_chip {
+	struct i2c_client *client;
+	struct regmap_irq_chip_data *irq_data;
+	const struct pm88x_data *data;
+	struct regmap *regmaps[PM88X_REGMAP_NR];
+};
+#endif /* __LINUX_MFD_88PM88X_H */