From ef436392192aaf92bbb86bfbb6704efd398d3b1c Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 17 Jun 2011 15:04:13 -0700 Subject: [PATCH] mfd: pm8xxx: Add PM8xxx battery alarm driver Add a driver to control the battery alarm module of PMIC PM8xxx devices. This module uses a pair of comparators to determine when battery under and over-voltage take place. A wakeup interrupt is triggered in these cases which can then run any notifiers which have been registered. Also add APIs to configure the threshold voltages and the frequency at which the hardware checks the state of the battery voltage. Change-Id: Id0b82f9090b29ce743b5e0faac17853c94111771 Signed-off-by: David Collins --- drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 1 + drivers/mfd/pm8xxx-batt-alarm.c | 806 ++++++++++++++++++++++++++ include/linux/mfd/pm8xxx/batt-alarm.h | 201 +++++++ 4 files changed, 1018 insertions(+) create mode 100644 drivers/mfd/pm8xxx-batt-alarm.c create mode 100644 include/linux/mfd/pm8xxx/batt-alarm.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0a5dbfd20c3..e3e3f2abb67 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1013,6 +1013,16 @@ config MFD_PM8XXX_SPK This driver implements several external speaker amplifier APIs that may be needed in order to control the PM8XXX PMIC chip. +config MFD_PM8XXX_BATT_ALARM + tristate "Support for Qualcomm PM8xxx battery voltage alarm" + depends on MFD_PM8XXX + help + This driver provides a means monitor battery under and over-voltage + conditions. An upper and/or lower threshold can be specified for + normal operation. A wakeable interrupt is triggered when the battery + voltage leaves the accepatable range which then calls a notifier call + chain. + config MFD_RC5T583 bool "Ricoh RC5T583 Power Management system device" depends on I2C=y && GENERIC_HARDIRQS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index ff69243a53e..53ae9afbdb8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -136,4 +136,5 @@ obj-$(CONFIG_MFD_PM8XXX_DEBUG) += pm8xxx-debug.o obj-$(CONFIG_MFD_PM8XXX_PWM) += pm8xxx-pwm.o obj-$(CONFIG_MFD_PM8XXX_MISC) += pm8xxx-misc.o obj-$(CONFIG_MFD_PM8XXX_SPK) += pm8xxx-spk.o +obj-$(CONFIG_MFD_PM8XXX_BATT_ALARM) += pm8xxx-batt-alarm.o obj-$(CONFIG_MFD_ANATOP) += anatop-mfd.o diff --git a/drivers/mfd/pm8xxx-batt-alarm.c b/drivers/mfd/pm8xxx-batt-alarm.c new file mode 100644 index 00000000000..1d30db9ad75 --- /dev/null +++ b/drivers/mfd/pm8xxx-batt-alarm.c @@ -0,0 +1,806 @@ +/* Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ +/* + * Qualcomm PMIC PM8xxx Battery Alarm driver + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Available voltage threshold values */ +#define THRESHOLD_MIN_MV 2500 +#define THRESHOLD_MAX_MV 5675 +#define THRESHOLD_STEP_MV 25 + +/* Register bit definitions */ + +/* Threshold register */ +#define THRESHOLD_UPPER_MASK 0xF0 +#define THRESHOLD_LOWER_MASK 0x0F +#define THRESHOLD_UPPER_SHIFT 4 +#define THRESHOLD_LOWER_SHIFT 0 + +/* CTRL 1 register */ +#define CTRL1_BATT_ALARM_ENABLE_MASK 0x80 +#define CTRL1_BATT_ALARM_ENABLE 0x80 +#define CTRL1_BATT_ALARM_DISABLE 0x00 +#define CTRL1_HOLD_TIME_MASK 0x70 +#define CTRL1_STATUS_UPPER_MASK 0x02 +#define CTRL1_STATUS_LOWER_MASK 0x01 +#define CTRL1_HOLD_TIME_SHIFT 4 +#define CTRL1_HOLD_TIME_MIN 0 +#define CTRL1_HOLD_TIME_MAX 7 + +/* CTRL 2 register */ +#define CTRL2_COMP_UPPER_DISABLE_MASK 0x80 +#define CTRL2_COMP_UPPER_ENABLE 0x00 +#define CTRL2_COMP_UPPER_DISABLE 0x80 +#define CTRL2_COMP_LOWER_DISABLE_MASK 0x40 +#define CTRL2_COMP_LOWER_ENABLE 0x00 +#define CTRL2_COMP_LOWER_DISABLE 0x40 +#define CTRL2_FINE_STEP_UPPER_MASK 0x30 +#define CTRL2_RANGE_EXT_UPPER_MASK 0x08 +#define CTRL2_FINE_STEP_LOWER_MASK 0x06 +#define CTRL2_RANGE_EXT_LOWER_MASK 0x01 +#define CTRL2_FINE_STEP_UPPER_SHIFT 4 +#define CTRL2_FINE_STEP_LOWER_SHIFT 1 + +/* PWM control register */ +#define PWM_CTRL_ALARM_EN_MASK 0xC0 +#define PWM_CTRL_ALARM_EN_NEVER 0x00 +#define PWM_CTRL_ALARM_EN_TCXO 0x40 +#define PWM_CTRL_ALARM_EN_PWM 0x80 +#define PWM_CTRL_ALARM_EN_ALWAYS 0xC0 +#define PWM_CTRL_PRE_MASK 0x38 +#define PWM_CTRL_DIV_MASK 0x07 +#define PWM_CTRL_PRE_SHIFT 3 +#define PWM_CTRL_DIV_SHIFT 0 +#define PWM_CTRL_PRE_MIN 0 +#define PWM_CTRL_PRE_MAX 7 +#define PWM_CTRL_DIV_MIN 1 +#define PWM_CTRL_DIV_MAX 7 + +/* PWM control input range */ +#define PWM_CTRL_PRE_INPUT_MIN 2 +#define PWM_CTRL_PRE_INPUT_MAX 9 +#define PWM_CTRL_DIV_INPUT_MIN 2 +#define PWM_CTRL_DIV_INPUT_MAX 8 + +/* Available voltage threshold values */ +#define THRESHOLD_BASIC_MIN_MV 2800 +#define THRESHOLD_EXT_MIN_MV 4400 + +/* + * Default values used during initialization: + * Slowest PWM rate to ensure minimal status jittering when crossing thresholds. + * Largest hold time also helps reduce status value jittering. Comparators + * are disabled by default and must be turned on by calling + * pm8xxx_batt_alarm_state_set. + */ +#define DEFAULT_THRESHOLD_LOWER 3200 +#define DEFAULT_THRESHOLD_UPPER 4300 +#define DEFAULT_HOLD_TIME PM8XXX_BATT_ALARM_HOLD_TIME_16_MS +#define DEFAULT_USE_PWM 1 +#define DEFAULT_PWM_SCALER 9 +#define DEFAULT_PWM_DIVIDER 8 +#define DEFAULT_LOWER_ENABLE 0 +#define DEFAULT_UPPER_ENABLE 0 + +struct pm8xxx_batt_alarm_chip { + struct pm8xxx_batt_alarm_core_data cdata; + struct srcu_notifier_head irq_notifier_list; + struct work_struct irq_work; + struct device *dev; + struct mutex lock; + unsigned int irq; + int notifier_count; + u8 reg_threshold; + u8 reg_ctrl1; + u8 reg_ctrl2; + u8 reg_pwm_ctrl; +}; +static struct pm8xxx_batt_alarm_chip *the_battalarm; + +static int pm8xxx_reg_write(struct pm8xxx_batt_alarm_chip *chip, u16 addr, + u8 val, u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) + rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + if (rc) + pr_err("pm8xxx_writeb failed; addr=%03X, rc=%d\n", addr, rc); + else + *reg_save = reg; + return rc; +} + +/** + * pm8xxx_batt_alarm_enable - enable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to enable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_enable(enum pm8xxx_batt_alarm_comparator comparator) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 val_ctrl2 = 0, mask_ctrl2 = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENODEV; + } + + if (comparator < 0 || comparator > PM8XXX_BATT_ALARM_UPPER_COMPARATOR) { + pr_err("invalid comparator ID number: %d\n", comparator); + return -EINVAL; + } + + if (comparator == PM8XXX_BATT_ALARM_LOWER_COMPARATOR) { + val_ctrl2 = CTRL2_COMP_LOWER_ENABLE; + mask_ctrl2 = CTRL2_COMP_LOWER_DISABLE_MASK; + } else { + val_ctrl2 = CTRL2_COMP_UPPER_ENABLE; + mask_ctrl2 = CTRL2_COMP_UPPER_DISABLE_MASK; + } + + mutex_lock(&chip->lock); + + /* Enable the battery alarm block. */ + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl1, + CTRL1_BATT_ALARM_ENABLE, + CTRL1_BATT_ALARM_ENABLE_MASK, &chip->reg_ctrl1); + if (rc) + goto bail; + + /* Enable the individual comparators. */ + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl2, val_ctrl2, + mask_ctrl2, &chip->reg_ctrl2); + +bail: + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_enable); + +/** + * pm8xxx_batt_alarm_disable - disable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to disable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_disable(enum pm8xxx_batt_alarm_comparator comparator) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 val_ctrl1 = 0, val_ctrl2 = 0, mask_ctrl2 = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENODEV; + } + + if (comparator < 0 || comparator > PM8XXX_BATT_ALARM_UPPER_COMPARATOR) { + pr_err("invalid comparator ID number: %d\n", comparator); + return -EINVAL; + } + + if (comparator == PM8XXX_BATT_ALARM_LOWER_COMPARATOR) { + val_ctrl2 = CTRL2_COMP_LOWER_DISABLE; + mask_ctrl2 = CTRL2_COMP_LOWER_DISABLE_MASK; + } else { + val_ctrl2 = CTRL2_COMP_UPPER_DISABLE; + mask_ctrl2 = CTRL2_COMP_UPPER_DISABLE_MASK; + } + + mutex_lock(&chip->lock); + + /* Disable the specified comparator. */ + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl2, val_ctrl2, + mask_ctrl2, &chip->reg_ctrl2); + if (rc) + goto bail; + + /* Disable the battery alarm block if both comparators are disabled. */ + val_ctrl2 = chip->reg_ctrl2 + & (CTRL2_COMP_LOWER_DISABLE_MASK | CTRL2_COMP_UPPER_DISABLE_MASK); + if (val_ctrl2 == (CTRL2_COMP_LOWER_DISABLE | CTRL2_COMP_UPPER_DISABLE)) + val_ctrl1 = CTRL1_BATT_ALARM_DISABLE; + else + val_ctrl1 = CTRL1_BATT_ALARM_ENABLE; + + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl1, val_ctrl1, + CTRL1_BATT_ALARM_ENABLE_MASK, &chip->reg_ctrl1); + +bail: + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_disable); + +/** + * pm8xxx_batt_alarm_threshold_set - set the lower and upper alarm thresholds + * @comparator: selects which comparator to set the threshold of + * @threshold_mV: battery voltage threshold in millivolts + * set points = 2500-5675 mV in 25 mV steps + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_threshold_set( + enum pm8xxx_batt_alarm_comparator comparator, int threshold_mV) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int step, fine_step, rc; + u8 val_threshold = 0, val_ctrl2 = 0; + int threshold_mask, threshold_shift, range_ext_mask, fine_step_mask; + int fine_step_shift; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (comparator < 0 || comparator > PM8XXX_BATT_ALARM_UPPER_COMPARATOR) { + pr_err("invalid comparator ID number: %d\n", comparator); + return -EINVAL; + } + + if (threshold_mV < THRESHOLD_MIN_MV + || threshold_mV > THRESHOLD_MAX_MV) { + pr_err("threshold value, %d mV, is outside of allowable " + "range: [%d, %d] mV\n", threshold_mV, + THRESHOLD_MIN_MV, THRESHOLD_MAX_MV); + return -EINVAL; + } + + if (comparator == PM8XXX_BATT_ALARM_LOWER_COMPARATOR) { + threshold_mask = THRESHOLD_LOWER_MASK; + threshold_shift = THRESHOLD_LOWER_SHIFT; + range_ext_mask = CTRL2_RANGE_EXT_LOWER_MASK; + fine_step_mask = CTRL2_FINE_STEP_LOWER_MASK; + fine_step_shift = CTRL2_FINE_STEP_LOWER_SHIFT; + } else { + threshold_mask = THRESHOLD_UPPER_MASK; + threshold_shift = THRESHOLD_UPPER_SHIFT; + range_ext_mask = CTRL2_RANGE_EXT_UPPER_MASK; + fine_step_mask = CTRL2_FINE_STEP_UPPER_MASK; + fine_step_shift = CTRL2_FINE_STEP_UPPER_SHIFT; + } + + /* Determine register settings to achieve the threshold. */ + if (threshold_mV < THRESHOLD_BASIC_MIN_MV) { + /* Extended low range */ + val_ctrl2 |= range_ext_mask; + + step = (threshold_mV - THRESHOLD_MIN_MV) / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended low range is for steps 0 to 2 */ + step >>= 2; + } else if (threshold_mV >= THRESHOLD_EXT_MIN_MV) { + /* Extended high range */ + val_ctrl2 |= range_ext_mask; + + step = (threshold_mV - THRESHOLD_EXT_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended high range is for steps 3 to 15 */ + step = (step >> 2) + 3; + } else { + /* Basic range */ + step = (threshold_mV - THRESHOLD_BASIC_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + step >>= 2; + } + val_threshold |= step << threshold_shift; + val_ctrl2 |= (fine_step << fine_step_shift) & fine_step_mask; + + mutex_lock(&chip->lock); + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_threshold, + val_threshold, threshold_mask, &chip->reg_threshold); + if (rc) + goto bail; + + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl2, val_ctrl2, + range_ext_mask | fine_step_mask, &chip->reg_ctrl2); + +bail: + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_threshold_set); + +/** + * pm8xxx_batt_alarm_status_read - get status of both threshold comparators + * + * RETURNS: < 0 = error + * 0 = battery voltage ok + * BIT(0) set = battery voltage below lower threshold + * BIT(1) set = battery voltage above upper threshold + */ +int pm8xxx_batt_alarm_status_read(void) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int status, rc; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + mutex_lock(&chip->lock); + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_ctrl1, + &chip->reg_ctrl1); + + status = ((chip->reg_ctrl1 & CTRL1_STATUS_LOWER_MASK) + ? PM8XXX_BATT_ALARM_STATUS_BELOW_LOWER : 0) + | ((chip->reg_ctrl1 & CTRL1_STATUS_UPPER_MASK) + ? PM8XXX_BATT_ALARM_STATUS_ABOVE_UPPER : 0); + mutex_unlock(&chip->lock); + + if (rc) { + pr_err("pm8xxx_readb failed, rc=%d\n", rc); + return rc; + } + + return status; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_status_read); + +/** + * pm8xxx_batt_alarm_hold_time_set - set hold time of interrupt output * + * @hold_time: amount of time that battery voltage must remain outside of the + * threshold range before the battery alarm interrupt triggers + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_hold_time_set(enum pm8xxx_batt_alarm_hold_time hold_time) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 reg_ctrl1 = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (hold_time < CTRL1_HOLD_TIME_MIN + || hold_time > CTRL1_HOLD_TIME_MAX) { + + pr_err("hold time, %d, is outside of allowable range: " + "[%d, %d]\n", hold_time, CTRL1_HOLD_TIME_MIN, + CTRL1_HOLD_TIME_MAX); + return -EINVAL; + } + + reg_ctrl1 = hold_time << CTRL1_HOLD_TIME_SHIFT; + + mutex_lock(&chip->lock); + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl1, reg_ctrl1, + CTRL1_HOLD_TIME_MASK, &chip->reg_ctrl1); + mutex_unlock(&chip->lock); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_hold_time_set); + +/** + * pm8xxx_batt_alarm_pwm_rate_set - set battery alarm update rate * + * @use_pwm: 1 = use PWM update rate, 0 = comparators always active + * @clock_scaler: PWM clock scaler = 2 to 9 + * @clock_divider: PWM clock divider = 2 to 8 + * + * This function sets the rate at which the battery alarm module enables + * the threshold comparators. The rate is determined by the following equation: + * + * f_update = (1024 Hz) / (clock_divider * (2 ^ clock_scaler)) + * + * Thus, the update rate can range from 0.25 Hz to 128 Hz. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, + int clock_divider) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 reg_pwm_ctrl = 0, mask = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (use_pwm && (clock_scaler < PWM_CTRL_PRE_INPUT_MIN + || clock_scaler > PWM_CTRL_PRE_INPUT_MAX)) { + pr_err("PWM clock scaler, %d, is outside of allowable range: " + "[%d, %d]\n", clock_scaler, PWM_CTRL_PRE_INPUT_MIN, + PWM_CTRL_PRE_INPUT_MAX); + return -EINVAL; + } + + if (use_pwm && (clock_divider < PWM_CTRL_DIV_INPUT_MIN + || clock_divider > PWM_CTRL_DIV_INPUT_MAX)) { + pr_err("PWM clock divider, %d, is outside of allowable range: " + "[%d, %d]\n", clock_divider, PWM_CTRL_DIV_INPUT_MIN, + PWM_CTRL_DIV_INPUT_MAX); + return -EINVAL; + } + + if (!use_pwm) { + /* Turn off PWM control and always enable. */ + reg_pwm_ctrl = PWM_CTRL_ALARM_EN_ALWAYS; + mask = PWM_CTRL_ALARM_EN_MASK; + } else { + /* Use PWM control. */ + reg_pwm_ctrl = PWM_CTRL_ALARM_EN_PWM; + mask = PWM_CTRL_ALARM_EN_MASK | PWM_CTRL_PRE_MASK + | PWM_CTRL_DIV_MASK; + + clock_scaler -= PWM_CTRL_PRE_INPUT_MIN - PWM_CTRL_PRE_MIN; + clock_divider -= PWM_CTRL_DIV_INPUT_MIN - PWM_CTRL_DIV_MIN; + + reg_pwm_ctrl |= (clock_scaler << PWM_CTRL_PRE_SHIFT) + & PWM_CTRL_PRE_MASK; + reg_pwm_ctrl |= (clock_divider << PWM_CTRL_DIV_SHIFT) + & PWM_CTRL_DIV_MASK; + } + + mutex_lock(&chip->lock); + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_pwm_ctrl, reg_pwm_ctrl, + mask, &chip->reg_pwm_ctrl); + mutex_unlock(&chip->lock); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_pwm_rate_set); + +/* + * Handle the BATT_ALARM interrupt: + * Battery voltage is above or below threshold range. + */ +static irqreturn_t pm8xxx_batt_alarm_isr(int irq, void *data) +{ + struct pm8xxx_batt_alarm_chip *chip = data; + + disable_irq_nosync(chip->irq); + schedule_work(&chip->irq_work); + + return IRQ_HANDLED; +} + +static void pm8xxx_batt_alarm_isr_work(struct work_struct *work) +{ + struct pm8xxx_batt_alarm_chip *chip + = container_of(work, struct pm8xxx_batt_alarm_chip, irq_work); + int status; + + if (!chip) + return; + + status = pm8xxx_batt_alarm_status_read(); + + if (status < 0) + pr_err("failed to read status, rc=%d\n", status); + else + srcu_notifier_call_chain(&chip->irq_notifier_list, + status, NULL); + + enable_irq(chip->irq); +} + +/** + * pm8xxx_batt_alarm_register_notifier - register a notifier to run when a + * battery voltage change interrupt fires + * @nb: notifier block containing callback function to register + * + * nb->notifier_call must point to a function of this form - + * int (*notifier_call)(struct notifier_block *nb, unsigned long status, + * void *unused); + * "status" will receive the battery alarm status; "unused" will be NULL. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_register_notifier(struct notifier_block *nb) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + rc = srcu_notifier_chain_register(&chip->irq_notifier_list, nb); + mutex_lock(&chip->lock); + if (rc == 0) { + if (chip->notifier_count == 0) { + enable_irq(chip->irq); + rc = irq_set_irq_wake(chip->irq, 1); + } + + chip->notifier_count++; + } + + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_register_notifier); + +/** + * pm8xxx_batt_alarm_unregister_notifier - unregister a notifier that is run + * when a battery voltage change interrupt fires + * @nb: notifier block containing callback function to unregister + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_unregister_notifier(struct notifier_block *nb) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + rc = srcu_notifier_chain_unregister(&chip->irq_notifier_list, nb); + if (rc == 0) { + mutex_lock(&chip->lock); + + chip->notifier_count--; + + if (chip->notifier_count == 0) { + rc = irq_set_irq_wake(chip->irq, 0); + disable_irq(chip->irq); + } + + WARN_ON(chip->notifier_count < 0); + + mutex_unlock(&chip->lock); + } + + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_unregister_notifier); + +static int pm8xxx_batt_alarm_reg_init(struct pm8xxx_batt_alarm_chip *chip) +{ + int rc = 0; + + /* save the current register states */ + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_threshold, + &chip->reg_threshold); + if (rc) + goto bail; + + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_ctrl1, + &chip->reg_ctrl1); + if (rc) + goto bail; + + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_ctrl2, + &chip->reg_ctrl2); + if (rc) + goto bail; + + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_pwm_ctrl, + &chip->reg_pwm_ctrl); + if (rc) + goto bail; + +bail: + if (rc) + pr_err("pm8xxx_readb failed; initial register states " + "unknown, rc=%d\n", rc); + return rc; +} + +/* TODO: should this default setting function be removed? */ +static int pm8xxx_batt_alarm_config_defaults(void) +{ + int rc = 0; + + /* Use default values when no platform data is provided. */ + rc = pm8xxx_batt_alarm_threshold_set(PM8XXX_BATT_ALARM_LOWER_COMPARATOR, + DEFAULT_THRESHOLD_LOWER); + if (rc) { + pr_err("threshold_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_threshold_set(PM8XXX_BATT_ALARM_UPPER_COMPARATOR, + DEFAULT_THRESHOLD_UPPER); + if (rc) { + pr_err("threshold_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_hold_time_set(DEFAULT_HOLD_TIME); + if (rc) { + pr_err("hold_time_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_pwm_rate_set(DEFAULT_USE_PWM, + DEFAULT_PWM_SCALER, DEFAULT_PWM_DIVIDER); + if (rc) { + pr_err("pwm_rate_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + if (rc) { + pr_err("disable lower failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (rc) { + pr_err("disable upper failed, rc=%d\n", rc); + goto done; + } + +done: + return rc; +} + +static int __devinit pm8xxx_batt_alarm_probe(struct platform_device *pdev) +{ + const struct pm8xxx_batt_alarm_core_data *cdata + = pdev->dev.platform_data; + struct pm8xxx_batt_alarm_chip *chip; + struct resource *res; + int rc; + + if (the_battalarm) { + pr_err("A PMIC battery alarm device has already probed.\n"); + return -ENODEV; + } + + if (!cdata) { + pr_err("missing core data\n"); + return -EINVAL; + } + + if (!cdata->irq_name) { + pr_err("missing IRQ name\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8xxx_batt_alarm_chip), GFP_KERNEL); + if (chip == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + cdata->irq_name); + if (res) { + chip->irq = res->start; + } else { + pr_err("Battery alarm IRQ not specified\n"); + rc = -EINVAL; + goto err_free_chip; + } + + chip->dev = &pdev->dev; + memcpy(&(chip->cdata), cdata, + sizeof(struct pm8xxx_batt_alarm_core_data)); + + srcu_init_notifier_head(&chip->irq_notifier_list); + + chip->notifier_count = 0; + mutex_init(&chip->lock); + + the_battalarm = chip; + + rc = pm8xxx_batt_alarm_reg_init(chip); + if (rc) + goto err_free_mutex; + + rc = pm8xxx_batt_alarm_config_defaults(); + if (rc) + goto err_free_mutex; + + INIT_WORK(&chip->irq_work, pm8xxx_batt_alarm_isr_work); + +/* TODO: Is it best to trigger on both edges? Should this be configurable? */ + rc = request_irq(chip->irq, pm8xxx_batt_alarm_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, cdata->irq_name, + chip); + if (rc < 0) { + pr_err("request_irq(%d) failed, rc=%d\n", chip->irq, rc); + goto err_cancel_work; + } + + /* Disable the IRQ until a notifier is registered. */ + disable_irq(chip->irq); + + platform_set_drvdata(pdev, chip); + + return 0; + +err_cancel_work: + cancel_work_sync(&chip->irq_work); +err_free_mutex: + mutex_destroy(&chip->lock); + srcu_cleanup_notifier_head(&chip->irq_notifier_list); +err_free_chip: + kfree(chip); + the_battalarm = NULL; + + return rc; +} + +static int __devexit pm8xxx_batt_alarm_remove(struct platform_device *pdev) +{ + struct pm8xxx_batt_alarm_chip *chip = platform_get_drvdata(pdev); + + if (chip) { + platform_set_drvdata(pdev, NULL); + irq_set_irq_wake(chip->irq, 0); + free_irq(chip->irq, chip); + cancel_work_sync(&chip->irq_work); + srcu_cleanup_notifier_head(&chip->irq_notifier_list); + mutex_destroy(&chip->lock); + kfree(chip); + the_battalarm = NULL; + } + + return 0; +} + +static struct platform_driver pm8xxx_batt_alarm_driver = { + .probe = pm8xxx_batt_alarm_probe, + .remove = __devexit_p(pm8xxx_batt_alarm_remove), + .driver = { + .name = PM8XXX_BATT_ALARM_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_batt_alarm_init(void) +{ + return platform_driver_register(&pm8xxx_batt_alarm_driver); +} + +static void __exit pm8xxx_batt_alarm_exit(void) +{ + platform_driver_unregister(&pm8xxx_batt_alarm_driver); +} + +module_init(pm8xxx_batt_alarm_init); +module_exit(pm8xxx_batt_alarm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC PM8xxx Battery Alarm"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_BATT_ALARM_DEV_NAME); diff --git a/include/linux/mfd/pm8xxx/batt-alarm.h b/include/linux/mfd/pm8xxx/batt-alarm.h new file mode 100644 index 00000000000..f10715d1761 --- /dev/null +++ b/include/linux/mfd/pm8xxx/batt-alarm.h @@ -0,0 +1,201 @@ +/* Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ +/* + * Qualcomm PMIC PM8xxx Battery Alarm driver + * + */ +#ifndef __MFD_PM8XXX_BATT_ALARM_H__ +#define __MFD_PM8XXX_BATT_ALARM_H__ + +#include +#include +#include + +#define PM8XXX_BATT_ALARM_DEV_NAME "pm8xxx-batt-alarm" + +/** + * enum pm8xxx_batt_alarm_core_data - PMIC core specific core passed into the + * batter alarm driver as platform data + * @irq_name: + * @reg_addr_batt_alarm_threshold: PMIC threshold register address + * @reg_addr_batt_alarm_ctrl1: PMIC control 1 register address + * @reg_addr_batt_alarm_ctrl2: PMIC control 2 register address + * @reg_addr_batt_alarm_pwm_ctrl: PMIC PWM control register address + */ +struct pm8xxx_batt_alarm_core_data { + char *irq_name; + u16 reg_addr_threshold; + u16 reg_addr_ctrl1; + u16 reg_addr_ctrl2; + u16 reg_addr_pwm_ctrl; +}; + +/** + * enum pm8xxx_batt_alarm_comparator - battery alarm comparator ID values + */ +enum pm8xxx_batt_alarm_comparator { + PM8XXX_BATT_ALARM_LOWER_COMPARATOR, + PM8XXX_BATT_ALARM_UPPER_COMPARATOR, +}; + +/** + * enum pm8xxx_batt_alarm_hold_time - hold time required for out of range + * battery voltage needed to trigger a status change. Enum names denote + * hold time in milliseconds. + */ +enum pm8xxx_batt_alarm_hold_time { + PM8XXX_BATT_ALARM_HOLD_TIME_0p125_MS = 0, + PM8XXX_BATT_ALARM_HOLD_TIME_0p25_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_0p5_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_1_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_2_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_4_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_8_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_16_MS, +}; + +/* + * Bits that are set in the return value of pm8xxx_batt_alarm_status_read + * to indicate crossing of the upper or lower threshold. + */ +#define PM8XXX_BATT_ALARM_STATUS_BELOW_LOWER BIT(0) +#define PM8XXX_BATT_ALARM_STATUS_ABOVE_UPPER BIT(1) + +#if defined(CONFIG_MFD_PM8XXX_BATT_ALARM) \ + || defined(CONFIG_MFD_PM8XXX_BATT_ALARM_MODULE) + +/** + * pm8xxx_batt_alarm_enable - enable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to enable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_enable(enum pm8xxx_batt_alarm_comparator comparator); + +/** + * pm8xxx_batt_alarm_disable - disable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to disable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_disable(enum pm8xxx_batt_alarm_comparator comparator); + + +/** + * pm8xxx_batt_alarm_threshold_set - set the lower and upper alarm thresholds + * @comparator: selects which comparator to set the threshold of + * @threshold_mV: battery voltage threshold in millivolts + * set points = 2500-5675 mV in 25 mV steps + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_threshold_set( + enum pm8xxx_batt_alarm_comparator comparator, int threshold_mV); + +/** + * pm8xxx_batt_alarm_status_read - get status of both threshold comparators + * + * RETURNS: < 0 = error + * 0 = battery voltage ok + * BIT(0) set = battery voltage below lower threshold + * BIT(1) set = battery voltage above upper threshold + */ +int pm8xxx_batt_alarm_status_read(void); + +/** + * pm8xxx_batt_alarm_register_notifier - register a notifier to run when a + * battery voltage change interrupt fires + * @nb: notifier block containing callback function to register + * + * nb->notifier_call must point to a function of this form - + * int (*notifier_call)(struct notifier_block *nb, unsigned long status, + * void *unused); + * "status" will receive the battery alarm status; "unused" will be NULL. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_register_notifier(struct notifier_block *nb); + +/** + * pm8xxx_batt_alarm_unregister_notifier - unregister a notifier that is run + * when a battery voltage change interrupt fires + * @nb: notifier block containing callback function to unregister + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_unregister_notifier(struct notifier_block *nb); + +/** + * pm8xxx_batt_alarm_hold_time_set - set hold time of interrupt output * + * @hold_time: amount of time that battery voltage must remain outside of the + * threshold range before the battery alarm interrupt triggers + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_hold_time_set(enum pm8xxx_batt_alarm_hold_time hold_time); + +/** + * pm8xxx_batt_alarm_pwm_rate_set - set battery alarm update rate * + * @use_pwm: 1 = use PWM update rate, 0 = comparators always active + * @clock_scaler: PWM clock scaler = 2 to 9 + * @clock_divider: PWM clock divider = 2 to 8 + * + * This function sets the rate at which the battery alarm module enables + * the threshold comparators. The rate is determined by the following equation: + * + * f_update = (1024 Hz) / (clock_divider * (2 ^ clock_scaler)) + * + * Thus, the update rate can range from 0.25 Hz to 128 Hz. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, + int clock_divider); +#else + +static inline int +pm8xxx_batt_alarm_enable(enum pm8xxx_batt_alarm_comparator comparator) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_disable(enum pm8xxx_batt_alarm_comparator comparator) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_threshold_set(enum pm8xxx_batt_alarm_comparator comparator, + int threshold_mV) +{ return -ENODEV; } + +static inline int pm8xxx_batt_alarm_status_read(void) +{ return -ENODEV; } + +static inline int pm8xxx_batt_alarm_register_notifier(struct notifier_block *nb) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_unregister_notifier(struct notifier_block *nb) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_hold_time_set(enum pm8xxx_batt_alarm_hold_time hold_time) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, int clock_divider) +{ return -ENODEV; } + +#endif + + +#endif /* __MFD_PM8XXX_BATT_ALARM_H__ */