| /* 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 <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/platform_device.h> |
| #include <linux/errno.h> |
| #include <linux/mfd/pm8xxx/core.h> |
| #include <linux/mfd/pm8xxx/pm8xxx-adc.h> |
| #include <linux/mfd/pm8xxx/ccadc.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/ioport.h> |
| #include <linux/debugfs.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/rtc.h> |
| |
| #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); |