2011-03-16 02:02:21

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH v6 0/2] MAX8997/8966 MFD: Add RTC and IRQ

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

This patch series is meant to add an initial driver for Maxim
Semiconductor 8997/8966's PMIC function.

However, as the first two parts (MFD and PMIC) are applied, we have
removed the frist two parts from the patch series.

The changes from previous patch
v6
with comments from Samuel
- IRQ register bit values use macros
corrected GPIO interrupt handling
RTC configuration bit values use macros
v5
with comments from Mark
- Style updated for PMIC
- Revised side-effect calculation
In the range given, choose a setting without side-effect
then, choose a setting with the least side-effect if allowed.
Added comments on FUEL-GAUGE issue in IRQ

v4
with comments from Randy:
- Spelling errors in Kconfig
with comments from Minsung Kim:
- RTC month bit error.

v3
with comments from Joe:
- Style updated for IRQ and RTC
with comments from Mark
- Style updated for RTC and PMIC
Merged support for RTC and IRQ

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: Add IRQ control feature
MAX8997/8966 RTC Driver Initial Release

drivers/mfd/Makefile | 2 +-
drivers/mfd/max8997-irq.c | 377 +++++++++++++++++++++++++++
drivers/rtc/Kconfig | 10 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-max8997.c | 476 +++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997-private.h | 21 ++
include/linux/mfd/max8997.h | 12 +-
7 files changed, 895 insertions(+), 4 deletions(-)
create mode 100644 drivers/mfd/max8997-irq.c
create mode 100644 drivers/rtc/rtc-max8997.c


2011-03-16 02:02:15

by MyungJoo Ham

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

This patch enables IRQ handling for MAX8997/8966 chips.

Please note that Fuel-Gauge-related IRQs are not implemented in this
initial release. The fuel gauge module in MAX8997 is identical to
MAX17042, which is already in Linux kernel. In order to use the
already-existing MAX17042 driver for fuel gauge module in MAX8997, the
main interrupt handler of MAX8997 should relay related interrupts to
MAX17042 driver. However, in order to do this, we need to modify
MAX17042 driver as well because MAX17042 driver does not have any
interrupt handlers for now. We are not going to implement this in this
initial release as it is not crucial in basic operations of MAX8997.

