diff mbox series

regulator: mt6360: Add support for MT6360 regulator

Message ID 1591254387-10304-1-git-send-email-gene.chen.richtek@gmail.com (mailing list archive)
State New, archived
Headers show
Series regulator: mt6360: Add support for MT6360 regulator | expand

Commit Message

Gene Chen June 4, 2020, 7:06 a.m. UTC
From: Gene Chen <gene_chen@richtek.com>

Add MT6360 regulator driver include 2-channel buck and
6-channel ldo

Signed-off-by: Gene Chen <gene_chen@richtek.com>
base-commit: 098c4adf249c198519a4abebe482b1e6b8c50e47
---
 drivers/regulator/Kconfig            |  10 +
 drivers/regulator/Makefile           |   1 +
 drivers/regulator/mt6360-regulator.c | 571 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/mt6360.h           |   5 +
 4 files changed, 587 insertions(+)
 create mode 100644 drivers/regulator/mt6360-regulator.c

Comments

Mark Brown June 4, 2020, 1:39 p.m. UTC | #1
On Thu, Jun 04, 2020 at 03:06:27PM +0800, Gene Chen wrote:

This looks nice and simple, a few fairly small comments below but high
level it's basically fine.

> --- /dev/null
> +++ b/drivers/regulator/mt6360-regulator.c
> @@ -0,0 +1,571 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 MediaTek Inc.

Please make the entire comment a C++ one so things look more
intentional.

