| /* |
| * Driver for the NXP PCA9468 battery charger. |
| * |
| * Copyright (C) 2018 NXP Semiconductor. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/err.h> |
| #include <linux/kernel.h> |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/mutex.h> |
| #include <linux/delay.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/power_supply.h> |
| #include <linux/gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/regmap.h> |
| #include <linux/rtc.h> |
| #include <linux/debugfs.h> |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| #include <linux/of_gpio.h> |
| #include "include/charger/pca9468_charger.h" |
| #include "include/sec_charging_common.h" |
| #include "include/sec_direct_charger.h" |
| #else |
| #include <linux/power/pca9468_charger.h> |
| #endif |
| |
| #if defined (CONFIG_OF) |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #endif /* CONFIG_OF */ |
| |
| #include <linux/pm_wakeup.h> |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| #include <linux/usb/usbpd.h> // Use Qualcomm USBPD PHY |
| #endif |
| |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| static int pca9468_usbpd_setup(struct pca9468_charger *pca9468); |
| #endif |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| static int pca9468_send_pd_message(struct pca9468_charger *pca9468, unsigned int msg_type); |
| static int get_system_current(struct pca9468_charger *pca9468); |
| #endif |
| |
| /*******************************/ |
| /* Switching charger control function */ |
| /*******************************/ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| char *charging_state_str[] = { |
| "NO_CHARGING", "CHECK_VBAT", "PRESET_DC", "CHECK_ACTIVE", "ADJUST_CC", |
| "START_CC", "CC_MODE", "START_CV", "CV_MODE", "CHARGING_DONE", |
| "ADJUST_TAVOL", "ADJUST_TACUR" |
| }; |
| |
| static int pca9468_read_reg(struct pca9468_charger *pca9468, unsigned reg, void *val) |
| { |
| int ret = 0; |
| |
| mutex_lock(&pca9468->i2c_lock); |
| ret = regmap_read(pca9468->regmap, reg, val); |
| mutex_unlock(&pca9468->i2c_lock); |
| if (ret < 0) |
| pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); |
| return ret; |
| } |
| |
| static int pca9468_bulk_read_reg(struct pca9468_charger *pca9468, int reg, void *val, int count) |
| { |
| int ret = 0; |
| |
| mutex_lock(&pca9468->i2c_lock); |
| ret = regmap_bulk_read(pca9468->regmap, reg, val, count); |
| mutex_unlock(&pca9468->i2c_lock); |
| if (ret < 0) |
| pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); |
| return ret; |
| } |
| |
| static int pca9468_write_reg(struct pca9468_charger *pca9468, int reg, u8 val) |
| { |
| int ret = 0; |
| |
| mutex_lock(&pca9468->i2c_lock); |
| ret = regmap_write(pca9468->regmap, reg, val); |
| mutex_unlock(&pca9468->i2c_lock); |
| if (ret < 0) |
| pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); |
| return ret; |
| } |
| |
| static int pca9468_update_reg(struct pca9468_charger *pca9468, int reg, u8 mask, u8 val) |
| { |
| int ret = 0; |
| |
| mutex_lock(&pca9468->i2c_lock); |
| ret = regmap_update_bits(pca9468->regmap, reg, mask, val); |
| if (reg == PCA9468_REG_START_CTRL && mask == PCA9468_BIT_STANDBY_EN) { |
| if (val == PCA9468_STANDBY_DONOT) { |
| pr_info("%s: PCA9468_STANDBY_DONOT 50ms\n", __func__); |
| /* Wait 50ms, first to keep the start-up sequence */ |
| msleep(50); |
| } else { |
| pr_info("%s: PCA9468_STANDBY_FORCED 5ms\n", __func__); |
| /* Wait 5ms to keep the shutdown sequence */ |
| usleep_range(5000, 6000); |
| } |
| } |
| mutex_unlock(&pca9468->i2c_lock); |
| if (ret < 0) |
| pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); |
| return ret; |
| } |
| |
| static int pca9468_read_adc(struct pca9468_charger *pca9468, u8 adc_ch); |
| |
| static int pca9468_set_charging_state(struct pca9468_charger *pca9468, unsigned int charging_state) { |
| union power_supply_propval value = {0,}; |
| static int prev_val = DC_STATE_NO_CHARGING; |
| |
| pca9468->charging_state = charging_state; |
| |
| switch (charging_state) { |
| case DC_STATE_NO_CHARGING: |
| value.intval = SEC_DIRECT_CHG_MODE_DIRECT_OFF; |
| break; |
| case DC_STATE_CHECK_VBAT: |
| value.intval = SEC_DIRECT_CHG_MODE_DIRECT_CHECK_VBAT; |
| break; |
| case DC_STATE_PRESET_DC: |
| value.intval = SEC_DIRECT_CHG_MODE_DIRECT_PRESET; |
| break; |
| case DC_STATE_CHECK_ACTIVE: |
| case DC_STATE_START_CC: |
| case DC_STATE_START_CV: |
| case DC_STATE_ADJUST_TAVOL: |
| case DC_STATE_ADJUST_TACUR: |
| value.intval = SEC_DIRECT_CHG_MODE_DIRECT_ON_ADJUST; |
| break; |
| case DC_STATE_CC_MODE: |
| case DC_STATE_CV_MODE: |
| value.intval = SEC_DIRECT_CHG_MODE_DIRECT_ON; |
| break; |
| case DC_STATE_CHARGING_DONE: |
| value.intval = SEC_DIRECT_CHG_MODE_DIRECT_DONE; |
| break; |
| default: |
| return -1; |
| break; |
| } |
| |
| if (prev_val == value.intval) |
| return -1; |
| |
| prev_val = value.intval; |
| psy_do_property(pca9468->pdata->sec_dc_name, set, |
| POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, value); |
| |
| return 0; |
| } |
| |
| static void pca9468_init_adc_val(struct pca9468_charger *pca9468, int val) |
| { |
| int i = 0; |
| |
| for (i = 0; i < ADCCH_MAX; ++i) |
| pca9468->adc_val[i] = val; |
| } |
| |
| static void pca9468_test_read(struct pca9468_charger *pca9468) |
| { |
| int address = 0; |
| unsigned int val; |
| char str[1024] = { 0, }; |
| |
| for (address = PCA9468_REG_INT1_STS; address <= PCA9468_REG_STS_ADC_9; address++) { |
| pca9468_read_reg(pca9468, address, &val); |
| sprintf(str + strlen(str), "[0x%02x]0x%02x, ", address, val); |
| } |
| |
| for (address = PCA9468_REG_ICHG_CTRL; address <= PCA9468_REG_NTC_TH_2; address++) { |
| pca9468_read_reg(pca9468, address, &val); |
| sprintf(str + strlen(str), "[0x%02x]0x%02x, ", address, val); |
| } |
| |
| pr_info("## pca9468 : [DC_CPEN:%d]%s\n", gpio_get_value(pca9468->pdata->chgen_gpio), str); |
| } |
| |
| static void pca9468_monitor_work(struct pca9468_charger *pca9468) |
| { |
| int ta_vol = pca9468->ta_vol / PCA9468_SEC_DENOM_U_M; |
| int ta_cur = pca9468->ta_cur / PCA9468_SEC_DENOM_U_M; |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) |
| return; |
| |
| /* update adc value */ |
| pca9468_read_adc(pca9468, ADCCH_VIN); |
| pca9468_read_adc(pca9468, ADCCH_IIN); |
| pca9468_read_adc(pca9468, ADCCH_VBAT); |
| pca9468_read_adc(pca9468, ADCCH_DIETEMP); |
| |
| pr_info("%s: state(%s), iin_cc(%dmA), v_float(%dmV), vbat(%dmV), vin(%dmV), iin(%dmA), die_temp(%d), isys(%dmA), pps_requested(%d/%dmV/%dmA)", __func__, |
| charging_state_str[pca9468->charging_state], |
| pca9468->iin_cc / PCA9468_SEC_DENOM_U_M, pca9468->pdata->v_float / PCA9468_SEC_DENOM_U_M, |
| pca9468->adc_val[ADCCH_VBAT], pca9468->adc_val[ADCCH_VIN], |
| pca9468->adc_val[ADCCH_IIN], pca9468->adc_val[ADCCH_DIETEMP], |
| get_system_current(pca9468), pca9468->ta_objpos, |
| ta_vol, ta_cur); |
| } |
| #endif |
| |
| |
| /*******************************/ |
| /* Switching charger control function */ |
| /*******************************/ |
| /* This function needs some modification by a customer */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| static void pca9468_set_wdt_enable(struct pca9468_charger *pca9468, bool enable) |
| { |
| int ret; |
| unsigned int val; |
| |
| val = enable << MASK2SHIFT(PCA9468_BIT_WATCHDOG_EN); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_SAFETY_CTRL, |
| PCA9468_BIT_WATCHDOG_EN, val); |
| pr_info("%s: set wdt enable = %d\n", __func__, enable); |
| } |
| |
| static void pca9468_set_wdt_timer(struct pca9468_charger *pca9468, int time) |
| { |
| int ret; |
| unsigned int val; |
| |
| val = time << MASK2SHIFT(PCA9468_BIT_WATCHDOG_CFG); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_SAFETY_CTRL, |
| PCA9468_BIT_WATCHDOG_CFG, val); |
| pr_info("%s: set wdt time = %d\n", __func__, time); |
| } |
| |
| #if defined(CONFIG_ENG_BATTERY_CONCEPT) |
| static void pca9468_check_wdt_control(struct pca9468_charger *pca9468) |
| { |
| struct device *dev = pca9468->dev; |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| |
| if (pca9468->wdt_kick) { |
| pca9468_set_wdt_timer(pca9468, WDT_8SEC); |
| schedule_delayed_work(&pca9468->wdt_control_work, msecs_to_jiffies(PCA9468_BATT_WDT_CONTROL_T)); |
| } else { |
| pca9468_set_wdt_timer(pca9468, WDT_8SEC); |
| if (client->addr == 0xff) |
| client->addr = 0x57; |
| } |
| } |
| |
| static void pca9468_wdt_control_work(struct work_struct *work) |
| { |
| struct pca9468_charger *pca9468 = container_of(work, struct pca9468_charger, |
| wdt_control_work.work); |
| struct device *dev = pca9468->dev; |
| struct i2c_client *client = container_of(dev, struct i2c_client, dev); |
| int vin, iin; |
| |
| pca9468_set_wdt_timer(pca9468, WDT_4SEC); |
| |
| /* this is for kick watchdog */ |
| vin = pca9468_read_adc(pca9468, ADCCH_VIN); |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| |
| pca9468_send_pd_message(pca9468, PD_MSG_REQUEST_APDO); |
| |
| client->addr = 0xff; |
| |
| pr_info("## %s: disable slave addr (vin:%dmV, iin:%dmA)\n", |
| __func__, vin/PCA9468_SEC_DENOM_U_M, iin/PCA9468_SEC_DENOM_U_M); |
| } |
| #endif |
| |
| static void pca9468_set_done(struct pca9468_charger *pca9468, bool enable) |
| { |
| int ret = 0; |
| union power_supply_propval value = {0, }; |
| |
| value.intval = enable; |
| psy_do_property(pca9468->pdata->sec_dc_name, set, |
| POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value); |
| |
| if (ret < 0) |
| pr_info("%s: error set_done, ret=%d\n", __func__, ret); |
| } |
| |
| static void pca9468_set_switching_charger(struct pca9468_charger *pca9468, bool enable) |
| { |
| int ret = 0; |
| union power_supply_propval value = {0, }; |
| |
| value.intval = enable; |
| psy_do_property(pca9468->pdata->sec_dc_name, set, |
| POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, value); |
| |
| if (ret < 0) |
| pr_info("%s: error switching_charger, ret=%d\n", __func__, ret); |
| } |
| #else |
| static int pca9468_set_switching_charger( bool enable, |
| unsigned int input_current, |
| unsigned int charging_current, |
| unsigned int vfloat) |
| { |
| int ret; |
| struct power_supply *psy_swcharger; |
| union power_supply_propval val; |
| |
| pr_info("%s: enable=%d, iin=%d, ichg=%d, vfloat=%d\n", |
| __func__, enable, input_current, charging_current, vfloat); |
| |
| /* Insert Code */ |
| |
| /* Get power supply name */ |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| psy_swcharger = power_supply_get_by_name("usb"); |
| #else |
| /* Change "sw-charger" to the customer's switching charger name */ |
| psy_swcharger = power_supply_get_by_name("sw-charger"); |
| #endif |
| if (psy_swcharger == NULL) { |
| pr_err("%s: cannot get power_supply_name-usb\n", __func__); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| if (enable == true) { |
| /* Set Switching charger */ |
| |
| /* input current */ |
| val.intval = input_current; |
| ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CURRENT_MAX, &val); |
| if (ret < 0) |
| goto error; |
| /* charigng current */ |
| val.intval = charging_current; |
| ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, &val); |
| if (ret < 0) |
| goto error; |
| /* vfloat voltage */ |
| val.intval = vfloat; |
| ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, &val); |
| if (ret < 0) |
| goto error; |
| |
| /* it depends on customer's code to enable charger */ |
| val.intval = enable; |
| ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CHARGING_ENABLED, &val); |
| if (ret < 0) |
| goto error; |
| } else { |
| /* disable charger */ |
| /* it depends on customer's code to disable charger */ |
| val.intval = enable; |
| ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CHARGING_ENABLED, &val); |
| if (ret < 0) |
| goto error; |
| |
| /* input_current */ |
| val.intval = input_current; |
| ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CURRENT_MAX, &val); |
| if (ret < 0) |
| goto error; |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| #endif |
| |
| #if !defined(CONFIG_BATTERY_SAMSUNG) |
| static int pca9468_get_switching_charger_property(enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| int ret; |
| struct power_supply *psy_swcharger; |
| |
| /* Get power supply name */ |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| psy_swcharger = power_supply_get_by_name("usb"); |
| #else |
| psy_swcharger = power_supply_get_by_name("sw-charger"); |
| #endif |
| if (psy_swcharger == NULL) { |
| ret = -EINVAL; |
| goto error; |
| } |
| ret = power_supply_get_property(psy_swcharger, prop, val); |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| #endif |
| |
| /******************/ |
| /* Send PD message */ |
| /******************/ |
| /* Send Request message to the source */ |
| /* This function needs some modification by a customer */ |
| static int pca9468_send_pd_message(struct pca9468_charger *pca9468, unsigned int msg_type) |
| { |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| #elif defined(CONFIG_BATTERY_SAMSUNG) |
| unsigned int pdo_idx, pps_vol, pps_cur; |
| #else |
| u8 msg_buf[4]; /* Data Buffer for raw PD message */ |
| unsigned int max_cur; |
| unsigned int op_cur, out_vol; |
| #endif |
| int ret = 0; |
| |
| /* Cancel pps request timer */ |
| cancel_delayed_work(&pca9468->pps_work); |
| |
| mutex_lock(&pca9468->lock); |
| |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| /* Vbus reset happened in the previous PD communication */ |
| goto out; |
| } |
| |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| /* check the phandle */ |
| if (pca9468->pd == NULL) { |
| pr_info("%s: get phandle\n", __func__); |
| ret = pca9468_usbpd_setup(pca9468); |
| if (ret != 0) { |
| dev_err(pca9468->dev, "Error usbpd setup!\n"); |
| pca9468->pd = NULL; |
| goto out; |
| } |
| } |
| #endif |
| |
| pr_info("%s: msg_type=%d, ta_cur=%d, ta_vol=%d, ta_objpos=%d\n", |
| __func__, msg_type, pca9468->ta_cur, pca9468->ta_vol, pca9468->ta_objpos); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pdo_idx = pca9468->ta_objpos; |
| pps_vol = pca9468->ta_vol / PCA9468_SEC_DENOM_U_M; |
| pps_cur = pca9468->ta_cur / PCA9468_SEC_DENOM_U_M; |
| pr_info("## %s: msg_type=%d, pdo_idx=%d, pps_vol=%dmV(max_vol=%dmV), pps_cur=%dmA(max_cur=%dmA)\n", |
| __func__, msg_type, pdo_idx, |
| pps_vol, pca9468->pdo_max_voltage, |
| pps_cur, pca9468->pdo_max_current); |
| #endif |
| |
| switch (msg_type) { |
| case PD_MSG_REQUEST_APDO: |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| ret = usbpd_request_pdo(pca9468->pd, pca9468->ta_objpos, pca9468->ta_vol, pca9468->ta_cur); |
| if (ret == -EBUSY) { |
| /* wait 100ms */ |
| msleep(100); |
| /* try again */ |
| ret = usbpd_request_pdo(pca9468->pd, pca9468->ta_objpos, pca9468->ta_vol, pca9468->ta_cur); |
| } |
| #elif defined(CONFIG_BATTERY_SAMSUNG) |
| ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); |
| if (ret == -EBUSY) { |
| pr_info("%s: request again ret=%d\n", __func__, ret); |
| msleep(100); |
| ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); |
| } |
| #else |
| op_cur = pca9468->ta_cur/50000; // Operating Current 50mA units |
| out_vol = pca9468->ta_vol/20000; // Output Voltage in 20mV units |
| msg_buf[0] = op_cur & 0x7F; // Operating Current 50mA units - B6...0 |
| msg_buf[1] = (out_vol<<1) & 0xFE; // Output Voltage in 20mV units - B19..(B15)..B9 |
| msg_buf[2] = (out_vol>>7) & 0x0F; // Output Voltage in 20mV units - B19..(B16)..B9, |
| msg_buf[3] = pca9468->ta_objpos<<4; // Object Position - B30...B28 |
| |
| /* Send the PD messge to CC/PD chip */ |
| /* Todo - insert code */ |
| #endif |
| /* Start pps request timer */ |
| if (ret == 0) { |
| schedule_delayed_work(&pca9468->pps_work, msecs_to_jiffies(PCA9468_PPS_PERIODIC_T)); |
| } |
| break; |
| |
| case PD_MSG_REQUEST_FIXED_PDO: |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| ret = usbpd_request_pdo(pca9468->pd, pca9468->ta_objpos, pca9468->ta_vol, pca9468->ta_cur); |
| if (ret == -EBUSY) { |
| /* wait 100ms */ |
| msleep(100); |
| /* try again */ |
| ret = usbpd_request_pdo(pca9468->pd, pca9468->ta_objpos, pca9468->ta_vol, pca9468->ta_cur); |
| } |
| #elif defined(CONFIG_BATTERY_SAMSUNG) |
| ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); |
| if (ret == -EBUSY) { |
| pr_info("%s: request again ret=%d\n", __func__, ret); |
| msleep(100); |
| ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); |
| } |
| #else |
| max_cur = pca9468->ta_cur/10000; // Maximum Operation Current 10mA units |
| op_cur = max_cur; // Operating Current 10mA units |
| msg_buf[0] = max_cur & 0xFF; // Maximum Operation Current -B9..(7)..0 |
| msg_buf[1] = ((max_cur>>8) & 0x03) | ((op_cur<<2) & 0xFC); // Operating Current - B19..(15)..10 |
| msg_buf[2] = ((op_cur>>6) & 0x0F); // Operating Current - B19..(16)..10, Unchunked Extended Messages Supported - not support |
| msg_buf[3] = pca9468->ta_objpos<<4; // Object Position - B30...B28 |
| |
| /* Send the PD messge to CC/PD chip */ |
| /* Todo - insert code */ |
| #endif |
| break; |
| |
| default: |
| break; |
| } |
| |
| out: |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| /* Even though PD communication success, Vbus reset might happen |
| So, check the charging state again */ |
| ret = -EINVAL; |
| } |
| |
| pr_info("%s: ret=%d\n", __func__, ret); |
| mutex_unlock(&pca9468->lock); |
| return ret; |
| } |
| |
| /************************/ |
| /* Get APDO max power */ |
| /************************/ |
| /* Get the max current/voltage/power of APDO from the CC/PD driver */ |
| /* This function needs some modification by a customer */ |
| static int pca9468_get_apdo_max_power(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| unsigned int ta_max_vol_mv = (pca9468->ta_max_vol/PCA9468_SEC_DENOM_U_M); |
| unsigned int ta_max_cur_ma = 0; |
| unsigned int ta_max_pwr_mw = 0; |
| #endif |
| |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| /* check the phandle */ |
| if (pca9468->pd == NULL) { |
| pr_info("%s: get phandle\n", __func__); |
| ret = pca9468_usbpd_setup(pca9468); |
| if (ret != 0) { |
| dev_err(pca9468->dev, "Error usbpd setup!\n"); |
| pca9468->pd = NULL; |
| goto out; |
| } |
| } |
| |
| ret = usbpd_get_apdo_max_power(pca9468->pd, &pca9468->ta_objpos, |
| &pca9468->ta_max_vol, &pca9468->ta_max_cur, &pca9468->ta_max_pwr); |
| #elif defined(CONFIG_BATTERY_SAMSUNG) |
| ret = sec_pd_get_apdo_max_power(&pca9468->ta_objpos, |
| &ta_max_vol_mv, &ta_max_cur_ma, &ta_max_pwr_mw); |
| /* mA,mV,mW --> uA,uV,uW */ |
| pca9468->ta_max_vol = ta_max_vol_mv * PCA9468_SEC_DENOM_U_M; |
| pca9468->ta_max_cur = ta_max_cur_ma * PCA9468_SEC_DENOM_U_M; |
| pca9468->ta_max_pwr = ta_max_pwr_mw; |
| |
| pr_info("%s: ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d\n", |
| __func__, pca9468->ta_max_vol, pca9468->ta_max_cur, pca9468->ta_max_pwr); |
| |
| pca9468->pdo_index = pca9468->ta_objpos; |
| pca9468->pdo_max_voltage = ta_max_vol_mv; |
| pca9468->pdo_max_current = ta_max_cur_ma; |
| #else |
| /* Put ta_max_vol to the desired ta maximum value, ex) 9800mV */ |
| /* Get new ta_max_vol and ta_max_cur, ta_max_power and proper object position by CC/PD IC */ |
| /* insert code */ |
| #endif |
| |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| out: |
| #endif |
| return ret; |
| } |
| |
| |
| /* ADC Read function */ |
| static int pca9468_read_adc(struct pca9468_charger *pca9468, u8 adc_ch) |
| { |
| u8 reg_data[2]; |
| u16 raw_adc; // raw ADC value |
| int conv_adc; // conversion ADC value |
| int ret; |
| u8 rsense; /* sense resistance */ |
| |
| switch (adc_ch) |
| { |
| case ADCCH_VOUT: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_4, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_VOUT9_2)<<2) | ((reg_data[0] & PCA9468_BIT_ADC_VOUT1_0)>>6); |
| conv_adc = raw_adc * VOUT_STEP; // unit - uV |
| break; |
| case ADCCH_VIN: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_3, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_VIN9_4)<<4) | ((reg_data[0] & PCA9468_BIT_ADC_VIN3_0)>>4); |
| conv_adc = raw_adc * VIN_STEP; // uint - uV |
| break; |
| |
| case ADCCH_VBAT: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_6, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_VBAT9_8)<<8) | ((reg_data[0] & PCA9468_BIT_ADC_VBAT7_0)>>0); |
| conv_adc = raw_adc * VBAT_STEP; // unit - uV |
| break; |
| |
| case ADCCH_ICHG: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_2, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC |
| rsense = (pca9468->pdata->snsres == 0) ? 5 : 10; // snsres : 0 - 5mOhm, 1 - 10mOhm |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_IOUT9_6)<<6) | ((reg_data[0] & PCA9468_BIT_ADC_IOUT5_0)>>2); |
| conv_adc = raw_adc * ICHG_STEP; // unit - uA |
| break; |
| |
| case ADCCH_IIN: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_1, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC - iin = rawadc*4.89 + (rawadc*4.89 - 900)*adc_comp_gain/100, unit - uA |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_IIN9_8)<<8) | ((reg_data[0] & PCA9468_BIT_ADC_IIN7_0)>>0); |
| conv_adc = raw_adc * IIN_STEP + (raw_adc * IIN_STEP - ADC_IIN_OFFSET)*pca9468->adc_comp_gain/100; // unit - uA |
| if (conv_adc < 0) |
| conv_adc = 0; // If ADC raw value is 0, convert value will be minus value becasue of compensation gain, so in this case conv_adc is 0 |
| break; |
| |
| |
| case ADCCH_DIETEMP: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_7, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_DIETEMP9_6)<<6) | ((reg_data[0] & PCA9468_BIT_ADC_DIETEMP5_0)>>2); |
| conv_adc = (935 - raw_adc)*DIETEMP_STEP/DIETEMP_DENOM; // Temp = (935-rawadc)*0.435, unit - C |
| if (conv_adc > DIETEMP_MAX) { |
| conv_adc = DIETEMP_MAX; |
| } else if (conv_adc < DIETEMP_MIN) { |
| conv_adc = DIETEMP_MIN; |
| } |
| break; |
| |
| case ADCCH_NTC: |
| // Read ADC value |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_ADC_8, reg_data, 2); |
| if (ret < 0) { |
| conv_adc = ret; |
| goto error; |
| } |
| // Convert ADC |
| raw_adc = ((reg_data[1] & PCA9468_BIT_ADC_NTCV9_4)<<4) | ((reg_data[0] & PCA9468_BIT_ADC_NTCV3_0)>>4); |
| conv_adc = raw_adc * NTCV_STEP; // unit - uV |
| break; |
| |
| default: |
| conv_adc = -EINVAL; |
| break; |
| } |
| |
| error: |
| pr_info("%s: adc_ch=%d, convert_val=%d\n", __func__, adc_ch, conv_adc); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| if (adc_ch == ADCCH_DIETEMP) |
| pca9468->adc_val[adc_ch] = conv_adc; |
| else |
| pca9468->adc_val[adc_ch] = conv_adc / PCA9468_SEC_DENOM_U_M; |
| #endif |
| return conv_adc; |
| } |
| |
| |
| static int pca9468_set_vfloat(struct pca9468_charger *pca9468, unsigned int v_float) |
| { |
| int ret, val; |
| |
| pr_info("%s: vfloat=%d\n", __func__, v_float); |
| |
| /* v float voltage */ |
| if (v_float < PCA9468_VFLOAT_MIN) { |
| v_float = PCA9468_VFLOAT_MIN; |
| pr_info("%s: -> vfloat=%d\n", __func__, v_float); |
| } |
| val = PCA9468_V_FLOAT(v_float); |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_V_FLOAT, val); |
| return ret; |
| } |
| |
| static int pca9468_set_charging_current(struct pca9468_charger *pca9468, unsigned int ichg) |
| { |
| int ret, val; |
| |
| pr_info("%s: ichg=%d\n", __func__, ichg); |
| |
| /* charging current */ |
| if (ichg > PCA9468_ICHG_CFG_MAX) { |
| ichg = PCA9468_ICHG_CFG_MAX; |
| pr_info("%s: -> ichg=%d\n", __func__, ichg); |
| } |
| val = PCA9468_ICHG_CFG(ichg); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_ICHG_CTRL, PCA9468_BIT_ICHG_CFG, val); |
| return ret; |
| } |
| |
| static int pca9468_set_input_current(struct pca9468_charger *pca9468, unsigned int iin) |
| { |
| int ret, val; |
| |
| pr_info("%s: iin=%d\n", __func__, iin); |
| |
| /* input current */ |
| /* round off and increase one step */ |
| iin = iin + PD_MSG_TA_CUR_STEP; |
| val = PCA9468_IIN_CFG(iin); |
| /* Set IIN_CFG to one step higher */ |
| val = val + 1; |
| |
| if (val > 0x32) |
| val = 0x32; /* maximum value is 5A */ |
| |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_IIN_CTRL, PCA9468_BIT_IIN_CFG, val); |
| pr_info("%s: real iin_cfg=%d\n", __func__, val*PCA9468_IIN_CFG_STEP); |
| return ret; |
| } |
| |
| static int pca9468_set_charging(struct pca9468_charger *pca9468, bool enable) |
| { |
| int ret, val; |
| |
| pr_info("%s: enable=%d\n", __func__, enable); |
| |
| if (enable == true) { |
| /* Improve adc */ |
| val = 0x5B; |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_ADC_ACCESS, val); |
| if (ret < 0) |
| goto error; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_ADC_IMPROVE, PCA9468_BIT_ADC_IIN_IMP, 0); |
| if (ret < 0) |
| goto error; |
| |
| /* For fixing input current error */ |
| /* Overwirte 0x00 in 0x41 register */ |
| val = 0x00; |
| ret = pca9468_write_reg(pca9468, 0x41, val); |
| if (ret < 0) |
| goto error; |
| /* Overwirte 0x01 in 0x43 register */ |
| val = 0x01; |
| ret = pca9468_write_reg(pca9468, 0x43, val); |
| if (ret < 0) |
| goto error; |
| |
| /* Overwirte 0x00 in 0x4B register */ |
| val = 0x00; |
| ret = pca9468_write_reg(pca9468, 0x4B, val); |
| if (ret < 0) |
| goto error; |
| /* End for fixing input current error */ |
| |
| } else { |
| /* Disable NTC_PROTECTION_EN */ |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_TEMP_CTRL, |
| PCA9468_BIT_NTC_PROTECTION_EN, 0); |
| } |
| |
| /* Enable PCA9468 */ |
| val = (enable == true) ? PCA9468_STANDBY_DONOT: PCA9468_STANDBY_FORCED; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, PCA9468_BIT_STANDBY_EN, val); |
| if (ret < 0) |
| goto error; |
| |
| if (enable == true) { |
| msleep(150); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_ADC_IMPROVE, PCA9468_BIT_ADC_IIN_IMP, PCA9468_BIT_ADC_IIN_IMP); |
| if (ret < 0) |
| goto error; |
| |
| val = 0x00; |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_ADC_ACCESS, val); |
| |
| /* Enable NTC_PROTECTION_EN */ |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_TEMP_CTRL, |
| PCA9468_BIT_NTC_PROTECTION_EN, PCA9468_BIT_NTC_PROTECTION_EN); |
| /* Enable TEMP_REG_EN */ |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_TEMP_CTRL, |
| PCA9468_BIT_TEMP_REG_EN, PCA9468_BIT_TEMP_REG_EN); |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| /* Stop Charging */ |
| static int pca9468_stop_charging(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| |
| /* Check the current state */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| if ((pca9468->charging_state != DC_STATE_NO_CHARGING) || |
| (pca9468->timer_id != TIMER_ID_NONE)) { |
| #else |
| if (pca9468->charging_state != DC_STATE_NO_CHARGING) { |
| #endif |
| // Stop Direct charging |
| cancel_delayed_work(&pca9468->timer_work); |
| cancel_delayed_work(&pca9468->pps_work); |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ID_NONE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| __pm_relax(&pca9468->monitor_wake_lock); |
| |
| /* Clear parameter */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_NO_CHARGING); |
| pca9468_init_adc_val(pca9468, -1); |
| #else |
| pca9468->charging_state = DC_STATE_NO_CHARGING; |
| #endif |
| pca9468->ret_state = DC_STATE_NO_CHARGING; |
| pca9468->ta_target_vol = PCA9468_TA_MAX_VOL; |
| pca9468->prev_iin = 0; |
| pca9468->prev_inc = INC_NONE; |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| pca9468->req_new_vfloat = false; |
| mutex_unlock(&pca9468->lock); |
| pca9468->ta_mode = TA_NO_DC_MODE; |
| |
| /* Set IIN_CFG and VFLOAT to the default value */ |
| pca9468->pdata->iin_cfg = PCA9468_IIN_CFG_DFT; |
| pca9468->pdata->v_float = pca9468->pdata->v_float_max; |
| |
| /* Clear new Vfloat and new IIN */ |
| pca9468->new_vfloat = pca9468->pdata->v_float; |
| pca9468->new_iin = pca9468->pdata->iin_cfg; |
| |
| /* Clear retry counter */ |
| pca9468->retry_cnt = 0; |
| |
| ret = pca9468_set_charging(pca9468, false); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| /* Set watchdog timer disable */ |
| pca9468_set_wdt_enable(pca9468, WDT_DISABLE); |
| #endif |
| } |
| |
| pr_info("%s: END, ret=%d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| |
| /* Compensate TA current for the target input current */ |
| static int pca9468_set_ta_current_comp(struct pca9468_charger *pca9468) |
| { |
| int iin; |
| |
| /* Read IIN ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| |
| /* Compare IIN ADC with targer input current */ |
| if (iin > (pca9468->iin_cc + PCA9468_IIN_CC_COMP_OFFSET)) { |
| /* TA current is higher than the target input current */ |
| /* Decrease TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP; |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) { |
| /* compare IIN ADC with previous IIN ADC + 20mA */ |
| if (iin > (pca9468->prev_iin + PCA9468_IIN_ADC_OFFSET)) { |
| /* Compare TA max voltage */ |
| if (pca9468->ta_vol == pca9468->ta_max_vol) { |
| /* TA voltage is already the maximum voltage */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK1_T; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Increase TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP; |
| pr_info("%s: Comp. Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_VOL; |
| } else { |
| /* TA current is lower than the target input current */ |
| /* Check the previous TA increment */ |
| if (pca9468->prev_inc == INC_TA_VOL) { |
| /* The previous increment is TA voltage, but input current does not increase */ |
| /* Try to increase TA current */ |
| /* Compare TA max current */ |
| if (pca9468->ta_cur == pca9468->ta_max_cur) { |
| /* TA current is already the maximum current */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK1_T; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Increase TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur + PD_MSG_TA_CUR_STEP; |
| pr_info("%s: Comp. Cont: ta_cur=%d\n", __func__, pca9468->ta_cur); |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_CUR; |
| } else { |
| /* The previous increment is TA current, but input current does not increase */ |
| /* Try to increase TA voltage */ |
| /* Compare TA max voltage */ |
| if (pca9468->ta_vol == pca9468->ta_max_vol) { |
| |
| /* TA voltage is already the maximum voltage */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK1_T; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Increase TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP; |
| pr_info("%s: Comp. Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_VOL; |
| } |
| } |
| } else { |
| /* IIN ADC is in valid range */ |
| /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK1_T; |
| mutex_unlock(&pca9468->lock); |
| } |
| } |
| |
| /* Save previous iin adc */ |
| pca9468->prev_iin = iin; |
| |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| return 0; |
| } |
| |
| /* Compensate TA current for constant power mode */ |
| static int pca9468_set_ta_current_comp2(struct pca9468_charger *pca9468) |
| { |
| int iin; |
| unsigned int val; |
| unsigned int iin_apdo; |
| |
| /* Read IIN ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| |
| /* Compare IIN ADC with targer input current */ |
| if (iin > (pca9468->pdata->iin_cfg + PCA9468_IIN_CC_UPPER_OFFSET)) { |
| /* TA current is higher than the target input current */ |
| /* Decrease TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP; |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET_CP)) { |
| /* IIN_ADC < IIN_CC -20mA */ |
| if (pca9468->ta_vol == pca9468->ta_max_vol) { |
| /* Check IIN_ADC < IIN_CC - 50mA */ |
| if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) { |
| /* Set new IIN_CC to IIN_CC - 50mA */ |
| pca9468->iin_cc = pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET; |
| /* Set new TA_MAX_VOL to TA_MAX_PWR/IIN_CC */ |
| /* Adjust new IIN_CC with APDO resolution */ |
| iin_apdo = pca9468->iin_cc/PD_MSG_TA_CUR_STEP; |
| iin_apdo = iin_apdo*PD_MSG_TA_CUR_STEP; |
| val = pca9468->ta_max_pwr/(iin_apdo/pca9468->ta_mode/1000); /* mV */ |
| val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ |
| val = val*PD_MSG_TA_VOL_STEP; /* uV */ |
| /* Set new TA_MAX_VOL */ |
| pca9468->ta_max_vol = MIN(val, PCA9468_TA_MAX_VOL*pca9468->ta_mode); |
| /* Increase TA voltage(40mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP*2; |
| |
| pr_info("%s: Comp. Cont1: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Wait for next current step compensation */ |
| /* IIN_CC - 50mA < IIN ADC < IIN_CC - 20mA*/ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| } |
| } else { |
| /* Increase TA voltage(40mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP*2; |
| if (pca9468->ta_vol > pca9468->ta_max_vol) |
| pca9468->ta_vol = pca9468->ta_max_vol; |
| |
| pr_info("%s: Comp. Cont2: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| } else { |
| /* IIN ADC is in valid range */ |
| /* IIN_CC - 50mA < IIN ADC < IIN_CFG + 50mA */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| } |
| |
| /* Save previous iin adc */ |
| pca9468->prev_iin = iin; |
| |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| return 0; |
| } |
| |
| /* Compensate TA voltage for the target input current */ |
| static int pca9468_set_ta_voltage_comp(struct pca9468_charger *pca9468) |
| { |
| int iin; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| /* Read IIN ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| |
| /* Compare IIN ADC with target input current */ |
| if (iin > (pca9468->iin_cc + PCA9468_IIN_CC_COMP_OFFSET)) { |
| /* TA current is higher than the target input current */ |
| /* Decrease TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP; |
| pr_info("%s: Decrease: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| if (iin < (pca9468->iin_cc - PCA9468_IIN_CC_COMP_OFFSET)) { |
| /* TA current is lower than the target input current */ |
| /* Compare TA max voltage */ |
| if (pca9468->ta_vol == pca9468->ta_max_vol) { |
| /* TA current is already the maximum voltage */ |
| /* Set timer */ |
| /* Check the current charging state */ |
| if (pca9468->charging_state == DC_STATE_CC_MODE) { |
| /* CC mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK1_T; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* CV mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| } |
| } else { |
| /* Increase TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP; |
| pr_info("%s: Increase: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| } else { |
| /* IIN ADC is in valid range */ |
| /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ |
| /* Set timer */ |
| /* Check the current charging state */ |
| if (pca9468->charging_state == DC_STATE_CC_MODE) { |
| /* CC mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = PCA9468_CCMODE_CHECK1_T; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* CV mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| } |
| } |
| } |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| return 0; |
| } |
| |
| |
| /* Set TA current for target current */ |
| static int pca9468_adjust_ta_current (struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| int vbat; |
| unsigned int val; |
| |
| /* Set charging state to ADJUST_TACUR */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_ADJUST_TACUR); |
| #else |
| pca9468->charging_state = DC_STATE_ADJUST_TACUR; |
| #endif |
| |
| if (pca9468->ta_cur == pca9468->iin_cc/pca9468->ta_mode) { |
| /* finish sending PD message */ |
| /* Recover IIN_CC to the original value(new_iin) */ |
| pca9468->iin_cc = pca9468->new_iin; |
| |
| /* Clear req_new_iin */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| |
| /* Go to return state */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, pca9468->ret_state); |
| #else |
| pca9468->charging_state = pca9468->ret_state; |
| #endif |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| if (pca9468->charging_state == DC_STATE_CC_MODE) |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| else |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = 1000; /* Wait 1000ms */ |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Compare new IIN with IIN_CFG */ |
| if (pca9468->iin_cc > pca9468->pdata->iin_cfg) { |
| /* New iin is higher than the current iin_cfg */ |
| /* Update iin_cfg */ |
| pca9468->pdata->iin_cfg = pca9468->iin_cc; |
| /* Set IIN_CFG to new IIN */ |
| ret = pca9468_set_input_current(pca9468, pca9468->iin_cc); |
| if (ret < 0) |
| goto error; |
| |
| /* Clear Request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| |
| /* Set new TA voltage and current */ |
| /* Read VBAT ADC */ |
| vbat = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| |
| /* Calculate new TA maximum current and voltage that used in the direct charging */ |
| /* Set IIN_CC to MIN[IIN, TA_MAX_CUR*TA_mode]*/ |
| pca9468->iin_cc = MIN(pca9468->pdata->iin_cfg, pca9468->ta_max_cur*pca9468->ta_mode); |
| /* Set the current IIN_CC to iin_cfg for recovering it after resolution adjustment */ |
| pca9468->pdata->iin_cfg = pca9468->iin_cc; |
| /* Calculate new TA max voltage */ |
| /* Adjust IIN_CC with APDO resoultion(50mA) - It will recover to the original value after max voltage calculation */ |
| val = pca9468->iin_cc/(PD_MSG_TA_CUR_STEP*pca9468->ta_mode); |
| pca9468->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9468->ta_mode); |
| /* Set TA_MAX_VOL to MIN[PCA9468_TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ |
| val = pca9468->ta_max_pwr/(pca9468->iin_cc/pca9468->ta_mode/1000); /* mV */ |
| val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ |
| val = val*PD_MSG_TA_VOL_STEP; /* uV */ |
| pca9468->ta_max_vol = MIN(val, PCA9468_TA_MAX_VOL*pca9468->ta_mode); |
| |
| /* Set TA voltage to MAX[8000mV, (2*VBAT_ADC + 500 mV)] */ |
| pca9468->ta_vol = max(PCA9468_TA_MIN_VOL_PRESET*pca9468->ta_mode, (2*vbat*pca9468->ta_mode + PCA9468_TA_VOL_PRE_OFFSET)); |
| val = pca9468->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ |
| pca9468->ta_vol = val*PD_MSG_TA_VOL_STEP; |
| /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL] */ |
| pca9468->ta_vol = MIN(pca9468->ta_vol, pca9468->ta_max_vol); |
| /* Set TA current to IIN_CC */ |
| pca9468->ta_cur = pca9468->iin_cc/pca9468->ta_mode; |
| /* Recover IIN_CC to the original value(iin_cfg) */ |
| pca9468->iin_cc = pca9468->pdata->iin_cfg; |
| |
| pr_info("%s: New IIN, ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, ta_mode=%d\n", |
| __func__, pca9468->ta_max_vol, pca9468->ta_max_cur, pca9468->ta_max_pwr, pca9468->iin_cc, pca9468->ta_mode); |
| |
| /* Clear previous IIN ADC */ |
| pca9468->prev_iin = 0; |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| |
| /* Send PD Message and go to Adjust CC mode */ |
| pca9468->charging_state = DC_STATE_ADJUST_CC; |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Set TA voltage to TA target voltage */ |
| pca9468->ta_vol = pca9468->ta_target_vol; |
| /* Adjust IIN_CC with APDO resoultion(50mA) - It will recover to the original value after sending PD message */ |
| val = pca9468->iin_cc/PD_MSG_TA_CUR_STEP; |
| pca9468->iin_cc = val*PD_MSG_TA_CUR_STEP; |
| /* Set TA current to IIN_CC */ |
| pca9468->ta_cur = pca9468->iin_cc/pca9468->ta_mode; |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| } |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| error: |
| return ret; |
| } |
| |
| |
| /* Set TA voltage for targer current */ |
| static int pca9468_adjust_ta_voltage(struct pca9468_charger *pca9468) |
| { |
| int iin; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_ADJUST_TAVOL); |
| #else |
| pca9468->charging_state = DC_STATE_ADJUST_TAVOL; |
| #endif |
| |
| /* Read IIN ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| |
| /* Compare IIN ADC with targer input current */ |
| if (iin > (pca9468->iin_cc + PD_MSG_TA_CUR_STEP)) { |
| /* TA current is higher than the target input current */ |
| /* Decrease TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP; |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| if (iin < (pca9468->iin_cc - PD_MSG_TA_CUR_STEP)) { |
| /* TA current is lower than the target input current */ |
| /* Compare TA max voltage */ |
| if (pca9468->ta_vol == pca9468->ta_max_vol) { |
| /* TA current is already the maximum voltage */ |
| /* Clear req_new_iin */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| /* Return charging state to the previous state */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, pca9468->ret_state); |
| #else |
| pca9468->charging_state = pca9468->ret_state; |
| #endif |
| /* Set TA current and voltage to the same value */ |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Increase TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PD_MSG_TA_VOL_STEP; |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| } else { |
| /* IIN ADC is in valid range */ |
| /* Clear req_new_iin */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| |
| /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ |
| /* Return charging state to the previous state */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, pca9468->ret_state); |
| #else |
| pca9468->charging_state = pca9468->ret_state; |
| #endif |
| /* Set TA current and voltage to the same value */ |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| } |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| return 0; |
| } |
| |
| |
| /* Set new input current */ |
| static int pca9468_set_new_iin(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| |
| pr_info("%s: new_iin=%d\n", __func__, pca9468->new_iin); |
| |
| /* Check the charging state */ |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| /* Apply new iin when the direct charging is started */ |
| pca9468->pdata->iin_cfg = pca9468->new_iin; |
| } else { |
| /* Check whether the previous request is done or not */ |
| if (pca9468->req_new_iin == true) { |
| /* The previous request is not done yet */ |
| pr_err("%s: There is the previous request for New iin\n", __func__); |
| ret = -EBUSY; |
| } else { |
| /* Set request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = true; |
| mutex_unlock(&pca9468->lock); |
| |
| /* Check the charging state */ |
| if ((pca9468->charging_state == DC_STATE_CC_MODE) || |
| (pca9468->charging_state == DC_STATE_CV_MODE)) { |
| /* cancel delayed_work */ |
| cancel_delayed_work(&pca9468->timer_work); |
| |
| /* Set new IIN to IIN_CC */ |
| pca9468->iin_cc = pca9468->new_iin; |
| |
| /* Save return state */ |
| pca9468->ret_state = pca9468->charging_state; |
| |
| /* Check new IIN with the minimum TA current */ |
| if (pca9468->iin_cc < (PCA9468_TA_MIN_CUR * pca9468->ta_mode)) { |
| /* Set the TA current to PCA9468_TA_MIN_CUR(1.0A) */ |
| pca9468->ta_cur = PCA9468_TA_MIN_CUR; |
| /* Need to control TA voltage for request current */ |
| ret = pca9468_adjust_ta_voltage(pca9468); |
| } else { |
| /* Need to control TA current for request current */ |
| ret = pca9468_adjust_ta_current(pca9468); |
| } |
| } else if (pca9468->charging_state == DC_STATE_WC_CV_MODE) { |
| /* Charging State is WC CV state */ |
| /* cancel delayed_work */ |
| cancel_delayed_work(&pca9468->timer_work); |
| |
| /* Set IIN_CFG to new iin */ |
| pca9468->pdata->iin_cfg = pca9468->new_iin; |
| ret = pca9468_set_input_current(pca9468, pca9468->pdata->iin_cfg); |
| if (ret < 0) |
| goto error; |
| |
| /* Clear req_new_iin */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| |
| /* Set IIN_CC to new iin */ |
| pca9468->iin_cc = pca9468->new_iin; |
| |
| pr_info("%s: WC CV state, New IIN=%d\n", __func__, pca9468->iin_cc); |
| |
| /* Go to WC CV mode */ |
| pca9468->charging_state = DC_STATE_WC_CV_MODE; |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } else { |
| /* Wait for next valid state */ |
| pr_info("%s: Not support new iin yet in charging state=%d\n", __func__, pca9468->charging_state); |
| } |
| } |
| } |
| |
| error: |
| pr_info("%s: ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* Set new float voltage */ |
| static int pca9468_set_new_vfloat(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| int vbat; |
| unsigned int val; |
| |
| /* Check the charging state */ |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| /* Apply new vfloat when the direct charging is started */ |
| pca9468->pdata->v_float = pca9468->new_vfloat; |
| } else { |
| /* Check whether the previous request is done or not */ |
| if (pca9468->req_new_vfloat == true) { |
| /* The previous request is not done yet */ |
| pr_err("%s: There is the previous request for New vfloat\n", __func__); |
| ret = -EBUSY; |
| } else { |
| /* Set request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_vfloat = true; |
| mutex_unlock(&pca9468->lock); |
| |
| /* Check the charging state */ |
| if ((pca9468->charging_state == DC_STATE_CC_MODE) || |
| (pca9468->charging_state == DC_STATE_CV_MODE)) { |
| /* Read VBAT ADC */ |
| vbat = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| /* Compare the new VBAT with the current VBAT */ |
| if (pca9468->new_vfloat > vbat) { |
| /* cancel delayed_work */ |
| cancel_delayed_work(&pca9468->timer_work); |
| |
| /* Set VFLOAT to new vfloat */ |
| pca9468->pdata->v_float = pca9468->new_vfloat; |
| ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float); |
| if (ret < 0) |
| goto error; |
| pca9468->pdata->iin_cfg = pca9468->iin_cc; /* save the current iin_cc in iin_cfg */ |
| /* Set IIN_CFG to the current IIN_CC */ |
| pca9468->pdata->iin_cfg = MIN(pca9468->pdata->iin_cfg, pca9468->ta_max_cur*pca9468->ta_mode); |
| ret = pca9468_set_input_current(pca9468, pca9468->pdata->iin_cfg); |
| if (ret < 0) |
| goto error; |
| pca9468->iin_cc = pca9468->pdata->iin_cfg; |
| |
| /* Clear req_new_vfloat */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_vfloat = false; |
| mutex_unlock(&pca9468->lock); |
| |
| /* Calculate new TA maximum voltage that used in the direct charging */ |
| /* Calculate new TA max voltage */ |
| /* Adjust IIN_CC with APDO resoultion(50mA) - It will recover to the original value after max voltage calculation */ |
| val = pca9468->iin_cc/(PD_MSG_TA_CUR_STEP*pca9468->ta_mode); |
| pca9468->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9468->ta_mode); |
| /* Set TA_MAX_VOL to MIN[PCA9468_TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ |
| val = pca9468->ta_max_pwr/(pca9468->iin_cc/pca9468->ta_mode/1000); /* mV */ |
| val = val*1000/PD_MSG_TA_VOL_STEP; /* uV */ |
| val = val*PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ |
| pca9468->ta_max_vol = MIN(val, PCA9468_TA_MAX_VOL*pca9468->ta_mode); |
| |
| /* Set TA voltage to MAX[8000mV*TA_mode, (2*VBAT_ADC*TA_mode + 500 mV)] */ |
| pca9468->ta_vol = max(PCA9468_TA_MIN_VOL_PRESET*pca9468->ta_mode, (2*vbat*pca9468->ta_mode + PCA9468_TA_VOL_PRE_OFFSET)); |
| val = pca9468->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ |
| pca9468->ta_vol = val*PD_MSG_TA_VOL_STEP; |
| /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL] */ |
| pca9468->ta_vol = MIN(pca9468->ta_vol, pca9468->ta_max_vol); |
| /* Set TA current to IIN_CC */ |
| pca9468->ta_cur = pca9468->iin_cc/pca9468->ta_mode; |
| /* Recover IIN_CC to the original value(iin_cfg) */ |
| pca9468->iin_cc = pca9468->pdata->iin_cfg; |
| |
| pr_info("%s: New VFLOAT, ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, ta_mode=%d\n", |
| __func__, pca9468->ta_max_vol, pca9468->ta_max_cur, pca9468->ta_max_pwr, pca9468->iin_cc, pca9468->ta_mode); |
| |
| /* Clear previous IIN ADC */ |
| pca9468->prev_iin = 0; |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| |
| /* Send PD Message and go to Adjust CC mode */ |
| pca9468->charging_state = DC_STATE_ADJUST_CC; |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } else { |
| /* The new VBAT is lower than the current VBAT */ |
| /* return invalid error */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pr_err("## %s: New vfloat(%duA) is lower than VBAT ADC(%duA)\n", |
| __func__, pca9468->new_vfloat, vbat); |
| #else |
| pr_err("%s: New vfloat is lower than VBAT ADC\n", __func__); |
| #endif |
| ret = -EINVAL; |
| } |
| } else if (pca9468->charging_state == DC_STATE_WC_CV_MODE) { |
| /* Charging State is WC CV state */ |
| /* Read VBAT ADC */ |
| vbat = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| /* Compare the new VBAT with the current VBAT */ |
| if (pca9468->new_vfloat > vbat) { |
| /* cancel delayed_work */ |
| cancel_delayed_work(&pca9468->timer_work); |
| |
| /* Set VFLOAT to new vfloat */ |
| pca9468->pdata->v_float = pca9468->new_vfloat; |
| ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float); |
| if (ret < 0) |
| goto error; |
| |
| /* Clear req_new_vfloat */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_vfloat = false; |
| mutex_unlock(&pca9468->lock); |
| |
| pr_info("%s: WC CV state, New VFLOAT=%d\n", __func__, pca9468->pdata->v_float); |
| |
| /* Go to WC CV mode */ |
| pca9468->charging_state = DC_STATE_WC_CV_MODE; |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } |
| } else { |
| /* Wait for next valid state */ |
| pr_info("%s: Not support new vfloat yet in charging state=%d\n", __func__, pca9468->charging_state); |
| } |
| } |
| } |
| error: |
| pr_info("%s: ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* Check Acitve status */ |
| static int pca9468_check_error(struct pca9468_charger *pca9468) |
| { |
| int ret; |
| unsigned int reg_val; |
| int vbatt; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468->chg_status = POWER_SUPPLY_STATUS_CHARGING; |
| pca9468->health_status = POWER_SUPPLY_HEALTH_GOOD; |
| #endif |
| |
| /* Read STS_B */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_STS_B, ®_val); |
| if (ret < 0) |
| goto error; |
| |
| /* Read VBAT_ADC */ |
| vbatt = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| |
| /* Check Active status */ |
| if (reg_val & PCA9468_BIT_ACTIVE_STATE_STS) { |
| /* PCA9468 is active state */ |
| /* PCA9468 is in charging */ |
| /* Check whether the battery voltage is over the minimum voltage level or not */ |
| if (vbatt > PCA9468_DC_VBAT_MIN) { |
| /* Check temperature regulation loop */ |
| /* Read INT1_STS register */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_INT1_STS, ®_val); |
| if (reg_val & PCA9468_BIT_TEMP_REG_STS) { |
| /* Over temperature protection */ |
| pr_err("%s: Device is in temperature regulation", __func__); |
| ret = -EINVAL; |
| } else { |
| /* Normal temperature */ |
| ret = 0; |
| } |
| } else { |
| /* Abnormal battery level */ |
| pr_err("%s: Error abnormal battery voltage=%d\n", __func__, vbatt); |
| ret = -EINVAL; |
| } |
| } else { |
| /* PCA9468 is not active state - stanby or shutdown */ |
| /* Stop charging in timer_work */ |
| |
| /* Read all status register for debugging */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| u8 val[8]; |
| u8 test_val[16]; /* Dump for test register */ |
| #else |
| unsigned int val[8]; |
| #endif |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_INT1, &val[PCA9468_REG_INT1], 7); |
| pr_err("%s: Error reg[1]=0x%x,[2]=0x%x,[3]=0x%x,[4]=0x%x,[5]=0x%x,[6]=0x%x,[7]=0x%x\n", |
| __func__, val[1], val[2], val[3], val[4], val[5], val[6], val[7]); |
| /* Read test register for debugging */ |
| ret = pca9468_bulk_read_reg(pca9468, 0x40, test_val, 16); |
| pr_err("%s: Error reg[0x40]=0x%x,[0x41]=0x%x,[0x42]=0x%x,[0x43]=0x%x,[0x44]=0x%x,[0x45]=0x%x,[0x46]=0x%x,[0x47]=0x%x\n", |
| __func__, test_val[0], test_val[1], test_val[2], test_val[3], test_val[4], test_val[5], test_val[6], test_val[7]); |
| pr_err("%s: Error reg[0x48]=0x%x,[0x49]=0x%x,[0x4A]=0x%x,[0x4B]=0x%x,[0x4C]=0x%x,[0x4D]=0x%x,[0x4E]=0x%x,[0x4F]=0x%x\n", |
| __func__, test_val[8], test_val[9], test_val[10], test_val[11], test_val[12], test_val[13], test_val[14], test_val[15]); |
| |
| /* Check INT1_STS first */ |
| if ((val[PCA9468_REG_INT1_STS] & PCA9468_BIT_V_OK_STS) != PCA9468_BIT_V_OK_STS) { |
| /* VBUS is invalid */ |
| pr_err("%s: VOK is invalid", __func__); |
| /* Check STS_A */ |
| if (val[PCA9468_REG_STS_A] & PCA9468_BIT_CFLY_SHORT_STS) |
| pr_err("%s: Flying Cap is shorted to GND", __func__); /* Flying cap is short to GND */ |
| else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VOUT_UV_STS) |
| pr_err("%s: VOUT UV", __func__); /* VOUT < VOUT_OK */ |
| else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VBAT_OV_STS) |
| pr_err("%s: VBAT OV", __func__); /* VBAT > VBAT_OV */ |
| else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VIN_OV_STS) |
| pr_err("%s: VIN OV", __func__); /* VIN > V_OV_FIXED or V_OV_TRACKING */ |
| else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_VIN_UV_STS) |
| pr_err("%s: VIN UV", __func__); /* VIN < V_UVTH */ |
| else |
| pr_err("%s: Invalid VIN or VOUT", __func__); |
| |
| ret = -EINVAL; |
| } else if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_NTC_TEMP_STS) { |
| /* NTC protection */ |
| int ntc_adc, ntc_th; |
| /* NTC threshold */ |
| u8 reg_data[2]; |
| pca9468_bulk_read_reg(pca9468, PCA9468_REG_NTC_TH_1, reg_data, 2); |
| ntc_th = ((reg_data[1] & PCA9468_BIT_NTC_THRESHOLD9_8)<<8) | reg_data[0]; /* uV unit */ |
| /* Read NTC ADC */ |
| ntc_adc = pca9468_read_adc(pca9468, ADCCH_NTC); /* uV unit */ |
| pr_err("%s: NTC Protection, NTC_TH=%d(uV), NTC_ADC=%d(uV)", __func__, ntc_th, ntc_adc); |
| |
| ret = -EINVAL; |
| } else if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_CTRL_LIMIT_STS) { |
| /* OCP event happens */ |
| /* Check STS_B */ |
| if (val[PCA9468_REG_STS_B] & PCA9468_BIT_OCP_FAST_STS) |
| pr_err("%s: IIN is over OCP_FAST", __func__); /* OCP_dlfeFAST happened */ |
| else if (val[PCA9468_REG_STS_B] & PCA9468_BIT_OCP_AVG_STS) |
| pr_err("%s: IIN is over OCP_AVG", __func__); /* OCP_AVG happened */ |
| else |
| pr_err("%s: No Loop active", __func__); |
| |
| ret = -EINVAL; |
| } else if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_TEMP_REG_STS) { |
| /* Over temperature protection */ |
| pr_err("%s: Device is in temperature regulation", __func__); |
| |
| ret = -EINVAL; |
| } else if (val[PCA9468_REG_INT1_STS] & PCA9468_BIT_TIMER_STS) { |
| /* Check STS_B */ |
| if (val[PCA9468_REG_STS_B] & PCA9468_BIT_CHARGE_TIMER_STS) |
| pr_err("%s: Charger timer is expired", __func__); |
| else if (val[PCA9468_REG_STS_B] & PCA9468_BIT_WATCHDOG_TIMER_STS) |
| pr_err("%s: Watchdog timer is expired", __func__); |
| else |
| pr_err("%s: Timer INT, but no timer STS", __func__); |
| |
| ret = -EINVAL; |
| } |
| else if (val[PCA9468_REG_STS_A] & PCA9468_BIT_CFLY_SHORT_STS) { |
| /* Flying cap short */ |
| pr_err("%s: Flying Cap is shorted to GND", __func__); /* Flying cap is short to GND */ |
| ret = -EINVAL; |
| } else { |
| /* There is no error, but not active state */ |
| if (reg_val & PCA9468_BIT_STANDBY_STATE_STS) { |
| /* Standby state */ |
| /* Check the RCP condition, T_REVI_DET is 300ms */ |
| /* Wait 200ms */ |
| msleep(200); |
| |
| /* Check the charging state */ |
| /* Sometimes battery driver might call set_property function to stop charging during msleep |
| At this case, charging state would change DC_STATE_NO_CHARGING. |
| PCA9468 should stop checking RCP condition and exit timer_work |
| */ |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| pr_err("%s: other driver forced to stop direct charging\n", __func__); |
| ret = -EINVAL; |
| } else { |
| /* Keep the current charging state */ |
| /* Check PCA948 state again */ |
| /* Read STS_B */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_STS_B, ®_val); |
| if (ret < 0) |
| goto error; |
| |
| pr_info("%s:RCP check, STS_B=0x%x\n", __func__, reg_val); |
| |
| /* Check Active status */ |
| if (reg_val & PCA9468_BIT_ACTIVE_STATE_STS) { |
| /* RCP condition happened, but VIN is still valid */ |
| /* If VIN is increased, input current will increase over IIN_LOW level */ |
| /* Normal charging */ |
| pr_info("%s: RCP happened before, but VIN is valid\n", __func__); |
| ret = 0; |
| } else if (reg_val & PCA9468_BIT_STANDBY_STATE_STS) { |
| /* It is not RCP condition */ |
| /* Need to retry if DC is in starting state */ |
| pr_err("%s: Any abnormal condition, retry\n", __func__); |
| /* Dump register */ |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_INT1, &val[PCA9468_REG_INT1], 7); |
| pr_err("%s: Error reg[1]=0x%x,[2]=0x%x,[3]=0x%x,[4]=0x%x,[5]=0x%x,[6]=0x%x,[7]=0x%x\n", |
| __func__, val[1], val[2], val[3], val[4], val[5], val[6], val[7]); |
| ret = pca9468_bulk_read_reg(pca9468, 0x48, test_val, 3); |
| pr_err("%s: Error reg[0x48]=0x%x,[0x49]=0x%x,[0x4a]=0x%x\n", |
| __func__, test_val[0], test_val[1], test_val[2]); |
| ret = -EAGAIN; |
| } else { |
| /* Shutdown State */ |
| pr_err("%s: Shutdown state\n", __func__); |
| /* Dump register */ |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_INT1, &val[PCA9468_REG_INT1], 7); |
| pr_err("%s: Error reg[1]=0x%x,[2]=0x%x,[3]=0x%x,[4]=0x%x,[5]=0x%x,[6]=0x%x,[7]=0x%x\n", |
| __func__, val[1], val[2], val[3], val[4], val[5], val[6], val[7]); |
| ret = pca9468_bulk_read_reg(pca9468, 0x48, test_val, 3); |
| pr_err("%s: Error reg[0x48]=0x%x,[0x49]=0x%x,[0x4a]=0x%x\n", |
| __func__, test_val[0], test_val[1], test_val[2]); |
| ret = -EINVAL; |
| } |
| } |
| } else { |
| /* PCA9468 is in shutdown state */ |
| pr_err("%s: PCA9468 is in shutdown state\n", __func__); |
| ret = -EINVAL; |
| } |
| } |
| } |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_test_read(pca9468); |
| #endif |
| |
| error: |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| if (ret == -EINVAL) { |
| pca9468->chg_status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| pca9468->health_status = POWER_SUPPLY_HEALTH_DC_ERR; |
| } |
| #endif |
| pr_info("%s: Active Status=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* Check CC Mode status */ |
| static int pca9468_check_ccmode_status(struct pca9468_charger *pca9468) |
| { |
| unsigned int reg_val; |
| int ret, vbat; |
| |
| /* Read STS_A */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_STS_A, ®_val); |
| if (ret < 0) |
| goto error; |
| |
| /* Read VBAT ADC */ |
| vbat = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| |
| /* Check STS_A */ |
| if (reg_val & PCA9468_BIT_VIN_UV_STS) { |
| ret = CCMODE_VIN_UVLO; |
| } else if (reg_val & PCA9468_BIT_CHG_LOOP_STS) { |
| ret = CCMODE_CHG_LOOP; |
| } else if (reg_val & PCA9468_BIT_VFLT_LOOP_STS) { |
| ret = CCMODE_VFLT_LOOP; |
| } else if (reg_val & PCA9468_BIT_IIN_LOOP_STS) { |
| ret = CCMODE_IIN_LOOP; |
| } else { |
| ret = CCMODE_LOOP_INACTIVE; |
| } |
| |
| error: |
| pr_info("%s: CCMODE Status=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* Check CVMode Status */ |
| static int pca9468_check_cvmode_status(struct pca9468_charger *pca9468) |
| { |
| unsigned int val; |
| int ret, iin; |
| |
| if (pca9468->charging_state == DC_STATE_START_CV) { |
| /* Read STS_A */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_STS_A, &val); |
| if (ret < 0) |
| goto error; |
| /* Check STS_A */ |
| if (val & PCA9468_BIT_CHG_LOOP_STS) { |
| ret = CVMODE_CHG_LOOP; |
| } else if (val & PCA9468_BIT_VFLT_LOOP_STS) { |
| ret = CVMODE_VFLT_LOOP; |
| } else if (val & PCA9468_BIT_IIN_LOOP_STS) { |
| ret = CVMODE_IIN_LOOP; |
| } else if (val & PCA9468_BIT_VIN_UV_STS) { |
| ret = CVMODE_VIN_UVLO; |
| } else { |
| /* Any LOOP is inactive */ |
| ret = CVMODE_LOOP_INACTIVE; |
| } |
| } else { |
| /* Read IIN ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| if (iin < 0) { |
| ret = iin; |
| goto error; |
| } |
| |
| /* Check IIN < Input Topoff current */ |
| if (pca9468->pdata->v_float == pca9468->pdata->v_float_max && |
| iin < pca9468->pdata->iin_topoff) { |
| /* Direct Charging Done */ |
| ret = CVMODE_CHG_DONE; |
| } else { |
| /* It doesn't reach top-off condition yet */ |
| |
| /* Read STS_A */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_STS_A, &val); |
| if (ret < 0) |
| goto error; |
| /* Check STS_A */ |
| if (val & PCA9468_BIT_CHG_LOOP_STS) { |
| ret = CVMODE_CHG_LOOP; |
| } else if (val & PCA9468_BIT_VFLT_LOOP_STS) { |
| ret = CVMODE_VFLT_LOOP; |
| } else if (val & PCA9468_BIT_IIN_LOOP_STS) { |
| ret = CVMODE_IIN_LOOP; |
| } else if (val & PCA9468_BIT_VIN_UV_STS) { |
| ret = CVMODE_VIN_UVLO; |
| } else { |
| /* Any LOOP is inactive */ |
| ret = CVMODE_LOOP_INACTIVE; |
| } |
| } |
| } |
| |
| error: |
| pr_info("%s: CVMODE Status=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* 2:1 Direct Charging Adjust CC MODE control */ |
| static int pca9468_charge_adjust_ccmode(struct pca9468_charger *pca9468) |
| { |
| int iin, ccmode; |
| int vbatt; |
| int vin_vol, val; |
| int ret = 0; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_ADJUST_CC); |
| #else |
| pca9468->charging_state = DC_STATE_ADJUST_CC; |
| #endif |
| |
| ret = pca9468_check_error(pca9468); |
| if (ret != 0) |
| goto error; // This is not active mode. |
| |
| ccmode = pca9468_check_ccmode_status(pca9468); |
| if (ccmode < 0) { |
| ret = ccmode; |
| goto error; |
| } |
| |
| switch(ccmode) { |
| case CCMODE_IIN_LOOP: |
| case CCMODE_CHG_LOOP: |
| /* Check TA current */ |
| if (pca9468->ta_cur > PCA9468_TA_MIN_CUR) { |
| /* TA current is higher than 1.0A */ |
| /* Decrease TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP; |
| } |
| pr_info("%s: CC adjust End(LOOP): ta_cur=%d, ta_vol=%d\n", __func__, pca9468->ta_cur, pca9468->ta_vol); |
| /* Read VBAT ADC */ |
| vbatt = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| /* TA target voltage = TA voltage + (VFLOAT - VBAT_ADC)*2*TA_mode + 100mV */ |
| val = pca9468->ta_vol + (pca9468->pdata->v_float - vbatt)*2*pca9468->ta_mode + 100000; |
| val = val/PD_MSG_TA_VOL_STEP; |
| pca9468->ta_target_vol = val*PD_MSG_TA_VOL_STEP; |
| if (pca9468->ta_target_vol > pca9468->ta_max_vol) |
| pca9468->ta_target_vol = pca9468->ta_max_vol; |
| /* End TA voltage and current adjustment */ |
| pr_info("%s: CC adjust End(LOOP): ta_target_vol=%d\n", __func__, pca9468->ta_target_vol); |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| /* Send PD Message and go to Start CC mode */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_START_CC); |
| #else |
| pca9468->charging_state = DC_STATE_START_CC; |
| #endif |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CCMODE_VFLT_LOOP: |
| /* Save TA target and voltage*/ |
| pca9468->ta_target_vol = pca9468->ta_vol; |
| pr_info("%s: CC adjust End(VFLOAT): ta_target_vol=%d\n", __func__, pca9468->ta_target_vol); |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| /* Go to Pre-CV mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ENTER_CVMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CCMODE_LOOP_INACTIVE: |
| /* Check IIN ADC with IIN */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| /* IIN_ADC > IIN_CC -20mA ? */ |
| if (iin > (pca9468->iin_cc - PCA9468_IIN_ADC_OFFSET)) { |
| /* Input current is already over IIN_CC */ |
| /* End TA voltage and current adjustment */ |
| /* change charging state to Start CC mode */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_START_CC); |
| #else |
| pca9468->charging_state = DC_STATE_START_CC; |
| #endif |
| /* Read VBAT ADC */ |
| vbatt = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| /* TA target voltage = TA voltage + (VFLOAT - VBAT_ADC)*2*TA_mode + 100mV */ |
| val = pca9468->ta_vol + (pca9468->pdata->v_float - vbatt)*2*pca9468->ta_mode + 100000; |
| val = val/PD_MSG_TA_VOL_STEP; |
| pca9468->ta_target_vol = val*PD_MSG_TA_VOL_STEP; |
| if (pca9468->ta_target_vol > pca9468->ta_max_vol) |
| pca9468->ta_target_vol = pca9468->ta_max_vol; |
| pr_info("%s: CC adjust End: IIN_ADC=%d, ta_target_vol=%d\n", __func__, iin, pca9468->ta_target_vol); |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| /* Go to Start CC mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ENTER_CCMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Check TA voltage */ |
| if (pca9468->ta_vol == pca9468->ta_max_vol) { |
| /* TA voltage is already max value */ |
| pr_info("%s: CC adjust End: MAX value, ta_vol=%d, ta_cur=%d\n", |
| __func__, pca9468->ta_vol, pca9468->ta_cur); |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| /* Save TA target voltage */ |
| pca9468->ta_target_vol = pca9468->ta_vol; |
| /* Go to CC mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Check TA tolerance */ |
| /* The current input current compares the final input current(IIN_CC) with 100mA offset */ |
| /* PPS current tolerance has +/-150mA, so offset defined 100mA(tolerance +50mA) */ |
| if (iin < (pca9468->iin_cc - PCA9468_TA_IIN_OFFSET)) { |
| /* TA voltage too low to enter TA CC mode, so we should increae TA voltage */ |
| pca9468->ta_vol = pca9468->ta_vol + PCA9468_TA_VOL_STEP_ADJ_CC*pca9468->ta_mode; |
| if (pca9468->ta_vol > pca9468->ta_max_vol) |
| pca9468->ta_vol = pca9468->ta_max_vol; |
| pr_info("%s: CC adjust Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_VOL; |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* compare IIN ADC with previous IIN ADC + 20mA */ |
| if (iin > (pca9468->prev_iin + PCA9468_IIN_ADC_OFFSET)) { |
| /* TA can supply more current if TA voltage is high */ |
| /* TA voltage too low to enter TA CC mode, so we should increae TA voltage */ |
| pca9468->ta_vol = pca9468->ta_vol + PCA9468_TA_VOL_STEP_ADJ_CC*pca9468->ta_mode; |
| if (pca9468->ta_vol > pca9468->ta_max_vol) |
| pca9468->ta_vol = pca9468->ta_max_vol; |
| pr_info("%s: CC adjust Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_VOL; |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Check the previous increment */ |
| if (pca9468->prev_inc == INC_TA_CUR) { |
| /* The previous increment is TA current, but input current does not increase */ |
| /* Try to increase TA voltage(40mV) */ |
| pca9468->ta_vol = pca9468->ta_vol + PCA9468_TA_VOL_STEP_ADJ_CC*pca9468->ta_mode;; |
| if (pca9468->ta_vol > pca9468->ta_max_vol) |
| pca9468->ta_vol = pca9468->ta_max_vol; |
| pr_info("%s: CC adjust(flag) Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_VOL; |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* The previous increment is TA voltage, but input current does not increase */ |
| /* Try to increase TA current */ |
| /* Check APDO max current */ |
| if (pca9468->ta_cur == pca9468->ta_max_cur) { |
| /* TA current is maximum current */ |
| /* Read VBAT ADC */ |
| vbatt = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| /* TA target voltage = TA voltage + (VFLOAT - VBAT_ADC)*2*TA_mode + 100mV */ |
| val = pca9468->ta_vol + (pca9468->pdata->v_float - vbatt)*2*pca9468->ta_mode + 100000; |
| val = val/PD_MSG_TA_VOL_STEP; |
| pca9468->ta_target_vol = val*PD_MSG_TA_VOL_STEP; |
| if (pca9468->ta_target_vol > pca9468->ta_max_vol) |
| pca9468->ta_target_vol = pca9468->ta_max_vol; |
| pr_info("%s: CC adjust End(MAX_CUR): IIN_ADC=%d, ta_target_vol=%d\n", __func__, iin, pca9468->ta_target_vol); |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| /* Go to Start CC mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ENTER_CCMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* TA has tolerance and compensate it as real current */ |
| /* Increase TA current(50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur + PD_MSG_TA_CUR_STEP; |
| if (pca9468->ta_cur > pca9468->ta_max_cur) |
| pca9468->ta_cur = pca9468->ta_max_cur; |
| pr_info("%s: CC adjust Cont: ta_cur=%d\n", __func__, pca9468->ta_cur); |
| /* Set TA increment flag */ |
| pca9468->prev_inc = INC_TA_CUR; |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| } |
| } |
| } |
| } |
| } |
| /* Save previous iin adc */ |
| pca9468->prev_iin = iin; |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CCMODE_VIN_UVLO: |
| /* VIN UVLO - just notification , it works by hardware */ |
| vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN); |
| pr_info("%s: CC adjust VIN_UVLO: ta_vol=%d, vin_vol=%d\n", __func__, pca9468->ta_cur, vin_vol); |
| /* Check VIN after 1sec */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ADJUST_CCMODE; |
| pca9468->timer_period = 1000; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| default: |
| break; |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| /* 2:1 Direct Charging Start CC MODE control - Pre CC MODE */ |
| /* Increase TA voltage to TA target voltage */ |
| static int pca9468_charge_start_ccmode(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_START_CC); |
| #else |
| pca9468->charging_state = DC_STATE_START_CC; |
| #endif |
| |
| /* Increase TA voltage */ |
| pca9468->ta_vol = pca9468->ta_vol + PCA9468_TA_VOL_STEP_PRE_CC * pca9468->ta_mode; |
| /* Check TA target voltage */ |
| if (pca9468->ta_vol >= pca9468->ta_target_vol) { |
| pca9468->ta_vol = pca9468->ta_target_vol; |
| pr_info("%s: PreCC END: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| |
| /* Change to DC state to CC mode */ |
| pca9468->charging_state = DC_STATE_CC_MODE; |
| } else { |
| pr_info("%s: PreCC: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| } |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| return ret; |
| } |
| |
| /* 2:1 Direct Charging CC MODE control */ |
| static int pca9468_charge_ccmode(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| int ccmode; |
| int vin_vol, iin; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_CC_MODE); |
| #else |
| pca9468->charging_state = DC_STATE_CC_MODE; |
| #endif |
| |
| ret = pca9468_check_error(pca9468); |
| if (ret != 0) |
| goto error; // This is not active mode. |
| |
| /* Check new vfloat request and new iin request */ |
| if (pca9468->req_new_vfloat == true) { |
| /* Clear request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_vfloat = false; |
| mutex_unlock(&pca9468->lock); |
| ret = pca9468_set_new_vfloat(pca9468); |
| } else if (pca9468->req_new_iin == true) { |
| /* Clear request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| ret = pca9468_set_new_iin(pca9468); |
| } else { |
| ccmode = pca9468_check_ccmode_status(pca9468); |
| if (ccmode < 0) { |
| ret = ccmode; |
| goto error; |
| } |
| |
| switch(ccmode) { |
| case CCMODE_LOOP_INACTIVE: |
| /* Set input current compensation */ |
| /* Check the current TA current with TA_MIN_CUR */ |
| if (pca9468->ta_cur <= PCA9468_TA_MIN_CUR) { |
| /* Set TA current to PCA9468_TA_MIN_CUR(1.0A) */ |
| pca9468->ta_cur = PCA9468_TA_MIN_CUR; |
| /* Need input voltage compensation */ |
| ret = pca9468_set_ta_voltage_comp(pca9468); |
| } else { |
| /* Check TA_MAX_VOL for detecting constant power mode */ |
| if (pca9468->ta_max_vol >= (PCA9468_TA_MAX_VOL_CP * pca9468->ta_mode)) { |
| /* Need input current compensation */ |
| ret = pca9468_set_ta_current_comp(pca9468); |
| } else { |
| /* Need input current compensation in constant power mode */ |
| ret = pca9468_set_ta_current_comp2(pca9468); |
| } |
| } |
| pr_info("%s: CC INACTIVE: ta_cur=%d, ta_vol=%d\n", __func__, pca9468->ta_cur, pca9468->ta_vol); |
| break; |
| |
| case CCMODE_VFLT_LOOP: |
| /* Read IIN_ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| pr_info("%s: CC VFLOAT: iin=%d\n", __func__, iin); |
| /* go to Pre-CV mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ENTER_CVMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CCMODE_IIN_LOOP: |
| case CCMODE_CHG_LOOP: |
| /* Read IIN_ADC */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| /* Check the current TA current with TA_MIN_CUR */ |
| if (pca9468->ta_cur <= PCA9468_TA_MIN_CUR) { |
| /* Decrease TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP; |
| pr_info("%s: CC LOOP:iin=%d, next_ta_vol=%d\n", __func__, iin, pca9468->ta_vol); |
| } else { |
| /* Decrease TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP; |
| pr_info("%s: CC LOOP:iin=%d, next_ta_cur=%d\n", __func__, iin, pca9468->ta_cur); |
| } |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CCMODE_VIN_UVLO: |
| /* VIN UVLO - just notification , it works by hardware */ |
| vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN); |
| pr_info("%s: CC VIN_UVLO: ta_vol=%d, vin_vol=%d\n", __func__, pca9468->ta_cur, vin_vol); |
| /* Check VIN after 1sec */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| pca9468->timer_period = 1000; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* 2:1 Direct Charging Start CV MODE control - Pre CV MODE */ |
| static int pca9468_charge_start_cvmode(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| int cvmode; |
| int vin_vol; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_START_CV); |
| #else |
| pca9468->charging_state = DC_STATE_START_CV; |
| #endif |
| |
| ret = pca9468_check_error(pca9468); |
| if (ret != 0) |
| goto error; // This is not active mode. |
| |
| cvmode = pca9468_check_cvmode_status(pca9468); |
| if (cvmode < 0) { |
| ret = cvmode; |
| goto error; |
| } |
| |
| switch(cvmode) { |
| case CVMODE_CHG_LOOP: |
| case CVMODE_IIN_LOOP: |
| /* Check TA current */ |
| if (pca9468->ta_cur > PCA9468_TA_MIN_CUR) { |
| /* TA current is higher than 1.0A */ |
| /* Decrease TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP; |
| pr_info("%s: PreCV Cont: ta_cur=%d\n", __func__, pca9468->ta_cur); |
| } else { |
| /* TA current is less than 1.0A */ |
| /* Decrease TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP; |
| pr_info("%s: PreCV Cont(IIN_LOOP): ta_vol=%d\n", __func__, pca9468->ta_vol); |
| } |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_VFLT_LOOP: |
| /* Decrease TA voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol - PCA9468_TA_VOL_STEP_PRE_CV * pca9468->ta_mode; |
| pr_info("%s: PreCV Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_LOOP_INACTIVE: |
| /* Exit Pre CV mode */ |
| pr_info("%s: PreCV End: ta_vol=%d, ta_cur=%d\n", __func__, pca9468->ta_vol, pca9468->ta_cur); |
| |
| /* Set TA target voltage to TA voltage */ |
| pca9468->ta_target_vol = pca9468->ta_vol; |
| |
| /* Need to implement notification to other driver */ |
| /* To do here */ |
| |
| /* Go to CV mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_VIN_UVLO: |
| /* VIN UVLO - just notification , it works by hardware */ |
| vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN); |
| pr_info("%s: PreCV VIN_UVLO: ta_vol=%d, vin_vol=%d\n", __func__, pca9468->ta_cur, vin_vol); |
| /* Check VIN after 1sec */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ENTER_CVMODE; |
| pca9468->timer_period = 1000; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| default: |
| break; |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| /* 2:1 Direct Charging CV MODE control */ |
| static int pca9468_charge_cvmode(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| int cvmode; |
| int vin_vol; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_CV_MODE); |
| #else |
| pca9468->charging_state = DC_STATE_CV_MODE; |
| #endif |
| |
| ret = pca9468_check_error(pca9468); |
| if (ret != 0) |
| goto error; // This is not active mode. |
| |
| /* Check new vfloat request and new iin request */ |
| if (pca9468->req_new_vfloat == true) { |
| /* Clear request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_vfloat = false; |
| mutex_unlock(&pca9468->lock); |
| ret = pca9468_set_new_vfloat(pca9468); |
| } else if (pca9468->req_new_iin == true) { |
| /* Clear request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| ret = pca9468_set_new_iin(pca9468); |
| } else { |
| cvmode = pca9468_check_cvmode_status(pca9468); |
| if (cvmode < 0) { |
| ret = cvmode; |
| goto error; |
| } |
| |
| switch(cvmode) { |
| case CVMODE_CHG_DONE: |
| /* Charging Done */ |
| /* Keep CV mode until battery driver send stop charging */ |
| |
| /* Need to implement notification function */ |
| /* To do here */ |
| |
| pr_info("%s: CV Done\n", __func__); |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_done(pca9468, true); |
| #endif |
| |
| /* Check the charging status after notification function */ |
| if(pca9468->charging_state != DC_STATE_NO_CHARGING) { |
| /* Notification function does not stop timer work yet */ |
| /* Keep the charging done state */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } else { |
| /* Already called stop charging by notification function */ |
| pr_info("%s: Already stop DC\n", __func__); |
| } |
| break; |
| |
| case CVMODE_CHG_LOOP: |
| case CVMODE_IIN_LOOP: |
| /* Check TA current */ |
| if (pca9468->ta_cur > PCA9468_TA_MIN_CUR) { |
| /* TA current is higher than (1.0A*TA_mode) */ |
| /* Decrease TA current (50mA) */ |
| pca9468->ta_cur = pca9468->ta_cur - PD_MSG_TA_CUR_STEP; |
| pr_info("%s: CV LOOP, Cont: ta_cur=%d\n", __func__, pca9468->ta_cur); |
| } else { |
| /* TA current is less than (1.0A*TA_mode) */ |
| /* Decrease TA Voltage (20mV) */ |
| pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP; |
| pr_info("%s: CV LOOP, Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| } |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_VFLT_LOOP: |
| /* Decrease TA voltage */ |
| pca9468->ta_vol = pca9468->ta_vol - PD_MSG_TA_VOL_STEP; |
| pr_info("%s: CV VFLOAT, Cont: ta_vol=%d\n", __func__, pca9468->ta_vol); |
| |
| /* Set TA target voltage to TA voltage */ |
| pca9468->ta_target_vol = pca9468->ta_vol; |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_LOOP_INACTIVE: |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_VIN_UVLO: |
| /* VIN UVLO - just notification , it works by hardware */ |
| vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN); |
| pr_info("%s: CC VIN_UVLO: ta_vol=%d, vin_vol=%d\n", __func__, pca9468->ta_cur, vin_vol); |
| /* Check VIN after 1sec */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| pca9468->timer_period = 1000; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| /* 2:1 Direct Charging WC CV MODE control */ |
| static int pca9468_charge_wc_cvmode(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| int cvmode; |
| int vin_vol; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| pca9468->charging_state = DC_STATE_WC_CV_MODE; |
| |
| ret = pca9468_check_error(pca9468); |
| if (ret != 0) |
| goto error; // This is not active mode. |
| |
| /* Check new vfloat request and new iin request */ |
| if (pca9468->req_new_vfloat == true) { |
| /* Clear request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_vfloat = false; |
| mutex_unlock(&pca9468->lock); |
| ret = pca9468_set_new_vfloat(pca9468); |
| } else if (pca9468->req_new_iin == true) { |
| /* Clear request flag */ |
| mutex_lock(&pca9468->lock); |
| pca9468->req_new_iin = false; |
| mutex_unlock(&pca9468->lock); |
| ret = pca9468_set_new_iin(pca9468); |
| } else { |
| cvmode = pca9468_check_cvmode_status(pca9468); |
| if (cvmode < 0) { |
| ret = cvmode; |
| goto error; |
| } |
| |
| switch(cvmode) { |
| case CVMODE_CHG_DONE: |
| /* Charging Done */ |
| /* Keep WC CV mode until battery driver send stop charging */ |
| |
| /* Need to implement notification function */ |
| /* To do here */ |
| |
| pr_info("%s: WC CV Done\n", __func__); |
| |
| /* Check the charging status after notification function */ |
| if(pca9468->charging_state != DC_STATE_NO_CHARGING) { |
| /* Notification function does not stop timer work yet */ |
| /* Keep the charging done state */ |
| /* Set timer */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } else { |
| /* Already called stop charging by notification function */ |
| pr_info("%s: Already stop DC\n", __func__); |
| } |
| break; |
| |
| case CVMODE_CHG_LOOP: |
| case CVMODE_IIN_LOOP: |
| /* IIN_LOOP happens */ |
| pr_info("%s: WC CV IIN_LOOP\n", __func__); |
| |
| /* Need to control WC RX voltage or current */ |
| |
| /* Need to implement notification function */ |
| |
| /* To do here */ |
| |
| /* Set timer - 1s */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_VFLT_LOOP: |
| /* VFLOAT_LOOP happens */ |
| pr_info("%s: WC CV VFLOAT_LOOP\n", __func__); |
| |
| /* Need to control WC RX voltage */ |
| |
| /* Need to implement notification function */ |
| |
| /* To do here */ |
| |
| /* Set timer - 1s */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_LOOP_INACTIVE: |
| pr_info("%s: WC CV INACTIVE\n", __func__); |
| /* Set timer - 10s */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case CVMODE_VIN_UVLO: |
| /* VIN UVLO - just notification , it works by hardware */ |
| vin_vol = pca9468_read_adc(pca9468, ADCCH_VIN); |
| pr_info("%s: CV VIN_UVLO: vin_vol=%d\n", __func__, vin_vol); |
| /* Check VIN after 1sec */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = PCA9468_CVMODE_CHECK2_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| /* Preset TA voltage and current for Direct Charging Mode */ |
| static int pca9468_preset_dcmode(struct pca9468_charger *pca9468) |
| { |
| int vbat; |
| unsigned int val; |
| int ret = 0; |
| int ta_mode; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_PRESET_DC); |
| #else |
| pca9468->charging_state = DC_STATE_PRESET_DC; |
| #endif |
| |
| /* Read VBAT ADC */ |
| vbat = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| if (vbat < 0) { |
| ret = vbat; |
| goto error; |
| } |
| |
| /* Compare VBAT with VBAT ADC */ |
| if (vbat > pca9468->pdata->v_float) { |
| /* Invalid battery voltage to start direct charging */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pr_err("## %s: vbat adc(%duA) is higher than VFLOAT(%duA)\n", |
| __func__, vbat, pca9468->pdata->v_float); |
| #else |
| pr_err("%s: vbat adc is higher than VFLOAT\n", __func__); |
| #endif |
| ret = -EINVAL; |
| goto error; |
| } |
| |
| /* Check the TA mode for wireless charging */ |
| if (pca9468->ta_mode == WC_DC_MODE) { |
| /* Wireless Charger DC mode */ |
| /* Set IIN_CC to IIN */ |
| pca9468->iin_cc = pca9468->pdata->iin_cfg; |
| /* Go to preset config */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PRESET_CONFIG; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| goto error; |
| } |
| |
| /* Set the TA max current to input request current(iin_cfg) initially |
| to get TA maximum current from PD IC */ |
| pca9468->ta_max_cur = pca9468->pdata->iin_cfg; |
| /* Set the TA max voltage to enough high value to find TA maximum voltage initially */ |
| pca9468->ta_max_vol = PCA9468_TA_MAX_VOL * pca9468->pdata->ta_mode; |
| /* Search the proper object position of PDO */ |
| pca9468->ta_objpos = 0; |
| /* Get the APDO max current/voltage(TA_MAX_CUR/VOL) */ |
| ret = pca9468_get_apdo_max_power(pca9468); |
| if (ret < 0) { |
| /* TA does not have the desired APDO */ |
| /* Check the desired mode */ |
| if (pca9468->pdata->ta_mode == TA_4TO1_DC_MODE) { |
| /* TA doesn't have any APDO to support 4:1 mode */ |
| /* Get the APDO max current/voltage with 2:1 mode */ |
| pca9468->ta_max_vol = PCA9468_TA_MAX_VOL; |
| pca9468->ta_objpos = 0; |
| ret = pca9468_get_apdo_max_power(pca9468); |
| if (ret < 0) { |
| pr_err("%s: TA doesn't have any APDO to support 2:1 or 4:1\n", __func__); |
| pca9468->ta_mode = TA_NO_DC_MODE; |
| goto error; |
| } else { |
| /* TA has APDO to support 2:1 mode */ |
| pca9468->ta_mode = TA_2TO1_DC_MODE; |
| } |
| } else { |
| /* The desired TA mode is 2:1 mode */ |
| /* TA doesn't have any APDO to support 2:1 mode*/ |
| pr_err("%s: TA doesn't have any APDO to support 2:1\n", __func__); |
| pca9468->ta_mode = TA_NO_DC_MODE; |
| goto error; |
| } |
| } else { |
| /* TA has the desired APDO */ |
| pca9468->ta_mode = pca9468->pdata->ta_mode; |
| } |
| |
| ta_mode = pca9468->ta_mode; |
| |
| /* Calculate new TA maximum current and voltage that used in the direct charging */ |
| /* Set IIN_CC to MIN[IIN, (TA_MAX_CUR by APDO)*TA_mode]*/ |
| pca9468->iin_cc = MIN(pca9468->pdata->iin_cfg, (pca9468->ta_max_cur*ta_mode)); |
| /* Set the current IIN_CC to iin_cfg for recovering it after resolution adjustment */ |
| pca9468->pdata->iin_cfg = pca9468->iin_cc; |
| /* Calculate new TA max voltage */ |
| /* Adjust IIN_CC with APDO resoultion(50mA) - It will recover to the original value after max voltage calculation */ |
| val = pca9468->iin_cc/PD_MSG_TA_CUR_STEP; |
| pca9468->iin_cc = val*PD_MSG_TA_CUR_STEP; |
| /* Set TA_MAX_VOL to MIN[PCA9468_TA_MAX_VOL*TA_mode, TA_MAX_PWR/(IIN_CC/TA_mode)] */ |
| val = pca9468->ta_max_pwr/(pca9468->iin_cc/ta_mode/1000); /* mV */ |
| val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ |
| val = val*PD_MSG_TA_VOL_STEP; /* uV */ |
| pca9468->ta_max_vol = MIN(val, PCA9468_TA_MAX_VOL*ta_mode); |
| |
| /* Set TA voltage to MAX[8000mV*TA_mode, (2*VBAT_ADC*TA_mode + 500 mV)] */ |
| pca9468->ta_vol = max(PCA9468_TA_MIN_VOL_PRESET*ta_mode, (2*vbat*ta_mode + PCA9468_TA_VOL_PRE_OFFSET)); |
| val = pca9468->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ |
| pca9468->ta_vol = val*PD_MSG_TA_VOL_STEP; |
| /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL*TA_mode] */ |
| pca9468->ta_vol = MIN(pca9468->ta_vol, pca9468->ta_max_vol*ta_mode); |
| /* Set the initial TA current to IIN_CC/TA_mode */ |
| pca9468->ta_cur = pca9468->iin_cc/ta_mode; |
| /* Recover IIN_CC to the original value(iin_cfg) */ |
| pca9468->iin_cc = pca9468->pdata->iin_cfg; |
| |
| pr_info("%s: Preset DC, ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, ta_mode=%d\n", |
| __func__, pca9468->ta_max_vol, pca9468->ta_max_cur, pca9468->ta_max_pwr, pca9468->iin_cc, pca9468->ta_mode); |
| |
| pr_info("%s: Preset DC, ta_vol=%d, ta_cur=%d\n", |
| __func__, pca9468->ta_vol, pca9468->ta_cur); |
| |
| /* Send PD Message */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PDMSG_SEND; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* Preset direct charging configuration */ |
| static int pca9468_preset_config(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_PRESET_DC); |
| #else |
| pca9468->charging_state = DC_STATE_PRESET_DC; |
| #endif |
| |
| /* Set IIN_CFG to IIN_CC */ |
| ret = pca9468_set_input_current(pca9468, pca9468->iin_cc); |
| if (ret < 0) |
| goto error; |
| |
| /* Set ICHG_CFG to enough high value */ |
| ret = pca9468_set_charging_current(pca9468, pca9468->pdata->ichg_cfg); |
| if (ret < 0) |
| goto error; |
| |
| /* Set VFLOAT */ |
| ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float); |
| if (ret < 0) |
| goto error; |
| |
| /* Enable PCA9468 */ |
| ret = pca9468_set_charging(pca9468, true); |
| if (ret < 0) |
| goto error; |
| |
| /* Clear previous iin adc */ |
| pca9468->prev_iin = 0; |
| |
| /* Clear TA increment flag */ |
| pca9468->prev_inc = INC_NONE; |
| |
| /* Go to CHECK_ACTIVE state after 150ms*/ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_ACTIVE; |
| pca9468->timer_period = PCA4968_ENABLE_DELAY_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| ret = 0; |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| /* Check the charging status before entering the adjust cc mode */ |
| static int pca9468_check_active_state(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| |
| pr_info("%s: ======START=======\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_CHECK_ACTIVE); |
| #else |
| pca9468->charging_state = DC_STATE_CHECK_ACTIVE; |
| #endif |
| |
| ret = pca9468_check_error(pca9468); |
| |
| if (ret == 0) { |
| /* PCA9468 is active state */ |
| /* Clear retry counter */ |
| pca9468->retry_cnt = 0; |
| /* Check TA mode */ |
| if (pca9468->ta_mode == WC_DC_MODE) { |
| /* Go to WC CV mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_CHECK_WCCVMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } else { |
| /* Go to Adjust CC mode */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_ADJUST_CCMODE; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| } |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| ret = 0; |
| } else if (ret == -EAGAIN) { |
| /* It is the retry condition */ |
| /* Check the retry counter */ |
| if (pca9468->retry_cnt < PCA9468_MAX_RETRY_CNT) { |
| /* Disable charging */ |
| ret = pca9468_set_charging(pca9468, false); |
| /* Increase retry counter */ |
| pca9468->retry_cnt++; |
| pr_err("%s: retry charging start - retry_cnt=%d\n", __func__, pca9468->retry_cnt); |
| /* Go to DC_STATE_PRESET_DC */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PRESET_DC; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| ret = 0; |
| } else { |
| pr_err("%s: retry fail\n", __func__); |
| /* Notify maximum retry error */ |
| ret = -EINVAL; |
| } |
| } else { |
| /* Implement error handler function if it is needed */ |
| /* Stop charging in timer_work */ |
| } |
| |
| return ret; |
| } |
| |
| |
| /* Enter direct charging algorithm */ |
| static int pca9468_start_direct_charging(struct pca9468_charger *pca9468) |
| { |
| int ret; |
| unsigned int val; |
| #if !defined(CONFIG_BATTERY_SAMSUNG) /* temp disable wc dc charging */ |
| struct power_supply *psy; |
| union power_supply_propval pro_val; |
| #endif |
| |
| pr_info("%s: =========START=========\n", __func__); |
| |
| /* Set OV_DELTA to 30% */ |
| #if defined(CONFIG_SEC_FACTORY) |
| val = OV_DELTA_40P << MASK2SHIFT(PCA9468_BIT_OV_DELTA); |
| #else |
| val = OV_DELTA_30P << MASK2SHIFT(PCA9468_BIT_OV_DELTA); |
| #endif |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_SAFETY_CTRL, |
| PCA9468_BIT_OV_DELTA, val); |
| if (ret < 0) |
| return ret; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| /* Set watchdog timer enable */ |
| pca9468_set_wdt_enable(pca9468, WDT_ENABLE); |
| #if defined(CONFIG_ENG_BATTERY_CONCEPT) |
| pca9468_check_wdt_control(pca9468); |
| #else |
| pca9468_set_wdt_timer(pca9468, WDT_8SEC); |
| #endif |
| #endif |
| |
| /* Set Switching Frequency */ |
| val = pca9468->pdata->fsw_cfg; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, |
| PCA9468_BIT_FSW_CFG, val); |
| if (ret < 0) |
| return ret; |
| |
| /* current sense resistance */ |
| val = pca9468->pdata->snsres << MASK2SHIFT(PCA9468_BIT_SNSRES); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, |
| PCA9468_BIT_SNSRES, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Set EN_CFG to active low */ |
| val = PCA9468_EN_ACTIVE_L; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, |
| PCA9468_BIT_EN_CFG, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Set NTC voltage threshold */ |
| val = pca9468->pdata->ntc_th / PCA9468_NTC_TH_STEP; |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_NTC_TH_1, (val & 0xFF)); |
| if (ret < 0) |
| return ret; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_NTC_TH_2, |
| PCA9468_BIT_NTC_THRESHOLD9_8, (val >> 8)); |
| if (ret < 0) |
| return ret; |
| #if !defined(CONFIG_BATTERY_SAMSUNG) /* temp disable wc dc charging */ |
| /* Check the current power supply type whether it is the wireless charger or USBPD TA */ |
| /* The below code should be modified by the customer */ |
| /* Get power supply name */ |
| psy = power_supply_get_by_name("battery"); |
| if (!psy) { |
| dev_err(pca9468->dev, "Cannot find battery power supply\n"); |
| ret = -ENODEV; |
| return ret; |
| } |
| |
| /* Get the current power supply type */ |
| ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &pro_val); |
| power_supply_put(psy); |
| if (ret < 0) { |
| dev_err(pca9468->dev, "Cannot get power supply type from battery driver\n"); |
| return ret; |
| } |
| /* Check the current power supply type */ |
| if (pro_val.intval == POWER_SUPPLY_TYPE_WIRELESS) { |
| /* The current power supply type is wireless charger */ |
| pca9468->ta_mode = WC_DC_MODE; |
| pr_info("%s: The current power supply type is WC, ta_mode=%d\n", __func__, pca9468->ta_mode); |
| } |
| #endif |
| /* wake lock */ |
| __pm_stay_awake(&pca9468->monitor_wake_lock); |
| |
| /* Preset charging configuration and TA condition */ |
| ret = pca9468_preset_dcmode(pca9468); |
| |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| /* Check Vbat minimum level to start direct charging */ |
| static int pca9468_check_vbatmin(struct pca9468_charger *pca9468) |
| { |
| int vbat; |
| int ret; |
| union power_supply_propval val; |
| |
| pr_info("%s: =========START=========\n", __func__); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_charging_state(pca9468, DC_STATE_CHECK_VBAT); |
| #else |
| pca9468->charging_state = DC_STATE_CHECK_VBAT; |
| #endif |
| |
| /* Check Vbat */ |
| vbat = pca9468_read_adc(pca9468, ADCCH_VBAT); |
| if (vbat < 0) { |
| ret = vbat; |
| } |
| |
| /* Read switching charger status */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| ret = psy_do_property(pca9468->pdata->sec_dc_name, get, |
| POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val); |
| #else |
| ret = pca9468_get_switching_charger_property(POWER_SUPPLY_PROP_CHARGING_ENABLED, &val); |
| #endif |
| if (ret < 0) { |
| /* Start Direct Charging again after 1sec */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_VBATMIN_CHECK; |
| pca9468->timer_period = PCA9468_VBATMIN_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| goto error; |
| } |
| |
| if (val.intval == 0) { |
| /* already disabled switching charger */ |
| /* Clear retry counter */ |
| pca9468->retry_cnt = 0; |
| /* Preset TA voltage and PCA9468 parameters */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_PRESET_DC; |
| pca9468->timer_period = 0; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } else { |
| /* Switching charger is enabled */ |
| if (vbat > PCA9468_DC_VBAT_MIN) { |
| /* Start Direct Charging */ |
| /* now switching charger is enabled */ |
| /* disable switching charger first */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_set_switching_charger(pca9468, false); |
| #else |
| ret = pca9468_set_switching_charger(false, 0, 0, 0); |
| #endif |
| } |
| |
| /* Wait 1sec for stopping switching charger or Start 1sec timer for battery check */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_VBATMIN_CHECK; |
| pca9468->timer_period = PCA9468_VBATMIN_CHECK_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| #ifdef CONFIG_RTC_HCTOSYS |
| 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); |
| goto close_time; |
| } |
| |
| rc = rtc_valid_tm(&tm); |
| if (rc) { |
| pr_err("Invalid RTC time (%s): %d\n", |
| CONFIG_RTC_HCTOSYS_DEVICE, rc); |
| goto close_time; |
| } |
| rtc_tm_to_time(&tm, now_tm_sec); |
| |
| close_time: |
| rtc_class_close(rtc); |
| return rc; |
| } |
| #endif |
| |
| /* delayed work function for charging timer */ |
| static void pca9468_timer_work(struct work_struct *work) |
| { |
| struct pca9468_charger *pca9468 = container_of(work, struct pca9468_charger, |
| timer_work.work); |
| int ret = 0; |
| unsigned int val; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| union power_supply_propval value = {0,}; |
| |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW, value); |
| if (value.intval == SEC_BATTERY_CABLE_NONE) { |
| goto error; |
| } |
| #endif |
| |
| #ifdef CONFIG_RTC_HCTOSYS |
| get_current_time(&pca9468->last_update_time); |
| |
| pr_info("%s: timer id=%d, charging_state=%d, last_update_time=%lu\n", |
| __func__, pca9468->timer_id, pca9468->charging_state, pca9468->last_update_time); |
| #else |
| pr_info("%s: timer id=%d, charging_state=%d\n", |
| __func__, pca9468->timer_id, pca9468->charging_state); |
| #endif |
| |
| switch (pca9468->timer_id) { |
| case TIMER_VBATMIN_CHECK: |
| ret = pca9468_check_vbatmin(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_PRESET_DC: |
| ret = pca9468_start_direct_charging(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_PRESET_CONFIG: |
| ret = pca9468_preset_config(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_CHECK_ACTIVE: |
| ret = pca9468_check_active_state(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_ADJUST_CCMODE: |
| ret = pca9468_charge_adjust_ccmode(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_ENTER_CCMODE: |
| ret = pca9468_charge_start_ccmode(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_CHECK_CCMODE: |
| ret = pca9468_charge_ccmode(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_ENTER_CVMODE: |
| /* Enter Pre-CV mode */ |
| ret = pca9468_charge_start_cvmode(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_CHECK_CVMODE: |
| ret = pca9468_charge_cvmode(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_PDMSG_SEND: |
| /* Adjust TA current and voltage step */ |
| val = pca9468->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ |
| pca9468->ta_vol = val*PD_MSG_TA_VOL_STEP; |
| val = pca9468->ta_cur/PD_MSG_TA_CUR_STEP; /* PPS current resolution is 50mA */ |
| pca9468->ta_cur = val*PD_MSG_TA_CUR_STEP; |
| if (pca9468->ta_cur < PCA9468_TA_MIN_CUR) /* PPS minimum current is 1000mA */ |
| pca9468->ta_cur = PCA9468_TA_MIN_CUR; |
| /* Send PD Message */ |
| ret = pca9468_send_pd_message(pca9468, PD_MSG_REQUEST_APDO); |
| if (ret < 0) |
| goto error; |
| |
| /* Go to the next state */ |
| mutex_lock(&pca9468->lock); |
| switch (pca9468->charging_state) { |
| case DC_STATE_PRESET_DC: |
| pca9468->timer_id = TIMER_PRESET_CONFIG; |
| break; |
| case DC_STATE_ADJUST_CC: |
| pca9468->timer_id = TIMER_ADJUST_CCMODE; |
| break; |
| case DC_STATE_START_CC: |
| pca9468->timer_id = TIMER_ENTER_CCMODE; |
| break; |
| case DC_STATE_CC_MODE: |
| pca9468->timer_id = TIMER_CHECK_CCMODE; |
| break; |
| case DC_STATE_START_CV: |
| pca9468->timer_id = TIMER_ENTER_CVMODE; |
| break; |
| case DC_STATE_CV_MODE: |
| pca9468->timer_id = TIMER_CHECK_CVMODE; |
| break; |
| case DC_STATE_ADJUST_TAVOL: |
| pca9468->timer_id = TIMER_ADJUST_TAVOL; |
| break; |
| case DC_STATE_ADJUST_TACUR: |
| pca9468->timer_id = TIMER_ADJUST_TACUR; |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| pca9468->timer_period = PCA9468_PDMSG_WAIT_T; |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| break; |
| |
| case TIMER_ADJUST_TAVOL: |
| ret = pca9468_adjust_ta_voltage(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_ADJUST_TACUR: |
| ret = pca9468_adjust_ta_current(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| case TIMER_CHECK_WCCVMODE: |
| ret = pca9468_charge_wc_cvmode(pca9468); |
| if (ret < 0) |
| goto error; |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* Check the charging state again */ |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| /* Cancel work queue again */ |
| cancel_delayed_work(&pca9468->timer_work); |
| cancel_delayed_work(&pca9468->pps_work); |
| } |
| return; |
| |
| error: |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468->chg_status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| pca9468->health_status = POWER_SUPPLY_HEALTH_DC_ERR; |
| #endif |
| pca9468_stop_charging(pca9468); |
| return; |
| } |
| |
| |
| /* delayed work function for pps periodic timer */ |
| static void pca9468_pps_request_work(struct work_struct *work) |
| { |
| struct pca9468_charger *pca9468 = container_of(work, struct pca9468_charger, |
| pps_work.work); |
| |
| int ret = 0; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| int vin, iin; |
| |
| /* this is for wdt */ |
| vin = pca9468_read_adc(pca9468, ADCCH_VIN); |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| pr_info("%s: pps_work_start (vin:%dmV, iin:%dmA)\n", |
| __func__, vin/PCA9468_SEC_DENOM_U_M, iin/PCA9468_SEC_DENOM_U_M); |
| #else |
| pr_info("%s: pps_work_start\n", __func__); |
| |
| /* Send PD message */ |
| ret = pca9468_send_pd_message(pca9468, PD_MSG_REQUEST_APDO); |
| #endif |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| } |
| |
| static int pca9468_hw_init(struct pca9468_charger *pca9468) |
| { |
| unsigned int val; |
| int ret; |
| |
| pr_info("%s: =========START=========\n", __func__); |
| |
| /* Read Device info register to check the incomplete I2C operation */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_DEVICE_INFO, &val); |
| if ((ret < 0) || (val != PCA9468_DEVICE_ID)) { |
| /* There is the incomplete I2C operation or I2C communication error */ |
| /* Read Device info register again */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_DEVICE_INFO, &val); |
| if ((ret < 0) || (val != PCA9468_DEVICE_ID)) { |
| dev_err(pca9468->dev, "reading DEVICE_INFO failed, val=0x%x\n", val); |
| ret = -EINVAL; |
| return ret; |
| } |
| } |
| |
| /* |
| * Program the platform specific configuration values to the device |
| * first. |
| */ |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468->chg_status = POWER_SUPPLY_STATUS_DISCHARGING; |
| pca9468->health_status = POWER_SUPPLY_HEALTH_GOOD; |
| #endif |
| |
| #if defined(CONFIG_SEC_FACTORY) |
| val = OV_DELTA_40P << MASK2SHIFT(PCA9468_BIT_OV_DELTA); |
| #else |
| val = OV_DELTA_30P << MASK2SHIFT(PCA9468_BIT_OV_DELTA); |
| #endif |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_SAFETY_CTRL, |
| PCA9468_BIT_OV_DELTA, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Set Switching Frequencey */ |
| val = pca9468->pdata->fsw_cfg << MASK2SHIFT(PCA9468_BIT_FSW_CFG); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, |
| PCA9468_BIT_FSW_CFG, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Die Temperature regulation 120'C */ |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_TEMP_CTRL, |
| PCA9468_BIT_TEMP_REG, 0x3 << MASK2SHIFT(PCA9468_BIT_TEMP_REG)); |
| |
| /* current sense resistance */ |
| val = pca9468->pdata->snsres << MASK2SHIFT(PCA9468_BIT_SNSRES); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, |
| PCA9468_BIT_SNSRES, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Set Reverse Current Detection and standby mode*/ |
| val = PCA9468_BIT_REV_IIN_DET | PCA9468_EN_ACTIVE_L | PCA9468_STANDBY_FORCED; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_START_CTRL, |
| (PCA9468_BIT_REV_IIN_DET | PCA9468_BIT_EN_CFG | PCA9468_BIT_STANDBY_EN), |
| val); |
| if (ret < 0) |
| return ret; |
| |
| /* clear LIMIT_INCREMENT_EN */ |
| val = 0; |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_IIN_CTRL, |
| PCA9468_BIT_LIMIT_INCREMENT_EN, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Set the ADC channel */ |
| val = (PCA9468_BIT_CH7_EN | /* NTC voltage ADC */ |
| PCA9468_BIT_CH6_EN | /* DIETEMP ADC */ |
| PCA9468_BIT_CH5_EN | /* IIN ADC */ |
| PCA9468_BIT_CH3_EN | /* VBAT ADC */ |
| PCA9468_BIT_CH2_EN | /* VIN ADC */ |
| PCA9468_BIT_CH1_EN); /* VOUT ADC */ |
| |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_ADC_CFG, val); |
| if (ret < 0) |
| return ret; |
| |
| /* ADC Mode change */ |
| val = 0x5B; |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_ADC_ACCESS, val); |
| if (ret < 0) |
| return ret; |
| val = 0x10; |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_ADC_MODE, val); |
| if (ret < 0) |
| return ret; |
| val = 0x00; |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_ADC_ACCESS, val); |
| if (ret < 0) |
| return ret; |
| |
| /* Read ADC compesation gain */ |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_ADC_ADJUST, &val); |
| if (ret < 0) |
| return ret; |
| pca9468->adc_comp_gain = adc_gain[(val>>MASK2SHIFT(PCA9468_BIT_ADC_GAIN))]; |
| |
| /* input current - uA*/ |
| ret = pca9468_set_input_current(pca9468, pca9468->pdata->iin_cfg); |
| if (ret < 0) |
| return ret; |
| |
| /* charging current */ |
| ret = pca9468_set_charging_current(pca9468, pca9468->pdata->ichg_cfg); |
| if (ret < 0) |
| return ret; |
| |
| /* v float voltage */ |
| ret = pca9468_set_vfloat(pca9468, pca9468->pdata->v_float); |
| if (ret < 0) |
| return ret; |
| pca9468->pdata->v_float_max = pca9468->pdata->v_float; |
| pr_info("%s: v_float_max(%duV)\n", __func__, pca9468->pdata->v_float_max); |
| |
| return ret; |
| } |
| |
| |
| static irqreturn_t pca9468_interrupt_handler(int irq, void *data) |
| { |
| struct pca9468_charger *pca9468 = data; |
| u8 int1[REG_INT1_MAX], sts[REG_STS_MAX]; /* INT1, INT1_MSK, INT1_STS, STS_A, B, C, D */ |
| u8 masked_int; /* masked int */ |
| bool handled = false; |
| int ret; |
| |
| /* Read INT1, INT1_MSK, INT1_STS */ |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_INT1, int1, 3); |
| if (ret < 0) { |
| dev_warn(pca9468->dev, "reading INT1_X failed\n"); |
| return IRQ_NONE; |
| } |
| |
| /* Read STS_A, B, C, D */ |
| ret = pca9468_bulk_read_reg(pca9468, PCA9468_REG_STS_A, sts, 4); |
| if (ret < 0) { |
| dev_warn(pca9468->dev, "reading STS_X failed\n"); |
| return IRQ_NONE; |
| } |
| |
| pr_info("%s: int1=0x%2x, int1_sts=0x%2x, sts_a=0x%2x\n", __func__, |
| int1[REG_INT1], int1[REG_INT1_STS], sts[REG_STS_A]); |
| |
| /* Check Interrupt */ |
| masked_int = int1[REG_INT1] & !int1[REG_INT1_MSK]; |
| if (masked_int & PCA9468_BIT_V_OK_INT) { |
| /* V_OK interrupt happened */ |
| mutex_lock(&pca9468->lock); |
| pca9468->mains_online = (int1[REG_INT1_STS] & PCA9468_BIT_V_OK_STS) ? true : false; |
| mutex_unlock(&pca9468->lock); |
| power_supply_changed(pca9468->psy_chg); |
| handled = true; |
| } |
| |
| if (masked_int & PCA9468_BIT_NTC_TEMP_INT) { |
| /* NTC_TEMP interrupt happened */ |
| if (int1[REG_INT1_STS] & PCA9468_BIT_NTC_TEMP_STS) { |
| /* above NTC_THRESHOLD */ |
| dev_err(pca9468->dev, "charging stopped due to NTC threshold voltage\n"); |
| } |
| handled = true; |
| } |
| |
| if (masked_int & PCA9468_BIT_CHG_PHASE_INT) { |
| /* CHG_PHASE interrupt happened */ |
| if (int1[REG_INT1_STS] & PCA9468_BIT_CHG_PHASE_STS) { |
| /* Any of loops is active*/ |
| if (sts[REG_STS_A] & PCA9468_BIT_VFLT_LOOP_STS) { |
| /* V_FLOAT loop is in regulation */ |
| pr_info("%s: V_FLOAT loop interrupt\n", __func__); |
| /* Disable CHG_PHASE_M */ |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_INT1_MSK, |
| PCA9468_BIT_CHG_PHASE_M, PCA9468_BIT_CHG_PHASE_M); |
| if (ret < 0) { |
| handled = false; |
| return handled; |
| } |
| /* Go to Pre CV Mode */ |
| pca9468->timer_id = TIMER_ENTER_CVMODE; |
| pca9468->timer_period = 10; |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } else if (sts[REG_STS_A] & PCA9468_BIT_IIN_LOOP_STS) { |
| /* IIN loop or ICHG loop is in regulation */ |
| pr_info("%s: IIN loop interrupt\n", __func__); |
| } else if (sts[REG_STS_A] & PCA9468_BIT_CHG_LOOP_STS) { |
| /* ICHG loop is in regulation */ |
| pr_info("%s: ICHG loop interrupt\n", __func__); |
| } |
| } |
| handled = true; |
| } |
| |
| if (masked_int & PCA9468_BIT_CTRL_LIMIT_INT) { |
| /* CTRL_LIMIT interrupt happened */ |
| if (int1[REG_INT1_STS] & PCA9468_BIT_CTRL_LIMIT_STS) { |
| /* No Loop is active or OCP */ |
| if (sts[REG_STS_B] & PCA9468_BIT_OCP_FAST_STS) { |
| /* Input fast over current */ |
| dev_err(pca9468->dev, "IIN > 50A instantaneously\n"); |
| } |
| if (sts[REG_STS_B] & PCA9468_BIT_OCP_AVG_STS) { |
| /* Input average over current */ |
| dev_err(pca9468->dev, "IIN > IIN_CFG*150percent\n"); |
| } |
| } |
| handled = true; |
| } |
| |
| if (masked_int & PCA9468_BIT_TEMP_REG_INT) { |
| /* TEMP_REG interrupt happened */ |
| if (int1[REG_INT1_STS] & PCA9468_BIT_TEMP_REG_STS) { |
| /* Device is in temperature regulation */ |
| dev_err(pca9468->dev, "Device is in temperature regulation\n"); |
| } |
| handled = true; |
| } |
| |
| if (masked_int & PCA9468_BIT_ADC_DONE_INT) { |
| /* ADC complete interrupt happened */ |
| dev_dbg(pca9468->dev, "ADC has been completed\n"); |
| handled = true; |
| } |
| |
| if (masked_int & PCA9468_BIT_TIMER_INT) { |
| /* Timer falut interrupt happened */ |
| if (int1[REG_INT1_STS] & PCA9468_BIT_TIMER_STS) { |
| if (sts[REG_STS_B] & PCA9468_BIT_CHARGE_TIMER_STS) { |
| /* Charger timer is expired */ |
| dev_err(pca9468->dev, "Charger timer is expired\n"); |
| } |
| if (sts[REG_STS_B] & PCA9468_BIT_WATCHDOG_TIMER_STS) { |
| /* Watchdog timer is expired */ |
| dev_err(pca9468->dev, "Watchdog timer is expired\n"); |
| } |
| } |
| handled = true; |
| } |
| |
| return handled ? IRQ_HANDLED : IRQ_NONE; |
| } |
| |
| static int pca9468_irq_init(struct pca9468_charger *pca9468, |
| struct i2c_client *client) |
| { |
| const struct pca9468_platform_data *pdata = pca9468->pdata; |
| int ret, msk, irq; |
| |
| pr_info("%s: =========START=========\n", __func__); |
| |
| irq = gpio_to_irq(pdata->irq_gpio); |
| |
| ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); |
| if (ret < 0) |
| goto fail; |
| |
| ret = request_threaded_irq(irq, NULL, pca9468_interrupt_handler, |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
| client->name, pca9468); |
| if (ret < 0) |
| goto fail_gpio; |
| |
| /* |
| * Configure the Mask Register for interrupts: disable all interrupts by default. |
| */ |
| msk = (PCA9468_BIT_V_OK_M | |
| PCA9468_BIT_NTC_TEMP_M | |
| PCA9468_BIT_CHG_PHASE_M | |
| PCA9468_BIT_RESERVED_M | |
| PCA9468_BIT_CTRL_LIMIT_M | |
| PCA9468_BIT_TEMP_REG_M | |
| PCA9468_BIT_ADC_DONE_M | |
| PCA9468_BIT_TIMER_M); |
| ret = pca9468_write_reg(pca9468, PCA9468_REG_INT1_MSK, msk); |
| if (ret < 0) |
| goto fail_wirte; |
| |
| client->irq = irq; |
| return 0; |
| |
| fail_wirte: |
| free_irq(irq, pca9468); |
| fail_gpio: |
| gpio_free(pdata->irq_gpio); |
| fail: |
| client->irq = 0; |
| return ret; |
| } |
| |
| |
| /* |
| * Returns the input current limit programmed |
| * into the charger in uA. |
| */ |
| static int get_input_current_limit(struct pca9468_charger *pca9468) |
| { |
| int ret, intval; |
| unsigned int val; |
| |
| if (!pca9468->mains_online) |
| return -ENODATA; |
| |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_IIN_CTRL, &val); |
| if (ret < 0) |
| return ret; |
| |
| intval = (val & PCA9468_BIT_IIN_CFG) * 100000; |
| |
| if (intval < 500000) |
| intval = 500000; |
| |
| return intval; |
| } |
| |
| /* |
| * Returns the constant charge current programmed |
| * into the charger in uA. |
| */ |
| static int get_const_charge_current(struct pca9468_charger *pca9468) |
| { |
| int ret, intval; |
| unsigned int val; |
| |
| if (!pca9468->mains_online) |
| return -ENODATA; |
| |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_ICHG_CTRL, &val); |
| if (ret < 0) |
| return ret; |
| |
| intval = (val & PCA9468_BIT_ICHG_CFG) * 100000; |
| |
| return intval; |
| } |
| |
| /* |
| * Returns the constant charge voltage programmed |
| * into the charger in uV. |
| */ |
| static int get_const_charge_voltage(struct pca9468_charger *pca9468) |
| { |
| int ret, intval; |
| unsigned int val; |
| |
| if (!pca9468->mains_online) |
| return -ENODATA; |
| |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_V_FLOAT, &val); |
| if (ret < 0) |
| return ret; |
| |
| intval = (val * 5 + 3725) * 1000; |
| |
| return intval; |
| } |
| |
| /* |
| * Returns the enable or disable value. |
| * into 1 or 0. |
| */ |
| static int get_charging_enabled(struct pca9468_charger *pca9468) |
| { |
| int ret, intval; |
| unsigned int val; |
| |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_START_CTRL, &val); |
| if (ret < 0) |
| return ret; |
| |
| intval = (val & PCA9468_BIT_STANDBY_EN) ? 0 : 1; |
| |
| return intval; |
| } |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| /* |
| * Returns the system current in uV. |
| */ |
| static int get_system_current(struct pca9468_charger *pca9468) |
| { |
| /* get the system current */ |
| /* get the battery power supply to get charging current */ |
| union power_supply_propval val; |
| struct power_supply *psy; |
| int ret; |
| int iin, ibat, isys; |
| |
| psy = power_supply_get_by_name("battery"); |
| if (!psy) { |
| dev_err(pca9468->dev, "Cannot find battery power supply\n"); |
| goto error; |
| } |
| |
| /* get the charging current */ |
| ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, &val); |
| power_supply_put(psy); |
| if (ret < 0) { |
| dev_err(pca9468->dev, "Cannot get battery current from FG\n"); |
| goto error; |
| } |
| ibat = val.intval; |
| |
| /* calculate the system current */ |
| /* get input current */ |
| iin = pca9468_read_adc(pca9468, ADCCH_IIN); |
| if (iin < 0) { |
| dev_err(pca9468->dev, "Invalid IIN ADC\n"); |
| goto error; |
| } |
| |
| /* calculate the system current */ |
| /* Isys = (Iin - Ifsw_cfg)*2 - Ibat */ |
| iin = (iin - iin_fsw_cfg[pca9468->pdata->fsw_cfg])*2; |
| iin /= PCA9468_SEC_DENOM_U_M; |
| isys = iin - ibat; |
| pr_info("%s: isys=%dmA\n", __func__, isys); |
| |
| return isys; |
| |
| error: |
| return -EINVAL; |
| } |
| #endif |
| |
| static int pca9468_chg_set_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| const union power_supply_propval *val) |
| { |
| struct pca9468_charger *pca9468 = power_supply_get_drvdata(psy); |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) prop; |
| #endif |
| int ret = 0; |
| unsigned int temp = 0; |
| |
| pr_info("%s: =========START=========\n", __func__); |
| pr_info("%s: prop=%d, val=%d\n", __func__, prop, val->intval); |
| |
| switch (prop) { |
| /* Todo - Insert code */ |
| /* It needs modification by a customer */ |
| /* The customer make a decision to start charging and stop charging property */ |
| |
| case POWER_SUPPLY_PROP_ONLINE: /* need to change property */ |
| if (val->intval == 0) { |
| pca9468->mains_online = false; |
| // Stop Direct charging |
| ret = pca9468_stop_charging(pca9468); |
| pca9468->chg_status = POWER_SUPPLY_STATUS_DISCHARGING; |
| pca9468->health_status = POWER_SUPPLY_HEALTH_GOOD; |
| if (ret < 0) |
| goto error; |
| } else { |
| // Start Direct charging |
| pca9468->mains_online = true; |
| #if !defined(CONFIG_BATTERY_SAMSUNG) |
| /* Start 1sec timer for battery check */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_VBATMIN_CHECK; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468->timer_period = PCA9468_VBATMIN_CHECK_T; /* The delay time for PD state goes to PE_SNK_STATE */ |
| #else |
| pca9468->timer_period = 5000; /* The dealy time for PD state goes to PE_SNK_STATE */ |
| #endif |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| #endif |
| ret = 0; |
| } |
| break; |
| |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| if (val->intval == 0) { |
| // Stop Direct Charging |
| ret = pca9468_stop_charging(pca9468); |
| if (ret < 0) |
| goto error; |
| } else { |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| if (pca9468->charging_state != DC_STATE_NO_CHARGING) { |
| pr_info("## %s: duplicate charging enabled(%d)\n", __func__, val->intval); |
| goto error; |
| } |
| if (!pca9468->mains_online) { |
| pr_info("## %s: mains_online is not attached(%d)\n", __func__, val->intval); |
| goto error; |
| } |
| #endif |
| // Start Direct Charging |
| /* Start 1sec timer for battery check */ |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_id = TIMER_VBATMIN_CHECK; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468->timer_period = PCA9468_VBATMIN_CHECK_T; /* The delay time for PD state goes to PE_SNK_STATE */ |
| #else |
| pca9468->timer_period = 5000; /* The delay time for PD state goes to PE_SNK_STATE */ |
| #endif |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| ret = 0; |
| } |
| break; |
| |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
| if (val->intval != pca9468->new_vfloat) { |
| /* request new float voltage */ |
| pca9468->new_vfloat = val->intval; |
| ret = pca9468_set_new_vfloat(pca9468); |
| } |
| break; |
| |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| if (val->intval != pca9468->new_iin) { |
| /* request new input current */ |
| pca9468->new_iin = val->intval; |
| ret = pca9468_set_new_iin(pca9468); |
| } |
| break; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| pca9468->charging_current = val->intval; |
| #if 0 |
| pca9468->pdata->ichg_cfg = val->intval * PCA9468_SEC_DENOM_U_M; |
| pr_info("## %s: charging current(%duA)\n", __func__, pca9468->pdata->ichg_cfg); |
| pca9468_set_charging_current(pca9468, pca9468->pdata->ichg_cfg); |
| #endif |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| /* this is same with POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT */ |
| pca9468->input_current = val->intval; |
| temp = pca9468->input_current * PCA9468_SEC_DENOM_U_M; |
| if (temp != pca9468->new_iin) { |
| /* request new input current */ |
| pca9468->new_iin = temp; |
| ret = pca9468_set_new_iin(pca9468); |
| pr_info("## %s: input current(new_iin: %duA)\n", __func__, pca9468->new_iin); |
| } |
| |
| break; |
| case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX: |
| switch (ext_psp) { |
| #if defined(CONFIG_BATTERY_SAMSUNG) && defined(CONFIG_ENG_BATTERY_CONCEPT) |
| case POWER_SUPPLY_EXT_PROP_DIRECT_WDT_CONTROL: |
| if (val->intval) { |
| pca9468->wdt_kick = true; |
| } else { |
| pca9468->wdt_kick = false; |
| cancel_delayed_work(&pca9468->wdt_control_work); |
| } |
| pr_info("%s: wdt kick (%d)\n", __func__, pca9468->wdt_kick); |
| break; |
| #endif |
| case POWER_SUPPLY_EXT_PROP_DIRECT_VOLTAGE_MAX: |
| pca9468->float_voltage = val->intval; |
| temp = pca9468->float_voltage * PCA9468_SEC_DENOM_U_M; |
| if (temp != pca9468->new_vfloat) { |
| /* request new float voltage */ |
| pca9468->new_vfloat = temp; |
| ret = pca9468_set_new_vfloat(pca9468); |
| pr_info("## %s: float voltage(%duV)\n", __func__, pca9468->new_vfloat); |
| } |
| break; |
| case POWER_SUPPLY_EXT_PROP_DIRECT_CURRENT_MAX: |
| pca9468->input_current = val->intval; |
| temp = pca9468->input_current * PCA9468_SEC_DENOM_U_M; |
| if (temp != pca9468->new_iin) { |
| /* request new input current */ |
| pca9468->new_iin = temp; |
| ret = pca9468_set_new_iin(pca9468); |
| pr_info("## %s: input current(new_iin: %duA)\n", __func__, pca9468->new_iin); |
| } |
| break; |
| case POWER_SUPPLY_EXT_PROP_DIRECT_FLOAT_MAX: |
| pca9468->pdata->v_float_max = val->intval * PCA9468_SEC_DENOM_U_M; |
| pr_info("%s: v_float_max(%duV)\n", __func__, pca9468->pdata->v_float_max); |
| break; |
| case POWER_SUPPLY_EXT_PROP_DIRECT_ADC_CTRL: |
| if (val->intval) { |
| temp = FORCE_NORMAL_MODE << MASK2SHIFT(PCA9468_BIT_FORCE_ADC_MODE); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_ADC_CTRL, PCA9468_BIT_FORCE_ADC_MODE, temp); |
| if (ret < 0) |
| return ret; |
| } else { |
| temp = AUTO_MODE << MASK2SHIFT(PCA9468_BIT_FORCE_ADC_MODE); |
| ret = pca9468_update_reg(pca9468, PCA9468_REG_ADC_CTRL, PCA9468_BIT_FORCE_ADC_MODE, temp); |
| if (ret < 0) |
| return ret; |
| } |
| |
| ret = pca9468_read_reg(pca9468, PCA9468_REG_ADC_CTRL, &temp); |
| pr_info("%s: ADC_CTRL : 0x%02x\n", __func__, temp); |
| break; |
| default: |
| return -EINVAL; |
| } |
| break; |
| #endif |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| error: |
| pr_info("%s: End, ret=%d\n", __func__, ret); |
| return ret; |
| } |
| |
| |
| static int pca9468_chg_get_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| int ret = 0; |
| struct pca9468_charger *pca9468 = power_supply_get_drvdata(psy); |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) prop; |
| #endif |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = pca9468->mains_online; |
| break; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = pca9468->chg_status; |
| pr_info("%s: CHG STATUS : %d\n", __func__, pca9468->chg_status); |
| break; |
| |
| case POWER_SUPPLY_PROP_HEALTH: |
| if (pca9468->charging_state >= DC_STATE_CHECK_ACTIVE && |
| pca9468->charging_state <= DC_STATE_CV_MODE) |
| pca9468_check_error(pca9468); |
| val->intval = pca9468->health_status; |
| pr_info("%s: HEALTH STATUS : %d\n", __func__, pca9468->health_status); |
| break; |
| #endif |
| |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
| ret = get_const_charge_voltage(pca9468); |
| if (ret < 0) |
| return ret; |
| else |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| val->intval = pca9468->float_voltage; |
| #else |
| val->intval = ret; |
| #endif |
| break; |
| |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
| ret = get_const_charge_current(pca9468); |
| if (ret < 0) |
| return ret; |
| else |
| val->intval = ret; |
| break; |
| |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| ret = get_charging_enabled(pca9468); |
| if (ret < 0) |
| return ret; |
| else |
| val->intval = ret; |
| break; |
| |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| ret = get_input_current_limit(pca9468); |
| if (ret < 0) |
| return ret; |
| else |
| val->intval = ret; |
| break; |
| |
| case POWER_SUPPLY_PROP_TEMP: |
| /* return NTC voltage - uV unit */ |
| ret = pca9468_read_adc(pca9468, ADCCH_NTC); |
| if (ret < 0) |
| return ret; |
| else |
| val->intval = ret; |
| break; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| val->intval = pca9468->input_current; |
| break; |
| |
| case POWER_SUPPLY_PROP_CURRENT_NOW: /* get charge current which was set */ |
| val->intval = pca9468->charging_current; |
| break; |
| |
| case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX: |
| switch (ext_psp) { |
| case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: |
| pca9468_monitor_work(pca9468); |
| pca9468_test_read(pca9468); |
| break; |
| case POWER_SUPPLY_EXT_PROP_MEASURE_INPUT: |
| switch (val->intval) { |
| case SEC_BATTERY_IIN_MA: |
| pca9468_read_adc(pca9468, ADCCH_IIN); |
| val->intval = pca9468->adc_val[ADCCH_IIN]; |
| break; |
| case SEC_BATTERY_IIN_UA: |
| pca9468_read_adc(pca9468, ADCCH_IIN); |
| val->intval = pca9468->adc_val[ADCCH_IIN] * PCA9468_SEC_DENOM_U_M; |
| break; |
| case SEC_BATTERY_VIN_MA: |
| val->intval = pca9468->adc_val[ADCCH_VIN]; |
| break; |
| case SEC_BATTERY_VIN_UA: |
| val->intval = pca9468->adc_val[ADCCH_VIN] * PCA9468_SEC_DENOM_U_M; |
| break; |
| default: |
| val->intval = 0; |
| break; |
| } |
| break; |
| case POWER_SUPPLY_EXT_PROP_MEASURE_SYS: |
| /* return system current - uA unit */ |
| /* check charging status */ |
| if (pca9468->charging_state == DC_STATE_NO_CHARGING) { |
| /* return invalid */ |
| val->intval = 0; |
| return -EINVAL; |
| } else { |
| /* calculate Isys */ |
| ret = get_system_current(pca9468); |
| if (ret < 0) |
| return 0; |
| else |
| val->intval = ret; |
| } |
| break; |
| case POWER_SUPPLY_EXT_PROP_DIRECT_VOLTAGE_MAX: |
| val->intval = pca9468->float_voltage; |
| break; |
| default: |
| return -EINVAL; |
| } |
| break; |
| #endif |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static enum power_supply_property pca9468_charger_props[] = { |
| }; |
| |
| |
| static const struct regmap_config pca9468_regmap = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = PCA9468_MAX_REGISTER, |
| }; |
| |
| static char *pca9468_supplied_to[] = { |
| "pca9468-charger", |
| }; |
| |
| static const struct power_supply_desc pca9468_charger_power_supply_desc = { |
| .name = "pca9468-charger", |
| .type = POWER_SUPPLY_TYPE_UNKNOWN, |
| .get_property = pca9468_chg_get_property, |
| .set_property = pca9468_chg_set_property, |
| .properties = pca9468_charger_props, |
| .num_properties = ARRAY_SIZE(pca9468_charger_props), |
| }; |
| |
| #if defined(CONFIG_OF) |
| static int pca9468_charger_parse_dt(struct device *dev, struct pca9468_platform_data *pdata) |
| { |
| struct device_node *np_pca9468 = dev->of_node; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| struct device_node *np; |
| #endif |
| int ret; |
| if(!np_pca9468) |
| return -EINVAL; |
| |
| /* irq gpio */ |
| pdata->irq_gpio = of_get_named_gpio(np_pca9468, "pca9468,irq-gpio", 0); |
| pr_info("%s: irq-gpio: %u \n", __func__, pdata->irq_gpio); |
| |
| /* input current limit */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,input-current-limit", |
| &pdata->iin_cfg); |
| if (ret) { |
| pr_info("%s: pca9468,input-current-limit is Empty\n", __func__); |
| pdata->iin_cfg = PCA9468_IIN_CFG_DFT; |
| } |
| pr_info("%s: pca9468,iin_cfg is %d\n", __func__, pdata->iin_cfg); |
| |
| /* charging current */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,charging-current", |
| &pdata->ichg_cfg); |
| if (ret) { |
| pr_info("%s: pca9468,charging-current is Empty\n", __func__); |
| pdata->ichg_cfg = PCA9468_ICHG_CFG_DFT; |
| } |
| pr_info("%s: pca9468,ichg_cfg is %d\n", __func__, pdata->ichg_cfg); |
| |
| #if !defined(CONFIG_BATTERY_SAMSUNG) |
| /* charging float voltage */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,float-voltage", |
| &pdata->v_float); |
| if (ret) { |
| pr_info("%s: pca9468,float-voltage is Empty\n", __func__); |
| pdata->v_float = PCA9468_VFLOAT_DFT; |
| } |
| pr_info("%s: pca9468,v_float is %d\n", __func__, pdata->v_float); |
| #endif |
| |
| /* input topoff current */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,input-itopoff", |
| &pdata->iin_topoff); |
| if (ret) { |
| pr_info("%s: pca9468,input-itopoff is Empty\n", __func__); |
| pdata->iin_topoff = PCA9468_IIN_DONE_DFT; |
| } |
| pr_info("%s: pca9468,iin_topoff is %d\n", __func__, pdata->iin_topoff); |
| |
| /* sense resistance */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,sense-resistance", |
| &pdata->snsres); |
| if (ret) { |
| pr_info("%s: pca9468,sense-resistance is Empty\n", __func__); |
| pdata->snsres = PCA9468_SENSE_R_DFT; |
| } |
| pr_info("%s: pca9468,snsres is %d\n", __func__, pdata->snsres); |
| |
| /* switching frequency */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,switching-frequency", |
| &pdata->fsw_cfg); |
| if (ret) { |
| pr_info("%s: pca9468,switching frequency is Empty\n", __func__); |
| pdata->fsw_cfg = PCA9468_FSW_CFG_DFT; |
| } |
| pr_info("%s: pca9468,fsw_cfg is %d\n", __func__, pdata->fsw_cfg); |
| |
| /* NTC threshold voltage */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,ntc-threshold", |
| &pdata->ntc_th); |
| if (ret) { |
| pr_info("%s: pca9468,ntc threshold voltage is Empty\n", __func__); |
| pdata->ntc_th = PCA9468_NTC_TH_DFT; |
| } |
| pr_info("%s: pca9468,ntc_th is %d\n", __func__, pdata->ntc_th); |
| |
| /* TA voltage mode */ |
| ret = of_property_read_u32(np_pca9468, "pca9468,ta-mode", |
| &pdata->ta_mode); |
| if (ret) { |
| pr_info("%s: pca9468,ta mode is Empty\n", __func__); |
| pdata->ta_mode = TA_2TO1_DC_MODE; |
| } |
| pr_info("%s: pca9468,ta_mode is %d\n", __func__, pdata->ta_mode); |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pdata->chgen_gpio = of_get_named_gpio(np_pca9468, "pca9468,chg_gpio_en", 0); |
| if (pdata->chgen_gpio < 0) { |
| pr_err("%s : cannot get chgen gpio : %d\n", |
| __func__, pdata->chgen_gpio); |
| return -ENODATA; |
| } else { |
| pr_info("%s: chgen gpio : %d\n", __func__, pdata->chgen_gpio); |
| } |
| |
| np = of_find_node_by_name(NULL, "battery"); |
| if (!np) { |
| pr_err("## %s: np(battery) NULL\n", __func__); |
| } else { |
| ret = of_property_read_u32(np, "battery,chg_float_voltage", |
| &pdata->v_float); |
| if (ret) { |
| pr_info("## %s: battery,chg_float_voltage is Empty\n", __func__); |
| pdata->v_float = PCA9468_VFLOAT_DFT; |
| } else { |
| pdata->v_float = pdata->v_float * PCA9468_SEC_DENOM_U_M; |
| pr_info("## %s: battery,chg_float_voltage is %d\n", __func__, |
| pdata->v_float); |
| } |
| |
| ret = of_property_read_string(np, "battery,charger_name", |
| (char const **)&pdata->sec_dc_name); |
| if (ret) { |
| pr_err("## %s: direct_charger is Empty\n", __func__); |
| pdata->sec_dc_name = "sec-direct-charger"; |
| } |
| pr_info("%s: battery,charger_name is %s\n", __func__, pdata->sec_dc_name); |
| |
| /* charging float voltage */ |
| ret = of_property_read_u32(np, "battery,chg_float_voltage", |
| &pdata->v_float); |
| pdata->v_float *= PCA9468_SEC_DENOM_U_M; |
| if (ret) { |
| pr_info("%s: battery,dc_float_voltage is Empty\n", __func__); |
| pdata->v_float = PCA9468_VFLOAT_DFT; |
| } |
| pr_info("%s: battery,v_float is %d\n", __func__, pdata->v_float); |
| } |
| #endif |
| |
| return 0; |
| } |
| #else |
| static int pca9468_charger_parse_dt(struct device *dev, struct pca9468_platform_data *pdata) |
| { |
| return 0; |
| } |
| #endif /* CONFIG_OF */ |
| |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| static int pca9468_usbpd_setup(struct pca9468_charger *pca9468) |
| { |
| int ret = 0; |
| const char *pd_phandle = "usbpd-phy"; |
| |
| pca9468->pd = devm_usbpd_get_by_phandle(pca9468->dev, pd_phandle); |
| |
| if (IS_ERR(pca9468->pd)) { |
| pr_err("get_usbpd phandle failed (%ld)\n", |
| PTR_ERR(pca9468->pd)); |
| return PTR_ERR(pca9468->pd); |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static int read_reg(void *data, u64 *val) |
| { |
| struct pca9468_charger *chip = data; |
| int rc; |
| unsigned int temp; |
| |
| rc = regmap_read(chip->regmap, chip->debug_address, &temp); |
| if (rc) { |
| pr_err("Couldn't read reg %x rc = %d\n", |
| chip->debug_address, rc); |
| return -EAGAIN; |
| } |
| *val = temp; |
| return 0; |
| } |
| |
| static int write_reg(void *data, u64 val) |
| { |
| struct pca9468_charger *chip = data; |
| int rc; |
| u8 temp; |
| |
| temp = (u8) val; |
| rc = regmap_write(chip->regmap, chip->debug_address, temp); |
| if (rc) { |
| pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n", |
| temp, chip->debug_address, rc); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(register_debug_ops, read_reg, write_reg, "0x%02llx\n"); |
| |
| static int pca9468_create_debugfs_entries(struct pca9468_charger *chip) |
| { |
| struct dentry *ent; |
| int rc = 0; |
| |
| chip->debug_root = debugfs_create_dir("charger-pca9468", NULL); |
| if (!chip->debug_root) { |
| dev_err(chip->dev, "Couldn't create debug dir\n"); |
| rc = -ENOENT; |
| } else { |
| ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, |
| &(chip->debug_address)); |
| if (!ent) { |
| dev_err(chip->dev, |
| "Couldn't create address debug file\n"); |
| rc = -ENOENT; |
| } |
| |
| ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, |
| chip->debug_root, chip, |
| ®ister_debug_ops); |
| if (!ent) { |
| dev_err(chip->dev, |
| "Couldn't create data debug file\n"); |
| rc = -ENOENT; |
| } |
| } |
| |
| return rc; |
| } |
| |
| |
| static int pca9468_charger_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct power_supply_config chager_cfg = {}; |
| struct pca9468_platform_data *pdata; |
| struct device *dev = &client->dev; |
| struct pca9468_charger *pca9468_chg; |
| int ret; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pr_info("%s: PCA9468 Charger Driver Loading\n", __func__); |
| #endif |
| pr_info("%s: =========START=========\n", __func__); |
| |
| pca9468_chg = devm_kzalloc(dev, sizeof(*pca9468_chg), GFP_KERNEL); |
| if (!pca9468_chg) |
| return -ENOMEM; |
| |
| #if defined(CONFIG_OF) |
| if (client->dev.of_node) { |
| pdata = devm_kzalloc(&client->dev, sizeof(struct pca9468_platform_data), |
| GFP_KERNEL); |
| if (!pdata) { |
| dev_err(&client->dev, "Failed to allocate memory \n"); |
| return -ENOMEM; |
| } |
| |
| ret = pca9468_charger_parse_dt(&client->dev, pdata); |
| if (ret < 0){ |
| dev_err(&client->dev, "Failed to get device of_node \n"); |
| return -ENOMEM; |
| } |
| |
| client->dev.platform_data = pdata; |
| } else { |
| pdata = client->dev.platform_data; |
| } |
| #else |
| pdata = dev->platform_data; |
| #endif |
| if (!pdata) |
| return -EINVAL; |
| |
| i2c_set_clientdata(client, pca9468_chg); |
| |
| mutex_init(&pca9468_chg->lock); |
| mutex_init(&pca9468_chg->i2c_lock); |
| pca9468_chg->dev = &client->dev; |
| pca9468_chg->pdata = pdata; |
| pca9468_chg->charging_state = DC_STATE_NO_CHARGING; |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_chg->wdt_kick = false; |
| #endif |
| |
| wakeup_source_init(&pca9468_chg->monitor_wake_lock, "pca9468-charger-monitor"); |
| |
| /* initialize work */ |
| INIT_DELAYED_WORK(&pca9468_chg->timer_work, pca9468_timer_work); |
| mutex_lock(&pca9468_chg->lock); |
| pca9468_chg->timer_id = TIMER_ID_NONE; |
| pca9468_chg->timer_period = 0; |
| mutex_unlock(&pca9468_chg->lock); |
| |
| INIT_DELAYED_WORK(&pca9468_chg->pps_work, pca9468_pps_request_work); |
| #if defined(CONFIG_BATTERY_SAMSUNG) && defined(CONFIG_ENG_BATTERY_CONCEPT) |
| INIT_DELAYED_WORK(&pca9468_chg->wdt_control_work, pca9468_wdt_control_work); |
| #endif |
| |
| pca9468_chg->regmap = devm_regmap_init_i2c(client, &pca9468_regmap); |
| if (IS_ERR(pca9468_chg->regmap)) { |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| ret = PTR_ERR(pca9468_chg->regmap); |
| goto err_regmap_init; |
| #else |
| return PTR_ERR(pca9468_chg->regmap); |
| #endif |
| } |
| |
| #ifdef CONFIG_USBPD_PHY_QCOM |
| if (pca9468_usbpd_setup(pca9468_chg)) { |
| dev_err(dev, "Error usbpd setup!\n"); |
| pca9468_chg->pd = NULL; |
| } |
| #endif |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pca9468_init_adc_val(pca9468_chg, -1); |
| #endif |
| |
| ret = pca9468_hw_init(pca9468_chg); |
| if (ret < 0) |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| goto err_hw_init; |
| #else |
| return ret; |
| #endif |
| |
| chager_cfg.supplied_to = pca9468_supplied_to; |
| chager_cfg.num_supplicants = ARRAY_SIZE(pca9468_supplied_to); |
| chager_cfg.drv_data = pca9468_chg; |
| pca9468_chg->psy_chg = power_supply_register(dev, |
| &pca9468_charger_power_supply_desc, &chager_cfg); |
| if (IS_ERR(pca9468_chg->psy_chg)) { |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| ret = PTR_ERR(pca9468_chg->psy_chg); |
| goto err_power_supply_regsister; |
| #else |
| return PTR_ERR(pca9468_chg->mains); |
| #endif |
| } |
| |
| /* |
| * Interrupt pin is optional. If it is connected, we setup the |
| * interrupt support here. |
| */ |
| if (pdata->irq_gpio >= 0) { |
| ret = pca9468_irq_init(pca9468_chg, client); |
| if (ret < 0) { |
| dev_warn(dev, "failed to initialize IRQ: %d\n", ret); |
| dev_warn(dev, "disabling IRQ support\n"); |
| } |
| /* disable interrupt */ |
| disable_irq(client->irq); |
| } |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| ret = gpio_request(pdata->chgen_gpio, "DC_CPEN"); |
| if (ret) { |
| pr_info("%s : Request GPIO %d failed\n", |
| __func__, (int)pdata->chgen_gpio); |
| } |
| |
| gpio_direction_output(pdata->chgen_gpio, |
| false); |
| #endif |
| |
| ret = pca9468_create_debugfs_entries(pca9468_chg); |
| if (ret < 0) |
| return ret; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| pr_info("%s: PCA9468 Charger Driver Loaded\n", __func__); |
| #endif |
| pr_info("%s: =========END=========\n", __func__); |
| |
| return 0; |
| |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| err_power_supply_regsister: |
| err_hw_init: |
| err_regmap_init: |
| wakeup_source_trash(&pca9468_chg->monitor_wake_lock); |
| return ret; |
| #endif |
| } |
| |
| static int pca9468_charger_remove(struct i2c_client *client) |
| { |
| struct pca9468_charger *pca9468_chg = i2c_get_clientdata(client); |
| |
| pr_info("%s: ++\n", __func__); |
| |
| if (client->irq) { |
| free_irq(client->irq, pca9468_chg); |
| gpio_free(pca9468_chg->pdata->irq_gpio); |
| } |
| |
| wakeup_source_trash(&pca9468_chg->monitor_wake_lock); |
| #if defined(CONFIG_BATTERY_SAMSUNG) |
| if (pca9468_chg->psy_chg) |
| power_supply_unregister(pca9468_chg->psy_chg); |
| #else |
| if (pca9468_chg->mains) |
| power_supply_unregister(pca9468_chg->mains); |
| #endif |
| |
| pr_info("%s: --\n", __func__); |
| |
| return 0; |
| } |
| |
| static void pca9468_charger_shutdown(struct i2c_client *client) |
| { |
| struct pca9468_charger *pca9468_chg = i2c_get_clientdata(client); |
| |
| pr_info("%s: ++\n", __func__); |
| |
| pca9468_set_charging(pca9468_chg, false); |
| |
| pr_info("%s: --\n", __func__); |
| } |
| |
| static const struct i2c_device_id pca9468_charger_id_table[] = { |
| { "pca9468-charger", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, pca9468_charger_id_table); |
| |
| #if defined(CONFIG_OF) |
| static struct of_device_id pca9468_charger_match_table[] = { |
| { .compatible = "nxp,pca9468" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, pca9468_charger_match_table); |
| #endif /* CONFIG_OF */ |
| |
| #if defined(CONFIG_PM) |
| #ifdef CONFIG_RTC_HCTOSYS |
| static void pca9468_check_and_update_charging_timer(struct pca9468_charger *pca9468) |
| { |
| unsigned long current_time = 0, next_update_time, time_left; |
| |
| get_current_time(¤t_time); |
| |
| if (pca9468->timer_id != TIMER_ID_NONE) { |
| next_update_time = pca9468->last_update_time + (pca9468->timer_period / 1000); // unit is second |
| |
| pr_info("%s: current_time=%ld, next_update_time=%ld\n", __func__, current_time, next_update_time); |
| |
| if (next_update_time > current_time) |
| time_left = next_update_time - current_time; |
| else |
| time_left = 0; |
| |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_period = time_left * 1000; // ms unit |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| pr_info("%s: timer_id=%d, time_period=%ld\n", __func__, pca9468->timer_id, pca9468->timer_period); |
| } |
| pca9468->last_update_time = current_time; |
| } |
| #endif |
| |
| static int pca9468_charger_suspend(struct device *dev) |
| { |
| struct pca9468_charger *pca9468 = dev_get_drvdata(dev); |
| |
| pr_info("%s: cancel delayed work\n", __func__); |
| |
| /* cancel delayed_work */ |
| cancel_delayed_work(&pca9468->timer_work); |
| return 0; |
| } |
| |
| static int pca9468_charger_resume(struct device *dev) |
| { |
| struct pca9468_charger *pca9468 = dev_get_drvdata(dev); |
| |
| pr_info("%s: update_timer\n", __func__); |
| |
| /* Update the current timer */ |
| #ifdef CONFIG_RTC_HCTOSYS |
| pca9468_check_and_update_charging_timer(pca9468); |
| #else |
| if (pca9468->timer_id != TIMER_ID_NONE) { |
| mutex_lock(&pca9468->lock); |
| pca9468->timer_period = 0; // ms unit |
| mutex_unlock(&pca9468->lock); |
| schedule_delayed_work(&pca9468->timer_work, msecs_to_jiffies(pca9468->timer_period)); |
| } |
| #endif |
| return 0; |
| } |
| #else |
| #define pca9468_charger_suspend NULL |
| #define pca9468_charger_resume NULL |
| #endif |
| |
| const struct dev_pm_ops pca9468_pm_ops = { |
| .suspend = pca9468_charger_suspend, |
| .resume = pca9468_charger_resume, |
| }; |
| |
| static struct i2c_driver pca9468_charger_driver = { |
| .driver = { |
| .name = "pca9468-charger", |
| #if defined(CONFIG_OF) |
| .of_match_table = pca9468_charger_match_table, |
| #endif /* CONFIG_OF */ |
| #if defined(CONFIG_PM) |
| .pm = &pca9468_pm_ops, |
| #endif |
| }, |
| .probe = pca9468_charger_probe, |
| .remove = pca9468_charger_remove, |
| .shutdown = pca9468_charger_shutdown, |
| .id_table = pca9468_charger_id_table, |
| }; |
| |
| module_i2c_driver(pca9468_charger_driver); |
| |
| MODULE_AUTHOR("Clark Kim <clark.kim@nxp.com>"); |
| MODULE_DESCRIPTION("PCA9468 charger driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION("3.4.10S"); |