The IRQ handling of MAX8997/8966 MFD relays IRQ handlers to
PMIC, MUIC, GPIO, and FLASH. Note that PMIC-IRQ handles RTC and Charger
related inetrrupts as well.

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>
---
drivers/mfd/Makefile | 2 +-
drivers/mfd/max8997-irq.c | 377 +++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997-private.h | 21 ++
include/linux/mfd/max8997.h | 7 +-
4 files changed, 404 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..c52f29f
--- /dev/null
+++ b/drivers/mfd/max8997-irq.c
@@ -0,0 +1,377 @@
+/*
+ * 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 const 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;
+};
+
+#define DECLARE_IRQ(idx, _group, _mask) \
+ [(idx)] = { .group = (_group), .mask = (_mask) }
+static const struct max8997_irq_data max8997_irqs[] = {
+ DECLARE_IRQ(MAX8997_PMICIRQ_PWRONR, PMIC_INT1, 1 << 0),
+ DECLARE_IRQ(MAX8997_PMICIRQ_PWRONF, PMIC_INT1, 1 << 1),
+ DECLARE_IRQ(MAX8997_PMICIRQ_PWRON1SEC, PMIC_INT1, 1 << 3),
+ DECLARE_IRQ(MAX8997_PMICIRQ_JIGONR, PMIC_INT1, 1 << 4),
+ DECLARE_IRQ(MAX8997_PMICIRQ_JIGONF, PMIC_INT1, 1 << 5),
+ DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT2, PMIC_INT1, 1 << 6),
+ DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT1, PMIC_INT1, 1 << 7),
+
+ DECLARE_IRQ(MAX8997_PMICIRQ_JIGR, PMIC_INT2, 1 << 0),
+ DECLARE_IRQ(MAX8997_PMICIRQ_JIGF, PMIC_INT2, 1 << 1),
+ DECLARE_IRQ(MAX8997_PMICIRQ_MR, PMIC_INT2, 1 << 2),
+ DECLARE_IRQ(MAX8997_PMICIRQ_DVS1OK, PMIC_INT2, 1 << 3),
+ DECLARE_IRQ(MAX8997_PMICIRQ_DVS2OK, PMIC_INT2, 1 << 4),
+ DECLARE_IRQ(MAX8997_PMICIRQ_DVS3OK, PMIC_INT2, 1 << 5),
+ DECLARE_IRQ(MAX8997_PMICIRQ_DVS4OK, PMIC_INT2, 1 << 6),
+
+ DECLARE_IRQ(MAX8997_PMICIRQ_CHGINS, PMIC_INT3, 1 << 0),
+ DECLARE_IRQ(MAX8997_PMICIRQ_CHGRM, PMIC_INT3, 1 << 1),
+ DECLARE_IRQ(MAX8997_PMICIRQ_DCINOVP, PMIC_INT3, 1 << 2),
+ DECLARE_IRQ(MAX8997_PMICIRQ_TOPOFFR, PMIC_INT3, 1 << 3),
+ DECLARE_IRQ(MAX8997_PMICIRQ_CHGRSTF, PMIC_INT3, 1 << 5),
+ DECLARE_IRQ(MAX8997_PMICIRQ_MBCHGTMEXPD, PMIC_INT3, 1 << 7),
+
+ DECLARE_IRQ(MAX8997_PMICIRQ_RTC60S, PMIC_INT4, 1 << 0),
+ DECLARE_IRQ(MAX8997_PMICIRQ_RTCA1, PMIC_INT4, 1 << 1),
+ DECLARE_IRQ(MAX8997_PMICIRQ_RTCA2, PMIC_INT4, 1 << 2),
+ DECLARE_IRQ(MAX8997_PMICIRQ_SMPL_INT, PMIC_INT4, 1 << 3),
+ DECLARE_IRQ(MAX8997_PMICIRQ_RTC1S, PMIC_INT4, 1 << 4),
+ DECLARE_IRQ(MAX8997_PMICIRQ_WTSR, PMIC_INT4, 1 << 5),
+
+ DECLARE_IRQ(MAX8997_MUICIRQ_ADCError, MUIC_INT1, 1 << 2),
+ DECLARE_IRQ(MAX8997_MUICIRQ_ADCLow, MUIC_INT1, 1 << 1),
+ DECLARE_IRQ(MAX8997_MUICIRQ_ADC, MUIC_INT1, 1 << 0),
+
+ DECLARE_IRQ(MAX8997_MUICIRQ_VBVolt, MUIC_INT2, 1 << 4),
+ DECLARE_IRQ(MAX8997_MUICIRQ_DBChg, MUIC_INT2, 1 << 3),
+ DECLARE_IRQ(MAX8997_MUICIRQ_DCDTmr, MUIC_INT2, 1 << 2),
+ DECLARE_IRQ(MAX8997_MUICIRQ_ChgDetRun, MUIC_INT2, 1 << 1),
+ DECLARE_IRQ(MAX8997_MUICIRQ_ChgTyp, MUIC_INT2, 1 << 0),
+
+ DECLARE_IRQ(MAX8997_MUICIRQ_OVP, MUIC_INT3, 1 << 2),
+};
+
+static const 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);
+ const 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);
+ const 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,
+};
+
+#define MAX8997_IRQSRC_PMIC (1 << 1)
+#define MAX8997_IRQSRC_FUELGAUGE (1 << 2)
+#define MAX8997_IRQSRC_MUIC (1 << 3)
+#define MAX8997_IRQSRC_GPIO (1 << 4)
+#define MAX8997_IRQSRC_FLASH (1 << 5)
+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;
+ }
+
+ if (irq_src & MAX8997_IRQSRC_PMIC) {
+ /* PMIC INT1 ~ INT4 */
+ max8997_bulk_read(max8997->i2c, MAX8997_REG_INT1, 4,
+ &irq_reg[PMIC_INT1]);
+ }
+ if (irq_src & MAX8997_IRQSRC_FUELGAUGE) {
+ /*
+ * TODO: FUEL GAUGE
+ *
+ * This is to be supported by Max17042 driver. When
+ * an interrupt incurs here, it should be relayed to a
+ * Max17042 device that is connected (probably by
+ * platform-data). However, we do not have interrupt
+ * handling in Max17042 driver currently. The Max17042 IRQ
+ * driver should be ready to be used as a stand-alone device and
+ * a Max8997-dependent device. Because it is not ready in
+ * Max17042-side and it is not too critical in operating
+ * Max8997, we do not implement this in initial releases.
+ */
+ irq_reg[FUEL_GAUGE] = 0;
+ }
+ if (irq_src & MAX8997_IRQSRC_MUIC) {
+ /* MUIC INT1 ~ INT3 */
+ max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, 3,
+ &irq_reg[MUIC_INT1]);
+ }
+ if (irq_src & MAX8997_IRQSRC_GPIO) {
+ /* GPIO Interrupt */
+ u8 gpio_info[MAX8997_NUM_GPIO];
+
+ irq_reg[GPIO_LOW] = 0;
+ irq_reg[GPIO_HI] = 0;
+
+ max8997_bulk_read(max8997->i2c, MAX8997_REG_GPIOCNTL1,
+ MAX8997_NUM_GPIO, gpio_info);
+ for (i = 0; i < MAX8997_NUM_GPIO; i++) {
+ bool interrupt = false;
+
+ switch (gpio_info[i] & MAX8997_GPIO_INT_MASK) {
+ case MAX8997_GPIO_INT_BOTH:
+ if (max8997->gpio_status[i] != gpio_info[i])
+ interrupt = true;
+ break;
+ case MAX8997_GPIO_INT_RISE:
+ if ((max8997->gpio_status[i] != gpio_info[i]) &&
+ (gpio_info[i] & MAX8997_GPIO_DATA_MASK))
+ interrupt = true;
+ break;
+ case MAX8997_GPIO_INT_FALL:
+ if ((max8997->gpio_status[i] != gpio_info[i]) &&
+ !(gpio_info[i] & MAX8997_GPIO_DATA_MASK))
+ interrupt = true;
+ break;
+ default:
+ break;
+ }
+
+ if (interrupt) {
+ if (i < 8)
+ irq_reg[GPIO_LOW] |= (1 << i);
+ else
+ irq_reg[GPIO_HI] |= (1 << (i - 8));
+ }
+
+ }
+ }
+ if (irq_src & MAX8997_IRQSRC_FLASH) {
+ /* 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;
+ u8 val;
+
+ 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);
+ }
+
+ for (i = 0; i < MAX8997_NUM_GPIO; i++) {
+ max8997->gpio_status[i] = (max8997_read_reg(max8997->i2c,
+ MAX8997_REG_GPIOCNTL1 + i,
+ &val)
+ & MAX8997_GPIO_DATA_MASK) ?
+ true : false;
+ }
+
+ /* 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..69d1010 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,
@@ -313,6 +315,7 @@ enum max8997_irq {
#define MAX8997_REG_BUCK2DVS(x) (MAX8997_REG_BUCK2DVS1 + (x) - 1)
#define MAX8997_REG_BUCK5DVS(x) (MAX8997_REG_BUCK5DVS1 + (x) - 1)

+#define MAX8997_NUM_GPIO 12
struct max8997_dev {
struct device *dev;
struct i2c_client *i2c; /* 0xcc / PMIC, Battery Control, and FLASH */
@@ -324,11 +327,19 @@ 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 +
MAX8997_HAPTIC_REG_END];
+
+ bool gpio_status[MAX8997_NUM_GPIO];
};

