blob: 6639aead12bd68b29a09047339b5bb17c3bcf738 [file] [log] [blame]
/*
* 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;
int wake;
};
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(struct irq_data *data)
{
struct max8907c *chip = irq_data_get_irq_chip_data(data);
mutex_lock(&chip->irq_lock);
}
static void max8907c_irq_sync_unlock(struct irq_data *data)
{
struct max8907c *chip = irq_data_get_irq_chip_data(data);
struct max8907c_irq_data *irq_data;
unsigned char irq_chg[2], irq_on[2];
unsigned char irq_rtc;
int i;
irq_chg[0] = irq_chg[1] = irq_on[0] = irq_on[1] = irq_rtc = 0xFF;
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 (chip->cache_chg[0] != irq_chg[0]) {
chip->cache_chg[0] = irq_chg[0];
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ1_MASK,
irq_chg[0]);
}
if (chip->cache_chg[1] != irq_chg[1]) {
chip->cache_chg[1] = irq_chg[1];
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ2_MASK,
irq_chg[1]);
}
if (chip->cache_on[0] != irq_on[0]) {
chip->cache_on[0] = irq_on[0];
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ1_MASK,
irq_on[0]);
}
if (chip->cache_on[1] != irq_on[1]) {
chip->cache_on[1] = irq_on[1];
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ2_MASK,
irq_on[1]);
}
if (chip->cache_rtc != irq_rtc) {
chip->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(struct irq_data *data)
{
struct max8907c *chip = irq_data_get_irq_chip_data(data);
max8907c_irqs[data->irq - chip->irq_base].enable
= max8907c_irqs[data->irq - chip->irq_base].offs;
}
static void max8907c_irq_disable(struct irq_data *data)
{
struct max8907c *chip = irq_data_get_irq_chip_data(data);
max8907c_irqs[data->irq - chip->irq_base].enable = 0;
}
static int max8907c_irq_set_wake(struct irq_data *data, unsigned int on)
{
struct max8907c *chip = irq_data_get_irq_chip_data(data);
if (on) {
max8907c_irqs[data->irq - chip->irq_base].wake
= max8907c_irqs[data->irq - chip->irq_base].enable;
} else {
max8907c_irqs[data->irq - chip->irq_base].wake = 0;
}
return 0;
}
static struct irq_chip max8907c_irq_chip = {
.name = "max8907c",
.irq_bus_lock = max8907c_irq_lock,
.irq_bus_sync_unlock = max8907c_irq_sync_unlock,
.irq_enable = max8907c_irq_enable,
.irq_disable = max8907c_irq_disable,
.irq_set_wake = max8907c_irq_set_wake,
};
int max8907c_irq_init(struct max8907c *chip, int irq, int irq_base)
{
unsigned long flags = 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);
chip->cache_chg[0] = chip->cache_chg[1] =
chip->cache_on[0] = chip->cache_on[1] =
chip->cache_rtc = 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;
irq_set_chip_data(__irq, chip);
irq_set_chip_and_handler(__irq, &max8907c_irq_chip,
handle_edge_irq);
irq_set_nested_thread(__irq, 1);
#ifdef CONFIG_ARM
/* ARM requires an extra step to clear IRQ_NOREQUEST, which it
* sets on behalf of every irq_chip.
*/
set_irq_flags(__irq, IRQF_VALID);
#else
irq_set_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;
}
device_init_wakeup(chip->dev, 1);
return ret;
}
int max8907c_suspend(struct device *dev)
{
struct i2c_client *i2c = to_i2c_client(dev);
struct max8907c *chip = i2c_get_clientdata(i2c);
struct max8907c_irq_data *irq_data;
unsigned char irq_chg[2], irq_on[2];
unsigned char irq_rtc;
int i;
irq_chg[0] = irq_chg[1] = irq_on[0] = irq_on[1] = irq_rtc = 0xFF;
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->wake;
break;
case MAX8907C_REG_CHG_IRQ2_MASK:
irq_chg[1] &= ~irq_data->wake;
break;
case MAX8907C_REG_ON_OFF_IRQ1_MASK:
irq_on[0] &= ~irq_data->wake;
break;
case MAX8907C_REG_ON_OFF_IRQ2_MASK:
irq_on[1] &= ~irq_data->wake;
break;
case MAX8907C_REG_RTC_IRQ_MASK:
irq_rtc &= ~irq_data->wake;
break;
default:
dev_err(chip->dev, "wrong IRQ\n");
break;
}
}
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ1_MASK, irq_chg[0]);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ2_MASK, irq_chg[1]);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ1_MASK, irq_on[0]);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ2_MASK, irq_on[1]);
max8907c_reg_write(chip->i2c_rtc, MAX8907C_REG_RTC_IRQ_MASK, irq_rtc);
if (device_may_wakeup(chip->dev))
enable_irq_wake(chip->core_irq);
else
disable_irq(chip->core_irq);
return 0;
}
int max8907c_resume(struct device *dev)
{
struct i2c_client *i2c = to_i2c_client(dev);
struct max8907c *chip = i2c_get_clientdata(i2c);
if (device_may_wakeup(chip->dev))
disable_irq_wake(chip->core_irq);
else
enable_irq(chip->core_irq);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ1_MASK, chip->cache_chg[0]);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_CHG_IRQ2_MASK, chip->cache_chg[1]);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ1_MASK, chip->cache_on[0]);
max8907c_reg_write(chip->i2c_power, MAX8907C_REG_ON_OFF_IRQ2_MASK, chip->cache_on[1]);
max8907c_reg_write(chip->i2c_rtc, MAX8907C_REG_RTC_IRQ_MASK, chip->cache_rtc);
return 0;
}
void max8907c_irq_free(struct max8907c *chip)
{
if (chip->core_irq)
free_irq(chip->core_irq, chip);
}