diff mbox

[1/4] mfd: support 88pm80x in 80x driver

Message ID 1341392115-9425-2-git-send-email-zhouqiao@marvell.com (mailing list archive)
State New, archived
Headers show

Commit Message

Qiao Zhou July 4, 2012, 8:55 a.m. UTC
88PM800 and 88PM805 are two discrete chips used for power management.
Hardware designer can use them together or only one of them according
to requirement.

88pm80x_i2c.c provides common i2c driver handling for both 800 and
805, such as i2c_driver init, regmap init, read/write api etc.

88pm800_core.c handles specifically for 800, such as chip init, irq
init/handle, mfd device register, including rtc, onkey, regulator(
to be add later) etc. besides that, 800 has three i2c device, one
regular i2c client, two other i2c dummy for gpadc and power purpose.

88pm805_core.c handles specifically for 805, such as chip init, irq
init/handle, mfd device register, including codec, headset/mic detect
etc.

the i2c operation of both 800 and 805 are via regmap.

Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
 drivers/mfd/88pm800-core.c  |  492 ++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/88pm805-core.c  |  273 ++++++++++++++++++++++
 drivers/mfd/88pm80x-i2c.c   |  203 +++++++++++++++++
 drivers/mfd/Kconfig         |   12 +
 drivers/mfd/Makefile        |    2 +
 include/linux/mfd/88pm80x.h |  521 +++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1503 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/88pm800-core.c
 create mode 100644 drivers/mfd/88pm805-core.c
 create mode 100644 drivers/mfd/88pm80x-i2c.c
 create mode 100644 include/linux/mfd/88pm80x.h

Comments

Arnd Bergmann July 4, 2012, 11:37 a.m. UTC | #1
On Wednesday 04 July 2012, Qiao Zhou wrote:
> 88PM800 and 88PM805 are two discrete chips used for power management.
> Hardware designer can use them together or only one of them according
> to requirement.
> 
> 88pm80x_i2c.c provides common i2c driver handling for both 800 and
> 805, such as i2c_driver init, regmap init, read/write api etc.
> 
> 88pm800_core.c handles specifically for 800, such as chip init, irq
> init/handle, mfd device register, including rtc, onkey, regulator(
> to be add later) etc. besides that, 800 has three i2c device, one
> regular i2c client, two other i2c dummy for gpadc and power purpose.
> 
> 88pm805_core.c handles specifically for 805, such as chip init, irq
> init/handle, mfd device register, including codec, headset/mic detect
> etc.
> 
> the i2c operation of both 800 and 805 are via regmap.
> 
> Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>

The split between the two files looks very good now, I think this way
it makes much more sense for the reader.


> +	ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
> +			      ARRAY_SIZE(onkey_devs), &onkey_resources[0],
> +			      chip->irq_base);

According to what I discussed with Mark in the previous version, I think you
need to pass 0 instead of chip->irq_base here, and transform the interrupt
numbers using the domain in the client drivers.

> +
> +const struct i2c_device_id pm80x_id_table[] = {
> +	{"88PM800", CHIP_PM800},
> +	{"88PM805", CHIP_PM805},
> +};
> +MODULE_DEVICE_TABLE(i2c, pm80x_id_table);

Since these are separate modules now, you have to move the device table
into the split files as well.

> +
> +/**
> + * pm80x_reg_read: Read a single 88PM80x register.
> + *
> + * @map: regmap to read from.
> + * @reg: Register to read.
> + */
> +int pm80x_reg_read(struct regmap *map, u32 reg)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(map, reg, &val);
> +
> +	if (ret < 0)
> +		return ret;
> +	else
> +		return val;
> +}
> +EXPORT_SYMBOL_GPL(pm80x_reg_read);

In your introductory email you write

"Exported r/w API which requires regmap handle. as currently the pm800
chip has 3 i2c device, only passing a pm80x_chip info can't ensure r/w the
register in correct i2c device."

Your first driver version had this, then you removed the functions
after I asked you to, and now they are back, so I assume there is something
I don't see yet. It looks like the function is just an unnecessary wrapper
that is better open-coded in the caller. Can you explain again what the
difference is?

> +/**
> + * pm80x_bulk_read: Read multiple 88PM80x registers
> + *
> + * @map: regmap to read from
> + * @reg: First register
> + * @buf: Buffer to fill.  The data will be returned big endian.
> + * @count: Number of registers
> + */
> +int pm80x_bulk_read(struct regmap *map, u32 reg, u8 *buf, int count)
> +{
> +	return regmap_raw_read(map, reg, buf, count);
> +}

Unused function? Either export this if you want to provide it as
the general API, or drop the function.

> +int __devinit pm80x_device_init(struct pm80x_chip *chip,
> +				struct pm80x_platform_data *pdata)

> +void __devexit pm80x_device_exit(struct pm80x_chip *chip)

> +SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);

I would think that these need to be exported as well, at least if
you want the driver to be modular.

> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index e129c82..96dd2d7 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -20,6 +20,18 @@ config MFD_88PM860X
>  	  select individual components like voltage regulators, RTC and
>  	  battery-charger under the corresponding menus.
>  
> +config MFD_88PM80X
> +	bool "Support Marvell 88PM800/88PM805"
> +	depends on I2C=y && GENERIC_HARDIRQS
> +	select REGMAP_I2C
> +	select REGMAP_IRQ
> +	select MFD_CORE
> +	help
> +	  This supports for Marvell 88PM800/88PM805 Power Management IC.
> +	  This includes the I2C driver and the core APIs _only_, you have to
> +	  select individual components like voltage regulators, RTC and
> +	  battery-charger under the corresponding menus.
> +
>  config MFD_SM501
>  	tristate "Support for Silicon Motion SM501"
>  	 ---help---
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 75f6ed6..dc2584e 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -3,7 +3,9 @@
>  #
>  
>  88pm860x-objs			:= 88pm860x-core.o 88pm860x-i2c.o
> +88pm80x-objs			:= 88pm800-core.o 88pm805-core.o 88pm80x-i2c.o
>  obj-$(CONFIG_MFD_88PM860X)	+= 88pm860x.o
> +obj-$(CONFIG_MFD_88PM80X)	+= 88pm80x.o
>  obj-$(CONFIG_MFD_SM501)		+= sm501.o
>  obj-$(CONFIG_MFD_ASIC3)		+= asic3.o tmio_core.o

I just noticed that it can currently only be builtin. Is there a strict
requirement for that? If not, better make it a tristate option.

If you do that, aside from adding the exports mentioned above, you have
to use three separate modules, as in

obj-$(CONFIG_MFD_88PM80X)	+= 88pm800-core.o 88pm805-core.o 88pm80x-i2c.o

or even

obj-$(CONFIG_MFD_88PM800)	+= 88pm800-core.o 88pm800-core.o
obj-$(CONFIG_MFD_88PM805)	+= 88pm800-core.o 88pm805-core.o

with the respective Kconfig change.

> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
> new file mode 100644
> index 0000000..115348a
> --- /dev/null
> +++ b/include/linux/mfd/88pm80x.h
> @@ -0,0 +1,521 @@
> +#ifndef __LINUX_MFD_88PM80X_H
> +#define __LINUX_MFD_88PM80X_H
> +
> +#define PM80X_VERSION_MASK		(0xFF)	/* 80X chip ID mask */
> +
> +enum {
> +	/* Procida */
> +	PM800_CHIP_A0  = 0x60,
> +	PM800_CHIP_A1  = 0x61,
> +	PM800_CHIP_B0  = 0x62,
> +	PM800_CHIP_C0  = 0x63,
> +	PM800_CHIP_END = PM800_CHIP_C0,
> +
> +	/* Make sure to update this to the last stepping */
> +	PM8XXX_CHIP_END = PM800_CHIP_END
> +};
> +
> +enum {
> +	PM800_ID_INVALID,
> +	PM800_ID_VIBRATOR,
> +	PM800_ID_SOUND,
> +	PM800_ID_MAX,
> +};
> +
> +enum {
> +	PM800_ID_BUCK1 = 0,
> +	PM800_ID_BUCK2,
> +	PM800_ID_BUCK3,
> +	PM800_ID_BUCK4,
> +	PM800_ID_BUCK5,

I would still argue that the majority of the constants in this file
should get moved into the driver .c file that uses them. Putting them
into the header is better done only for interfaces between the
driver parts, and for constants that are used by multiple drivers.

> +struct pm80x_subchip {
> +	struct i2c_client *power_page;	/* chip client for power page */
> +	struct i2c_client *gpadc_page;	/* chip client for gpadc page */
> +	struct regmap *regmap_power;
> +	struct regmap *regmap_gpadc;
> +	unsigned short power_page_addr;	/* power page I2C address */
> +	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
> +};
> +
> +struct pm80x_chip {
> +	struct pm80x_subchip *subchip;
> +	struct device *dev;
> +	struct i2c_client *client;
> +	struct regmap *regmap;
> +	struct regmap_irq_chip *regmap_irq_chip;
> +	struct regmap_irq_chip_data *irq_data;
> +	unsigned char version;
> +	int id;
> +	int irq;
> +	int irq_mode;
> +	int irq_base;
> +	unsigned int wu_flag;
> +};

One thing I forgot to ask in the previous review although I had already
noticed it then: What is the separation of pm80x_chip and pm80x_subchip
used for?

> +struct pm80x_rtc_pdata {
> +	int		vrtc;
> +	int		rtc_wakeup;
> +};
> +
> +struct pm80x_platform_data {
> +	struct pm80x_rtc_pdata *rtc;
> +	unsigned short pm800_addr;
> +	unsigned short pm805_addr;
> +	unsigned short power_page_addr;	/* power page I2C address */
> +	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
> +	int irq_mode;		/* Clear interrupt by read/write(0/1) */
> +	int irq_base;		/* IRQ base of chip */
> +	int batt_det;		/* enable/disable */
> +};

You removed the callback here as I asked you to, which I think is a useful
cleanup, but you can actually managed to convinced me that it would be ok
to have it, so I don't mind if you want to put it back and use auxdata.

On the other hand, I think it probably makes sense to drop the irq_base
member in this struct and rely on irq domains to allocate them dynamically
as mentioned before.

	Arnd
Qiao Zhou July 4, 2012, 3:14 p.m. UTC | #2
>> +	ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
>> +			      ARRAY_SIZE(onkey_devs), &onkey_resources[0],
>> +			      chip->irq_base);
>
>According to what I discussed with Mark in the previous version, I think you
>need to pass 0 instead of chip->irq_base here, and transform the interrupt
>numbers using the domain in the client drivers.
>
>> +
>> +const struct i2c_device_id pm80x_id_table[] = {
>> +	{"88PM800", CHIP_PM800},
>> +	{"88PM805", CHIP_PM805},
>> +};
>> +MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
>
>Since these are separate modules now, you have to move the device table
>into the split files as well.
Is it ok to export it in 88pm80x.h?
>> +
>> +/**
>> + * pm80x_reg_read: Read a single 88PM80x register.
>> + *
>> + * @map: regmap to read from.
>> + * @reg: Register to read.
>> + */
>> +int pm80x_reg_read(struct regmap *map, u32 reg)
>> +{
>> +	unsigned int val;
>> +	int ret;
>> +
>> +	ret = regmap_read(map, reg, &val);
>> +
>> +	if (ret < 0)
>> +		return ret;
>> +	else
>> +		return val;
>> +}
>> +EXPORT_SYMBOL_GPL(pm80x_reg_read);
>
>In your introductory email you write
>
>"Exported r/w API which requires regmap handle. as currently the pm800
>chip has 3 i2c device, only passing a pm80x_chip info can't ensure r/w the
>register in correct i2c device."
>
>Your first driver version had this, then you removed the functions
>after I asked you to, and now they are back, so I assume there is something
>I don't see yet. It looks like the function is just an unnecessary wrapper
>that is better open-coded in the caller. Can you explain again what the
>difference is?
After you suggest to change the r/w API so that caller doesn't care about it's
via i2c or spi, it makes sense. However due to pm800 has 3 i2c devices, and
it's hard to export such interface for pm800. Currently to add such interface
via regmap handle, caller still doesn't care about the actual hw implement,
also it's clear that all pm80x sub-driver or plat call the unified r/w API.
>> +/**
>> + * pm80x_bulk_read: Read multiple 88PM80x registers
>> + *
>> + * @map: regmap to read from
>> + * @reg: First register
>> + * @buf: Buffer to fill.  The data will be returned big endian.
>> + * @count: Number of registers
>> + */
>> +int pm80x_bulk_read(struct regmap *map, u32 reg, u8 *buf, int count)
>> +{
>> +	return regmap_raw_read(map, reg, buf, count);
>> +}
>
>Unused function? Either export this if you want to provide it as
>the general API, or drop the function.
It's used by rtc driver.
>> +int __devinit pm80x_device_init(struct pm80x_chip *chip,
>> +				struct pm80x_platform_data *pdata)
>
>> +void __devexit pm80x_device_exit(struct pm80x_chip *chip)
>
>> +SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);
>
>I would think that these need to be exported as well, at least if
>you want the driver to be modular.
Would update. Thanks.
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>> index e129c82..96dd2d7 100644
>> --- a/drivers/mfd/Kconfig
>> +++ b/drivers/mfd/Kconfig
>> @@ -20,6 +20,18 @@ config MFD_88PM860X
>>  	  select individual components like voltage regulators, RTC and
>>  	  battery-charger under the corresponding menus.
>>  
>> +config MFD_88PM80X
>> +	bool "Support Marvell 88PM800/88PM805"
>> +	depends on I2C=y && GENERIC_HARDIRQS
>> +	select REGMAP_I2C
>> +	select REGMAP_IRQ
>> +	select MFD_CORE
>> +	help
>> +	  This supports for Marvell 88PM800/88PM805 Power Management IC.
>> +	  This includes the I2C driver and the core APIs _only_, you have to
>> +	  select individual components like voltage regulators, RTC and
>> +	  battery-charger under the corresponding menus.
>> +
>>  config MFD_SM501
>>  	tristate "Support for Silicon Motion SM501"
>>  	 ---help---
>> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
>> index 75f6ed6..dc2584e 100644
>> --- a/drivers/mfd/Makefile
>> +++ b/drivers/mfd/Makefile
>> @@ -3,7 +3,9 @@
>>  #
>>  
>>  88pm860x-objs			:= 88pm860x-core.o 88pm860x-i2c.o
>> +88pm80x-objs			:= 88pm800-core.o 88pm805-core.o 88pm80x-i2c.o
>>  obj-$(CONFIG_MFD_88PM860X)	+= 88pm860x.o
>> +obj-$(CONFIG_MFD_88PM80X)	+= 88pm80x.o
>>  obj-$(CONFIG_MFD_SM501)		+= sm501.o
>>  obj-$(CONFIG_MFD_ASIC3)		+= asic3.o tmio_core.o
>
>I just noticed that it can currently only be builtin. Is there a strict
>>requirement for that? If not, better make it a tristate option.
>
>If you do that, aside from adding the exports mentioned above, you have
>to use three separate modules, as in
>
>obj-$(CONFIG_MFD_88PM80X)	+= 88pm800-core.o 88pm805-core.o 88pm80x-i2c.o
>
>or even
>
>obj-$(CONFIG_MFD_88PM800)	+= 88pm800-core.o 88pm800-core.o
>obj-$(CONFIG_MFD_88PM805)	+= 88pm800-core.o 88pm805-core.o
>
>with the respective Kconfig change.
Would update. Thanks.
>> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
>> new file mode 100644
>> index 0000000..115348a
>> --- /dev/null
>> +++ b/include/linux/mfd/88pm80x.h
>> @@ -0,0 +1,521 @@
>> +#ifndef __LINUX_MFD_88PM80X_H
>> +#define __LINUX_MFD_88PM80X_H
>> +
>> +#define PM80X_VERSION_MASK		(0xFF)	/* 80X chip ID mask */
>> +
>> +enum {
>> +	/* Procida */
>> +	PM800_CHIP_A0  = 0x60,
>> +	PM800_CHIP_A1  = 0x61,
>> +	PM800_CHIP_B0  = 0x62,
>> +	PM800_CHIP_C0  = 0x63,
>> +	PM800_CHIP_END = PM800_CHIP_C0,
>> +
>> +	/* Make sure to update this to the last stepping */
>> +	PM8XXX_CHIP_END = PM800_CHIP_END
>> +};
>> +
>> +enum {
>> +	PM800_ID_INVALID,
>> +	PM800_ID_VIBRATOR,
>> +	PM800_ID_SOUND,
>> +	PM800_ID_MAX,
>> +};
>> +
>> +enum {
>> +	PM800_ID_BUCK1 = 0,
>> +	PM800_ID_BUCK2,
>> +	PM800_ID_BUCK3,
> +	PM800_ID_BUCK4,
>> +	PM800_ID_BUCK5,
>
>I would still argue that the majority of the constants in this file
>should get moved into the driver .c file that uses them. Putting them
>into the header is better done only for interfaces between the
>driver parts, and for constants that are used by multiple drivers.
These registers still in this header file are either used by mfd core
driver, regulator/rtc/onkey/codec, or used in platform initial setting.
>> +struct pm80x_subchip {
>> +	struct i2c_client *power_page;	/* chip client for power page */
>> +	struct i2c_client *gpadc_page;	/* chip client for gpadc page */
>> +	struct regmap *regmap_power;
>> +	struct regmap *regmap_gpadc;
>> +	unsigned short power_page_addr;	/* power page I2C address */
>> +	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
>> +};
>> +
>> +struct pm80x_chip {
>> +	struct pm80x_subchip *subchip;
>> +	struct device *dev;
>> +	struct i2c_client *client;
>> +	struct regmap *regmap;
>> +	struct regmap_irq_chip *regmap_irq_chip;
>> +	struct regmap_irq_chip_data *irq_data;
>> +	unsigned char version;
>> +	int id;
>> +	int irq;
>> +	int irq_mode;
>> +	int irq_base;
>> +	unsigned int wu_flag;
>> +};
>
>One thing I forgot to ask in the previous review although I had already
>noticed it then: What is the separation of pm80x_chip and pm80x_subchip
>used for?
Pm80x_chip is common for both pm800 and pm805, while pm80x_subchip
handles the other i2c devices as discussed before, only that these two
i2c devices are not as much as pm800 and pm805 i2c device, and they are
only for gpadc & power settings purpose. The subchip can be treated as
some special feature/function for pm800.
>> +struct pm80x_rtc_pdata {
>> +	int		vrtc;
>> +	int		rtc_wakeup;
>> +};
>> +
>> +struct pm80x_platform_data {
>> +	struct pm80x_rtc_pdata *rtc;
>> +	unsigned short pm800_addr;
>> +	unsigned short pm805_addr;
>> +	unsigned short power_page_addr;	/* power page I2C address */
>> +	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
>> +	int irq_mode;		/* Clear interrupt by read/write(0/1) */
>> +	int irq_base;		/* IRQ base of chip */
>> +	int batt_det;		/* enable/disable */
>> +};
>
>You removed the callback here as I asked you to, which I think is a useful
>cleanup, but you can actually managed to convinced me that it would be ok
>to have it, so I don't mind if you want to put it back and use auxdata.
Actually it's convenient for us to put it back currently, and would use
auxdata instead.
>
>On the other hand, I think it probably makes sense to drop the irq_base
>member in this struct and rely on irq domains to allocate them dynamically
>as mentioned before.
Do you mean that both regmap_add_irq_chip and mfd_add_devices api pass -1 as
the irq_base, so that system can dynamically allocate the irq_base for it?
Given the proper regmap_irq_chip & device resource, is there anything else
needed? As I don't want to miss the exact meaning of " transform the
interrupt numbers using the domain in the client drivers" you mentioned above.
>	Arnd
Arnd,  thanks for your review.

Best Regards
Qiao
Arnd Bergmann July 4, 2012, 3:27 p.m. UTC | #3
On Wednesday 04 July 2012, Qiao Zhou wrote:
> >> +	ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
> >> +			      ARRAY_SIZE(onkey_devs), &onkey_resources[0],
> >> +			      chip->irq_base);
> >
> >According to what I discussed with Mark in the previous version, I think you
> >need to pass 0 instead of chip->irq_base here, and transform the interrupt
> >numbers using the domain in the client drivers.
> >
> >> +
> >> +const struct i2c_device_id pm80x_id_table[] = {
> >> +	{"88PM800", CHIP_PM800},
> >> +	{"88PM805", CHIP_PM805},
> >> +};
> >> +MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
> >
> >Since these are separate modules now, you have to move the device table
> >into the split files as well.
> Is it ok to export it in 88pm80x.h?

You mean putting the MODULE_DEVICE_TABLE() into 88pm80x.h? No, that would
not work.

The correct way is to have two MODULE_DEVICE_TABLE statements, one per file.
Actually the table only makes sense for loadable modules, so if you decide
to keep the driver only built-in, it's best to just drop this table.

