diff options
Diffstat (limited to 'drivers/power/max77693_charger.c')
-rw-r--r-- | drivers/power/max77693_charger.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/drivers/power/max77693_charger.c b/drivers/power/max77693_charger.c index 060cab5..922f43f 100644 --- a/drivers/power/max77693_charger.c +++ b/drivers/power/max77693_charger.c @@ -22,8 +22,11 @@ #include <linux/mfd/max77693.h> #include <linux/mfd/max77693-common.h> #include <linux/mfd/max77693-private.h> +#include <linux/extcon.h> +#include <linux/regulator/consumer.h> #define MAX77693_CHARGER_NAME "max77693-charger" +#define MAX77693_EXTCON_DEV_NAME "max77693-muic" static const char *max77693_charger_model = "MAX77693"; static const char *max77693_charger_manufacturer = "Maxim Integrated"; @@ -31,12 +34,21 @@ struct max77693_charger { struct device *dev; struct max77693_dev *max77693; struct power_supply *charger; + struct regulator *regu; u32 constant_volt; u32 min_system_volt; u32 thermal_regulation_temp; u32 batttery_overcurrent; u32 charge_input_threshold_volt; + + /* SDP/DCP USB charging cable notifications */ + struct { + struct extcon_dev *edev; + bool connected; + struct notifier_block nb; + struct work_struct work; + } cable; }; static int max77693_get_charger_state(struct regmap *regmap, int *val) @@ -207,12 +219,28 @@ static int max77693_get_online(struct regmap *regmap, int *val) return 0; } +int max77693_get_charge_current(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_CNFG_02, &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_02_CC_MASK; + *val = data * 333 / 10; /* 3 steps/0.1A */ + + return 0; +} + static enum power_supply_property max77693_charger_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, }; @@ -241,6 +269,9 @@ static int max77693_charger_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ONLINE: ret = max77693_get_online(regmap, &val->intval); break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = max77693_get_charge_current(regmap, &val->intval); + break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = max77693_charger_model; break; @@ -295,6 +326,7 @@ static ssize_t fast_charge_timer_show(struct device *dev, data &= CHG_CNFG_01_FCHGTIME_MASK; data >>= CHG_CNFG_01_FCHGTIME_SHIFT; + switch (data) { case 0x1 ... 0x7: /* Starting from 4 hours, step by 2 hours */ @@ -582,6 +614,97 @@ static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg CHG_CNFG_12_VCHGINREG_MASK, data); } +static int max77693_enable_charger(struct max77693_charger *chg, bool enable) +{ + int ret; + + if (enable) { + regulator_set_current_limit(chg->regu, + CHG_CNFG_09_CHGIN_ILIM_500_MIN, + CHG_CNFG_09_CHGIN_ILIM_500_MAX); + if (ret < 0) + return ret; + + ret = regulator_enable(chg->regu); + if (ret < 0) + return ret; + } else { + /* sets fast charge current to zero */ + ret = regulator_set_current_limit(chg->regu, + CHG_CNFG_09_CHGIN_ILIM_0_MIN, + CHG_CNFG_09_CHGIN_ILIM_0_MAX); + if (ret < 0) + return ret; + + ret = regulator_disable(chg->regu); + if (ret < 0) + return ret; + } + + return ret; +} + +static void max77693_extcon_evt_worker(struct work_struct *work) +{ + struct max77693_charger *chg = + container_of(work, struct max77693_charger, cable.work); + bool changed = false; + struct extcon_dev *edev = chg->cable.edev; + bool old_connected = chg->cable.connected; + bool is_charger_enabled; + int ret; + + /* Determine cable/charger type */ + if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_SDP) || + extcon_get_cable_state_(edev, EXTCON_CHG_USB_DCP)) { + dev_dbg(chg->dev, "USB charger is connected"); + chg->cable.connected = true; + } else { + if (old_connected) + dev_dbg(chg->dev, "USB charger disconnected"); + + chg->cable.connected = false; + } + + /* Cable status changed */ + if (old_connected != chg->cable.connected) + changed = true; + + if (!changed) + return; + + if (regulator_is_enabled(chg->regu)) + is_charger_enabled = true; + else + is_charger_enabled = false; + + if (is_charger_enabled && !chg->cable.connected) { + ret = max77693_enable_charger(chg, false); + if (ret < 0) + dev_err(chg->dev, + "failed to disable charger (%d)", ret); + } else if (!is_charger_enabled && chg->cable.connected) { + ret = max77693_enable_charger(chg, true); + if (ret < 0) + dev_err(chg->dev, + "cannot enable charger (%d)", ret); + } + + if (changed) + power_supply_changed(chg->charger); +} + +static int max77693_handle_cable_evt(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct max77693_charger *chg = + container_of(nb, struct max77693_charger, cable.nb); + + schedule_work(&chg->cable.work); + + return NOTIFY_OK; +} + /* * Sets charger registers to proper and safe default values. */ @@ -693,6 +816,45 @@ static int max77693_charger_probe(struct platform_device *pdev) if (ret) return ret; + chg->regu = devm_regulator_get(chg->dev, "CHARGER"); + if (IS_ERR(chg->regu)) { + ret = PTR_ERR(chg->regu); + dev_err(&pdev->dev, + "failed to get charger regulator %d\n", ret); + return ret; + } + + chg->cable.edev = extcon_get_extcon_dev(MAX77693_EXTCON_DEV_NAME); + if (chg->cable.edev == NULL) { + dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n", + MAX77693_EXTCON_DEV_NAME); + return -EPROBE_DEFER; + } + + /* set initial value */ + chg->cable.connected = false; + + /* Register for extcon notification */ + INIT_WORK(&chg->cable.work, max77693_extcon_evt_worker); + chg->cable.nb.notifier_call = max77693_handle_cable_evt; + ret = extcon_register_notifier(chg->cable.edev, EXTCON_CHG_USB_SDP, + &chg->cable.nb); + if (ret) { + dev_err(&pdev->dev, + "failed to register extcon notifier for SDP %d\n", ret); + return ret; + } + + ret = extcon_register_notifier(chg->cable.edev, EXTCON_CHG_USB_DCP, + &chg->cable.nb); + if (ret) { + dev_err(&pdev->dev, + "failed to register extcon notifier for DCP %d\n", ret); + extcon_unregister_notifier(chg->cable.edev, + EXTCON_CHG_USB_SDP, &chg->cable.nb); + return ret; + } + ret = max77693_reg_init(chg); if (ret) return ret; @@ -733,6 +895,10 @@ err: device_remove_file(&pdev->dev, &dev_attr_top_off_timer); device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + extcon_unregister_notifier(chg->cable.edev, EXTCON_CHG_USB_SDP, + &chg->cable.nb); + extcon_unregister_notifier(chg->cable.edev, EXTCON_CHG_USB_DCP, + &chg->cable.nb); return ret; } @@ -745,6 +911,11 @@ static int max77693_charger_remove(struct platform_device *pdev) device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + extcon_unregister_notifier(chg->cable.edev, EXTCON_CHG_USB_SDP, + &chg->cable.nb); + extcon_unregister_notifier(chg->cable.edev, EXTCON_CHG_USB_DCP, + &chg->cable.nb); + power_supply_unregister(chg->charger); return 0; |