#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "axp2101_charger.h" struct pmc_usb_power { char *name; struct device *dev; struct axp_config_info dts_info; struct regmap *regmap; struct power_supply *usb_supply; struct delayed_work usb_supply_mon; struct delayed_work usb_chg_state; atomic_t set_current_limit; }; static void pmc_usb_set_current_fsm(struct work_struct *work) { struct pmc_usb_power *usb_power = container_of(work, typeof(*usb_power), usb_chg_state.work); /* struct axp_config_info *axp_config = &usb_power->dts_info; */ if (atomic_read(&usb_power->set_current_limit)) { pr_info("current limit setted: usb pc type\n"); } else { /* axp803_usb_set_ihold(usb_power, axp_config->pmu_usbad_cur); */ pr_info("current limit not set: usb adapter type\n"); } } static enum power_supply_property pmc_usb_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, /* POWER_SUPPLY_PROP_PRESENT, */ POWER_SUPPLY_PROP_ONLINE, /* POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, */ }; static int pmc_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { int ret = 0; unsigned int reg_value; struct pmc_usb_power *usb_power = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = psy->desc->name; break; /* case POWER_SUPPLY_PROP_PRESENT: */ /* ret = regmap_read(usb_power->regmap, AXP803_STATUS, ®_value); */ /* if (ret) */ /* return ret; */ /* val->intval = !!(reg_value & AXP803_STATUS_VBUS_PRESENT); */ /* break; */ case POWER_SUPPLY_PROP_ONLINE: ret = regmap_read(usb_power->regmap, PMC_STATUS_REG, ®_value); if (ret) return ret; val->intval = !!(reg_value & PMC_VBUS_STATUS); break; /* case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: */ /* ret = axp803_usb_get_ihold(usb_power); */ /* if (ret < 0) */ /* return ret; */ /* val->intval = ret; */ /* break; */ default: ret = -EINVAL; break; } return ret; } static int pmc_usb_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { int ret = 0; /* struct pmc_usb_power *usb_power = power_supply_get_drvdata(psy); */ switch (psp) { /* case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: */ /* ret = axp803_usb_set_ihold(usb_power, val->intval); */ /* atomic_set(&usb_power->set_current_limit, 1); */ /* break; */ default: ret = -EINVAL; } return ret; } static int pmc_usb_power_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { int ret; switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ret = 1; break; default: ret = 0; } return ret; } static const struct power_supply_desc pmc_usb_desc = { .name = "pmc-usb", .type = POWER_SUPPLY_TYPE_USB, .get_property = pmc_usb_get_property, .properties = pmc_usb_props, .set_property = pmc_usb_set_property, .num_properties = ARRAY_SIZE(pmc_usb_props), .property_is_writeable = pmc_usb_power_property_is_writeable, }; static int pmc_usb_power_init(struct pmc_usb_power *usb_power) { /* struct axp_config_info *axp_config = &usb_power->dts_info; */ /* axp803_usb_set_ihold(usb_power, axp_config->pmu_usbpc_cur); */ return 0; } static irqreturn_t pmc_usb_power_in_irq(int irq, void *data) { struct pmc_usb_power *usb_power = data; /* struct axp_config_info *axp_config = &usb_power->dts_info; */ power_supply_changed(usb_power->usb_supply); /* axp803_usb_set_ihold(usb_power, axp_config->pmu_usbpc_cur); */ atomic_set(&usb_power->set_current_limit, 0); cancel_delayed_work_sync(&usb_power->usb_chg_state); schedule_delayed_work(&usb_power->usb_chg_state, msecs_to_jiffies(5 * 1000)); return IRQ_HANDLED; } static irqreturn_t pmc_usb_power_out_irq(int irq, void *data) { struct pmc_usb_power *usb_power = data; power_supply_changed(usb_power->usb_supply); return IRQ_HANDLED; } enum pmc_usb_power_virqs { PMC_VIRQ_USBIN, PMC_VIRQ_USBRE, PMC_USB_VIRQ_MAX_VIRQ, }; static struct axp_interrupts pmc_usb_irq[] = { [PMC_VIRQ_USBIN] = {"usb in", pmc_usb_power_in_irq}, [PMC_VIRQ_USBRE] = {"usb out", pmc_usb_power_out_irq}, }; static void pmc_usb_power_monitor(struct work_struct *work) { struct pmc_usb_power *usb_power = container_of(work, typeof(*usb_power), usb_supply_mon.work); schedule_delayed_work(&usb_power->usb_supply_mon, msecs_to_jiffies(500)); } static int pmc_usb_power_dt_parse(struct pmc_usb_power *usb_power) { struct axp_config_info *axp_config = &usb_power->dts_info; struct device_node *node = usb_power->dev->of_node; if (!of_device_is_available(node)) { pr_err("%s: failed\n", __func__); return -1; } AXP_OF_PROP_READ(pmu_usbpc_vol, 4400); AXP_OF_PROP_READ(pmu_usbpc_cur, 0); AXP_OF_PROP_READ(pmu_usbad_vol, 4400); AXP_OF_PROP_READ(pmu_usbad_cur, 0); axp_config->wakeup_usb_in = of_property_read_bool(node, "wakeup_usb_in"); axp_config->wakeup_usb_out = of_property_read_bool(node, "wakeup_usb_out"); return 0; } static int pmc_usb_power_probe(struct platform_device *pdev) { struct sunxi_pmc_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = {}; struct pmc_usb_power *usb_power; int i, irq; int ret = 0; if (!axp_dev->irq) { pr_err("can not register pmc usb without irq\n"); return -EINVAL; } usb_power = devm_kzalloc(&pdev->dev, sizeof(*usb_power), GFP_KERNEL); if (!usb_power) { pr_err("pmc usb power alloc failed\n"); ret = -ENOMEM; return ret; } usb_power->name = "pmc-usb-power"; usb_power->dev = &pdev->dev; usb_power->regmap = axp_dev->regmap; platform_set_drvdata(pdev, usb_power); ret = pmc_usb_power_dt_parse(usb_power); if (ret) { pr_err("%s parse device tree err\n", __func__); ret = -EINVAL; return ret; } ret = pmc_usb_power_init(usb_power); if (ret < 0) { pr_err("pmc init usb power fail!\n"); ret = -ENODEV; return ret; } psy_cfg.of_node = pdev->dev.of_node; psy_cfg.drv_data = usb_power; usb_power->usb_supply = devm_power_supply_register(usb_power->dev, &pmc_usb_desc, &psy_cfg); if (IS_ERR(usb_power->usb_supply)) { pr_err("pmc failed to register usb power\n"); ret = PTR_ERR(usb_power->usb_supply); return ret; } for (i = 0; i < ARRAY_SIZE(pmc_usb_irq); i++) { irq = platform_get_irq_byname(pdev, pmc_usb_irq[i].name); if (irq < 0) { dev_warn(&pdev->dev, "No IRQ for %s: %d\n", pmc_usb_irq[i].name, irq); continue; } irq = regmap_irq_get_virq(axp_dev->regmap_irqc, irq); ret = devm_request_any_context_irq(&pdev->dev, irq, pmc_usb_irq[i].isr, 0, pmc_usb_irq[i].name, usb_power); if (ret < 0) dev_warn(&pdev->dev, "Error requesting %s IRQ %d: %d\n", pmc_usb_irq[i].name, irq, ret); dev_dbg(&pdev->dev, "Requested %s IRQ %d: %d\n", pmc_usb_irq[i].name, irq, ret); /* we use this variable to suspend irq */ pmc_usb_irq[i].irq = irq; } INIT_DELAYED_WORK(&usb_power->usb_supply_mon, pmc_usb_power_monitor); schedule_delayed_work(&usb_power->usb_supply_mon, msecs_to_jiffies(500)); INIT_DELAYED_WORK(&usb_power->usb_chg_state, pmc_usb_set_current_fsm); schedule_delayed_work(&usb_power->usb_chg_state, msecs_to_jiffies(20 * 1000)); return 0; } static int pmc_usb_power_remove(struct platform_device *pdev) { struct pmc_usb_power *usb_power = platform_get_drvdata(pdev); if (usb_power->usb_supply) power_supply_unregister(usb_power->usb_supply); return 0; } static inline void axp_irq_set(unsigned int irq, bool enable) { if (enable) enable_irq(irq); else disable_irq(irq); } static void pmc_usb_virq_dts_set(struct pmc_usb_power *usb_power, bool enable) { struct axp_config_info *dts_info = &usb_power->dts_info; if (!dts_info->wakeup_usb_in) axp_irq_set(pmc_usb_irq[PMC_VIRQ_USBIN].irq, enable); if (!dts_info->wakeup_usb_out) axp_irq_set(pmc_usb_irq[PMC_VIRQ_USBRE].irq, enable); } static int pmc_usb_power_suspend(struct platform_device *pdev, pm_message_t state) { struct pmc_usb_power *usb_power = platform_get_drvdata(pdev); pmc_usb_virq_dts_set(usb_power, false); return 0; } static int pmc_usb_power_resume(struct platform_device *pdev) { struct pmc_usb_power *usb_power = platform_get_drvdata(pdev); pmc_usb_virq_dts_set(usb_power, true); return 0; } static const struct of_device_id pmc_usb_power_match[] = { { .compatible = "x-powers,pmc-usb-power-supply", .data = (void *)PMC_V1_ID, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, pmc_usb_power_match); static struct platform_driver pmc_usb_power_driver = { .driver = { .name = "pmc-usb-power-supply", .of_match_table = pmc_usb_power_match, }, .probe = pmc_usb_power_probe, .remove = pmc_usb_power_remove, .suspend = pmc_usb_power_suspend, .resume = pmc_usb_power_resume, }; module_platform_driver(pmc_usb_power_driver); MODULE_AUTHOR("wangxiaoliang "); MODULE_DESCRIPTION("pmc usb power driver"); MODULE_LICENSE("GPL");