summaryrefslogtreecommitdiff
path: root/drivers/battery/sprd27x3_charger4samsung.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/battery/sprd27x3_charger4samsung.c')
-rwxr-xr-xdrivers/battery/sprd27x3_charger4samsung.c960
1 files changed, 960 insertions, 0 deletions
diff --git a/drivers/battery/sprd27x3_charger4samsung.c b/drivers/battery/sprd27x3_charger4samsung.c
new file mode 100755
index 00000000..bc69c8ab
--- /dev/null
+++ b/drivers/battery/sprd27x3_charger4samsung.c
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/reboot.h>
+#include <linux/string.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+
+#include <mach/hardware.h>
+#include <mach/sci.h>
+#include <mach/sci_glb_regs.h>
+#include <mach/gpio.h>
+#include <mach/adi.h>
+#include <mach/adc.h>
+#include <mach/usb.h>
+#include <mach/irqs.h>
+#include <linux/irq.h>
+#include <asm/io.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <mach/pinmap.h>
+
+#include <linux/battery/sec_charger.h>
+#define BATT_DETECT 43
+#define SIOP_CHARGING_LIMIT_CURRENT1 300
+#define SIOP_CHARGING_LIMIT_CURRENT2 350
+#define SIOP_CHARGING_LIMIT_CURRENT3 400
+#define SIOP_CHARGING_LIMIT_CURRENT4 450
+
+extern void sprdfgu_adp_status_set(int plugin);
+extern int sci_adc_get_value(unsigned chan, int scale);
+void sprdchg_set_chg_ovp(uint32_t ovp_vol);
+void sprdchg_set_cccvpoint(unsigned int cvpoint);
+// static int sprdbat_adjust_cccvpoint(void);
+static uint32_t sprdchg_tune_endvol_cccv(uint32_t chg_end_vol, uint32_t cal_cccv);
+void sprdchg_stop_charge(void);
+void sprdchg_start_charge(void);
+
+#define EIC_VCHG_OVI (A_EIC_START + 6)
+static uint32_t irq_vchg_ovi;
+static uint32_t irq_vf_det;
+static int vchg_ovp_state = 0;
+static int is_fully_charged = 0;
+
+struct sprdbat_auxadc_cal adc_cal = {
+ 4200, 3310,
+ 3600, 2832,
+ SPRDBAT_AUXADC_CAL_NO,
+};
+
+static ssize_t sprdbat_store_caliberate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+static ssize_t sprdbat_show_caliberate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+uint32_t sprdchg_read_vbat_vol(void);
+uint32_t sprdchg_get_cccvpoint(void);
+uint32_t sprdchg_read_vchg_vol(void);
+void sprdchg_stop_charge(void);
+static uint32_t sprdchg_read_chg_current(void);
+
+#define SPRDBAT_CALIBERATE_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IRUGO | S_IWUSR | S_IWGRP, }, \
+ .show = sprdbat_show_caliberate, \
+ .store = sprdbat_store_caliberate, \
+}
+#define SPRDBAT_CALIBERATE_ATTR_RO(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IRUGO, }, \
+ .show = sprdbat_show_caliberate, \
+}
+#define SPRDBAT_CALIBERATE_ATTR_WO(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IWUSR | S_IWGRP, }, \
+ .store = sprdbat_store_caliberate, \
+}
+
+static struct device_attribute sprd_caliberate[] = {
+ SPRDBAT_CALIBERATE_ATTR_RO(real_time_voltage),
+ SPRDBAT_CALIBERATE_ATTR_WO(stop_charge),
+ SPRDBAT_CALIBERATE_ATTR_RO(real_time_current),
+ SPRDBAT_CALIBERATE_ATTR_WO(battery_0),
+ SPRDBAT_CALIBERATE_ATTR_WO(battery_1),
+ SPRDBAT_CALIBERATE_ATTR(hw_switch_point),
+ SPRDBAT_CALIBERATE_ATTR_RO(charger_voltage),
+ SPRDBAT_CALIBERATE_ATTR_RO(real_time_vbat_adc),
+ SPRDBAT_CALIBERATE_ATTR_WO(save_capacity),
+};
+
+enum sprdbat_attribute {
+ BATTERY_VOLTAGE = 0,
+ STOP_CHARGE,
+ BATTERY_NOW_CURRENT,
+ BATTERY_0,
+ BATTERY_1,
+ HW_SWITCH_POINT,
+ CHARGER_VOLTAGE,
+ BATTERY_ADC,
+ SAVE_CAPACITY,
+};
+
+extern struct device_attribute sprd_caliberate[];
+static ssize_t sprdbat_store_caliberate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long set_value;
+ const ptrdiff_t off = attr - sprd_caliberate;
+
+ set_value = simple_strtoul(buf, NULL, 10);
+ pr_info("battery calibrate value %d %lu\n", off, set_value);
+
+ //mutex_lock(&sprdbat_data->lock);
+ switch (off) {
+ case STOP_CHARGE:
+ sprdchg_stop_charge();
+ break;
+ case BATTERY_0:
+ adc_cal.p0_vol = set_value & 0xffff; //only for debug
+ adc_cal.p0_adc = (set_value >> 16) & 0xffff;
+ break;
+ case BATTERY_1:
+ adc_cal.p1_vol = set_value & 0xffff;
+ adc_cal.p1_adc = (set_value >> 16) & 0xffff;
+ adc_cal.cal_type = SPRDBAT_AUXADC_CAL_NV;
+ break;
+ case HW_SWITCH_POINT:
+ sprdchg_set_cccvpoint(set_value);
+
+ //if (sprdbat_cv_irq_dis && sprdfgu_is_new_chip()) {
+ break;
+ case SAVE_CAPACITY:
+ {
+#if 0
+ int temp = set_value - poweron_capacity;
+
+ pr_info("battery temp:%d\n", temp);
+ if (abs(temp) > SPRDBAT_VALID_CAP || 0 == set_value) {
+ pr_info("battery poweron capacity:%lu,%d\n",
+ set_value, poweron_capacity);
+ sprdbat_data->bat_info.capacity =
+ poweron_capacity;
+ } else {
+ pr_info("battery old capacity:%lu,%d\n",
+ set_value, poweron_capacity);
+ sprdbat_data->bat_info.capacity = set_value;
+ }
+ power_supply_changed(&sprdbat_data->battery);
+#endif
+ }
+ break;
+ default:
+ count = -EINVAL;
+ break;
+ }
+ //mutex_unlock(&sprdbat_data->lock);
+ return count;
+}
+
+static ssize_t sprdbat_show_caliberate(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i = 0;
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ int ret;
+ const ptrdiff_t off = attr - sprd_caliberate;
+ int adc_value;
+ int voltage;
+ uint32_t now_current;
+
+ ret = sci_adi_read(ANA_REG_GLB_CHGR_STATUS);
+
+ if (ret & BIT_CHGR_ON)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ switch (off) {
+ case BATTERY_VOLTAGE:
+ voltage = sprdchg_read_vbat_vol();
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", voltage);
+ break;
+ case BATTERY_NOW_CURRENT:
+ if (status == POWER_SUPPLY_STATUS_CHARGING) {
+ now_current = sprdchg_read_chg_current();
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ now_current);
+ } else {
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n",
+ "discharging");
+ }
+ break;
+ case HW_SWITCH_POINT:
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n",
+ sprdchg_get_cccvpoint());
+ break;
+ case CHARGER_VOLTAGE:
+ if (status == POWER_SUPPLY_STATUS_CHARGING) {
+ voltage = sprdchg_read_vchg_vol();
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", voltage);
+ } else {
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n",
+ "discharging");
+ }
+
+ break;
+ case BATTERY_ADC:
+ adc_value = sci_adc_get_value(ADC_CHANNEL_VBAT, false);
+ if (adc_value < 0)
+ adc_value = 0;
+ i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", adc_value);
+ break;
+ default:
+ i = -EINVAL;
+ break;
+ }
+
+ return i;
+}
+
+int sprdbat_creat_caliberate_attr(struct device *dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(sprd_caliberate); i++) {
+ rc = device_create_file(dev, &sprd_caliberate[i]);
+ if (rc)
+ goto sprd_attrs_failed;
+ }
+ goto sprd_attrs_succeed;
+
+sprd_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sprd_caliberate[i]);
+
+sprd_attrs_succeed:
+ return rc;
+}
+
+static int __init adc_cal_start(char *str)
+{
+ unsigned int adc_data[2] = { 0 };
+ char *cali_data = &str[1];
+ if (str) {
+ pr_info("adc_cal%s!\n", str);
+ sscanf(cali_data, "%d,%d", &adc_data[0], &adc_data[1]);
+ pr_info("adc_data: 0x%x 0x%x!\n", adc_data[0], adc_data[1]);
+ adc_cal.p0_vol = adc_data[0] & 0xffff;
+ adc_cal.p0_adc = (adc_data[0] >> 16) & 0xffff;
+ adc_cal.p1_vol = adc_data[1] & 0xffff;
+ adc_cal.p1_adc = (adc_data[1] >> 16) & 0xffff;
+ adc_cal.cal_type = SPRDBAT_AUXADC_CAL_NV;
+ printk
+ ("auxadc cal from cmdline ok!!! adc_data[0]: 0x%x, adc_data[1]:0x%x\n",
+ adc_data[0], adc_data[1]);
+ }
+ return 1;
+}
+__setup("adc_cal", adc_cal_start);
+
+static __used irqreturn_t sprdchg_vchg_ovi_irq(int irq, void *irq_data)
+{
+ int value;
+ struct sec_charger_info *charger = irq_data;
+
+ value = gpio_get_value(EIC_VCHG_OVI);
+ if (value) {
+ printk("charger ovi high\n");
+ vchg_ovp_state = 1;
+ sprdchg_stop_charge();
+ irq_set_irq_type(irq_vchg_ovi,
+ IRQ_TYPE_LEVEL_LOW);
+ } else {
+ vchg_ovp_state = 0;
+ if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) {
+ sprdchg_start_charge();
+ }
+ printk("charger ovi low\n");
+ irq_set_irq_type(irq_vchg_ovi,
+ IRQ_TYPE_LEVEL_HIGH);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static __used irqreturn_t sprdchg_vf_det_irq(int irq, void *irq_data)
+{
+ int adc;
+ struct sec_charger_info *charger = irq_data;
+
+ adc = gpio_get_value(BATT_DETECT);
+ if (adc != 0) {
+ sprdchg_stop_charge();
+ irq_set_irq_type(irq_vf_det,
+ IRQ_TYPE_LEVEL_LOW);
+ pr_info("sprdchg_get_batt_presence irq : %X\n", adc);
+ } else {
+ if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY) {
+ sprdchg_start_charge();
+ }
+ irq_set_irq_type(irq_vf_det,
+ IRQ_TYPE_LEVEL_HIGH);
+ }
+ return IRQ_HANDLED;
+}
+
+void sprdchg_init(struct sec_charger_info *charger)
+{
+ int ret = -ENODEV;
+
+ sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN);
+
+ pr_info("########### CHARGer init ############\n");
+
+ sprdchg_set_chg_ovp(SPRDBAT_OVP_STOP_VOL);
+
+/*OVP interrupt to sprd27x3_charger4samsung.c,and OVP state is polled by sec_battery.c */
+/*Had better use interrupt for OVP,if OVI occur,should stop charging immediately*/
+ ret = gpio_request(EIC_VCHG_OVI, "vchg_ovi");
+ if (ret) {
+ printk("failed to request gpio: %d\n", ret);
+ }
+ gpio_direction_input(EIC_VCHG_OVI);
+
+ irq_vchg_ovi = gpio_to_irq(EIC_VCHG_OVI);
+
+ set_irq_flags(irq_vchg_ovi, IRQF_VALID | IRQF_NOAUTOEN);
+ ret = request_irq(irq_vchg_ovi, sprdchg_vchg_ovi_irq,
+ IRQF_NO_SUSPEND, "sprdbat_vchg_ovi", charger);
+
+ ret = gpio_request(BATT_DETECT, "battery_Detect");
+ if (ret) {
+ printk("failed to request gpio: %d\n", ret);
+ }
+ gpio_direction_input(BATT_DETECT);
+ gpio_export(BATT_DETECT,1);
+
+ irq_vf_det = gpio_to_irq(BATT_DETECT);
+
+ set_irq_flags(irq_vf_det, IRQF_VALID | IRQF_NOAUTOEN);
+ ret = request_irq(irq_vf_det, sprdchg_vf_det_irq,
+ IRQF_NO_SUSPEND, "irq_vf_det", charger);
+
+ irq_set_irq_type(irq_vf_det, IRQ_TYPE_LEVEL_HIGH);
+ enable_irq(irq_vf_det);
+
+#if defined(CONFIG_MACH_YOUNG2)
+ __raw_writel((BITS_PIN_DS(1)|BITS_PIN_AF(3)|BIT_PIN_WPU|BIT_PIN_SLP_WPU|BIT_PIN_SLP_IE), SCI_ADDR(SPRD_PIN_BASE, 0x00D0));
+#endif
+//
+ /*sprdbat_adjust_cccvpoint();*/
+
+ /*if want to support 4.35V battery,please change SPRDBAT_CHG_END_VOL_PURE to 4350*/
+ {
+ uint32_t cv_point;
+ uint32_t target_cv;
+ extern int sci_efuse_cccv_cal_get(unsigned int *p_cal_data);
+ if (sci_efuse_cccv_cal_get(&cv_point)) {
+ printk("cccv_point efuse:%d\n", cv_point);
+ target_cv = sprdchg_tune_endvol_cccv(SPRDBAT_CHG_END_VOL_PURE, cv_point);
+ sprdchg_set_cccvpoint(target_cv);
+ printk("cccv_point sprdchg_tune_endvol_cccv:%d\n", target_cv);
+ } else {
+ sprdchg_set_cccvpoint(SPRDBAT_CCCV_DEFAULT);
+ printk("cccv_point default\n");
+ }
+ }
+
+ if (adc_cal.cal_type == SPRDBAT_AUXADC_CAL_NO) {
+ extern int sci_efuse_calibration_get(unsigned int *p_cal_data);
+ unsigned int efuse_cal_data[2] = { 0 };
+ if (sci_efuse_calibration_get(efuse_cal_data)) {
+ adc_cal.p0_vol = efuse_cal_data[0] & 0xffff;
+ adc_cal.p0_adc = (efuse_cal_data[0] >> 16) & 0xffff;
+ adc_cal.p1_vol = efuse_cal_data[1] & 0xffff;
+ adc_cal.p1_adc = (efuse_cal_data[1] >> 16) & 0xffff;
+ adc_cal.cal_type = SPRDBAT_AUXADC_CAL_CHIP;
+ printk
+ ("auxadc cal from efuse ok!!! efuse_cal_data[0]: 0x%x, efuse_cal_data[1]:0x%x\n",
+ efuse_cal_data[0], efuse_cal_data[1]);
+ }
+ }
+ sci_adi_write((ANA_CTL_EIC_BASE + 0x50), 100, (0xFFF)); //eic debunce
+ printk("ANA_CTL_EIC_BASE0x%x\n", sci_adi_read(ANA_CTL_EIC_BASE + 0x50));
+}
+
+static int sprdchg_get_batt_presence(void)
+{
+ int adc;
+
+ adc = gpio_get_value(BATT_DETECT);
+ pr_info("sprdchg_get_batt_presence : %X\n", adc);
+ if (adc==0)
+ return 1;
+ else
+ return 0;
+}
+
+uint16_t sprdchg_bat_adc_to_vol(uint16_t adcvalue)
+{
+ int32_t temp;
+
+ temp = adc_cal.p0_vol - adc_cal.p1_vol;
+ temp = temp * (adcvalue - adc_cal.p0_adc);
+ temp = temp / (adc_cal.p0_adc - adc_cal.p1_adc);
+ temp = temp + adc_cal.p0_vol;
+
+ return temp;
+}
+
+static uint16_t sprdbat_charger_adc_to_vol(uint16_t adcvalue)
+{
+ uint32_t result;
+ uint32_t vbat_vol = sprdchg_bat_adc_to_vol(adcvalue);
+ uint32_t m, n;
+ uint32_t bat_numerators, bat_denominators;
+ uint32_t vchg_numerators, vchg_denominators;
+
+ sci_adc_get_vol_ratio(ADC_CHANNEL_VBAT, 0, &bat_numerators,
+ &bat_denominators);
+ sci_adc_get_vol_ratio(SPRDBAT_ADC_CHANNEL_VCHG, 0, &vchg_numerators,
+ &vchg_denominators);
+
+ ///v1 = vbat_vol*0.268 = vol_bat_m * r2 /(r1+r2)
+ n = bat_denominators * vchg_numerators;
+ m = vbat_vol * bat_numerators * (vchg_denominators);
+ result = (m + n / 2) / n;
+ return result;
+
+}
+
+uint32_t sprdchg_read_vchg_vol(void)
+{
+ int vchg_value;
+ vchg_value = sci_adc_get_value(SPRDBAT_ADC_CHANNEL_VCHG, false);
+ return sprdbat_charger_adc_to_vol(vchg_value);
+}
+
+void sprdchg_set_chg_ovp(uint32_t ovp_vol)
+{
+ uint32_t temp;
+
+ if (ovp_vol > SPRDBAT_CHG_OVP_LEVEL_MAX) {
+ ovp_vol = SPRDBAT_CHG_OVP_LEVEL_MAX;
+ }
+
+ if (ovp_vol < SPRDBAT_CHG_OVP_LEVEL_MIN) {
+ ovp_vol = SPRDBAT_CHG_OVP_LEVEL_MIN;
+ }
+
+ temp = ((ovp_vol - SPRDBAT_CHG_OVP_LEVEL_MIN) / 100);
+
+ sci_adi_clr(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN);
+
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL1,
+ BITS_VCHG_OVP_V(temp), BITS_VCHG_OVP_V(~0));
+
+ sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN);
+}
+
+void sprdchg_set_chg_cur(uint32_t chg_current)
+{
+ uint32_t temp;
+
+ if (chg_current > SPRDBAT_CHG_CUR_LEVEL_MAX) {
+ chg_current = SPRDBAT_CHG_CUR_LEVEL_MAX;
+ }
+
+ if (chg_current < SPRDBAT_CHG_CUR_LEVEL_MIN) {
+ chg_current = SPRDBAT_CHG_CUR_LEVEL_MIN;
+ }
+
+ /*if chg_current >= 1400,one step is 100mA*/
+ if (chg_current < 1400) {
+ temp = ((chg_current - 300) / 50);
+ } else {
+ temp = ((chg_current - 1400) / 100);
+ temp += 0x16;
+ }
+ printk("sprdchg_set_chg_cur : %d\n",
+ chg_current);
+
+ sci_adi_clr(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN);
+
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL1,
+ BITS_CHGR_CC_I(temp), BITS_CHGR_CC_I(~0));
+
+ sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_CHGR_CC_EN);
+}
+
+void sprdchg_set_cccvpoint(unsigned int cvpoint)
+{
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BITS_CHGR_CV_V(cvpoint), BITS_CHGR_CV_V(~0));
+
+}
+
+uint32_t sprdchg_get_cccvpoint(void)
+{
+ int shft = __ffs(BITS_CHGR_CV_V(~0));
+ return (sci_adi_read(ANA_REG_GLB_CHGR_CTRL0) & BITS_CHGR_CV_V(~0)) >>
+ shft;
+}
+static uint32_t sprdchg_tune_endvol_cccv(uint32_t chg_end_vol, uint32_t cal_cccv)
+{
+ uint32_t cv;
+
+ BUG_ON(chg_end_vol > 4400);
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BITS_CHGR_END_V(0), BITS_CHGR_END_V(~0));
+ if (chg_end_vol >= 4200) {
+ if (chg_end_vol < 4300) {
+ cv = (((chg_end_vol - 4200) * 10) +
+ (ONE_CCCV_STEP_VOL >> 1)) / ONE_CCCV_STEP_VOL +
+ cal_cccv;
+ if (cv > SPRDBAT_CCCV_MAX) {
+ printk("sprdchg: cv > SPRDBAT_CCCV_MAX!\n");
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BITS_CHGR_END_V(1),
+ BITS_CHGR_END_V(~0));
+ return (cal_cccv - (((4300 - chg_end_vol) * 10) +
+ (ONE_CCCV_STEP_VOL >> 1)) /
+ ONE_CCCV_STEP_VOL);
+ } else {
+ return cv;
+ }
+ } else {
+ cv = (((chg_end_vol - 4300) * 10) +
+ (ONE_CCCV_STEP_VOL >> 1)) / ONE_CCCV_STEP_VOL +
+ cal_cccv;
+ if (cv > SPRDBAT_CCCV_MAX) {
+ printk("sprdchg: cv > SPRDBAT_CCCV_MAX!\n");
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BITS_CHGR_END_V(2),
+ BITS_CHGR_END_V(~0));
+ return (cal_cccv - (((4400 - chg_end_vol) * 10) +
+ (ONE_CCCV_STEP_VOL >> 1)) /
+ ONE_CCCV_STEP_VOL);
+ } else {
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BITS_CHGR_END_V(1),
+ BITS_CHGR_END_V(~0));
+ return cv;
+ }
+ }
+ } else {
+ cv = (((4200 - chg_end_vol) * 10) +
+ (ONE_CCCV_STEP_VOL >> 1)) / ONE_CCCV_STEP_VOL;
+ if (cv > cal_cccv) {
+ return 0;
+ } else {
+ return (cal_cccv - cv);
+ }
+ }
+}
+/*please do NOT call it on charge init phase,
+it only be used on SPRD code when cccv point be NOT calibrated by ATE */
+#if 0
+static int sprdbat_adjust_cccvpoint(void)
+{
+ uint32_t cv;
+ uint32_t vbat_now;
+
+ union power_supply_propval value;
+
+ psy_do_property("sec-fuelgauge", get,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, value);
+
+ vbat_now = value.intval;
+
+ if (vbat_now <= SPRDBAT_CHG_END_VOL_PURE) {
+ cv = ((SPRDBAT_CHG_END_VOL_PURE -
+ vbat_now) * 10) / ONE_CCCV_STEP_VOL + 1;
+ cv += sprdchg_get_cccvpoint();
+
+ printk("sprdbat_adjust_cccvpoint turn high cv:0x%x\n",
+ cv);
+ //BUG_ON(cv > SPRDBAT_CCCV_MAX);
+ if (cv > SPRDBAT_CCCV_MAX) {
+ cv = SPRDBAT_CCCV_MAX;
+ }
+
+ sprdchg_set_cccvpoint(cv);
+ } else {
+ cv = sprdchg_get_cccvpoint();
+ //BUG_ON(cv < (SPRDBAT_CCCV_MIN + 2));
+ if (cv < (SPRDBAT_CCCV_MIN + 1)) {
+ cv = SPRDBAT_CCCV_MIN + 1;
+ }
+ cv -= 1;
+
+ printk("sprdbat_adjust_cccvpoint turn low cv:0x%x\n",
+ cv);
+ sprdchg_set_cccvpoint(cv);
+ }
+ return 0;
+}
+#endif
+static void _sprdchg_set_recharge(void)
+{
+ sci_adi_set(ANA_REG_GLB_CHGR_CTRL2, BIT_RECHG);
+}
+
+static void _sprdchg_stop_recharge(void)
+{
+ sci_adi_clr(ANA_REG_GLB_CHGR_CTRL2, BIT_RECHG);
+}
+
+void sprdchg_stop_charge(void)
+{
+#if defined(CONFIG_ARCH_SCX15) ||defined(CONFIG_ADIE_SC2723S) ||defined(CONFIG_ADIE_SC2723)
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, BIT_CHGR_PD, BIT_CHGR_PD);
+#else
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BIT_CHGR_PD_RTCSET,
+ BIT_CHGR_PD_RTCCLR | BIT_CHGR_PD_RTCSET);
+#endif
+ _sprdchg_stop_recharge();
+}
+
+void sprdchg_start_charge(void)
+{
+#if defined(CONFIG_ARCH_SCX15) ||defined(CONFIG_ADIE_SC2723S) ||defined(CONFIG_ADIE_SC2723)
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0, 0, BIT_CHGR_PD);
+#else
+ sci_adi_write(ANA_REG_GLB_CHGR_CTRL0,
+ BIT_CHGR_PD_RTCCLR,
+ BIT_CHGR_PD_RTCCLR | BIT_CHGR_PD_RTCSET);
+#endif
+ _sprdchg_set_recharge();
+}
+
+// static int sprd_get_charging_health(struct sec_charger_info *charger);
+void sprdchg_put_chgcur(uint32_t chging_current);
+uint32_t sprdchg_get_chgcur_ave(void);
+
+
+void sprdchg_set_charge(struct sec_charger_info *charger)
+{
+ union power_supply_propval value;
+ pr_info("########### CHARGING ############\n");
+ //charger->cable_type = POWER_SUPPLY_TYPE_MAINS;
+
+ vchg_ovp_state = 0;
+ if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) {
+ if (is_fully_charged) {
+ psy_do_property("battery", get,
+ POWER_SUPPLY_PROP_CHARGE_NOW, value);
+ if (value.intval != SEC_BATTERY_CHARGING_2ND)
+ is_fully_charged = 0;
+ }
+ disable_irq_nosync(irq_vchg_ovi);
+
+ sprdchg_stop_charge();
+ } else {
+ if (((charger->siop_level < 100) && (charger->siop_level > 0)) &&
+ charger->cable_type == POWER_SUPPLY_TYPE_MAINS) {
+ if (charger->siop_level >= 80) {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT4);
+ } else if (charger->siop_level >= 70) {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT3);
+ } else if (charger->siop_level >= 60) {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT2);
+ } else {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT1);
+ }
+ } else {
+ sprdchg_set_chg_cur(charger->pdata->charging_current[
+ charger->cable_type].fast_charging_current);
+ }
+ sprdchg_start_charge();
+ irq_set_irq_type(irq_vchg_ovi, IRQ_TYPE_LEVEL_HIGH);
+ enable_irq(irq_vchg_ovi);
+ }
+}
+
+static uint32_t _sprdchg_read_chg_current(void)
+{
+ uint32_t vbat, isense;
+ uint32_t cnt = 0;
+
+ for (cnt = 0; cnt < 3; cnt++) {
+ isense =
+ sprdchg_bat_adc_to_vol(sci_adc_get_value(ADC_CHANNEL_ISENSE,
+ false));
+ vbat =
+ sprdchg_bat_adc_to_vol(sci_adc_get_value(ADC_CHANNEL_VBAT,
+ false));
+ if (isense >= vbat) {
+ break;
+ }
+ }
+ if (isense > vbat) {
+ uint32_t temp = ((isense - vbat) * 1000) / 68; //(vol/68mohm)
+ //printk(KERN_ERR "sprdchg: sprdchg_read_chg_current:%d\n", temp);
+ return temp;
+ } else {
+ printk(KERN_ERR
+ "chg_current warning....isense:%d....vbat:%d\n",
+ isense, vbat);
+ return 0;
+ }
+}
+
+uint32_t sprdchg_read_chg_current(void)
+{
+#define CUR_RESULT_NUM 9
+
+ int i, temp;
+ volatile int j;
+ uint32_t cur_result[CUR_RESULT_NUM];
+
+ for (i = 0; i < CUR_RESULT_NUM; i++) {
+ cur_result[i] = _sprdchg_read_chg_current();
+ }
+
+ for (j = 1; j <= CUR_RESULT_NUM - 1; j++) {
+ for (i = 0; i < CUR_RESULT_NUM - j; i++) {
+ if (cur_result[i] > cur_result[i + 1]) {
+ temp = cur_result[i];
+ cur_result[i] = cur_result[i + 1];
+ cur_result[i + 1] = temp;
+ }
+ }
+ }
+
+ return cur_result[1];
+}
+
+static int sprd_get_charging_status(struct sec_charger_info *charger)
+{
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+ int ret;
+ int cur;
+ union power_supply_propval value;
+
+ ret = sci_adi_read(ANA_REG_GLB_CHGR_STATUS);
+ printk("ANA_REG_GLB_CHGR_STATUS : 0x%x\n", ret);//for debuf
+
+ if (ret & BIT_CHGR_ON)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING) {
+#if 0 //deleted by mingwei
+ for (i = 0; i < SPRDBAT_AVERAGE_COUNT; i++) {
+ cur = sprdchg_read_chg_current();
+ sprdchg_put_chgcur(cur);
+ }
+ cur = sprdchg_get_chgcur_ave();
+ pr_info("charging current : %d\n", cur);
+ if ((cur <= charger->pdata->charging_current[
+ charger->cable_type].full_check_current_1st) \
+ && (cur > 0)) {
+ status = POWER_SUPPLY_STATUS_FULL;
+ is_fully_charged = 1;
+ }
+#endif
+ /*please use battery current of fuelgauge and cv status to check charging full condition,
+ because charging current of SPRD is unstable*/
+#if 1
+ psy_do_property("battery", get,
+ POWER_SUPPLY_PROP_CURRENT_NOW, value);
+ cur = value.intval;
+ pr_info("sprd_get_charging_status current : %d\n", cur);
+ if ((ret & BIT_CHGR_CV_STATUS)\
+ &&(cur <= charger->pdata->charging_current[
+ charger->cable_type].full_check_current_1st) )
+ status = POWER_SUPPLY_STATUS_FULL;
+#endif
+ }
+
+ if (is_fully_charged) {
+ status = POWER_SUPPLY_STATUS_FULL;
+ }
+
+ if (vchg_ovp_state) {
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+ pr_info("charging status : %d\n", status);
+
+ return status;
+}
+/*Had better use interrupt for OVP,if OVI occur,should stop charging immediately*/
+#if 0
+static int sprd_get_charging_health(struct sec_charger_info *charger)
+{
+ int health = POWER_SUPPLY_HEALTH_GOOD;
+ int ret;
+ static int before_flag = 0;
+
+ ret = sci_adi_read(ANA_REG_GLB_CHGR_STATUS);
+
+ if (ret & 0x01) {
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ sprdchg_stop_charge();
+ }
+
+ if ((before_flag == POWER_SUPPLY_HEALTH_OVERVOLTAGE) &&
+ (health == POWER_SUPPLY_HEALTH_GOOD)) {
+ sprdchg_set_chg_cur(charger->pdata->charging_current[
+ charger->cable_type].fast_charging_current);
+ sprdchg_start_charge();
+ pr_info("%s: not-charging -> charging\n", __func__);
+ }
+ before_flag = health;
+ pr_info("charging health : %d\n", health);
+
+ return health;
+}
+#endif
+
+uint32_t chg_cur_buf[SPRDBAT_AVERAGE_COUNT];
+void sprdchg_put_chgcur(uint32_t chging_current)
+{
+ static uint32_t cnt = 0;
+
+ if (cnt == SPRDBAT_AVERAGE_COUNT) {
+ cnt = 0;
+ }
+ chg_cur_buf[cnt++] = chging_current;
+}
+
+uint32_t sprdchg_get_chgcur_ave(void)
+{
+ uint32_t i, sum = 0;
+ int count = 0;
+
+ for (i = 0; i < SPRDBAT_AVERAGE_COUNT; i++) {
+ sum = sum + chg_cur_buf[i];
+ if (!chg_cur_buf[i])
+ count++;
+ }
+
+ if (count == SPRDBAT_AVERAGE_COUNT)
+ return 0;
+ else
+ return sum / (SPRDBAT_AVERAGE_COUNT - count);
+}
+
+uint32_t sprdchg_read_vbat_vol(void)
+{
+ uint32_t voltage;
+ voltage =
+ sprdchg_bat_adc_to_vol(sci_adc_get_value(ADC_CHANNEL_VBAT, false));
+
+ return voltage;
+}
+
+bool sec_hal_chg_init(struct sec_charger_info *charger)
+{
+ sprdchg_init(charger);
+
+ return true;
+}
+
+bool sec_hal_chg_suspend(struct sec_charger_info *charger)
+{
+ return true;
+}
+
+bool sec_hal_chg_resume(struct sec_charger_info *charger)
+{
+ return true;
+}
+
+bool sec_hal_chg_get_property(struct sec_charger_info *charger,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ val->intval = 1;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = sprd_get_charging_status(charger);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ //val->intval = sprd_get_charging_health(charger);
+ if (vchg_ovp_state) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else {
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ //val->intval = charger->cable_type;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = sprdchg_get_batt_presence();
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = 1;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool sec_hal_chg_set_property(struct sec_charger_info *charger,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ charger->status = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ charger->cable_type = val->intval;
+ sprdchg_set_charge(charger);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ charger->siop_level = val->intval;
+ if (((charger->siop_level < 100) && (charger->siop_level > 0)) &&
+ charger->cable_type == POWER_SUPPLY_TYPE_MAINS) {
+ if (charger->siop_level >= 80) {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT4);
+ } else if (charger->siop_level >= 70) {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT3);
+ } else if (charger->siop_level >= 60) {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT2);
+ } else {
+ sprdchg_set_chg_cur(SIOP_CHARGING_LIMIT_CURRENT1);
+ }
+ } else {
+ sprdchg_set_chg_cur(charger->pdata->charging_current[
+ charger->cable_type].fast_charging_current);
+ }
+ if (charger->cable_type != POWER_SUPPLY_TYPE_BATTERY)
+ sprdchg_start_charge();
+ break;
+ default:
+ return false;
+ }
+ return true;
+}