Loading Documentation/devicetree/bindings/power/supply/qcom/smb1355-charger.txt 0 → 100644 +73 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. SMB1355 Charger Specific Bindings SMB1355 slave charger is paired with QTI family of standalone chargers to enable a high current, low profile Li+ battery charging system. The device provides 28V DC withstand, wide operating input range of 3.8 to 14.2V for standard 5V USB inputs as well as a wide variety of HVDCP Travel Adapters and is compatible with QTI's Quick Charge technology. ======================= Required Node Structure ======================= SMB1355 Charger must be described in two levels of device nodes. ================================== First Level Node - SMB1355 Charger ================================== Charger specific properties: - compatible Usage: required Value type: <string> Definition: "qcom,smb1355". - qcom,pmic-revid Usage: required Value type: phandle Definition: Should specify the phandle of SMB's revid module. This is used to identify the SMB subtype. ================================================ Second Level Nodes - SMB1355 Charger Peripherals ================================================ Peripheral specific properties: - reg Usage: required Value type: <prop-encoded-array> Definition: Address and size of the peripheral's register block. - interrupts Usage: required Value type: <prop-encoded-array> Definition: Peripheral interrupt specifier. - interrupt-names Usage: required Value type: <stringlist> Definition: Interrupt names. This list must match up 1-to-1 with the interrupts specified in the 'interrupts' property. ======= Example ======= smb1355_charger: qcom,smb1355-charger { compatible = "qcom,smb1355"; #address-cells = <1>; #size-cells = <1>; qcom,chgr@1000 { reg = <0x1000 0x100>; interrupts = <0x10 0x1 IRQ_TYPE_EDGE_BOTH>; interrupt-names = "chg-state-change"; }; qcom,chgr-misc@1600 { reg = <0x1600 0x100>; interrupts = <0x16 0x1 IRQ_TYPE_EDGE_BOTH>; interrupt-names = "wdog-bark"; }; }; drivers/power/supply/qcom/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,16 @@ config SMB135X_CHARGER The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. config SMB1355_SLAVE_CHARGER tristate "SMB1355 Slave Battery Charger" depends on MFD_I2C_PMIC help Say Y to include support for SMB1355 Battery Charger. SMB1355 is a single phase 5A battery charger. The driver supports charger enable/disable. The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. config SMB1351_USB_CHARGER tristate "smb1351 usb charger (with VBUS detection)" depends on I2C Loading drivers/power/supply/qcom/Makefile +1 −0 Original line number Diff line number Diff line obj-$(CONFIG_QPNP_FG_GEN3) += qpnp-fg-gen3.o fg-memif.o fg-util.o obj-$(CONFIG_SMB135X_CHARGER) += smb135x-charger.o pmic-voter.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o obj-$(CONFIG_QPNP_SMB2) += qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o battery.o obj-$(CONFIG_SMB138X_CHARGER) += smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o Loading drivers/power/supply/qcom/smb1355-charger.c 0 → 100644 +675 −0 Original line number Diff line number Diff line /* Copyright (c) 2016-2017 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 * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define pr_fmt(fmt) "SMB1355: %s: " fmt, __func__ #include <linux/device.h> #include <linux/regmap.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/qpnp/qpnp-revid.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/regulator/of_regulator.h> #include <linux/power_supply.h> #include <linux/pmic-voter.h> #define SMB1355_DEFAULT_FCC_UA 1000000 /* SMB1355 registers, different than mentioned in smb-reg.h */ #define CHGR_BASE 0x1000 #define BATIF_BASE 0x1200 #define USBIN_BASE 0x1300 #define MISC_BASE 0x1600 #define BATTERY_STATUS_2_REG (CHGR_BASE + 0x0B) #define DISABLE_CHARGING_BIT BIT(3) #define BATTERY_STATUS_3_REG (CHGR_BASE + 0x0C) #define BATT_GT_PRE_TO_FAST_BIT BIT(4) #define ENABLE_CHARGING_BIT BIT(3) #define CHGR_CFG2_REG (CHGR_BASE + 0x51) #define CHG_EN_SRC_BIT BIT(7) #define CHG_EN_POLARITY_BIT BIT(6) #define CFG_REG (CHGR_BASE + 0x53) #define CHG_OPTION_PIN_TRIM_BIT BIT(7) #define BATN_SNS_CFG_BIT BIT(4) #define CFG_TAPER_DIS_AFVC_BIT BIT(3) #define BATFET_SHUTDOWN_CFG_BIT BIT(2) #define VDISCHG_EN_CFG_BIT BIT(1) #define VCHG_EN_CFG_BIT BIT(0) #define FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61) #define FAST_CHARGE_CURRENT_SETTING_MASK GENMASK(7, 0) #define CHGR_BATTOV_CFG_REG (CHGR_BASE + 0x70) #define BATTOV_SETTING_MASK GENMASK(7, 0) #define BARK_BITE_WDOG_PET_REG (MISC_BASE + 0x43) #define BARK_BITE_WDOG_PET_BIT BIT(0) #define WD_CFG_REG (MISC_BASE + 0x51) #define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7) #define BARK_WDOG_INT_EN_BIT BIT(6) #define BITE_WDOG_INT_EN_BIT BIT(5) #define WDOG_IRQ_SFT_BIT BIT(2) #define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1) #define WDOG_TIMER_EN_BIT BIT(0) #define SNARL_BARK_BITE_WD_CFG_REG (MISC_BASE + 0x53) #define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7) #define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4) #define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2) #define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0) struct smb_chg_param { const char *name; u16 reg; int min_u; int max_u; int step_u; }; struct smb_params { struct smb_chg_param fcc; struct smb_chg_param ov; }; static struct smb_params v1_params = { .fcc = { .name = "fast charge current", .reg = FAST_CHARGE_CURRENT_CFG_REG, .min_u = 0, .max_u = 6000000, .step_u = 25000, }, .ov = { .name = "battery over voltage", .reg = CHGR_BATTOV_CFG_REG, .min_u = 2450000, .max_u = 5000000, .step_u = 10000, }, }; struct smb_irq_info { const char *name; const irq_handler_t handler; const bool wake; int irq; }; struct smb1355 { struct device *dev; char *name; struct regmap *regmap; struct smb_params param; struct mutex write_lock; struct power_supply *parallel_psy; struct pmic_revid_data *pmic_rev_id; }; static bool is_secure(struct smb1355 *chip, int addr) { /* assume everything above 0xA0 is secure */ return (addr & 0xFF) >= 0xA0; } static int smb1355_read(struct smb1355 *chip, u16 addr, u8 *val) { unsigned int temp; int rc; rc = regmap_read(chip->regmap, addr, &temp); if (rc >= 0) *val = (u8)temp; return rc; } static int smb1355_masked_write(struct smb1355 *chip, u16 addr, u8 mask, u8 val) { int rc; mutex_lock(&chip->write_lock); if (is_secure(chip, addr)) { rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5); if (rc < 0) goto unlock; } rc = regmap_update_bits(chip->regmap, addr, mask, val); unlock: mutex_unlock(&chip->write_lock); return rc; } static int smb1355_write(struct smb1355 *chip, u16 addr, u8 val) { int rc; mutex_lock(&chip->write_lock); if (is_secure(chip, addr)) { rc = regmap_write(chip->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5); if (rc < 0) goto unlock; } rc = regmap_write(chip->regmap, addr, val); unlock: mutex_unlock(&chip->write_lock); return rc; } static int smb1355_set_charge_param(struct smb1355 *chip, struct smb_chg_param *param, int val_u) { int rc; u8 val_raw; if (val_u > param->max_u || val_u < param->min_u) { pr_err("%s: %d is out of range [%d, %d]\n", param->name, val_u, param->min_u, param->max_u); return -EINVAL; } val_raw = (val_u - param->min_u) / param->step_u; rc = smb1355_write(chip, param->reg, val_raw); if (rc < 0) { pr_err("%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", param->name, val_raw, param->reg, rc); return rc; } return rc; } static int smb1355_get_charge_param(struct smb1355 *chip, struct smb_chg_param *param, int *val_u) { int rc; u8 val_raw; rc = smb1355_read(chip, param->reg, &val_raw); if (rc < 0) { pr_err("%s: Couldn't read from 0x%04x rc=%d\n", param->name, param->reg, rc); return rc; } *val_u = val_raw * param->step_u + param->min_u; return rc; } static irqreturn_t smb1355_handle_chg_state_change(int irq, void *data) { struct smb1355 *chip = data; if (chip->parallel_psy) power_supply_changed(chip->parallel_psy); return IRQ_HANDLED; } static irqreturn_t smb1355_handle_wdog_bark(int irq, void *data) { struct smb1355 *chip = data; int rc; rc = smb1355_write(chip, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT); if (rc < 0) pr_err("Couldn't pet the dog rc=%d\n", rc); return IRQ_HANDLED; } /***************************** * PARALLEL PSY REGISTRATION * *****************************/ static enum power_supply_property smb1355_parallel_props[] = { POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_CHARGING_ENABLED, POWER_SUPPLY_PROP_PIN_ENABLED, POWER_SUPPLY_PROP_INPUT_SUSPEND, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_MODEL_NAME, }; static int smb1355_get_prop_batt_charge_type(struct smb1355 *chip, union power_supply_propval *val) { int rc; u8 stat; rc = smb1355_read(chip, BATTERY_STATUS_3_REG, &stat); if (rc < 0) { pr_err("Couldn't read SMB1355_BATTERY_STATUS_3 rc=%d\n", rc); return rc; } if (stat & ENABLE_CHARGING_BIT) { if (stat & BATT_GT_PRE_TO_FAST_BIT) val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; else val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; } else { val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; } return rc; } static int smb1355_get_parallel_charging(struct smb1355 *chip, int *disabled) { int rc; u8 cfg2; rc = smb1355_read(chip, CHGR_CFG2_REG, &cfg2); if (rc < 0) { pr_err("Couldn't read en_cmg_reg rc=%d\n", rc); return rc; } if (cfg2 & CHG_EN_SRC_BIT) *disabled = 0; else *disabled = 1; return 0; } static int smb1355_parallel_get_prop(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { struct smb1355 *chip = power_supply_get_drvdata(psy); u8 stat; int rc = 0; switch (prop) { case POWER_SUPPLY_PROP_CHARGE_TYPE: rc = smb1355_get_prop_batt_charge_type(chip, val); break; case POWER_SUPPLY_PROP_CHARGING_ENABLED: rc = smb1355_read(chip, BATTERY_STATUS_3_REG, &stat); if (rc >= 0) val->intval = (bool)(stat & ENABLE_CHARGING_BIT); break; case POWER_SUPPLY_PROP_PIN_ENABLED: rc = smb1355_read(chip, BATTERY_STATUS_2_REG, &stat); if (rc >= 0) val->intval = !(stat & DISABLE_CHARGING_BIT); break; case POWER_SUPPLY_PROP_INPUT_SUSPEND: rc = smb1355_get_parallel_charging(chip, &val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: rc = smb1355_get_charge_param(chip, &chip->param.ov, &val->intval); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: rc = smb1355_get_charge_param(chip, &chip->param.fcc, &val->intval); break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = chip->name; break; case POWER_SUPPLY_PROP_PARALLEL_MODE: val->intval = POWER_SUPPLY_PL_USBMID_USBMID; break; default: pr_err_ratelimited("parallel psy get prop %d not supported\n", prop); return -EINVAL; } if (rc < 0) { pr_debug("Couldn't get prop %d rc = %d\n", prop, rc); return -ENODATA; } return rc; } static int smb1355_set_parallel_charging(struct smb1355 *chip, bool disable) { int rc; rc = smb1355_masked_write(chip, WD_CFG_REG, WDOG_TIMER_EN_BIT, disable ? 0 : WDOG_TIMER_EN_BIT); if (rc < 0) { pr_err("Couldn't %s watchdog rc=%d\n", disable ? "disable" : "enable", rc); disable = true; } /* * Configure charge enable for high polarity and * When disabling charging set it to cmd register control(cmd bit=0) * When enabling charging set it to pin control */ rc = smb1355_masked_write(chip, CHGR_CFG2_REG, CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, disable ? 0 : CHG_EN_SRC_BIT); if (rc < 0) { pr_err("Couldn't configure charge enable source rc=%d\n", rc); return rc; } return 0; } static int smb1355_parallel_set_prop(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { struct smb1355 *chip = power_supply_get_drvdata(psy); int rc = 0; switch (prop) { case POWER_SUPPLY_PROP_INPUT_SUSPEND: rc = smb1355_set_parallel_charging(chip, (bool)val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: rc = smb1355_set_charge_param(chip, &chip->param.ov, val->intval); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: rc = smb1355_set_charge_param(chip, &chip->param.fcc, val->intval); break; default: pr_debug("parallel power supply set prop %d not supported\n", prop); return -EINVAL; } return rc; } static int smb1355_parallel_prop_is_writeable(struct power_supply *psy, enum power_supply_property prop) { return 0; } static struct power_supply_desc parallel_psy_desc = { .name = "parallel", .type = POWER_SUPPLY_TYPE_PARALLEL, .properties = smb1355_parallel_props, .num_properties = ARRAY_SIZE(smb1355_parallel_props), .get_property = smb1355_parallel_get_prop, .set_property = smb1355_parallel_set_prop, .property_is_writeable = smb1355_parallel_prop_is_writeable, }; static int smb1355_init_parallel_psy(struct smb1355 *chip) { struct power_supply_config parallel_cfg = {}; parallel_cfg.drv_data = chip; parallel_cfg.of_node = chip->dev->of_node; /* change to smb1355's property list */ parallel_psy_desc.properties = smb1355_parallel_props; parallel_psy_desc.num_properties = ARRAY_SIZE(smb1355_parallel_props); chip->parallel_psy = devm_power_supply_register(chip->dev, ¶llel_psy_desc, ¶llel_cfg); if (IS_ERR(chip->parallel_psy)) { pr_err("Couldn't register parallel power supply\n"); return PTR_ERR(chip->parallel_psy); } return 0; } /*************************** * HARDWARE INITIALIZATION * ***************************/ static int smb1355_init_hw(struct smb1355 *chip) { int rc; /* enable watchdog bark and bite interrupts, and disable the watchdog */ rc = smb1355_masked_write(chip, WD_CFG_REG, WDOG_TIMER_EN_BIT | WDOG_TIMER_EN_ON_PLUGIN_BIT | BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT, BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT); if (rc < 0) { pr_err("Couldn't configure the watchdog rc=%d\n", rc); return rc; } /* disable charging when watchdog bites */ rc = smb1355_masked_write(chip, SNARL_BARK_BITE_WD_CFG_REG, BITE_WDOG_DISABLE_CHARGING_CFG_BIT, BITE_WDOG_DISABLE_CHARGING_CFG_BIT); if (rc < 0) { pr_err("Couldn't configure the watchdog bite rc=%d\n", rc); return rc; } /* disable parallel charging path */ rc = smb1355_set_parallel_charging(chip, true); if (rc < 0) { pr_err("Couldn't disable parallel path rc=%d\n", rc); return rc; } /* initialize FCC to 0 */ rc = smb1355_set_charge_param(chip, &chip->param.fcc, 0); if (rc < 0) { pr_err("Couldn't set 0 FCC rc=%d\n", rc); return rc; } /* enable parallel current sensing */ rc = smb1355_masked_write(chip, CFG_REG, VCHG_EN_CFG_BIT, VCHG_EN_CFG_BIT); if (rc < 0) { pr_err("Couldn't enable parallel current sensing rc=%d\n", rc); return rc; } return 0; } /************************** * INTERRUPT REGISTRATION * **************************/ static struct smb_irq_info smb1355_irqs[] = { [0] = { .name = "wdog-bark", .handler = smb1355_handle_wdog_bark, }, [1] = { .name = "chg-state-change", .handler = smb1355_handle_chg_state_change, .wake = true, }, }; static int smb1355_get_irq_index_byname(const char *irq_name) { int i; for (i = 0; i < ARRAY_SIZE(smb1355_irqs); i++) { if (strcmp(smb1355_irqs[i].name, irq_name) == 0) return i; } return -ENOENT; } static int smb1355_request_interrupt(struct smb1355 *chip, struct device_node *node, const char *irq_name) { int rc = 0, irq, irq_index; irq = of_irq_get_byname(node, irq_name); if (irq < 0) { pr_err("Couldn't get irq %s byname\n", irq_name); return irq; } irq_index = smb1355_get_irq_index_byname(irq_name); if (irq_index < 0) { pr_err("%s is not a defined irq\n", irq_name); return irq_index; } if (!smb1355_irqs[irq_index].handler) return 0; rc = devm_request_threaded_irq(chip->dev, irq, NULL, smb1355_irqs[irq_index].handler, IRQF_ONESHOT, irq_name, chip); if (rc < 0) { pr_err("Couldn't request irq %d rc=%d\n", irq, rc); return rc; } if (smb1355_irqs[irq_index].wake) enable_irq_wake(irq); return rc; } static int smb1355_request_interrupts(struct smb1355 *chip) { struct device_node *node = chip->dev->of_node; struct device_node *child; int rc = 0; const char *name; struct property *prop; for_each_available_child_of_node(node, child) { of_property_for_each_string(child, "interrupt-names", prop, name) { rc = smb1355_request_interrupt(chip, child, name); if (rc < 0) { pr_err("Couldn't request interrupt %s rc=%d\n", name, rc); return rc; } } } return rc; } /********* * PROBE * *********/ static const struct of_device_id match_table[] = { { .compatible = "qcom,smb1355", }, { }, }; static int smb1355_probe(struct platform_device *pdev) { struct smb1355 *chip; const struct of_device_id *id; int rc = 0; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; chip->param = v1_params; chip->name = "smb1355"; mutex_init(&chip->write_lock); chip->regmap = dev_get_regmap(chip->dev->parent, NULL); if (!chip->regmap) { pr_err("parent regmap is missing\n"); return -EINVAL; } id = of_match_device(of_match_ptr(match_table), chip->dev); if (!id) { pr_err("Couldn't find a matching device\n"); return -ENODEV; } platform_set_drvdata(pdev, chip); rc = smb1355_init_hw(chip); if (rc < 0) { pr_err("Couldn't initialize hardware rc=%d\n", rc); goto cleanup; } rc = smb1355_init_parallel_psy(chip); if (rc < 0) { pr_err("Couldn't initialize parallel psy rc=%d\n", rc); goto cleanup; } rc = smb1355_request_interrupts(chip); if (rc < 0) { pr_err("Couldn't request interrupts rc=%d\n", rc); goto cleanup; } pr_info("%s probed successfully\n", chip->name); return rc; cleanup: platform_set_drvdata(pdev, NULL); return rc; } static int smb1355_remove(struct platform_device *pdev) { platform_set_drvdata(pdev, NULL); return 0; } static struct platform_driver smb1355_driver = { .driver = { .name = "qcom,smb1355-charger", .owner = THIS_MODULE, .of_match_table = match_table, }, .probe = smb1355_probe, .remove = smb1355_remove, }; module_platform_driver(smb1355_driver); MODULE_DESCRIPTION("QPNP SMB1355 Charger Driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/power/supply/qcom/smb1355-charger.txt 0 → 100644 +73 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. SMB1355 Charger Specific Bindings SMB1355 slave charger is paired with QTI family of standalone chargers to enable a high current, low profile Li+ battery charging system. The device provides 28V DC withstand, wide operating input range of 3.8 to 14.2V for standard 5V USB inputs as well as a wide variety of HVDCP Travel Adapters and is compatible with QTI's Quick Charge technology. ======================= Required Node Structure ======================= SMB1355 Charger must be described in two levels of device nodes. ================================== First Level Node - SMB1355 Charger ================================== Charger specific properties: - compatible Usage: required Value type: <string> Definition: "qcom,smb1355". - qcom,pmic-revid Usage: required Value type: phandle Definition: Should specify the phandle of SMB's revid module. This is used to identify the SMB subtype. ================================================ Second Level Nodes - SMB1355 Charger Peripherals ================================================ Peripheral specific properties: - reg Usage: required Value type: <prop-encoded-array> Definition: Address and size of the peripheral's register block. - interrupts Usage: required Value type: <prop-encoded-array> Definition: Peripheral interrupt specifier. - interrupt-names Usage: required Value type: <stringlist> Definition: Interrupt names. This list must match up 1-to-1 with the interrupts specified in the 'interrupts' property. ======= Example ======= smb1355_charger: qcom,smb1355-charger { compatible = "qcom,smb1355"; #address-cells = <1>; #size-cells = <1>; qcom,chgr@1000 { reg = <0x1000 0x100>; interrupts = <0x10 0x1 IRQ_TYPE_EDGE_BOTH>; interrupt-names = "chg-state-change"; }; qcom,chgr-misc@1600 { reg = <0x1600 0x100>; interrupts = <0x16 0x1 IRQ_TYPE_EDGE_BOTH>; interrupt-names = "wdog-bark"; }; };
drivers/power/supply/qcom/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,16 @@ config SMB135X_CHARGER The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. config SMB1355_SLAVE_CHARGER tristate "SMB1355 Slave Battery Charger" depends on MFD_I2C_PMIC help Say Y to include support for SMB1355 Battery Charger. SMB1355 is a single phase 5A battery charger. The driver supports charger enable/disable. The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. config SMB1351_USB_CHARGER tristate "smb1351 usb charger (with VBUS detection)" depends on I2C Loading
drivers/power/supply/qcom/Makefile +1 −0 Original line number Diff line number Diff line obj-$(CONFIG_QPNP_FG_GEN3) += qpnp-fg-gen3.o fg-memif.o fg-util.o obj-$(CONFIG_SMB135X_CHARGER) += smb135x-charger.o pmic-voter.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o obj-$(CONFIG_QPNP_SMB2) += qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o battery.o obj-$(CONFIG_SMB138X_CHARGER) += smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o Loading
drivers/power/supply/qcom/smb1355-charger.c 0 → 100644 +675 −0 Original line number Diff line number Diff line /* Copyright (c) 2016-2017 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 * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define pr_fmt(fmt) "SMB1355: %s: " fmt, __func__ #include <linux/device.h> #include <linux/regmap.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/qpnp/qpnp-revid.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/regulator/of_regulator.h> #include <linux/power_supply.h> #include <linux/pmic-voter.h> #define SMB1355_DEFAULT_FCC_UA 1000000 /* SMB1355 registers, different than mentioned in smb-reg.h */ #define CHGR_BASE 0x1000 #define BATIF_BASE 0x1200 #define USBIN_BASE 0x1300 #define MISC_BASE 0x1600 #define BATTERY_STATUS_2_REG (CHGR_BASE + 0x0B) #define DISABLE_CHARGING_BIT BIT(3) #define BATTERY_STATUS_3_REG (CHGR_BASE + 0x0C) #define BATT_GT_PRE_TO_FAST_BIT BIT(4) #define ENABLE_CHARGING_BIT BIT(3) #define CHGR_CFG2_REG (CHGR_BASE + 0x51) #define CHG_EN_SRC_BIT BIT(7) #define CHG_EN_POLARITY_BIT BIT(6) #define CFG_REG (CHGR_BASE + 0x53) #define CHG_OPTION_PIN_TRIM_BIT BIT(7) #define BATN_SNS_CFG_BIT BIT(4) #define CFG_TAPER_DIS_AFVC_BIT BIT(3) #define BATFET_SHUTDOWN_CFG_BIT BIT(2) #define VDISCHG_EN_CFG_BIT BIT(1) #define VCHG_EN_CFG_BIT BIT(0) #define FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61) #define FAST_CHARGE_CURRENT_SETTING_MASK GENMASK(7, 0) #define CHGR_BATTOV_CFG_REG (CHGR_BASE + 0x70) #define BATTOV_SETTING_MASK GENMASK(7, 0) #define BARK_BITE_WDOG_PET_REG (MISC_BASE + 0x43) #define BARK_BITE_WDOG_PET_BIT BIT(0) #define WD_CFG_REG (MISC_BASE + 0x51) #define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7) #define BARK_WDOG_INT_EN_BIT BIT(6) #define BITE_WDOG_INT_EN_BIT BIT(5) #define WDOG_IRQ_SFT_BIT BIT(2) #define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1) #define WDOG_TIMER_EN_BIT BIT(0) #define SNARL_BARK_BITE_WD_CFG_REG (MISC_BASE + 0x53) #define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7) #define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4) #define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2) #define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0) struct smb_chg_param { const char *name; u16 reg; int min_u; int max_u; int step_u; }; struct smb_params { struct smb_chg_param fcc; struct smb_chg_param ov; }; static struct smb_params v1_params = { .fcc = { .name = "fast charge current", .reg = FAST_CHARGE_CURRENT_CFG_REG, .min_u = 0, .max_u = 6000000, .step_u = 25000, }, .ov = { .name = "battery over voltage", .reg = CHGR_BATTOV_CFG_REG, .min_u = 2450000, .max_u = 5000000, .step_u = 10000, }, }; struct smb_irq_info { const char *name; const irq_handler_t handler; const bool wake; int irq; }; struct smb1355 { struct device *dev; char *name; struct regmap *regmap; struct smb_params param; struct mutex write_lock; struct power_supply *parallel_psy; struct pmic_revid_data *pmic_rev_id; }; static bool is_secure(struct smb1355 *chip, int addr) { /* assume everything above 0xA0 is secure */ return (addr & 0xFF) >= 0xA0; } static int smb1355_read(struct smb1355 *chip, u16 addr, u8 *val) { unsigned int temp; int rc; rc = regmap_read(chip->regmap, addr, &temp); if (rc >= 0) *val = (u8)temp; return rc; } static int smb1355_masked_write(struct smb1355 *chip, u16 addr, u8 mask, u8 val) { int rc; mutex_lock(&chip->write_lock); if (is_secure(chip, addr)) { rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5); if (rc < 0) goto unlock; } rc = regmap_update_bits(chip->regmap, addr, mask, val); unlock: mutex_unlock(&chip->write_lock); return rc; } static int smb1355_write(struct smb1355 *chip, u16 addr, u8 val) { int rc; mutex_lock(&chip->write_lock); if (is_secure(chip, addr)) { rc = regmap_write(chip->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5); if (rc < 0) goto unlock; } rc = regmap_write(chip->regmap, addr, val); unlock: mutex_unlock(&chip->write_lock); return rc; } static int smb1355_set_charge_param(struct smb1355 *chip, struct smb_chg_param *param, int val_u) { int rc; u8 val_raw; if (val_u > param->max_u || val_u < param->min_u) { pr_err("%s: %d is out of range [%d, %d]\n", param->name, val_u, param->min_u, param->max_u); return -EINVAL; } val_raw = (val_u - param->min_u) / param->step_u; rc = smb1355_write(chip, param->reg, val_raw); if (rc < 0) { pr_err("%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", param->name, val_raw, param->reg, rc); return rc; } return rc; } static int smb1355_get_charge_param(struct smb1355 *chip, struct smb_chg_param *param, int *val_u) { int rc; u8 val_raw; rc = smb1355_read(chip, param->reg, &val_raw); if (rc < 0) { pr_err("%s: Couldn't read from 0x%04x rc=%d\n", param->name, param->reg, rc); return rc; } *val_u = val_raw * param->step_u + param->min_u; return rc; } static irqreturn_t smb1355_handle_chg_state_change(int irq, void *data) { struct smb1355 *chip = data; if (chip->parallel_psy) power_supply_changed(chip->parallel_psy); return IRQ_HANDLED; } static irqreturn_t smb1355_handle_wdog_bark(int irq, void *data) { struct smb1355 *chip = data; int rc; rc = smb1355_write(chip, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT); if (rc < 0) pr_err("Couldn't pet the dog rc=%d\n", rc); return IRQ_HANDLED; } /***************************** * PARALLEL PSY REGISTRATION * *****************************/ static enum power_supply_property smb1355_parallel_props[] = { POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_CHARGING_ENABLED, POWER_SUPPLY_PROP_PIN_ENABLED, POWER_SUPPLY_PROP_INPUT_SUSPEND, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_MODEL_NAME, }; static int smb1355_get_prop_batt_charge_type(struct smb1355 *chip, union power_supply_propval *val) { int rc; u8 stat; rc = smb1355_read(chip, BATTERY_STATUS_3_REG, &stat); if (rc < 0) { pr_err("Couldn't read SMB1355_BATTERY_STATUS_3 rc=%d\n", rc); return rc; } if (stat & ENABLE_CHARGING_BIT) { if (stat & BATT_GT_PRE_TO_FAST_BIT) val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; else val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; } else { val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; } return rc; } static int smb1355_get_parallel_charging(struct smb1355 *chip, int *disabled) { int rc; u8 cfg2; rc = smb1355_read(chip, CHGR_CFG2_REG, &cfg2); if (rc < 0) { pr_err("Couldn't read en_cmg_reg rc=%d\n", rc); return rc; } if (cfg2 & CHG_EN_SRC_BIT) *disabled = 0; else *disabled = 1; return 0; } static int smb1355_parallel_get_prop(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { struct smb1355 *chip = power_supply_get_drvdata(psy); u8 stat; int rc = 0; switch (prop) { case POWER_SUPPLY_PROP_CHARGE_TYPE: rc = smb1355_get_prop_batt_charge_type(chip, val); break; case POWER_SUPPLY_PROP_CHARGING_ENABLED: rc = smb1355_read(chip, BATTERY_STATUS_3_REG, &stat); if (rc >= 0) val->intval = (bool)(stat & ENABLE_CHARGING_BIT); break; case POWER_SUPPLY_PROP_PIN_ENABLED: rc = smb1355_read(chip, BATTERY_STATUS_2_REG, &stat); if (rc >= 0) val->intval = !(stat & DISABLE_CHARGING_BIT); break; case POWER_SUPPLY_PROP_INPUT_SUSPEND: rc = smb1355_get_parallel_charging(chip, &val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: rc = smb1355_get_charge_param(chip, &chip->param.ov, &val->intval); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: rc = smb1355_get_charge_param(chip, &chip->param.fcc, &val->intval); break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = chip->name; break; case POWER_SUPPLY_PROP_PARALLEL_MODE: val->intval = POWER_SUPPLY_PL_USBMID_USBMID; break; default: pr_err_ratelimited("parallel psy get prop %d not supported\n", prop); return -EINVAL; } if (rc < 0) { pr_debug("Couldn't get prop %d rc = %d\n", prop, rc); return -ENODATA; } return rc; } static int smb1355_set_parallel_charging(struct smb1355 *chip, bool disable) { int rc; rc = smb1355_masked_write(chip, WD_CFG_REG, WDOG_TIMER_EN_BIT, disable ? 0 : WDOG_TIMER_EN_BIT); if (rc < 0) { pr_err("Couldn't %s watchdog rc=%d\n", disable ? "disable" : "enable", rc); disable = true; } /* * Configure charge enable for high polarity and * When disabling charging set it to cmd register control(cmd bit=0) * When enabling charging set it to pin control */ rc = smb1355_masked_write(chip, CHGR_CFG2_REG, CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, disable ? 0 : CHG_EN_SRC_BIT); if (rc < 0) { pr_err("Couldn't configure charge enable source rc=%d\n", rc); return rc; } return 0; } static int smb1355_parallel_set_prop(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { struct smb1355 *chip = power_supply_get_drvdata(psy); int rc = 0; switch (prop) { case POWER_SUPPLY_PROP_INPUT_SUSPEND: rc = smb1355_set_parallel_charging(chip, (bool)val->intval); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: rc = smb1355_set_charge_param(chip, &chip->param.ov, val->intval); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: rc = smb1355_set_charge_param(chip, &chip->param.fcc, val->intval); break; default: pr_debug("parallel power supply set prop %d not supported\n", prop); return -EINVAL; } return rc; } static int smb1355_parallel_prop_is_writeable(struct power_supply *psy, enum power_supply_property prop) { return 0; } static struct power_supply_desc parallel_psy_desc = { .name = "parallel", .type = POWER_SUPPLY_TYPE_PARALLEL, .properties = smb1355_parallel_props, .num_properties = ARRAY_SIZE(smb1355_parallel_props), .get_property = smb1355_parallel_get_prop, .set_property = smb1355_parallel_set_prop, .property_is_writeable = smb1355_parallel_prop_is_writeable, }; static int smb1355_init_parallel_psy(struct smb1355 *chip) { struct power_supply_config parallel_cfg = {}; parallel_cfg.drv_data = chip; parallel_cfg.of_node = chip->dev->of_node; /* change to smb1355's property list */ parallel_psy_desc.properties = smb1355_parallel_props; parallel_psy_desc.num_properties = ARRAY_SIZE(smb1355_parallel_props); chip->parallel_psy = devm_power_supply_register(chip->dev, ¶llel_psy_desc, ¶llel_cfg); if (IS_ERR(chip->parallel_psy)) { pr_err("Couldn't register parallel power supply\n"); return PTR_ERR(chip->parallel_psy); } return 0; } /*************************** * HARDWARE INITIALIZATION * ***************************/ static int smb1355_init_hw(struct smb1355 *chip) { int rc; /* enable watchdog bark and bite interrupts, and disable the watchdog */ rc = smb1355_masked_write(chip, WD_CFG_REG, WDOG_TIMER_EN_BIT | WDOG_TIMER_EN_ON_PLUGIN_BIT | BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT, BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT); if (rc < 0) { pr_err("Couldn't configure the watchdog rc=%d\n", rc); return rc; } /* disable charging when watchdog bites */ rc = smb1355_masked_write(chip, SNARL_BARK_BITE_WD_CFG_REG, BITE_WDOG_DISABLE_CHARGING_CFG_BIT, BITE_WDOG_DISABLE_CHARGING_CFG_BIT); if (rc < 0) { pr_err("Couldn't configure the watchdog bite rc=%d\n", rc); return rc; } /* disable parallel charging path */ rc = smb1355_set_parallel_charging(chip, true); if (rc < 0) { pr_err("Couldn't disable parallel path rc=%d\n", rc); return rc; } /* initialize FCC to 0 */ rc = smb1355_set_charge_param(chip, &chip->param.fcc, 0); if (rc < 0) { pr_err("Couldn't set 0 FCC rc=%d\n", rc); return rc; } /* enable parallel current sensing */ rc = smb1355_masked_write(chip, CFG_REG, VCHG_EN_CFG_BIT, VCHG_EN_CFG_BIT); if (rc < 0) { pr_err("Couldn't enable parallel current sensing rc=%d\n", rc); return rc; } return 0; } /************************** * INTERRUPT REGISTRATION * **************************/ static struct smb_irq_info smb1355_irqs[] = { [0] = { .name = "wdog-bark", .handler = smb1355_handle_wdog_bark, }, [1] = { .name = "chg-state-change", .handler = smb1355_handle_chg_state_change, .wake = true, }, }; static int smb1355_get_irq_index_byname(const char *irq_name) { int i; for (i = 0; i < ARRAY_SIZE(smb1355_irqs); i++) { if (strcmp(smb1355_irqs[i].name, irq_name) == 0) return i; } return -ENOENT; } static int smb1355_request_interrupt(struct smb1355 *chip, struct device_node *node, const char *irq_name) { int rc = 0, irq, irq_index; irq = of_irq_get_byname(node, irq_name); if (irq < 0) { pr_err("Couldn't get irq %s byname\n", irq_name); return irq; } irq_index = smb1355_get_irq_index_byname(irq_name); if (irq_index < 0) { pr_err("%s is not a defined irq\n", irq_name); return irq_index; } if (!smb1355_irqs[irq_index].handler) return 0; rc = devm_request_threaded_irq(chip->dev, irq, NULL, smb1355_irqs[irq_index].handler, IRQF_ONESHOT, irq_name, chip); if (rc < 0) { pr_err("Couldn't request irq %d rc=%d\n", irq, rc); return rc; } if (smb1355_irqs[irq_index].wake) enable_irq_wake(irq); return rc; } static int smb1355_request_interrupts(struct smb1355 *chip) { struct device_node *node = chip->dev->of_node; struct device_node *child; int rc = 0; const char *name; struct property *prop; for_each_available_child_of_node(node, child) { of_property_for_each_string(child, "interrupt-names", prop, name) { rc = smb1355_request_interrupt(chip, child, name); if (rc < 0) { pr_err("Couldn't request interrupt %s rc=%d\n", name, rc); return rc; } } } return rc; } /********* * PROBE * *********/ static const struct of_device_id match_table[] = { { .compatible = "qcom,smb1355", }, { }, }; static int smb1355_probe(struct platform_device *pdev) { struct smb1355 *chip; const struct of_device_id *id; int rc = 0; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; chip->param = v1_params; chip->name = "smb1355"; mutex_init(&chip->write_lock); chip->regmap = dev_get_regmap(chip->dev->parent, NULL); if (!chip->regmap) { pr_err("parent regmap is missing\n"); return -EINVAL; } id = of_match_device(of_match_ptr(match_table), chip->dev); if (!id) { pr_err("Couldn't find a matching device\n"); return -ENODEV; } platform_set_drvdata(pdev, chip); rc = smb1355_init_hw(chip); if (rc < 0) { pr_err("Couldn't initialize hardware rc=%d\n", rc); goto cleanup; } rc = smb1355_init_parallel_psy(chip); if (rc < 0) { pr_err("Couldn't initialize parallel psy rc=%d\n", rc); goto cleanup; } rc = smb1355_request_interrupts(chip); if (rc < 0) { pr_err("Couldn't request interrupts rc=%d\n", rc); goto cleanup; } pr_info("%s probed successfully\n", chip->name); return rc; cleanup: platform_set_drvdata(pdev, NULL); return rc; } static int smb1355_remove(struct platform_device *pdev) { platform_set_drvdata(pdev, NULL); return 0; } static struct platform_driver smb1355_driver = { .driver = { .name = "qcom,smb1355-charger", .owner = THIS_MODULE, .of_match_table = match_table, }, .probe = smb1355_probe, .remove = smb1355_remove, }; module_platform_driver(smb1355_driver); MODULE_DESCRIPTION("QPNP SMB1355 Charger Driver"); MODULE_LICENSE("GPL v2");