Loading Documentation/devicetree/bindings/power/qpnp-linear-charger.txt +6 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,12 @@ Parent node optional properties: charging. BMS and charger communicates with each other via power_supply framework. - qcom,parallel-charger This is a bool property to indicate the LBC will operate as a secondary charger in the parallel mode. If this is enabled the charging operations will be controlled by the primary-charger. Sub node required structure: - A qcom,charger node must be a child of an SPMI node that has specified Loading drivers/power/qpnp-linear-charger.c +394 −58 Original line number Diff line number Diff line /* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -48,6 +48,7 @@ #define CHG_OPTION_MASK BIT(7) #define CHG_STATUS_REG 0x09 #define CHG_VDD_LOOP_BIT BIT(1) #define VINMIN_LOOP_BIT BIT(3) #define CHG_VDD_MAX_REG 0x40 #define CHG_VDD_SAFE_REG 0x41 #define CHG_IBAT_MAX_REG 0x44 Loading Loading @@ -92,6 +93,7 @@ #define BTC_COMP_EN_MASK BIT(7) #define BTC_COLD_MASK BIT(1) #define BTC_HOT_MASK BIT(0) #define BTC_COMP_OVERRIDE_REG 0xE5 /* MISC peripheral register offset */ #define MISC_REV2_REG 0x01 Loading Loading @@ -144,6 +146,7 @@ enum { THERMAL = BIT(1), CURRENT = BIT(2), SOC = BIT(3), PARALLEL = BIT(4), }; enum bpd_type { Loading Loading @@ -352,6 +355,12 @@ struct qpnp_lbc_chip { int usb_psy_ma; int delta_vddmax_uv; int init_trim_uv; /* parallel-chg params */ int parallel_charging_enabled; int lbc_max_chg_current; int ichg_now; struct alarm vddtrim_alarm; struct work_struct vddtrim_work; struct qpnp_lbc_irq irqs[MAX_IRQS]; Loading @@ -368,6 +377,10 @@ struct qpnp_lbc_chip { struct qpnp_adc_tm_chip *adc_tm_dev; struct led_classdev led_cdev; struct dentry *debug_root; /* parallel-chg params */ struct power_supply parallel_psy; struct delayed_work parallel_work; }; static void qpnp_lbc_enable_irq(struct qpnp_lbc_chip *chip, Loading Loading @@ -1040,6 +1053,22 @@ static int qpnp_lbc_register_chgr_led(struct qpnp_lbc_chip *chip) return rc; }; static int is_vinmin_set(struct qpnp_lbc_chip *chip) { u8 reg; int rc; rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG, ®, 1); if (rc) { pr_err("Unable to read charger status rc=%d\n", rc); return false; } pr_debug("chg_status=0x%x\n", reg); return !!(reg & VINMIN_LOOP_BIT); } static int qpnp_lbc_vbatdet_override(struct qpnp_lbc_chip *chip, int ovr_val) { int rc; Loading Loading @@ -1635,6 +1664,147 @@ static int qpnp_batt_power_get_property(struct power_supply *psy, return 0; } #define VINMIN_DELAY msecs_to_jiffies(500) static void qpnp_lbc_parallel_work(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); struct qpnp_lbc_chip *chip = container_of(dwork, struct qpnp_lbc_chip, parallel_work); if (is_vinmin_set(chip)) { /* vinmin-loop triggered - stop ibat increase */ pr_debug("vinmin_loop triggered ichg_now=%d\n", chip->ichg_now); goto exit_work; } else { int temp = chip->ichg_now + QPNP_LBC_I_STEP_MA; if (temp > chip->lbc_max_chg_current) { pr_debug("ichg_now=%d beyond max_chg_limit=%d - stopping\n", temp, chip->lbc_max_chg_current); goto exit_work; } chip->ichg_now = temp; qpnp_lbc_ibatmax_set(chip, chip->ichg_now); pr_debug("ichg_now increased to %d\n", chip->ichg_now); } schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY); return; exit_work: pm_relax(chip->dev); } static int qpnp_lbc_parallel_charging_config(struct qpnp_lbc_chip *chip, int enable) { chip->parallel_charging_enabled = !!enable; if (enable) { /* Prevent sleep until charger is configured */ chip->ichg_now = QPNP_LBC_IBATMAX_MIN; qpnp_lbc_ibatmax_set(chip, chip->ichg_now); qpnp_lbc_charger_enable(chip, PARALLEL, 1); pm_stay_awake(chip->dev); schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY); } else { cancel_delayed_work_sync(&chip->parallel_work); pm_relax(chip->dev); /* set minimum charging current and disable charging */ chip->ichg_now = 0; chip->lbc_max_chg_current = 0; qpnp_lbc_ibatmax_set(chip, 0); qpnp_lbc_charger_enable(chip, PARALLEL, 0); } pr_debug("charging=%d ichg_now=%d max_chg_current=%d\n", enable, chip->ichg_now, chip->lbc_max_chg_current); return 0; } static enum power_supply_property qpnp_lbc_parallel_properties[] = { POWER_SUPPLY_PROP_CHARGING_ENABLED, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, }; static int qpnp_lbc_parallel_set_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { int rc = 0; struct qpnp_lbc_chip *chip = container_of(psy, struct qpnp_lbc_chip, parallel_psy); switch (prop) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: qpnp_lbc_parallel_charging_config(chip, !!val->intval); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: chip->lbc_max_chg_current = val->intval / 1000; pr_debug("lbc_max_current=%d\n", chip->lbc_max_chg_current); break; default: return -EINVAL; } return rc; } static int qpnp_lbc_parallel_is_writeable(struct power_supply *psy, enum power_supply_property prop) { int rc; switch (prop) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: rc = 1; break; default: rc = 0; break; } return rc; } static int qpnp_lbc_parallel_get_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { struct qpnp_lbc_chip *chip = container_of(psy, struct qpnp_lbc_chip, parallel_psy); switch (prop) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: val->intval = chip->parallel_charging_enabled; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: val->intval = chip->lbc_max_chg_current * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = chip->ichg_now * 1000; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: val->intval = get_prop_charge_type(chip); break; case POWER_SUPPLY_PROP_STATUS: val->intval = get_prop_batt_status(chip); break; case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: val->intval = is_vinmin_set(chip); break; default: return -EINVAL; } return 0; } static void qpnp_lbc_jeita_adc_notification(enum qpnp_tm_state state, void *ctx) { struct qpnp_lbc_chip *chip = ctx; Loading Loading @@ -2630,55 +2800,95 @@ static enum alarmtimer_restart vddtrim_callback(struct alarm *alarm, return ALARMTIMER_NORESTART; } static int qpnp_lbc_probe(struct spmi_device *spmi) static int qpnp_lbc_parallel_charger_init(struct qpnp_lbc_chip *chip) { u8 subtype; ktime_t kt; struct qpnp_lbc_chip *chip; struct resource *resource; struct spmi_resource *spmi_resource; struct power_supply *usb_psy; int rc = 0; u8 reg_val; int rc; usb_psy = power_supply_get_by_name("usb"); if (!usb_psy) { pr_err("usb supply not found deferring probe\n"); return -EPROBE_DEFER; rc = qpnp_lbc_vinmin_set(chip, chip->cfg_min_voltage_mv); if (rc) { pr_err("Failed to set vin_min rc=%d\n", rc); return rc; } chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip), GFP_KERNEL); if (!chip) { pr_err("memory allocation failed.\n"); return -ENOMEM; rc = qpnp_lbc_vddsafe_set(chip, chip->cfg_max_voltage_mv); if (rc) { pr_err("Failed to set vdd_safe rc=%d\n", rc); return rc; } rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv); if (rc) { pr_err("Failed to set vdd_max rc=%d\n", rc); return rc; } chip->usb_psy = usb_psy; chip->dev = &spmi->dev; chip->spmi = spmi; chip->fake_battery_soc = -EINVAL; dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); mutex_init(&chip->jeita_configure_lock); mutex_init(&chip->chg_enable_lock); spin_lock_init(&chip->hw_access_lock); spin_lock_init(&chip->ibat_change_lock); spin_lock_init(&chip->irq_lock); INIT_WORK(&chip->vddtrim_work, qpnp_lbc_vddtrim_work_fn); alarm_init(&chip->vddtrim_alarm, ALARM_REALTIME, vddtrim_callback); /* set the minimum charging current */ rc = qpnp_lbc_ibatmax_set(chip, 0); if (rc) { pr_err("Failed to set IBAT_MAX to 0 rc=%d\n", rc); return rc; } /* Get all device-tree properties */ rc = qpnp_charger_read_dt_props(chip); /* disable charging */ rc = qpnp_lbc_charger_enable(chip, PARALLEL, 0); if (rc) { pr_err("Failed to read DT properties rc=%d\n", rc); pr_err("Unable to disable charging rc=%d\n", rc); return 0; } /* Enable BID and disable THM based BPD */ reg_val = BATT_ID_EN | BATT_BPD_OFFMODE_EN; rc = qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BPD_CTRL_REG, ®_val, 1); if (rc) pr_err("Failed to override BPD configuration rc=%d\n", rc); /* Disable and override BTC */ reg_val = 0x2A; rc = __qpnp_lbc_secure_write(chip->spmi, chip->bat_if_base, BTC_COMP_OVERRIDE_REG, ®_val, 1); if (rc) pr_err("Failed to disable BTC override rc=%d\n", rc); reg_val = 0; rc = qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BTC_CTRL, ®_val, 1); if (rc) pr_err("Failed to disable BTC rc=%d\n", rc); /* override VBAT_DET */ rc = qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); if (rc) pr_err("Failed to override VBAT_DET rc=%d\n", rc); /* Set BOOT_DONE and ENUM complete */ reg_val = 0; rc = qpnp_lbc_write(chip, chip->usb_chgpth_base + CHG_USB_ENUM_T_STOP_REG, ®_val, 1); if (rc) pr_err("Failed to stop enum-timer rc=%d\n", rc); reg_val = MISC_BOOT_DONE; rc = qpnp_lbc_write(chip, chip->misc_base + MISC_BOOT_DONE_REG, ®_val, 1); if (rc) pr_err("Failed to set boot-done rc=%d\n", rc); return rc; } static int qpnp_lbc_parse_resources(struct qpnp_lbc_chip *chip) { u8 subtype; int rc = 0; struct resource *resource; struct spmi_resource *spmi_resource; struct spmi_device *spmi = chip->spmi; spmi_for_each_container_dev(spmi_resource, spmi) { if (!spmi_resource) { pr_err("spmi resource absent\n"); rc = -ENXIO; goto fail_chg_enable; return -ENXIO; } resource = spmi_get_resource(spmi, spmi_resource, Loading @@ -2686,26 +2896,23 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) if (!(resource && resource->start)) { pr_err("node %s IO resource absent!\n", spmi->dev.of_node->full_name); rc = -ENXIO; goto fail_chg_enable; return -ENXIO; } rc = qpnp_lbc_read(chip, resource->start + PERP_SUBTYPE_REG, &subtype, 1); if (rc) { pr_err("Peripheral subtype read failed rc=%d\n", rc); goto fail_chg_enable; return rc; } switch (subtype) { case LBC_CHGR_SUBTYPE: chip->chgr_base = resource->start; /* Get Charger peripheral irq numbers */ rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource); if (rc) { pr_err("Failed to get CHGR irqs rc=%d\n", rc); goto fail_chg_enable; return rc; } break; case LBC_USB_PTH_SUBTYPE: Loading @@ -2714,24 +2921,15 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) if (rc) { pr_err("Failed to get USB_PTH irqs rc=%d\n", rc); goto fail_chg_enable; return rc; } break; case LBC_BAT_IF_SUBTYPE: chip->bat_if_base = resource->start; chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg"); if (IS_ERR(chip->vadc_dev)) { rc = PTR_ERR(chip->vadc_dev); if (rc != -EPROBE_DEFER) pr_err("vadc prop missing rc=%d\n", rc); goto fail_chg_enable; } /* Get Charger Batt-IF peripheral irq numbers */ rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource); if (rc) { pr_err("Failed to get BAT_IF irqs rc=%d\n", rc); goto fail_chg_enable; return rc; } break; case LBC_MISC_SUBTYPE: Loading @@ -2743,6 +2941,120 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) } } pr_debug("chgr_base=%x usb_chgpth_base=%x bat_if_base=%x misc_base=%x\n", chip->chgr_base, chip->usb_chgpth_base, chip->bat_if_base, chip->misc_base); return rc; } static int qpnp_lbc_parallel_probe(struct spmi_device *spmi) { int rc = 0; struct qpnp_lbc_chip *chip; chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip), GFP_KERNEL); if (!chip) { pr_err("memory allocation failed.\n"); return -ENOMEM; } chip->dev = &spmi->dev; chip->spmi = spmi; dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); spin_lock_init(&chip->hw_access_lock); spin_lock_init(&chip->ibat_change_lock); INIT_DELAYED_WORK(&chip->parallel_work, qpnp_lbc_parallel_work); OF_PROP_READ(chip, cfg_max_voltage_mv, "vddmax-mv", rc, 0); if (rc) return rc; OF_PROP_READ(chip, cfg_min_voltage_mv, "vinmin-mv", rc, 0); if (rc) return rc; rc = qpnp_lbc_parse_resources(chip); if (rc) { pr_err("Unable to parse LBC(parallel) resources rc=%d\n", rc); return rc; } rc = qpnp_lbc_parallel_charger_init(chip); if (rc) { pr_err("Unable to initialize LBC(parallel) rc=%d\n", rc); return rc; } chip->parallel_psy.name = "usb-parallel"; chip->parallel_psy.type = POWER_SUPPLY_TYPE_USB_PARALLEL; chip->parallel_psy.get_property = qpnp_lbc_parallel_get_property; chip->parallel_psy.set_property = qpnp_lbc_parallel_set_property; chip->parallel_psy.properties = qpnp_lbc_parallel_properties; chip->parallel_psy.property_is_writeable = qpnp_lbc_parallel_is_writeable; chip->parallel_psy.num_properties = ARRAY_SIZE(qpnp_lbc_parallel_properties); rc = power_supply_register(chip->dev, &chip->parallel_psy); if (rc < 0) { pr_err("Unable to register LBC parallel_psy rc = %d\n", rc); return rc; } pr_info("LBC (parallel) registered successfully!\n"); return 0; } static int qpnp_lbc_main_probe(struct spmi_device *spmi) { ktime_t kt; struct qpnp_lbc_chip *chip; struct power_supply *usb_psy; int rc = 0; usb_psy = power_supply_get_by_name("usb"); if (!usb_psy) { pr_err("usb supply not found deferring probe\n"); return -EPROBE_DEFER; } chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip), GFP_KERNEL); if (!chip) { pr_err("memory allocation failed.\n"); return -ENOMEM; } chip->usb_psy = usb_psy; chip->dev = &spmi->dev; chip->spmi = spmi; chip->fake_battery_soc = -EINVAL; dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); mutex_init(&chip->jeita_configure_lock); mutex_init(&chip->chg_enable_lock); spin_lock_init(&chip->hw_access_lock); spin_lock_init(&chip->ibat_change_lock); spin_lock_init(&chip->irq_lock); INIT_WORK(&chip->vddtrim_work, qpnp_lbc_vddtrim_work_fn); alarm_init(&chip->vddtrim_alarm, ALARM_REALTIME, vddtrim_callback); /* Get all device-tree properties */ rc = qpnp_charger_read_dt_props(chip); if (rc) { pr_err("Failed to read DT properties rc=%d\n", rc); return rc; } rc = qpnp_lbc_parse_resources(chip); if (rc) { pr_err("Unable to parse LBC resources rc=%d\n", rc); goto fail_chg_enable; } if (chip->cfg_use_external_charger) { pr_warn("Disabling Linear Charger (e-external-charger = 1)\n"); rc = qpnp_disable_lbc_charger(chip); Loading @@ -2751,6 +3063,15 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) return -ENODEV; } chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg"); if (IS_ERR(chip->vadc_dev)) { rc = PTR_ERR(chip->vadc_dev); if (rc != -EPROBE_DEFER) pr_err("vadc prop missing rc=%d\n", rc); goto fail_chg_enable; } /* Initialize h/w */ rc = qpnp_lbc_misc_init(chip); if (rc) { Loading Loading @@ -2882,6 +3203,21 @@ fail_chg_enable: return rc; } static int is_parallel_charger(struct spmi_device *spmi) { return of_property_read_bool(spmi->dev.of_node, "qcom,parallel-charger"); } static int qpnp_lbc_probe(struct spmi_device *spmi) { if (is_parallel_charger(spmi)) return qpnp_lbc_parallel_probe(spmi); else return qpnp_lbc_main_probe(spmi); } static int qpnp_lbc_remove(struct spmi_device *spmi) { struct qpnp_lbc_chip *chip = dev_get_drvdata(&spmi->dev); Loading Loading
Documentation/devicetree/bindings/power/qpnp-linear-charger.txt +6 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,12 @@ Parent node optional properties: charging. BMS and charger communicates with each other via power_supply framework. - qcom,parallel-charger This is a bool property to indicate the LBC will operate as a secondary charger in the parallel mode. If this is enabled the charging operations will be controlled by the primary-charger. Sub node required structure: - A qcom,charger node must be a child of an SPMI node that has specified Loading
drivers/power/qpnp-linear-charger.c +394 −58 Original line number Diff line number Diff line /* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -48,6 +48,7 @@ #define CHG_OPTION_MASK BIT(7) #define CHG_STATUS_REG 0x09 #define CHG_VDD_LOOP_BIT BIT(1) #define VINMIN_LOOP_BIT BIT(3) #define CHG_VDD_MAX_REG 0x40 #define CHG_VDD_SAFE_REG 0x41 #define CHG_IBAT_MAX_REG 0x44 Loading Loading @@ -92,6 +93,7 @@ #define BTC_COMP_EN_MASK BIT(7) #define BTC_COLD_MASK BIT(1) #define BTC_HOT_MASK BIT(0) #define BTC_COMP_OVERRIDE_REG 0xE5 /* MISC peripheral register offset */ #define MISC_REV2_REG 0x01 Loading Loading @@ -144,6 +146,7 @@ enum { THERMAL = BIT(1), CURRENT = BIT(2), SOC = BIT(3), PARALLEL = BIT(4), }; enum bpd_type { Loading Loading @@ -352,6 +355,12 @@ struct qpnp_lbc_chip { int usb_psy_ma; int delta_vddmax_uv; int init_trim_uv; /* parallel-chg params */ int parallel_charging_enabled; int lbc_max_chg_current; int ichg_now; struct alarm vddtrim_alarm; struct work_struct vddtrim_work; struct qpnp_lbc_irq irqs[MAX_IRQS]; Loading @@ -368,6 +377,10 @@ struct qpnp_lbc_chip { struct qpnp_adc_tm_chip *adc_tm_dev; struct led_classdev led_cdev; struct dentry *debug_root; /* parallel-chg params */ struct power_supply parallel_psy; struct delayed_work parallel_work; }; static void qpnp_lbc_enable_irq(struct qpnp_lbc_chip *chip, Loading Loading @@ -1040,6 +1053,22 @@ static int qpnp_lbc_register_chgr_led(struct qpnp_lbc_chip *chip) return rc; }; static int is_vinmin_set(struct qpnp_lbc_chip *chip) { u8 reg; int rc; rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG, ®, 1); if (rc) { pr_err("Unable to read charger status rc=%d\n", rc); return false; } pr_debug("chg_status=0x%x\n", reg); return !!(reg & VINMIN_LOOP_BIT); } static int qpnp_lbc_vbatdet_override(struct qpnp_lbc_chip *chip, int ovr_val) { int rc; Loading Loading @@ -1635,6 +1664,147 @@ static int qpnp_batt_power_get_property(struct power_supply *psy, return 0; } #define VINMIN_DELAY msecs_to_jiffies(500) static void qpnp_lbc_parallel_work(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); struct qpnp_lbc_chip *chip = container_of(dwork, struct qpnp_lbc_chip, parallel_work); if (is_vinmin_set(chip)) { /* vinmin-loop triggered - stop ibat increase */ pr_debug("vinmin_loop triggered ichg_now=%d\n", chip->ichg_now); goto exit_work; } else { int temp = chip->ichg_now + QPNP_LBC_I_STEP_MA; if (temp > chip->lbc_max_chg_current) { pr_debug("ichg_now=%d beyond max_chg_limit=%d - stopping\n", temp, chip->lbc_max_chg_current); goto exit_work; } chip->ichg_now = temp; qpnp_lbc_ibatmax_set(chip, chip->ichg_now); pr_debug("ichg_now increased to %d\n", chip->ichg_now); } schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY); return; exit_work: pm_relax(chip->dev); } static int qpnp_lbc_parallel_charging_config(struct qpnp_lbc_chip *chip, int enable) { chip->parallel_charging_enabled = !!enable; if (enable) { /* Prevent sleep until charger is configured */ chip->ichg_now = QPNP_LBC_IBATMAX_MIN; qpnp_lbc_ibatmax_set(chip, chip->ichg_now); qpnp_lbc_charger_enable(chip, PARALLEL, 1); pm_stay_awake(chip->dev); schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY); } else { cancel_delayed_work_sync(&chip->parallel_work); pm_relax(chip->dev); /* set minimum charging current and disable charging */ chip->ichg_now = 0; chip->lbc_max_chg_current = 0; qpnp_lbc_ibatmax_set(chip, 0); qpnp_lbc_charger_enable(chip, PARALLEL, 0); } pr_debug("charging=%d ichg_now=%d max_chg_current=%d\n", enable, chip->ichg_now, chip->lbc_max_chg_current); return 0; } static enum power_supply_property qpnp_lbc_parallel_properties[] = { POWER_SUPPLY_PROP_CHARGING_ENABLED, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, }; static int qpnp_lbc_parallel_set_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { int rc = 0; struct qpnp_lbc_chip *chip = container_of(psy, struct qpnp_lbc_chip, parallel_psy); switch (prop) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: qpnp_lbc_parallel_charging_config(chip, !!val->intval); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: chip->lbc_max_chg_current = val->intval / 1000; pr_debug("lbc_max_current=%d\n", chip->lbc_max_chg_current); break; default: return -EINVAL; } return rc; } static int qpnp_lbc_parallel_is_writeable(struct power_supply *psy, enum power_supply_property prop) { int rc; switch (prop) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: rc = 1; break; default: rc = 0; break; } return rc; } static int qpnp_lbc_parallel_get_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { struct qpnp_lbc_chip *chip = container_of(psy, struct qpnp_lbc_chip, parallel_psy); switch (prop) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: val->intval = chip->parallel_charging_enabled; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: val->intval = chip->lbc_max_chg_current * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = chip->ichg_now * 1000; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: val->intval = get_prop_charge_type(chip); break; case POWER_SUPPLY_PROP_STATUS: val->intval = get_prop_batt_status(chip); break; case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: val->intval = is_vinmin_set(chip); break; default: return -EINVAL; } return 0; } static void qpnp_lbc_jeita_adc_notification(enum qpnp_tm_state state, void *ctx) { struct qpnp_lbc_chip *chip = ctx; Loading Loading @@ -2630,55 +2800,95 @@ static enum alarmtimer_restart vddtrim_callback(struct alarm *alarm, return ALARMTIMER_NORESTART; } static int qpnp_lbc_probe(struct spmi_device *spmi) static int qpnp_lbc_parallel_charger_init(struct qpnp_lbc_chip *chip) { u8 subtype; ktime_t kt; struct qpnp_lbc_chip *chip; struct resource *resource; struct spmi_resource *spmi_resource; struct power_supply *usb_psy; int rc = 0; u8 reg_val; int rc; usb_psy = power_supply_get_by_name("usb"); if (!usb_psy) { pr_err("usb supply not found deferring probe\n"); return -EPROBE_DEFER; rc = qpnp_lbc_vinmin_set(chip, chip->cfg_min_voltage_mv); if (rc) { pr_err("Failed to set vin_min rc=%d\n", rc); return rc; } chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip), GFP_KERNEL); if (!chip) { pr_err("memory allocation failed.\n"); return -ENOMEM; rc = qpnp_lbc_vddsafe_set(chip, chip->cfg_max_voltage_mv); if (rc) { pr_err("Failed to set vdd_safe rc=%d\n", rc); return rc; } rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv); if (rc) { pr_err("Failed to set vdd_max rc=%d\n", rc); return rc; } chip->usb_psy = usb_psy; chip->dev = &spmi->dev; chip->spmi = spmi; chip->fake_battery_soc = -EINVAL; dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); mutex_init(&chip->jeita_configure_lock); mutex_init(&chip->chg_enable_lock); spin_lock_init(&chip->hw_access_lock); spin_lock_init(&chip->ibat_change_lock); spin_lock_init(&chip->irq_lock); INIT_WORK(&chip->vddtrim_work, qpnp_lbc_vddtrim_work_fn); alarm_init(&chip->vddtrim_alarm, ALARM_REALTIME, vddtrim_callback); /* set the minimum charging current */ rc = qpnp_lbc_ibatmax_set(chip, 0); if (rc) { pr_err("Failed to set IBAT_MAX to 0 rc=%d\n", rc); return rc; } /* Get all device-tree properties */ rc = qpnp_charger_read_dt_props(chip); /* disable charging */ rc = qpnp_lbc_charger_enable(chip, PARALLEL, 0); if (rc) { pr_err("Failed to read DT properties rc=%d\n", rc); pr_err("Unable to disable charging rc=%d\n", rc); return 0; } /* Enable BID and disable THM based BPD */ reg_val = BATT_ID_EN | BATT_BPD_OFFMODE_EN; rc = qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BPD_CTRL_REG, ®_val, 1); if (rc) pr_err("Failed to override BPD configuration rc=%d\n", rc); /* Disable and override BTC */ reg_val = 0x2A; rc = __qpnp_lbc_secure_write(chip->spmi, chip->bat_if_base, BTC_COMP_OVERRIDE_REG, ®_val, 1); if (rc) pr_err("Failed to disable BTC override rc=%d\n", rc); reg_val = 0; rc = qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BTC_CTRL, ®_val, 1); if (rc) pr_err("Failed to disable BTC rc=%d\n", rc); /* override VBAT_DET */ rc = qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); if (rc) pr_err("Failed to override VBAT_DET rc=%d\n", rc); /* Set BOOT_DONE and ENUM complete */ reg_val = 0; rc = qpnp_lbc_write(chip, chip->usb_chgpth_base + CHG_USB_ENUM_T_STOP_REG, ®_val, 1); if (rc) pr_err("Failed to stop enum-timer rc=%d\n", rc); reg_val = MISC_BOOT_DONE; rc = qpnp_lbc_write(chip, chip->misc_base + MISC_BOOT_DONE_REG, ®_val, 1); if (rc) pr_err("Failed to set boot-done rc=%d\n", rc); return rc; } static int qpnp_lbc_parse_resources(struct qpnp_lbc_chip *chip) { u8 subtype; int rc = 0; struct resource *resource; struct spmi_resource *spmi_resource; struct spmi_device *spmi = chip->spmi; spmi_for_each_container_dev(spmi_resource, spmi) { if (!spmi_resource) { pr_err("spmi resource absent\n"); rc = -ENXIO; goto fail_chg_enable; return -ENXIO; } resource = spmi_get_resource(spmi, spmi_resource, Loading @@ -2686,26 +2896,23 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) if (!(resource && resource->start)) { pr_err("node %s IO resource absent!\n", spmi->dev.of_node->full_name); rc = -ENXIO; goto fail_chg_enable; return -ENXIO; } rc = qpnp_lbc_read(chip, resource->start + PERP_SUBTYPE_REG, &subtype, 1); if (rc) { pr_err("Peripheral subtype read failed rc=%d\n", rc); goto fail_chg_enable; return rc; } switch (subtype) { case LBC_CHGR_SUBTYPE: chip->chgr_base = resource->start; /* Get Charger peripheral irq numbers */ rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource); if (rc) { pr_err("Failed to get CHGR irqs rc=%d\n", rc); goto fail_chg_enable; return rc; } break; case LBC_USB_PTH_SUBTYPE: Loading @@ -2714,24 +2921,15 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) if (rc) { pr_err("Failed to get USB_PTH irqs rc=%d\n", rc); goto fail_chg_enable; return rc; } break; case LBC_BAT_IF_SUBTYPE: chip->bat_if_base = resource->start; chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg"); if (IS_ERR(chip->vadc_dev)) { rc = PTR_ERR(chip->vadc_dev); if (rc != -EPROBE_DEFER) pr_err("vadc prop missing rc=%d\n", rc); goto fail_chg_enable; } /* Get Charger Batt-IF peripheral irq numbers */ rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource); if (rc) { pr_err("Failed to get BAT_IF irqs rc=%d\n", rc); goto fail_chg_enable; return rc; } break; case LBC_MISC_SUBTYPE: Loading @@ -2743,6 +2941,120 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) } } pr_debug("chgr_base=%x usb_chgpth_base=%x bat_if_base=%x misc_base=%x\n", chip->chgr_base, chip->usb_chgpth_base, chip->bat_if_base, chip->misc_base); return rc; } static int qpnp_lbc_parallel_probe(struct spmi_device *spmi) { int rc = 0; struct qpnp_lbc_chip *chip; chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip), GFP_KERNEL); if (!chip) { pr_err("memory allocation failed.\n"); return -ENOMEM; } chip->dev = &spmi->dev; chip->spmi = spmi; dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); spin_lock_init(&chip->hw_access_lock); spin_lock_init(&chip->ibat_change_lock); INIT_DELAYED_WORK(&chip->parallel_work, qpnp_lbc_parallel_work); OF_PROP_READ(chip, cfg_max_voltage_mv, "vddmax-mv", rc, 0); if (rc) return rc; OF_PROP_READ(chip, cfg_min_voltage_mv, "vinmin-mv", rc, 0); if (rc) return rc; rc = qpnp_lbc_parse_resources(chip); if (rc) { pr_err("Unable to parse LBC(parallel) resources rc=%d\n", rc); return rc; } rc = qpnp_lbc_parallel_charger_init(chip); if (rc) { pr_err("Unable to initialize LBC(parallel) rc=%d\n", rc); return rc; } chip->parallel_psy.name = "usb-parallel"; chip->parallel_psy.type = POWER_SUPPLY_TYPE_USB_PARALLEL; chip->parallel_psy.get_property = qpnp_lbc_parallel_get_property; chip->parallel_psy.set_property = qpnp_lbc_parallel_set_property; chip->parallel_psy.properties = qpnp_lbc_parallel_properties; chip->parallel_psy.property_is_writeable = qpnp_lbc_parallel_is_writeable; chip->parallel_psy.num_properties = ARRAY_SIZE(qpnp_lbc_parallel_properties); rc = power_supply_register(chip->dev, &chip->parallel_psy); if (rc < 0) { pr_err("Unable to register LBC parallel_psy rc = %d\n", rc); return rc; } pr_info("LBC (parallel) registered successfully!\n"); return 0; } static int qpnp_lbc_main_probe(struct spmi_device *spmi) { ktime_t kt; struct qpnp_lbc_chip *chip; struct power_supply *usb_psy; int rc = 0; usb_psy = power_supply_get_by_name("usb"); if (!usb_psy) { pr_err("usb supply not found deferring probe\n"); return -EPROBE_DEFER; } chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip), GFP_KERNEL); if (!chip) { pr_err("memory allocation failed.\n"); return -ENOMEM; } chip->usb_psy = usb_psy; chip->dev = &spmi->dev; chip->spmi = spmi; chip->fake_battery_soc = -EINVAL; dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); mutex_init(&chip->jeita_configure_lock); mutex_init(&chip->chg_enable_lock); spin_lock_init(&chip->hw_access_lock); spin_lock_init(&chip->ibat_change_lock); spin_lock_init(&chip->irq_lock); INIT_WORK(&chip->vddtrim_work, qpnp_lbc_vddtrim_work_fn); alarm_init(&chip->vddtrim_alarm, ALARM_REALTIME, vddtrim_callback); /* Get all device-tree properties */ rc = qpnp_charger_read_dt_props(chip); if (rc) { pr_err("Failed to read DT properties rc=%d\n", rc); return rc; } rc = qpnp_lbc_parse_resources(chip); if (rc) { pr_err("Unable to parse LBC resources rc=%d\n", rc); goto fail_chg_enable; } if (chip->cfg_use_external_charger) { pr_warn("Disabling Linear Charger (e-external-charger = 1)\n"); rc = qpnp_disable_lbc_charger(chip); Loading @@ -2751,6 +3063,15 @@ static int qpnp_lbc_probe(struct spmi_device *spmi) return -ENODEV; } chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg"); if (IS_ERR(chip->vadc_dev)) { rc = PTR_ERR(chip->vadc_dev); if (rc != -EPROBE_DEFER) pr_err("vadc prop missing rc=%d\n", rc); goto fail_chg_enable; } /* Initialize h/w */ rc = qpnp_lbc_misc_init(chip); if (rc) { Loading Loading @@ -2882,6 +3203,21 @@ fail_chg_enable: return rc; } static int is_parallel_charger(struct spmi_device *spmi) { return of_property_read_bool(spmi->dev.of_node, "qcom,parallel-charger"); } static int qpnp_lbc_probe(struct spmi_device *spmi) { if (is_parallel_charger(spmi)) return qpnp_lbc_parallel_probe(spmi); else return qpnp_lbc_main_probe(spmi); } static int qpnp_lbc_remove(struct spmi_device *spmi) { struct qpnp_lbc_chip *chip = dev_get_drvdata(&spmi->dev); Loading