Loading Documentation/devicetree/bindings/power/supply/qcom/smb1390-charger.txt 0 → 100644 +76 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. SMB1390 Charger Specific Bindings SMB1390 charge pump is paired with QTI family of standalone chargers to enable a high current, high efficiency Li+ battery charging system. ======================= Required Node Structure ======================= SMB1390 Charger must be described in two levels of device nodes. ================================== First Level Node - SMB1390 Charger ================================== Charger specific properties: - compatible Usage: required Value type: <string> Definition: "qcom,smb1390-charger". - 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. - io-channels - io-channel-names Usage: required Value type: <phandle> Definition: For details about IIO bindings see: Documentation/devicetree/bindings/iio/iio-bindings.txt ================================================ Second Level Nodes - SMB1390 Charger Peripherals ================================================ Peripheral specific properties: - 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 ======= smb1390_charger: qcom,charge_pump { compatible = "qcom,smb1390-charger"; qcom,pmic-revid = <&smb1390_revid>; interrupt-parent = <&smb1390>; status = "disabled"; io-channels = <&pm855b_vadc ADC_AMUX_THM2>; io-channel-names = "cp_die_temp"; qcom,core { interrupts = <0x10 0x0 IRQ_TYPE_EDGE_RISING>, <0x10 0x1 IRQ_TYPE_EDGE_RISING>, <0x10 0x2 IRQ_TYPE_EDGE_RISING>, <0x10 0x3 IRQ_TYPE_EDGE_RISING>, <0x10 0x4 IRQ_TYPE_EDGE_RISING>; interrupt-names = "switcher-off-window", "switcher-off-fault", "vph-ov-soft", "ilim", "temp-alarm"; }; }; drivers/power/supply/qcom/Kconfig +8 −0 Original line number Diff line number Diff line Loading @@ -75,4 +75,12 @@ config QPNP_QNOVO module. It also allows userspace code to read diagnostics of voltage and current measured during certain phases of the pulses. config SMB1390_CHARGE_PUMP tristate "SMB1390 Charge Pump" depends on MFD_I2C_PMIC help Say Y to include support for SMB1390 Charge Pump. SMB1390 is a div2 charge pump capable of delivering 6A charge current with very high efficiency. endmenu drivers/power/supply/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -5,3 +5,4 @@ obj-$(CONFIG_QPNP_SMB2) += step-chg-jeita.o battery.o qpnp-smb2.o smb-lib.o pmi obj-$(CONFIG_SMB138X_CHARGER) += step-chg-jeita.o smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o obj-$(CONFIG_QPNP_QNOVO) += qpnp-qnovo.o battery.o obj-$(CONFIG_QPNP_SMB5) += step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.o obj-$(CONFIG_SMB1390_CHARGE_PUMP) += smb1390-charger.o pmic-voter.o drivers/power/supply/qcom/smb1390-charger.c 0 → 100644 +767 −0 Original line number Diff line number Diff line /* Copyright (c) 2017-2018 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) "SMB1390: %s: " fmt, __func__ #include <linux/device.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/pmic-voter.h> #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/iio/consumer.h> #define CORE_STATUS1_REG 0x1006 #define WIN_OV_BIT BIT(0) #define WIN_UV_BIT BIT(1) #define EN_PIN_OUT_BIT BIT(2) #define LCM_AUTO_BIT BIT(3) #define LCM_PIN_BIT BIT(4) #define ILIM_BIT BIT(5) #define TEMP_ALARM_BIT BIT(6) #define VPH_OV_SOFT_BIT BIT(7) #define CORE_STATUS2_REG 0x1007 #define SWITCHER_HOLD_OFF_BIT BIT(0) #define VPH_OV_HARD_BIT BIT(1) #define TSD_BIT BIT(2) #define IREV_BIT BIT(3) #define IOC_BIT BIT(4) #define VIN_UV_BIT BIT(5) #define VIN_OV_BIT BIT(6) #define EN_PIN_OUT2_BIT BIT(7) #define CORE_STATUS3_REG 0x1008 #define EN_SL_BIT BIT(0) #define IIN_REF_SS_DONE_BIT BIT(1) #define FLYCAP_SS_DONE_BIT BIT(2) #define SL_DETECTED_BIT BIT(3) #define CORE_INT_RT_STS_REG 0x1010 #define SWITCHER_OFF_WINDOW_STS_BIT BIT(0) #define SWITCHER_OFF_FAULT_STS_BIT BIT(1) #define TSD_STS_BIT BIT(2) #define IREV_STS_BIT BIT(3) #define VPH_OV_HARD_STS_BIT BIT(4) #define VPH_OV_SOFT_STS_BIT BIT(5) #define ILIM_STS_BIT BIT(6) #define TEMP_ALARM_STS_BIT BIT(7) #define CORE_CONTROL1_REG 0x1020 #define CMD_EN_SWITCHER_BIT BIT(0) #define CMD_EN_SL_BIT BIT(1) #define CORE_FTRIM_ILIM_REG 0x1030 #define CFG_ILIM_MASK GENMASK(4, 0) #define CP_VOTER "CP_VOTER" #define USER_VOTER "USER_VOTER" #define ILIM_VOTER "ILIM_VOTER" #define FCC_VOTER "FCC_VOTER" #define ICL_VOTER "ICL_VOTER" #define USB_VOTER "USB_VOTER" enum { SWITCHER_OFF_WINDOW_IRQ = 0, SWITCHER_OFF_FAULT_IRQ, TSD_IRQ, IREV_IRQ, VPH_OV_HARD_IRQ, VPH_OV_SOFT_IRQ, ILIM_IRQ, TEMP_ALARM_IRQ, NUM_IRQS, }; struct smb1390_iio { struct iio_channel *die_temp_chan; }; struct smb1390 { struct device *dev; struct regmap *regmap; struct notifier_block nb; struct class cp_class; /* work structs */ struct work_struct status_change_work; struct work_struct taper_work; /* mutexes */ spinlock_t status_change_lock; /* votables */ struct votable *disable_votable; struct votable *ilim_votable; struct votable *pl_disable_votable; struct votable *fcc_votable; struct votable *hvdcp_hw_inov_dis_votable; /* power supplies */ struct power_supply *usb_psy; struct power_supply *batt_psy; int irqs[NUM_IRQS]; bool status_change_running; bool taper_work_running; struct smb1390_iio iio; }; struct smb_irq { const char *name; const irq_handler_t handler; const bool wake; }; static const struct smb_irq smb_irqs[]; static int smb1390_read(struct smb1390 *chip, int reg, int *val) { int rc; rc = regmap_read(chip->regmap, reg, val); if (rc < 0) pr_err("Couldn't read 0x%04x\n", reg); return rc; } static int smb1390_masked_write(struct smb1390 *chip, int reg, int mask, int val) { int rc; pr_debug("Writing 0x%02x to 0x%04x with mask 0x%02x\n", val, reg, mask); rc = regmap_update_bits(chip->regmap, reg, mask, val); if (rc < 0) pr_err("Couldn't write 0x%02x to 0x%04x with mask 0x%02x\n", val, reg, mask); return rc; } static bool is_psy_voter_available(struct smb1390 *chip) { if (!chip->batt_psy) { chip->batt_psy = power_supply_get_by_name("battery"); if (!chip->batt_psy) { pr_debug("Couldn't find battery psy\n"); return false; } } if (!chip->usb_psy) { chip->usb_psy = power_supply_get_by_name("usb"); if (!chip->usb_psy) { pr_debug("Couldn't find usb psy\n"); return false; } } if (!chip->fcc_votable) { chip->fcc_votable = find_votable("FCC"); if (!chip->fcc_votable) { pr_debug("Couldn't find FCC votable\n"); return false; } } if (!chip->pl_disable_votable) { chip->pl_disable_votable = find_votable("PL_DISABLE"); if (!chip->pl_disable_votable) { pr_debug("Couldn't find PL_DISABLE votable\n"); return false; } } if (!chip->hvdcp_hw_inov_dis_votable) { chip->hvdcp_hw_inov_dis_votable = find_votable("HVDCP_HW_INOV_DIS"); if (!chip->hvdcp_hw_inov_dis_votable) { pr_debug("Couldn't find HVDCP_HW_INOV_DIS votable\n"); return false; } } return true; } static irqreturn_t default_irq_handler(int irq, void *data) { struct smb1390 *chip = data; int i; for (i = 0; i < NUM_IRQS; ++i) { if (irq == chip->irqs[i]) pr_debug("%s IRQ triggered\n", smb_irqs[i].name); } kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return IRQ_HANDLED; } static const struct smb_irq smb_irqs[] = { [SWITCHER_OFF_WINDOW_IRQ] = { .name = "switcher-off-window", .handler = default_irq_handler, .wake = true, }, [SWITCHER_OFF_FAULT_IRQ] = { .name = "switcher-off-fault", .handler = default_irq_handler, .wake = true, }, [TSD_IRQ] = { .name = "tsd-fault", .handler = default_irq_handler, .wake = true, }, [IREV_IRQ] = { .name = "irev-fault", .handler = default_irq_handler, .wake = true, }, [VPH_OV_HARD_IRQ] = { .name = "vph-ov-hard", .handler = default_irq_handler, .wake = true, }, [VPH_OV_SOFT_IRQ] = { .name = "vph-ov-soft", .handler = default_irq_handler, .wake = true, }, [ILIM_IRQ] = { .name = "ilim", .handler = default_irq_handler, .wake = true, }, [TEMP_ALARM_IRQ] = { .name = "temp-alarm", .handler = default_irq_handler, .wake = true, }, }; /* SYSFS functions for reporting smb1390 charge pump state */ static ssize_t stat1_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); int rc, val; rc = smb1390_read(chip, CORE_STATUS1_REG, &val); if (rc < 0) return -EINVAL; return snprintf(buf, PAGE_SIZE, "%x\n", val); } static CLASS_ATTR_RO(stat1); static ssize_t stat2_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); int rc, val; rc = smb1390_read(chip, CORE_STATUS2_REG, &val); if (rc < 0) return -EINVAL; return snprintf(buf, PAGE_SIZE, "%x\n", val); } static CLASS_ATTR_RO(stat2); static ssize_t enable_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); return snprintf(buf, PAGE_SIZE, "%d\n", !get_effective_result(chip->disable_votable)); } static ssize_t enable_store(struct class *c, struct class_attribute *attr, const char *buf, size_t count) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); unsigned long val; if (kstrtoul(buf, 0, &val)) return -EINVAL; vote(chip->disable_votable, USER_VOTER, !val, 0); return count; } static CLASS_ATTR_RW(enable); static ssize_t die_temp_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); int die_temp_deciC = 0; int rc; rc = iio_read_channel_processed(chip->iio.die_temp_chan, &die_temp_deciC); return snprintf(buf, PAGE_SIZE, "%d\n", die_temp_deciC / 100); } static CLASS_ATTR_RO(die_temp); static struct attribute *cp_class_attrs[] = { &class_attr_stat1.attr, &class_attr_stat2.attr, &class_attr_enable.attr, &class_attr_die_temp.attr, NULL, }; ATTRIBUTE_GROUPS(cp_class); /* voter callbacks */ static int smb1390_disable_vote_cb(struct votable *votable, void *data, int disable, const char *client) { struct smb1390 *chip = data; int rc = 0; if (!is_psy_voter_available(chip)) return -EAGAIN; if (disable) { rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, CMD_EN_SWITCHER_BIT, 0); if (rc < 0) return rc; vote(chip->hvdcp_hw_inov_dis_votable, CP_VOTER, false, 0); vote(chip->pl_disable_votable, CP_VOTER, false, 0); } else { vote(chip->hvdcp_hw_inov_dis_votable, CP_VOTER, true, 0); vote(chip->pl_disable_votable, CP_VOTER, true, 0); rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, CMD_EN_SWITCHER_BIT, CMD_EN_SWITCHER_BIT); if (rc < 0) return rc; } /* charging may have been disabled by ILIM; send uevent */ kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return rc; } static int smb1390_ilim_vote_cb(struct votable *votable, void *data, int ilim_uA, const char *client) { struct smb1390 *chip = data; int rc = 0; if (!is_psy_voter_available(chip)) return -EAGAIN; /* ILIM should always have at least one active vote */ if (!client) { pr_err("Client missing\n"); return -EINVAL; } /* ILIM less than 1A is not accurate; disable charging */ if (ilim_uA < 1000000) { pr_debug("ILIM %duA is too low to allow charging\n", ilim_uA); vote(chip->disable_votable, ILIM_VOTER, true, 0); } else { pr_debug("setting ILIM to %duA\n", ilim_uA); rc = smb1390_masked_write(chip, CORE_FTRIM_ILIM_REG, CFG_ILIM_MASK, DIV_ROUND_CLOSEST(ilim_uA - 500000, 100000)); if (rc < 0) pr_err("Failed to write ILIM Register, rc=%d\n", rc); if (rc >= 0) vote(chip->disable_votable, ILIM_VOTER, false, 0); } return rc; } static int smb1390_notifier_cb(struct notifier_block *nb, unsigned long event, void *data) { struct smb1390 *chip = container_of(nb, struct smb1390, nb); struct power_supply *psy = data; unsigned long flags; if (event != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; if (strcmp(psy->desc->name, "battery") == 0 || strcmp(psy->desc->name, "usb") == 0 || strcmp(psy->desc->name, "main") == 0) { spin_lock_irqsave(&chip->status_change_lock, flags); if (!chip->status_change_running) { chip->status_change_running = true; pm_stay_awake(chip->dev); schedule_work(&chip->status_change_work); } spin_unlock_irqrestore(&chip->status_change_lock, flags); } return NOTIFY_OK; } static void smb1390_status_change_work(struct work_struct *work) { struct smb1390 *chip = container_of(work, struct smb1390, status_change_work); union power_supply_propval pval = {0, }; int rc; if (!is_psy_voter_available(chip)) goto out; rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_TYPEC_MODE, &pval); if (rc < 0) { pr_err("Couldn't get usb present rc=%d\n", rc); goto out; } if (pval.intval != POWER_SUPPLY_TYPEC_SOURCE_DEFAULT && pval.intval != POWER_SUPPLY_TYPEC_SOURCE_MEDIUM && pval.intval != POWER_SUPPLY_TYPEC_SOURCE_HIGH) { vote(chip->disable_votable, USB_VOTER, true, 0); vote(chip->fcc_votable, CP_VOTER, false, 0); } else { vote(chip->disable_votable, USB_VOTER, false, 0); /* * ILIM is set based on the primary chargers AICL result. This * ensures VBUS does not collapse due to the current drawn via * MID. */ rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval); if (rc < 0) pr_err("Couldn't get usb icl rc=%d\n", rc); else vote(chip->ilim_votable, ICL_VOTER, true, pval.intval); /* input current is always half the charge current */ vote(chip->ilim_votable, FCC_VOTER, true, get_effective_result(chip->fcc_votable) / 2); /* * all votes that would result in disabling the charge pump have * been cast; ensure the charhe pump is still enabled before * continuing. */ if (get_effective_result(chip->disable_votable)) goto out; rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); if (rc < 0) { pr_err("Couldn't get charge type rc=%d\n", rc); } else if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { /* * mutual exclusion is already guaranteed by * chip->status_change_running */ if (!chip->taper_work_running) { chip->taper_work_running = true; queue_work(system_long_wq, &chip->taper_work); } } } out: pm_relax(chip->dev); chip->status_change_running = false; } static void smb1390_taper_work(struct work_struct *work) { struct smb1390 *chip = container_of(work, struct smb1390, taper_work); union power_supply_propval pval = {0, }; int rc, fcc_uA; if (!is_psy_voter_available(chip)) goto out; do { fcc_uA = get_effective_result(chip->fcc_votable) - 100000; pr_debug("taper work reducing FCC to %duA\n", fcc_uA); vote(chip->fcc_votable, CP_VOTER, true, fcc_uA); rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); if (rc < 0) { pr_err("Couldn't get charge type rc=%d\n", rc); goto out; } msleep(500); } while (fcc_uA >= 2000000 && pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER); out: pr_debug("taper work exit\n"); chip->taper_work_running = false; } static int smb1390_parse_dt(struct smb1390 *chip) { int rc; rc = of_property_match_string(chip->dev->of_node, "io-channel-names", "cp_die_temp"); if (rc >= 0) { chip->iio.die_temp_chan = iio_channel_get(chip->dev, "cp_die_temp"); rc = PTR_ERR(chip->iio.die_temp_chan); if (IS_ERR(chip->iio.die_temp_chan)) { if (rc != -EPROBE_DEFER) dev_err(chip->dev, "cp_die_temp channel unavailable %ld\n", PTR_ERR(chip->iio.die_temp_chan)); chip->iio.die_temp_chan = NULL; return rc; } } return rc; } static void smb1390_release_channels(struct smb1390 *chip) { if (!IS_ERR_OR_NULL(chip->iio.die_temp_chan)) iio_channel_release(chip->iio.die_temp_chan); } static int smb1390_create_votables(struct smb1390 *chip) { chip->disable_votable = create_votable("CP_DISABLE", VOTE_SET_ANY, smb1390_disable_vote_cb, chip); if (IS_ERR(chip->disable_votable)) return PTR_ERR(chip->disable_votable); chip->ilim_votable = create_votable("CP_ILIM", VOTE_MIN, smb1390_ilim_vote_cb, chip); if (IS_ERR(chip->ilim_votable)) return PTR_ERR(chip->ilim_votable); return 0; } static void smb1390_destroy_votables(struct smb1390 *chip) { destroy_votable(chip->disable_votable); destroy_votable(chip->ilim_votable); } static int smb1390_init_hw(struct smb1390 *chip) { /* * charge pump is initially disabled; this indirectly votes to allow * traditional parallel charging if present */ vote(chip->disable_votable, USER_VOTER, true, 0); return 0; } static int smb1390_get_irq_index_byname(const char *irq_name) { int i; for (i = 0; i < ARRAY_SIZE(smb_irqs); i++) { if (strcmp(smb_irqs[i].name, irq_name) == 0) return i; } return -ENOENT; } static int smb1390_request_interrupt(struct smb1390 *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 = smb1390_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 (!smb_irqs[irq_index].handler) return 0; rc = devm_request_threaded_irq(chip->dev, irq, NULL, smb_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; } chip->irqs[irq_index] = irq; if (smb_irqs[irq_index].wake) enable_irq_wake(irq); return rc; } static int smb1390_request_interrupts(struct smb1390 *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 = smb1390_request_interrupt(chip, child, name); if (rc < 0) { pr_err("Couldn't request interrupt %s rc=%d\n", name, rc); return rc; } } } return rc; } static int smb1390_probe(struct platform_device *pdev) { struct smb1390 *chip; int rc; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; spin_lock_init(&chip->status_change_lock); chip->regmap = dev_get_regmap(chip->dev->parent, NULL); if (!chip->regmap) { pr_err("Couldn't get regmap\n"); return -EINVAL; } INIT_WORK(&chip->status_change_work, smb1390_status_change_work); INIT_WORK(&chip->taper_work, smb1390_taper_work); rc = smb1390_parse_dt(chip); if (rc < 0) { pr_err("Couldn't parse device tree rc=%d\n", rc); goto out_work; } rc = smb1390_create_votables(chip); if (rc < 0) { pr_err("Couldn't create votables rc=%d\n", rc); goto out_work; } rc = smb1390_init_hw(chip); if (rc < 0) { pr_err("Couldn't init hardware rc=%d\n", rc); goto out_votables; } chip->nb.notifier_call = smb1390_notifier_cb; rc = power_supply_reg_notifier(&chip->nb); if (rc < 0) { pr_err("Couldn't register psy notifier rc=%d\n", rc); goto out_votables; } chip->cp_class.name = "charge_pump"; chip->cp_class.owner = THIS_MODULE; chip->cp_class.class_groups = cp_class_groups; rc = class_register(&chip->cp_class); if (rc < 0) { pr_err("Couldn't register charge_pump sysfs class rc=%d\n", rc); goto out_notifier; } rc = smb1390_request_interrupts(chip); if (rc < 0) { pr_err("Couldn't request interrupts rc=%d\n", rc); goto out_class; } pr_info("smb1390 probed successfully"); return 0; out_class: class_unregister(&chip->cp_class); out_notifier: power_supply_unreg_notifier(&chip->nb); out_votables: smb1390_destroy_votables(chip); out_work: cancel_work(&chip->taper_work); cancel_work(&chip->status_change_work); return rc; } static int smb1390_remove(struct platform_device *pdev) { struct smb1390 *chip = platform_get_drvdata(pdev); class_unregister(&chip->cp_class); power_supply_unreg_notifier(&chip->nb); /* explicitly disable charging */ vote(chip->disable_votable, USER_VOTER, true, 0); cancel_work(&chip->taper_work); cancel_work(&chip->status_change_work); smb1390_destroy_votables(chip); smb1390_release_channels(chip); return 0; } static const struct of_device_id match_table[] = { { .compatible = "qcom,smb1390-charger", }, { }, }; static struct platform_driver smb1390_driver = { .driver = { .name = "qcom,smb1390-charger", .owner = THIS_MODULE, .of_match_table = match_table, }, .probe = smb1390_probe, .remove = smb1390_remove, }; module_platform_driver(smb1390_driver); MODULE_DESCRIPTION("SMB1390 Charge Pump Driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/power/supply/qcom/smb1390-charger.txt 0 → 100644 +76 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. SMB1390 Charger Specific Bindings SMB1390 charge pump is paired with QTI family of standalone chargers to enable a high current, high efficiency Li+ battery charging system. ======================= Required Node Structure ======================= SMB1390 Charger must be described in two levels of device nodes. ================================== First Level Node - SMB1390 Charger ================================== Charger specific properties: - compatible Usage: required Value type: <string> Definition: "qcom,smb1390-charger". - 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. - io-channels - io-channel-names Usage: required Value type: <phandle> Definition: For details about IIO bindings see: Documentation/devicetree/bindings/iio/iio-bindings.txt ================================================ Second Level Nodes - SMB1390 Charger Peripherals ================================================ Peripheral specific properties: - 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 ======= smb1390_charger: qcom,charge_pump { compatible = "qcom,smb1390-charger"; qcom,pmic-revid = <&smb1390_revid>; interrupt-parent = <&smb1390>; status = "disabled"; io-channels = <&pm855b_vadc ADC_AMUX_THM2>; io-channel-names = "cp_die_temp"; qcom,core { interrupts = <0x10 0x0 IRQ_TYPE_EDGE_RISING>, <0x10 0x1 IRQ_TYPE_EDGE_RISING>, <0x10 0x2 IRQ_TYPE_EDGE_RISING>, <0x10 0x3 IRQ_TYPE_EDGE_RISING>, <0x10 0x4 IRQ_TYPE_EDGE_RISING>; interrupt-names = "switcher-off-window", "switcher-off-fault", "vph-ov-soft", "ilim", "temp-alarm"; }; };
drivers/power/supply/qcom/Kconfig +8 −0 Original line number Diff line number Diff line Loading @@ -75,4 +75,12 @@ config QPNP_QNOVO module. It also allows userspace code to read diagnostics of voltage and current measured during certain phases of the pulses. config SMB1390_CHARGE_PUMP tristate "SMB1390 Charge Pump" depends on MFD_I2C_PMIC help Say Y to include support for SMB1390 Charge Pump. SMB1390 is a div2 charge pump capable of delivering 6A charge current with very high efficiency. endmenu
drivers/power/supply/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -5,3 +5,4 @@ obj-$(CONFIG_QPNP_SMB2) += step-chg-jeita.o battery.o qpnp-smb2.o smb-lib.o pmi obj-$(CONFIG_SMB138X_CHARGER) += step-chg-jeita.o smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o obj-$(CONFIG_QPNP_QNOVO) += qpnp-qnovo.o battery.o obj-$(CONFIG_QPNP_SMB5) += step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.o obj-$(CONFIG_SMB1390_CHARGE_PUMP) += smb1390-charger.o pmic-voter.o
drivers/power/supply/qcom/smb1390-charger.c 0 → 100644 +767 −0 Original line number Diff line number Diff line /* Copyright (c) 2017-2018 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) "SMB1390: %s: " fmt, __func__ #include <linux/device.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/pmic-voter.h> #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/iio/consumer.h> #define CORE_STATUS1_REG 0x1006 #define WIN_OV_BIT BIT(0) #define WIN_UV_BIT BIT(1) #define EN_PIN_OUT_BIT BIT(2) #define LCM_AUTO_BIT BIT(3) #define LCM_PIN_BIT BIT(4) #define ILIM_BIT BIT(5) #define TEMP_ALARM_BIT BIT(6) #define VPH_OV_SOFT_BIT BIT(7) #define CORE_STATUS2_REG 0x1007 #define SWITCHER_HOLD_OFF_BIT BIT(0) #define VPH_OV_HARD_BIT BIT(1) #define TSD_BIT BIT(2) #define IREV_BIT BIT(3) #define IOC_BIT BIT(4) #define VIN_UV_BIT BIT(5) #define VIN_OV_BIT BIT(6) #define EN_PIN_OUT2_BIT BIT(7) #define CORE_STATUS3_REG 0x1008 #define EN_SL_BIT BIT(0) #define IIN_REF_SS_DONE_BIT BIT(1) #define FLYCAP_SS_DONE_BIT BIT(2) #define SL_DETECTED_BIT BIT(3) #define CORE_INT_RT_STS_REG 0x1010 #define SWITCHER_OFF_WINDOW_STS_BIT BIT(0) #define SWITCHER_OFF_FAULT_STS_BIT BIT(1) #define TSD_STS_BIT BIT(2) #define IREV_STS_BIT BIT(3) #define VPH_OV_HARD_STS_BIT BIT(4) #define VPH_OV_SOFT_STS_BIT BIT(5) #define ILIM_STS_BIT BIT(6) #define TEMP_ALARM_STS_BIT BIT(7) #define CORE_CONTROL1_REG 0x1020 #define CMD_EN_SWITCHER_BIT BIT(0) #define CMD_EN_SL_BIT BIT(1) #define CORE_FTRIM_ILIM_REG 0x1030 #define CFG_ILIM_MASK GENMASK(4, 0) #define CP_VOTER "CP_VOTER" #define USER_VOTER "USER_VOTER" #define ILIM_VOTER "ILIM_VOTER" #define FCC_VOTER "FCC_VOTER" #define ICL_VOTER "ICL_VOTER" #define USB_VOTER "USB_VOTER" enum { SWITCHER_OFF_WINDOW_IRQ = 0, SWITCHER_OFF_FAULT_IRQ, TSD_IRQ, IREV_IRQ, VPH_OV_HARD_IRQ, VPH_OV_SOFT_IRQ, ILIM_IRQ, TEMP_ALARM_IRQ, NUM_IRQS, }; struct smb1390_iio { struct iio_channel *die_temp_chan; }; struct smb1390 { struct device *dev; struct regmap *regmap; struct notifier_block nb; struct class cp_class; /* work structs */ struct work_struct status_change_work; struct work_struct taper_work; /* mutexes */ spinlock_t status_change_lock; /* votables */ struct votable *disable_votable; struct votable *ilim_votable; struct votable *pl_disable_votable; struct votable *fcc_votable; struct votable *hvdcp_hw_inov_dis_votable; /* power supplies */ struct power_supply *usb_psy; struct power_supply *batt_psy; int irqs[NUM_IRQS]; bool status_change_running; bool taper_work_running; struct smb1390_iio iio; }; struct smb_irq { const char *name; const irq_handler_t handler; const bool wake; }; static const struct smb_irq smb_irqs[]; static int smb1390_read(struct smb1390 *chip, int reg, int *val) { int rc; rc = regmap_read(chip->regmap, reg, val); if (rc < 0) pr_err("Couldn't read 0x%04x\n", reg); return rc; } static int smb1390_masked_write(struct smb1390 *chip, int reg, int mask, int val) { int rc; pr_debug("Writing 0x%02x to 0x%04x with mask 0x%02x\n", val, reg, mask); rc = regmap_update_bits(chip->regmap, reg, mask, val); if (rc < 0) pr_err("Couldn't write 0x%02x to 0x%04x with mask 0x%02x\n", val, reg, mask); return rc; } static bool is_psy_voter_available(struct smb1390 *chip) { if (!chip->batt_psy) { chip->batt_psy = power_supply_get_by_name("battery"); if (!chip->batt_psy) { pr_debug("Couldn't find battery psy\n"); return false; } } if (!chip->usb_psy) { chip->usb_psy = power_supply_get_by_name("usb"); if (!chip->usb_psy) { pr_debug("Couldn't find usb psy\n"); return false; } } if (!chip->fcc_votable) { chip->fcc_votable = find_votable("FCC"); if (!chip->fcc_votable) { pr_debug("Couldn't find FCC votable\n"); return false; } } if (!chip->pl_disable_votable) { chip->pl_disable_votable = find_votable("PL_DISABLE"); if (!chip->pl_disable_votable) { pr_debug("Couldn't find PL_DISABLE votable\n"); return false; } } if (!chip->hvdcp_hw_inov_dis_votable) { chip->hvdcp_hw_inov_dis_votable = find_votable("HVDCP_HW_INOV_DIS"); if (!chip->hvdcp_hw_inov_dis_votable) { pr_debug("Couldn't find HVDCP_HW_INOV_DIS votable\n"); return false; } } return true; } static irqreturn_t default_irq_handler(int irq, void *data) { struct smb1390 *chip = data; int i; for (i = 0; i < NUM_IRQS; ++i) { if (irq == chip->irqs[i]) pr_debug("%s IRQ triggered\n", smb_irqs[i].name); } kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return IRQ_HANDLED; } static const struct smb_irq smb_irqs[] = { [SWITCHER_OFF_WINDOW_IRQ] = { .name = "switcher-off-window", .handler = default_irq_handler, .wake = true, }, [SWITCHER_OFF_FAULT_IRQ] = { .name = "switcher-off-fault", .handler = default_irq_handler, .wake = true, }, [TSD_IRQ] = { .name = "tsd-fault", .handler = default_irq_handler, .wake = true, }, [IREV_IRQ] = { .name = "irev-fault", .handler = default_irq_handler, .wake = true, }, [VPH_OV_HARD_IRQ] = { .name = "vph-ov-hard", .handler = default_irq_handler, .wake = true, }, [VPH_OV_SOFT_IRQ] = { .name = "vph-ov-soft", .handler = default_irq_handler, .wake = true, }, [ILIM_IRQ] = { .name = "ilim", .handler = default_irq_handler, .wake = true, }, [TEMP_ALARM_IRQ] = { .name = "temp-alarm", .handler = default_irq_handler, .wake = true, }, }; /* SYSFS functions for reporting smb1390 charge pump state */ static ssize_t stat1_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); int rc, val; rc = smb1390_read(chip, CORE_STATUS1_REG, &val); if (rc < 0) return -EINVAL; return snprintf(buf, PAGE_SIZE, "%x\n", val); } static CLASS_ATTR_RO(stat1); static ssize_t stat2_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); int rc, val; rc = smb1390_read(chip, CORE_STATUS2_REG, &val); if (rc < 0) return -EINVAL; return snprintf(buf, PAGE_SIZE, "%x\n", val); } static CLASS_ATTR_RO(stat2); static ssize_t enable_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); return snprintf(buf, PAGE_SIZE, "%d\n", !get_effective_result(chip->disable_votable)); } static ssize_t enable_store(struct class *c, struct class_attribute *attr, const char *buf, size_t count) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); unsigned long val; if (kstrtoul(buf, 0, &val)) return -EINVAL; vote(chip->disable_votable, USER_VOTER, !val, 0); return count; } static CLASS_ATTR_RW(enable); static ssize_t die_temp_show(struct class *c, struct class_attribute *attr, char *buf) { struct smb1390 *chip = container_of(c, struct smb1390, cp_class); int die_temp_deciC = 0; int rc; rc = iio_read_channel_processed(chip->iio.die_temp_chan, &die_temp_deciC); return snprintf(buf, PAGE_SIZE, "%d\n", die_temp_deciC / 100); } static CLASS_ATTR_RO(die_temp); static struct attribute *cp_class_attrs[] = { &class_attr_stat1.attr, &class_attr_stat2.attr, &class_attr_enable.attr, &class_attr_die_temp.attr, NULL, }; ATTRIBUTE_GROUPS(cp_class); /* voter callbacks */ static int smb1390_disable_vote_cb(struct votable *votable, void *data, int disable, const char *client) { struct smb1390 *chip = data; int rc = 0; if (!is_psy_voter_available(chip)) return -EAGAIN; if (disable) { rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, CMD_EN_SWITCHER_BIT, 0); if (rc < 0) return rc; vote(chip->hvdcp_hw_inov_dis_votable, CP_VOTER, false, 0); vote(chip->pl_disable_votable, CP_VOTER, false, 0); } else { vote(chip->hvdcp_hw_inov_dis_votable, CP_VOTER, true, 0); vote(chip->pl_disable_votable, CP_VOTER, true, 0); rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, CMD_EN_SWITCHER_BIT, CMD_EN_SWITCHER_BIT); if (rc < 0) return rc; } /* charging may have been disabled by ILIM; send uevent */ kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return rc; } static int smb1390_ilim_vote_cb(struct votable *votable, void *data, int ilim_uA, const char *client) { struct smb1390 *chip = data; int rc = 0; if (!is_psy_voter_available(chip)) return -EAGAIN; /* ILIM should always have at least one active vote */ if (!client) { pr_err("Client missing\n"); return -EINVAL; } /* ILIM less than 1A is not accurate; disable charging */ if (ilim_uA < 1000000) { pr_debug("ILIM %duA is too low to allow charging\n", ilim_uA); vote(chip->disable_votable, ILIM_VOTER, true, 0); } else { pr_debug("setting ILIM to %duA\n", ilim_uA); rc = smb1390_masked_write(chip, CORE_FTRIM_ILIM_REG, CFG_ILIM_MASK, DIV_ROUND_CLOSEST(ilim_uA - 500000, 100000)); if (rc < 0) pr_err("Failed to write ILIM Register, rc=%d\n", rc); if (rc >= 0) vote(chip->disable_votable, ILIM_VOTER, false, 0); } return rc; } static int smb1390_notifier_cb(struct notifier_block *nb, unsigned long event, void *data) { struct smb1390 *chip = container_of(nb, struct smb1390, nb); struct power_supply *psy = data; unsigned long flags; if (event != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; if (strcmp(psy->desc->name, "battery") == 0 || strcmp(psy->desc->name, "usb") == 0 || strcmp(psy->desc->name, "main") == 0) { spin_lock_irqsave(&chip->status_change_lock, flags); if (!chip->status_change_running) { chip->status_change_running = true; pm_stay_awake(chip->dev); schedule_work(&chip->status_change_work); } spin_unlock_irqrestore(&chip->status_change_lock, flags); } return NOTIFY_OK; } static void smb1390_status_change_work(struct work_struct *work) { struct smb1390 *chip = container_of(work, struct smb1390, status_change_work); union power_supply_propval pval = {0, }; int rc; if (!is_psy_voter_available(chip)) goto out; rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_TYPEC_MODE, &pval); if (rc < 0) { pr_err("Couldn't get usb present rc=%d\n", rc); goto out; } if (pval.intval != POWER_SUPPLY_TYPEC_SOURCE_DEFAULT && pval.intval != POWER_SUPPLY_TYPEC_SOURCE_MEDIUM && pval.intval != POWER_SUPPLY_TYPEC_SOURCE_HIGH) { vote(chip->disable_votable, USB_VOTER, true, 0); vote(chip->fcc_votable, CP_VOTER, false, 0); } else { vote(chip->disable_votable, USB_VOTER, false, 0); /* * ILIM is set based on the primary chargers AICL result. This * ensures VBUS does not collapse due to the current drawn via * MID. */ rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval); if (rc < 0) pr_err("Couldn't get usb icl rc=%d\n", rc); else vote(chip->ilim_votable, ICL_VOTER, true, pval.intval); /* input current is always half the charge current */ vote(chip->ilim_votable, FCC_VOTER, true, get_effective_result(chip->fcc_votable) / 2); /* * all votes that would result in disabling the charge pump have * been cast; ensure the charhe pump is still enabled before * continuing. */ if (get_effective_result(chip->disable_votable)) goto out; rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); if (rc < 0) { pr_err("Couldn't get charge type rc=%d\n", rc); } else if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { /* * mutual exclusion is already guaranteed by * chip->status_change_running */ if (!chip->taper_work_running) { chip->taper_work_running = true; queue_work(system_long_wq, &chip->taper_work); } } } out: pm_relax(chip->dev); chip->status_change_running = false; } static void smb1390_taper_work(struct work_struct *work) { struct smb1390 *chip = container_of(work, struct smb1390, taper_work); union power_supply_propval pval = {0, }; int rc, fcc_uA; if (!is_psy_voter_available(chip)) goto out; do { fcc_uA = get_effective_result(chip->fcc_votable) - 100000; pr_debug("taper work reducing FCC to %duA\n", fcc_uA); vote(chip->fcc_votable, CP_VOTER, true, fcc_uA); rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); if (rc < 0) { pr_err("Couldn't get charge type rc=%d\n", rc); goto out; } msleep(500); } while (fcc_uA >= 2000000 && pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER); out: pr_debug("taper work exit\n"); chip->taper_work_running = false; } static int smb1390_parse_dt(struct smb1390 *chip) { int rc; rc = of_property_match_string(chip->dev->of_node, "io-channel-names", "cp_die_temp"); if (rc >= 0) { chip->iio.die_temp_chan = iio_channel_get(chip->dev, "cp_die_temp"); rc = PTR_ERR(chip->iio.die_temp_chan); if (IS_ERR(chip->iio.die_temp_chan)) { if (rc != -EPROBE_DEFER) dev_err(chip->dev, "cp_die_temp channel unavailable %ld\n", PTR_ERR(chip->iio.die_temp_chan)); chip->iio.die_temp_chan = NULL; return rc; } } return rc; } static void smb1390_release_channels(struct smb1390 *chip) { if (!IS_ERR_OR_NULL(chip->iio.die_temp_chan)) iio_channel_release(chip->iio.die_temp_chan); } static int smb1390_create_votables(struct smb1390 *chip) { chip->disable_votable = create_votable("CP_DISABLE", VOTE_SET_ANY, smb1390_disable_vote_cb, chip); if (IS_ERR(chip->disable_votable)) return PTR_ERR(chip->disable_votable); chip->ilim_votable = create_votable("CP_ILIM", VOTE_MIN, smb1390_ilim_vote_cb, chip); if (IS_ERR(chip->ilim_votable)) return PTR_ERR(chip->ilim_votable); return 0; } static void smb1390_destroy_votables(struct smb1390 *chip) { destroy_votable(chip->disable_votable); destroy_votable(chip->ilim_votable); } static int smb1390_init_hw(struct smb1390 *chip) { /* * charge pump is initially disabled; this indirectly votes to allow * traditional parallel charging if present */ vote(chip->disable_votable, USER_VOTER, true, 0); return 0; } static int smb1390_get_irq_index_byname(const char *irq_name) { int i; for (i = 0; i < ARRAY_SIZE(smb_irqs); i++) { if (strcmp(smb_irqs[i].name, irq_name) == 0) return i; } return -ENOENT; } static int smb1390_request_interrupt(struct smb1390 *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 = smb1390_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 (!smb_irqs[irq_index].handler) return 0; rc = devm_request_threaded_irq(chip->dev, irq, NULL, smb_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; } chip->irqs[irq_index] = irq; if (smb_irqs[irq_index].wake) enable_irq_wake(irq); return rc; } static int smb1390_request_interrupts(struct smb1390 *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 = smb1390_request_interrupt(chip, child, name); if (rc < 0) { pr_err("Couldn't request interrupt %s rc=%d\n", name, rc); return rc; } } } return rc; } static int smb1390_probe(struct platform_device *pdev) { struct smb1390 *chip; int rc; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; spin_lock_init(&chip->status_change_lock); chip->regmap = dev_get_regmap(chip->dev->parent, NULL); if (!chip->regmap) { pr_err("Couldn't get regmap\n"); return -EINVAL; } INIT_WORK(&chip->status_change_work, smb1390_status_change_work); INIT_WORK(&chip->taper_work, smb1390_taper_work); rc = smb1390_parse_dt(chip); if (rc < 0) { pr_err("Couldn't parse device tree rc=%d\n", rc); goto out_work; } rc = smb1390_create_votables(chip); if (rc < 0) { pr_err("Couldn't create votables rc=%d\n", rc); goto out_work; } rc = smb1390_init_hw(chip); if (rc < 0) { pr_err("Couldn't init hardware rc=%d\n", rc); goto out_votables; } chip->nb.notifier_call = smb1390_notifier_cb; rc = power_supply_reg_notifier(&chip->nb); if (rc < 0) { pr_err("Couldn't register psy notifier rc=%d\n", rc); goto out_votables; } chip->cp_class.name = "charge_pump"; chip->cp_class.owner = THIS_MODULE; chip->cp_class.class_groups = cp_class_groups; rc = class_register(&chip->cp_class); if (rc < 0) { pr_err("Couldn't register charge_pump sysfs class rc=%d\n", rc); goto out_notifier; } rc = smb1390_request_interrupts(chip); if (rc < 0) { pr_err("Couldn't request interrupts rc=%d\n", rc); goto out_class; } pr_info("smb1390 probed successfully"); return 0; out_class: class_unregister(&chip->cp_class); out_notifier: power_supply_unreg_notifier(&chip->nb); out_votables: smb1390_destroy_votables(chip); out_work: cancel_work(&chip->taper_work); cancel_work(&chip->status_change_work); return rc; } static int smb1390_remove(struct platform_device *pdev) { struct smb1390 *chip = platform_get_drvdata(pdev); class_unregister(&chip->cp_class); power_supply_unreg_notifier(&chip->nb); /* explicitly disable charging */ vote(chip->disable_votable, USER_VOTER, true, 0); cancel_work(&chip->taper_work); cancel_work(&chip->status_change_work); smb1390_destroy_votables(chip); smb1390_release_channels(chip); return 0; } static const struct of_device_id match_table[] = { { .compatible = "qcom,smb1390-charger", }, { }, }; static struct platform_driver smb1390_driver = { .driver = { .name = "qcom,smb1390-charger", .owner = THIS_MODULE, .of_match_table = match_table, }, .probe = smb1390_probe, .remove = smb1390_remove, }; module_platform_driver(smb1390_driver); MODULE_DESCRIPTION("SMB1390 Charge Pump Driver"); MODULE_LICENSE("GPL v2");