| // SPDX-License-Identifier: GPL-2.0-or-later |
| |
| #include <linux/devm-helpers.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/extcon.h> |
| #include <linux/i2c.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/of_graph.h> |
| #include <linux/property.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/pm.h> |
| #include <linux/power_supply.h> |
| #include <linux/regmap.h> |
| #include <linux/sysfs.h> |
| #include <linux/types.h> |
| |
| #define MAX8971_REG_CHGINT 0x0f |
| #define MAX8971_REG_CHG_RST BIT(0) |
| #define MAX8971_REG_CHGINT_MASK 0x01 |
| #define MAX8971_AICL_MASK BIT(7) |
| #define MAX8971_REG_CHG_STAT 0x02 |
| #define MAX8971_CHG_MASK BIT(3) |
| #define MAX8971_REG_DETAILS1 0x03 |
| #define MAX8971_REG_DETAILS2 0x04 |
| #define MAX8971_REG_CHGCNTL1 0x05 |
| #define MAX8971_REG_FCHGCRNT 0x06 |
| #define MAX8971_REG_DCCRNT 0x07 |
| #define MAX8971_CHGRSTRT_MASK BIT(6) |
| #define MAX8971_REG_TOPOFF 0x08 |
| #define MAX8971_REG_TEMPREG 0x09 |
| #define MAX8971_REG_PROTCMD 0x0a |
| #define MAX8971_CHGPROT_LOCKED 0x00 |
| #define MAX8971_CHGPROT_UNLOCKED 0x03 |
| |
| #define MAX8971_FCHGT_DEFAULT 2 |
| #define MAX8971_TOPOFFT_DEFAULT 3 |
| |
| static const char *max8971_manufacturer = "Maxim Integrated"; |
| static const char *max8971_model = "MAX8971"; |
| |
| enum max8971_charging_state { |
| MAX8971_CHARGING_DEAD_BATTERY, |
| MAX8971_CHARGING_PREQUALIFICATION, |
| MAX8971_CHARGING_FAST_CONST_CURRENT, |
| MAX8971_CHARGING_FAST_CONST_VOLTAGE, |
| MAX8971_CHARGING_TOP_OFF, |
| MAX8971_CHARGING_DONE, |
| MAX8971_CHARGING_TIMER_FAULT, |
| MAX8971_CHARGING_SUSPENDED_THERMAL, |
| MAX8971_CHARGING_OFF, |
| MAX8971_CHARGING_THERMAL_LOOP, |
| }; |
| |
| enum max8971_health_state { |
| MAX8971_HEALTH_UNKNOWN, |
| MAX8971_HEALTH_COLD, |
| MAX8971_HEALTH_COOL, |
| MAX8971_HEALTH_WARM, |
| MAX8971_HEALTH_HOT, |
| MAX8971_HEALTH_OVERHEAT, |
| }; |
| |
| /* Fast-Charge current limit, 250..1550 mA, 50 mA steps */ |
| #define MAX8971_CHG_CC_STEP 50000U |
| #define MAX8971_CHG_CC_MIN 250000U |
| #define MAX8971_CHG_CC_MAX 1550000U |
| |
| /* Input current limit, 250..1500 mA, 25 mA steps */ |
| #define MAX8971_DCILMT_STEP 25000U |
| #define MAX8971_DCILMT_MIN 250000U |
| #define MAX8971_DCILMT_MAX 1500000U |
| |
| enum max8971_field_idx { |
| THM_DTLS, /* DETAILS1 */ |
| BAT_DTLS, CHG_DTLS, /* DETAILS2 */ |
| CHG_CC, FCHG_T, /* FCHGCRNT */ |
| DCI_LMT, /* DCCRNT */ |
| TOPOFF_T, TOPOFF_S, /* TOPOFF */ |
| CPROT, /* PROTCMD */ |
| MAX8971_N_REGMAP_FIELDS |
| }; |
| |
| static const struct reg_field max8971_reg_field[MAX8971_N_REGMAP_FIELDS] = { |
| [THM_DTLS] = REG_FIELD(MAX8971_REG_DETAILS1, 0, 2), |
| [BAT_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 4, 5), |
| [CHG_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 0, 3), |
| [CHG_CC] = REG_FIELD(MAX8971_REG_FCHGCRNT, 0, 4), |
| [FCHG_T] = REG_FIELD(MAX8971_REG_FCHGCRNT, 5, 7), |
| [DCI_LMT] = REG_FIELD(MAX8971_REG_DCCRNT, 0, 5), |
| [TOPOFF_T] = REG_FIELD(MAX8971_REG_TOPOFF, 5, 7), |
| [TOPOFF_S] = REG_FIELD(MAX8971_REG_TOPOFF, 2, 3), |
| [CPROT] = REG_FIELD(MAX8971_REG_PROTCMD, 2, 3), |
| }; |
| |
| static const struct regmap_config max8971_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = MAX8971_REG_CHGINT, |
| }; |
| |
| struct max8971_data { |
| struct device *dev; |
| struct power_supply *psy_mains; |
| |
| struct extcon_dev *edev; |
| struct notifier_block extcon_nb; |
| struct delayed_work extcon_work; |
| |
| struct regmap *regmap; |
| struct regmap_field *rfield[MAX8971_N_REGMAP_FIELDS]; |
| |
| enum power_supply_usb_type usb_type; |
| |
| u32 fchgt; |
| u32 tofft; |
| u32 toffs; |
| |
| bool present; |
| }; |
| |
| static int max8971_get_status(struct max8971_data *priv, int *val) |
| { |
| u32 regval; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[CHG_DTLS], ®val); |
| if (err) |
| return err; |
| |
| switch (regval) { |
| case MAX8971_CHARGING_DEAD_BATTERY: |
| case MAX8971_CHARGING_PREQUALIFICATION: |
| case MAX8971_CHARGING_FAST_CONST_CURRENT: |
| case MAX8971_CHARGING_FAST_CONST_VOLTAGE: |
| case MAX8971_CHARGING_TOP_OFF: |
| case MAX8971_CHARGING_THERMAL_LOOP: |
| *val = POWER_SUPPLY_STATUS_CHARGING; |
| break; |
| case MAX8971_CHARGING_DONE: |
| *val = POWER_SUPPLY_STATUS_FULL; |
| break; |
| case MAX8971_CHARGING_TIMER_FAULT: |
| *val = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| break; |
| case MAX8971_CHARGING_OFF: |
| case MAX8971_CHARGING_SUSPENDED_THERMAL: |
| *val = POWER_SUPPLY_STATUS_DISCHARGING; |
| break; |
| default: |
| *val = POWER_SUPPLY_STATUS_UNKNOWN; |
| } |
| |
| return 0; |
| } |
| |
| static int max8971_get_charge_type(struct max8971_data *priv, int *val) |
| { |
| u32 regval; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[CHG_DTLS], ®val); |
| if (err) |
| return err; |
| |
| switch (regval) { |
| case MAX8971_CHARGING_DEAD_BATTERY: |
| case MAX8971_CHARGING_PREQUALIFICATION: |
| *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| break; |
| case MAX8971_CHARGING_FAST_CONST_CURRENT: |
| case MAX8971_CHARGING_FAST_CONST_VOLTAGE: |
| *val = POWER_SUPPLY_CHARGE_TYPE_FAST; |
| break; |
| case MAX8971_CHARGING_TOP_OFF: |
| case MAX8971_CHARGING_THERMAL_LOOP: |
| *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; |
| break; |
| case MAX8971_CHARGING_DONE: |
| case MAX8971_CHARGING_TIMER_FAULT: |
| case MAX8971_CHARGING_SUSPENDED_THERMAL: |
| case MAX8971_CHARGING_OFF: |
| *val = POWER_SUPPLY_CHARGE_TYPE_NONE; |
| break; |
| default: |
| *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; |
| } |
| |
| return 0; |
| } |
| |
| static int max8971_get_health(struct max8971_data *priv, int *val) |
| { |
| u32 regval; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[THM_DTLS], ®val); |
| if (err) |
| return err; |
| |
| switch (regval) { |
| case MAX8971_HEALTH_COLD: |
| *val = POWER_SUPPLY_HEALTH_COLD; |
| break; |
| case MAX8971_HEALTH_COOL: |
| *val = POWER_SUPPLY_HEALTH_COOL; |
| break; |
| case MAX8971_HEALTH_WARM: |
| *val = POWER_SUPPLY_HEALTH_GOOD; |
| break; |
| case MAX8971_HEALTH_HOT: |
| *val = POWER_SUPPLY_HEALTH_HOT; |
| break; |
| case MAX8971_HEALTH_OVERHEAT: |
| *val = POWER_SUPPLY_HEALTH_OVERHEAT; |
| break; |
| case MAX8971_HEALTH_UNKNOWN: |
| default: |
| *val = POWER_SUPPLY_HEALTH_UNKNOWN; |
| } |
| |
| return 0; |
| } |
| |
| static int max8971_get_online(struct max8971_data *priv, int *val) |
| { |
| u32 regval; |
| int err; |
| |
| err = regmap_read(priv->regmap, MAX8971_REG_CHG_STAT, ®val); |
| if (err) |
| return err; |
| |
| if (priv->present) |
| /* CHG_OK bit is 0 when charger is online */ |
| *val = !(regval & MAX8971_CHG_MASK); |
| else |
| *val = priv->present; |
| |
| return 0; |
| } |
| |
| static int max8971_get_integer(struct max8971_data *priv, enum max8971_field_idx fidx, |
| u32 clamp_min, u32 clamp_max, u32 mult, int *val) |
| { |
| u32 regval; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[fidx], ®val); |
| if (err) |
| return err; |
| |
| *val = clamp_val(regval * mult, clamp_min, clamp_max); |
| |
| return 0; |
| } |
| |
| static int max8971_set_integer(struct max8971_data *priv, enum max8971_field_idx fidx, |
| u32 clamp_min, u32 clamp_max, u32 div, int val) |
| { |
| u32 regval; |
| |
| regval = clamp_val(val, clamp_min, clamp_max) / div; |
| |
| return regmap_field_write(priv->rfield[fidx], regval); |
| } |
| |
| static int max8971_get_property(struct power_supply *psy, enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| int err = 0; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| err = max8971_get_status(priv, &val->intval); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| err = max8971_get_charge_type(priv, &val->intval); |
| break; |
| case POWER_SUPPLY_PROP_USB_TYPE: |
| val->intval = priv->usb_type; |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| err = max8971_get_health(priv, &val->intval); |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| err = max8971_get_online(priv, &val->intval); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = priv->present; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: |
| val->intval = MAX8971_CHG_CC_MAX; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: |
| err = max8971_get_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, |
| MAX8971_CHG_CC_STEP, &val->intval); |
| break; |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| err = max8971_get_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, |
| MAX8971_DCILMT_STEP, &val->intval); |
| break; |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| val->strval = max8971_model; |
| break; |
| case POWER_SUPPLY_PROP_MANUFACTURER: |
| val->strval = max8971_manufacturer; |
| break; |
| default: |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int max8971_set_property(struct power_supply *psy, enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| int err = 0; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: |
| err = max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, |
| MAX8971_CHG_CC_STEP, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| err = max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, |
| MAX8971_DCILMT_STEP, val->intval); |
| break; |
| default: |
| err = -EINVAL; |
| } |
| |
| return err; |
| }; |
| |
| static int max8971_property_is_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static enum power_supply_property max8971_properties[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_USB_TYPE, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, |
| POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, |
| POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
| POWER_SUPPLY_PROP_MODEL_NAME, |
| POWER_SUPPLY_PROP_MANUFACTURER, |
| }; |
| |
| static const struct power_supply_desc max8971_charger_desc = { |
| .name = "max8971-charger", |
| .type = POWER_SUPPLY_TYPE_USB, |
| .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | |
| BIT(POWER_SUPPLY_USB_TYPE_SDP) | |
| BIT(POWER_SUPPLY_USB_TYPE_DCP) | |
| BIT(POWER_SUPPLY_USB_TYPE_CDP) | |
| BIT(POWER_SUPPLY_USB_TYPE_ACA), |
| .properties = max8971_properties, |
| .num_properties = ARRAY_SIZE(max8971_properties), |
| .get_property = max8971_get_property, |
| .set_property = max8971_set_property, |
| .property_is_writeable = max8971_property_is_writeable, |
| }; |
| |
| static void max8971_update_config(struct max8971_data *priv) |
| { |
| regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED); |
| |
| if (priv->fchgt != MAX8971_FCHGT_DEFAULT) |
| regmap_field_write(priv->rfield[FCHG_T], priv->fchgt); |
| |
| regmap_write_bits(priv->regmap, MAX8971_REG_DCCRNT, MAX8971_CHGRSTRT_MASK, |
| MAX8971_CHGRSTRT_MASK); |
| |
| if (priv->tofft != MAX8971_TOPOFFT_DEFAULT) |
| regmap_field_write(priv->rfield[TOPOFF_T], priv->tofft); |
| |
| if (priv->toffs) |
| regmap_field_write(priv->rfield[TOPOFF_S], priv->toffs); |
| |
| regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED); |
| } |
| |
| static ssize_t fast_charge_timer_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct power_supply *psy = to_power_supply(dev); |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| u32 regval; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[FCHG_T], ®val); |
| if (err) |
| return err; |
| |
| switch (regval) { |
| case 0x1 ... 0x7: |
| /* Time is off by 3 hours comparing to value */ |
| regval += 3; |
| break; |
| case 0x0: |
| default: |
| regval = 0; |
| break; |
| } |
| |
| return sysfs_emit(buf, "%u\n", regval); |
| } |
| |
| static ssize_t fast_charge_timer_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct power_supply *psy = to_power_supply(dev); |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| unsigned long hours; |
| int val, err; |
| |
| err = kstrtoul(buf, 10, &hours); |
| if (err) |
| return err; |
| |
| val = hours - 3; |
| if (val <= 0 || val > 7) |
| priv->fchgt = 0; |
| else |
| priv->fchgt = val; |
| |
| max8971_update_config(priv); |
| |
| return count; |
| } |
| |
| static ssize_t top_off_threshold_current_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct power_supply *psy = to_power_supply(dev); |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| u32 regval, val; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[TOPOFF_S], ®val); |
| if (err) |
| return err; |
| |
| /* 50uA start with 50uA step */ |
| val = regval * 50 + 50; |
| val *= 1000; |
| |
| return sysfs_emit(buf, "%u\n", val); |
| } |
| |
| static ssize_t top_off_threshold_current_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct power_supply *psy = to_power_supply(dev); |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| unsigned long uamp; |
| int err; |
| |
| err = kstrtoul(buf, 10, &uamp); |
| if (err) |
| return err; |
| |
| if (uamp < 50000 || uamp > 200000) |
| return -EINVAL; |
| |
| priv->toffs = uamp / 50000 - 1; |
| |
| max8971_update_config(priv); |
| |
| return count; |
| } |
| |
| static ssize_t top_off_timer_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct power_supply *psy = to_power_supply(dev); |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| u32 regval; |
| int err; |
| |
| err = regmap_field_read(priv->rfield[TOPOFF_T], ®val); |
| if (err) |
| return err; |
| |
| /* 10 min intervals */ |
| regval *= 10; |
| |
| return sysfs_emit(buf, "%u\n", regval); |
| } |
| |
| static ssize_t top_off_timer_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct power_supply *psy = to_power_supply(dev); |
| struct max8971_data *priv = power_supply_get_drvdata(psy); |
| unsigned long minutes; |
| int err; |
| |
| err = kstrtoul(buf, 10, &minutes); |
| if (err) |
| return err; |
| |
| if (minutes > 70) |
| return -EINVAL; |
| |
| priv->tofft = minutes / 10; |
| |
| max8971_update_config(priv); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR_RW(fast_charge_timer); |
| static DEVICE_ATTR_RW(top_off_threshold_current); |
| static DEVICE_ATTR_RW(top_off_timer); |
| |
| static struct attribute *max8971_attrs[] = { |
| &dev_attr_fast_charge_timer.attr, |
| &dev_attr_top_off_threshold_current.attr, |
| &dev_attr_top_off_timer.attr, |
| NULL |
| }; |
| ATTRIBUTE_GROUPS(max8971); |
| |
| static void max8971_extcon_evt_worker(struct work_struct *work) |
| { |
| struct max8971_data *priv = |
| container_of(work, struct max8971_data, extcon_work.work); |
| struct device *dev = priv->dev; |
| struct extcon_dev *edev = priv->edev; |
| u32 chgcc, dcilmt; |
| |
| if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) { |
| dev_dbg(dev, "USB SDP charger is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; |
| chgcc = 500000; |
| dcilmt = 500000; |
| } else if (extcon_get_state(edev, EXTCON_USB) > 0) { |
| dev_dbg(dev, "USB charger is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; |
| chgcc = 500000; |
| dcilmt = 500000; |
| } else if (extcon_get_state(edev, EXTCON_DISP_MHL) > 0) { |
| dev_dbg(dev, "MHL plug is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; |
| chgcc = 500000; |
| dcilmt = 500000; |
| } else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) { |
| dev_dbg(dev, "USB DCP charger is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_DCP; |
| chgcc = 900000; |
| dcilmt = 1200000; |
| } else if (extcon_get_state(edev, EXTCON_CHG_USB_FAST) > 0) { |
| dev_dbg(dev, "USB FAST charger is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA; |
| chgcc = 900000; |
| dcilmt = 1200000; |
| } else if (extcon_get_state(edev, EXTCON_CHG_USB_SLOW) > 0) { |
| dev_dbg(dev, "USB SLOW charger is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA; |
| chgcc = 900000; |
| dcilmt = 1200000; |
| } else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) { |
| dev_dbg(dev, "USB CDP charger is connected\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_CDP; |
| chgcc = 900000; |
| dcilmt = 1200000; |
| } else { |
| dev_dbg(dev, "USB state is unknown\n"); |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; |
| return; |
| } |
| |
| regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED); |
| |
| max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, |
| MAX8971_CHG_CC_STEP, chgcc); |
| max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, |
| MAX8971_DCILMT_STEP, dcilmt); |
| |
| regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED); |
| } |
| |
| static int extcon_get_charger_type(struct notifier_block *nb, |
| unsigned long state, void *data) |
| { |
| struct max8971_data *priv = |
| container_of(nb, struct max8971_data, extcon_nb); |
| schedule_delayed_work(&priv->extcon_work, 0); |
| |
| return NOTIFY_OK; |
| } |
| |
| static irqreturn_t max8971_interrupt(int irq, void *dev_id) |
| { |
| struct max8971_data *priv = dev_id; |
| struct device *dev = priv->dev; |
| int err, state; |
| |
| err = regmap_read(priv->regmap, MAX8971_REG_CHGINT, &state); |
| if (err) |
| dev_err(dev, "interrupt reg read failed %d\n", err); |
| |
| err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, |
| MAX8971_AICL_MASK, MAX8971_AICL_MASK); |
| if (err) |
| dev_err(dev, "failed to mask IRQ\n"); |
| |
| /* set presence prop */ |
| priv->present = state & MAX8971_REG_CHG_RST; |
| |
| /* on every plug chip resets to default */ |
| if (priv->present) |
| max8971_update_config(priv); |
| |
| /* update supply status */ |
| power_supply_changed(priv->psy_mains); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int max8971_probe(struct i2c_client *client) |
| { |
| struct device *dev = &client->dev; |
| struct max8971_data *priv; |
| struct device_node *extcon; |
| struct power_supply_config cfg = { }; |
| int err, i; |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| priv->dev = dev; |
| priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; |
| |
| i2c_set_clientdata(client, priv); |
| |
| priv->regmap = devm_regmap_init_i2c(client, &max8971_regmap_config); |
| if (IS_ERR(priv->regmap)) |
| return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); |
| |
| for (i = 0; i < MAX8971_N_REGMAP_FIELDS; i++) { |
| priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, max8971_reg_field[i]); |
| if (IS_ERR(priv->rfield[i])) |
| return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), |
| "cannot allocate regmap field\n"); |
| } |
| |
| cfg.attr_grp = max8971_groups; |
| cfg.drv_data = priv; |
| cfg.fwnode = dev_fwnode(dev); |
| |
| priv->psy_mains = devm_power_supply_register(dev, &max8971_charger_desc, &cfg); |
| if (IS_ERR(priv->psy_mains)) |
| return dev_err_probe(dev, PTR_ERR(priv->psy_mains), |
| "failed to register mains supply\n"); |
| |
| err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, MAX8971_AICL_MASK, |
| MAX8971_AICL_MASK); |
| if (err) |
| return dev_err_probe(dev, err, "failed to mask IRQ\n"); |
| |
| err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt, |
| IRQF_ONESHOT | IRQF_SHARED, client->name, priv); |
| if (err) |
| return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq); |
| |
| extcon = of_graph_get_remote_node(dev->of_node, -1, -1); |
| if (!extcon) |
| return 0; |
| |
| priv->edev = extcon_find_edev_by_node(extcon); |
| of_node_put(extcon); |
| if (IS_ERR(priv->edev)) |
| return dev_err_probe(dev, PTR_ERR(priv->edev), "failed to find extcon\n"); |
| |
| err = devm_delayed_work_autocancel(dev, &priv->extcon_work, |
| max8971_extcon_evt_worker); |
| if (err) |
| return dev_err_probe(dev, err, "failed to add extcon evt stop action\n"); |
| |
| priv->extcon_nb.notifier_call = extcon_get_charger_type; |
| |
| err = devm_extcon_register_notifier_all(dev, priv->edev, &priv->extcon_nb); |
| if (err) |
| return dev_err_probe(dev, err, "failed to register notifier\n"); |
| |
| /* Initial configuration work with 1 sec delay */ |
| schedule_delayed_work(&priv->extcon_work, msecs_to_jiffies(1000)); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused max8971_resume(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct max8971_data *priv = i2c_get_clientdata(client); |
| |
| irq_wake_thread(client->irq, priv); |
| |
| return 0; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(max8971_pm_ops, NULL, max8971_resume); |
| |
| static const struct of_device_id max8971_match_ids[] = { |
| { .compatible = "maxim,max8971" }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, max8971_match_ids); |
| |
| static const struct i2c_device_id max8971_i2c_id[] = { |
| { "max8971" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, max8971_i2c_id); |
| |
| static struct i2c_driver max8971_driver = { |
| .driver = { |
| .name = "max8971-charger", |
| .of_match_table = max8971_match_ids, |
| .pm = &max8971_pm_ops, |
| }, |
| .probe = max8971_probe, |
| .id_table = max8971_i2c_id, |
| }; |
| module_i2c_driver(max8971_driver); |
| |
| MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); |
| MODULE_DESCRIPTION("MAX8971 Charger Driver"); |
| MODULE_LICENSE("GPL"); |