summaryrefslogtreecommitdiff
path: root/drivers/mfd/max8907c-irq.c
diff options
context:
space:
mode:
authorTom Cherry <tcherry@nvidia.com>2011-01-21 14:44:49 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:42:58 -0800
commitd10ab1cfc666f68dbed0b277bb871f055f9cd88a (patch)
tree042970bc49f4d568eec77cb3ea7d62260c2fb4a5 /drivers/mfd/max8907c-irq.c
parentac19e4fde4b3928ae4b666f47a1d46aa875f68ca (diff)
max8907c MFD driver: add second i2c bus and irq handling
Clean up portions of max8907c.c driver Add capability for bulk i2c read and write Add capability for second i2c bus for rtc Add capability for irq handling for rtc and battery charger Original-Change-Id: I6c2c2c42591aee766635a2e32a7404bb2591f4a7 Reviewed-on: http://git-master/r/16613 Tested-by: Thomas Cherry <tcherry@nvidia.com> Reviewed-by: Sachin Nikam <snikam@nvidia.com> Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> Rebase-Id: Rb2215121adf39249e1c0f5fe88534179077cae1f
Diffstat (limited to 'drivers/mfd/max8907c-irq.c')
-rw-r--r--drivers/mfd/max8907c-irq.c359
1 files changed, 359 insertions, 0 deletions
diff --git a/drivers/mfd/max8907c-irq.c b/drivers/mfd/max8907c-irq.c
new file mode 100644
index 000000000000..e9773c0fe00e
--- /dev/null
+++ b/drivers/mfd/max8907c-irq.c
@@ -0,0 +1,359 @@
+/*
+ * Battery driver for Maxim MAX8907C
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ * Based on driver/mfd/max8925-core.c, Copyright (C) 2009-2010 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max8907c.h>
+
+struct max8907c_irq_data {
+ int reg;
+ int mask_reg;
+ int enable; /* enable or not */
+ int offs; /* bit offset in mask register */
+ bool is_rtc;
+};
+
+static struct max8907c_irq_data max8907c_irqs[] = {
+ [MAX8907C_IRQ_VCHG_DC_OVP] = {
+ .reg = MAX8907C_REG_CHG_IRQ1,
+ .mask_reg = MAX8907C_REG_CHG_IRQ1_MASK,
+ .offs = 1 << 0,
+ },
+ [MAX8907C_IRQ_VCHG_DC_F] = {
+ .reg = MAX8907C_REG_CHG_IRQ1,
+ .mask_reg = MAX8907C_REG_CHG_IRQ1_MASK,
+ .offs = 1 << 1,
+ },
+ [MAX8907C_IRQ_VCHG_DC_R] = {
+ .reg = MAX8907C_REG_CHG_IRQ1,
+ .mask_reg = MAX8907C_REG_CHG_IRQ1_MASK,
+ .offs = 1 << 2,
+ },
+ [MAX8907C_IRQ_VCHG_THM_OK_R] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 0,
+ },
+ [MAX8907C_IRQ_VCHG_THM_OK_F] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 1,
+ },
+ [MAX8907C_IRQ_VCHG_MBATTLOW_F] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 2,
+ },
+ [MAX8907C_IRQ_VCHG_MBATTLOW_R] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 3,
+ },
+ [MAX8907C_IRQ_VCHG_RST] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 4,
+ },
+ [MAX8907C_IRQ_VCHG_DONE] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 5,
+ },
+ [MAX8907C_IRQ_VCHG_TOPOFF] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 6,
+ },
+ [MAX8907C_IRQ_VCHG_TMR_FAULT] = {
+ .reg = MAX8907C_REG_CHG_IRQ2,
+ .mask_reg = MAX8907C_REG_CHG_IRQ2_MASK,
+ .offs = 1 << 7,
+ },
+ [MAX8907C_IRQ_GPM_RSTIN] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 0,
+ },
+ [MAX8907C_IRQ_GPM_MPL] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 1,
+ },
+ [MAX8907C_IRQ_GPM_SW_3SEC] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 2,
+ },
+ [MAX8907C_IRQ_GPM_EXTON_F] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 3,
+ },
+ [MAX8907C_IRQ_GPM_EXTON_R] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 4,
+ },
+ [MAX8907C_IRQ_GPM_SW_1SEC] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 5,
+ },
+ [MAX8907C_IRQ_GPM_SW_F] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 6,
+ },
+ [MAX8907C_IRQ_GPM_SW_R] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ1,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ .offs = 1 << 7,
+ },
+ [MAX8907C_IRQ_GPM_SYSCKEN_F] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ2,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ2_MASK,
+ .offs = 1 << 0,
+ },
+ [MAX8907C_IRQ_GPM_SYSCKEN_R] = {
+ .reg = MAX8907C_REG_ON_OFF_IRQ2,
+ .mask_reg = MAX8907C_REG_ON_OFF_IRQ2_MASK,
+ .offs = 1 << 1,
+ },
+ [MAX8907C_IRQ_RTC_ALARM1] = {
+ .reg = MAX8907C_REG_RTC_IRQ,
+ .mask_reg = MAX8907C_REG_RTC_IRQ_MASK,
+ .offs = 1 << 2,
+ .is_rtc = true,
+ },
+ [MAX8907C_IRQ_RTC_ALARM0] = {
+ .reg = MAX8907C_REG_RTC_IRQ,
+ .mask_reg = MAX8907C_REG_RTC_IRQ_MASK,
+ .offs = 1 << 3,
+ .is_rtc = true,
+ },
+};
+
+static inline struct max8907c_irq_data *irq_to_max8907c(struct max8907c *chip,
+ int irq)
+{
+ return &max8907c_irqs[irq - chip->irq_base];
+}
+
+static irqreturn_t max8907c_irq(int irq, void *data)
+{
+ struct max8907c *chip = data;
+ struct max8907c_irq_data *irq_data;
+ struct i2c_client *i2c;
+ int read_reg = -1, value = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(max8907c_irqs); i++) {
+ irq_data = &max8907c_irqs[i];
+
+ if (irq_data->is_rtc)
+ i2c = chip->i2c_rtc;
+ else
+ i2c = chip->i2c_power;
+
+ if (read_reg != irq_data->reg) {
+ read_reg = irq_data->reg;
+ value = max8907c_reg_read(i2c, irq_data->reg);
+ }
+
+ if (value & irq_data->enable)
+ handle_nested_irq(chip->irq_base + i);
+ }
+ return IRQ_HANDLED;
+}
+
+static void max8907c_irq_lock(unsigned int irq)
+{
+ struct max8907c *chip = get_irq_chip_data(irq);
+
+ mutex_lock(&chip->irq_lock);
+}
+
+static void max8907c_irq_sync_unlock(unsigned int irq)
+{
+ struct max8907c *chip = get_irq_chip_data(irq);
+ struct max8907c_irq_data *irq_data;
+ static unsigned char cache_chg[2] = {0xff, 0xff};
+ static unsigned char cache_on[2] = {0xff, 0xff};
+ static unsigned char cache_rtc = 0xff;
+ unsigned char irq_chg[2], irq_on[2];
+ unsigned char irq_rtc;
+ int i;
+
+ /* Load cached value. In initial, all IRQs are masked */
+ irq_chg[0] = cache_chg[0];
+ irq_chg[1] = cache_chg[1];
+ irq_on[0] = cache_on[0];
+ irq_on[1] = cache_on[1];
+ irq_rtc = cache_rtc;
+ for (i = 0; i < ARRAY_SIZE(max8907c_irqs); i++) {
+ irq_data = &max8907c_irqs[i];
+ /* 1 -- disable, 0 -- enable */
+ switch (irq_data->mask_reg) {
+ case MAX8907C_REG_CHG_IRQ1_MASK:
+ irq_chg[0] &= ~irq_data->enable;
+ break;
+ case MAX8907C_REG_CHG_IRQ2_MASK:
+ irq_chg[1] &= ~irq_data->enable;
+ break;
+ case MAX8907C_REG_ON_OFF_IRQ1_MASK:
+ irq_on[0] &= ~irq_data->enable;
+ break;
+ case MAX8907C_REG_ON_OFF_IRQ2_MASK:
+ irq_on[1] &= ~irq_data->enable;
+ break;
+ case MAX8907C_REG_RTC_IRQ_MASK:
+ irq_rtc &= ~irq_data->enable;
+ break;
+ default:
+ dev_err(chip->dev, "wrong IRQ\n");
+ break;
+ }
+ }
+ /* update mask into registers */
+ if (cache_chg[0] != irq_chg[0]) {
+ cache_chg[0] = irq_chg[0];
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ1_MASK,
+ irq_chg[0]);
+ }
+ if (cache_chg[1] != irq_chg[1]) {
+ cache_chg[1] = irq_chg[1];
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ2_MASK,
+ irq_chg[1]);
+ }
+ if (cache_on[0] != irq_on[0]) {
+ cache_on[0] = irq_on[0];
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ1_MASK,
+ irq_on[0]);
+ }
+ if (cache_on[1] != irq_on[1]) {
+ cache_on[1] = irq_on[1];
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ2_MASK,
+ irq_on[1]);
+ }
+ if (cache_rtc != irq_rtc) {
+ cache_rtc = irq_rtc;
+ max8907c_reg_write(chip->i2c_rtc, MAX8907C_REG_RTC_IRQ_MASK,
+ irq_rtc);
+ }
+
+ mutex_unlock(&chip->irq_lock);
+}
+
+static void max8907c_irq_enable(unsigned int irq)
+{
+ struct max8907c *chip = get_irq_chip_data(irq);
+ max8907c_irqs[irq - chip->irq_base].enable
+ = max8907c_irqs[irq - chip->irq_base].offs;
+}
+
+static void max8907c_irq_disable(unsigned int irq)
+{
+ struct max8907c *chip = get_irq_chip_data(irq);
+ max8907c_irqs[irq - chip->irq_base].enable = 0;
+}
+
+static struct irq_chip max8907c_irq_chip = {
+ .name = "max8907c",
+ .bus_lock = max8907c_irq_lock,
+ .bus_sync_unlock = max8907c_irq_sync_unlock,
+ .enable = max8907c_irq_enable,
+ .disable = max8907c_irq_disable,
+};
+
+int max8907c_irq_init(struct max8907c *chip, int irq, int irq_base)
+{
+ unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+ struct irq_desc *desc;
+ int i, ret;
+ int __irq;
+
+ if (!irq_base || !irq) {
+ dev_warn(chip->dev, "No interrupt support\n");
+ return -EINVAL;
+ }
+ /* clear all interrupts */
+ max8907c_reg_read(chip->i2c_power, MAX8907C_REG_CHG_IRQ1);
+ max8907c_reg_read(chip->i2c_power, MAX8907C_REG_CHG_IRQ2);
+ max8907c_reg_read(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ1);
+ max8907c_reg_read(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ2);
+ max8907c_reg_read(chip->i2c_rtc, MAX8907C_REG_RTC_IRQ);
+ /* mask all interrupts */
+ max8907c_reg_write(chip->i2c_rtc, MAX8907C_REG_ALARM0_CNTL, 0);
+ max8907c_reg_write(chip->i2c_rtc, MAX8907C_REG_ALARM1_CNTL, 0);
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ1_MASK, 0xff);
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ2_MASK, 0xff);
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ1_MASK, 0xff);
+ max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ2_MASK, 0xff);
+ max8907c_reg_write(chip->i2c_rtc, MAX8907C_REG_RTC_IRQ_MASK, 0xff);
+
+ mutex_init(&chip->irq_lock);
+ chip->core_irq = irq;
+ chip->irq_base = irq_base;
+ desc = irq_to_desc(chip->core_irq);
+
+ /* register with genirq */
+ for (i = 0; i < ARRAY_SIZE(max8907c_irqs); i++) {
+ __irq = i + chip->irq_base;
+ set_irq_chip_data(__irq, chip);
+ set_irq_chip_and_handler(__irq, &max8907c_irq_chip,
+ handle_edge_irq);
+ set_irq_nested_thread(__irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(__irq, IRQF_VALID);
+#else
+ set_irq_noprobe(__irq);
+#endif
+ }
+
+ ret = request_threaded_irq(irq, NULL, max8907c_irq, flags,
+ "max8907c", chip);
+ if (ret) {
+ dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret);
+ chip->core_irq = 0;
+ }
+
+ return ret;
+}
+
+int max8907c_suspend(struct i2c_client *i2c, pm_message_t state)
+{
+ struct max8907c *max8907c = i2c_get_clientdata(i2c);
+
+ disable_irq(max8907c->core_irq);
+
+ return 0;
+}
+
+int max8907c_resume(struct i2c_client *i2c)
+{
+ struct max8907c *max8907c = i2c_get_clientdata(i2c);
+
+ enable_irq(max8907c->core_irq);
+
+ return 0;
+}
+
+void max8907c_irq_free(struct max8907c *chip)
+{
+ if (chip->core_irq)
+ free_irq(chip->core_irq, chip);
+}
+