/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_BATTERY_SAMSUNG) #include #include "include/charger/pca9468_charger.h" #include "include/sec_charging_common.h" #include "include/sec_direct_charger.h" #else #include #endif #if defined (CONFIG_OF) #include #include #endif /* CONFIG_OF */ #include #ifdef CONFIG_USBPD_PHY_QCOM #include // 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); } if (pca9468->pdata->chgen_gpio >= 0) 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); } 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); } 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); pca9468_check_wdt_control(pca9468); #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_CHARGER_MAX77705) // In Maxim IF-PMIC, periodic pps request runs on F/W 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) 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; 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); } 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) 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) if(pdata->chgen_gpio >= 0) { 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 "); MODULE_DESCRIPTION("PCA9468 charger driver"); MODULE_LICENSE("GPL"); MODULE_VERSION("3.4.10S");