/* Copyright (c) 2011, The Linux Foundation. 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);