2011-03-04 06:50:30

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH v2 0/2] MAX8997/8966 MFD (includig PMIC) Initial Release

MAX8997/8966 has
- PMIC
- RTC
- MUIC (usb switch)
- Flash control
- Haptic control
- Fuel Gauge (MAX17042 compatible)
- Battery charger control

This patch adds an initial driver for Maxim Semiconductor 8997/8966's
PMIC function.

In this initial release, PMIC (as set of regulators) is included.

The changes from previous patch
v2
with comments from Samuel:
- Style updated for MFD
with comments from Mark:
- Updated API for next and 2.6.38
- Style updated for regulators
Support for hibernation
Support for bulk register access
Corrected register names
Added RTC/IRQ registers
LDO access bug fixed
Support for regulator suspend state control

MyungJoo Ham (2):
MAX8997/8966 MFD Driver Initial Release (PMIC+RTC+MUIC+Haptic+...)
MAX8997/8966 PMIC Regulator Driver Initial Release

drivers/mfd/Kconfig | 12 +
drivers/mfd/Makefile | 1 +
drivers/mfd/max8997.c | 427 ++++++++++++
drivers/regulator/Kconfig | 9 +
drivers/regulator/Makefile | 1 +
drivers/regulator/max8997.c | 1238 +++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997-private.h | 347 ++++++++++
include/linux/mfd/max8997.h | 114 ++++
8 files changed, 2149 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/max8997.c
create mode 100644 drivers/regulator/max8997.c
create mode 100644 include/linux/mfd/max8997-private.h
create mode 100644 include/linux/mfd/max8997.h


2011-03-04 06:50:33

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH v2 1/2] MAX8997/8966 MFD Driver Initial Release (PMIC+RTC+MUIC+Haptic+...)

MAX8997/MAX8966 chip is a multi-function device with I2C bussses. The
chip includes PMIC, RTC, Fuel Gauge, MUIC, Haptic, Flash control, and
Battery (charging) control.

This patch is an initial release of a MAX8997/8966 driver that supports
to enable the chip with its primary I2C bus that connects every device
mentioned above except for Fuel Gauge, which uses another I2C bus. The
fuel gauge is not supported by this mfd driver and is supported by a
seperated driver of MAX17042 Fuel Gauge (yes, the fuel gauge part is
compatible with MAX17042).

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/mfd/Kconfig | 12 +
drivers/mfd/Makefile | 1 +
drivers/mfd/max8997.c | 427 +++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997-private.h | 347 ++++++++++++++++++++++++++++
include/linux/mfd/max8997.h | 88 +++++++
5 files changed, 875 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/max8997.c
create mode 100644 include/linux/mfd/max8997-private.h
create mode 100644 include/linux/mfd/max8997.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index fd01836..c682ad1 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -293,6 +293,18 @@ config MFD_MAX8925
accessing the device, additional drivers must be enabled in order
to use the functionality of the device.

+config MFD_MAX8997
+ bool "Maxim Semiconductor MAX8997/8966 PMIC Support"
+ depends on I2C=y && GENERIC_HARDIRQS
+ select MFD_CORE
+ help
+ Say yes here to support for Maxim Semiconductor MAX8998/8966.
+ This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic,
+ MUIC controls on chip.
+ This driver provies common support for accessing the device,
+ additional drivers must be enabled in order to use the functionality
+ of the device.
+
config MFD_MAX8998
bool "Maxim Semiconductor MAX8998/National LP3974 PMIC Support"
depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a54e2c7..f6662e3 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
obj-$(CONFIG_PMIC_DA903X) += da903x.o
max8925-objs := max8925-core.o max8925-i2c.o
obj-$(CONFIG_MFD_MAX8925) += max8925.o
+obj-$(CONFIG_MFD_MAX8997) += max8997.o
obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o