> +	for (i = 0; i < devdata->num_irq_descs; i++) {
> +		irq_desc = devdata->irq_descs + i;
> +		if (unlikely(!irq_desc->name))
> +			continue;

Do we really need an unlikely here?  This shouldn't be a hot path.

> +static int mt6360_regulator_set_mode(
> +				  struct regulator_dev *rdev, unsigned int mode)
> +{

> +	switch (1 << (ffs(mode) - 1)) {
> +	case REGULATOR_MODE_NORMAL:

I don't understand why this isn't just a straight switch on mode?

> +static unsigned int mt6360_regulator_get_mode(struct regulator_dev *rdev)
> +{
> +	const struct mt6360_regulator_desc *desc =
> +			       (const struct mt6360_regulator_desc *)rdev->desc;
> +	int shift = ffs(desc->mode_get_mask) - 1, ret;
> +	unsigned int val = 0;
> +
> +	default:
> +		ret = 0;
> +	}

If we can't parse a valid value from the hardware then that's an error.

> +static int mt6360_regulator_reg_write(void *context,
> +				      unsigned int reg, unsigned int val)
> +{
> +	struct mt6360_regulator_data *mrd = context;
> +	u8 chunk[4] = {0};
> +
> +	/* chunk 0 ->i2c addr, 1 -> reg_addr, 2 -> reg_val 3-> crc8 */
> +	chunk[0] = (mrd->i2c->addr & 0x7f) << 1;
> +	chunk[1] = reg & 0x3f;
> +	chunk[2] = (u8)val;
> +	chunk[3] = crc8(mrd->crc8_table, chunk, 3, 0);
> +	/* also dummy one byte */
> +	return i2c_smbus_write_i2c_block_data(mrd->i2c, chunk[1], 3, chunk + 2);
> +}

Oh, wow - that's a fun I/O interface!

> +static const struct of_device_id __maybe_unused mt6360_regulator_of_id[] = {
> +	{
> +		.compatible = "mediatek,mt6360_pmic",
> +		.data = (void *)&mt6360_pmic_devdata,
> +	},
> +	{
> +		.compatible = "mediatek,mt6360_ldo",
> +		.data = (void *)&mt6360_ldo_devdata,
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, mt6360_regulator_of_id);

I don't see any DT bindings documentation for this, documentation is
required for all new bindings.

> +	mrd->regmap = devm_regmap_init(&(mrd->i2c->dev),
> +				       NULL, mrd, devdata->regmap_config);
> +	if (IS_ERR(mrd->regmap)) {
> +		dev_err(&pdev->dev, "Failed to register regmap\n");
> +		return PTR_ERR(mrd->regmap);
> +	}

This looks like a MFD so it's surprising to see us defining a regmap at
this level.  Why are we doing this?
Randy Dunlap June 4, 2020, 3:06 p.m. UTC | #2
On 6/4/20 12:06 AM, Gene Chen wrote:
> diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
> index f4b72cb..05a3b14 100644
> --- a/drivers/regulator/Kconfig
> +++ b/drivers/regulator/Kconfig
> @@ -680,6 +680,16 @@ config REGULATOR_MT6358
>  	  This driver supports the control of different power rails of device
>  	  through regulator interface.
>  
> +config REGULATOR_MT6360
> +	tristate "MT6360 SubPMIC Regulator"
> +	depends on MFD_MT6360
> +	select CRC8
> +	help
> +	  Say Y here to enable MT6360 regulator support.
> +	  This is support MT6360 PMIC/LDO part include

	  This supports the MT6300 PMIC/LDO part, which includes

> +	  2-channel buck with Thermal Shutdown and Overlord Protection

	                              is that      Overload  ?
Yes, it could be Overlord.

> +	  6-channel High PSRR and Low Dropout LDO.
> +
>  config REGULATOR_MT6380
>  	tristate "MediaTek MT6380 PMIC"
>  	depends on MTK_PMIC_WRAP
Gene Chen June 15, 2020, 9:48 a.m. UTC | #3
Loop MFD maintainer,

Mark Brown <broonie@kernel.org> 於 2020年6月4日 週四 下午9:39寫道:
>
> On Thu, Jun 04, 2020 at 03:06:27PM +0800, Gene Chen wrote:
>
> This looks nice and simple, a few fairly small comments below but high
> level it's basically fine.
>
> > --- /dev/null
> > +++ b/drivers/regulator/mt6360-regulator.c
> > @@ -0,0 +1,571 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2020 MediaTek Inc.
>
> Please make the entire comment a C++ one so things look more
> intentional.
>

ACK

> > +     for (i = 0; i < devdata->num_irq_descs; i++) {
> > +             irq_desc = devdata->irq_descs + i;
> > +             if (unlikely(!irq_desc->name))
> > +                     continue;
>
> Do we really need an unlikely here?  This shouldn't be a hot path.
>

ACK, we will remove it

> > +static int mt6360_regulator_set_mode(
> > +                               struct regulator_dev *rdev, unsigned int mode)
> > +{
>
> > +     switch (1 << (ffs(mode) - 1)) {
> > +     case REGULATOR_MODE_NORMAL:
>
> I don't understand why this isn't just a straight switch on mode?
>

ACK

> > +static unsigned int mt6360_regulator_get_mode(struct regulator_dev *rdev)
> > +{
> > +     const struct mt6360_regulator_desc *desc =
> > +                            (const struct mt6360_regulator_desc *)rdev->desc;
> > +     int shift = ffs(desc->mode_get_mask) - 1, ret;
> > +     unsigned int val = 0;
> > +
> > +     default:
> > +             ret = 0;
> > +     }
>
> If we can't parse a valid value from the hardware then that's an error.
>

ACK

> > +static int mt6360_regulator_reg_write(void *context,
> > +                                   unsigned int reg, unsigned int val)
> > +{
> > +     struct mt6360_regulator_data *mrd = context;
> > +     u8 chunk[4] = {0};
> > +
> > +     /* chunk 0 ->i2c addr, 1 -> reg_addr, 2 -> reg_val 3-> crc8 */
> > +     chunk[0] = (mrd->i2c->addr & 0x7f) << 1;
> > +     chunk[1] = reg & 0x3f;
> > +     chunk[2] = (u8)val;
> > +     chunk[3] = crc8(mrd->crc8_table, chunk, 3, 0);
> > +     /* also dummy one byte */
> > +     return i2c_smbus_write_i2c_block_data(mrd->i2c, chunk[1], 3, chunk + 2);
> > +}
>
> Oh, wow - that's a fun I/O interface!
>
> > +static const struct of_device_id __maybe_unused mt6360_regulator_of_id[] = {
> > +     {
> > +             .compatible = "mediatek,mt6360_pmic",
> > +             .data = (void *)&mt6360_pmic_devdata,
> > +     },
> > +     {
> > +             .compatible = "mediatek,mt6360_ldo",
> > +             .data = (void *)&mt6360_ldo_devdata,
> > +     },
> > +     {},
> > +};
> > +MODULE_DEVICE_TABLE(of, mt6360_regulator_of_id);
>
> I don't see any DT bindings documentation for this, documentation is
> required for all new bindings.
>

ACK, we will update binding document later.

> > +     mrd->regmap = devm_regmap_init(&(mrd->i2c->dev),
> > +                                    NULL, mrd, devdata->regmap_config);
> > +     if (IS_ERR(mrd->regmap)) {
> > +             dev_err(&pdev->dev, "Failed to register regmap\n");
> > +             return PTR_ERR(mrd->regmap);
> > +     }
>
> This looks like a MFD so it's surprising to see us defining a regmap at
> this level.  Why are we doing this?

Because MT6360 include PMU/PMIC/LDO/TYPEC part, only PMIC/LDO i2c R/W
need contain CRC format.
We will update new patch to conclude r/w in mfd driver.
According to different register address with bank number, we can
distinguish different part of module.
Gene Chen June 18, 2020, 11:35 a.m. UTC | #4
Mark Brown <broonie@kernel.org> 於 2020年6月4日 週四 下午9:39寫道:
>
> On Thu, Jun 04, 2020 at 03:06:27PM +0800, Gene Chen wrote:
>
> This looks nice and simple, a few fairly small comments below but high
> level it's basically fine.
>
> > --- /dev/null
> > +++ b/drivers/regulator/mt6360-regulator.c
> > @@ -0,0 +1,571 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2020 MediaTek Inc.
>
> Please make the entire comment a C++ one so things look more
> intentional.
>

ACK

> > +     for (i = 0; i < devdata->num_irq_descs; i++) {
> > +             irq_desc = devdata->irq_descs + i;
> > +             if (unlikely(!irq_desc->name))
> > +                     continue;
>
> Do we really need an unlikely here?  This shouldn't be a hot path.
>
> > +static int mt6360_regulator_set_mode(
> > +                               struct regulator_dev *rdev, unsigned int mode)
> > +{
>
> > +     switch (1 << (ffs(mode) - 1)) {
> > +     case REGULATOR_MODE_NORMAL:
>
> I don't understand why this isn't just a straight switch on mode?
>

ACK, we will fix it

> > +static unsigned int mt6360_regulator_get_mode(struct regulator_dev *rdev)
> > +{
> > +     const struct mt6360_regulator_desc *desc =
> > +                            (const struct mt6360_regulator_desc *)rdev->desc;
> > +     int shift = ffs(desc->mode_get_mask) - 1, ret;
> > +     unsigned int val = 0;
> > +
> > +     default:
> > +             ret = 0;
> > +     }
>
> If we can't parse a valid value from the hardware then that's an error.
>

ACK

> > +static int mt6360_regulator_reg_write(void *context,
> > +                                   unsigned int reg, unsigned int val)
> > +{
> > +     struct mt6360_regulator_data *mrd = context;
> > +     u8 chunk[4] = {0};
> > +
> > +     /* chunk 0 ->i2c addr, 1 -> reg_addr, 2 -> reg_val 3-> crc8 */
> > +     chunk[0] = (mrd->i2c->addr & 0x7f) << 1;
> > +     chunk[1] = reg & 0x3f;
> > +     chunk[2] = (u8)val;
> > +     chunk[3] = crc8(mrd->crc8_table, chunk, 3, 0);
> > +     /* also dummy one byte */
> > +     return i2c_smbus_write_i2c_block_data(mrd->i2c, chunk[1], 3, chunk + 2);
> > +}
>
> Oh, wow - that's a fun I/O interface!
>

MT6360 PMIC/LDO part use CRC to avoid i2c write mistaken

> > +static const struct of_device_id __maybe_unused mt6360_regulator_of_id[] = {
> > +     {
> > +             .compatible = "mediatek,mt6360_pmic",
> > +             .data = (void *)&mt6360_pmic_devdata,
> > +     },
> > +     {
> > +             .compatible = "mediatek,mt6360_ldo",
> > +             .data = (void *)&mt6360_ldo_devdata,
> > +     },
> > +     {},
> > +};
> > +MODULE_DEVICE_TABLE(of, mt6360_regulator_of_id);
>
> I don't see any DT bindings documentation for this, documentation is
> required for all new bindings.
>

ACK, we will update binding document

> > +     mrd->regmap = devm_regmap_init(&(mrd->i2c->dev),
> > +                                    NULL, mrd, devdata->regmap_config);
> > +     if (IS_ERR(mrd->regmap)) {
> > +             dev_err(&pdev->dev, "Failed to register regmap\n");
> > +             return PTR_ERR(mrd->regmap);
> > +     }
>
> This looks like a MFD so it's surprising to see us defining a regmap at
> this level.  Why are we doing this?

because other sub-device (e.g. CHARGER/LED/ADC) no need CRC when i2c R/W
we will merge remgap into mfd, and use "bank" strategy to distinguish
different part
Gene Chen June 18, 2020, 11:38 a.m. UTC | #5
Randy Dunlap <rdunlap@infradead.org> 於 2020年6月4日 週四 下午11:06寫道:
>
> On 6/4/20 12:06 AM, Gene Chen wrote:
> > diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
> > index f4b72cb..05a3b14 100644
> > --- a/drivers/regulator/Kconfig
> > +++ b/drivers/regulator/Kconfig
> > @@ -680,6 +680,16 @@ config REGULATOR_MT6358
> >         This driver supports the control of different power rails of device
> >         through regulator interface.
> >
> > +config REGULATOR_MT6360
> > +     tristate "MT6360 SubPMIC Regulator"
> > +     depends on MFD_MT6360
> > +     select CRC8
> > +     help
> > +       Say Y here to enable MT6360 regulator support.
> > +       This is support MT6360 PMIC/LDO part include
>
>           This supports the MT6300 PMIC/LDO part, which includes
>
> > +       2-channel buck with Thermal Shutdown and Overlord Protection
>
>                                       is that      Overload  ?
> Yes, it could be Overlord.
>

typo, overload is suitable, thanks

> > +       6-channel High PSRR and Low Dropout LDO.
> > +
> >  config REGULATOR_MT6380
> >       tristate "MediaTek MT6380 PMIC"
> >       depends on MTK_PMIC_WRAP
>
>
> --
> ~Randy
>
diff mbox series

Patch

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index f4b72cb..05a3b14 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -680,6 +680,16 @@  config REGULATOR_MT6358
 	  This driver supports the control of different power rails of device
 	  through regulator interface.
 
+config REGULATOR_MT6360
+	tristate "MT6360 SubPMIC Regulator"
+	depends on MFD_MT6360
+	select CRC8
+	help
+	  Say Y here to enable MT6360 regulator support.
+	  This is support MT6360 PMIC/LDO part include
+	  2-channel buck with Thermal Shutdown and Overlord Protection
+	  6-channel High PSRR and Low Dropout LDO.
+
 config REGULATOR_MT6380
 	tristate "MediaTek MT6380 PMIC"
 	depends on MTK_PMIC_WRAP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 6610ee0..1137af0 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -85,6 +85,7 @@  obj-$(CONFIG_REGULATOR_MPQ7920) += mpq7920.o
 obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o
 obj-$(CONFIG_REGULATOR_MT6323)	+= mt6323-regulator.o
 obj-$(CONFIG_REGULATOR_MT6358)	+= mt6358-regulator.o
+obj-$(CONFIG_REGULATOR_MT6360) += mt6360-regulator.o
 obj-$(CONFIG_REGULATOR_MT6380)	+= mt6380-regulator.o
 obj-$(CONFIG_REGULATOR_MT6397)	+= mt6397-regulator.o
 obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
diff --git a/drivers/regulator/mt6360-regulator.c b/drivers/regulator/mt6360-regulator.c
new file mode 100644
index 0000000..f0d878b
--- /dev/null
+++ b/drivers/regulator/mt6360-regulator.c
@@ -0,0 +1,571 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ *
+ * Author: Gene Chen <gene_chen@richtek.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/crc8.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+
+#include <linux/mfd/mt6360.h>
+
+enum {
+	MT6360_PMIC_BUCK1 = 0,
+	MT6360_PMIC_BUCK2,
+	MT6360_PMIC_LDO6,
+	MT6360_PMIC_LDO7,
+};
+
+enum {
+	MT6360_LDO_LDO1 = 0,
+	MT6360_LDO_LDO2,
+	MT6360_LDO_LDO3,
+	MT6360_LDO_LDO5,
+};
+
+#define MT6360_MAX_REGULATOR		(4)
+
+/* PMIC register defininition */
+#define MT6360_PMIC_BUCK1_VOSEL		(0x10)
+#define MT6360_PMIC_BUCK1_EN_CTRL2	(0x17)
+#define MT6360_PMIC_BUCK2_VOSEL		(0x20)
+#define MT6360_PMIC_BUCK2_EN_CTRL2	(0x27)
+#define MT6360_PMIC_LDO7_EN_CTRL2	(0x31)
+#define MT6360_PMIC_LDO7_CTRL3		(0x35)
+#define MT6360_PMIC_LDO6_EN_CTRL2	(0x37)
+#define MT6360_PMIC_LDO6_CTRL3		(0x3B)
+#define MT6360_PMIC_REGMAX		(0x3C)
+
+/* LDO register defininition */
+#define MT6360_LDO_LDO3_EN_CTRL2	(0x05)
+#define MT6360_LDO_LDO3_CTRL3		(0x09)
+#define MT6360_LDO_LDO5_EN_CTRL2	(0x0B)
+#define MT6360_LDO_LDO5_CTRL0		(0x0C)
+#define MT6360_LDO_LDO5_CTRL3		(0x0F)
+#define MT6360_LDO_LDO2_EN_CTRL2	(0x11)
+#define MT6360_LDO_LDO2_CTRL3		(0x15)
+#define MT6360_LDO_LDO1_EN_CTRL2	(0x17)
+#define MT6360_LDO_LDO1_CTRL3		(0x1B)
+#define MT6360_LDO_REGMAX		(0x1C)
+
+#define MT6360_OPMODE_LP		(2)
+#define MT6360_OPMODE_ULP		(3)
+#define MT6360_OPMODE_NORMAL		(0)
+
+struct mt6360_regulator_desc {
+	const struct regulator_desc desc;
+	unsigned int control_reg;
+	unsigned int mode_set_mask;
+	unsigned int mode_get_mask;
+};
+
+struct mt6360_regulator_devdata {
+	int i2c_idx;
+	const struct regmap_config *regmap_config;
+	const struct mt6360_regulator_desc *reg_descs;
+	int num_reg_descs;
+	const struct mt6360_pmu_irq_desc *irq_descs;
+	int num_irq_descs;
+};
+
+struct mt6360_regulator_data {
+	struct i2c_client *i2c;
+	struct device *dev;
+	struct regulator_dev *rdev[MT6360_MAX_REGULATOR];
+	struct regmap *regmap;
+	unsigned int chip_rev;
+	u8 crc8_table[CRC8_TABLE_SIZE];
+};
+
+#define MT6360_REGU_IRQH(_name, _rid, _event) \
+static irqreturn_t mt6360_pmu_##_name##_handler(int irq, void *data) \
+{ \
+	struct mt6360_regulator_data *mrd = data; \
+	dev_warn(mrd->dev, "%s\n", __func__); \
+	regulator_notifier_call_chain(mrd->rdev[_rid], _event, NULL); \
+	return IRQ_HANDLED; \
+}
+
+#define MT6360_REGU_IRQ(_name) { #_name, mt6360_pmu_##_name##_handler }
+
+/* PMIC irqs */
+MT6360_REGU_IRQH(buck1_pgb_evt, MT6360_PMIC_BUCK1, REGULATOR_EVENT_FAIL)
+MT6360_REGU_IRQH(buck1_oc_evt, MT6360_PMIC_BUCK1, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(buck1_ov_evt,
+		 MT6360_PMIC_BUCK1, REGULATOR_EVENT_REGULATION_OUT)
+MT6360_REGU_IRQH(buck1_uv_evt, MT6360_PMIC_BUCK1, REGULATOR_EVENT_UNDER_VOLTAGE)
+MT6360_REGU_IRQH(buck2_pgb_evt, MT6360_PMIC_BUCK2, REGULATOR_EVENT_FAIL)
+MT6360_REGU_IRQH(buck2_oc_evt, MT6360_PMIC_BUCK2, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(buck2_ov_evt,
+		 MT6360_PMIC_BUCK2, REGULATOR_EVENT_REGULATION_OUT)
+MT6360_REGU_IRQH(buck2_uv_evt, MT6360_PMIC_BUCK2, REGULATOR_EVENT_UNDER_VOLTAGE)
+MT6360_REGU_IRQH(ldo6_oc_evt, MT6360_PMIC_LDO6, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(ldo7_oc_evt, MT6360_PMIC_LDO7, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(ldo6_pgb_evt, MT6360_PMIC_LDO6, REGULATOR_EVENT_FAIL)
+MT6360_REGU_IRQH(ldo7_pgb_evt, MT6360_PMIC_LDO7, REGULATOR_EVENT_FAIL)
+
+/* LDO irqs */
+MT6360_REGU_IRQH(ldo1_oc_evt, MT6360_LDO_LDO1, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(ldo2_oc_evt, MT6360_LDO_LDO2, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(ldo3_oc_evt, MT6360_LDO_LDO3, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(ldo5_oc_evt, MT6360_LDO_LDO5, REGULATOR_EVENT_OVER_CURRENT)
+MT6360_REGU_IRQH(ldo1_pgb_evt, MT6360_LDO_LDO1, REGULATOR_EVENT_FAIL)
+MT6360_REGU_IRQH(ldo2_pgb_evt, MT6360_LDO_LDO2, REGULATOR_EVENT_FAIL)
+MT6360_REGU_IRQH(ldo3_pgb_evt, MT6360_LDO_LDO3, REGULATOR_EVENT_FAIL)
+MT6360_REGU_IRQH(ldo5_pgb_evt, MT6360_LDO_LDO5, REGULATOR_EVENT_FAIL)
+
+static const struct mt6360_pmu_irq_desc mt6360_pmic_irq_descs[] = {
+	MT6360_REGU_IRQ(buck1_pgb_evt),
+	MT6360_REGU_IRQ(buck1_oc_evt),
+	MT6360_REGU_IRQ(buck1_ov_evt),
+	MT6360_REGU_IRQ(buck1_uv_evt),
+	MT6360_REGU_IRQ(buck2_pgb_evt),
+	MT6360_REGU_IRQ(buck2_oc_evt),
+	MT6360_REGU_IRQ(buck2_ov_evt),
+	MT6360_REGU_IRQ(buck2_uv_evt),
+	MT6360_REGU_IRQ(ldo6_oc_evt),
+	MT6360_REGU_IRQ(ldo7_oc_evt),
+	MT6360_REGU_IRQ(ldo6_pgb_evt),
+	MT6360_REGU_IRQ(ldo7_pgb_evt),
+};
+
+static const struct mt6360_pmu_irq_desc mt6360_ldo_irq_descs[] = {
+	MT6360_REGU_IRQ(ldo1_oc_evt),
+	MT6360_REGU_IRQ(ldo2_oc_evt),
+	MT6360_REGU_IRQ(ldo3_oc_evt),
+	MT6360_REGU_IRQ(ldo5_oc_evt),
+	MT6360_REGU_IRQ(ldo1_pgb_evt),
+	MT6360_REGU_IRQ(ldo2_pgb_evt),
+	MT6360_REGU_IRQ(ldo3_pgb_evt),
+	MT6360_REGU_IRQ(ldo5_pgb_evt),
+};
+
+static int mt6360_regulator_irq_register(struct platform_device *pdev,
+				       struct mt6360_regulator_devdata *devdata)
+{
+	const struct mt6360_pmu_irq_desc *irq_desc;
+	int i, irq, ret;
+
+	for (i = 0; i < devdata->num_irq_descs; i++) {
+		irq_desc = devdata->irq_descs + i;
+		if (unlikely(!irq_desc->name))
+			continue;
+		irq = platform_get_irq_byname(pdev, irq_desc->name);
+		if (irq < 0)
+			continue;
+		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+						irq_desc->irq_handler,
+						IRQF_TRIGGER_FALLING,
+						irq_desc->name,
+						platform_get_drvdata(pdev));
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"request %s irq fail\n", irq_desc->name);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static int mt6360_regulator_set_mode(
+				  struct regulator_dev *rdev, unsigned int mode)
+{
+	const struct mt6360_regulator_desc *desc =
+			       (const struct mt6360_regulator_desc *)rdev->desc;
+	int shift = ffs(desc->mode_set_mask) - 1, ret;
+	unsigned int val;
+
+	if (!mode)
+		return -EINVAL;
+	switch (1 << (ffs(mode) - 1)) {
+	case REGULATOR_MODE_NORMAL:
+		val = MT6360_OPMODE_NORMAL;
+		break;
+	case REGULATOR_MODE_STANDBY:
+		val = MT6360_OPMODE_ULP;
+		break;
+	case REGULATOR_MODE_IDLE:
+		val = MT6360_OPMODE_LP;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+	ret = regmap_update_bits(rdev->regmap, desc->control_reg,
+				 desc->mode_set_mask, val << shift);
+	if (ret < 0) {
+		dev_err(&rdev->dev, "%s: fail (%d)\n", __func__, ret);
+		return ret;
+	}
+	return 0;
+}
+
+static unsigned int mt6360_regulator_get_mode(struct regulator_dev *rdev)
+{
+	const struct mt6360_regulator_desc *desc =
+			       (const struct mt6360_regulator_desc *)rdev->desc;
+	int shift = ffs(desc->mode_get_mask) - 1, ret;
+	unsigned int val = 0;
+
+	ret = regmap_read(rdev->regmap, desc->control_reg, &val);
+	if (ret < 0)
+		return ret;
+	val &= desc->mode_get_mask;
+	val >>= shift;
+	switch (val) {
+	case MT6360_OPMODE_LP:
+		ret = REGULATOR_MODE_IDLE;
+		break;
+	case MT6360_OPMODE_ULP:
+		ret = REGULATOR_MODE_STANDBY;
+		break;
+	case MT6360_OPMODE_NORMAL:
+		ret = REGULATOR_MODE_NORMAL;
+		break;
+	default:
+		ret = 0;
+	}
+	return ret;
+}
+
+static unsigned int mt6360_regulator_of_map_mode(unsigned int hw_mode)
+{
+	unsigned int trans_mode = 0;
+
+	switch (hw_mode) {
+	case MT6360_OPMODE_NORMAL:
+		trans_mode = REGULATOR_MODE_NORMAL;
+		break;
+	case MT6360_OPMODE_LP:
+		trans_mode = REGULATOR_MODE_IDLE;
+		break;
+	case MT6360_OPMODE_ULP:
+		trans_mode = REGULATOR_MODE_STANDBY;
+		break;
+	}
+	return trans_mode;
+}
+
+static const struct regulator_ops mt6360_pmic_regulator_ops = {
+	.list_voltage = regulator_list_voltage_linear,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_voltage_sel = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_regmap,
+	.set_mode = mt6360_regulator_set_mode,
+	.get_mode = mt6360_regulator_get_mode,
+};
+
+#define MT6360_PMIC_DESC(_name, _min, _stp, _cnt, _vreg, _vmask, \
+			 _enreg, _enmask, _ctrlreg, _modesmask, _modegmask) \
+{									\
+	.desc = {							\
+		.name = #_name,						\
+		.id =  MT6360_PMIC_##_name,				\
+		.owner = THIS_MODULE,					\
+		.ops = &mt6360_pmic_regulator_ops,			\
+		.of_match = of_match_ptr(#_name),			\
+		.min_uV = _min,						\
+		.uV_step = _stp,					\
+		.n_voltages = _cnt,					\
+		.type = REGULATOR_VOLTAGE,				\
+		.vsel_reg = _vreg,					\
+		.vsel_mask = _vmask,					\
+		.enable_reg = _enreg,					\
+		.enable_mask = _enmask,					\
+		.of_map_mode = mt6360_regulator_of_map_mode,		\
+	},								\
+	.control_reg = _ctrlreg,					\
+	.mode_set_mask = _modesmask,					\
+	.mode_get_mask = _modegmask,					\
+}
+
+static const struct mt6360_regulator_desc mt6360_pmic_descs[] =  {
+	MT6360_PMIC_DESC(BUCK1, 300000, 5000, 201, MT6360_PMIC_BUCK1_VOSEL,
+			 0xff, MT6360_PMIC_BUCK1_EN_CTRL2, 0x40,
+			 MT6360_PMIC_BUCK1_EN_CTRL2, 0x30, 0x03),
+	MT6360_PMIC_DESC(BUCK2, 300000, 5000, 201, MT6360_PMIC_BUCK2_VOSEL,
+			 0xff, MT6360_PMIC_BUCK2_EN_CTRL2, 0x40,
+			 MT6360_PMIC_BUCK2_EN_CTRL2, 0x30, 0x03),
+	MT6360_PMIC_DESC(LDO6, 500000, 10000, 161, MT6360_PMIC_LDO6_CTRL3,
+			 0xff, MT6360_PMIC_LDO6_EN_CTRL2, 0x40,
+			 MT6360_PMIC_LDO6_EN_CTRL2, 0x30, 0x03),
+	MT6360_PMIC_DESC(LDO7, 500000, 10000, 161, MT6360_PMIC_LDO7_CTRL3,
+			 0xff, MT6360_PMIC_LDO7_EN_CTRL2, 0x40,
+			 MT6360_PMIC_LDO7_EN_CTRL2, 0x30, 0x03),
+};
+
+static const struct regulator_ops mt6360_ldo_regulator_ops = {
+	.list_voltage = regulator_list_voltage_linear_range,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_voltage_sel = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_regmap,
+	.set_mode = mt6360_regulator_set_mode,
+	.get_mode = mt6360_regulator_get_mode,
+};
+
+static const struct regulator_linear_range ldo_volt_ranges1[] = {
+	REGULATOR_LINEAR_RANGE(1200000, 0x00, 0x09, 10000),
+	REGULATOR_LINEAR_RANGE(1300000, 0x0a, 0x10, 0),
+	REGULATOR_LINEAR_RANGE(1310000, 0x11, 0x19, 10000),
+	REGULATOR_LINEAR_RANGE(1400000, 0x1a, 0x1f, 0),
+	REGULATOR_LINEAR_RANGE(1500000, 0x20, 0x29, 10000),
+	REGULATOR_LINEAR_RANGE(1600000, 0x2a, 0x2f, 0),
+	REGULATOR_LINEAR_RANGE(1700000, 0x30, 0x39, 10000),
+	REGULATOR_LINEAR_RANGE(1800000, 0x3a, 0x40, 0),
+	REGULATOR_LINEAR_RANGE(1810000, 0x41, 0x49, 10000),
+	REGULATOR_LINEAR_RANGE(1900000, 0x4a, 0x4f, 0),
+	REGULATOR_LINEAR_RANGE(2000000, 0x50, 0x59, 10000),
+	REGULATOR_LINEAR_RANGE(2100000, 0x5a, 0x60, 0),
+	REGULATOR_LINEAR_RANGE(2110000, 0x61, 0x69, 10000),
+	REGULATOR_LINEAR_RANGE(2200000, 0x6a, 0x70, 0),
+	REGULATOR_LINEAR_RANGE(2210000, 0x71, 0x79, 10000),
+	REGULATOR_LINEAR_RANGE(2300000, 0x7a, 0x7f, 0),
+	REGULATOR_LINEAR_RANGE(2700000, 0x80, 0x89, 10000),
+	REGULATOR_LINEAR_RANGE(2800000, 0x8a, 0x90, 0),
+	REGULATOR_LINEAR_RANGE(2810000, 0x91, 0x99, 10000),
+	REGULATOR_LINEAR_RANGE(2900000, 0x9a, 0xa0, 0),
+	REGULATOR_LINEAR_RANGE(2910000, 0xa1, 0xa9, 10000),
+	REGULATOR_LINEAR_RANGE(3000000, 0xaa, 0xb0, 0),
+	REGULATOR_LINEAR_RANGE(3010000, 0xb1, 0xb9, 10000),
+	REGULATOR_LINEAR_RANGE(3100000, 0xba, 0xc0, 0),
+	REGULATOR_LINEAR_RANGE(3110000, 0xc1, 0xc9, 10000),
+	REGULATOR_LINEAR_RANGE(3200000, 0xca, 0xcf, 0),
+	REGULATOR_LINEAR_RANGE(3300000, 0xd0, 0xd9, 10000),
+	REGULATOR_LINEAR_RANGE(3400000, 0xda, 0xe0, 0),
+	REGULATOR_LINEAR_RANGE(3410000, 0xe1, 0xe9, 10000),
+	REGULATOR_LINEAR_RANGE(3500000, 0xea, 0xf0, 0),
+	REGULATOR_LINEAR_RANGE(3510000, 0xf1, 0xf9, 10000),
+	REGULATOR_LINEAR_RANGE(3600000, 0xfa, 0xff, 0),
+};
+
+static const struct regulator_linear_range ldo_volt_ranges2[] = {
+	REGULATOR_LINEAR_RANGE(2700000, 0x00, 0x09, 10000),
+	REGULATOR_LINEAR_RANGE(2800000, 0x0a, 0x10, 0),
+	REGULATOR_LINEAR_RANGE(2810000, 0x11, 0x19, 10000),
+	REGULATOR_LINEAR_RANGE(2900000, 0x1a, 0x20, 0),
+	REGULATOR_LINEAR_RANGE(2910000, 0x21, 0x29, 10000),
+	REGULATOR_LINEAR_RANGE(3000000, 0x2a, 0x30, 0),
+	REGULATOR_LINEAR_RANGE(3010000, 0x31, 0x39, 10000),
+	REGULATOR_LINEAR_RANGE(3100000, 0x3a, 0x40, 0),
+	REGULATOR_LINEAR_RANGE(3110000, 0x41, 0x49, 10000),
+	REGULATOR_LINEAR_RANGE(3200000, 0x4a, 0x4f, 0),
+	REGULATOR_LINEAR_RANGE(3300000, 0x50, 0x59, 10000),
+	REGULATOR_LINEAR_RANGE(3400000, 0x5a, 0x60, 0),
+	REGULATOR_LINEAR_RANGE(3410000, 0x61, 0x69, 10000),
+	REGULATOR_LINEAR_RANGE(3500000, 0x6a, 0x70, 0),
+	REGULATOR_LINEAR_RANGE(3510000, 0x71, 0x79, 10000),
+	REGULATOR_LINEAR_RANGE(3600000, 0x7a, 0x7f, 0),
+};
+
+#define MT6360_LDO_DESC(_name, _vranges, _vcnt, _vreg, _vmask, _enreg, \
+		      _enmask, _ctrlreg, _modesmask, _modegmask, _offon_delay) \
+{									\
+	.desc = {							\
+		.name = #_name,						\
+		.id =  MT6360_LDO_##_name,				\
+		.owner = THIS_MODULE,					\
+		.ops = &mt6360_ldo_regulator_ops,			\
+		.of_match = of_match_ptr(#_name),			\
+		.linear_ranges = _vranges,				\
+		.n_linear_ranges = ARRAY_SIZE(_vranges),		\
+		.n_voltages = _vcnt,					\
+		.type = REGULATOR_VOLTAGE,				\
+		.vsel_reg = _vreg,					\
+		.vsel_mask = _vmask,					\
+		.enable_reg = _enreg,					\
+		.enable_mask = _enmask,					\
+		.off_on_delay = _offon_delay,				\
+		.of_map_mode = mt6360_regulator_of_map_mode,		\
+	},								\
+	.control_reg = _ctrlreg,					\
+	.mode_set_mask = _modesmask,					\
+	.mode_get_mask = _modegmask,					\
+}
+
+static const struct mt6360_regulator_desc mt6360_ldo_descs[] =  {
+	MT6360_LDO_DESC(LDO1, ldo_volt_ranges1, 256, MT6360_LDO_LDO1_CTRL3,
+			0xff, MT6360_LDO_LDO1_EN_CTRL2, 0x40,
+			MT6360_LDO_LDO1_EN_CTRL2, 0x30, 0x03, 0),
+	MT6360_LDO_DESC(LDO2, ldo_volt_ranges1, 256, MT6360_LDO_LDO2_CTRL3,
+			0xff, MT6360_LDO_LDO2_EN_CTRL2, 0x40,
+			MT6360_LDO_LDO2_EN_CTRL2, 0x30, 0x03, 0),
+	MT6360_LDO_DESC(LDO3, ldo_volt_ranges1, 256, MT6360_LDO_LDO3_CTRL3,
+			0xff, MT6360_LDO_LDO3_EN_CTRL2, 0x40,
+			MT6360_LDO_LDO3_EN_CTRL2, 0x30, 0x03, 100),
+	MT6360_LDO_DESC(LDO5, ldo_volt_ranges2, 128, MT6360_LDO_LDO5_CTRL3,
+			0xff, MT6360_LDO_LDO5_EN_CTRL2, 0x40,
+			MT6360_LDO_LDO5_EN_CTRL2, 0x30, 0x03, 100),
+};
+
+static int mt6360_regulator_reg_write(void *context,
+				      unsigned int reg, unsigned int val)
+{
+	struct mt6360_regulator_data *mrd = context;
+	u8 chunk[4] = {0};
+
+	/* chunk 0 ->i2c addr, 1 -> reg_addr, 2 -> reg_val 3-> crc8 */
+	chunk[0] = (mrd->i2c->addr & 0x7f) << 1;
+	chunk[1] = reg & 0x3f;
+	chunk[2] = (u8)val;
+	chunk[3] = crc8(mrd->crc8_table, chunk, 3, 0);
+	/* also dummy one byte */
+	return i2c_smbus_write_i2c_block_data(mrd->i2c, chunk[1], 3, chunk + 2);
+}
+
+static int mt6360_regulator_reg_read(void *context,
+				     unsigned int reg, unsigned int *val)
+{
+	struct mt6360_regulator_data *mrd = context;
+	u8 chunk[5] = {0};
+	int ret;
+
+	/* chunk 0->i2c addr, 1->reg_addr, 2->reg_val, 3->crc8, 4->crck */
+	chunk[0] = ((mrd->i2c->addr & 0x7f) << 1) + 1;
+	chunk[1] = reg & 0x3f;
+	ret =  i2c_smbus_read_i2c_block_data(mrd->i2c, chunk[1], 2, chunk + 2);
+	if (ret < 0)
+		return ret;
+	chunk[4] = crc8(mrd->crc8_table, chunk, 3, 0);
+	if (chunk[3] != chunk[4])
+		return -EINVAL;
+	*val = chunk[2];
+	return 0;
+}
+
+static const struct regmap_config mt6360_pmic_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.reg_read = mt6360_regulator_reg_read,
+	.reg_write = mt6360_regulator_reg_write,
+	.max_register = MT6360_PMIC_REGMAX,
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+static const struct regmap_config mt6360_ldo_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.reg_read = mt6360_regulator_reg_read,
+	.reg_write = mt6360_regulator_reg_write,
+	.max_register = MT6360_LDO_REGMAX,
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+static const struct mt6360_regulator_devdata mt6360_pmic_devdata = {
+	.i2c_idx = MT6360_SLAVE_PMIC,
+	.regmap_config = &mt6360_pmic_regmap_config,
+	.reg_descs = mt6360_pmic_descs,
+	.num_reg_descs = ARRAY_SIZE(mt6360_pmic_descs),
+	.irq_descs = mt6360_pmic_irq_descs,
+	.num_irq_descs = ARRAY_SIZE(mt6360_pmic_irq_descs),
+};
+
+static const struct mt6360_regulator_devdata mt6360_ldo_devdata = {
+	.i2c_idx = MT6360_SLAVE_LDO,
+	.regmap_config = &mt6360_ldo_regmap_config,
+	.reg_descs = mt6360_ldo_descs,
+	.num_reg_descs = ARRAY_SIZE(mt6360_ldo_descs),
+	.irq_descs = mt6360_ldo_irq_descs,
+	.num_irq_descs = ARRAY_SIZE(mt6360_ldo_irq_descs),
+};
+
+static const struct of_device_id __maybe_unused mt6360_regulator_of_id[] = {
+	{
+		.compatible = "mediatek,mt6360_pmic",
+		.data = (void *)&mt6360_pmic_devdata,
+	},
+	{
+		.compatible = "mediatek,mt6360_ldo",
+		.data = (void *)&mt6360_ldo_devdata,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, mt6360_regulator_of_id);
+
+static int mt6360_regulator_probe(struct platform_device *pdev)
+{
+	struct mt6360_pmu_data *pmu_data = dev_get_drvdata(pdev->dev.parent);
+	struct mt6360_regulator_devdata *devdata;
+	struct mt6360_regulator_data *mrd;
+	struct regulator_config config = {};
+	const struct of_device_id *match;
+	const struct platform_device_id *id;
+	int i, ret;
+
+	mrd = devm_kzalloc(&pdev->dev, sizeof(*mrd), GFP_KERNEL);
+	if (!mrd)
+		return -ENOMEM;
+
+	if (pdev->dev.of_node) {
+		match = of_match_device(
+			      of_match_ptr(mt6360_regulator_of_id), &pdev->dev);
+		if (!match) {
+			dev_err(&pdev->dev, "no match device id\n");
+			return -EINVAL;
+		}
+		devdata = (struct mt6360_regulator_devdata *)match->data;
+	} else {
+		id = platform_get_device_id(pdev);
+		devdata = (struct mt6360_regulator_devdata *)id->driver_data;
+	}
+
+	mrd->i2c = pmu_data->i2c[devdata->i2c_idx];
+	mrd->dev = &pdev->dev;
+	mrd->chip_rev = pmu_data->chip_rev;
+	crc8_populate_msb(mrd->crc8_table, 0x7);
+	platform_set_drvdata(pdev, mrd);
+
+	mrd->regmap = devm_regmap_init(&(mrd->i2c->dev),
+				       NULL, mrd, devdata->regmap_config);
+	if (IS_ERR(mrd->regmap)) {
+		dev_err(&pdev->dev, "Failed to register regmap\n");
+		return PTR_ERR(mrd->regmap);
+	}
+
+	config.dev = &pdev->dev;
+	config.driver_data = mrd;
+	config.regmap = mrd->regmap;
+
+	for (i = 0; i < devdata->num_reg_descs; i++) {
+		mrd->rdev[i] = devm_regulator_register(&pdev->dev,
+					&(devdata->reg_descs[i].desc), &config);
+		if (IS_ERR(mrd->rdev[i])) {
+			dev_err(&pdev->dev,
+				"Failed to register  %d regulaotr\n", i);
+			return PTR_ERR(mrd->rdev[i]);
+		}
+	}
+
+	ret = mt6360_regulator_irq_register(pdev, devdata);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to register irqs\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver mt6360_regulator_driver = {
+	.driver = {
+		.name = "mt6360_regulator",
+		.of_match_table = of_match_ptr(mt6360_regulator_of_id),
+	},
+	.probe = mt6360_regulator_probe,
+};
+module_platform_driver(mt6360_regulator_driver);
+
+MODULE_AUTHOR("Gene Chen <gene_chen@richtek.com>");
+MODULE_DESCRIPTION("MT6360 Regulator Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/mt6360.h b/include/linux/mfd/mt6360.h
index ea13040..495dfa8 100644
--- a/include/linux/mfd/mt6360.h
+++ b/include/linux/mfd/mt6360.h
@@ -29,6 +29,11 @@  struct mt6360_pmu_data {
 	unsigned int chip_rev;
 };
 
+struct mt6360_pmu_irq_desc {
+	const char *name;
+	irq_handler_t irq_handler;
+};
+
 /* PMU register defininition */
 #define MT6360_PMU_DEV_INFO			(0x00)
 #define MT6360_PMU_CORE_CTRL1			(0x01)