> >> +
> >> +/**
> >> + * pm80x_reg_read: Read a single 88PM80x register.
> >> + *
> >> + * @map: regmap to read from.
> >> + * @reg: Register to read.
> >> + */
> >> +int pm80x_reg_read(struct regmap *map, u32 reg)
> >> +{
> >> +	unsigned int val;
> >> +	int ret;
> >> +
> >> +	ret = regmap_read(map, reg, &val);
> >> +
> >> +	if (ret < 0)
> >> +		return ret;
> >> +	else
> >> +		return val;
> >> +}
> >> +EXPORT_SYMBOL_GPL(pm80x_reg_read);
> >
> >In your introductory email you write
> >
> >"Exported r/w API which requires regmap handle. as currently the pm800
> >chip has 3 i2c device, only passing a pm80x_chip info can't ensure r/w the
> >register in correct i2c device."
> >
> >Your first driver version had this, then you removed the functions
> >after I asked you to, and now they are back, so I assume there is something
> >I don't see yet. It looks like the function is just an unnecessary wrapper
> >that is better open-coded in the caller. Can you explain again what the
> >difference is?
>
> After you suggest to change the r/w API so that caller doesn't care about it's
> via i2c or spi, it makes sense. However due to pm800 has 3 i2c devices, and
> it's hard to export such interface for pm800. Currently to add such interface
> via regmap handle, caller still doesn't care about the actual hw implement,
> also it's clear that all pm80x sub-driver or plat call the unified r/w API.

But there is nothing unified about the function above, it just calls
regmap_read(), so the caller already has access to the regmap pointer.

> >> +/**
> >> + * pm80x_bulk_read: Read multiple 88PM80x registers
> >> + *
> >> + * @map: regmap to read from
> >> + * @reg: First register
> >> + * @buf: Buffer to fill.  The data will be returned big endian.
> >> + * @count: Number of registers
> >> + */
> >> +int pm80x_bulk_read(struct regmap *map, u32 reg, u8 *buf, int count)
> >> +{
> >> +	return regmap_raw_read(map, reg, buf, count);
> >> +}
> >
> >Unused function? Either export this if you want to provide it as
> >the general API, or drop the function.
> It's used by rtc driver.

Then it needs to be exported, so the rtc driver can be a module.


> >
> >I would still argue that the majority of the constants in this file
> >should get moved into the driver .c file that uses them. Putting them
> >into the header is better done only for interfaces between the
> >driver parts, and for constants that are used by multiple drivers.
>
> These registers still in this header file are either used by mfd core
> driver, regulator/rtc/onkey/codec, or used in platform initial setting.

Exactly. All the values that are only used by the core driver should be
part of the core driver (or a local header in the mfd directory if they
need to be shared between multiple files). The platform code should not
really touch the registers, but only things like the platform_data
structure, which indeed belongs into the global header.

> >> +struct pm80x_subchip {
> >> +	struct i2c_client *power_page;	/* chip client for power page */
> >> +	struct i2c_client *gpadc_page;	/* chip client for gpadc page */
> >> +	struct regmap *regmap_power;
> >> +	struct regmap *regmap_gpadc;
> >> +	unsigned short power_page_addr;	/* power page I2C address */
> >> +	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
> >> +};
> >> +
> >> +struct pm80x_chip {
> >> +	struct pm80x_subchip *subchip;
> >> +	struct device *dev;
> >> +	struct i2c_client *client;
> >> +	struct regmap *regmap;
> >> +	struct regmap_irq_chip *regmap_irq_chip;
> >> +	struct regmap_irq_chip_data *irq_data;
> >> +	unsigned char version;
> >> +	int id;
> >> +	int irq;
> >> +	int irq_mode;
> >> +	int irq_base;
> >> +	unsigned int wu_flag;
> >> +};
> >
> >One thing I forgot to ask in the previous review although I had already
> >noticed it then: What is the separation of pm80x_chip and pm80x_subchip
> >used for?
> Pm80x_chip is common for both pm800 and pm805, while pm80x_subchip
> handles the other i2c devices as discussed before, only that these two
> i2c devices are not as much as pm800 and pm805 i2c device, and they are
> only for gpadc & power settings purpose. The subchip can be treated as
> some special feature/function for pm800.

ok

> >> +struct pm80x_rtc_pdata {
> >> +	int		vrtc;
> >> +	int		rtc_wakeup;
> >> +};
> >> +
> >> +struct pm80x_platform_data {
> >> +	struct pm80x_rtc_pdata *rtc;
> >> +	unsigned short pm800_addr;
> >> +	unsigned short pm805_addr;
> >> +	unsigned short power_page_addr;	/* power page I2C address */
> >> +	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
> >> +	int irq_mode;		/* Clear interrupt by read/write(0/1) */
> >> +	int irq_base;		/* IRQ base of chip */
> >> +	int batt_det;		/* enable/disable */
> >> +};
> >
> >You removed the callback here as I asked you to, which I think is a useful
> >cleanup, but you can actually managed to convinced me that it would be ok
> >to have it, so I don't mind if you want to put it back and use auxdata.
> Actually it's convenient for us to put it back currently, and would use
> auxdata instead.

ok

> >On the other hand, I think it probably makes sense to drop the irq_base
> >member in this struct and rely on irq domains to allocate them dynamically
> >as mentioned before.
> Do you mean that both regmap_add_irq_chip and mfd_add_devices api pass -1 as
> the irq_base, so that system can dynamically allocate the irq_base for it?

regmap_add_irq_chip should pass -1, mfd_add_devices should pass 0.
Mark can probably correct me if that's wrong.

> Given the proper regmap_irq_chip & device resource, is there anything else
> needed? As I don't want to miss the exact meaning of " transform the
> interrupt numbers using the domain in the client drivers" you mentioned above.

The drivers using the numbers need to call regmap_irq_get_virq() to get the
real interrupt number. See include/linux/mfd/wm8994/core.h for an example.

	Arnd
Mark Brown July 4, 2012, 3:33 p.m. UTC | #4
On Wed, Jul 04, 2012 at 03:27:13PM +0000, Arnd Bergmann wrote:
> On Wednesday 04 July 2012, Qiao Zhou wrote:

> > >On the other hand, I think it probably makes sense to drop the irq_base
> > >member in this struct and rely on irq domains to allocate them dynamically
> > >as mentioned before.

> > Do you mean that both regmap_add_irq_chip and mfd_add_devices api pass -1 as
> > the irq_base, so that system can dynamically allocate the irq_base for it?

> regmap_add_irq_chip should pass -1, mfd_add_devices should pass 0.
> Mark can probably correct me if that's wrong.

That's right.

I do need to grovel through the irqdomain code and try to figure out if
the stuff added recently for MFDs to pass an irqdomain about would also
support doing the same mapping.  Unfortunately all the irqdomain code I
found that I didn't write was rather tied to DT which makes things more
obscure, it's not clear what's for irqdomain and what's for DT.
Qiao Zhou July 4, 2012, 3:44 p.m. UTC | #5
> On Wednesday 04 July 2012, Qiao Zhou wrote:
> > >> +	ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
> > >> +			      ARRAY_SIZE(onkey_devs), &onkey_resources[0],
> > >> +			      chip->irq_base);
> > >
> > >According to what I discussed with Mark in the previous version, I think you
> > >need to pass 0 instead of chip->irq_base here, and transform the interrupt
> > >numbers using the domain in the client drivers.
> > >
> > >> +
> > >> +const struct i2c_device_id pm80x_id_table[] = {
> > >> +	{"88PM800", CHIP_PM800},
> > >> +	{"88PM805", CHIP_PM805},
> > >> +};
> > >> +MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
> > >
> > >Since these are separate modules now, you have to move the device table
> > >into the split files as well.
> > Is it ok to export it in 88pm80x.h?
>
> You mean putting the MODULE_DEVICE_TABLE() into 88pm80x.h? No, that would
> not work.
>
> The correct way is to have two MODULE_DEVICE_TABLE statements, one per file.
> Actually the table only makes sense for loadable modules, so if you decide
> to keep the driver only built-in, it's best to just drop this table.
Understood and would update. 
> > >> +
> > >> +/**
> > >> + * pm80x_reg_read: Read a single 88PM80x register.
> > >> + *
> > >> + * @map: regmap to read from.
> > >> + * @reg: Register to read.
> > >> + */
> > >> +int pm80x_reg_read(struct regmap *map, u32 reg)
> > >> +{
> > >> +	unsigned int val;
> > >> +	int ret;
> > >> +
> > >> +	ret = regmap_read(map, reg, &val);
> > >> +
> > >> +	if (ret < 0)
> > >> +		return ret;
> > >> +	else
> > >> +		return val;
> > >> +}
> > >> +EXPORT_SYMBOL_GPL(pm80x_reg_read);
> > >
> > >In your introductory email you write
> > >
> > >"Exported r/w API which requires regmap handle. as currently the pm800
> > >chip has 3 i2c device, only passing a pm80x_chip info can't ensure r/w the
> > >register in correct i2c device."
> > >
> > >Your first driver version had this, then you removed the functions
> > >after I asked you to, and now they are back, so I assume there is something
> > >I don't see yet. It looks like the function is just an unnecessary wrapper
> > >that is better open-coded in the caller. Can you explain again what the
> > >difference is?
> >
> > After you suggest to change the r/w API so that caller doesn't care about it's
> > via i2c or spi, it makes sense. However due to pm800 has 3 i2c devices, and
> > it's hard to export such interface for pm800. Currently to add such interface
> > via regmap handle, caller still doesn't care about the actual hw implement,
> > also it's clear that all pm80x sub-driver or plat call the unified r/w API.
>
> But there is nothing unified about the function above, it just calls
> regmap_read(), so the caller already has access to the regmap pointer.
would just use open-coded regmap api.
> > >> +/**
> > >> + * pm80x_bulk_read: Read multiple 88PM80x registers
> > >> + *
> > >> + * @map: regmap to read from
> > >> + * @reg: First register
> > >> + * @buf: Buffer to fill.  The data will be returned big endian.
> > >> + * @count: Number of registers
> > >> + */
> > >> +int pm80x_bulk_read(struct regmap *map, u32 reg, u8 *buf, int count)
> > >> +{
> > >> +	return regmap_raw_read(map, reg, buf, count);
> > >> +}
> > >
> > >Unused function? Either export this if you want to provide it as
> > >the general API, or drop the function.
> > It's used by rtc driver.
>
> Then it needs to be exported, so the rtc driver can be a module.
Would update.
>
> > >On the other hand, I think it probably makes sense to drop the irq_base
> > >member in this struct and rely on irq domains to allocate them dynamically
> > >as mentioned before.
> > Do you mean that both regmap_add_irq_chip and mfd_add_devices api pass -1 as
> > the irq_base, so that system can dynamically allocate the irq_base for it?
>
> regmap_add_irq_chip should pass -1, mfd_add_devices should pass 0.
> Mark can probably correct me if that's wrong.
>
> > Given the proper regmap_irq_chip & device resource, is there anything else
> > needed? As I don't want to miss the exact meaning of " transform the
> > interrupt numbers using the domain in the client drivers" you mentioned above.
>
> The drivers using the numbers need to call regmap_irq_get_virq() to get the
> real interrupt number. See include/linux/mfd/wm8994/core.h for an example.
OK, understood. Will check the reference and update accordingly.
>	Arnd
Arnd, thanks for the review.