pcf50633-objs := pcf50633-core.o pcf50633-irq.o
diff --git a/drivers/mfd/max8997.c b/drivers/mfd/max8997.c
new file mode 100644
index 0000000..5d1fca0
--- /dev/null
+++ b/drivers/mfd/max8997.c
@@ -0,0 +1,427 @@
+/*
+ * max8997.c - mfd core driver for the Maxim 8966 and 8997
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8998.c
+ */
+
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/pm_runtime.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+#define I2C_ADDR_PMIC (0xCC >> 1)
+#define I2C_ADDR_MUIC (0x4A >> 1)
+#define I2C_ADDR_BATTERY (0x6C >> 1)
+#define I2C_ADDR_RTC (0x0C >> 1)
+#define I2C_ADDR_HAPTIC (0x90 >> 1)
+
+static struct mfd_cell max8997_devs[] = {
+ { .name = "max8997-pmic", },
+ { .name = "max8997-rtc", },
+ { .name = "max8997-battery", },
+ { .name = "max8997-haptic", },
+ { .name = "max8997-muic", },
+ { .name = "max8997-flash", },
+};
+
+int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
+{
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8997->iolock);
+ ret = i2c_smbus_read_byte_data(i2c, reg);
+ mutex_unlock(&max8997->iolock);
+ if (ret < 0)
+ return ret;
+
+ ret &= 0xff;
+ *dest = ret;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max8997_read_reg);
+
+int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8997->iolock);
+ ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf);
+ mutex_unlock(&max8997->iolock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max8997_bulk_read);
+
+int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
+{
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8997->iolock);
+ ret = i2c_smbus_write_byte_data(i2c, reg, value);
+ mutex_unlock(&max8997->iolock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(max8997_write_reg);
+
+int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
+{
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8997->iolock);
+ ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf);
+ mutex_unlock(&max8997->iolock);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max8997_bulk_write);
+
+int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
+{
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&max8997->iolock);
+ ret = i2c_smbus_read_byte_data(i2c, reg);
+ if (ret >= 0) {
+ u8 old_val = ret & 0xff;
+ u8 new_val = (val & mask) | (old_val & (~mask));
+ ret = i2c_smbus_write_byte_data(i2c, reg, new_val);
+ }
+ mutex_unlock(&max8997->iolock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(max8997_update_reg);
+
+static int max8997_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max8997_dev *max8997;
+ struct max8997_platform_data *pdata = i2c->dev.platform_data;
+ int ret = 0;
+
+ max8997 = kzalloc(sizeof(struct max8997_dev), GFP_KERNEL);
+ if (max8997 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, max8997);
+ max8997->dev = &i2c->dev;
+ max8997->i2c = i2c;
+ max8997->type = id->driver_data;
+
+ if (!pdata)
+ goto err;
+
+ max8997->wakeup = pdata->wakeup;
+
+ mutex_init(&max8997->iolock);
+
+ max8997->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC);
+ i2c_set_clientdata(max8997->rtc, max8997);
+ max8997->haptic = i2c_new_dummy(i2c->adapter, I2C_ADDR_HAPTIC);
+ i2c_set_clientdata(max8997->haptic, max8997);
+ max8997->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC);
+ i2c_set_clientdata(max8997->muic, max8997);
+
+ pm_runtime_set_active(max8997->dev);
+
+ mfd_add_devices(max8997->dev, -1, max8997_devs,
+ ARRAY_SIZE(max8997_devs),
+ NULL, 0);
+
+ /*
+ * TODO: enable others (flash, muic, rtc, battery, ...) and
+ * check the return value
+ */
+
+ if (ret < 0)
+ goto err_mfd;
+
+ return ret;
+
+err_mfd:
+ mfd_remove_devices(max8997->dev);
+ i2c_unregister_device(max8997->muic);
+ i2c_unregister_device(max8997->haptic);
+ i2c_unregister_device(max8997->rtc);
+err:
+ kfree(max8997);
+ return ret;
+}
+
+static int max8997_i2c_remove(struct i2c_client *i2c)
+{
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+
+ mfd_remove_devices(max8997->dev);
+ i2c_unregister_device(max8997->muic);
+ i2c_unregister_device(max8997->haptic);
+ i2c_unregister_device(max8997->rtc);
+ kfree(max8997);
+
+ return 0;
+}
+
+static const struct i2c_device_id max8997_i2c_id[] = {
+ { "max8997", TYPE_MAX8997 },
+ { "max8966", TYPE_MAX8966 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max8998_i2c_id);
+
+u8 max8997_dumpaddr_pmic[] = {
+ MAX8997_REG_INT1MSK,
+ MAX8997_REG_INT2MSK,
+ MAX8997_REG_INT3MSK,
+ MAX8997_REG_INT4MSK,
+ MAX8997_REG_MAINCON1,
+ MAX8997_REG_MAINCON2,
+ MAX8997_REG_BUCKRAMP,
+ MAX8997_REG_BUCK1CTRL,
+ MAX8997_REG_BUCK1DVS1,
+ MAX8997_REG_BUCK1DVS2,
+ MAX8997_REG_BUCK1DVS3,
+ MAX8997_REG_BUCK1DVS4,
+ MAX8997_REG_BUCK1DVS5,
+ MAX8997_REG_BUCK1DVS6,
+ MAX8997_REG_BUCK1DVS7,
+ MAX8997_REG_BUCK1DVS8,
+ MAX8997_REG_BUCK2CTRL,
+ MAX8997_REG_BUCK2DVS1,
+ MAX8997_REG_BUCK2DVS2,
+ MAX8997_REG_BUCK2DVS3,
+ MAX8997_REG_BUCK2DVS4,
+ MAX8997_REG_BUCK2DVS5,
+ MAX8997_REG_BUCK2DVS6,
+ MAX8997_REG_BUCK2DVS7,
+ MAX8997_REG_BUCK2DVS8,
+ MAX8997_REG_BUCK3CTRL,
+ MAX8997_REG_BUCK3DVS,
+ MAX8997_REG_BUCK4CTRL,
+ MAX8997_REG_BUCK4DVS,
+ MAX8997_REG_BUCK5CTRL,
+ MAX8997_REG_BUCK5DVS1,
+ MAX8997_REG_BUCK5DVS2,
+ MAX8997_REG_BUCK5DVS3,
+ MAX8997_REG_BUCK5DVS4,
+ MAX8997_REG_BUCK5DVS5,
+ MAX8997_REG_BUCK5DVS6,
+ MAX8997_REG_BUCK5DVS7,
+ MAX8997_REG_BUCK5DVS8,
+ MAX8997_REG_BUCK6CTRL,
+ MAX8997_REG_BUCK6BPSKIPCTRL,
+ MAX8997_REG_BUCK7CTRL,
+ MAX8997_REG_BUCK7DVS,
+ MAX8997_REG_LDO1CTRL,
+ MAX8997_REG_LDO2CTRL,
+ MAX8997_REG_LDO3CTRL,
+ MAX8997_REG_LDO4CTRL,
+ MAX8997_REG_LDO5CTRL,
+ MAX8997_REG_LDO6CTRL,
+ MAX8997_REG_LDO7CTRL,
+ MAX8997_REG_LDO8CTRL,
+ MAX8997_REG_LDO9CTRL,
+ MAX8997_REG_LDO10CTRL,
+ MAX8997_REG_LDO11CTRL,
+ MAX8997_REG_LDO12CTRL,
+ MAX8997_REG_LDO13CTRL,
+ MAX8997_REG_LDO14CTRL,
+ MAX8997_REG_LDO15CTRL,
+ MAX8997_REG_LDO16CTRL,
+ MAX8997_REG_LDO17CTRL,
+ MAX8997_REG_LDO18CTRL,
+ MAX8997_REG_LDO21CTRL,
+ MAX8997_REG_MBCCTRL1,
+ MAX8997_REG_MBCCTRL2,
+ MAX8997_REG_MBCCTRL3,
+ MAX8997_REG_MBCCTRL4,
+ MAX8997_REG_MBCCTRL5,
+ MAX8997_REG_MBCCTRL6,
+ MAX8997_REG_OTPCGHCVS,
+ MAX8997_REG_SAFEOUTCTRL,
+ MAX8997_REG_LBCNFG1,
+ MAX8997_REG_LBCNFG2,
+ MAX8997_REG_BBCCTRL,
+
+ MAX8997_REG_FLASH1_CUR,
+ MAX8997_REG_FLASH2_CUR,
+ MAX8997_REG_MOVIE_CUR,
+ MAX8997_REG_GSMB_CUR,
+ MAX8997_REG_BOOST_CNTL,
+ MAX8997_REG_LEN_CNTL,
+ MAX8997_REG_FLASH_CNTL,
+ MAX8997_REG_WDT_CNTL,
+ MAX8997_REG_MAXFLASH1,
+ MAX8997_REG_MAXFLASH2,
+ MAX8997_REG_FLASHSTATUSMASK,
+
+ MAX8997_REG_GPIOCNTL1,
+ MAX8997_REG_GPIOCNTL2,
+ MAX8997_REG_GPIOCNTL3,
+ MAX8997_REG_GPIOCNTL4,
+ MAX8997_REG_GPIOCNTL5,
+ MAX8997_REG_GPIOCNTL6,
+ MAX8997_REG_GPIOCNTL7,
+ MAX8997_REG_GPIOCNTL8,
+ MAX8997_REG_GPIOCNTL9,
+ MAX8997_REG_GPIOCNTL10,
+ MAX8997_REG_GPIOCNTL11,
+ MAX8997_REG_GPIOCNTL12,
+
+ MAX8997_REG_LDO1CONFIG,
+ MAX8997_REG_LDO2CONFIG,
+ MAX8997_REG_LDO3CONFIG,
+ MAX8997_REG_LDO4CONFIG,
+ MAX8997_REG_LDO5CONFIG,
+ MAX8997_REG_LDO6CONFIG,
+ MAX8997_REG_LDO7CONFIG,
+ MAX8997_REG_LDO8CONFIG,
+ MAX8997_REG_LDO9CONFIG,
+ MAX8997_REG_LDO10CONFIG,
+ MAX8997_REG_LDO11CONFIG,
+ MAX8997_REG_LDO12CONFIG,
+ MAX8997_REG_LDO13CONFIG,
+ MAX8997_REG_LDO14CONFIG,
+ MAX8997_REG_LDO15CONFIG,
+ MAX8997_REG_LDO16CONFIG,
+ MAX8997_REG_LDO17CONFIG,
+ MAX8997_REG_LDO18CONFIG,
+ MAX8997_REG_LDO21CONFIG,
+
+ MAX8997_REG_DVSOKTIMER1,
+ MAX8997_REG_DVSOKTIMER2,
+ MAX8997_REG_DVSOKTIMER4,
+ MAX8997_REG_DVSOKTIMER5,
+};
+
+u8 max8997_dumpaddr_muic[] = {
+ MAX8997_MUIC_REG_INTMASK1,
+ MAX8997_MUIC_REG_INTMASK2,
+ MAX8997_MUIC_REG_INTMASK3,
+ MAX8997_MUIC_REG_CDETCTRL,
+ MAX8997_MUIC_REG_CONTROL1,
+ MAX8997_MUIC_REG_CONTROL2,
+ MAX8997_MUIC_REG_CONTROL3,
+};
+
+u8 max8997_dumpaddr_haptic[] = {
+ MAX8997_HAPTIC_REG_CONF1,
+ MAX8997_HAPTIC_REG_CONF2,
+ MAX8997_HAPTIC_REG_DRVCONF,
+ MAX8997_HAPTIC_REG_CYCLECONF1,
+ MAX8997_HAPTIC_REG_CYCLECONF2,
+ MAX8997_HAPTIC_REG_SIGCONF1,
+ MAX8997_HAPTIC_REG_SIGCONF2,
+ MAX8997_HAPTIC_REG_SIGCONF3,
+ MAX8997_HAPTIC_REG_SIGCONF4,
+ MAX8997_HAPTIC_REG_SIGDC1,
+ MAX8997_HAPTIC_REG_SIGDC2,
+ MAX8997_HAPTIC_REG_SIGPWMDC1,
+ MAX8997_HAPTIC_REG_SIGPWMDC2,
+ MAX8997_HAPTIC_REG_SIGPWMDC3,
+ MAX8997_HAPTIC_REG_SIGPWMDC4,
+};
+
+static int max8997_freeze(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++)
+ max8997_read_reg(i2c, max8997_dumpaddr_pmic[i],
+ &max8997->reg_dump[i]);
+
+ for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++)
+ max8997_read_reg(i2c, max8997_dumpaddr_muic[i],
+ &max8997->reg_dump[i + MAX8997_REG_PMIC_END]);
+
+ for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++)
+ max8997_read_reg(i2c, max8997_dumpaddr_haptic[i],
+ &max8997->reg_dump[i + MAX8997_REG_PMIC_END +
+ MAX8997_MUIC_REG_END]);
+
+ return 0;
+}
+
+static int max8997_restore(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++)
+ max8997_write_reg(i2c, max8997_dumpaddr_pmic[i],
+ max8997->reg_dump[i]);
+
+ for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++)
+ max8997_write_reg(i2c, max8997_dumpaddr_muic[i],
+ max8997->reg_dump[i + MAX8997_REG_PMIC_END]);
+
+ for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++)
+ max8997_write_reg(i2c, max8997_dumpaddr_haptic[i],
+ max8997->reg_dump[i + MAX8997_REG_PMIC_END +
+ MAX8997_MUIC_REG_END]);
+
+ return 0;
+}
+
+const struct dev_pm_ops max8997_pm = {
+ .freeze = max8997_freeze,
+ .restore = max8997_restore,
+};
+
+static struct i2c_driver max8997_i2c_driver = {
+ .driver = {
+ .name = "max8997",
+ .owner = THIS_MODULE,
+ .pm = &max8997_pm,
+ },
+ .probe = max8997_i2c_probe,
+ .remove = max8997_i2c_remove,
+ .id_table = max8997_i2c_id,
+};
+
+static int __init max8997_i2c_init(void)
+{
+ return i2c_add_driver(&max8997_i2c_driver);
+}
+/* init early so consumer devices can complete system boot */
+subsys_initcall(max8997_i2c_init);
+
+static void __exit max8997_i2c_exit(void)
+{
+ i2c_del_driver(&max8997_i2c_driver);
+}
+module_exit(max8997_i2c_exit);
+
+MODULE_DESCRIPTION("MAXIM 8997 multi-function core driver");
+MODULE_AUTHOR("MyungJoo Ham <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h
new file mode 100644
index 0000000..93a9477
--- /dev/null
+++ b/include/linux/mfd/max8997-private.h
@@ -0,0 +1,347 @@
+/*
+ * max8997.h - Voltage regulator driver for the Maxim 8997
+ *
+ * Copyright (C) 2010 Samsung Electrnoics
+ * MyungJoo Ham <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ */
+
+#ifndef __LINUX_MFD_MAX8997_PRIV_H
+#define __LINUX_MFD_MAX8997_PRIV_H
+
+#include <linux/i2c.h>
+
+enum max8997_pmic_reg {
+ MAX8997_REG_PMIC_ID0 = 0x00,
+ MAX8997_REG_PMIC_ID1 = 0x01,
+ MAX8997_REG_INTSRC = 0x02,
+ MAX8997_REG_INT1 = 0x03,
+ MAX8997_REG_INT2 = 0x04,
+ MAX8997_REG_INT3 = 0x05,
+ MAX8997_REG_INT4 = 0x06,
+
+ MAX8997_REG_INT1MSK = 0x08,
+ MAX8997_REG_INT2MSK = 0x09,
+ MAX8997_REG_INT3MSK = 0x0a,
+ MAX8997_REG_INT4MSK = 0x0b,
+
+ MAX8997_REG_STATUS1 = 0x0d,
+ MAX8997_REG_STATUS2 = 0x0e,
+ MAX8997_REG_STATUS3 = 0x0f,
+ MAX8997_REG_STATUS4 = 0x10,
+
+ MAX8997_REG_MAINCON1 = 0x13,
+ MAX8997_REG_MAINCON2 = 0x14,
+ MAX8997_REG_BUCKRAMP = 0x15,
+
+ MAX8997_REG_BUCK1CTRL = 0x18,
+ MAX8997_REG_BUCK1DVS1 = 0x19,
+ MAX8997_REG_BUCK1DVS2 = 0x1a,
+ MAX8997_REG_BUCK1DVS3 = 0x1b,
+ MAX8997_REG_BUCK1DVS4 = 0x1c,
+ MAX8997_REG_BUCK1DVS5 = 0x1d,
+ MAX8997_REG_BUCK1DVS6 = 0x1e,
+ MAX8997_REG_BUCK1DVS7 = 0x1f,
+ MAX8997_REG_BUCK1DVS8 = 0x20,
+ MAX8997_REG_BUCK2CTRL = 0x21,
+ MAX8997_REG_BUCK2DVS1 = 0x22,
+ MAX8997_REG_BUCK2DVS2 = 0x23,
+ MAX8997_REG_BUCK2DVS3 = 0x24,
+ MAX8997_REG_BUCK2DVS4 = 0x25,
+ MAX8997_REG_BUCK2DVS5 = 0x26,
+ MAX8997_REG_BUCK2DVS6 = 0x27,
+ MAX8997_REG_BUCK2DVS7 = 0x28,
+ MAX8997_REG_BUCK2DVS8 = 0x29,
+ MAX8997_REG_BUCK3CTRL = 0x2a,
+ MAX8997_REG_BUCK3DVS = 0x2b,
+ MAX8997_REG_BUCK4CTRL = 0x2c,
+ MAX8997_REG_BUCK4DVS = 0x2d,
+ MAX8997_REG_BUCK5CTRL = 0x2e,
+ MAX8997_REG_BUCK5DVS1 = 0x2f,
+ MAX8997_REG_BUCK5DVS2 = 0x30,
+ MAX8997_REG_BUCK5DVS3 = 0x31,
+ MAX8997_REG_BUCK5DVS4 = 0x32,
+ MAX8997_REG_BUCK5DVS5 = 0x33,
+ MAX8997_REG_BUCK5DVS6 = 0x34,
+ MAX8997_REG_BUCK5DVS7 = 0x35,
+ MAX8997_REG_BUCK5DVS8 = 0x36,
+ MAX8997_REG_BUCK6CTRL = 0x37,
+ MAX8997_REG_BUCK6BPSKIPCTRL = 0x38,
+ MAX8997_REG_BUCK7CTRL = 0x39,
+ MAX8997_REG_BUCK7DVS = 0x3a,
+ MAX8997_REG_LDO1CTRL = 0x3b,
+ MAX8997_REG_LDO2CTRL = 0x3c,
+ MAX8997_REG_LDO3CTRL = 0x3d,
+ MAX8997_REG_LDO4CTRL = 0x3e,
+ MAX8997_REG_LDO5CTRL = 0x3f,
+ MAX8997_REG_LDO6CTRL = 0x40,
+ MAX8997_REG_LDO7CTRL = 0x41,
+ MAX8997_REG_LDO8CTRL = 0x42,
+ MAX8997_REG_LDO9CTRL = 0x43,
+ MAX8997_REG_LDO10CTRL = 0x44,
+ MAX8997_REG_LDO11CTRL = 0x45,
+ MAX8997_REG_LDO12CTRL = 0x46,
+ MAX8997_REG_LDO13CTRL = 0x47,
+ MAX8997_REG_LDO14CTRL = 0x48,
+ MAX8997_REG_LDO15CTRL = 0x49,
+ MAX8997_REG_LDO16CTRL = 0x4a,
+ MAX8997_REG_LDO17CTRL = 0x4b,
+ MAX8997_REG_LDO18CTRL = 0x4c,
+ MAX8997_REG_LDO21CTRL = 0x4d,
+
+ MAX8997_REG_MBCCTRL1 = 0x50,
+ MAX8997_REG_MBCCTRL2 = 0x51,
+ MAX8997_REG_MBCCTRL3 = 0x52,
+ MAX8997_REG_MBCCTRL4 = 0x53,
+ MAX8997_REG_MBCCTRL5 = 0x54,
+ MAX8997_REG_MBCCTRL6 = 0x55,
+ MAX8997_REG_OTPCGHCVS = 0x56,
+
+ MAX8997_REG_SAFEOUTCTRL = 0x5a,
+
+ MAX8997_REG_LBCNFG1 = 0x5e,
+ MAX8997_REG_LBCNFG2 = 0x5f,
+ MAX8997_REG_BBCCTRL = 0x60,
+
+ MAX8997_REG_FLASH1_CUR = 0x63, /* 0x63 ~ 0x6e for FLASH */
+ MAX8997_REG_FLASH2_CUR = 0x64,
+ MAX8997_REG_MOVIE_CUR = 0x65,
+ MAX8997_REG_GSMB_CUR = 0x66,
+ MAX8997_REG_BOOST_CNTL = 0x67,
+ MAX8997_REG_LEN_CNTL = 0x68,
+ MAX8997_REG_FLASH_CNTL = 0x69,
+ MAX8997_REG_WDT_CNTL = 0x6a,
+ MAX8997_REG_MAXFLASH1 = 0x6b,
+ MAX8997_REG_MAXFLASH2 = 0x6c,
+ MAX8997_REG_FLASHSTATUS = 0x6d,
+ MAX8997_REG_FLASHSTATUSMASK = 0x6e,
+
+ MAX8997_REG_GPIOCNTL1 = 0x70,
+ MAX8997_REG_GPIOCNTL2 = 0x71,
+ MAX8997_REG_GPIOCNTL3 = 0x72,
+ MAX8997_REG_GPIOCNTL4 = 0x73,
+ MAX8997_REG_GPIOCNTL5 = 0x74,
+ MAX8997_REG_GPIOCNTL6 = 0x75,
+ MAX8997_REG_GPIOCNTL7 = 0x76,
+ MAX8997_REG_GPIOCNTL8 = 0x77,
+ MAX8997_REG_GPIOCNTL9 = 0x78,
+ MAX8997_REG_GPIOCNTL10 = 0x79,
+ MAX8997_REG_GPIOCNTL11 = 0x7a,
+ MAX8997_REG_GPIOCNTL12 = 0x7b,
+
+ MAX8997_REG_LDO1CONFIG = 0x80,
+ MAX8997_REG_LDO2CONFIG = 0x81,
+ MAX8997_REG_LDO3CONFIG = 0x82,
+ MAX8997_REG_LDO4CONFIG = 0x83,
+ MAX8997_REG_LDO5CONFIG = 0x84,
+ MAX8997_REG_LDO6CONFIG = 0x85,
+ MAX8997_REG_LDO7CONFIG = 0x86,
+ MAX8997_REG_LDO8CONFIG = 0x87,
+ MAX8997_REG_LDO9CONFIG = 0x88,
+ MAX8997_REG_LDO10CONFIG = 0x89,
+ MAX8997_REG_LDO11CONFIG = 0x8a,
+ MAX8997_REG_LDO12CONFIG = 0x8b,
+ MAX8997_REG_LDO13CONFIG = 0x8c,
+ MAX8997_REG_LDO14CONFIG = 0x8d,
+ MAX8997_REG_LDO15CONFIG = 0x8e,
+ MAX8997_REG_LDO16CONFIG = 0x8f,
+ MAX8997_REG_LDO17CONFIG = 0x90,
+ MAX8997_REG_LDO18CONFIG = 0x91,
+ MAX8997_REG_LDO21CONFIG = 0x92,
+
+ MAX8997_REG_DVSOKTIMER1 = 0x97,
+ MAX8997_REG_DVSOKTIMER2 = 0x98,
+ MAX8997_REG_DVSOKTIMER4 = 0x99,
+ MAX8997_REG_DVSOKTIMER5 = 0x9a,
+
+ MAX8997_REG_PMIC_END = 0x9b,
+};
+
+enum max8997_muic_reg {
+ MAX8997_MUIC_REG_ID = 0x0,
+ MAX8997_MUIC_REG_INT1 = 0x1,
+ MAX8997_MUIC_REG_INT2 = 0x2,
+ MAX8997_MUIC_REG_INT3 = 0x3,
+ MAX8997_MUIC_REG_STATUS1 = 0x4,
+ MAX8997_MUIC_REG_STATUS2 = 0x5,
+ MAX8997_MUIC_REG_STATUS3 = 0x6,
+ MAX8997_MUIC_REG_INTMASK1 = 0x7,
+ MAX8997_MUIC_REG_INTMASK2 = 0x8,
+ MAX8997_MUIC_REG_INTMASK3 = 0x9,
+ MAX8997_MUIC_REG_CDETCTRL = 0xa,
+
+ MAX8997_MUIC_REG_CONTROL1 = 0xc,
+ MAX8997_MUIC_REG_CONTROL2 = 0xd,
+ MAX8997_MUIC_REG_CONTROL3 = 0xe,
+
+ MAX8997_MUIC_REG_END = 0xf,
+};
+
+enum max8997_haptic_reg {
+ MAX8997_HAPTIC_REG_GENERAL = 0x00,
+ MAX8997_HAPTIC_REG_CONF1 = 0x01,
+ MAX8997_HAPTIC_REG_CONF2 = 0x02,
+ MAX8997_HAPTIC_REG_DRVCONF = 0x03,
+ MAX8997_HAPTIC_REG_CYCLECONF1 = 0x04,
+ MAX8997_HAPTIC_REG_CYCLECONF2 = 0x05,
+ MAX8997_HAPTIC_REG_SIGCONF1 = 0x06,
+ MAX8997_HAPTIC_REG_SIGCONF2 = 0x07,
+ MAX8997_HAPTIC_REG_SIGCONF3 = 0x08,
+ MAX8997_HAPTIC_REG_SIGCONF4 = 0x09,
+ MAX8997_HAPTIC_REG_SIGDC1 = 0x0a,
+ MAX8997_HAPTIC_REG_SIGDC2 = 0x0b,
+ MAX8997_HAPTIC_REG_SIGPWMDC1 = 0x0c,
+ MAX8997_HAPTIC_REG_SIGPWMDC2 = 0x0d,
+ MAX8997_HAPTIC_REG_SIGPWMDC3 = 0x0e,
+ MAX8997_HAPTIC_REG_SIGPWMDC4 = 0x0f,
+ MAX8997_HAPTIC_REG_MTR_REV = 0x10,
+
+ MAX8997_HAPTIC_REG_END = 0x11,
+};
+
+/* slave addr = 0x0c: using "2nd part" of rev4 datasheet */
+enum max8997_rtc_reg {
+ MAX8997_RTC_CTRLMASK = 0x02,
+ MAX8997_RTC_CTRL = 0x03,
+ MAX8997_RTC_UPDATE1 = 0x04,
+ MAX8997_RTC_UPDATE2 = 0x05,
+ MAX8997_RTC_WTSR_SMPL = 0x06,
+
+ MAX8997_RTC_SEC = 0x10,
+ MAX8997_RTC_MIN = 0x11,
+ MAX8997_RTC_HOUR = 0x12,
+ MAX8997_RTC_DAY_OF_WEEK = 0x13,
+ MAX8997_RTC_MONTH = 0x14,
+ MAX8997_RTC_YEAR = 0x15,
+ MAX8997_RTC_DAY_OF_MONTH = 0x16,
+ MAX8997_RTC_ALARM1_SEC = 0x17,
+ MAX8997_RTC_ALARM1_MIN = 0x18,
+ MAX8997_RTC_ALARM1_HOUR = 0x19,
+ MAX8997_RTC_ALARM1_DAY_OF_WEEK = 0x1a,
+ MAX8997_RTC_ALARM1_MONTH = 0x1b,
+ MAX8997_RTC_ALARM1_YEAR = 0x1c,
+ MAX8997_RTC_ALARM1_DAY_OF_MONTH = 0x1d,
+ MAX8997_RTC_ALARM2_SEC = 0x1e,
+ MAX8997_RTC_ALARM2_MIN = 0x1f,
+ MAX8997_RTC_ALARM2_HOUR = 0x20,
+ MAX8997_RTC_ALARM2_DAY_OF_WEEK = 0x21,
+ MAX8997_RTC_ALARM2_MONTH = 0x22,
+ MAX8997_RTC_ALARM2_YEAR = 0x23,
+ MAX8997_RTC_ALARM2_DAY_OF_MONTH = 0x24,
+};
+
+enum max8997_irq_source {
+ PMIC_INT1 = 0,
+ PMIC_INT2,
+ PMIC_INT3,
+ PMIC_INT4,
+
+ FUEL_GAUGE, /* Ignored (MAX17042 driver handles) */
+
+ MUIC_INT1,
+ MUIC_INT2,
+ MUIC_INT3,
+
+ GPIO_LOW, /* Not implemented */
+ GPIO_HI, /* Not implemented */
+
+ FLASH_STATUS, /* Not implemented */
+
+ MAX8997_IRQ_GROUP_NR,
+};
+
+enum max8997_irq {
+ MAX8997_PMICIRQ_PWRONR,
+ MAX8997_PMICIRQ_PWRONF,
+ MAX8997_PMICIRQ_PWRON1SEC,
+ MAX8997_PMICIRQ_JIGONR,
+ MAX8997_PMICIRQ_JIGONF,
+ MAX8997_PMICIRQ_LOWBAT2,
+ MAX8997_PMICIRQ_LOWBAT1,
+
+ MAX8997_PMICIRQ_JIGR,
+ MAX8997_PMICIRQ_JIGF,
+ MAX8997_PMICIRQ_MR,
+ MAX8997_PMICIRQ_DVS1OK,
+ MAX8997_PMICIRQ_DVS2OK,
+ MAX8997_PMICIRQ_DVS3OK,
+ MAX8997_PMICIRQ_DVS4OK,
+
+ MAX8997_PMICIRQ_CHGINS,
+ MAX8997_PMICIRQ_CHGRM,
+ MAX8997_PMICIRQ_DCINOVP,
+ MAX8997_PMICIRQ_TOPOFFR,
+ MAX8997_PMICIRQ_CHGRSTF,
+ MAX8997_PMICIRQ_MBCHGTMEXPD,
+
+ MAX8997_PMICIRQ_RTC60S,
+ MAX8997_PMICIRQ_RTCA1,
+ MAX8997_PMICIRQ_RTCA2,
+ MAX8997_PMICIRQ_SMPL_INT,
+ MAX8997_PMICIRQ_RTC1S,
+ MAX8997_PMICIRQ_WTSR,
+
+ MAX8997_MUICIRQ_ADCError,
+ MAX8997_MUICIRQ_ADCLow,
+ MAX8997_MUICIRQ_ADC,
+
+ MAX8997_MUICIRQ_VBVolt,
+ MAX8997_MUICIRQ_DBChg,
+ MAX8997_MUICIRQ_DCDTmr,
+ MAX8997_MUICIRQ_ChgDetRun,
+ MAX8997_MUICIRQ_ChgTyp,
+
+ MAX8997_MUICIRQ_OVP,
+
+ MAX8997_IRQ_NR,
+};
+
+#define MAX8997_REG_BUCK1DVS(x) (MAX8997_REG_BUCK1DVS1 + (x) - 1)
+#define MAX8997_REG_BUCK2DVS(x) (MAX8997_REG_BUCK2DVS1 + (x) - 1)
+#define MAX8997_REG_BUCK5DVS(x) (MAX8997_REG_BUCK5DVS1 + (x) - 1)
+
+struct max8997_dev {
+ struct device *dev;
+ struct i2c_client *i2c; /* 0xcc / PMIC, Battery Control, and FLASH */
+ struct i2c_client *rtc; /* slave addr 0x0c */
+ struct i2c_client *haptic; /* slave addr 0x90 */
+ struct i2c_client *muic; /* slave addr 0x4a */
+ struct mutex iolock;
+
+ int type;
+ struct platform_device *battery; /* battery control (not fuel gauge) */
+
+ bool wakeup;
+
+ /* For hibernation */
+ u8 reg_dump[MAX8997_REG_PMIC_END + MAX8997_MUIC_REG_END +
+ MAX8997_HAPTIC_REG_END];
+};
+
+enum max8997_types {
+ TYPE_MAX8997,
+ TYPE_MAX8966,
+};
+
+extern int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
+extern int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count,
+ u8 *buf);
+extern int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value);
+extern int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count,
+ u8 *buf);
+extern int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask);
+
+#endif /* __LINUX_MFD_MAX8997_PRIV_H */
diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h
new file mode 100644
index 0000000..d0d9136
--- /dev/null
+++ b/include/linux/mfd/max8997.h
@@ -0,0 +1,88 @@
+/*
+ * max8997.h - Driver for the Maxim 8997/8966
+ *
+ * Copyright (C) 2009-2010 Samsung Electrnoics
+ * MyungJoo Ham <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8998.h
+ *
+ * MAX8997 has PMIC, MUIC, HAPTIC, RTC, FLASH, and Fuel Gauge devices.
+ * Except Fuel Gauge, every device shares the same I2C bus and included in
+ * this mfd driver. Although the fuel gauge is included in the chip, it is
+ * excluded from the driver because a) it has a different I2C bus from
+ * others and b) it can be enabled simply by using MAX17042 driver.
+ */
+
+#ifndef __LINUX_MFD_MAX8998_H
+#define __LINUX_MFD_MAX8998_H
+
+#include <linux/regulator/consumer.h>
+
+/* MAX8997/8966 regulator IDs */
+enum max8998_regulators {
+ MAX8997_LDO1 = 0,
+ MAX8997_LDO2,
+ MAX8997_LDO3,
+ MAX8997_LDO4,
+ MAX8997_LDO5,
+ MAX8997_LDO6,
+ MAX8997_LDO7,
+ MAX8997_LDO8,
+ MAX8997_LDO9,
+ MAX8997_LDO10,
+ MAX8997_LDO11,
+ MAX8997_LDO12,
+ MAX8997_LDO13,
+ MAX8997_LDO14,
+ MAX8997_LDO15,
+ MAX8997_LDO16,
+ MAX8997_LDO17,
+ MAX8997_LDO18,
+ MAX8997_LDO21,
+ MAX8997_BUCK1,
+ MAX8997_BUCK2,
+ MAX8997_BUCK3,
+ MAX8997_BUCK4,
+ MAX8997_BUCK5,
+ MAX8997_BUCK6,
+ MAX8997_BUCK7,
+ MAX8997_EN32KHZ_AP,
+ MAX8997_EN32KHZ_CP,
+ MAX8997_ENVICHG,
+ MAX8997_ESAFEOUT1,
+ MAX8997_ESAFEOUT2,
+ MAX8997_CHARGER_CV, /* control MBCCV of MBCCTRL3 */
+ MAX8997_CHARGER, /* charger current, MBCCTRL4 */
+ MAX8997_CHARGER_TOPOFF, /* MBCCTRL5 */
+};
+
+struct max8997_regulator_data {
+ int id;
+ struct regulator_init_data *initdata;
+};
+
+struct max8997_platform_data {
+ bool wakeup;
+ /* PMIC: Not implemented */
+ /* MUIC: Not implemented */
+ /* HAPTIC: Not implemented */
+ /* RTC: Not implemented */
+ /* Flash: Not implemented */
+ /* Charger control: Not implemented */
+};
+
+#endif /* __LINUX_MFD_MAX8998_H */
--
1.7.1

