diff --git a/drivers/power/fet-control/fet_control.c b/drivers/power/fet-control/fet_control.c index 3849b8c44afb..d4d5c58e6a99 100755 --- a/drivers/power/fet-control/fet_control.c +++ b/drivers/power/fet-control/fet_control.c @@ -35,19 +35,20 @@ #include #include #include +#include -#define RBALANCE_VDIFF_MV 100 -#define MAIN_MV_MID_LO 3600 -#define MAIN_MV_MID_HI 3800 -#define MAIN_CURR_SWITCH 500 +#define RBALANCE_VDIFF_MV 200 #define GPIO_SET_DELAY 50 #define CHRG_FULLCURR_EN 1 -#define HBDLY_DISCHARGE_MS 30000 -#define HBDLY_CHARGE_MS 6000 +#define CHRG_FULLCURR_DIS 0 +#define HBDLY_DISCHARGE_SEC 30 +#define HBDLY_CHARGE_SEC 6 static struct fet_control_data { struct device *dev; struct power_supply *main_batt_psy; + struct power_supply *usb_psy; + struct notifier_block ps_notif; struct delayed_work update; bool init_done; bool ps_is_present; @@ -56,11 +57,14 @@ static struct fet_control_data { int battplus_en_gpio; int balance_en_n_gpio; int chrg_fullcurr_en_gpio; + int vbus_ocp_fault_n_gpio; + struct iio_channel *vbatt2_sns_chan; } fetControlData; int battplus_state = -1; int balance_state = -1; int fullcurr_state = -1; +int ocp_fault_n_state = -1; static int get_ps_int_prop(struct power_supply *psy, enum power_supply_property prop) { @@ -179,6 +183,32 @@ static int set_fullcurr_state(const char *val, const struct kernel_param *kp) return 0; } +static int set_ocp_fault_n_state(const char *val, const struct kernel_param *kp) +{ + int rc; + long mode; + + if (!fetControlData.init_done) + return -ENODEV; + + rc = kstrtol(val, 0, &mode); + if (rc) + return rc; + + rc = update_state_gpio(fetControlData.vbus_ocp_fault_n_gpio, !!mode); + if (rc) { + pr_err("Update fet-ctl gpio [%d] failed\n", fetControlData.vbus_ocp_fault_n_gpio); + return rc; + } else { + ocp_fault_n_state = !!mode; + pr_debug("Set vbus_ocp_fault_n gpio[%d], VAL:[%d]\n", + fetControlData.vbus_ocp_fault_n_gpio, + gpio_get_value(fetControlData.vbus_ocp_fault_n_gpio)); + } + + return 0; +} + static struct kernel_param_ops battplus_ops = { .set = &set_battplus_state, @@ -197,6 +227,12 @@ static struct kernel_param_ops fullcurr_ops = .get = param_get_int, }; +static struct kernel_param_ops ocpfault_ops = +{ + .set = &set_ocp_fault_n_state, + .get = param_get_int, +}; + module_param_cb(battplus_state, &battplus_ops, &battplus_state, @@ -215,72 +251,178 @@ module_param_cb(fullcurr_state, S_IRUGO | S_IWUSR ); +module_param_cb(ocp_fault_n_state, + &ocpfault_ops, + &ocp_fault_n_state, + S_IRUGO | S_IWUSR +); + +static int mmi_is_factory(void) +{ + const char *bootargs_str = NULL; + char *kvpair = NULL; + char *value = NULL; + int ret = -1; + struct device_node *n = of_find_node_by_path("/chosen"); + size_t bootargs_str_len = 0; + + if (n == NULL) + goto ret; + + if (of_property_read_string(n, "mmi,bootconfig", &bootargs_str) != 0) + goto putnode; + + bootargs_str_len = strlen(bootargs_str); + kvpair = strnstr(bootargs_str, "androidboot.mode=", strlen(bootargs_str)); + if (kvpair) { // found key + value = strchr(kvpair, '=') + 1; + if (!strncmp(value, "mot-factory", 11)) + ret = 1; + else + ret = 0; + } + +putnode: + of_node_put(n); +ret: + return ret; +} + static void update_work(struct work_struct *work) { struct fet_control_data *data = container_of(work, struct fet_control_data, update.work); - int main_curr, main_mv; - int main_dischg = -1; - int hb_sched_time = HBDLY_DISCHARGE_MS; + int main_curr, main_mv, flip_mv = 0, usbtype; + int main_chg = 1, rc = 0; + int hb_sched_time = HBDLY_DISCHARGE_SEC; + int vbatt2_sns_uv = -EINVAL; + const char * usb_types[] = {"UNKNOWN","SDP","DCP","CDP","ACA","Type-C","PD","PD_DRP","PD_PPS","BRICKID"}; - fullcurr_state = CHRG_FULLCURR_EN; - if (battplus_state) { - pr_info("BATTFET Closed- Ending Work..\n"); + fullcurr_state = CHRG_FULLCURR_DIS; + + usbtype = get_ps_int_prop(data->usb_psy, + POWER_SUPPLY_PROP_USB_TYPE); + pr_info("USB-TYPE found:%d [%s]\n", usbtype,usb_types[usbtype] ); + + /* For no Charger connected & battfet already closed, nothing more to be done */ + if (mmi_is_factory()) { + pr_err("Factory Mode- Cancel Work.. \n"); + cancel_delayed_work(&data->update); + return; + } else if (usbtype == POWER_SUPPLY_USB_TYPE_UNKNOWN && battplus_state) { + pr_err("BATTFET Closed & Not Charging- Cancel Work..\n"); cancel_delayed_work(&data->update); return; } - /*flip_mv = get_ps_int_prop(data->flip_batt_psy, - POWER_SUPPLY_PROP_VOLTAGE_NOW); - flip_mv /= 1000;*/ - + /* VMain in mV */ main_mv = get_ps_int_prop(data->main_batt_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW); main_mv /= 1000; - /* Main current sysfs already in mA */ + /* Main current in uA */ main_curr = get_ps_int_prop(data->main_batt_psy, POWER_SUPPLY_PROP_CURRENT_NOW); - if (main_curr > 0) { + if (main_curr > 0 || usbtype != POWER_SUPPLY_USB_TYPE_UNKNOWN) pr_info("Charging Main\n"); - main_dischg = 1; - } + else + main_chg = -1; // Discharging Main data->main_chg_curr_max = get_ps_int_prop(data->main_batt_psy, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN); - data->main_chg_curr_max /= 1000; if (data->main_chg_curr_max < 0) { pr_err("Failed to get Main Max chrg curr\n"); return; } - pr_info("Main_curr:%d, main_Ichg_MAX:%d\n", - main_curr, data->main_chg_curr_max); - pr_info("Flip-Vbatt: --, Main-Vbatt: %d\n", main_mv); + ocp_fault_n_state = gpio_get_value(fetControlData.vbus_ocp_fault_n_gpio); + pr_info("ocp_fault_n GPIO:[%d], VAL:[%d]\n", + fetControlData.vbus_ocp_fault_n_gpio, ocp_fault_n_state); - /* FET paths set in Batt discharge state */ - if (main_dischg == -1) { - /* Leave Balance dflt-en & toggle in/out parallel low-Z battplus fet */ - balance_state = 0; - /*if ((main_mv - flip_mv) < RBALANCE_VDIFF_MV) { - battplus_state = 1;*/ - if ( (main_mv >= MAIN_MV_MID_LO && main_mv <= MAIN_MV_MID_HI) && - (main_dischg * main_curr/1000 < MAIN_CURR_SWITCH) ) { - battplus_state = 1; - pr_info("battplus-EN: %d\n", battplus_state); + /* Leave FULLCURR_DIS until charger is connected */ + if (usbtype != POWER_SUPPLY_USB_TYPE_UNKNOWN) { + if ( (main_chg * main_curr > data->main_chg_curr_max / 2) || + !ocp_fault_n_state ) { + fullcurr_state = CHRG_FULLCURR_DIS; + pr_err("WARNING! Main-OC or OCP-TRIP: Main_curr:%d, ocp_fault_n:%d\n", + main_curr, ocp_fault_n_state); } else { - battplus_state = 0; - pr_info("battplus-DIS: %d\n", battplus_state); - schedule_delayed_work(&data->update, msecs_to_jiffies(hb_sched_time)); + fullcurr_state = CHRG_FULLCURR_EN; } } + /* Read the ADC pm8350b_vbatt2_sns div3, scale *3 and to mV */ + rc = iio_read_channel_processed(fetControlData.vbatt2_sns_chan, &vbatt2_sns_uv); + if (rc < 0) { + pr_err("Error Reading pm8350b_vbatt2_sns_voltage- rc:%d\n", rc); + return; + } else { + pr_info("Read pm8350b_vbatt2_sns_voltage- VAL-uV:%d\n", vbatt2_sns_uv); + flip_mv = (3 * vbatt2_sns_uv) / 1000; + } + + pr_info("Main_curr:%d, main_Ichg_MAX:%d\n", + main_curr, data->main_chg_curr_max); + pr_err("Flip-Vbatt: %d, Main-Vbatt: %d\n", flip_mv, main_mv); + + /* FET BattFET paths */ + /* Leave Balance dflt-en & toggle in/out parallel low-Z battplus fet */ + balance_state = 0; + if ((main_mv - flip_mv) < RBALANCE_VDIFF_MV) { + battplus_state = 1; + pr_info("battplus-EN: %d\n", battplus_state); + } else { + battplus_state = 0; + pr_info("battplus-DIS: %d\n", battplus_state); + } + if (main_chg == 1) + hb_sched_time = HBDLY_CHARGE_SEC; + /* Update GPIO controls */ update_state_gpio(fetControlData.battplus_en_gpio, battplus_state); update_state_gpio(fetControlData.balance_en_n_gpio, balance_state); update_state_gpio(fetControlData.chrg_fullcurr_en_gpio, fullcurr_state); + schedule_delayed_work(&data->update, hb_sched_time * HZ); } +static int ps_notify_callback(struct notifier_block *nb, + unsigned long event, void *p) +{ + struct fet_control_data *data = container_of(nb, struct fet_control_data, ps_notif); + struct power_supply *psy = p; + union power_supply_propval pval = {0}; + int retval; + bool present_ps; + + if ((event == PSY_EVENT_PROP_CHANGED) && + psy && psy->desc->get_property && psy->desc->name && + !strncmp(psy->desc->name, "usb", sizeof("usb")) && data) { + pr_info("psy notif: event = %lu\n", event); + + retval = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, + &pval); + if (retval) { + pr_err("%s psy get property failed, ERR: %d\n", psy->desc->name, retval); + return retval; + } + present_ps = (pval.intval) ? true : false; + pr_info("%s is %s\n", psy->desc->name, + (present_ps) ? "present" : "not present"); + + if (event == PSY_EVENT_PROP_CHANGED) { + if (data->ps_is_present == present_ps) { + pr_info("ps present state unchanged\n"); + return 0; + } + } + data->ps_is_present = present_ps; + + cancel_delayed_work(&data->update); + schedule_delayed_work(&data->update, 3 * HZ); + } + + return 0; +} static int parse_dt(struct device_node *node) { @@ -291,7 +433,7 @@ static int parse_dt(struct device_node *node) if (!rc && main_batt_psy_name) { fetControlData.main_batt_psy = power_supply_get_by_name(main_batt_psy_name); if (!fetControlData.main_batt_psy) { - pr_debug("Could not get flip batt psy and main batt psy, maybe we are early - defer."); + pr_debug("Could not get main batt psy, maybe we are early - defer."); return -EPROBE_DEFER; } } @@ -317,6 +459,13 @@ static int parse_dt(struct device_node *node) return -ENODEV; } + fetControlData.vbus_ocp_fault_n_gpio = of_get_named_gpio(node, + "mmi,vbus-ocp-fault-n-gpio", 0); + if (!gpio_is_valid(fetControlData.vbus_ocp_fault_n_gpio)) { + pr_err("ocp-fault-gpio is not valid!\n"); + return -ENODEV; + } + return rc; } @@ -334,47 +483,83 @@ static int fet_control_probe(struct platform_device *pdev) goto fail; } - rc = gpio_request(fetControlData.battplus_en_gpio, "mmi,flip_battplus_en_gpio"); + fetControlData.usb_psy = power_supply_get_by_name("usb"); + if (!fetControlData.usb_psy) { + pr_debug("Could not get usb psy, maybe we are early - defer."); + return -EPROBE_DEFER; + } + + /* Get the ADC device instance pm8350b_vbatt2_sns, gpio3_pu0_div3 */ + fetControlData.vbatt2_sns_chan = iio_channel_get(fetControlData.dev,"pm8350b_vbatt2_sns"); + if (!fetControlData.vbatt2_sns_chan) { + pr_err("Error Getting pm8350b_vbatt2_sns_voltage channel- rc:%d\n",rc); + return -EINVAL; + } else { + pr_info("Found pm8350b_vbatt2_sns voltage channel\n"); + } + + rc = gpio_request(fetControlData.battplus_en_gpio, + "mmi,flip_battplus_en_gpio"); if (rc) { pr_err("Failed request battplus_en_gpio\n"); goto fail; } - rc = gpio_request(fetControlData.balance_en_n_gpio, "mmi,flip_balance_en_n_gpio"); + rc = gpio_request(fetControlData.balance_en_n_gpio, + "mmi,flip_balance_en_n_gpio"); if (rc) { pr_err("Failed request balance_en_n_gpio\n"); goto fail; } - rc = gpio_request(fetControlData.chrg_fullcurr_en_gpio, "mmi,chrg-fullcurr-en-gpio"); + rc = gpio_request(fetControlData.chrg_fullcurr_en_gpio, + "mmi,chrg-fullcurr-en-gpio"); if (rc) { pr_err("Failed request chrg-fullcurr-en-gpio\n"); goto fail; } - /* Set default Flip Battery Path FETs: battplus path disable*/ - rc = gpio_direction_output(fetControlData.battplus_en_gpio, 0); //Path Disabled + rc = gpio_request(fetControlData.vbus_ocp_fault_n_gpio, + "mmi,vbus-ocp-fault-n-gpio"); if (rc) { - pr_err("Unable to set DIR flip_battplus_en [%d]\n", fetControlData.battplus_en_gpio); + pr_err("Failed request vbus-ocp-fault-n-gpio\n"); + goto fail; + } + + /* Set default Flip Battery Path FETs: battplus path disable*/ + rc = gpio_direction_output(fetControlData.battplus_en_gpio, 0);//Path Disabled + if (rc) { + pr_err("Unable to set DIR flip_battplus_en [%d]\n", + fetControlData.battplus_en_gpio); goto fail; } battplus_state = 0; /* Set Flip Rbalance path enable */ - rc = gpio_direction_output(fetControlData.balance_en_n_gpio, 0); // Path Enabled + rc = gpio_direction_output(fetControlData.balance_en_n_gpio, 0);// Path Enabled if (rc) { - pr_err("Unable to set DIR/VAL Flip_balance_en_n [%d]\n", fetControlData.balance_en_n_gpio); + pr_err("Unable to set DIR/VAL Flip_balance_en_n [%d]\n", + fetControlData.balance_en_n_gpio); goto fail; } balance_state = 0; /* Set charger fullcurr enable */ - rc = gpio_direction_output(fetControlData.chrg_fullcurr_en_gpio, 1); // ChrgFullCurr dflt ENABLE + rc = gpio_direction_output(fetControlData.chrg_fullcurr_en_gpio, 0);// ChrgFullCurr dflt DISABLE if (rc) { - pr_err("Unable to set DIR/VAL chrg_fullcurr_en_gpio [%d]\n", fetControlData.chrg_fullcurr_en_gpio); + pr_err("Unable to set DIR/VAL chrg_fullcurr_en_gpio [%d]\n", + fetControlData.chrg_fullcurr_en_gpio); + goto fail; + } + fullcurr_state = CHRG_FULLCURR_DIS; + + /* Set OCP_FAULT input */ + rc = gpio_direction_input(fetControlData.vbus_ocp_fault_n_gpio); + if (rc) { + pr_err("Unable to set DIR vbus_ocp_fault_n [%d]\n", + fetControlData.vbus_ocp_fault_n_gpio); goto fail; } - fullcurr_state = CHRG_FULLCURR_EN; pr_info("Flip_battplus_en Init GPIO:[%d], VAL:[%d]\n", fetControlData.battplus_en_gpio, @@ -385,10 +570,20 @@ static int fet_control_probe(struct platform_device *pdev) pr_info("chrg_fullcurr Init GPIO:[%d], VAL:[%d]\n", fetControlData.chrg_fullcurr_en_gpio, gpio_get_value(fetControlData.chrg_fullcurr_en_gpio) ); + pr_info("ocp_fault_n Init GPIO:[%d], VAL:[%d]\n", + fetControlData.vbus_ocp_fault_n_gpio, + gpio_get_value(fetControlData.vbus_ocp_fault_n_gpio) ); + + // Notify on plug/unplug + fetControlData.ps_notif.notifier_call = ps_notify_callback; + if (power_supply_reg_notifier(&fetControlData.ps_notif)) { + pr_err("Failed to register notifier\n"); + goto fail; + } // Work to update the fet & charge current states. INIT_DELAYED_WORK(&fetControlData.update, update_work); - schedule_delayed_work(&fetControlData.update, 10); + schedule_delayed_work(&fetControlData.update, HBDLY_DISCHARGE_SEC * HZ); fetControlData.init_done = true; return 0; @@ -401,9 +596,11 @@ static int fet_control_remove(struct platform_device *pdev) if (fetControlData.init_done) { cancel_delayed_work(&fetControlData.update); + power_supply_unreg_notifier(&fetControlData.ps_notif); gpio_free(fetControlData.battplus_en_gpio); gpio_free(fetControlData.balance_en_n_gpio); gpio_free(fetControlData.chrg_fullcurr_en_gpio); + gpio_free(fetControlData.vbus_ocp_fault_n_gpio); } return 0;