/* * 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 #include #include #include #include #include #include #include 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 i2c_client *i2c, pm_message_t state) { 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 i2c_client *i2c) { 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); }