2011-03-04 06:50:49

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH v2 2/2] MAX8997/8966 PMIC Regulator Driver Initial Release

This patch supports PMIC/Regulator part of MAX8997/MAX8966 MFD.
In this initial release, selecting voltages or current-limit
and switching on/off the regulators are supported.

Controlling voltages for DVS with GPIOs is not implemented fully
and requires more considerations: it controls multiple bucks (selection
of 1, 2, and 5) at the same time with SET1~3 gpios. Thus, when DVS-GPIO
is activated, we lose the ability to control the voltage of a single
buck regulator independently; i.e., contolling a buck affects other two
bucks. Therefore, using the conventional regulator framework directly
might be problematic.

On the other hand, controlling all the three bucks simultenously based
on the voltage set table may help build cpufreq and similar system
more robust; i.e., all the three voltages are consistent every time
without glitches during transition.

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/regulator/Kconfig | 9 +
drivers/regulator/Makefile | 1 +
drivers/regulator/max8997.c | 1238 +++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997.h | 28 +-
4 files changed, 1275 insertions(+), 1 deletions(-)
create mode 100644 drivers/regulator/max8997.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index e1d9436..395d359 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -108,6 +108,15 @@ config REGULATOR_MAX8952
via I2C bus. Maxim 8952 has one voltage output and supports 4 DVS
modes ranging from 0.77V to 1.40V by 0.01V steps.