enum max8997_types {
@@ -336,6 +347,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);
@@ -344,4 +359,10 @@ 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);

+#define MAX8997_GPIO_INT_BOTH (0x3 << 4)
+#define MAX8997_GPIO_INT_RISE (0x2 << 4)
+#define MAX8997_GPIO_INT_FALL (0x1 << 4)
+
+#define MAX8997_GPIO_INT_MASK (0x3 << 4)
+#define MAX8997_GPIO_DATA_MASK (0x1 << 2)
#endif /* __LINUX_MFD_MAX8997_PRIV_H */
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-16 02:04:50

by MyungJoo Ham

[permalink] [raw]
Subject: [PATCH v6 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.

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 | 476 +++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/max8997.h | 5 +-
4 files changed, 491 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..b1a4b66
--- /dev/null
+++ b/drivers/rtc/rtc-max8997.c
@@ -0,0 +1,476 @@
+/*
+ * 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/printk.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>
+
+/* Allow RTC Update */
+#define MAX8997_RTC_UPDATE1_ENABLE_UPDATE (1 << 0)
+/*
+ * Set Interrupt Mode (about how the interrupt is cleared.)
+ * Set the bit in order to let inetrrupt be cleared with register read
+ * Unset the bit in order to let it be cleared by writing 0.
+ */
+#define MAX8997_RTC_UPDATE1_INT_READ_CLEAR (1 << 1)
+
+/* Mode between BCD(1) and Binary(0) */
+#define MAX8997_RTC_CTRL_BCD (1 << 0)
+/* Mode between 24hrs(1) and 12hrs(0) */
+#define MAX8997_RTC_CTRL_Mode24_12 (1 << 1)
+
+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;
+ 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);
+}
+
+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) - 1;
+ 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 int 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 + 1) & 0x1f;
+ data[RTC_YEAR] = bin2bcd((tm->tm_year > 100) ? (tm->tm_year - 100) : 0)
+ & 0x7f;
+ data[RTC_MONTHDAY] = bin2bcd(tm->tm_mday) & 0x3f;
+
+ /* MAX8997 RTC cannot deal with years prior to 2000 */
+ if (tm->tm_year < 100) {
+ pr_err("MAX8997 RTC cannot handle the year %d. "
+ "Assume it's 2000.\n", 1900 + tm->tm_year);
+ return -EINVAL;
+ } else {
+ return 0;
+ }
+}
+
+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;
+
+ ret = tm_to_data(tm, data);
+ if (ret)
+ return ret;
+
+ mutex_lock(&rtc->lock);
+
+ /* Allow RTC Update */
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1,
+ MAX8997_RTC_UPDATE1_ENABLE_UPDATE |
+ MAX8997_RTC_UPDATE1_INT_READ_CLEAR);
+
+ 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);
+
+ /* Allow RTC Update */
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1,
+ MAX8997_RTC_UPDATE1_ENABLE_UPDATE |
+ MAX8997_RTC_UPDATE1_INT_READ_CLEAR);
+
+ 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);
+
+ /* Allow RTC Update */
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1,
+ MAX8997_RTC_UPDATE1_ENABLE_UPDATE |
+ MAX8997_RTC_UPDATE1_INT_READ_CLEAR);
+
+ 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;
+
+ ret = tm_to_data(&alrm->time, data);
+ if (ret)
+ return ret;
+
+ 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);
+ }
+
+ /* Allow RTC Update */
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1,
+ MAX8997_RTC_UPDATE1_ENABLE_UPDATE |
+ MAX8997_RTC_UPDATE1_INT_READ_CLEAR);
+
+ 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);
+
+ val = MAX8997_RTC_CTRL_BCD |
+ MAX8997_RTC_CTRL_Mode24_12;
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRLMASK, val);
+
+ val = MAX8997_RTC_CTRL_BCD |
+ MAX8997_RTC_CTRL_Mode24_12;
+ max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRL, val);
+
+ /* Allow RTC Update */
+ val = MAX8997_RTC_UPDATE1_ENABLE_UPDATE |
+ MAX8997_RTC_UPDATE1_INT_READ_CLEAR;
+ 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");
+MODULE_ALIAS("platform:max8997-rtc");
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-17 23:57:18