Best Regards
Qiao
Qiao Zhou July 4, 2012, 3:47 p.m. UTC | #6
> On Wed, Jul 04, 2012 at 03:27:13PM +0000, Arnd Bergmann wrote:
> > On Wednesday 04 July 2012, Qiao Zhou wrote:
>>
> > > >On the other hand, I think it probably makes sense to drop the 
> > > >irq_base member in this struct and rely on irq domains to allocate 
> > > >them dynamically as mentioned before.
>
> > > Do you mean that both regmap_add_irq_chip and mfd_add_devices api 
> > > pass -1 as the irq_base, so that system can dynamically allocate the irq_base for it?
>
> > regmap_add_irq_chip should pass -1, mfd_add_devices should pass 0.
> > Mark can probably correct me if that's wrong.
>
> That's right.
>
Mark, thanks for the confirmation.

Best Regards
Qiao
diff mbox

Patch

diff --git a/drivers/mfd/88pm800-core.c b/drivers/mfd/88pm800-core.c
new file mode 100644
index 0000000..fdc4853
--- /dev/null
+++ b/drivers/mfd/88pm800-core.c
@@ -0,0 +1,492 @@ 
+/*
+ * Base driver for Marvell 88PM800
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Joseph(Yossi) Hanin <yhanin@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+static struct resource rtc_resources[] = {
+	{
+	 .name = "88pm80x-rtc",
+	 .start = PM800_IRQ_RTC,
+	 .end = PM800_IRQ_RTC,
+	 .flags = IORESOURCE_IRQ,
+	 },
+};
+
+static struct mfd_cell rtc_devs[] = {
+	{
+	 .name = "88pm80x-rtc",
+	 .num_resources = ARRAY_SIZE(rtc_resources),
+	 .resources = &rtc_resources[0],
+	 .id = -1,
+	 },
+};
+
+static struct resource onkey_resources[] = {
+	{
+	 .name = "88pm80x-onkey",
+	 .start = PM800_IRQ_ONKEY,
+	 .end = PM800_IRQ_ONKEY,
+	 .flags = IORESOURCE_IRQ,
+	 },
+};
+
+static struct mfd_cell onkey_devs[] = {
+	{
+	 .name = "88pm80x-onkey",
+	 .num_resources = 1,
+	 .resources = &onkey_resources[0],
+	 .id = -1,
+	 },
+};
+
+static const struct regmap_irq pm800_irqs[] = {
+	/* INT0 */
+	[PM800_IRQ_ONKEY] = {
+		.mask = PM800_ONKEY_INT_ENA1,
+	},
+	[PM800_IRQ_EXTON] = {
+		.mask = PM800_EXTON_INT_ENA1,
+	},
+	[PM800_IRQ_CHG] = {
+		.mask = PM800_CHG_INT_ENA1,
+	},
+	[PM800_IRQ_BAT] = {
+		.mask = PM800_BAT_INT_ENA1,
+	},
+	[PM800_IRQ_RTC] = {
+		.mask = PM800_RTC_INT_ENA1,
+	},
+	[PM800_IRQ_CLASSD] = {
+		.mask = PM800_CLASSD_OC_INT_ENA1,
+	},
+	/* INT1 */
+	[PM800_IRQ_VBAT] = {
+		.reg_offset = 1,
+		.mask = PM800_VBAT_INT_ENA2,
+	},
+	[PM800_IRQ_VSYS] = {
+		.reg_offset = 1,
+		.mask = PM800_VSYS_INT_ENA2,
+	},
+	[PM800_IRQ_VCHG] = {
+		.reg_offset = 1,
+		.mask = PM800_VCHG_INT_ENA2,
+	},
+	[PM800_IRQ_TINT] = {
+		.reg_offset = 1,
+		.mask = PM800_TINT_INT_ENA2,
+	},
+	/* INT2 */
+	[PM800_IRQ_GPADC0] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC0_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC1] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC1_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC2] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC2_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC3] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC3_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC4] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC4_INT_ENA3,
+	},
+	/* INT3 */
+	[PM800_IRQ_GPIO0] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO0_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO1] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO1_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO2] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO2_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO3] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO3_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO4] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO4_INT_ENA4,
+	},
+};
+
+static int __devinit device_gpadc_init(struct pm80x_chip *chip,
+				       struct pm80x_platform_data *pdata)
+{
+	struct pm80x_subchip *subchip = chip->subchip;
+	struct regmap *map = subchip->regmap_gpadc;
+	int data = 0, mask = 0, ret = 0;
+
+	if (!map) {
+		dev_warn(chip->dev,
+			 "Warning: gpadc regmap is not available!\n");
+		return -EINVAL;
+	}
+	/*
+	 * initialize GPADC without activating it turn on GPADC
+	 * measurments
+	 */
+	ret = pm80x_set_bits(map,
+				 PM800_GPADC_MISC_CONFIG2,
+				 PM800_GPADC_MISC_GPFSM_EN,
+				 PM800_GPADC_MISC_GPFSM_EN);
+	if (ret < 0)
+		goto out;
+	/*
+	 * This function configures the ADC as requires for
+	 * CP implementation.CP does not "own" the ADC configuration
+	 * registers and relies on AP.
+	 * Reason: enable automatic ADC measurements needed
+	 * for CP to get VBAT and RF temperature readings.
+	 */
+	ret = pm80x_set_bits(map, PM800_GPADC_MEAS_EN1,
+				 PM800_MEAS_EN1_VBAT, PM800_MEAS_EN1_VBAT);
+	if (ret < 0)
+		goto out;
+	ret = pm80x_set_bits(map, PM800_GPADC_MEAS_EN2,
+				 (PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN),
+				 (PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN));
+	if (ret < 0)
+		goto out;
+
+	/*
+	 * the defult of PM800 is GPADC operates at 100Ks/s rate
+	 * and Number of GPADC slots with active current bias prior
+	 * to GPADC sampling = 1 slot for all GPADCs set for
+	 * Temprature mesurmants
+	 */
+	mask = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 |
+		PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3);
+
+	if (pdata && (pdata->batt_det == 0))
+		data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 |
+			PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3);
+	else
+		data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN2 |
+			PM800_GPADC_GP_BIAS_EN3);
+
+	ret = pm80x_set_bits(map, PM800_GP_BIAS_ENA1, mask, data);
+	if (ret < 0)
+		goto out;
+
+	dev_info(chip->dev, "pm800 device_gpadc_init: Done\n");
+	return 0;
+
+out:
+	dev_info(chip->dev, "pm800 device_gpadc_init: Failed!\n");
+	return ret;
+}
+
+static int __devinit device_irq_init_800(struct pm80x_chip *chip)
+{
+	struct regmap *map = chip->regmap;
+	unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+	int data, mask, ret = -EINVAL;
+
+	if (!map || !chip->irq) {
+		dev_err(chip->dev, "incorrect parameters\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * irq_mode defines the way of clearing interrupt. it's read-clear by
+	 * default.
+	 */
+	mask =
+	    PM800_WAKEUP2_INV_INT | PM800_WAKEUP2_INT_CLEAR |
+	    PM800_WAKEUP2_INT_MASK;
+
+	data = PM800_WAKEUP2_INT_CLEAR;
+	ret = pm80x_set_bits(map, PM800_WAKEUP2, mask, data);
+
+	if (ret < 0)
+		goto out;
+
+	ret =
+	    regmap_add_irq_chip(chip->regmap, chip->irq, flags, chip->irq_base,
+				chip->regmap_irq_chip, &chip->irq_data);
+
+out:
+	return ret;
+}
+
+static void device_irq_exit_800(struct pm80x_chip *chip)
+{
+	regmap_del_irq_chip(chip->irq, chip->irq_data);
+}
+
+static struct regmap_irq_chip pm800_irq_chip = {
+	.name = "88pm800",
+	.irqs = pm800_irqs,
+	.num_irqs = ARRAY_SIZE(pm800_irqs),
+
+	.num_regs = 4,
+	.status_base = PM800_INT_STATUS1,
+	.mask_base = PM800_INT_ENA_1,
+	.ack_base = PM800_INT_STATUS1,
+};
+
+static int pm800_pages_init(struct pm80x_chip *chip)
+{
+	struct pm80x_subchip *subchip;
+	struct i2c_client *client = chip->client;
+
+	subchip = chip->subchip;
+	/* PM800 block power: i2c addr 0x31 */
+	if (subchip->power_page_addr) {
+		subchip->power_page =
+		    i2c_new_dummy(client->adapter, subchip->power_page_addr);
+		subchip->regmap_power =
+		    devm_regmap_init_i2c(subchip->power_page,
+					 &pm80x_regmap_config);
+		i2c_set_clientdata(subchip->power_page, chip);
+	} else
+		dev_info(chip->dev,
+			 "PM800 block power 0x31: No power_page_addr\n");
+
+	/* PM800 block GPADC: i2c addr 0x32 */
+	if (subchip->gpadc_page_addr) {
+		subchip->gpadc_page = i2c_new_dummy(client->adapter,
+						    subchip->gpadc_page_addr);
+		subchip->regmap_gpadc =
+		    devm_regmap_init_i2c(subchip->gpadc_page,
+					 &pm80x_regmap_config);
+		i2c_set_clientdata(subchip->gpadc_page, chip);
+	} else
+		dev_info(chip->dev,
+			 "PM800 block GPADC 0x32: No gpadc_page_addr\n");
+
+	return 0;
+}
+
+static void pm800_pages_exit(struct pm80x_chip *chip)
+{
+	struct pm80x_subchip *subchip;
+
+	regmap_exit(chip->regmap);
+	i2c_unregister_device(chip->client);
+
+	subchip = chip->subchip;
+	if (subchip->power_page) {
+		regmap_exit(subchip->regmap_power);
+		i2c_unregister_device(subchip->power_page);
+	}
+	if (subchip->gpadc_page) {
+		regmap_exit(subchip->regmap_gpadc);
+		i2c_unregister_device(subchip->gpadc_page);
+	}
+}
+
+static int __devinit device_800_init(struct pm80x_chip *chip,
+				     struct pm80x_platform_data *pdata)
+{
+	int ret, pmic_id;
+
+	ret = pm80x_reg_read(chip->regmap, PM800_CHIP_ID);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
+		goto out;
+	}
+
+	pmic_id = ret & PM80X_VERSION_MASK;
+
+	if ((pmic_id >= PM800_CHIP_A0) && (pmic_id <= PM800_CHIP_END)) {
+		chip->version = ret;
+		dev_info(chip->dev,
+			 "88PM80x:Marvell 88PM800 (ID:0x%x) detected\n", ret);
+	} else {
+		dev_err(chip->dev,
+			"Failed to detect Marvell 88PM800:ChipID[0x%x]\n", ret);
+		goto out;
+	}
+
+	/*
+	 * alarm wake up bit will be clear in device_irq_init(),
+	 * read before that
+	 */
+	ret = pm80x_reg_read(chip->regmap, PM800_RTC_CONTROL);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to read RTC register: %d\n", ret);
+		goto out;
+	}
+	if (ret & PM800_ALARM_WAKEUP) {
+		if (pdata && pdata->rtc)
+			pdata->rtc->rtc_wakeup = 1;
+	}
+
+	ret = device_gpadc_init(chip, pdata);
+	if (ret < 0) {
+		dev_err(chip->dev, "[%s]Failed to init gpadc\n", __func__);
+		goto out;
+	}
+
+	chip->regmap_irq_chip = &pm800_irq_chip;
+
+	ret = device_irq_init_800(chip);
+	if (ret < 0) {
+		dev_err(chip->dev, "[%s]Failed to init pm800 irq\n", __func__);
+		goto out;
+	}
+
+	ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
+			      ARRAY_SIZE(onkey_devs), &onkey_resources[0],
+			      chip->irq_base);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to add onkey subdev\n");
+		goto out_dev;
+	} else
+		dev_info(chip->dev, "[%s]:Added mfd onkey_devs\n", __func__);
+
+	if (pdata && pdata->rtc) {
+		rtc_devs[0].platform_data = pdata->rtc;
+		rtc_devs[0].pdata_size = sizeof(struct pm80x_rtc_pdata);
+		ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0],
+				      ARRAY_SIZE(rtc_devs), NULL,
+				      chip->irq_base);
+		if (ret < 0) {
+			dev_err(chip->dev, "Failed to add rtc subdev\n");
+			goto out_dev;
+		} else
+			dev_info(chip->dev,
+				 "[%s]:Added mfd rtc_devs\n", __func__);
+	}
+
+	return 0;
+out_dev:
+	mfd_remove_devices(chip->dev);
+	device_irq_exit_800(chip);
+out:
+	return ret;
+}
+
+static int __devinit pm800_probe(struct i2c_client *client,
+				 const struct i2c_device_id *id)
+{
+	int ret = 0;
+	struct pm80x_chip *chip;
+	struct pm80x_platform_data *pdata = client->dev.platform_data;
+	struct pm80x_subchip *subchip;
+
+	ret = pm80x_init(client, id);
+	if (ret) {
+		dev_err(&client->dev, "pm800_init fail\n");
+		goto out_init;
+	}
+
+	chip = i2c_get_clientdata(client);
+
+	/* init subchip for PM800 */
+	subchip =
+	    devm_kzalloc(&client->dev, sizeof(struct pm80x_subchip),
+			 GFP_KERNEL);
+	if (!subchip) {
+		ret = -ENOMEM;
+		goto err_subchip_alloc;
+	}
+
+	subchip->power_page_addr = pdata->power_page_addr;
+	subchip->gpadc_page_addr = pdata->gpadc_page_addr;
+	chip->subchip = subchip;
+
+	ret = device_800_init(chip, pdata);
+	if (ret) {
+		dev_err(chip->dev, "%s id 0x%x failed!\n", __func__, chip->id);
+		goto err_800_init;
+	}
+
+	ret = pm800_pages_init(chip);
+	if (ret) {
+		dev_err(&client->dev, "pm800_pages_init failed!\n");
+		goto err_page_init;
+	}
+
+err_page_init:
+	mfd_remove_devices(chip->dev);
+	device_irq_exit_800(chip);
+err_800_init:
+	devm_kfree(&client->dev, subchip);
+err_subchip_alloc:
+	pm80x_deinit(client);
+out_init:
+	return ret;
+}
+
+static int __devexit pm800_remove(struct i2c_client *client)
+{
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	mfd_remove_devices(chip->dev);
+	device_irq_exit_800(chip);
+
+	pm800_pages_exit(chip);
+	devm_kfree(&client->dev, chip->subchip);
+
+	pm80x_deinit(client);
+
+	return 0;
+}
+
+static struct i2c_driver pm800_driver = {
+	.driver = {
+		.name = "88PM80X",
+		.owner = THIS_MODULE,
+		.pm = &pm80x_pm_ops,
+		},
+	.probe = pm800_probe,
+	.remove = __devexit_p(pm800_remove),
+	.id_table = pm80x_id_table,
+};
+
+static int __init pm800_i2c_init(void)
+{
+	return i2c_add_driver(&pm800_driver);
+}
+subsys_initcall(pm800_i2c_init);
+
+static void __exit pm800_i2c_exit(void)
+{
+	i2c_del_driver(&pm800_driver);
+}
+module_exit(pm800_i2c_exit);
+
+MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM800");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/88pm805-core.c b/drivers/mfd/88pm805-core.c
new file mode 100644
index 0000000..d04e7f8
--- /dev/null
+++ b/drivers/mfd/88pm805-core.c
@@ -0,0 +1,273 @@ 
+/*
+ * Base driver for Marvell 88PM805
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Joseph(Yossi) Hanin <yhanin@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+static struct resource codec_resources[] = {
+	{
+	 /* Headset microphone insertion or removal */
+	 .name = "micin",
+	 .start = PM805_IRQ_MIC_DET,
+	 .end = PM805_IRQ_MIC_DET,
+	 .flags = IORESOURCE_IRQ,
+	 },
+	{
+	 /* Audio short HP1 */
+	 .name = "audio-short1",
+	 .start = PM805_IRQ_HP1_SHRT,
+	 .end = PM805_IRQ_HP1_SHRT,
+	 .flags = IORESOURCE_IRQ,
+	 },
+	{
+	 /* Audio short HP2 */
+	 .name = "audio-short2",
+	 .start = PM805_IRQ_HP2_SHRT,
+	 .end = PM805_IRQ_HP2_SHRT,
+	 .flags = IORESOURCE_IRQ,
+	 },
+};
+
+static struct mfd_cell codec_devs[] = {
+	{
+	 .name = "88pm80x-codec",
+	 .num_resources = ARRAY_SIZE(codec_resources),
+	 .resources = &codec_resources[0],
+	 .id = -1,
+	 },
+};
+
+static struct regmap_irq pm805_irqs[] = {
+	/* INT0 */
+	[PM805_IRQ_LDO_OFF] = {
+		.mask = PM805_INT1_HP1_SHRT,
+	},
+	[PM805_IRQ_SRC_DPLL_LOCK] = {
+		.mask = PM805_INT1_HP2_SHRT,
+	},
+	[PM805_IRQ_CLIP_FAULT] = {
+		.mask = PM805_INT1_MIC_CONFLICT,
+	},
+	[PM805_IRQ_MIC_CONFLICT] = {
+		.mask = PM805_INT1_CLIP_FAULT,
+	},
+	[PM805_IRQ_HP2_SHRT] = {
+		.mask = PM805_INT1_LDO_OFF,
+	},
+	[PM805_IRQ_HP1_SHRT] = {
+		.mask = PM805_INT1_SRC_DPLL_LOCK,
+	},
+	/* INT1 */
+	[PM805_IRQ_FINE_PLL_FAULT] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_MIC_DET,
+	},
+	[PM805_IRQ_RAW_PLL_FAULT] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_SHRT_BTN_DET,
+	},
+	[PM805_IRQ_VOLP_BTN_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_VOLM_BTN_DET,
+	},
+	[PM805_IRQ_VOLM_BTN_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_VOLP_BTN_DET,
+	},
+	[PM805_IRQ_SHRT_BTN_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_RAW_PLL_FAULT,
+	},
+	[PM805_IRQ_MIC_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_FINE_PLL_FAULT,
+	},
+};
+
+static int __devinit device_irq_init_805(struct pm80x_chip *chip)
+{
+	struct regmap *map = chip->regmap;
+	unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+	int data, mask, ret = -EINVAL;
+
+	if (!map || !chip->irq) {
+		dev_err(chip->dev, "incorrect parameters\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * irq_mode defines the way of clearing interrupt. it's read-clear by
+	 * default.
+	 */
+	mask =
+	    PM805_STATUS0_INT_CLEAR | PM805_STATUS0_INV_INT |
+	    PM800_STATUS0_INT_MASK;
+
+	data = PM805_STATUS0_INT_CLEAR;
+	ret = pm80x_set_bits(map, PM805_INT_STATUS0, mask, data);
+	/*
+	 * PM805_INT_STATUS is under 32K clock domain, so need to
+	 * add proper delay before the next I2C register access.
+	 */
+	msleep(1);
+
+	if (ret < 0)
+		goto out;
+
+	ret =
+	    regmap_add_irq_chip(chip->regmap, chip->irq, flags, chip->irq_base,
+				chip->regmap_irq_chip, &chip->irq_data);
+
+out:
+	return ret;
+}
+
+static void device_irq_exit_805(struct pm80x_chip *chip)
+{
+	regmap_del_irq_chip(chip->irq, chip->irq_data);
+}
+
+static struct regmap_irq_chip pm805_irq_chip = {
+	.name = "88pm805",
+	.irqs = pm805_irqs,
+	.num_irqs = ARRAY_SIZE(pm805_irqs),
+
+	.num_regs = 2,
+	.status_base = PM805_INT_STATUS1,
+	.mask_base = PM805_INT_MASK1,
+	.ack_base = PM805_INT_STATUS1,
+};
+
+static int __devinit device_805_init(struct pm80x_chip *chip)
+{
+	int ret = 0;
+	struct regmap *map = chip->regmap;
+
+	if (!map) {
+		dev_err(chip->dev, "regmap is invalid\n");
+		return -EINVAL;
+	}
+
+	ret = pm80x_reg_read(map, PM805_CHIP_ID);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
+		goto out_irq_init;
+	}
+	chip->version = ret;
+
+	chip->regmap_irq_chip = &pm805_irq_chip;
+
+	ret = device_irq_init_805(chip);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to init pm805 irq!\n");
+		goto out_irq_init;
+	}
+
+	ret = mfd_add_devices(chip->dev, 0, &codec_devs[0],
+			      ARRAY_SIZE(codec_devs), &codec_resources[0], 0);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to add codec subdev\n");
+		goto out_codec;
+	} else
+		dev_info(chip->dev, "[%s]:Added mfd codec_devs\n", __func__);
+
+	return 0;
+
+out_codec:
+	device_irq_exit_805(chip);
+out_irq_init:
+	return ret;
+}
+
+static int __devinit pm805_probe(struct i2c_client *client,
+				 const struct i2c_device_id *id)
+{
+	int ret = 0;
+	struct pm80x_chip *chip;
+
+	ret = pm80x_init(client, id);
+	if (ret) {
+		dev_err(&client->dev, "pm805_init fail!\n");
+		goto out_init;
+	}
+
+	chip = i2c_get_clientdata(client);
+
+	ret = device_805_init(chip);
+	if (ret) {
+		dev_err(chip->dev, "%s id 0x%x failed!\n", __func__, chip->id);
+		goto err_805_init;
+	}
+
+err_805_init:
+	pm80x_deinit(client);
+out_init:
+	return ret;
+}
+
+static int __devexit pm805_remove(struct i2c_client *client)
+{
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	mfd_remove_devices(chip->dev);
+	device_irq_exit_805(chip);
+
+	pm80x_deinit(client);
+
+	return 0;
+}
+
+static struct i2c_driver pm805_driver = {
+	.driver = {
+		.name = "88PM80X",
+		.owner = THIS_MODULE,
+		.pm = &pm80x_pm_ops,
+		},
+	.probe = pm805_probe,
+	.remove = __devexit_p(pm805_remove),
+	.id_table = pm80x_id_table,
+};
+
+static int __init pm805_i2c_init(void)
+{
+	return i2c_add_driver(&pm805_driver);
+}
+subsys_initcall(pm805_i2c_init);
+
+static void __exit pm805_i2c_exit(void)
+{
+	i2c_del_driver(&pm805_driver);
+}
+module_exit(pm805_i2c_exit);
+
+MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM805");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/88pm80x-i2c.c b/drivers/mfd/88pm80x-i2c.c
new file mode 100644
index 0000000..fe37900
--- /dev/null
+++ b/drivers/mfd/88pm80x-i2c.c
@@ -0,0 +1,203 @@ 
+/*
+ * I2C driver for Marvell 88PM80x
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Joseph(Yossi) Hanin <yhanin@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <linux/err.h>
+
+
+const struct regmap_config pm80x_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+const struct i2c_device_id pm80x_id_table[] = {
+	{"88PM800", CHIP_PM800},
+	{"88PM805", CHIP_PM805},
+};
+MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
+
+/**
+ * pm80x_reg_read: Read a single 88PM80x register.
+ *
+ * @map: regmap to read from.
+ * @reg: Register to read.
+ */
+int pm80x_reg_read(struct regmap *map, u32 reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(map, reg, &val);
+
+	if (ret < 0)
+		return ret;
+	else
+		return val;
+}
+EXPORT_SYMBOL_GPL(pm80x_reg_read);
+
+/**
+ * pm80x_bulk_read: Read multiple 88PM80x registers
+ *
+ * @map: regmap to read from
+ * @reg: First register
+ * @buf: Buffer to fill.  The data will be returned big endian.
+ * @count: Number of registers
+ */
+int pm80x_bulk_read(struct regmap *map, u32 reg, u8 *buf, int count)
+{
+	return regmap_raw_read(map, reg, buf, count);
+}
+
+/**
+ * pm80x_reg_write: Write a single 88PM80x register.
+ *
+ * @map: regmap to write to.
+ * @reg: Register to write to.
+ * @val: Value to write.
+ */
+int pm80x_reg_write(struct regmap *map, u32 reg, u32 val)
+{
+	return regmap_write(map, reg, val);
+}
+EXPORT_SYMBOL_GPL(pm80x_reg_write);
+
+/**
+ * pm80x_bulk_write: Write multiple 88PM80x registers
+ *
+ * @map: regmap to write to
+ * @reg: First register
+ * @buf: Buffer to write from.  Data must be big-endian formatted.
+ * @count: Number of registers
+ */
+int pm80x_bulk_write(struct regmap *map, u32 reg, const u8 *buf, int count)
+{
+	return regmap_raw_write(map, reg, buf, count * sizeof(u8));
+}
+EXPORT_SYMBOL_GPL(pm80x_bulk_write);
+
+/**
+ * pm80x_set_bits: Set the value of a bitfield in a 88PM80x register
+ *
+ * @map: regmap to write to.
+ * @reg: Register to write to.
+ * @mask: Mask of bits to set.
+ * @val: Value to set (unshifted)
+ */
+int pm80x_set_bits(struct regmap *map, u32 reg, u32 mask, u32 val)
+{
+	return regmap_update_bits(map, reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(pm80x_set_bits);
+
+int __devinit pm80x_init(struct i2c_client *client,
+				 const struct i2c_device_id *id)
+{
+	struct pm80x_platform_data *pdata = client->dev.platform_data;
+	struct pm80x_chip *chip;
+	struct regmap *map;
+	int ret = 0;
+
+	if (!pdata) {
+		dev_err(&client->dev, "No platform data in %s!\n", __func__);
+		return -EINVAL;
+	}
+
+	chip =
+	    devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	map = devm_regmap_init_i2c(client, &pm80x_regmap_config);
+	if (IS_ERR(map)) {
+		ret = PTR_ERR(map);
+		dev_err(&client->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto err_regmap_init;
+	}
+
+	chip->id = id->driver_data;
+	if (chip->id < CHIP_PM800 || chip->id > CHIP_PM805) {
+		ret = -EINVAL;
+		goto err_chip_id;
+	}
+
+	chip->client = client;
+	chip->regmap = map;
+
+	chip->irq = client->irq;
+	chip->irq_base = pdata->irq_base;
+
+	chip->dev = &client->dev;
+	dev_set_drvdata(chip->dev, chip);
+	i2c_set_clientdata(chip->client, chip);
+
+	device_init_wakeup(&client->dev, 1);
+
+	return 0;
+
+err_chip_id:
+	regmap_exit(map);
+err_regmap_init:
+	devm_kfree(&client->dev, chip);
+	return ret;
+}
+
+int __devexit pm80x_deinit(struct i2c_client *client)
+{
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	regmap_exit(chip->regmap);
+	devm_kfree(&client->dev, chip);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pm80x_suspend(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	if (chip && chip->wu_flag)
+		if (device_may_wakeup(chip->dev))
+			enable_irq_wake(chip->irq);
+
+	return 0;
+}
+
+static int pm80x_resume(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	if (chip && chip->wu_flag)
+		if (device_may_wakeup(chip->dev))
+			disable_irq_wake(chip->irq);
+
+	return 0;
+}
+#endif
+
+SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);
+
+MODULE_DESCRIPTION("I2C Driver for Marvell 88PM80x");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e129c82..96dd2d7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -20,6 +20,18 @@  config MFD_88PM860X
 	  select individual components like voltage regulators, RTC and
 	  battery-charger under the corresponding menus.
 
+config MFD_88PM80X
+	bool "Support Marvell 88PM800/88PM805"
+	depends on I2C=y && GENERIC_HARDIRQS
+	select REGMAP_I2C
+	select REGMAP_IRQ
+	select MFD_CORE
+	help
+	  This supports for Marvell 88PM800/88PM805 Power Management IC.
+	  This includes the I2C driver and the core APIs _only_, you have to
+	  select individual components like voltage regulators, RTC and
+	  battery-charger under the corresponding menus.
+
 config MFD_SM501
 	tristate "Support for Silicon Motion SM501"
 	 ---help---
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 75f6ed6..dc2584e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -3,7 +3,9 @@ 
 #
 
 88pm860x-objs			:= 88pm860x-core.o 88pm860x-i2c.o
+88pm80x-objs			:= 88pm800-core.o 88pm805-core.o 88pm80x-i2c.o
 obj-$(CONFIG_MFD_88PM860X)	+= 88pm860x.o
+obj-$(CONFIG_MFD_88PM80X)	+= 88pm80x.o
 obj-$(CONFIG_MFD_SM501)		+= sm501.o
 obj-$(CONFIG_MFD_ASIC3)		+= asic3.o tmio_core.o
 
diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
new file mode 100644
index 0000000..115348a
--- /dev/null
+++ b/include/linux/mfd/88pm80x.h
@@ -0,0 +1,521 @@ 
+/*
+ * Marvell 88PM80x Interface
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * 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_88PM80X_H
+#define __LINUX_MFD_88PM80X_H
+
+#define PM80X_VERSION_MASK		(0xFF)	/* 80X chip ID mask */
+enum {
+	CHIP_INVALID = 0,
+	CHIP_PM800,
+	CHIP_PM805,
+	CHIP_MAX,
+};
+
+enum {
+	/* Procida */
+	PM800_CHIP_A0  = 0x60,
+	PM800_CHIP_A1  = 0x61,
+	PM800_CHIP_B0  = 0x62,
+	PM800_CHIP_C0  = 0x63,
+	PM800_CHIP_END = PM800_CHIP_C0,
+
+	/* Make sure to update this to the last stepping */
+	PM8XXX_CHIP_END = PM800_CHIP_END
+};
+
+enum {
+	PM800_ID_INVALID,
+	PM800_ID_VIBRATOR,
+	PM800_ID_SOUND,
+	PM800_ID_MAX,
+};
+
+enum {
+	PM800_ID_BUCK1 = 0,
+	PM800_ID_BUCK2,
+	PM800_ID_BUCK3,
+	PM800_ID_BUCK4,
+	PM800_ID_BUCK5,
+
+	PM800_ID_LDO1,
+	PM800_ID_LDO2,
+	PM800_ID_LDO3,
+	PM800_ID_LDO4,
+	PM800_ID_LDO5,
+	PM800_ID_LDO6,
+	PM800_ID_LDO7,
+	PM800_ID_LDO8,
+	PM800_ID_LDO9,
+	PM800_ID_LDO10,
+	PM800_ID_LDO11,
+	PM800_ID_LDO12,
+	PM800_ID_LDO13,
+	PM800_ID_LDO14,
+	PM800_ID_LDO15,
+	PM800_ID_LDO16,
+	PM800_ID_LDO17,
+	PM800_ID_LDO18,
+	PM800_ID_LDO19,
+
+	PM800_ID_RG_MAX,
+};
+#define PM800_MAX_REGULATOR	PM800_ID_RG_MAX	/* 5 Bucks, 19 LDOs */
+#define PM800_NUM_BUCK (5)	/*5 Bucks */
+#define PM800_NUM_LDO (19)	/*19 Bucks */
+
+/* 88PM805 Registers */
+#define PM805_CHIP_ID			(0x00)
+
+/* Audio */
+
+/* 88PM800 registers */
+enum {
+	PM80X_INVALID_PAGE = 0,
+	PM80X_BASE_PAGE,
+	PM80X_POWER_PAGE,
+	PM80X_GPADC_PAGE,
+	PM80X_TEST_PAGE,
+};
+
+/* page 0 basic: slave adder 0x60 */
+
+/* Interrupt Registers */
+#define PM800_CHIP_ID			(0x00)
+
+#define PM800_STATUS_1			(0x01)
+#define PM800_ONKEY_STS1		(1 << 0)
+#define PM800_EXTON_STS1		(1 << 1)
+#define PM800_CHG_STS1			(1 << 2)
+#define PM800_BAT_STS1			(1 << 3)
+#define PM800_VBUS_STS1			(1 << 4)
+#define PM800_LDO_PGOOD_STS1	(1 << 5)
+#define PM800_BUCK_PGOOD_STS1	(1 << 6)
+
+#define PM800_STATUS_2			(0x02)
+#define PM800_RTC_ALARM_STS2	(1 << 0)
+
+#define PM800_INT_STATUS1		(0x05)
+#define PM800_ONKEY_INT_STS1		(1 << 0)
+#define PM800_EXTON_INT_STS1		(1 << 1)
+#define PM800_CHG_INT_STS1			(1 << 2)
+#define PM800_BAT_INT_STS1			(1 << 3)
+#define PM800_RTC_INT_STS1			(1 << 4)
+#define PM800_CLASSD_OC_INT_STS1	(1 << 5)
+
+#define PM800_INT_STATUS2		(0x06)
+#define PM800_VBAT_INT_STS2		(1 << 0)
+#define PM800_VSYS_INT_STS2		(1 << 1)
+#define PM800_VCHG_INT_STS2		(1 << 2)
+#define PM800_TINT_INT_STS2		(1 << 3)
+#define PM800_GPADC0_INT_STS2	(1 << 4)
+#define PM800_TBAT_INT_STS2		(1 << 5)
+#define PM800_GPADC2_INT_STS2	(1 << 6)
+#define PM800_GPADC3_INT_STS2	(1 << 7)
+
+#define PM800_INT_STATUS3		(0x07)
+
+#define PM800_INT_STATUS4		(0x08)
+#define PM800_GPIO0_INT_STS4		(1 << 0)
+#define PM800_GPIO1_INT_STS4		(1 << 1)
+#define PM800_GPIO2_INT_STS4		(1 << 2)
+#define PM800_GPIO3_INT_STS4		(1 << 3)
+#define PM800_GPIO4_INT_STS4		(1 << 4)
+
+#define PM800_INT_ENA_1		(0x09)
+#define PM800_ONKEY_INT_ENA1		(1 << 0)
+#define PM800_EXTON_INT_ENA1		(1 << 1)
+#define PM800_CHG_INT_ENA1			(1 << 2)
+#define PM800_BAT_INT_ENA1			(1 << 3)
+#define PM800_RTC_INT_ENA1			(1 << 4)
+#define PM800_CLASSD_OC_INT_ENA1	(1 << 5)
+
+#define PM800_INT_ENA_2		(0x0A)
+#define PM800_VBAT_INT_ENA2		(1 << 0)
+#define PM800_VSYS_INT_ENA2		(1 << 1)
+#define PM800_VCHG_INT_ENA2		(1 << 2)
+#define PM800_TINT_INT_ENA2		(1 << 3)
+
+#define PM800_INT_ENA_3		(0x0B)
+#define PM800_GPADC0_INT_ENA3		(1 << 0)
+#define PM800_GPADC1_INT_ENA3		(1 << 1)
+#define PM800_GPADC2_INT_ENA3		(1 << 2)
+#define PM800_GPADC3_INT_ENA3		(1 << 3)
+#define PM800_GPADC4_INT_ENA3		(1 << 4)
+
+#define PM800_INT_ENA_4		(0x0C)
+#define PM800_GPIO0_INT_ENA4		(1 << 0)
+#define PM800_GPIO1_INT_ENA4		(1 << 1)
+#define PM800_GPIO2_INT_ENA4		(1 << 2)
+#define PM800_GPIO3_INT_ENA4		(1 << 3)
+#define PM800_GPIO4_INT_ENA4		(1 << 4)
+
+/* number of INT_ENA & INT_STATUS regs */
+#define PM800_INT_REG_NUM			(4)
+
+/* Wakeup Registers */
+#define PM800_WAKEUP1		(0x0D)
+
+#define PM800_WAKEUP2		(0x0E)
+#define PM800_WAKEUP2_INV_INT		(1 << 0)
+#define PM800_WAKEUP2_INT_CLEAR		(1 << 1)
+#define PM800_WAKEUP2_INT_MASK		(1 << 2)
+
+#define PM800_POWER_UP_LOG	(0x10)
+
+/* Referance and low power registers */
+#define PM800_LOW_POWER1		(0x20)
+#define PM800_LOW_POWER2		(0x21)
+#define PM800_LOW_POWER_CONFIG3	(0x22)
+#define PM800_LOW_POWER_CONFIG4	(0x23)
+
+/* GPIO register */
+#define PM800_GPIO_0_1_CNTRL		(0x30)
+#define PM800_GPIO0_VAL				(1 << 0)
+#define PM800_GPIO0_GPIO_MODE(x)	(x << 1)
+#define PM800_GPIO1_VAL				(1 << 4)
+#define PM800_GPIO1_GPIO_MODE(x)	(x << 5)
+
+#define PM800_GPIO_2_3_CNTRL		(0x31)
+#define PM800_GPIO2_VAL				(1 << 0)
+#define PM800_GPIO2_GPIO_MODE(x)	(x << 1)
+#define PM800_GPIO3_VAL				(1 << 4)
+#define PM800_GPIO3_GPIO_MODE(x)	(x << 5)
+#define PM800_GPIO3_MODE_MASK		0x1F
+#define PM800_GPIO3_HEADSET_MODE	PM800_GPIO3_GPIO_MODE(6)
+
+#define PM800_GPIO_4_CNTRL			(0x32)
+#define PM800_GPIO4_VAL				(1 << 0)
+#define PM800_GPIO4_GPIO_MODE(x)	(x << 1)
+
+#define PM800_HEADSET_CNTRL		(0x38)
+#define PM800_HEADSET_DET_EN		(1 << 7)
+#define PM800_HSDET_SLP			(1 << 1)
+/* PWM register */
+#define PM800_PWM1		(0x40)
+#define PM800_PWM2		(0x41)
+#define PM800_PWM3		(0x42)
+#define PM800_PWM4		(0x43)
+
+/* RTC Registers */
+#define PM800_RTC_CONTROL		(0xD0)
+#define PM800_RTC_COUNTER1		(0xD1)
+#define PM800_RTC_COUNTER2		(0xD2)
+#define PM800_RTC_COUNTER3		(0xD3)
+#define PM800_RTC_COUNTER4		(0xD4)
+#define PM800_RTC_EXPIRE1_1		(0xD5)
+#define PM800_RTC_EXPIRE1_2		(0xD6)
+#define PM800_RTC_EXPIRE1_3		(0xD7)
+#define PM800_RTC_EXPIRE1_4		(0xD8)
+#define PM800_RTC_TRIM1			(0xD9)
+#define PM800_RTC_TRIM2			(0xDA)
+#define PM800_RTC_TRIM3			(0xDB)
+#define PM800_RTC_TRIM4			(0xDC)
+#define PM800_RTC_EXPIRE2_1		(0xDD)
+#define PM800_RTC_EXPIRE2_2		(0xDE)
+#define PM800_RTC_EXPIRE2_3		(0xDF)
+#define PM800_RTC_EXPIRE2_4		(0xE0)
+#define PM800_RTC_MISC1			(0xE1)
+#define PM800_RTC_MISC2			(0xE2)
+#define PM800_RTC_MISC3			(0xE3)
+#define PM800_RTC_MISC4			(0xE4)
+
+/* bit definitions of RTC Register 1 (0xD0) */
+#define PM800_ALARM1_EN			(1 << 0)
+#define PM800_ALARM_WAKEUP		(1 << 4)
+#define PM800_ALARM			(1 << 5)
+#define PM800_RTC1_USE_XO		(1 << 7)
+
+#define PM800_POWER_DOWN_LOG1	(0xE5)
+#define PM800_POWER_DOWN_LOG2	(0xE6)
+
+#define PM800_RTC_MISC5			(0xE7)
+
+/* Regulator Control Registers: BUCK1,BUCK5,LDO1 have DVC */
+
+/* LDO1 with DVC[0..3] */
+#define PM800_LDO1_VOUT		(0x08) /* VOUT1 */
+#define PM800_LDO1_VOUT_2	(0x09)
+#define PM800_LDO1_VOUT_3	(0x0A)
+#define PM800_LDO2_VOUT		(0x0B)
+#define PM800_LDO3_VOUT		(0x0C)
+#define PM800_LDO4_VOUT		(0x0D)
+#define PM800_LDO5_VOUT		(0x0E)
+#define PM800_LDO6_VOUT		(0x0F)
+#define PM800_LDO7_VOUT		(0x10)
+#define PM800_LDO8_VOUT		(0x11)
+#define PM800_LDO9_VOUT		(0x12)
+#define PM800_LDO10_VOUT	(0x13)
+#define PM800_LDO11_VOUT	(0x14)
+#define PM800_LDO12_VOUT	(0x15)
+#define PM800_LDO13_VOUT	(0x16)
+#define PM800_LDO14_VOUT	(0x17)
+#define PM800_LDO15_VOUT	(0x18)
+#define PM800_LDO16_VOUT	(0x19)
+#define PM800_LDO17_VOUT	(0x1A)
+#define PM800_LDO18_VOUT	(0x1B)
+#define PM800_LDO19_VOUT	(0x1C)
+
+/* buck registers */
+#define PM800_SLEEP_BUCK1	(0x30)
+
+/* BUCK1 with DVC[0..3] */
+#define PM800_BUCK1			(0x3C)
+#define PM800_BUCK1_1		(0x3D)
+#define PM800_BUCK1_2		(0x3E)
+#define PM800_BUCK1_3		(0x3F)
+
+#define PM800_BUCK_ENA		(0x50)
+#define PM800_LDO_ENA1_1	(0x51)
+#define PM800_LDO_ENA1_2	(0x52)
+#define PM800_LDO_ENA1_3	(0x53)
+
+/* BUCK Sleep Mode Register 1: BUCK[1..4] */
+#define PM800_BUCK_SLP1		(0x5A)
+#define PM800_BUCK1_SLP1_SHIFT	0
+#define PM800_BUCK1_SLP1_MASK	(0x3 << PM800_BUCK1_SLP1_SHIFT)
+
+/* page 2 GPADC: slave adder 0x02 */
+#define PM800_GPADC_MEAS_EN1		(0x01)
+#define PM800_MEAS_EN1_VBAT         (1 << 2)
+#define PM800_GPADC_MEAS_EN2		(0x02)
+#define PM800_MEAS_EN2_RFTMP        (1 << 0)
+#define PM800_MEAS_GP0_EN			(1 << 2)
+#define PM800_MEAS_GP1_EN			(1 << 3)
+#define PM800_MEAS_GP2_EN			(1 << 4)
+#define PM800_MEAS_GP3_EN			(1 << 5)
+#define PM800_MEAS_GP4_EN			(1 << 6)
+
+#define PM800_GPADC_MISC_CONFIG1	(0x05)
+#define PM800_GPADC_MISC_CONFIG2	(0x06)
+#define PM800_GPADC_MISC_GPFSM_EN	(1 << 0)
+#define PM800_GPADC_SLOW_MODE(x)	(x << 3)
+
+#define PM800_GPADC_MISC_CONFIG3		(0x09)
+#define PM800_GPADC_MISC_CONFIG4		(0x0A)
+
+#define PM800_GPADC_PREBIAS1			(0x0F)
+#define PM800_GPADC0_GP_PREBIAS_TIME(x)	(x << 0)
+#define PM800_GPADC_PREBIAS2			(0x10)
+
+#define PM800_GP_BIAS_ENA1				(0x14)
+#define PM800_GPADC_GP_BIAS_EN0			(1 << 0)
+#define PM800_GPADC_GP_BIAS_EN1			(1 << 1)
+#define PM800_GPADC_GP_BIAS_EN2			(1 << 2)
+#define PM800_GPADC_GP_BIAS_EN3			(1 << 3)
+
+#define PM800_GP_BIAS_OUT1		(0x15)
+#define PM800_BIAS_OUT_GP0		(1 << 0)
+#define PM800_BIAS_OUT_GP1		(1 << 1)
+#define PM800_BIAS_OUT_GP2		(1 << 2)
+#define PM800_BIAS_OUT_GP3		(1 << 3)
+
+#define PM800_GPADC0_LOW_TH		0x20
+#define PM800_GPADC1_LOW_TH		0x21
+#define PM800_GPADC2_LOW_TH		0x22
+#define PM800_GPADC3_LOW_TH		0x23
+#define PM800_GPADC4_LOW_TH		0x24
+
+#define PM800_GPADC0_UPP_TH		0x30
+#define PM800_GPADC1_UPP_TH		0x31
+#define PM800_GPADC2_UPP_TH		0x32
+#define PM800_GPADC3_UPP_TH		0x33
+#define PM800_GPADC4_UPP_TH		0x34
+
+#define PM800_VBBAT_MEAS1		0x40
+#define PM800_VBBAT_MEAS2		0x41
+#define PM800_VBAT_MEAS1		0x42
+#define PM800_VBAT_MEAS2		0x43
+#define PM800_VSYS_MEAS1		0x44
+#define PM800_VSYS_MEAS2		0x45
+#define PM800_VCHG_MEAS1		0x46
+#define PM800_VCHG_MEAS2		0x47
+#define PM800_TINT_MEAS1		0x50
+#define PM800_TINT_MEAS2		0x51
+#define PM800_PMOD_MEAS1		0x52
+#define PM800_PMOD_MEAS2		0x53
+
+#define PM800_GPADC0_MEAS1		0x54
+#define PM800_GPADC0_MEAS2		0x55
+#define PM800_GPADC1_MEAS1		0x56
+#define PM800_GPADC1_MEAS2		0x57
+#define PM800_GPADC2_MEAS1		0x58
+#define PM800_GPADC2_MEAS2		0x59
+#define PM800_GPADC3_MEAS1		0x5A
+#define PM800_GPADC3_MEAS2		0x5B
+#define PM800_GPADC4_MEAS1		0x5C
+#define PM800_GPADC4_MEAS2		0x5D
+
+#define PM800_GPADC4_AVG1		0xA8
+#define PM800_GPADC4_AVG2		0xA9
+
+/* 88PM805 Registers */
+#define PM805_MAIN_POWERUP		(0x01)
+#define PM805_INT_STATUS0		(0x02)	/* for ena/dis all interrupts */
+
+#define PM805_STATUS0_INT_CLEAR		(1 << 0)
+#define PM805_STATUS0_INV_INT		(1 << 1)
+#define PM800_STATUS0_INT_MASK		(1 << 2)
+
+#define PM805_INT_STATUS1		(0x03)
+
+#define PM805_INT1_HP1_SHRT		(1 << 0)
+#define PM805_INT1_HP2_SHRT		(1 << 1)
+#define PM805_INT1_MIC_CONFLICT		(1 << 2)
+#define PM805_INT1_CLIP_FAULT		(1 << 3)
+#define PM805_INT1_LDO_OFF			(1 << 4)
+#define PM805_INT1_SRC_DPLL_LOCK	(1 << 5)
+
+#define PM805_INT_STATUS2		(0x04)
+
+#define PM805_INT2_MIC_DET			(1 << 0)
+#define PM805_INT2_SHRT_BTN_DET		(1 << 1)
+#define PM805_INT2_VOLM_BTN_DET		(1 << 2)
+#define PM805_INT2_VOLP_BTN_DET		(1 << 3)
+#define PM805_INT2_RAW_PLL_FAULT	(1 << 4)
+#define PM805_INT2_FINE_PLL_FAULT	(1 << 5)
+
+#define PM805_INT_MASK1			(0x05)
+#define PM805_INT_MASK2			(0x06)
+#define PM805_SHRT_BTN_DET		(1 << 1)
+
+/* number of status and int reg in a row */
+#define PM805_INT_REG_NUM		(2)
+
+#define PM805_MIC_DET1			(0x07)
+#define PM805_MIC_DET_EN_MIC_DET (1 << 0)
+#define PM805_MIC_DET2			(0x08)
+#define PM805_MIC_DET_STATUS1	(0x09)
+
+#define PM805_MIC_DET_STATUS3	(0x0A)
+#define PM805_AUTO_SEQ_STATUS1	(0x0B)
+#define PM805_AUTO_SEQ_STATUS2	(0x0C)
+
+#define PM805_ADC_SETTING1		(0x10)
+#define PM805_ADC_SETTING2		(0x11)
+#define PM805_ADC_SETTING3		(0x11)
+#define PM805_ADC_GAIN1			(0x12)
+#define PM805_ADC_GAIN2			(0x13)
+#define PM805_DMIC_SETTING		(0x15)
+#define PM805_DWS_SETTING		(0x16)
+#define PM805_MIC_CONFLICT_STS	(0x17)
+
+#define PM805_PDM_SETTING1		(0x20)
+#define PM805_PDM_SETTING2		(0x21)
+#define PM805_PDM_SETTING3		(0x22)
+#define PM805_PDM_CONTROL1		(0x23)
+#define PM805_PDM_CONTROL2		(0x24)
+#define PM805_PDM_CONTROL3		(0x25)
+
+#define PM805_HEADPHONE_SETTING			(0x26)
+#define PM805_HEADPHONE_GAIN_A2A		(0x27)
+#define PM805_HEADPHONE_SHORT_STATE		(0x28)
+#define PM805_EARPHONE_SETTING			(0x29)
+#define PM805_AUTO_SEQ_SETTING			(0x2A)
+
+#define get_pmic_version(chip) (*(unsigned char *) chip)
+
+/* Interrupt Number in 88PM800 */
+enum {
+	PM800_IRQ_ONKEY,	/*EN1b0 *//*0 */
+	PM800_IRQ_EXTON,	/*EN1b1 */
+	PM800_IRQ_CHG,		/*EN1b2 */
+	PM800_IRQ_BAT,		/*EN1b3 */
+	PM800_IRQ_RTC,		/*EN1b4 */
+	PM800_IRQ_CLASSD,	/*EN1b5 *//*5 */
+	PM800_IRQ_VBAT,		/*EN2b0 */
+	PM800_IRQ_VSYS,		/*EN2b1 */
+	PM800_IRQ_VCHG,		/*EN2b2 */
+	PM800_IRQ_TINT,		/*EN2b3 */
+	PM800_IRQ_GPADC0,	/*EN3b0 *//*10 */
+	PM800_IRQ_GPADC1,	/*EN3b1 */
+	PM800_IRQ_GPADC2,	/*EN3b2 */
+	PM800_IRQ_GPADC3,	/*EN3b3 */
+	PM800_IRQ_GPADC4,	/*EN3b4 */
+	PM800_IRQ_GPIO0,	/*EN4b0 *//*15 */
+	PM800_IRQ_GPIO1,	/*EN4b1 */
+	PM800_IRQ_GPIO2,	/*EN4b2 */
+	PM800_IRQ_GPIO3,	/*EN4b3 */
+	PM800_IRQ_GPIO4,	/*EN4b4 *//*19 */
+	PM800_MAX_IRQ,
+};
+
+/* Interrupt Number in 88PM805 */
+enum {
+	PM805_IRQ_LDO_OFF,	/*0 */
+	PM805_IRQ_SRC_DPLL_LOCK,	/*1 */
+	PM805_IRQ_CLIP_FAULT,
+	PM805_IRQ_MIC_CONFLICT,
+	PM805_IRQ_HP2_SHRT,
+	PM805_IRQ_HP1_SHRT,	/*5 */
+	PM805_IRQ_FINE_PLL_FAULT,
+	PM805_IRQ_RAW_PLL_FAULT,
+	PM805_IRQ_VOLP_BTN_DET,
+	PM805_IRQ_VOLM_BTN_DET,
+	PM805_IRQ_SHRT_BTN_DET,	/*10 */
+	PM805_IRQ_MIC_DET,	/*11 */
+
+	PM805_MAX_IRQ,
+};
+
+struct pm80x_subchip {
+	struct i2c_client *power_page;	/* chip client for power page */
+	struct i2c_client *gpadc_page;	/* chip client for gpadc page */
+	struct regmap *regmap_power;
+	struct regmap *regmap_gpadc;
+	unsigned short power_page_addr;	/* power page I2C address */
+	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
+};
+
+struct pm80x_chip {
+	struct pm80x_subchip *subchip;
+	struct device *dev;
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct regmap_irq_chip *regmap_irq_chip;
+	struct regmap_irq_chip_data *irq_data;
+	unsigned char version;
+	int id;
+	int irq;
+	int irq_mode;
+	int irq_base;
+	unsigned int wu_flag;
+};
+
+struct pm80x_rtc_pdata {
+	int		vrtc;
+	int		rtc_wakeup;
+};
+
+struct pm80x_platform_data {
+	struct pm80x_rtc_pdata *rtc;
+	unsigned short pm800_addr;
+	unsigned short pm805_addr;
+	unsigned short power_page_addr;	/* power page I2C address */
+	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
+	int irq_mode;		/* Clear interrupt by read/write(0/1) */
+	int irq_base;		/* IRQ base of chip */
+	int batt_det;		/* enable/disable */
+};
+
+extern const struct dev_pm_ops pm80x_pm_ops;
+extern const struct regmap_config pm80x_regmap_config;
+extern const struct i2c_device_id pm80x_id_table[2];
+extern int pm80x_reg_read(struct regmap *, u32);
+extern int pm80x_bulk_read(struct regmap *, u32, u8 *, int);
+extern int pm80x_reg_write(struct regmap *, u32, u32);
+extern int pm80x_bulk_write(struct regmap *, u32, const u8 *, int);
+extern int pm80x_set_bits(struct regmap *, u32, u32, u32);
+
+extern int pm80x_init(struct i2c_client *client,
+			     const struct i2c_device_id *id) __devinit;
+extern int pm80x_deinit(struct i2c_client *client) __devexit;
+#endif /* __LINUX_MFD_88PM80X_H */