+config REGULATOR_MAX8997
+ tristate "Maxim 8997/8966 regulator"
+ depends on MFD_MAX8997
+ help
+ This driver controls a Maxim 8997/8966 regulator
+ via I2C bus. The provided regulator is suitable for S5PC110,
+ S5PV210, and Exynos-4 chips to control VCC_CORE and
+ VCC_USIM voltages.
+
config REGULATOR_MAX8998
tristate "Maxim 8998 voltage regulator"
depends on MFD_MAX8998
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 0b5e88c..e43b852 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o
obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o
obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o
obj-$(CONFIG_REGULATOR_MAX8952) += max8952.o
+obj-$(CONFIG_REGULATOR_MAX8997) += max8997.o
obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o
diff --git a/drivers/regulator/max8997.c b/drivers/regulator/max8997.c
new file mode 100644
index 0000000..b32f5b5
--- /dev/null
+++ b/drivers/regulator/max8997.c
@@ -0,0 +1,1238 @@
+/*
+ * max8997.c - Regulator driver for the Maxim 8997/8966
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8998.c
+ */
+
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+struct max8997_data {
+ struct device *dev;
+ struct max8997_dev *iodev;
+ int num_regulators;
+ struct regulator_dev **rdev;
+ int ramp_delay; /* in mV/us */
+
+ u8 buck1_vol[8];
+ u8 buck2_vol[8];
+ u8 buck5_vol[8];
+ int buck125_gpioindex;
+
+ u8 saved_states[MAX8997_REG_MAX];
+ struct regulator_dev *saved_rdev[MAX8997_REG_MAX];
+};
+
+static inline void max8997_set_gpio(struct max8997_data *max8997)
+{
+ struct max8997_platform_data *pdata =
+ dev_get_platdata(max8997->iodev->dev);
+ int set3 = (max8997->buck125_gpioindex) & 0x1;
+ int set2 = ((max8997->buck125_gpioindex) >> 1) & 0x1;
+ int set1 = ((max8997->buck125_gpioindex) >> 2) & 0x1;
+
+ gpio_set_value(pdata->buck125_gpios[0], set1);
+ gpio_set_value(pdata->buck125_gpios[1], set2);
+ gpio_set_value(pdata->buck125_gpios[2], set3);
+}
+
+struct voltage_map_desc {
+ int min;
+ int max;
+ int step;
+ unsigned int n_bits;
+};
+
+/* Voltage maps in mV */
+static const struct voltage_map_desc ldo_voltage_map_desc = {
+ .min = 800, .max = 3950, .step = 50, .n_bits = 6,
+}; /* LDO1 ~ 18, 21 all */
+
+static const struct voltage_map_desc buck1245_voltage_map_desc = {
+ .min = 650, .max = 2225, .step = 25, .n_bits = 6,
+}; /* Buck1, 2, 4, 5 */
+
+static const struct voltage_map_desc buck37_voltage_map_desc = {
+ .min = 750, .max = 3900, .step = 50, .n_bits = 6,
+}; /* Buck3, 7 */
+
+/* current map in mA */
+static const struct voltage_map_desc charger_current_map_desc = {
+ .min = 200, .max = 950, .step = 50, .n_bits = 4,
+};
+
+static const struct voltage_map_desc topoff_current_map_desc = {
+ .min = 50, .max = 200, .step = 10, .n_bits = 4,
+};
+
+static const struct voltage_map_desc *reg_voltage_map[] = {
+ [MAX8997_LDO1] = &ldo_voltage_map_desc,
+ [MAX8997_LDO2] = &ldo_voltage_map_desc,
+ [MAX8997_LDO3] = &ldo_voltage_map_desc,
+ [MAX8997_LDO4] = &ldo_voltage_map_desc,
+ [MAX8997_LDO5] = &ldo_voltage_map_desc,
+ [MAX8997_LDO6] = &ldo_voltage_map_desc,
+ [MAX8997_LDO7] = &ldo_voltage_map_desc,
+ [MAX8997_LDO8] = &ldo_voltage_map_desc,
+ [MAX8997_LDO9] = &ldo_voltage_map_desc,
+ [MAX8997_LDO10] = &ldo_voltage_map_desc,
+ [MAX8997_LDO11] = &ldo_voltage_map_desc,
+ [MAX8997_LDO12] = &ldo_voltage_map_desc,
+ [MAX8997_LDO13] = &ldo_voltage_map_desc,
+ [MAX8997_LDO14] = &ldo_voltage_map_desc,
+ [MAX8997_LDO15] = &ldo_voltage_map_desc,
+ [MAX8997_LDO16] = &ldo_voltage_map_desc,
+ [MAX8997_LDO17] = &ldo_voltage_map_desc,
+ [MAX8997_LDO18] = &ldo_voltage_map_desc,
+ [MAX8997_LDO21] = &ldo_voltage_map_desc,
+ [MAX8997_BUCK1] = &buck1245_voltage_map_desc,
+ [MAX8997_BUCK2] = &buck1245_voltage_map_desc,
+ [MAX8997_BUCK3] = &buck37_voltage_map_desc,
+ [MAX8997_BUCK4] = &buck1245_voltage_map_desc,
+ [MAX8997_BUCK5] = &buck1245_voltage_map_desc,
+ [MAX8997_BUCK6] = NULL,
+ [MAX8997_BUCK7] = &buck37_voltage_map_desc,
+ [MAX8997_EN32KHZ_AP] = NULL,
+ [MAX8997_EN32KHZ_CP] = NULL,
+ [MAX8997_ENVICHG] = NULL,
+ [MAX8997_ESAFEOUT1] = NULL,
+ [MAX8997_ESAFEOUT2] = NULL,
+ [MAX8997_CHARGER_CV] = NULL,
+ [MAX8997_CHARGER] = &charger_current_map_desc,
+ [MAX8997_CHARGER_TOPOFF] = &topoff_current_map_desc,
+};
+
+static inline int max8997_get_ldo(struct regulator_dev *rdev)
+{
+ return rdev_get_id(rdev);
+}
+
+static int max8997_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ const struct voltage_map_desc *desc;
+ int ldo = max8997_get_ldo(rdev);
+ int val;
+
+ if (ldo >= ARRAY_SIZE(reg_voltage_map) ||
+ ldo < 0)
+ return -EINVAL;
+
+ if (ldo == MAX8997_ESAFEOUT1 || ldo == MAX8997_ESAFEOUT2) {
+ switch (selector) {
+ case 0:
+ return 4850000;
+ case 1:
+ return 4900000;
+ case 2:
+ return 4950000;
+ case 3:
+ return 3300000;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (ldo == MAX8997_CHARGER_CV) {
+ switch (selector) {
+ case 0x00:
+ return 4200000;
+ case 0x01 ... 0x0E:
+ return 4000000 + 20000 * (selector - 0x01);
+ case 0x0F:
+ return 4350000;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ desc = reg_voltage_map[ldo];
+ if (desc == NULL)
+ return -EINVAL;
+
+ val = desc->min + desc->step * selector;
+ if (val > desc->max)
+ return -EINVAL;
+
+ return val * 1000;
+}
+
+static int max8997_get_enable_register(struct regulator_dev *rdev,
+ int *reg, int *mask, int *pattern)
+{
+ int ldo = max8997_get_ldo(rdev);
+
+ switch (ldo) {
+ case MAX8997_LDO1 ... MAX8997_LDO21:
+ *reg = MAX8997_REG_LDO1CTRL + (ldo - MAX8997_LDO1);
+ *mask = 0xC0;
+ *pattern = 0xC0;
+ break;
+ case MAX8997_BUCK1:
+ *reg = MAX8997_REG_BUCK1CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_BUCK2:
+ *reg = MAX8997_REG_BUCK2CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_BUCK3:
+ *reg = MAX8997_REG_BUCK3CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_BUCK4:
+ *reg = MAX8997_REG_BUCK4CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_BUCK5:
+ *reg = MAX8997_REG_BUCK5CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_BUCK6:
+ *reg = MAX8997_REG_BUCK6CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_BUCK7:
+ *reg = MAX8997_REG_BUCK7CTRL;
+ *mask = 0x01;
+ *pattern = 0x01;
+ break;
+ case MAX8997_EN32KHZ_AP ... MAX8997_EN32KHZ_CP:
+ *reg = MAX8997_REG_MAINCON1;
+ *mask = 0x01 << (ldo - MAX8997_EN32KHZ_AP);
+ *pattern = 0x01 << (ldo - MAX8997_EN32KHZ_AP);
+ break;
+ case MAX8997_ENVICHG:
+ *reg = MAX8997_REG_MBCCTRL1;
+ *mask = 0x80;
+ *pattern = 0x80;
+ break;
+ case MAX8997_ESAFEOUT1 ... MAX8997_ESAFEOUT2:
+ *reg = MAX8997_REG_SAFEOUTCTRL;
+ *mask = 0x40 << (ldo - MAX8997_ESAFEOUT1);
+ *pattern = 0x40 << (ldo - MAX8997_ESAFEOUT1);
+ break;
+ case MAX8997_CHARGER:
+ *reg = MAX8997_REG_MBCCTRL2;
+ *mask = 0x40;
+ *pattern = 0x40;
+ break;
+ default:
+ /* Not controllable or not exists */
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+static int max8997_reg_is_enabled(struct regulator_dev *rdev)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int ret, reg, mask, pattern;
+ u8 val;
+
+ ret = max8997_get_enable_register(rdev, &reg, &mask, &pattern);
+ if (ret == -EINVAL)
+ return 1; /* "not controllable" */
+ else if (ret)
+ return ret;
+
+ ret = max8997_read_reg(i2c, reg, &val);
+ if (ret)
+ return ret;
+
+ return (val & mask) == pattern;
+}
+
+static int max8997_reg_enable(struct regulator_dev *rdev)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int ret, reg, mask, pattern;
+
+ ret = max8997_get_enable_register(rdev, &reg, &mask, &pattern);
+ if (ret)
+ return ret;
+
+ return max8997_update_reg(i2c, reg, pattern, mask);
+}
+
+static int max8997_reg_disable(struct regulator_dev *rdev)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int ret, reg, mask, pattern;
+
+ ret = max8997_get_enable_register(rdev, &reg, &mask, &pattern);
+ if (ret)
+ return ret;
+
+ return max8997_update_reg(i2c, reg, ~pattern, mask);
+}
+
+static int max8997_get_voltage_register(struct regulator_dev *rdev,
+ int *_reg, int *_shift, int *_mask)
+{
+ int ldo = max8997_get_ldo(rdev);
+ int reg, shift = 0, mask = 0x3f;
+
+ switch (ldo) {
+ case MAX8997_LDO1 ... MAX8997_LDO21:
+ reg = MAX8997_REG_LDO1CTRL + (ldo - MAX8997_LDO1);
+ break;
+ case MAX8997_BUCK1:
+ reg = MAX8997_REG_BUCK1DVS1;
+ break;
+ case MAX8997_BUCK2:
+ reg = MAX8997_REG_BUCK2DVS1;
+ break;
+ case MAX8997_BUCK3:
+ reg = MAX8997_REG_BUCK3DVS;
+ break;
+ case MAX8997_BUCK4:
+ reg = MAX8997_REG_BUCK4DVS;
+ break;
+ case MAX8997_BUCK5:
+ reg = MAX8997_REG_BUCK5DVS1;
+ break;
+ case MAX8997_BUCK7:
+ reg = MAX8997_REG_BUCK7DVS;
+ break;
+ case MAX8997_ESAFEOUT1 ... MAX8997_ESAFEOUT2:
+ reg = MAX8997_REG_SAFEOUTCTRL;
+ shift = (ldo == MAX8997_ESAFEOUT2) ? 2 : 0;
+ mask = 0x3;
+ break;
+ case MAX8997_CHARGER_CV:
+ reg = MAX8997_REG_MBCCTRL3;
+ shift = 0;
+ mask = 0xf;
+ break;
+ case MAX8997_CHARGER:
+ reg = MAX8997_REG_MBCCTRL4;
+ shift = 0;
+ mask = 0xf;
+ break;
+ case MAX8997_CHARGER_TOPOFF:
+ reg = MAX8997_REG_MBCCTRL5;
+ shift = 0;
+ mask = 0xf;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *_reg = reg;
+ *_shift = shift;
+ *_mask = mask;
+
+ return 0;
+}
+
+static int max8997_get_voltage(struct regulator_dev *rdev)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct max8997_platform_data *pdata =
+ dev_get_platdata(max8997->iodev->dev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int reg, shift, mask, ret;
+ int ldo = max8997_get_ldo(rdev);
+ u8 val;
+
+ ret = max8997_get_voltage_register(rdev, &reg, &shift, &mask);
+ if (ret)
+ return ret;
+
+
+ if ((ldo == MAX8997_BUCK1 && pdata->buck1_gpiodvs) ||
+ (ldo == MAX8997_BUCK2 && pdata->buck2_gpiodvs) ||
+ (ldo == MAX8997_BUCK5 && pdata->buck5_gpiodvs))
+ reg += max8997->buck125_gpioindex;
+
+ ret = max8997_read_reg(i2c, reg, &val);
+ if (ret)
+ return ret;
+
+ val >>= shift;
+ val &= mask;
+
+ ret = max8997_list_voltage(rdev, val);
+
+ return ret;
+}
+
+static inline int max8997_get_voltage_proper_val(
+ const struct voltage_map_desc *desc,
+ int min_vol, int max_vol)
+{
+ int i = 0;
+
+ if (desc == NULL)
+ return -EINVAL;
+
+ if (max_vol < desc->min || min_vol > desc->max)
+ return -EINVAL;
+
+ while (desc->min + desc->step * i < min_vol &&
+ desc->min + desc->step * i < desc->max)
+ i++;
+
+ if (desc->min + desc->step * i > max_vol)
+ return -EINVAL;
+
+ if (i >= (1 << desc->n_bits))
+ return -EINVAL;
+
+ return i;
+}
+
+static int max8997_set_voltage_charger_cv(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int ldo = max8997_get_ldo(rdev);
+ int lb, ub;
+ int reg, shift = 0, mask, ret = 0;
+ u8 val = 0x0;
+
+ if (ldo != MAX8997_CHARGER_CV)
+ return -EINVAL;
+
+ ret = max8997_get_voltage_register(rdev, &reg, &shift, &mask);
+ if (ret)
+ return ret;
+
+ if (max_uV < 4000000 || min_uV > 4350000)
+ return -EINVAL;
+
+ if (min_uV <= 4000000) {
+ if (max_uV >= 4000000)
+ return -EINVAL;
+ else
+ val = 0x1;
+ } else if (min_uV <= 4200000 && max_uV >= 4200000)
+ val = 0x0;
+ else {
+ lb = (min_uV - 4000001) / 20000 + 2;
+ ub = (max_uV - 4000000) / 20000 + 1;
+
+ if (lb > ub)
+ return -EINVAL;
+
+ if (lb < 0xf)
+ val = lb;
+ else {
+ if (ub >= 0xf)
+ val = 0xf;
+ else
+ return -EINVAL;
+ }
+ }
+
+ *selector = val;
+
+ ret = max8997_update_reg(i2c, reg, val << shift, mask);
+
+ return ret;
+}
+
+/*
+ * For LDO1 ~ LDO21, BUCK1~5, BUCK7, CHARGER, CHARGER_TOPOFF
+ * BUCK1, 2, and 5 are available if they are not controlled by gpio
+ */
+static int max8997_set_voltage_ldo(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int min_vol = min_uV / 1000, max_vol = max_uV / 1000;
+ const struct voltage_map_desc *desc;
+ int ldo = max8997_get_ldo(rdev);
+ int reg, shift = 0, mask, ret;
+ int i;
+ u8 org;
+
+ switch (ldo) {
+ case MAX8997_LDO1 ... MAX8997_LDO21:
+ break;
+ case MAX8997_BUCK1 ... MAX8997_BUCK5:
+ break;
+ case MAX8997_BUCK6:
+ return -EINVAL;
+ case MAX8997_BUCK7:
+ break;
+ case MAX8997_CHARGER:
+ break;
+ case MAX8997_CHARGER_TOPOFF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ desc = reg_voltage_map[ldo];
+
+ i = max8997_get_voltage_proper_val(desc, min_vol, max_vol);
+ if (i < 0)
+ return i;
+
+ ret = max8997_get_voltage_register(rdev, &reg, &shift, &mask);
+ if (ret)
+ return ret;
+
+ max8997_read_reg(i2c, reg, &org);
+ org = (org & mask) >> shift;
+
+ ret = max8997_update_reg(i2c, reg, i << shift, mask << shift);
+ *selector = i;
+
+ if (ldo == MAX8997_BUCK1 || ldo == MAX8997_BUCK2 ||
+ ldo == MAX8997_BUCK4 || ldo == MAX8997_BUCK5) {
+ /* If the voltage is increasing */
+ if (org < i)
+ udelay(desc->step * (i - org) / max8997->ramp_delay);
+ }
+
+ return ret;
+}
+
+/*
+ * Assess the damage on the voltage setting of BUCK1,2,5 by the change.
+ */
+static int max8997_assess_side_effect(struct regulator_dev *rdev,
+ u8 new_val, int *best)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct max8997_platform_data *pdata =
+ dev_get_platdata(max8997->iodev->dev);
+ int ldo = max8997_get_ldo(rdev);
+ u8 *buckx_val[3];
+ bool buckx_gpiodvs[3];
+ int side_effect[8];
+ int min_side_effect = INT_MAX;
+ int i;
+
+ *best = -1;
+
+ switch (ldo) {
+ case MAX8997_BUCK1:
+ ldo = 0;
+ break;
+ case MAX8997_BUCK2:
+ ldo = 1;
+ break;
+ case MAX8997_BUCK5:
+ ldo = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ buckx_val[0] = max8997->buck1_vol;
+ buckx_val[1] = max8997->buck2_vol;
+ buckx_val[2] = max8997->buck5_vol;
+ buckx_gpiodvs[0] = pdata->buck1_gpiodvs;
+ buckx_gpiodvs[1] = pdata->buck2_gpiodvs;
+ buckx_gpiodvs[2] = pdata->buck5_gpiodvs;
+
+ for (i = 0; i < 8; i++) {
+ if (new_val == (buckx_val[ldo])[i]) {
+ int others;
+
+ side_effect[i] = 0;
+ for (others = 0; others < 3; others++) {
+ int diff;
+
+ if (others == ldo)
+ continue;
+ if (buckx_gpiodvs[others] == false)
+ continue; /* Not affected */
+ diff = (buckx_val[others])[i] -
+ (buckx_val[others])
+ [max8997->buck125_gpioindex];
+ if (diff > 0)
+ side_effect[i] += diff;
+ else if (diff < 0)
+ side_effect[i] -= diff;
+ }
+ if (side_effect[i] == 0) {
+ *best = i;
+ return 0; /* NO SIDE EFFECT! Use This! */
+ }
+ if (side_effect[i] < min_side_effect) {
+ min_side_effect = side_effect[i];
+ *best = i;
+ }
+ } else
+ side_effect[i] = -1;
+ }
+
+ if (*best == -1)
+ return -EINVAL;
+
+ if (pdata->ignore_gpiodvs_side_effect == false)
+ return -EINVAL;
+
+ WARN(true, "MAX8997 GPIO-DVS Side Effect Warning: GPIO SET: %d -> %d\n",
+ max8997->buck125_gpioindex, *best);
+
+ return side_effect[*best];
+}
+
+/*
+ * For Buck 1 ~ 5 and 7. If it is not controlled by GPIO, this calls
+ * max8997_set_voltage_ldo to do the job.
+ */
+static int max8997_set_voltage_buck(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct max8997_platform_data *pdata =
+ dev_get_platdata(max8997->iodev->dev);
+ int ldo = max8997_get_ldo(rdev);
+ const struct voltage_map_desc *desc;
+ int new_val, new_idx, damage;
+ bool gpio_dvs_mode = false;
+ int min_vol = min_uV / 1000, max_vol = max_uV / 1000;
+
+ if (ldo < MAX8997_BUCK1 || ldo > MAX8997_BUCK7)
+ return -EINVAL;
+
+ switch (ldo) {
+ case MAX8997_BUCK1:
+ if (pdata->buck1_gpiodvs)
+ gpio_dvs_mode = true;
+ break;
+ case MAX8997_BUCK2:
+ if (pdata->buck2_gpiodvs)
+ gpio_dvs_mode = true;
+ break;
+ case MAX8997_BUCK5:
+ if (pdata->buck5_gpiodvs)
+ gpio_dvs_mode = true;
+ break;
+ }
+
+ if (gpio_dvs_mode) {
+ desc = reg_voltage_map[ldo];
+ new_val = max8997_get_voltage_proper_val(desc, min_vol,
+ max_vol);
+ if (new_val < 0)
+ return new_val;
+
+ damage = max8997_assess_side_effect(rdev, new_val, &new_idx);
+
+ if (damage < 0)
+ return damage;
+
+ max8997->buck125_gpioindex = new_idx;
+ max8997_set_gpio(max8997);
+ *selector = new_val;
+
+ return 0;
+ }
+
+ return max8997_set_voltage_ldo(rdev, min_uV, max_uV, selector);
+}
+
+static const int safeoutvolt[] = {
+ 3300000,
+ 4850000,
+ 4900000,
+ 4950000,
+};
+
+/* For SAFEOUT1 and SAFEOUT2 */
+static int max8997_set_voltage_safeout(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int ldo = max8997_get_ldo(rdev);
+ int reg, shift = 0, mask, ret;
+ int i = 0;
+ u8 val;
+
+ if (ldo != MAX8997_ESAFEOUT1 && ldo != MAX8997_ESAFEOUT2)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(safeoutvolt); i++) {
+ if (min_uV <= safeoutvolt[i] &&
+ max_uV >= safeoutvolt[i])
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(safeoutvolt))
+ return -EINVAL;
+
+ if (i == 0)
+ val = 0x3;
+ else
+ val = i - 1;
+
+ ret = max8997_get_voltage_register(rdev, &reg, &shift, &mask);
+ if (ret)
+ return ret;
+
+ ret = max8997_update_reg(i2c, reg, val << shift, mask << shift);
+ *selector = val;
+
+ return ret;
+}
+
+static int max8997_reg_enable_suspend(struct regulator_dev *rdev)
+{
+ return 0;
+}
+
+static int max8997_reg_disable_suspend(struct regulator_dev *rdev)
+{
+ struct max8997_data *max8997 = rdev_get_drvdata(rdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ int ret, reg, mask, pattern;
+ int ldo = max8997_get_ldo(rdev);
+
+ ret = max8997_get_enable_register(rdev, &reg, &mask, &pattern);
+ if (ret)
+ return ret;
+
+ max8997_read_reg(i2c, reg, &max8997->saved_states[ldo]);
+ max8997->saved_rdev[ldo] = rdev;
+
+ if (ldo == MAX8997_LDO1 ||
+ ldo == MAX8997_LDO10 ||
+ ldo == MAX8997_LDO21) {
+ pr_info("Conditional Power-Off for %s\n", rdev->desc->name);
+ return max8997_update_reg(i2c, reg, 0x40, mask);
+ }
+
+ pr_info("Full Power-Off for %s (%xh -> %xh)\n", rdev->desc->name,
+ max8997->saved_states[ldo] & mask, (~pattern) & mask);
+ return max8997_update_reg(i2c, reg, ~pattern, mask);
+}
+
+static struct regulator_ops max8997_ldo_ops = {
+ .list_voltage = max8997_list_voltage,
+ .is_enabled = max8997_reg_is_enabled,
+ .enable = max8997_reg_enable,
+ .disable = max8997_reg_disable,
+ .get_voltage = max8997_get_voltage,
+ .set_voltage = max8997_set_voltage_ldo,
+ .set_suspend_enable = max8997_reg_enable_suspend,
+ .set_suspend_disable = max8997_reg_disable_suspend,
+};
+
+static struct regulator_ops max8997_buck_ops = {
+ .list_voltage = max8997_list_voltage,
+ .is_enabled = max8997_reg_is_enabled,
+ .enable = max8997_reg_enable,
+ .disable = max8997_reg_disable,
+ .get_voltage = max8997_get_voltage,
+ .set_voltage = max8997_set_voltage_buck,
+ .set_suspend_enable = max8997_reg_enable_suspend,
+ .set_suspend_disable = max8997_reg_disable_suspend,
+};
+
+static struct regulator_ops max8997_fixedvolt_ops = {
+ .list_voltage = max8997_list_voltage,
+ .is_enabled = max8997_reg_is_enabled,
+ .enable = max8997_reg_enable,
+ .disable = max8997_reg_disable,
+ .set_suspend_enable = max8997_reg_enable_suspend,
+ .set_suspend_disable = max8997_reg_disable_suspend,
+};
+
+static struct regulator_ops max8997_safeout_ops = {
+ .is_enabled = max8997_reg_is_enabled,
+ .enable = max8997_reg_enable,
+ .disable = max8997_reg_disable,
+ .get_voltage = max8997_get_voltage,
+ .set_voltage = max8997_set_voltage_safeout,
+ .set_suspend_enable = max8997_reg_enable_suspend,
+ .set_suspend_disable = max8997_reg_disable_suspend,
+};
+
+static struct regulator_ops max8997_fixedstate_ops = {
+ .get_voltage = max8997_get_voltage,
+ .set_voltage = max8997_set_voltage_charger_cv,
+};
+
+static struct regulator_ops max8997_charger_ops = {
+ .is_enabled = max8997_reg_is_enabled,
+ .enable = max8997_reg_enable,
+ .disable = max8997_reg_disable,
+ .get_current_limit = max8997_get_voltage,
+ .set_current_limit = max8997_set_voltage_ldo,
+};
+
+static struct regulator_ops max8997_charger_fixedstate_ops = {
+ .is_enabled = max8997_reg_is_enabled,
+ .get_current_limit = max8997_get_voltage,
+ .set_current_limit = max8997_set_voltage_ldo,
+};
+
+#define regulator_desc_ldo(num) { \
+ .name = "LDO"#num, \
+ .id = MAX8997_LDO##num, \
+ .ops = &max8997_ldo_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+}
+#define regulator_desc_buck(num) { \
+ .name = "BUCK"#num, \
+ .id = MAX8997_BUCK##num, \
+ .ops = &max8997_buck_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+}
+
+static struct regulator_desc regulators[] = {
+ regulator_desc_ldo(1),
+ regulator_desc_ldo(2),
+ regulator_desc_ldo(3),
+ regulator_desc_ldo(4),
+ regulator_desc_ldo(5),
+ regulator_desc_ldo(6),
+ regulator_desc_ldo(7),
+ regulator_desc_ldo(8),
+ regulator_desc_ldo(9),
+ regulator_desc_ldo(10),
+ regulator_desc_ldo(11),
+ regulator_desc_ldo(12),
+ regulator_desc_ldo(13),
+ regulator_desc_ldo(14),
+ regulator_desc_ldo(15),
+ regulator_desc_ldo(16),
+ regulator_desc_ldo(17),
+ regulator_desc_ldo(18),
+ regulator_desc_ldo(21),
+ regulator_desc_buck(1),
+ regulator_desc_buck(2),
+ regulator_desc_buck(3),
+ regulator_desc_buck(4),
+ regulator_desc_buck(5),
+ {
+ .name = "BUCK6",
+ .id = MAX8997_BUCK6,
+ .ops = &max8997_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ regulator_desc_buck(7),
+ {
+ .name = "EN32KHz AP",
+ .id = MAX8997_EN32KHZ_AP,
+ .ops = &max8997_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "EN32KHz CP",
+ .id = MAX8997_EN32KHZ_CP,
+ .ops = &max8997_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "ENVICHG",
+ .id = MAX8997_ENVICHG,
+ .ops = &max8997_fixedvolt_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "ESAFEOUT1",
+ .id = MAX8997_ESAFEOUT1,
+ .ops = &max8997_safeout_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "ESAFEOUT2",
+ .id = MAX8997_ESAFEOUT2,
+ .ops = &max8997_safeout_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "CHARGER CV",
+ .id = MAX8997_CHARGER_CV,
+ .ops = &max8997_fixedstate_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "CHARGER",
+ .id = MAX8997_CHARGER,
+ .ops = &max8997_charger_ops,
+ .type = REGULATOR_CURRENT,
+ .owner = THIS_MODULE,
+ }, {
+ .name = "CHARGER TOPOFF",
+ .id = MAX8997_CHARGER_TOPOFF,
+ .ops = &max8997_charger_fixedstate_ops,
+ .type = REGULATOR_CURRENT,
+ .owner = THIS_MODULE,
+ },
+};
+
+static __devinit int max8997_pmic_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct regulator_dev **rdev;
+ struct max8997_data *max8997;
+ struct i2c_client *i2c;
+ int i, ret, size;
+ u8 max_buck1 = 0, max_buck2 = 0, max_buck5 = 0;
+
+ if (!pdata) {
+ dev_err(pdev->dev.parent, "No platform init data supplied.\n");
+ return -ENODEV;
+ }
+
+ max8997 = kzalloc(sizeof(struct max8997_data), GFP_KERNEL);
+ if (!max8997)
+ return -ENOMEM;
+
+ size = sizeof(struct regulator_dev *) * pdata->num_regulators;
+ max8997->rdev = kzalloc(size, GFP_KERNEL);
+ if (!max8997->rdev) {
+ kfree(max8997);
+ return -ENOMEM;
+ }
+
+ rdev = max8997->rdev;
+ max8997->dev = &pdev->dev;
+ max8997->iodev = iodev;
+ max8997->num_regulators = pdata->num_regulators;
+ platform_set_drvdata(pdev, max8997);
+ i2c = max8997->iodev->i2c;
+
+ max8997->buck125_gpioindex = pdata->buck125_default_idx;
+
+ for (i = 0; i < 8; i++) {
+ max8997->buck1_vol[i] = ret =
+ max8997_get_voltage_proper_val(
+ &buck1245_voltage_map_desc,
+ pdata->buck1_voltage[i] / 1000,
+ pdata->buck1_voltage[i] / 1000 +
+ buck1245_voltage_map_desc.step);
+ if (ret < 0)
+ goto err_alloc;
+
+ max8997->buck2_vol[i] = ret =
+ max8997_get_voltage_proper_val(
+ &buck1245_voltage_map_desc,
+ pdata->buck2_voltage[i] / 1000,
+ pdata->buck2_voltage[i] / 1000 +
+ buck1245_voltage_map_desc.step);
+ if (ret < 0)
+ goto err_alloc;
+
+ max8997->buck5_vol[i] = ret =
+ max8997_get_voltage_proper_val(
+ &buck1245_voltage_map_desc,
+ pdata->buck5_voltage[i] / 1000,
+ pdata->buck5_voltage[i] / 1000 +
+ buck1245_voltage_map_desc.step);
+ if (ret < 0)
+ goto err_alloc;
+
+ if (max_buck1 < max8997->buck1_vol[i])
+ max_buck1 = max8997->buck1_vol[i];
+ if (max_buck2 < max8997->buck2_vol[i])
+ max_buck2 = max8997->buck2_vol[i];
+ if (max_buck5 < max8997->buck5_vol[i])
+ max_buck5 = max8997->buck5_vol[i];
+ }
+
+ /* For the safety, set max voltage before setting up */
+ for (i = 0; i < 8; i++) {
+ max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS(i + 1),
+ max_buck1, 0x3f);
+ max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS(i + 1),
+ max_buck2, 0x3f);
+ max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS(i + 1),
+ max_buck5, 0x3f);
+ }
+
+ /*
+ * If buck 1, 2, and 5 do not care DVS GPIO settings, ignore them.
+ * If at least one of them cares, set gpios.
+ */
+ if (pdata->buck1_gpiodvs || pdata->buck2_gpiodvs ||
+ pdata->buck5_gpiodvs) {
+ bool gpio1set = false, gpio2set = false;
+
+ if (!gpio_is_valid(pdata->buck125_gpios[0]) ||
+ !gpio_is_valid(pdata->buck125_gpios[1]) ||
+ !gpio_is_valid(pdata->buck125_gpios[2])) {
+ dev_err(&pdev->dev, "GPIO NOT VALID\n");
+ ret = -EINVAL;
+ goto err_alloc;
+ }
+
+ ret = gpio_request(pdata->buck125_gpios[0],
+ "MAX8997 SET1");
+ if (ret == -EBUSY)
+ dev_warn(&pdev->dev, "Duplicated gpio request"
+ " on SET1\n");
+ else if (ret)
+ goto err_alloc;
+ else
+ gpio1set = true;
+
+ ret = gpio_request(pdata->buck125_gpios[1],
+ "MAX8997 SET2");
+ if (ret == -EBUSY)
+ dev_warn(&pdev->dev, "Duplicated gpio request"
+ " on SET2\n");
+ else if (ret) {
+ if (gpio1set)
+ gpio_free(pdata->buck125_gpios[0]);
+ goto err_alloc;
+ } else
+ gpio2set = true;
+
+ ret = gpio_request(pdata->buck125_gpios[2],
+ "MAX8997 SET3");
+ if (ret == -EBUSY)
+ dev_warn(&pdev->dev, "Duplicated gpio request"
+ " on SET3\n");
+ else if (ret) {
+ if (gpio1set)
+ gpio_free(pdata->buck125_gpios[0]);
+ if (gpio2set)
+ gpio_free(pdata->buck125_gpios[1]);
+ goto err_alloc;
+ }
+
+ gpio_direction_output(pdata->buck125_gpios[0],
+ (max8997->buck125_gpioindex >> 2)
+ & 0x1); /* SET1 */
+ gpio_direction_output(pdata->buck125_gpios[1],
+ (max8997->buck125_gpioindex >> 1)
+ & 0x1); /* SET2 */
+ gpio_direction_output(pdata->buck125_gpios[2],
+ (max8997->buck125_gpioindex >> 0)
+ & 0x1); /* SET3 */
+ ret = 0;
+ }
+
+ /* DVS-GPIO disabled */
+ max8997_update_reg(i2c, MAX8997_REG_BUCK1CTRL, (pdata->buck1_gpiodvs) ?
+ (1 << 1) : (0 << 1), 1 << 1);
+ max8997_update_reg(i2c, MAX8997_REG_BUCK2CTRL, (pdata->buck2_gpiodvs) ?
+ (1 << 1) : (0 << 1), 1 << 1);
+ max8997_update_reg(i2c, MAX8997_REG_BUCK5CTRL, (pdata->buck5_gpiodvs) ?
+ (1 << 1) : (0 << 1), 1 << 1);
+
+ /* Initialize all the DVS related BUCK registers */
+ for (i = 0; i < 8; i++) {
+ max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS(i + 1),
+ max8997->buck1_vol[i],
+ 0x3f);
+ max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS(i + 1),
+ max8997->buck2_vol[i],
+ 0x3f);
+ max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS(i + 1),
+ max8997->buck5_vol[i],
+ 0x3f);
+ }
+
+ for (i = 0; i < pdata->num_regulators; i++) {
+ const struct voltage_map_desc *desc;
+ int id = pdata->regulators[i].id;
+
+ desc = reg_voltage_map[id];
+ if (desc)
+ regulators[id].n_voltages =
+ (desc->max - desc->min) / desc->step + 1;
+ else if (id == MAX8997_ESAFEOUT1 || id == MAX8997_ESAFEOUT2)
+ regulators[id].n_voltages = 4;
+ else if (id == MAX8997_CHARGER_CV)
+ regulators[id].n_voltages = 16;
+
+ rdev[i] = regulator_register(&regulators[id], max8997->dev,
+ pdata->regulators[i].initdata, max8997);
+ if (IS_ERR(rdev[i])) {
+ ret = PTR_ERR(rdev[i]);
+ dev_err(max8997->dev, "regulator init failed for %d\n",
+ id);
+ rdev[i] = NULL;
+ goto err;
+ }
+ }
+
+ /* Misc Settings */
+ /* TODO: Read BUCK RAMP and apply delays for it */
+ /* Default is 10mV/us */
+ max8997->ramp_delay = 10;
+ max8997_write_reg(i2c, MAX8997_REG_BUCKRAMP, (0xf << 4) | 0x9);
+
+ return 0;
+err:
+ for (i = 0; i < max8997->num_regulators; i++)
+ if (rdev[i])
+ regulator_unregister(rdev[i]);
+err_alloc:
+ kfree(max8997->rdev);
+ kfree(max8997);
+
+ return ret;
+}
+
+static int __devexit max8997_pmic_remove(struct platform_device *pdev)
+{
+ struct max8997_data *max8997 = platform_get_drvdata(pdev);
+ struct regulator_dev **rdev = max8997->rdev;
+ int i;
+
+ for (i = 0; i < max8997->num_regulators; i++)
+ if (rdev[i])
+ regulator_unregister(rdev[i]);
+
+ kfree(max8997->rdev);
+ kfree(max8997);
+
+ return 0;
+}
+
+static const struct platform_device_id max8997_pmic_id[] = {
+ { "max8997-pmic", 0},
+ { },
+};
+
+static int max8997_resume(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev,
+ struct platform_device, dev);
+ struct max8997_data *max8997 = platform_get_drvdata(pdev);
+ struct i2c_client *i2c = max8997->iodev->i2c;
+ struct regulator_dev *rdev;
+ int ret, reg, mask, pattern;
+ int i;
+ u8 val;
+
+ /* Show Error/Warning Messages for Inconsistency */
+ for (i = 0; i < MAX8997_REG_MAX; i++) {
+ if (max8997->saved_rdev[i]) {
+ rdev = max8997->saved_rdev[i];
+ ret = max8997_get_enable_register(rdev, &reg, &mask,
+ &pattern);
+ if (ret) {
+ dev_err(dev, "Cannot recover %s.\n",
+ rdev->desc->name);
+ continue;
+ }
+
+ ret = max8997_read_reg(i2c, reg, &val);
+ if (ret) {
+ dev_err(dev, "Cannot read %s.\n",
+ rdev->desc->name);
+ continue;
+ }
+
+ if (rdev->use_count > 0 || (rdev->constraints &&
+ rdev->constraints->always_on)) {
+ if ((val & mask) != (pattern & mask)) {
+ dev_err(dev, "regulator_suspend_prepare"
+ "(ON) missed turning on"
+ " %s (%xh)\n",
+ rdev->desc->name,
+ val & mask);
+ max8997_update_reg(i2c, reg, pattern,
+ mask);
+ }
+ continue;
+ }
+
+ if ((val & mask) == (pattern & mask)) {
+ dev_err(dev, "Regulator %s has NO use_count and"
+ " is NOT always-on but enabled."
+ " reg = %2.2xh\n",
+ rdev->desc->name, val);
+ continue;
+ }
+
+ ret = max8997_update_reg(i2c, reg,
+ max8997->saved_states[i], mask);
+ if (ret) {
+ dev_err(dev, "Cannot update %s.\n",
+ rdev->desc->name);
+ continue;
+ }
+
+ if ((val & mask) != (max8997->saved_states[i] & mask)) {
+ dev_err(dev, "Regulator %s resumes from %2.2xh"
+ " to %2.2xh (uc = %d)\n",
+ rdev->desc->name,
+ val & mask,
+ max8997->saved_states[i] & mask,
+ rdev->use_count);
+ }
+ }
+ max8997->saved_rdev[i] = NULL;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops max8997_pm = {
+ .resume = max8997_resume,
+};
+
+static struct platform_driver max8997_pmic_driver = {
+ .driver = {
+ .name = "max8997-pmic",
+ .owner = THIS_MODULE,
+ .pm = &max8997_pm,
+ },
+ .probe = max8997_pmic_probe,
+ .remove = __devexit_p(max8997_pmic_remove),
+ .id_table = max8997_pmic_id,
+};
+
+static int __init max8997_pmic_init(void)
+{
+ return platform_driver_register(&max8997_pmic_driver);
+}
+subsys_initcall(max8997_pmic_init);
+
+static void __exit max8997_pmic_cleanup(void)
+{
+ platform_driver_unregister(&max8997_pmic_driver);
+}
+module_exit(max8997_pmic_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 8997/8966 Regulator Driver");
+MODULE_AUTHOR("MyungJoo Ham <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h
index d0d9136..cb671b3 100644
--- a/include/linux/mfd/max8997.h
+++ b/include/linux/mfd/max8997.h
@@ -68,6 +68,8 @@ enum max8998_regulators {
MAX8997_CHARGER_CV, /* control MBCCV of MBCCTRL3 */
MAX8997_CHARGER, /* charger current, MBCCTRL4 */
MAX8997_CHARGER_TOPOFF, /* MBCCTRL5 */
+
+ MAX8997_REG_MAX,
};

struct max8997_regulator_data {
@@ -77,7 +79,31 @@ struct max8997_regulator_data {

struct max8997_platform_data {
bool wakeup;
- /* PMIC: Not implemented */
+ /* IRQ: Not implemented */
+ /* ---- PMIC ---- */
+ struct max8997_regulator_data *regulators;
+ int num_regulators;
+
+ /*
+ * SET1~3 DVS GPIOs control Buck1, 2, and 5 simultaneously. Therefore,
+ * With buckx_gpiodvs enabled, the buckx cannot be controlled
+ * independently. To control buckx (of 1, 2, and 5) independently,
+ * disable buckx_gpiodvs and control with BUCKxDVS1 register.
+ *
+ * When buckx_gpiodvs and bucky_gpiodvs are both enabled, set_voltage
+ * on buckx will change the voltage of bucky at the same time.
+ *
+ */
+ bool ignore_gpiodvs_side_effect;
+ int buck125_gpios[3]; /* GPIO of [0]SET1, [1]SET2, [2]SET3 */
+ int buck125_default_idx; /* Default value of SET1, 2, 3 */
+ unsigned int buck1_voltage[8]; /* buckx_voltage in uV */
+ bool buck1_gpiodvs;
+ unsigned int buck2_voltage[8];
+ bool buck2_gpiodvs;
+ unsigned int buck5_voltage[8];
+ bool buck5_gpiodvs;
+
/* MUIC: Not implemented */
/* HAPTIC: Not implemented */
/* RTC: Not implemented */
--
1.7.1

2011-03-04 07:24:22

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH] MAX8997/8966 PMIC: compiler warning removed (incompatible pointer)

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/regulator/max8997.c | 13 +++++++++++--
1 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/regulator/max8997.c b/drivers/regulator/max8997.c
index 30778c5..957063a 100644
--- a/drivers/regulator/max8997.c
+++ b/drivers/regulator/max8997.c
@@ -790,18 +790,27 @@ static struct regulator_ops max8997_fixedstate_ops = {
.set_voltage = max8997_set_voltage_charger_cv,
};

+static int max8997_set_voltage_ldo_wrap(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ unsigned dummy;
+
+ return max8997_set_voltage_ldo(rdev, min_uV, max_uV, &dummy);
+}
+
+
static struct regulator_ops max8997_charger_ops = {
.is_enabled = max8997_reg_is_enabled,
.enable = max8997_reg_enable,
.disable = max8997_reg_disable,
.get_current_limit = max8997_get_voltage,
- .set_current_limit = max8997_set_voltage_ldo,
+ .set_current_limit = max8997_set_voltage_ldo_wrap,
};

static struct regulator_ops max8997_charger_fixedstate_ops = {
.is_enabled = max8997_reg_is_enabled,
.get_current_limit = max8997_get_voltage,
- .set_current_limit = max8997_set_voltage_ldo,
+ .set_current_limit = max8997_set_voltage_ldo_wrap,
};

#define regulator_desc_ldo(num) { \
--
1.7.1

2011-03-04 08:10:33

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH 1/2] MAX8997/8966 MFD: Add IRQ control feature

This patch enables IRQ handling for MAX8997/8966 chips.

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/mfd/Makefile | 2 +-
drivers/mfd/max8997-irq.c | 441 +++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997-private.h | 12 +
include/linux/mfd/max8997.h | 7 +-
4 files changed, 459 insertions(+), 3 deletions(-)
create mode 100644 drivers/mfd/max8997-irq.c

diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f6662e3..4cff10c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -60,7 +60,7 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
obj-$(CONFIG_PMIC_DA903X) += da903x.o
max8925-objs := max8925-core.o max8925-i2c.o
obj-$(CONFIG_MFD_MAX8925) += max8925.o
-obj-$(CONFIG_MFD_MAX8997) += max8997.o
+obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o
obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o

pcf50633-objs := pcf50633-core.o pcf50633-irq.o
diff --git a/drivers/mfd/max8997-irq.c b/drivers/mfd/max8997-irq.c
new file mode 100644
index 0000000..7eda6f3
--- /dev/null
+++ b/drivers/mfd/max8997-irq.c
@@ -0,0 +1,441 @@
+/*
+ * max8997-irq.c - Interrupt controller support for MAX8997
+ *
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ * MyungJoo Ham <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8998-irq.c
+ */
+
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+static u8 max8997_mask_reg[] = {
+ [PMIC_INT1] = MAX8997_REG_INT1MSK,
+ [PMIC_INT2] = MAX8997_REG_INT2MSK,
+ [PMIC_INT3] = MAX8997_REG_INT3MSK,
+ [PMIC_INT4] = MAX8997_REG_INT4MSK,
+ [FUEL_GAUGE] = MAX8997_REG_INVALID,
+ [MUIC_INT1] = MAX8997_MUIC_REG_INTMASK1,
+ [MUIC_INT2] = MAX8997_MUIC_REG_INTMASK2,
+ [MUIC_INT3] = MAX8997_MUIC_REG_INTMASK3,
+ [GPIO_LOW] = MAX8997_REG_INVALID,
+ [GPIO_HI] = MAX8997_REG_INVALID,
+ [FLASH_STATUS] = MAX8997_REG_INVALID,
+};
+
+static struct i2c_client *get_i2c(struct max8997_dev *max8997,
+ enum max8997_irq_source src)
+{
+ switch (src) {
+ case PMIC_INT1 ... PMIC_INT4:
+ return max8997->i2c;
+ case FUEL_GAUGE:
+ return NULL;
+ case MUIC_INT1 ... MUIC_INT3:
+ return max8997->muic;
+ case GPIO_LOW ... GPIO_HI:
+ return max8997->i2c;
+ case FLASH_STATUS:
+ return max8997->i2c;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+
+struct max8997_irq_data {
+ int mask;
+ enum max8997_irq_source group;
+};
+
+static struct max8997_irq_data max8997_irqs[] = {
+ [MAX8997_PMICIRQ_PWRONR] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 0,
+ },
+ [MAX8997_PMICIRQ_PWRONF] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 1,
+ },
+ [MAX8997_PMICIRQ_PWRON1SEC] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 3,
+ },
+ [MAX8997_PMICIRQ_JIGONR] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 4,
+ },
+ [MAX8997_PMICIRQ_JIGONF] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 5,
+ },
+ [MAX8997_PMICIRQ_LOWBAT2] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 6,
+ },
+ [MAX8997_PMICIRQ_LOWBAT1] = {
+ .group = PMIC_INT1,
+ .mask = 1 << 7,
+ },
+
+ [MAX8997_PMICIRQ_JIGR] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 0,
+ },
+ [MAX8997_PMICIRQ_JIGF] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 1,
+ },
+ [MAX8997_PMICIRQ_MR] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 2,
+ },
+ [MAX8997_PMICIRQ_DVS1OK] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 3,
+ },
+ [MAX8997_PMICIRQ_DVS2OK] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 4,
+ },
+ [MAX8997_PMICIRQ_DVS3OK] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 5,
+ },
+ [MAX8997_PMICIRQ_DVS4OK] = {
+ .group = PMIC_INT2,
+ .mask = 1 << 6,
+ },
+
+ [MAX8997_PMICIRQ_CHGINS] = {
+ .group = PMIC_INT3,
+ .mask = 1 << 0,
+ },
+ [MAX8997_PMICIRQ_CHGRM] = {
+ .group = PMIC_INT3,
+ .mask = 1 << 1,
+ },
+ [MAX8997_PMICIRQ_DCINOVP] = {
+ .group = PMIC_INT3,
+ .mask = 1 << 2,
+ },
+ [MAX8997_PMICIRQ_TOPOFFR] = {
+ .group = PMIC_INT3,
+ .mask = 1 << 3,
+ },
+ [MAX8997_PMICIRQ_CHGRSTF] = {
+ .group = PMIC_INT3,
+ .mask = 1 << 5,
+ },
+ [MAX8997_PMICIRQ_MBCHGTMEXPD] = {
+ .group = PMIC_INT3,
+ .mask = 1 << 7,
+ },
+
+ [MAX8997_PMICIRQ_RTC60S] = {
+ .group = PMIC_INT4,
+ .mask = 1 << 0,
+ },
+ [MAX8997_PMICIRQ_RTCA1] = {
+ .group = PMIC_INT4,
+ .mask = 1 << 1,
+ },
+ [MAX8997_PMICIRQ_RTCA2] = {
+ .group = PMIC_INT4,
+ .mask = 1 << 2,
+ },
+ [MAX8997_PMICIRQ_SMPL_INT] = {
+ .group = PMIC_INT4,
+ .mask = 1 << 3,
+ },
+ [MAX8997_PMICIRQ_RTC1S] = {
+ .group = PMIC_INT4,
+ .mask = 1 << 4,
+ },
+ [MAX8997_PMICIRQ_WTSR] = {
+ .group = PMIC_INT4,
+ .mask = 1 << 5,
+ },
+
+ [MAX8997_MUICIRQ_ADCError] = {
+ .group = MUIC_INT1,
+ .mask = 1 << 2,
+ },
+ [MAX8997_MUICIRQ_ADCLow] = {
+ .group = MUIC_INT1,
+ .mask = 1 << 1,
+ },
+ [MAX8997_MUICIRQ_ADC] = {
+ .group = MUIC_INT1,
+ .mask = 1 << 0,
+ },
+
+ [MAX8997_MUICIRQ_VBVolt] = {
+ .group = MUIC_INT2,
+ .mask = 1 << 4,
+ },
+ [MAX8997_MUICIRQ_DBChg] = {
+ .group = MUIC_INT2,
+ .mask = 1 << 3,
+ },
+ [MAX8997_MUICIRQ_DCDTmr] = {
+ .group = MUIC_INT2,
+ .mask = 1 << 2,
+ },
+ [MAX8997_MUICIRQ_ChgDetRun] = {
+ .group = MUIC_INT2,
+ .mask = 1 << 1,
+ },
+ [MAX8997_MUICIRQ_ChgTyp] = {
+ .group = MUIC_INT2,
+ .mask = 1 << 0,
+ },
+
+ [MAX8997_MUICIRQ_OVP] = {
+ .group = MUIC_INT3,
+ .mask = 1 << 2,
+ },
+};
+
+static inline struct max8997_irq_data *
+irq_to_max8997_irq(struct max8997_dev *max8997, int irq)
+{
+ return &max8997_irqs[irq - max8997->irq_base];
+}
+
+static void max8997_irq_lock(unsigned int irq)
+{
+ struct max8997_dev *max8997 = get_irq_chip_data(irq);
+
+ mutex_lock(&max8997->irqlock);
+}
+
+static void max8997_irq_sync_unlock(unsigned int irq)
+{
+ struct max8997_dev *max8997 = get_irq_chip_data(irq);
+ int i;
+
+ for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) {
+ u8 mask_reg = max8997_mask_reg[i];
+ struct i2c_client *i2c = get_i2c(max8997, i);
+
+ if (mask_reg == MAX8997_REG_INVALID ||
+ IS_ERR_OR_NULL(i2c))
+ continue;
+ max8997->irq_masks_cache[i] = max8997->irq_masks_cur[i];
+
+ max8997_write_reg(i2c, max8997_mask_reg[i],
+ max8997->irq_masks_cur[i]);
+ }
+
+ mutex_unlock(&max8997->irqlock);
+}
+
+static void max8997_irq_mask(unsigned int irq)
+{
+ struct max8997_dev *max8997 = get_irq_chip_data(irq);
+ struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, irq);
+
+ max8997->irq_masks_cur[irq_data->group] |= irq_data->mask;
+}
+
+static void max8997_irq_unmask(unsigned int irq)
+{
+ struct max8997_dev *max8997 = get_irq_chip_data(irq);
+ struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, irq);
+
+ max8997->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
+}
+
+static struct irq_chip max8997_irq_chip = {
+ .name = "max8997",
+ .bus_lock = max8997_irq_lock,
+ .bus_sync_unlock = max8997_irq_sync_unlock,
+ .mask = max8997_irq_mask,
+ .unmask = max8997_irq_unmask,
+};
+
+static irqreturn_t max8997_irq_thread(int irq, void *data)
+{
+ struct max8997_dev *max8997 = data;
+ u8 irq_reg[MAX8997_IRQ_GROUP_NR];
+ u8 irq_src;
+ int ret;
+ int i;
+
+ ret = max8997_read_reg(max8997->i2c, MAX8997_REG_INTSRC, &irq_src);
+ if (ret < 0) {
+ dev_err(max8997->dev, "Failed to read interrupt source: %d\n",
+ ret);
+ return IRQ_NONE;
+ }
+
+ for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++)
+ irq_reg[i] = 0;
+
+ if (irq_src & (1 << 1)) {
+ /* PMIC INT1 ~ INT4 */
+ max8997_bulk_read(max8997->i2c, MAX8997_REG_INT1, 4,
+ &irq_reg[PMIC_INT1]);
+ }
+ if (irq_src & (1 << 2)) {
+ /* FUEL GAUGE Interrupt */
+ /* Ignored */
+ irq_reg[FUEL_GAUGE] = 0;
+ }
+ if (irq_src & (1 << 3)) {
+ /* MUIC INT1 ~ INT3 */
+ max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, 3,
+ &irq_reg[MUIC_INT1]);
+ }
+ if (irq_src & (1 << 4)) {
+ /* GPIO Interrupt */
+ u8 gpio_info[12];
+ int gpio;
+
+ irq_reg[GPIO_LOW] = 0;
+ irq_reg[GPIO_HI] = 0;
+
+ max8997_bulk_read(max8997->i2c, MAX8997_REG_GPIOCNTL1, 12,
+ gpio_info);
+ for (gpio = 0; gpio < 8; gpio++) {
+ u8 val = gpio_info[gpio] & 0x34;
+ if (((val & 0x30) == 0x30) ||
+ (val == 0x10) ||
+ (val == 0x24))
+ irq_reg[GPIO_LOW] |= (1 << gpio);
+ }
+ for (gpio = 8; gpio < 12; gpio++) {
+ u8 val = gpio_info[gpio] & 0x34;
+ if (((val & 0x30) == 0x30) ||
+ (val == 0x10) ||
+ (val == 0x24))
+ irq_reg[GPIO_HI] |= (1 << (gpio - 8));
+ }
+ }
+ if (irq_src & (1 << 5)) {
+ /* Flash Status Interrupt */
+ ret = max8997_read_reg(max8997->i2c, MAX8997_REG_FLASHSTATUS,
+ &irq_reg[FLASH_STATUS]);
+ }
+
+ /* Apply masking */
+ for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++)
+ irq_reg[i] &= ~max8997->irq_masks_cur[i];
+
+ /* Report */
+ for (i = 0; i < MAX8997_IRQ_NR; i++) {
+ if (irq_reg[max8997_irqs[i].group] & max8997_irqs[i].mask)
+ handle_nested_irq(max8997->irq_base + i);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int max8997_irq_resume(struct max8997_dev *max8997)
+{
+ if (max8997->irq && max8997->irq_base)
+ max8997_irq_thread(max8997->irq_base, max8997);
+ return 0;
+}
+
+int max8997_irq_init(struct max8997_dev *max8997)
+{
+ int i;
+ int cur_irq;
+ int ret;
+
+ if (!max8997->irq) {
+ dev_warn(max8997->dev, "No interrupt specified.\n");
+ max8997->irq_base = 0;
+ return 0;
+ }
+
+ if (!max8997->irq_base) {
+ dev_err(max8997->dev, "No interrupt base specified.\n");
+ return 0;
+ }
+
+ mutex_init(&max8997->irqlock);
+
+ /* Mask individual interrupt sources */
+ for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) {
+ struct i2c_client *i2c;
+
+ max8997->irq_masks_cur[i] = 0xff;
+ max8997->irq_masks_cache[i] = 0xff;
+ i2c = get_i2c(max8997, i);
+
+ if (IS_ERR_OR_NULL(i2c))
+ continue;
+ if (max8997_mask_reg[i] == MAX8997_REG_INVALID)
+ continue;
+
+ max8997_write_reg(i2c, max8997_mask_reg[i], 0xff);
+ }
+
+ /* Register with genirq */
+ for (i = 0; i < MAX8997_IRQ_NR; i++) {
+ cur_irq = i + max8997->irq_base;
+ set_irq_chip_data(cur_irq, max8997);
+ set_irq_chip_and_handler(cur_irq, &max8997_irq_chip,
+ handle_edge_irq);
+ set_irq_nested_thread(cur_irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(cur_irq, IRQF_VALID);
+#else
+ set_irq_noprobe(cur_irq);
+#endif
+ }
+
+ ret = request_threaded_irq(max8997->irq, NULL, max8997_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "max8997-irq", max8997);
+
+ if (ret) {
+ dev_err(max8997->dev, "Failed to request IRQ %d: %d\n",
+ max8997->irq, ret);
+ return ret;
+ }
+
+ if (!max8997->ono)
+ return 0;
+
+ ret = request_threaded_irq(max8997->ono, NULL, max8997_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT, "max8997-ono", max8997);
+
+ if (ret)
+ dev_err(max8997->dev, "Failed to request ono-IRQ %d: %d\n",
+ max8997->ono, ret);
+
+ return 0;
+}
+
+void max8997_irq_exit(struct max8997_dev *max8997)
+{
+ if (max8997->ono)
+ free_irq(max8997->ono, max8997);
+
+ if (max8997->irq)
+ free_irq(max8997->irq, max8997);
+}
diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h
index 93a9477..8c0528c 100644
--- a/include/linux/mfd/max8997-private.h
+++ b/include/linux/mfd/max8997-private.h
@@ -24,6 +24,8 @@

#include <linux/i2c.h>

+#define MAX8997_REG_INVALID (0xff)
+
enum max8997_pmic_reg {
MAX8997_REG_PMIC_ID0 = 0x00,
MAX8997_REG_PMIC_ID1 = 0x01,
@@ -324,7 +326,13 @@ struct max8997_dev {
int type;
struct platform_device *battery; /* battery control (not fuel gauge) */

+ int irq;
+ int ono;
+ int irq_base;
bool wakeup;
+ struct mutex irqlock;
+ int irq_masks_cur[MAX8997_IRQ_GROUP_NR];
+ int irq_masks_cache[MAX8997_IRQ_GROUP_NR];

/* For hibernation */
u8 reg_dump[MAX8997_REG_PMIC_END + MAX8997_MUIC_REG_END +
@@ -336,6 +344,10 @@ enum max8997_types {
TYPE_MAX8966,
};

+extern int max8997_irq_init(struct max8997_dev *max8997);
+extern void max8997_irq_exit(struct max8997_dev *max8997);
+extern int max8997_irq_resume(struct max8997_dev *max8997);
+
extern int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest);
extern int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count,
u8 *buf);
diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h
index cb671b3..60931d0 100644
--- a/include/linux/mfd/max8997.h
+++ b/include/linux/mfd/max8997.h
@@ -78,8 +78,11 @@ struct max8997_regulator_data {
};

struct max8997_platform_data {
- bool wakeup;
- /* IRQ: Not implemented */
+ /* IRQ */
+ int irq_base;
+ int ono;
+ int wakeup;
+
/* ---- PMIC ---- */
struct max8997_regulator_data *regulators;
int num_regulators;
--
1.7.1

2011-03-04 08:21:11

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH 2/2] MAX8997/8966 RTC Driver Initial Release

This patch adds support for RTC functionality for MAX8997/8966 chips.

The 16ms delay is somehow required by MAX8997 chips (at least for the
initial releases) and it can be enabled by .delay in platform_data.

This patch requires MAX8997 MFD driver.

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/rtc/Kconfig | 10 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-max8997.c | 442 +++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997.h | 5 +-
4 files changed, 457 insertions(+), 1 deletions(-)
create mode 100644 drivers/rtc/rtc-max8997.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4941cad..8dc93e8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -196,6 +196,16 @@ config RTC_DRV_MAX8925
This driver can also be built as a module. If so, the module
will be called rtc-max8925.

+config RTC_DRV_MAX8997
+ tristate "Maxim MAX8997/8966"
+ depends on MFD_MAX8997
+ help
+ If you say yes here you will get support for the
+ RTC of Maxim MAX8997 and MAX8966 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-max8997.
+
config RTC_DRV_MAX8998
tristate "Maxim MAX8998"
depends on MFD_MAX8998
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2afdaf3..8b74d41 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
obj-$(CONFIG_RTC_MXC) += rtc-mxc.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
+obj-$(CONFIG_RTC_DRV_MAX8997) += rtc-max8997.o
obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o
diff --git a/drivers/rtc/rtc-max8997.c b/drivers/rtc/rtc-max8997.c
new file mode 100644
index 0000000..b7ad517
--- /dev/null
+++ b/drivers/rtc/rtc-max8997.c
@@ -0,0 +1,442 @@
+/*
+ * rtc-max8997.c - RTC driver for Maxim 8966 and 8997
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/bcd.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+struct rtc_data {
+ struct mutex lock;
+ struct i2c_client *i2c;
+ struct max8997_dev *iodev;
+
+ struct device *dev;
+ struct rtc_device *rtc_dev;
+
+ int irq;
+ bool delay;
+};
+
+enum {
+ RTC_SEC = 0,
+ RTC_MIN,
+ RTC_HOUR,
+ RTC_WEEKDAY,
+ RTC_MONTH,
+ RTC_YEAR,
+ RTC_MONTHDAY,
+ RTC_DATA_SIZE,
+};
+
+static inline int wday_8997_to_kernel(u8 shifted)
+{
+ int counter = -1;
+ while (shifted) {
+ shifted >>= 1;
+ counter++;
+ }
+ return counter;
+}
+
+static void __maybe_unused dump(struct rtc_data *rtc)
+{
+ int i, j;
+ char buf[256];
+ char *ptr;
+ u8 val;
+
+ for (i = 0; i < 3; i++) {
+ ptr = buf;
+ ptr += sprintf(ptr, "[%2.2xh] ", i * 16);
+ for (j = 0; j < 16; j++) {
+ max8997_read_reg(rtc->i2c, i * 16 + j, &val);
+ ptr += sprintf(ptr, "%2.2x ", val);
+ }
+ pr_info("%s\n", buf);
+ }
+}
+
+static inline u8 wday_kernel_to_8997(u8 wday)
+{
+ return 1 << wday;
+}
+
+static void data_to_tm(u8 *data, struct rtc_time *tm)
+{
+ /* Binary Mode (not BCD) */
+ tm->tm_sec = bcd2bin(data[RTC_SEC] & 0x7f);
+ tm->tm_min = bcd2bin(data[RTC_MIN] & 0x7f);
+ tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x3f); /* 24 hour mode */
+ tm->tm_wday = wday_8997_to_kernel(data[RTC_WEEKDAY] & 0x7f);
+ tm->tm_mon = bcd2bin(data[RTC_MONTH] & 0x1f);
+ tm->tm_year = bcd2bin(data[RTC_YEAR] & 0x7f) + 100;
+ tm->tm_mday = bcd2bin(data[RTC_MONTHDAY] & 0x3f);
+ tm->tm_yday = 0;
+ tm->tm_isdst = 0;
+}
+
+static void tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ data[RTC_SEC] = bin2bcd(tm->tm_sec) & 0x7f;
+ data[RTC_MIN] = bin2bcd(tm->tm_min) & 0x7f;
+ data[RTC_HOUR] = bin2bcd(tm->tm_hour) & 0x3f;
+ data[RTC_WEEKDAY] = wday_kernel_to_8997(tm->tm_wday) & 0x7f;
+ data[RTC_MONTH] = bin2bcd(tm->tm_mon) & 0x1f;
+ data[RTC_YEAR] = bin2bcd((tm->tm_year > 100) ? (tm->tm_year - 100) : 0)
+ & 0x7f;
+ data[RTC_MONTHDAY] = bin2bcd(tm->tm_mday) & 0x3f;
+
+ if (tm->tm_year < 100) /* Prior 2000 */
+ pr_warn("MAX8997 RTC cannot handle the year %d. "
+ "Assume it's 2000.\n", 1900 + tm->tm_year);
+}
+
+static int max8997_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct rtc_data *rtc = dev_get_drvdata(dev);
+ u8 data[RTC_DATA_SIZE];
+ int ret;
+
+ mutex_lock(&rtc->lock);
+ ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_SEC, RTC_DATA_SIZE, data);
+ mutex_unlock(&rtc->lock);
+
+ if (ret < 0)
+ return ret;
+
+ data_to_tm(data, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int max8997_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct rtc_data *rtc = dev_get_drvdata(dev);
+ u8 data[RTC_DATA_SIZE];
+ int ret;
+
+ tm_to_data(tm, data);
+
+ mutex_lock(&rtc->lock);
+
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+
+ ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_SEC,
+ RTC_DATA_SIZE, data);
+
+ if (rtc->delay)
+ msleep(20);
+
+ mutex_unlock(&rtc->lock);
+
+ return ret;
+}
+
+static int max8997_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rtc_data *rtc = dev_get_drvdata(dev);
+ u8 data[RTC_DATA_SIZE];
+ u8 val;
+ int i;
+ int ret;
+
+ mutex_lock(&rtc->lock);
+ ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+ RTC_DATA_SIZE, data);
+
+ if (ret < 0)
+ goto exit;
+
+ data_to_tm(data, &alrm->time);
+
+ alrm->enabled = 0;
+ for (i = 0; i < RTC_DATA_SIZE; i++)
+ if (data[i] & (1 << 7)) {
+ alrm->enabled = 1;
+ break;
+ }
+
+ alrm->pending = 0;
+ ret = max8997_read_reg(rtc->iodev->i2c, MAX8997_REG_STATUS1, &val);
+ if (val & (1 << 4)) /* "RTCA1" */
+ alrm->pending = 1;
+exit:
+ mutex_unlock(&rtc->lock);
+ return ret;
+}
+
+static int max8997_rtc_start_alarm(struct rtc_data *rtc)
+{
+ u8 data[RTC_DATA_SIZE];
+ int ret;
+
+ /* Should be locked before entering here */
+ if (!mutex_is_locked(&rtc->lock))
+ dev_warn(rtc->dev, "%s should have mutex locked.\n", __func__);
+
+ ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+ RTC_DATA_SIZE, data);
+ if (rtc < 0)
+ goto exit;
+
+ data[RTC_SEC] |= (1 << 7);
+ data[RTC_MIN] |= (1 << 7);
+ data[RTC_HOUR] |= (1 << 7);
+ data[RTC_WEEKDAY] &= ~(1 << 7);
+ if (data[RTC_MONTH] & 0x1f)
+ data[RTC_MONTH] |= (1 << 7);
+ if (data[RTC_YEAR] & 0x7f)
+ data[RTC_YEAR] |= (1 << 7);
+ if (data[RTC_MONTHDAY] & 0x3f)
+ data[RTC_MONTHDAY] |= (1 << 7);
+
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+ ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+ RTC_DATA_SIZE, data);
+ if (ret < 0)
+ goto exit;
+ if (rtc->delay)
+ msleep(20);
+
+exit:
+ return ret;
+}
+
+static int max8997_rtc_stop_alarm(struct rtc_data *rtc)
+{
+ u8 data[RTC_DATA_SIZE];
+ int ret;
+ int i;
+
+ /* Should be locked before entering here */
+ if (!mutex_is_locked(&rtc->lock))
+ dev_warn(rtc->dev, "%s should have mutex locked.\n", __func__);
+
+ ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+ RTC_DATA_SIZE, data);
+ if (rtc < 0)
+ goto exit;
+
+ for (i = 0; i < RTC_DATA_SIZE; i++)
+ data[i] &= ~(1 << 7);
+
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+ ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+ RTC_DATA_SIZE, data);
+ if (ret < 0)
+ goto exit;
+ if (rtc->delay)
+ msleep(20);
+
+exit:
+ return ret;
+}
+
+static int max8997_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rtc_data *rtc = dev_get_drvdata(dev);
+ u8 data[RTC_DATA_SIZE];
+ int ret = 0;
+
+ tm_to_data(&alrm->time, data);
+
+ mutex_lock(&rtc->lock);
+ ret = max8997_rtc_stop_alarm(rtc);
+ if (ret < 0)
+ goto exit;
+
+ if (alrm->enabled) {
+ data[RTC_SEC] |= (1 << 7);
+ data[RTC_MIN] |= (1 << 7);
+ data[RTC_HOUR] |= (1 << 7);
+ data[RTC_WEEKDAY] &= ~(1 << 7);
+ if (data[RTC_MONTH] & 0x1f)
+ data[RTC_MONTH] |= (1 << 7);
+ if (data[RTC_YEAR] & 0x7f)
+ data[RTC_YEAR] |= (1 << 7);
+ if (data[RTC_MONTHDAY] & 0x3f)
+ data[RTC_MONTHDAY] |= (1 << 7);
+ }
+
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+
+ ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+ RTC_DATA_SIZE, data);
+ if (ret < 0)
+ goto exit;
+ if (rtc->delay)
+ msleep(20);
+
+exit:
+ mutex_unlock(&rtc->lock);
+ return ret;
+}
+
+static int max8997_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct rtc_data *rtc = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&rtc->lock);
+ if (enabled)
+ ret = max8997_rtc_start_alarm(rtc);
+ else
+ ret = max8997_rtc_stop_alarm(rtc);
+ mutex_unlock(&rtc->lock);
+
+ return ret;
+}
+
+static irqreturn_t max8997_rtc_alarm_irq(int irq, void *data)
+{
+ struct rtc_data *rtc = data;
+
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops max8997_rtc_ops = {
+ .read_time = max8997_rtc_read_time,
+ .set_time = max8997_rtc_set_time,
+ .read_alarm = max8997_rtc_read_alarm,
+ .set_alarm = max8997_rtc_set_alarm,
+ .alarm_irq_enable = max8997_rtc_alarm_irq_enable,
+};
+
+static __devinit int rtc_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct rtc_data *rtc;
+ int ret = 0;
+ u8 val;
+
+ if (!pdata) {
+ dev_err(pdev->dev.parent, "No platform init data supplied.\n");
+ return -ENODEV;
+ }
+
+ rtc = kzalloc(sizeof(struct rtc_data), GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ mutex_init(&rtc->lock);
+ mutex_lock(&rtc->lock);
+ rtc->i2c = iodev->rtc;
+ rtc->dev = &pdev->dev;
+ rtc->iodev = iodev;
+ rtc->irq = iodev->irq_base + MAX8997_PMICIRQ_RTCA1;
+
+ if (pdata->delay)
+ rtc->delay = true;
+
+ platform_set_drvdata(pdev, rtc);
+
+ /* Allow update setting */
+ val = 0x03;
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRLMASK, val);
+ /* 24 hour | BCD */
+ val = (1 << 1) | (1 << 0);
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRL, val);
+ /* Allow RTC Update */
+ val = 0x03;
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, val);
+
+ if (pdata->wakeup)
+ device_init_wakeup(&pdev->dev, 1);
+
+ rtc->rtc_dev = rtc_device_register("max8997-rtc", &pdev->dev,
+ &max8997_rtc_ops, THIS_MODULE);
+ if (IS_ERR_OR_NULL(rtc->rtc_dev)) {
+ ret = PTR_ERR(rtc->rtc_dev);
+
+ dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+ if (ret == 0)
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = request_threaded_irq(rtc->irq, NULL, max8997_rtc_alarm_irq, 0,
+ "max8997-rtc-alarm1", rtc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to request alarm IRQ %d (%d)\n",
+ rtc->irq, ret);
+ goto err;
+ }
+
+ goto exit;
+err:
+ kfree(rtc);
+exit:
+ mutex_unlock(&rtc->lock);
+ return ret;
+}
+
+static __devexit int rtc_remove(struct platform_device *pdev)
+{
+ struct rtc_data *rtc = platform_get_drvdata(pdev);
+
+ rtc_device_unregister(rtc->rtc_dev);
+ free_irq(rtc->irq, rtc);
+ kfree(rtc);
+
+ return 0;
+}
+
+static const struct platform_device_id rtc_id[] = {
+ { "max8997-rtc", 0 },
+ { },
+};
+
+static struct platform_driver max8997_rtc_driver = {
+ .driver = {
+ .name = "max8997-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = rtc_probe,
+ .remove = __devexit_p(rtc_remove),
+ .id_table = rtc_id,
+};
+
+static int __init max8997_rtc_init(void)
+{
+ return platform_driver_register(&max8997_rtc_driver);
+}
+subsys_initcall(max8997_rtc_init);
+
+static void __exit max8997_rtc_cleanup(void)
+{
+ platform_driver_unregister(&max8997_rtc_driver);
+}
+module_exit(max8997_rtc_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 8997/8966 RTC Driver");
+MODULE_AUTHOR("MyungJoo Ham <[email protected]>");
+MODULE_LICENSE("GPL");
+
diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h
index 60931d0..83f39b7 100644
--- a/include/linux/mfd/max8997.h
+++ b/include/linux/mfd/max8997.h
@@ -109,7 +109,10 @@ struct max8997_platform_data {

/* MUIC: Not implemented */
/* HAPTIC: Not implemented */
- /* RTC: Not implemented */
+
+ /* ---- RTC ---- */
+ bool delay; /* The MAX8997 RTC write delay is requred. */
+
/* Flash: Not implemented */
/* Charger control: Not implemented */
};
--
1.7.1

2011-03-04 08:58:56

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH 2/2] MAX8997/8966 RTC Driver Initial Release

On Fri, 2011-03-04 at 16:48 +0900, MyungJoo Ham wrote:
> This patch adds support for RTC functionality for MAX8997/8966 chips.

just trivial comments here too.

> diff --git a/drivers/rtc/rtc-max8997.c b/drivers/rtc/rtc-max8997.c
[]
> +static void __maybe_unused dump(struct rtc_data *rtc)
> +{
> + int i, j;
> + char buf[256];
> + char *ptr;
> + u8 val;
> +
> + for (i = 0; i < 3; i++) {
> + ptr = buf;
> + ptr += sprintf(ptr, "[%2.2xh] ", i * 16);
> + for (j = 0; j < 16; j++) {
> + max8997_read_reg(rtc->i2c, i * 16 + j, &val);
> + ptr += sprintf(ptr, "%2.2x ", val);
> + }
> + pr_info("%s\n", buf);
> + }
> +}

It might be better to use a loop to read all the registers
and then print_hex_dump. Something like:

static void __maybe_unused dump(struct rtc_data *rtc)
{
int i;
u8 buf[48];

for (i = 0; i < 48; i++)
max8997_read_reg(rtc->i2c, i, &buf[i]);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
buf, sizeof(buf), false);
}


