/* Copyright (c) 2011-2013, 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. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CCADC_ANA_PARAM 0x240 #define CCADC_DIG_PARAM 0x241 #define CCADC_RSV 0x242 #define CCADC_DATA0 0x244 #define CCADC_DATA1 0x245 #define CCADC_OFFSET_TRIM1 0x34A #define CCADC_OFFSET_TRIM0 0x34B #define CCADC_FULLSCALE_TRIM1 0x34C #define CCADC_FULLSCALE_TRIM0 0x34D /* note : TRIM1 is the msb and TRIM0 is the lsb */ #define ADC_ARB_SECP_CNTRL 0x190 #define ADC_ARB_SECP_AMUX_CNTRL 0x191 #define ADC_ARB_SECP_ANA_PARAM 0x192 #define ADC_ARB_SECP_DIG_PARAM 0x193 #define ADC_ARB_SECP_RSV 0x194 #define ADC_ARB_SECP_DATA1 0x195 #define ADC_ARB_SECP_DATA0 0x196 #define ADC_ARB_BMS_CNTRL 0x18D #define START_CONV_BIT BIT(7) #define EOC_CONV_BIT BIT(6) #define SEL_CCADC_BIT BIT(1) #define EN_ARB_BIT BIT(0) #define CCADC_CALIB_DIG_PARAM 0xE3 #define CCADC_CALIB_RSV_GND 0x40 #define CCADC_CALIB_RSV_25MV 0x80 #define CCADC_CALIB_ANA_PARAM 0x1B #define SAMPLE_COUNT 16 #define ADC_WAIT_COUNT 10 #define CCADC_MAX_25MV 30000 #define CCADC_MIN_25MV 20000 #define CCADC_MAX_0UV -4000 #define CCADC_MIN_0UV -7000 #define CCADC_INTRINSIC_OFFSET 0xC000 struct pm8xxx_ccadc_chip { struct device *dev; struct dentry *dent; unsigned int batt_temp_channel; u16 ccadc_offset; int ccadc_gain_uv; unsigned int revision; unsigned int calib_delay_ms; unsigned long last_calib_time; int last_calib_temp; int eoc_irq; int r_sense_uohm; struct delayed_work calib_ccadc_work; struct mutex calib_mutex; bool periodic_wakeup; }; static struct pm8xxx_ccadc_chip *the_chip; #ifdef DEBUG static s64 microvolt_to_ccadc_reading(struct pm8xxx_ccadc_chip *chip, s64 cc) { return div_s64(uv * CCADC_READING_RESOLUTION_D, CCADC_READING_RESOLUTION_N); } #endif static int cc_adjust_for_offset(u16 raw) { /* this has the intrinsic offset */ return (int)raw - the_chip->ccadc_offset; } #define GAIN_REFERENCE_UV 25000 /* * gain compensation for ccadc readings - common for vsense based and * couloumb counter based readings */ s64 pm8xxx_cc_adjust_for_gain(s64 uv) { if (the_chip == NULL || the_chip->ccadc_gain_uv == 0) return uv; return div_s64(uv * GAIN_REFERENCE_UV, the_chip->ccadc_gain_uv); } EXPORT_SYMBOL(pm8xxx_cc_adjust_for_gain); static int pm_ccadc_masked_write(struct pm8xxx_ccadc_chip *chip, u16 addr, u8 mask, u8 val) { int rc; u8 reg; rc = pm8xxx_readb(chip->dev->parent, addr, ®); if (rc) { pr_err("read failed addr = %03X, rc = %d\n", addr, rc); return rc; } reg &= ~mask; reg |= val & mask; rc = pm8xxx_writeb(chip->dev->parent, addr, reg); if (rc) { pr_err("write failed addr = %03X, rc = %d\n", addr, rc); return rc; } return 0; } #define REG_SBI_CONFIG 0x04F #define PAGE3_ENABLE_MASK 0x6 static int calib_ccadc_enable_trim_access(struct pm8xxx_ccadc_chip *chip, u8 *sbi_config) { u8 reg; int rc; rc = pm8xxx_readb(chip->dev->parent, REG_SBI_CONFIG, sbi_config); if (rc) { pr_err("error = %d reading sbi config reg\n", rc); return rc; } reg = *sbi_config | PAGE3_ENABLE_MASK; return pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, reg); } static int calib_ccadc_restore_trim_access(struct pm8xxx_ccadc_chip *chip, u8 sbi_config) { return pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, sbi_config); } static int calib_ccadc_enable_arbiter(struct pm8xxx_ccadc_chip *chip) { int rc; /* enable Arbiter, must be sent twice */ rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL, SEL_CCADC_BIT | EN_ARB_BIT, SEL_CCADC_BIT | EN_ARB_BIT); if (rc < 0) { pr_err("error = %d enabling arbiter for offset\n", rc); return rc; } rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL, SEL_CCADC_BIT | EN_ARB_BIT, SEL_CCADC_BIT | EN_ARB_BIT); if (rc < 0) { pr_err("error = %d writing ADC_ARB_SECP_CNTRL\n", rc); return rc; } return 0; } static int calib_start_conv(struct pm8xxx_ccadc_chip *chip, u16 *result) { int rc, i; u8 data_msb, data_lsb, reg; /* Start conversion */ rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL, START_CONV_BIT, START_CONV_BIT); if (rc < 0) { pr_err("error = %d starting offset meas\n", rc); return rc; } /* Wait for End of conversion */ for (i = 0; i < ADC_WAIT_COUNT; i++) { rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_CNTRL, ®); if (rc < 0) { pr_err("error = %d read eoc for offset\n", rc); return rc; } if ((reg & (START_CONV_BIT | EOC_CONV_BIT)) != EOC_CONV_BIT) msleep(20); else break; } if (i == ADC_WAIT_COUNT) { pr_err("waited too long for offset eoc returning -EBUSY\n"); return -EBUSY; } rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_DATA0, &data_lsb); if (rc < 0) { pr_err("error = %d reading offset lsb\n", rc); return rc; } rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_DATA1, &data_msb); if (rc < 0) { pr_err("error = %d reading offset msb\n", rc); return rc; } *result = (data_msb << 8) | data_lsb; return 0; } static int calib_ccadc_read_trim(struct pm8xxx_ccadc_chip *chip, int addr, u8 *data_msb, u8 *data_lsb) { int rc; u8 sbi_config; calib_ccadc_enable_trim_access(chip, &sbi_config); rc = pm8xxx_readb(chip->dev->parent, addr, data_msb); if (rc < 0) { pr_err("error = %d read msb\n", rc); return rc; } rc = pm8xxx_readb(chip->dev->parent, addr + 1, data_lsb); if (rc < 0) { pr_err("error = %d read lsb\n", rc); return rc; } calib_ccadc_restore_trim_access(chip, sbi_config); return 0; } static void calib_ccadc_read_offset_and_gain(struct pm8xxx_ccadc_chip *chip, int *gain, u16 *offset) { u8 data_msb; u8 data_lsb; int rc; rc = calib_ccadc_read_trim(chip, CCADC_FULLSCALE_TRIM1, &data_msb, &data_lsb); *gain = (data_msb << 8) | data_lsb; rc = calib_ccadc_read_trim(chip, CCADC_OFFSET_TRIM1, &data_msb, &data_lsb); *offset = (data_msb << 8) | data_lsb; pr_debug("raw gain trim = 0x%x offset trim =0x%x\n", *gain, *offset); *gain = pm8xxx_ccadc_reading_to_microvolt(chip->revision, (s64)*gain - *offset); pr_debug("gain uv = %duV offset=0x%x\n", *gain, *offset); } #define CCADC_PROGRAM_TRIM_COUNT 2 #define ADC_ARB_BMS_CNTRL_CCADC_SHIFT 4 #define ADC_ARB_BMS_CNTRL_CONV_MASK 0x03 #define BMS_CONV_IN_PROGRESS 0x2 static int calib_ccadc_program_trim(struct pm8xxx_ccadc_chip *chip, int addr, u8 data_msb, u8 data_lsb, int wait) { int i, rc, loop; u8 cntrl, sbi_config; bool in_progress = 0; loop = wait ? CCADC_PROGRAM_TRIM_COUNT : 0; calib_ccadc_enable_trim_access(chip, &sbi_config); for (i = 0; i < loop; i++) { rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_BMS_CNTRL, &cntrl); if (rc < 0) { pr_err("error = %d reading ADC_ARB_BMS_CNTRL\n", rc); return rc; } /* break if a ccadc conversion is not happening */ in_progress = (((cntrl >> ADC_ARB_BMS_CNTRL_CCADC_SHIFT) & ADC_ARB_BMS_CNTRL_CONV_MASK) == BMS_CONV_IN_PROGRESS); if (!in_progress) break; } if (in_progress) { pr_debug("conv in progress cannot write trim,returing EBUSY\n"); return -EBUSY; } rc = pm8xxx_writeb(chip->dev->parent, addr, data_msb); if (rc < 0) { pr_err("error = %d write msb = 0x%x\n", rc, data_msb); return rc; } rc = pm8xxx_writeb(chip->dev->parent, addr + 1, data_lsb); if (rc < 0) { pr_err("error = %d write lsb = 0x%x\n", rc, data_lsb); return rc; } calib_ccadc_restore_trim_access(chip, sbi_config); return 0; } static int get_batt_temp(struct pm8xxx_ccadc_chip *chip, int *batt_temp) { int rc; struct pm8xxx_adc_chan_result result; rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); if (rc) { pr_err("error reading batt_temp_channel = %d, rc = %d\n", chip->batt_temp_channel, rc); return rc; } *batt_temp = result.physical; pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, result.measurement); return 0; } static int get_current_time(unsigned long *now_tm_sec) { struct rtc_time tm; struct rtc_device *rtc; int rc; rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); if (rtc == NULL) { pr_err("%s: unable to open rtc device (%s)\n", __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); return -EINVAL; } rc = rtc_read_time(rtc, &tm); if (rc) { pr_err("Error reading rtc device (%s) : %d\n", CONFIG_RTC_HCTOSYS_DEVICE, rc); return rc; } rc = rtc_valid_tm(&tm); if (rc) { pr_err("Invalid RTC time (%s): %d\n", CONFIG_RTC_HCTOSYS_DEVICE, rc); return rc; } rtc_tm_to_time(&tm, now_tm_sec); return 0; } static void __pm8xxx_calib_ccadc(int sample_count) { u8 data_msb, data_lsb, sec_cntrl; int result_offset, result_gain; u16 result; int i, rc; if (!the_chip) { pr_err("chip not initialized\n"); return; } pr_debug("sample_count = %d\n", sample_count); mutex_lock(&the_chip->calib_mutex); rc = pm8xxx_readb(the_chip->dev->parent, ADC_ARB_SECP_CNTRL, &sec_cntrl); if (rc < 0) { pr_err("error = %d reading ADC_ARB_SECP_CNTRL\n", rc); goto calibration_unlock; } rc = calib_ccadc_enable_arbiter(the_chip); if (rc < 0) { pr_err("error = %d enabling arbiter for offset\n", rc); goto bail; } /* * Set decimation ratio to 4k, lower ratio may be used in order to speed * up, pending verification through bench */ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM, CCADC_CALIB_DIG_PARAM); if (rc < 0) { pr_err("error = %d writing ADC_ARB_SECP_DIG_PARAM\n", rc); goto bail; } result_offset = 0; for (i = 0; i < sample_count; i++) { /* Short analog inputs to CCADC internally to ground */ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV, CCADC_CALIB_RSV_GND); if (rc < 0) { pr_err("error = %d selecting gnd voltage\n", rc); goto bail; } /* Enable CCADC */ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_ANA_PARAM, CCADC_CALIB_ANA_PARAM); if (rc < 0) { pr_err("error = %d enabling ccadc\n", rc); goto bail; } rc = calib_start_conv(the_chip, &result); if (rc < 0) { pr_err("error = %d for zero volt measurement\n", rc); goto bail; } result_offset += result; } result_offset = result_offset / sample_count; pr_debug("offset result_offset = 0x%x, voltage = %llduV\n", result_offset, pm8xxx_ccadc_reading_to_microvolt(the_chip->revision, ((s64)result_offset - CCADC_INTRINSIC_OFFSET))); the_chip->ccadc_offset = result_offset; data_msb = the_chip->ccadc_offset >> 8; data_lsb = the_chip->ccadc_offset; rc = calib_ccadc_program_trim(the_chip, CCADC_OFFSET_TRIM1, data_msb, data_lsb, 1); if (rc) { pr_debug("error = %d programming offset trim 0x%02x 0x%02x\n", rc, data_msb, data_lsb); /* enable the interrupt and write it when it fires */ enable_irq(the_chip->eoc_irq); } rc = calib_ccadc_enable_arbiter(the_chip); if (rc < 0) { pr_err("error = %d enabling arbiter for gain\n", rc); goto bail; } /* * Set decimation ratio to 4k, lower ratio may be used in order to speed * up, pending verification through bench */ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM, CCADC_CALIB_DIG_PARAM); if (rc < 0) { pr_err("error = %d enabling decimation ration for gain\n", rc); goto bail; } result_gain = 0; for (i = 0; i < sample_count; i++) { rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV, CCADC_CALIB_RSV_25MV); if (rc < 0) { pr_err("error = %d selecting 25mV for gain\n", rc); goto bail; } /* Enable CCADC */ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_ANA_PARAM, CCADC_CALIB_ANA_PARAM); if (rc < 0) { pr_err("error = %d enabling ccadc\n", rc); goto bail; } rc = calib_start_conv(the_chip, &result); if (rc < 0) { pr_err("error = %d for adc reading 25mV\n", rc); goto bail; } result_gain += result; } result_gain = result_gain / sample_count; /* * result_offset includes INTRINSIC OFFSET * the_chip->ccadc_gain_uv will be the actual voltage * measured for 25000UV */ the_chip->ccadc_gain_uv = pm8xxx_ccadc_reading_to_microvolt( the_chip->revision, ((s64)result_gain - result_offset)); pr_debug("gain result_gain = 0x%x, voltage = %d microVolts\n", result_gain, the_chip->ccadc_gain_uv); data_msb = result_gain >> 8; data_lsb = result_gain; rc = calib_ccadc_program_trim(the_chip, CCADC_FULLSCALE_TRIM1, data_msb, data_lsb, 0); if (rc) pr_debug("error = %d programming gain trim\n", rc); bail: pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_CNTRL, sec_cntrl); calibration_unlock: mutex_unlock(&the_chip->calib_mutex); } static void pm8xxx_calib_ccadc_quick(void) { __pm8xxx_calib_ccadc(2); } void pm8xxx_calib_ccadc(void) { __pm8xxx_calib_ccadc(SAMPLE_COUNT); } EXPORT_SYMBOL(pm8xxx_calib_ccadc); static void calibrate_ccadc_work(struct work_struct *work) { struct pm8xxx_ccadc_chip *chip = container_of(work, struct pm8xxx_ccadc_chip, calib_ccadc_work.work); pm8xxx_calib_ccadc(); schedule_delayed_work(&chip->calib_ccadc_work, round_jiffies_relative(msecs_to_jiffies (chip->calib_delay_ms))); } static irqreturn_t pm8921_bms_ccadc_eoc_handler(int irq, void *data) { u8 data_msb, data_lsb; struct pm8xxx_ccadc_chip *chip = data; int rc; if (!the_chip) goto out; pr_debug("irq = %d triggered\n", irq); data_msb = chip->ccadc_offset >> 8; data_lsb = chip->ccadc_offset; rc = calib_ccadc_program_trim(chip, CCADC_OFFSET_TRIM1, data_msb, data_lsb, 0); disable_irq_nosync(chip->eoc_irq); out: return IRQ_HANDLED; } #define CCADC_IBAT_DIG_PARAM 0xA3 #define CCADC_IBAT_RSV 0x10 #define CCADC_IBAT_ANA_PARAM 0x1A static int ccadc_get_rsense_voltage(int *voltage_uv) { u16 raw; int result; int rc = 0; rc = calib_ccadc_enable_arbiter(the_chip); if (rc < 0) { pr_err("error = %d enabling arbiter for offset\n", rc); return rc; } rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM, CCADC_IBAT_DIG_PARAM); if (rc < 0) { pr_err("error = %d writing ADC_ARB_SECP_DIG_PARAM\n", rc); return rc; } rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV, CCADC_IBAT_RSV); if (rc < 0) { pr_err("error = %d selecting rsense\n", rc); return rc; } rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_ANA_PARAM, CCADC_IBAT_ANA_PARAM); if (rc < 0) { pr_err("error = %d enabling ccadc\n", rc); return rc; } rc = calib_start_conv(the_chip, &raw); if (rc < 0) { pr_err("error = %d for zero volt measurement\n", rc); return rc; } pr_debug("Vsense raw = 0x%x\n", raw); result = cc_adjust_for_offset(raw); pr_debug("Vsense after offset raw = 0x%x offset=0x%x\n", result, the_chip->ccadc_offset); *voltage_uv = pm8xxx_ccadc_reading_to_microvolt(the_chip->revision, ((s64)result)); pr_debug("Vsense before gain of %d = %d uV\n", the_chip->ccadc_gain_uv, *voltage_uv); *voltage_uv = pm8xxx_cc_adjust_for_gain(*voltage_uv); pr_debug("Vsense = %d uV\n", *voltage_uv); return 0; } int pm8xxx_ccadc_get_battery_current(int *bat_current_ua) { int voltage_uv = 0, rc; rc = ccadc_get_rsense_voltage(&voltage_uv); if (rc) { pr_err("cant get voltage across rsense rc = %d\n", rc); return rc; } *bat_current_ua = div_s64((s64)voltage_uv * 1000000LL, the_chip->r_sense_uohm); /* * ccadc reads +ve current when the battery is charging * We need to return -ve if the battery is charging */ *bat_current_ua = -1 * (*bat_current_ua); pr_debug("bat current = %d ma\n", *bat_current_ua); return 0; } EXPORT_SYMBOL(pm8xxx_ccadc_get_battery_current); static int get_reg(void *data, u64 * val) { int addr = (int)data; int ret; u8 temp; ret = pm8xxx_readb(the_chip->dev->parent, addr, &temp); if (ret) { pr_err("pm8xxx_readb to %x value = %d errored = %d\n", addr, temp, ret); return -EAGAIN; } *val = temp; return 0; } static int set_reg(void *data, u64 val) { int addr = (int)data; int ret; u8 temp; temp = (u8) val; ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp); if (ret) { pr_err("pm8xxx_writeb to %x value = %d errored = %d\n", addr, temp, ret); return -EAGAIN; } return 0; } DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); static int get_calc(void *data, u64 * val) { int ibat, rc; rc = pm8xxx_ccadc_get_battery_current(&ibat); *val = ibat; return rc; } DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, NULL, "%lld\n"); static void create_debugfs_entries(struct pm8xxx_ccadc_chip *chip) { chip->dent = debugfs_create_dir("pm8xxx-ccadc", NULL); if (IS_ERR(chip->dent)) { pr_err("ccadc couldnt create debugfs dir\n"); return; } debugfs_create_file("CCADC_ANA_PARAM", 0644, chip->dent, (void *)CCADC_ANA_PARAM, ®_fops); debugfs_create_file("CCADC_DIG_PARAM", 0644, chip->dent, (void *)CCADC_DIG_PARAM, ®_fops); debugfs_create_file("CCADC_RSV", 0644, chip->dent, (void *)CCADC_RSV, ®_fops); debugfs_create_file("CCADC_DATA0", 0644, chip->dent, (void *)CCADC_DATA0, ®_fops); debugfs_create_file("CCADC_DATA1", 0644, chip->dent, (void *)CCADC_DATA1, ®_fops); debugfs_create_file("CCADC_OFFSET_TRIM1", 0644, chip->dent, (void *)CCADC_OFFSET_TRIM1, ®_fops); debugfs_create_file("CCADC_OFFSET_TRIM0", 0644, chip->dent, (void *)CCADC_OFFSET_TRIM0, ®_fops); debugfs_create_file("CCADC_FULLSCALE_TRIM1", 0644, chip->dent, (void *)CCADC_FULLSCALE_TRIM1, ®_fops); debugfs_create_file("CCADC_FULLSCALE_TRIM0", 0644, chip->dent, (void *)CCADC_FULLSCALE_TRIM0, ®_fops); debugfs_create_file("show_ibatt", 0644, chip->dent, (void *)0, &calc_fops); } static int __devinit pm8xxx_ccadc_probe(struct platform_device *pdev) { int rc = 0; struct pm8xxx_ccadc_chip *chip; struct resource *res; const struct pm8xxx_ccadc_platform_data *pdata = pdev->dev.platform_data; if (!pdata) { pr_err("missing platform data\n"); return -EINVAL; } res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "PM8921_BMS_CCADC_EOC"); if (!res) { pr_err("failed to get irq\n"); return -EINVAL; } chip = kzalloc(sizeof(struct pm8xxx_ccadc_chip), GFP_KERNEL); if (!chip) { pr_err("Cannot allocate pm_bms_chip\n"); return -ENOMEM; } chip->dev = &pdev->dev; chip->revision = pm8xxx_get_revision(chip->dev->parent); chip->eoc_irq = res->start; chip->r_sense_uohm = pdata->r_sense_uohm; chip->calib_delay_ms = pdata->calib_delay_ms; chip->batt_temp_channel = pdata->ccadc_cdata.batt_temp_channel; chip->periodic_wakeup = pdata->periodic_wakeup; mutex_init(&chip->calib_mutex); calib_ccadc_read_offset_and_gain(chip, &chip->ccadc_gain_uv, &chip->ccadc_offset); irq_set_status_flags(chip->eoc_irq, IRQ_NOAUTOEN); rc = request_irq(chip->eoc_irq, pm8921_bms_ccadc_eoc_handler, IRQF_TRIGGER_RISING, "bms_eoc_ccadc", chip); if (rc) { pr_err("failed to request %d irq rc= %d\n", chip->eoc_irq, rc); goto free_chip; } platform_set_drvdata(pdev, chip); the_chip = chip; INIT_DELAYED_WORK(&chip->calib_ccadc_work, calibrate_ccadc_work); schedule_delayed_work(&chip->calib_ccadc_work, 0); create_debugfs_entries(chip); return 0; free_chip: mutex_destroy(&chip->calib_mutex); kfree(chip); return rc; } static int __devexit pm8xxx_ccadc_remove(struct platform_device *pdev) { struct pm8xxx_ccadc_chip *chip = platform_get_drvdata(pdev); debugfs_remove_recursive(chip->dent); the_chip = NULL; kfree(chip); return 0; } static int pm8xxx_ccadc_suspend(struct device *dev) { struct pm8xxx_ccadc_chip *chip = dev_get_drvdata(dev); cancel_delayed_work_sync(&chip->calib_ccadc_work); return 0; } #define CCADC_CALIB_TEMP_THRESH 20 static int pm8xxx_ccadc_resume(struct device *dev) { int rc, batt_temp, delta_temp; unsigned long current_time_sec; unsigned long time_since_last_calib; rc = get_batt_temp(the_chip, &batt_temp); if (rc) { pr_err("unable to get batt_temp: %d\n", rc); return 0; } rc = get_current_time(¤t_time_sec); if (rc) { pr_err("unable to get current time: %d\n", rc); return 0; } if (the_chip->periodic_wakeup) { pm8xxx_calib_ccadc_quick(); return 0; } if (current_time_sec > the_chip->last_calib_time) { time_since_last_calib = current_time_sec - the_chip->last_calib_time; delta_temp = abs(batt_temp - the_chip->last_calib_temp); pr_debug("time since last calib: %lu, delta_temp = %d\n", time_since_last_calib, delta_temp); if (time_since_last_calib >= the_chip->calib_delay_ms/1000 || delta_temp > CCADC_CALIB_TEMP_THRESH) { the_chip->last_calib_time = current_time_sec; the_chip->last_calib_temp = batt_temp; schedule_delayed_work(&the_chip->calib_ccadc_work, 0); } else { schedule_delayed_work(&the_chip->calib_ccadc_work, msecs_to_jiffies(the_chip->calib_delay_ms - (time_since_last_calib * 1000))); } } return 0; } static const struct dev_pm_ops pm8xxx_ccadc_pm_ops = { .suspend = pm8xxx_ccadc_suspend, .resume = pm8xxx_ccadc_resume, }; static struct platform_driver pm8xxx_ccadc_driver = { .probe = pm8xxx_ccadc_probe, .remove = __devexit_p(pm8xxx_ccadc_remove), .driver = { .name = PM8XXX_CCADC_DEV_NAME, .owner = THIS_MODULE, .pm = &pm8xxx_ccadc_pm_ops, }, }; static int __init pm8xxx_ccadc_init(void) { return platform_driver_register(&pm8xxx_ccadc_driver); } static void __exit pm8xxx_ccadc_exit(void) { platform_driver_unregister(&pm8xxx_ccadc_driver); } module_init(pm8xxx_ccadc_init); module_exit(pm8xxx_ccadc_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("PMIC8XXX ccadc driver"); MODULE_VERSION("1.0"); MODULE_ALIAS("platform:" PM8XXX_CCADC_DEV_NAME);