| // 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/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_STATUS_1_CHG_STATUS(status1); | |
| if (chg_type >= ARRAY_SIZE(adp5061_chg_type)) | |
| val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
| else | |
| val->intval = adp5061_chg_type[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; | |
| default: | |
| val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; | |
| 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 | |
| * below 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) | |
| { | |
| 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[] = { | |
| { .name = "adp5061" }, | |
| { } | |
| }; | |
| 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"); |