2011-03-04 08:59:12

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH 1/2] MAX8997/8966 MFD: Add IRQ control feature

On Fri, 2011-03-04 at 16:45 +0900, MyungJoo Ham wrote:
> This patch enables IRQ handling for MAX8997/8966 chips.

Just trivial comments.

> +++ b/drivers/mfd/max8997-irq.c
> +static u8 max8997_mask_reg[] = {

static const?

[]

> +static struct max8997_irq_data max8997_irqs[] = {

here too?

> + [MAX8997_PMICIRQ_PWRONR] = {
> + .group = PMIC_INT1,
> + .mask = 1 << 0,
> + },

I think the declaration style is a little hard to
read and verify. Maybe use a macro something like:

#define DECLARE_IRQ(val, _group, _mask) \
[ val ] = { .group = _group, .mask = _mask }

> + [MAX8997_PMICIRQ_PWRONF] = {
> + .group = PMIC_INT1,
> + .mask = 1 << 1,
> + },

So this becomes

DECLARE_IRQ(MAX8997_PMICIRQ_PWRONR, PMIC_INT1, 1 << 0),
DECLARE_IRQ(MAX8997_PMICIRQ_PWRONF, PMIC_INT1, 1 << 1),
etc...

[]

> +static struct irq_chip max8997_irq_chip = {

static const?

> +static irqreturn_t max8997_irq_thread(int irq, void *data)
> +{
> + struct max8997_dev *max8997 = data;
> + u8 irq_reg[MAX8997_IRQ_GROUP_NR];
[]
> + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++)
> + irq_reg[i] = 0;

Maybe use a memset or change the declaration to
u8 irq_reg[MAX8997_IRQ_GROUP_NR] = {};

2011-03-05 11:31:23

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 1/2] MAX8997/8966 MFD Driver Initial Release (PMIC+RTC+MUIC+Haptic+...)

On Fri, Mar 04, 2011 at 03:50:26PM +0900, MyungJoo Ham wrote:
> MAX8997/MAX8966 chip is a multi-function device with I2C bussses. The
> chip includes PMIC, RTC, Fuel Gauge, MUIC, Haptic, Flash control, and
> Battery (charging) control.

Reviewed-by: Mark Brown <[email protected]>

2011-03-05 12:02:42

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] MAX8997/8966 PMIC Regulator Driver Initial Release

On Fri, Mar 04, 2011 at 03:50:27PM +0900, MyungJoo Ham wrote:

Overall this looks very good.

> + if (ldo == MAX8997_ESAFEOUT1 || ldo == MAX8997_ESAFEOUT2) {
> + switch (selector) {
> + case 0:
> + return 4850000;

...

> + if (ldo == MAX8997_CHARGER_CV) {

For these special cases it'd be cleaner to just have separate functions
rather than conditional code in the implementation used by the majority.

> + switch (ldo) {
> + case MAX8997_LDO1 ... MAX8997_LDO21:
> + *reg = MAX8997_REG_LDO1CTRL + (ldo - MAX8997_LDO1);
> + *mask = 0xC0;
> + *pattern = 0xC0;
> + break;
> + case MAX8997_BUCK1:

The ldo variable is slightly misnamed, then? :)

[get_voltage]
> +
> + ret = max8997_list_voltage(rdev, val);
> +
> + return ret;

If you implement get_voltage_sel() you can just return the selector
directly.

> +/*
> + * Assess the damage on the voltage setting of BUCK1,2,5 by the change.
> + */
> +static int max8997_assess_side_effect(struct regulator_dev *rdev,
> + u8 new_val, int *best)

Some more detail in the comment would be very helpful here - what sort
of damage and what sort of change?

> + if (gpio_dvs_mode) {
> + desc = reg_voltage_map[ldo];
> + new_val = max8997_get_voltage_proper_val(desc, min_vol,
> + max_vol);
> + if (new_val < 0)
> + return new_val;
> +
> + damage = max8997_assess_side_effect(rdev, new_val, &new_idx);
> +
> + if (damage < 0)
> + return damage;
> +
> + max8997->buck125_gpioindex = new_idx;
> + max8997_set_gpio(max8997);
> + *selector = new_val;
> +
> + return 0;
> + }
> +
> + return max8997_set_voltage_ldo(rdev, min_uV, max_uV, selector);

Putting this in the else clause would be a little clearer.

> +/* For SAFEOUT1 and SAFEOUT2 */
> +static int max8997_set_voltage_safeout(struct regulator_dev *rdev,
> + int min_uV, int max_uV, unsigned *selector)
> +{
> + struct max8997_data *max8997 = rdev_get_drvdata(rdev);
> + struct i2c_client *i2c = max8997->iodev->i2c;
> + int ldo = max8997_get_ldo(rdev);
> + int reg, shift = 0, mask, ret;
> + int i = 0;
> + u8 val;
> +
> + if (ldo != MAX8997_ESAFEOUT1 && ldo != MAX8997_ESAFEOUT2)
> + return -EINVAL;
> +
> + for (i = 0; i < ARRAY_SIZE(safeoutvolt); i++) {
> + if (min_uV <= safeoutvolt[i] &&
> + max_uV >= safeoutvolt[i])
> + break;

If you implement this as a set_voltage_sel() operation it'd simplify
your code by doing this sort of lookup for you, including bounds
checking and so on.

> +static int max8997_reg_enable_suspend(struct regulator_dev *rdev)
> +{
> + return 0;
> +}
> +

This looks odd, especially since you have a disable operation?

> + if (ldo == MAX8997_LDO1 ||
> + ldo == MAX8997_LDO10 ||
> + ldo == MAX8997_LDO21) {
> + pr_info("Conditional Power-Off for %s\n", rdev->desc->name);
> + return max8997_update_reg(i2c, reg, 0x40, mask);
> + }
> +
> + pr_info("Full Power-Off for %s (%xh -> %xh)\n", rdev->desc->name,
> + max8997->saved_states[ldo] & mask, (~pattern) & mask);

dev_info().

> +static int max8997_resume(struct device *dev)
> +{
> + struct platform_device *pdev = container_of(dev,
> + struct platform_device, dev);
> + struct max8997_data *max8997 = platform_get_drvdata(pdev);
> + struct i2c_client *i2c = max8997->iodev->i2c;
> + struct regulator_dev *rdev;
> + int ret, reg, mask, pattern;
> + int i;
> + u8 val;
> +
> + /* Show Error/Warning Messages for Inconsistency */
> + for (i = 0; i < MAX8997_REG_MAX; i++) {
> + if (max8997->saved_rdev[i]) {

We should put this into the regulator core rather than doing it in
drivers - it's not really driver specific and other regulators that
can't preserve state over suspend and resume will need it.

2011-03-05 12:06:51

by Mark Brown

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCH 2/2] MAX8997/8966 RTC Driver Initial Release

On Fri, Mar 04, 2011 at 04:48:44PM +0900, MyungJoo Ham wrote:

> +static void tm_to_data(struct rtc_time *tm, u8 *data)
> +{
> + data[RTC_SEC] = bin2bcd(tm->tm_sec) & 0x7f;
> + data[RTC_MIN] = bin2bcd(tm->tm_min) & 0x7f;
> + data[RTC_HOUR] = bin2bcd(tm->tm_hour) & 0x3f;
> + data[RTC_WEEKDAY] = wday_kernel_to_8997(tm->tm_wday) & 0x7f;
> + data[RTC_MONTH] = bin2bcd(tm->tm_mon) & 0x1f;
> + data[RTC_YEAR] = bin2bcd((tm->tm_year > 100) ? (tm->tm_year - 100) : 0)
> + & 0x7f;
> + data[RTC_MONTHDAY] = bin2bcd(tm->tm_mday) & 0x3f;
> +
> + if (tm->tm_year < 100) /* Prior 2000 */
> + pr_warn("MAX8997 RTC cannot handle the year %d. "
> + "Assume it's 2000.\n", 1900 + tm->tm_year);

It seems like an attempt to set an unsupported year should return an
error rather than just displaying a warning (though the warning is
useful so the user can figure out what went wrong).

> +MODULE_DESCRIPTION("MAXIM 8997/8966 RTC Driver");
> +MODULE_AUTHOR("MyungJoo Ham <[email protected]>");
> +MODULE_LICENSE("GPL");

A MODULE_ALIAS() would allow the driver to be automatically loaded when
built as a module.

2011-03-08 01:50:10

by MyungJoo Ham

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] MAX8997/8966 PMIC Regulator Driver Initial Release

On Sat, Mar 5, 2011 at 9:03 PM, Mark Brown
<[email protected]> wrote:
> On Fri, Mar 04, 2011 at 03:50:27PM +0900, MyungJoo Ham wrote:

[]

>
>> +static int max8997_reg_enable_suspend(struct regulator_dev *rdev)
>> +{
>> +     return 0;
>> +}
>> +
>
> This looks odd, especially since you have a disable operation?

The intention is to keep it enabled if it was enabled before entering
sleep and not to enable if it has not been using while the system is
running. Probably, we need three states for suspend-prepare for
regulators: disable, enable, keep_state?

>

[]

>
>> +static int max8997_resume(struct device *dev)
>> +{
>> +     struct platform_device *pdev = container_of(dev,
>> +                     struct platform_device, dev);
>> +     struct max8997_data *max8997 = platform_get_drvdata(pdev);
>> +     struct i2c_client *i2c = max8997->iodev->i2c;
>> +     struct regulator_dev *rdev;
>> +     int ret, reg, mask, pattern;
>> +     int i;
>> +     u8 val;
>> +
>> +     /* Show Error/Warning Messages for Inconsistency */
>> +     for (i = 0; i < MAX8997_REG_MAX; i++) {
>> +             if (max8997->saved_rdev[i]) {
>
> We should put this into the regulator core rather than doing it in
> drivers - it's not really driver specific and other regulators that
> can't preserve state over suspend and resume will need it.
>

This part was sort of "debug and test" part of the driver and removed
in the recent version of the patch.
However, the regulator core depends on calling
regulator_suspend_prepare by machine/architecture pm code and the core
does not have resume-related support. For that matter, I've made and
been using "regulator_suspend_finish" locally. I will post that patch
soon.



Thanks.


- MyungJoo
--
MyungJoo Ham (함명주), Ph.D.
Mobile Software Platform Lab,
Digital Media and Communications (DMC) Business
Samsung Electronics
cell: 82-10-6714-2858

2011-03-08 12:41:55

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] MAX8997/8966 PMIC Regulator Driver Initial Release

On Tue, Mar 08, 2011 at 10:50:04AM +0900, MyungJoo Ham wrote:
> On Sat, Mar 5, 2011 at 9:03 PM, Mark Brown

> > This looks odd, especially since you have a disable operation?

> The intention is to keep it enabled if it was enabled before entering
> sleep and not to enable if it has not been using while the system is
> running. Probably, we need three states for suspend-prepare for
> regulators: disable, enable, keep_state?

No, that's not what the suspend mode settings are doing. Many
regulators have separate state configuration for use when the system is
suspended, the purpose of suspend_prepare() is to configure that. They
are orthogonal to the state seen when the system is running and it is
expected that the previously configured state will be automatically
recovered when exiting suspend.

The reason we set this stuff immediately before suspend is that Linux
has multiple suspend types so we need to adjust the configuration to
reflect the suspend type that's been chosen.

2011-03-14 09:49:17

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v2 0/2] MAX8997/8966 MFD (includig PMIC) Initial Release

Hi Myung,

On Fri, Mar 04, 2011 at 03:50:25PM +0900, MyungJoo Ham wrote:
> MAX8997/8966 has
> - PMIC
> - RTC
> - MUIC (usb switch)
> - Flash control
> - Haptic control
> - Fuel Gauge (MAX17042 compatible)
> - Battery charger control
>
> This patch adds an initial driver for Maxim Semiconductor 8997/8966's
> PMIC function.
I applied patch #1 from thsi serie, thanks a lot.
Please address Mark's comments for patch #2 and I'll take it as well.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2011-03-14 10:11:24

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH 1/2] MAX8997/8966 MFD: Add IRQ control feature

Hi MyungJoo,

On Fri, Mar 04, 2011 at 04:45:47PM +0900, MyungJoo Ham wrote:
> This patch enables IRQ handling for MAX8997/8966 chips.
>
> Signed-off-by: MyungJoo Ham <[email protected]>
> Signed-off-by: Kyungmin Park <[email protected]>
Joe's comments make sense. Also, here are my comments:

> +static irqreturn_t max8997_irq_thread(int irq, void *data)
> +{
> + struct max8997_dev *max8997 = data;
> + u8 irq_reg[MAX8997_IRQ_GROUP_NR];
> + u8 irq_src;
> + int ret;
> + int i;
> +
> + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_INTSRC, &irq_src);
> + if (ret < 0) {
> + dev_err(max8997->dev, "Failed to read interrupt source: %d\n",
> + ret);
> + return IRQ_NONE;
> + }
> +
> + for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++)
> + irq_reg[i] = 0;
> +
> + if (irq_src & (1 << 1)) {
All the (1 << n) parts could be nicely replaced with relevant #define. That
would make your code more readable.



> + /* PMIC INT1 ~ INT4 */
> + max8997_bulk_read(max8997->i2c, MAX8997_REG_INT1, 4,
> + &irq_reg[PMIC_INT1]);
> + }
> + if (irq_src & (1 << 2)) {
> + /* FUEL GAUGE Interrupt */
> + /* Ignored */
> + irq_reg[FUEL_GAUGE] = 0;
> + }
> + if (irq_src & (1 << 3)) {
> + /* MUIC INT1 ~ INT3 */
> + max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, 3,
> + &irq_reg[MUIC_INT1]);
> + }
> + if (irq_src & (1 << 4)) {
> + /* GPIO Interrupt */
> + u8 gpio_info[12];
> + int gpio;
> +
> + irq_reg[GPIO_LOW] = 0;
> + irq_reg[GPIO_HI] = 0;
> +
> + max8997_bulk_read(max8997->i2c, MAX8997_REG_GPIOCNTL1, 12,
> + gpio_info);
> + for (gpio = 0; gpio < 8; gpio++) {
> + u8 val = gpio_info[gpio] & 0x34;
> + if (((val & 0x30) == 0x30) ||
> + (val == 0x10) ||
> + (val == 0x24))
There are a lot of magic constants here, and I'd love to see that replaced as
well with some more descriptive macros.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2011-03-14 10:37:51

by MyungJoo Ham

[permalink] [raw]
Subject: Re: [PATCH 1/2] MAX8997/8966 MFD: Add IRQ control feature

Hello Samuel,

On Mon, Mar 14, 2011 at 7:11 PM, Samuel Ortiz <[email protected]> wrote:
>
> Hi MyungJoo,
>
> On Fri, Mar 04, 2011 at 04:45:47PM +0900, MyungJoo Ham wrote:
> > This patch enables IRQ handling for MAX8997/8966 chips.
> >
> > Signed-off-by: MyungJoo Ham <[email protected]>
> > Signed-off-by: Kyungmin Park <[email protected]>
> Joe's comments make sense. Also, here are my comments:
>

Yes, in the patch series of MAX8997/8966 version 5, his comments are
applied at "[PATCH v5 3/4] MAX8997/8966 MFD: Add IRQ control feature".

[]

> All the (1 << n) parts could be nicely replaced with relevant #define. That
> would make your code more readable.

[]

> There are a lot of magic constants here, and I'd love to see that replaced as
> well with some more descriptive macros.
>

Thanks. I will post "PATCH v6" with some macros for the register bits.


> Cheers,
> Samuel.
>
> --
> Intel Open Source Technology Centre
> http://oss.intel.com/


Cheers!
MyungJoo
--
MyungJoo Ham (함명주), Ph.D.
Mobile Software Platform Lab,
Digital Media and Communications (DMC) Business
Samsung Electronics
cell: 82-10-6714-2858