by Samuel Ortiz

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

Hi MyungJoo,

On Wed, Mar 16, 2011 at 10:39:10AM +0900, MyungJoo Ham wrote:
> This patch enables IRQ handling for MAX8997/8966 chips.
>
> Please note that Fuel-Gauge-related IRQs are not implemented in this
> initial release. The fuel gauge module in MAX8997 is identical to
> MAX17042, which is already in Linux kernel. In order to use the
> already-existing MAX17042 driver for fuel gauge module in MAX8997, the
> main interrupt handler of MAX8997 should relay related interrupts to
> MAX17042 driver. However, in order to do this, we need to modify
> MAX17042 driver as well because MAX17042 driver does not have any
> interrupt handlers for now. We are not going to implement this in this
> initial release as it is not crucial in basic operations of MAX8997.
>
> The IRQ handling of MAX8997/8966 MFD relays IRQ handlers to
> PMIC, MUIC, GPIO, and FLASH. Note that PMIC-IRQ handles RTC and Charger
> related inetrrupts as well.
Thanks a lot, patch applied.

Cheers,
Samuel.

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

2011-03-17 23:58:15

by Samuel Ortiz

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

Hi MyungJoo,

On Wed, Mar 16, 2011 at 10:39:11AM +0900, MyungJoo Ham wrote:
> 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.
I'll wait for Alessandro's ACK before pushing this one.

Cheers,
Samuel.

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