| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * ADP5061 I2C Programmable Linear Battery Charger |
| * |
| * Copyright 2018 Analog Devices Inc. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/delay.h> |
| #include <linux/pm.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/power_supply.h> |
| #include <linux/platform_device.h> |
| #include <linux/of.h> |
| #include <linux/regmap.h> |
| |
| /* ADP5061 registers definition */ |
| #define ADP5061_ID 0x00 |
| #define ADP5061_REV 0x01 |
| #define ADP5061_VINX_SET 0x02 |
| #define ADP5061_TERM_SET 0x03 |
| #define ADP5061_CHG_CURR 0x04 |
| #define ADP5061_VOLTAGE_TH 0x05 |
| #define ADP5061_TIMER_SET 0x06 |
| #define ADP5061_FUNC_SET_1 0x07 |
| #define ADP5061_FUNC_SET_2 0x08 |
| #define ADP5061_INT_EN 0x09 |
| #define ADP5061_INT_ACT 0x0A |
| #define ADP5061_CHG_STATUS_1 0x0B |
| #define ADP5061_CHG_STATUS_2 0x0C |
| #define ADP5061_FAULT 0x0D |
| #define ADP5061_BATTERY_SHORT 0x10 |
| #define ADP5061_IEND 0x11 |
| |
| /* ADP5061_VINX_SET */ |
| #define ADP5061_VINX_SET_ILIM_MSK GENMASK(3, 0) |
| #define ADP5061_VINX_SET_ILIM_MODE(x) (((x) & 0x0F) << 0) |
| |
| /* ADP5061_TERM_SET */ |
| #define ADP5061_TERM_SET_VTRM_MSK GENMASK(7, 2) |
| #define ADP5061_TERM_SET_VTRM_MODE(x) (((x) & 0x3F) << 2) |
| #define ADP5061_TERM_SET_CHG_VLIM_MSK GENMASK(1, 0) |
| #define ADP5061_TERM_SET_CHG_VLIM_MODE(x) (((x) & 0x03) << 0) |
| |
| /* ADP5061_CHG_CURR */ |
| #define ADP5061_CHG_CURR_ICHG_MSK GENMASK(6, 2) |
| #define ADP5061_CHG_CURR_ICHG_MODE(x) (((x) & 0x1F) << 2) |
| #define ADP5061_CHG_CURR_ITRK_DEAD_MSK GENMASK(1, 0) |
| #define ADP5061_CHG_CURR_ITRK_DEAD_MODE(x) (((x) & 0x03) << 0) |
| |
| /* ADP5061_VOLTAGE_TH */ |
| #define ADP5061_VOLTAGE_TH_DIS_RCH_MSK BIT(7) |
| #define ADP5061_VOLTAGE_TH_DIS_RCH_MODE(x) (((x) & 0x01) << 7) |
| #define ADP5061_VOLTAGE_TH_VRCH_MSK GENMASK(6, 5) |
| #define ADP5061_VOLTAGE_TH_VRCH_MODE(x) (((x) & 0x03) << 5) |
| #define ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK GENMASK(4, 3) |
| #define ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(x) (((x) & 0x03) << 3) |
| #define ADP5061_VOLTAGE_TH_VWEAK_MSK GENMASK(2, 0) |
| #define ADP5061_VOLTAGE_TH_VWEAK_MODE(x) (((x) & 0x07) << 0) |
| |
| /* ADP5061_CHG_STATUS_1 */ |
| #define ADP5061_CHG_STATUS_1_VIN_OV(x) (((x) >> 7) & 0x1) |
| #define ADP5061_CHG_STATUS_1_VIN_OK(x) (((x) >> 6) & 0x1) |
| #define ADP5061_CHG_STATUS_1_VIN_ILIM(x) (((x) >> 5) & 0x1) |
| #define ADP5061_CHG_STATUS_1_THERM_LIM(x) (((x) >> 4) & 0x1) |
| #define ADP5061_CHG_STATUS_1_CHDONE(x) (((x) >> 3) & 0x1) |
| #define ADP5061_CHG_STATUS_1_CHG_STATUS(x) (((x) >> 0) & 0x7) |
| |
| /* ADP5061_CHG_STATUS_2 */ |
| #define ADP5061_CHG_STATUS_2_THR_STATUS(x) (((x) >> 5) & 0x7) |
| #define ADP5061_CHG_STATUS_2_RCH_LIM_INFO(x) (((x) >> 3) & 0x1) |
| #define ADP5061_CHG_STATUS_2_BAT_STATUS(x) (((x) >> 0) & 0x7) |
| |
| /* ADP5061_IEND */ |
| #define ADP5061_IEND_IEND_MSK GENMASK(7, 5) |
| #define ADP5061_IEND_IEND_MODE(x) (((x) & 0x07) << 5) |
| |
| #define ADP5061_NO_BATTERY 0x01 |
| #define ADP5061_ICHG_MAX 1300 // mA |
| |
| enum adp5061_chg_status { |
| ADP5061_CHG_OFF, |
| ADP5061_CHG_TRICKLE, |
| ADP5061_CHG_FAST_CC, |
| ADP5061_CHG_FAST_CV, |
| ADP5061_CHG_COMPLETE, |
| ADP5061_CHG_LDO_MODE, |
| ADP5061_CHG_TIMER_EXP, |
| ADP5061_CHG_BAT_DET, |
| }; |
| |
| static const int adp5061_chg_type[4] = { |
| [ADP5061_CHG_OFF] = POWER_SUPPLY_CHARGE_TYPE_NONE, |
| [ADP5061_CHG_TRICKLE] = POWER_SUPPLY_CHARGE_TYPE_TRICKLE, |
| [ADP5061_CHG_FAST_CC] = POWER_SUPPLY_CHARGE_TYPE_FAST, |
| [ADP5061_CHG_FAST_CV] = POWER_SUPPLY_CHARGE_TYPE_FAST, |
| }; |
| |
| static const int adp5061_vweak_th[8] = { |
| 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, |
| }; |
| |
| static const int adp5061_prechg_current[4] = { |
| 5, 10, 20, 80, |
| }; |
| |
| static const int adp5061_vmin[4] = { |
| 2000, 2500, 2600, 2900, |
| }; |
| |
| static const int adp5061_const_chg_vmax[4] = { |
| 3200, 3400, 3700, 3800, |
| }; |
| |
| static const int adp5061_const_ichg[24] = { |
| 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, |
| 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1200, 1300, |
| }; |
| |
| static const int adp5061_vmax[36] = { |
| 3800, 3820, 3840, 3860, 3880, 3900, 3920, 3940, 3960, 3980, |
| 4000, 4020, 4040, 4060, 4080, 4100, 4120, 4140, 4160, 4180, |
| 4200, 4220, 4240, 4260, 4280, 4300, 4320, 4340, 4360, 4380, |
| 4400, 4420, 4440, 4460, 4480, 4500, |
| }; |
| |
| static const int adp5061_in_current_lim[16] = { |
| 100, 150, 200, 250, 300, 400, 500, 600, 700, |
| 800, 900, 1000, 1200, 1500, 1800, 2100, |
| }; |
| |
| static const int adp5061_iend[8] = { |
| 12500, 32500, 52500, 72500, 92500, 117500, 142500, 170000, |
| }; |
| |
| struct adp5061_state { |
| struct i2c_client *client; |
| struct regmap *regmap; |
| struct power_supply *psy; |
| }; |
| |
| static int adp5061_get_array_index(const int *array, u8 size, int val) |
| { |
| int i; |
| |
| for (i = 1; i < size; i++) { |
| if (val < array[i]) |
| break; |
| } |
| |
| return i-1; |
| } |
| |
| static int adp5061_get_status(struct adp5061_state *st, |
| u8 *status1, u8 *status2) |
| { |
| u8 buf[2]; |
| int ret; |
| |
| /* CHG_STATUS1 and CHG_STATUS2 are adjacent regs */ |
| ret = regmap_bulk_read(st->regmap, ADP5061_CHG_STATUS_1, |
| &buf[0], 2); |
| if (ret < 0) |
| return ret; |
| |
| *status1 = buf[0]; |
| *status2 = buf[1]; |
| |
| return ret; |
| } |
| |
| static int adp5061_get_input_current_limit(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int mode, ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_VINX_SET, ®val); |
| if (ret < 0) |
| return ret; |
| |
| mode = ADP5061_VINX_SET_ILIM_MODE(regval); |
| val->intval = adp5061_in_current_lim[mode] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_set_input_current_limit(struct adp5061_state *st, int val) |
| { |
| int index; |
| |
| /* Convert from uA to mA */ |
| val /= 1000; |
| index = adp5061_get_array_index(adp5061_in_current_lim, |
| ARRAY_SIZE(adp5061_in_current_lim), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_VINX_SET, |
| ADP5061_VINX_SET_ILIM_MSK, |
| ADP5061_VINX_SET_ILIM_MODE(index)); |
| } |
| |
| static int adp5061_set_min_voltage(struct adp5061_state *st, int val) |
| { |
| int index; |
| |
| /* Convert from uV to mV */ |
| val /= 1000; |
| index = adp5061_get_array_index(adp5061_vmin, |
| ARRAY_SIZE(adp5061_vmin), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH, |
| ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK, |
| ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(index)); |
| } |
| |
| static int adp5061_get_min_voltage(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val); |
| if (ret < 0) |
| return ret; |
| |
| regval = ((regval & ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK) >> 3); |
| val->intval = adp5061_vmin[regval] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_get_chg_volt_lim(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int mode, ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val); |
| if (ret < 0) |
| return ret; |
| |
| mode = ADP5061_TERM_SET_CHG_VLIM_MODE(regval); |
| val->intval = adp5061_const_chg_vmax[mode] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_get_max_voltage(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val); |
| if (ret < 0) |
| return ret; |
| |
| regval = ((regval & ADP5061_TERM_SET_VTRM_MSK) >> 2) - 0x0F; |
| if (regval >= ARRAY_SIZE(adp5061_vmax)) |
| regval = ARRAY_SIZE(adp5061_vmax) - 1; |
| |
| val->intval = adp5061_vmax[regval] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_set_max_voltage(struct adp5061_state *st, int val) |
| { |
| int vmax_index; |
| |
| /* Convert from uV to mV */ |
| val /= 1000; |
| if (val > 4500) |
| val = 4500; |
| |
| vmax_index = adp5061_get_array_index(adp5061_vmax, |
| ARRAY_SIZE(adp5061_vmax), val); |
| if (vmax_index < 0) |
| return vmax_index; |
| |
| vmax_index += 0x0F; |
| |
| return regmap_update_bits(st->regmap, ADP5061_TERM_SET, |
| ADP5061_TERM_SET_VTRM_MSK, |
| ADP5061_TERM_SET_VTRM_MODE(vmax_index)); |
| } |
| |
| static int adp5061_set_const_chg_vmax(struct adp5061_state *st, int val) |
| { |
| int index; |
| |
| /* Convert from uV to mV */ |
| val /= 1000; |
| index = adp5061_get_array_index(adp5061_const_chg_vmax, |
| ARRAY_SIZE(adp5061_const_chg_vmax), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_TERM_SET, |
| ADP5061_TERM_SET_CHG_VLIM_MSK, |
| ADP5061_TERM_SET_CHG_VLIM_MODE(index)); |
| } |
| |
| static int adp5061_set_const_chg_current(struct adp5061_state *st, int val) |
| { |
| |
| int index; |
| |
| /* Convert from uA to mA */ |
| val /= 1000; |
| if (val > ADP5061_ICHG_MAX) |
| val = ADP5061_ICHG_MAX; |
| |
| index = adp5061_get_array_index(adp5061_const_ichg, |
| ARRAY_SIZE(adp5061_const_ichg), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_CHG_CURR, |
| ADP5061_CHG_CURR_ICHG_MSK, |
| ADP5061_CHG_CURR_ICHG_MODE(index)); |
| } |
| |
| static int adp5061_get_const_chg_current(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val); |
| if (ret < 0) |
| return ret; |
| |
| regval = ((regval & ADP5061_CHG_CURR_ICHG_MSK) >> 2); |
| if (regval >= ARRAY_SIZE(adp5061_const_ichg)) |
| regval = ARRAY_SIZE(adp5061_const_ichg) - 1; |
| |
| val->intval = adp5061_const_ichg[regval] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_get_prechg_current(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val); |
| if (ret < 0) |
| return ret; |
| |
| regval &= ADP5061_CHG_CURR_ITRK_DEAD_MSK; |
| val->intval = adp5061_prechg_current[regval] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_set_prechg_current(struct adp5061_state *st, int val) |
| { |
| int index; |
| |
| /* Convert from uA to mA */ |
| val /= 1000; |
| index = adp5061_get_array_index(adp5061_prechg_current, |
| ARRAY_SIZE(adp5061_prechg_current), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_CHG_CURR, |
| ADP5061_CHG_CURR_ITRK_DEAD_MSK, |
| ADP5061_CHG_CURR_ITRK_DEAD_MODE(index)); |
| } |
| |
| static int adp5061_get_vweak_th(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val); |
| if (ret < 0) |
| return ret; |
| |
| regval &= ADP5061_VOLTAGE_TH_VWEAK_MSK; |
| val->intval = adp5061_vweak_th[regval] * 1000; |
| |
| return ret; |
| } |
| |
| static int adp5061_set_vweak_th(struct adp5061_state *st, int val) |
| { |
| int index; |
| |
| /* Convert from uV to mV */ |
| val /= 1000; |
| index = adp5061_get_array_index(adp5061_vweak_th, |
| ARRAY_SIZE(adp5061_vweak_th), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH, |
| ADP5061_VOLTAGE_TH_VWEAK_MSK, |
| ADP5061_VOLTAGE_TH_VWEAK_MODE(index)); |
| } |
| |
| static int adp5061_get_chg_type(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| u8 status1, status2; |
| int chg_type, ret; |
| |
| ret = adp5061_get_status(st, &status1, &status2); |
| if (ret < 0) |
| return ret; |
| |
| chg_type = adp5061_chg_type[ADP5061_CHG_STATUS_1_CHG_STATUS(status1)]; |
| if (chg_type > ADP5061_CHG_FAST_CV) |
| val->intval = POWER_SUPPLY_STATUS_UNKNOWN; |
| else |
| val->intval = chg_type; |
| |
| return ret; |
| } |
| |
| static int adp5061_get_charger_status(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| u8 status1, status2; |
| int ret; |
| |
| ret = adp5061_get_status(st, &status1, &status2); |
| if (ret < 0) |
| return ret; |
| |
| switch (ADP5061_CHG_STATUS_1_CHG_STATUS(status1)) { |
| case ADP5061_CHG_OFF: |
| val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| break; |
| case ADP5061_CHG_TRICKLE: |
| case ADP5061_CHG_FAST_CC: |
| case ADP5061_CHG_FAST_CV: |
| val->intval = POWER_SUPPLY_STATUS_CHARGING; |
| break; |
| case ADP5061_CHG_COMPLETE: |
| val->intval = POWER_SUPPLY_STATUS_FULL; |
| break; |
| case ADP5061_CHG_TIMER_EXP: |
| /* The battery must be discharging if there is a charge fault */ |
| val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| break; |
| default: |
| val->intval = POWER_SUPPLY_STATUS_UNKNOWN; |
| } |
| |
| return ret; |
| } |
| |
| static int adp5061_get_battery_status(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| u8 status1, status2; |
| int ret; |
| |
| ret = adp5061_get_status(st, &status1, &status2); |
| if (ret < 0) |
| return ret; |
| |
| switch (ADP5061_CHG_STATUS_2_BAT_STATUS(status2)) { |
| case 0x0: /* Battery monitor off */ |
| case 0x1: /* No battery */ |
| val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; |
| break; |
| case 0x2: /* VBAT < VTRK */ |
| val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; |
| break; |
| case 0x3: /* VTRK < VBAT_SNS < VWEAK */ |
| val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; |
| break; |
| case 0x4: /* VBAT_SNS > VWEAK */ |
| val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int adp5061_get_termination_current(struct adp5061_state *st, |
| union power_supply_propval *val) |
| { |
| unsigned int regval; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADP5061_IEND, ®val); |
| if (ret < 0) |
| return ret; |
| |
| regval = (regval & ADP5061_IEND_IEND_MSK) >> 5; |
| val->intval = adp5061_iend[regval]; |
| |
| return ret; |
| } |
| |
| static int adp5061_set_termination_current(struct adp5061_state *st, int val) |
| { |
| int index; |
| |
| index = adp5061_get_array_index(adp5061_iend, |
| ARRAY_SIZE(adp5061_iend), |
| val); |
| if (index < 0) |
| return index; |
| |
| return regmap_update_bits(st->regmap, ADP5061_IEND, |
| ADP5061_IEND_IEND_MSK, |
| ADP5061_IEND_IEND_MODE(index)); |
| } |
| |
| static int adp5061_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct adp5061_state *st = power_supply_get_drvdata(psy); |
| u8 status1, status2; |
| int mode, ret; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_PRESENT: |
| ret = adp5061_get_status(st, &status1, &status2); |
| if (ret < 0) |
| return ret; |
| |
| mode = ADP5061_CHG_STATUS_2_BAT_STATUS(status2); |
| if (mode == ADP5061_NO_BATTERY) |
| val->intval = 0; |
| else |
| val->intval = 1; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| return adp5061_get_chg_type(st, val); |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| /* This property is used to indicate the input current |
| * limit into VINx (ILIM) |
| */ |
| return adp5061_get_input_current_limit(st, val); |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| /* This property is used to indicate the termination |
| * voltage (VTRM) |
| */ |
| return adp5061_get_max_voltage(st, val); |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
| /* |
| * This property is used to indicate the trickle to fast |
| * charge threshold (VTRK_DEAD) |
| */ |
| return adp5061_get_min_voltage(st, val); |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
| /* This property is used to indicate the charging |
| * voltage limit (CHG_VLIM) |
| */ |
| return adp5061_get_chg_volt_lim(st, val); |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
| /* |
| * This property is used to indicate the value of the constant |
| * current charge (ICHG) |
| */ |
| return adp5061_get_const_chg_current(st, val); |
| case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: |
| /* |
| * This property is used to indicate the value of the trickle |
| * and weak charge currents (ITRK_DEAD) |
| */ |
| return adp5061_get_prechg_current(st, val); |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| /* |
| * This property is used to set the VWEAK threshold |
| * bellow this value, weak charge mode is entered |
| * above this value, fast chargerge mode is entered |
| */ |
| return adp5061_get_vweak_th(st, val); |
| case POWER_SUPPLY_PROP_STATUS: |
| /* |
| * Indicate the charger status in relation to power |
| * supply status property |
| */ |
| return adp5061_get_charger_status(st, val); |
| case POWER_SUPPLY_PROP_CAPACITY_LEVEL: |
| /* |
| * Indicate the battery status in relation to power |
| * supply capacity level property |
| */ |
| return adp5061_get_battery_status(st, val); |
| case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
| /* Indicate the values of the termination current */ |
| return adp5061_get_termination_current(st, val); |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int adp5061_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct adp5061_state *st = power_supply_get_drvdata(psy); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| return adp5061_set_input_current_limit(st, val->intval); |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| return adp5061_set_max_voltage(st, val->intval); |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
| return adp5061_set_min_voltage(st, val->intval); |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
| return adp5061_set_const_chg_vmax(st, val->intval); |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
| return adp5061_set_const_chg_current(st, val->intval); |
| case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: |
| return adp5061_set_prechg_current(st, val->intval); |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| return adp5061_set_vweak_th(st, val->intval); |
| case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
| return adp5061_set_termination_current(st, val->intval); |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int adp5061_prop_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
| case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
| return 1; |
| default: |
| return 0; |
| } |
| } |
| |
| static enum power_supply_property adp5061_props[] = { |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, |
| POWER_SUPPLY_PROP_VOLTAGE_MIN, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, |
| POWER_SUPPLY_PROP_PRECHARGE_CURRENT, |
| POWER_SUPPLY_PROP_VOLTAGE_AVG, |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_CAPACITY_LEVEL, |
| POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, |
| }; |
| |
| static const struct regmap_config adp5061_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| }; |
| |
| static const struct power_supply_desc adp5061_desc = { |
| .name = "adp5061", |
| .type = POWER_SUPPLY_TYPE_USB, |
| .get_property = adp5061_get_property, |
| .set_property = adp5061_set_property, |
| .property_is_writeable = adp5061_prop_writeable, |
| .properties = adp5061_props, |
| .num_properties = ARRAY_SIZE(adp5061_props), |
| }; |
| |
| static int adp5061_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct power_supply_config psy_cfg = {}; |
| struct adp5061_state *st; |
| |
| st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); |
| if (!st) |
| return -ENOMEM; |
| |
| st->client = client; |
| st->regmap = devm_regmap_init_i2c(client, |
| &adp5061_regmap_config); |
| if (IS_ERR(st->regmap)) { |
| dev_err(&client->dev, "Failed to initialize register map\n"); |
| return -EINVAL; |
| } |
| |
| i2c_set_clientdata(client, st); |
| psy_cfg.drv_data = st; |
| |
| st->psy = devm_power_supply_register(&client->dev, |
| &adp5061_desc, |
| &psy_cfg); |
| |
| if (IS_ERR(st->psy)) { |
| dev_err(&client->dev, "Failed to register power supply\n"); |
| return PTR_ERR(st->psy); |
| } |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id adp5061_id[] = { |
| { "adp5061", 0}, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, adp5061_id); |
| |
| static struct i2c_driver adp5061_driver = { |
| .driver = { |
| .name = KBUILD_MODNAME, |
| }, |
| .probe = adp5061_probe, |
| .id_table = adp5061_id, |
| }; |
| module_i2c_driver(adp5061_driver); |
| |
| MODULE_DESCRIPTION("Analog Devices adp5061 battery charger driver"); |
| MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>"); |
| MODULE_LICENSE("GPL v2"); |