power supply and reset changes for the 7.4 series

* power-supply core
   - introduce LOAD_SWITCH property
   - pack power_supply_desc to optimize memory
   - prevent power-supply device unregistration while callback runs
   - drop last CONFIG_OF build-time dependencies from the core
   - documentation fixes
  * power-supply drivers
   - bq27xxx: add support for bq27z746, bq28z620
   - ab8500: add AB8505 support
   - bq257xx: fix probe time configuration race condition
   - multiple: convert drivers with custom LOAD_SWITCH properties to the
     new standard one
   - cros_charge-control: restore EC state on resume
   - qcom_battmgr: fix USB type reporting, notify charger online state,
     report full batteries and correct capacity on X1E
   - gpio-charger: always report as discharging if input power is missing
   - bq24190_charger: don't reset registers across system suspend
   - qcom_smbx: a lot of independent fixes
   - lots of error and module removal path fixes
   - misc. fixes
  * reset drivers
   - misc. fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmrdEVEACgkQ2O7X88g7
 +pplTw//SLNG+0B8UHHPDyFndW7gbAlvAAPytFvh1EHhOyYBtuoogqhDPCfVjWkp
 P2ZjTjjfGhYA2k5jaDTt+eP4xLOAuE4e1V5V1JcEbTXfhqWp6Tas+m9LWMK1Wqto
 ozckezlQ4XEDdCRXUjdYKz3WBKk4tyfmZaUZ6+LX6N/a+Dz8VF9MyPitOIYeCTbD
 USBfqcR9Aj98XAP4e9fgQbNVhIifqz6AQ/Vp+YcprIgy8zcYilwn4FwiEXtBsyND
 aUbVrSs7ZGL5F7mgXdvPVR9ZSODWyE3Xpkfywc2dJrGcl2N2fF1lDoupOA781FqG
 r3RQBlZSinO4LWf+idt71ge97e4/Leqd92c46UOHmySnJ4BtkpjamzroFI0++llV
 Y3CjPxYvIZr0PzSBBtsX6xMYGuIJ5SnofZfAaCswHexSEV9Kawem08gYSGK246Il
 fG5gZHC58w1F5ES/6qBOQNEuspJJupezeZvLnPAWI+QyTFeuOGxB8ClsKYSaD6P1
 /1/58DzL/Rf1MCmVO1RaYI9HfUciLX2f/TiXLbgBkJUHWsw8XnUDqvmRmkWrZBky
 HUsXXy8GGoquBdOS2C8X5jd3UZFYBZfn5KiUXwd7kB6f34hlIucc4mYlUJ6LAR5o
 5mYNIk9hxBhuwYHx1Jj7clh/4i4uNAFSPzcwmBwoo91cTgi3t0Y=
 =SDvw
 -----END PGP SIGNATURE-----

Merge tag 'for-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply

Pull power supply and reset updates from Sebastian Reichel:
 "Power-supply core:
   - introduce LOAD_SWITCH property
   - pack power_supply_desc to optimize memory
   - prevent power-supply device unregistration while callback runs
   - drop last CONFIG_OF build-time dependencies from the core
   - documentation fixes

  Power-supply drivers:
   - bq27xxx: add support for bq27z746, bq28z620
   - ab8500: add AB8505 support
   - bq257xx: fix probe time configuration race condition
   - multiple: convert drivers with custom LOAD_SWITCH properties to the
     new standard one
   - cros_charge-control: restore EC state on resume
   - qcom_battmgr: fix USB type reporting, notify charger online state,
     report full batteries and correct capacity on X1E
   - gpio-charger: always report as discharging if input power is
     missing
   - bq24190_charger: don't reset registers across system suspend
   - qcom_smbx: a lot of independent fixes
   - lots of error and module removal path fixes
   - misc fixes

  Reset drivers
   - misc fixes"

* tag 'for-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (77 commits)
  power: supply: gpio-charger: Report discharging without input power
  power: supply: qcom_battmgr: report full batteries
  power_supply: serialize property access with unregister
  power: supply: bq257xx: Describe the dev field in the driver data structure
  power: supply: qcom_battmgr: Notify charger supplies of online changes
  power: supply: generic-adc-battery: fix status with no supplier
  power: supply: qcom_battmgr: report the USB type from USB_ADAP_TYPE
  power: supply: sysfs: Ratelimit "unavailable enum value" warning
  power: supply: cros_charge-control: restore EC state on resume
  power: supply: qcom_smbx: set the charge enable after software control
  power: supply: generic-adc-battery: Propagate charged GPIO errors
  power: supply: sbs-battery: disable polling before supply teardown
  power: supply: pf1550: drain IRQ work before unregistering supplies
  power: supply: bq256xx: Expose VBUS state through extcon
  power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit
  power: supply: ab8500_fg: Finalize current measurements
  power: supply: ab8500_charger: Recover inactive AB8505 charger
  power: supply: ab8500_charger: Retry AB8505 USB detection
  power: supply: ab8500_charger: Skip absent AB8505 main charger
  power: supply: ab8500_charger: Handle detection errors
  ...
This commit is contained in:
Linus Torvalds 2026-10-28 07:06:06 -10:00
commit 0ddbc782c0
54 changed files with 1566 additions and 568 deletions

View File

@ -365,9 +365,12 @@ Contact: linux-pm@vger.kernel.org
Description:
Represents a battery percentage level, above which charging will
stop. Not all hardware is capable of setting this to an arbitrary
percentage. Drivers will round written values to the nearest
supported value. Reading back the value will show the actual
threshold set by the driver.
value, instead providing different minimum, maximum, or step
values. Drivers will round written values to the nearest supported
value. Reading back the value will show the actual threshold set
by the driver. For hardware that only supports a single fixed
value, use charge_types with a value of "Long Life" (vs "Standard")
instead'
Access: Read, Write
@ -398,8 +401,9 @@ Description:
when to start and stop charging. Advanced users
can use this to drastically extend battery life.
Long Life:
The charger reduces its charging rate in order to
prolong the battery health.
The charger firmware reduces its charging rate and/or
maximum charging percentage to a hardware specified
fixed limit in order to prolong the battery health.
Bypass:
The charger bypasses the charging path around the
integrated converter allowing for a "smart" wall
@ -590,6 +594,31 @@ Description:
Valid values: 0 - 100 (percent)
What: /sys/class/power_supply/<supply_name>/load_switch
Date: June 2026
Contact: linux-pm@vger.kernel.org
Description:
Devices can control the connection from power to system load.
For example, with a field-effect transistor between a battery
and the system load (BATFET). This entry controls such switch.
The obvious states are "on" and "off", i.e. there is a
connection or not, respectively. However, many devices can also
enter special modes such as "low-power", "shipping" or "deep
sleep". In these modes the switch is usually off and the
quiescent current quite low.
Access: Read, Write
Valid values:
============= ==================================
"Unknown" (0) Unknown mode.
"On" (1) Power is connected to the load.
"Off" (2) Power is disconnected to the load.
"Standby" (3) Low-power mode.
"Ship" (4) Ship mode.
============= ==================================
**USB Properties**
What: /sys/class/power_supply/<supply_name>/input_current_limit
@ -600,8 +629,8 @@ Description:
supply. Normally this is configured based on the type of
connection made (e.g. A configured SDP should output a maximum
of 500mA so the input current limit is set to the same value).
Use preferably input_power_limit, and for problems that can be
solved using power limit use input_current_limit.
Use preferably input_power_limit, and for problems that can not
be solved using power limit use input_current_limit.
Access: Read, Write
@ -618,8 +647,8 @@ Description:
input power to 5V when the screen is on to meet Google's skin
temperature targets). Note that this feature should not be
used for safety critical things.
Use preferably input_power_limit, and for problems that can be
solved using power limit use input_voltage_limit.
Use preferably input_power_limit, and for problems that can not
be solved using power limit use input_voltage_limit.
Access: Read, Write
@ -633,7 +662,7 @@ Description:
in the supply. Normally this is configured based on
system-level knowledge or user input. Use preferably this
feature to limit the incoming power and use current/voltage
limit only for problems that can be solved using power limit.
limit only for problems that can not be solved using power limit.
Access: Read, Write

View File

@ -77,6 +77,8 @@ Description:
The ship mode, when armed, activates once the input voltage
drops below 1V.
Note: use /sys/class/power_supply/ltc4162-l/load_switch instead.
Access: Read, Write
Valid values: 0 (disable) or 1 (enable)

View File

@ -1,4 +1,4 @@
What: /sys/class/power_supply/mp2629_battery/batt_impedance_compen
What: /sys/class/power_supply/mp2629_battery/batt_impedance_compensation
Date: April 2020
KernelVersion: 5.7
Description:

View File

@ -13,6 +13,8 @@ Description:
'Disable' also can help to leave it, but it's more like to
abort the action before the device really enter shipping mode.
Note: use /sys/class/power_supply/rt9467-*/load_switch instead.
Access: Read, Write
Valid values:
- 1: enabled

View File

@ -11,6 +11,8 @@ Description:
mode. 'Disable' also can help to leave it, but it's more like to abort
the action before the device really enter shipping mode.
Note: use /sys/class/power_supply/rt9471-*/load_switch instead.
Access: Read, Write
Valid values:
- 1: enabled

View File

@ -154,7 +154,8 @@ patternProperties:
of the battery and corresponding battery capacity percent, which is used
to look up battery capacity according to current OCV value. And the open
circuit voltage unit is microvolt.
maxItems: 100
# One point for each capacity percent from 0 to 100
maxItems: 101
items:
items:
- description: open circuit voltage (OCV) in microvolts

View File

@ -47,7 +47,9 @@ properties:
- ti,bq27441
- ti,bq27621
- ti,bq27z561
- ti,bq27z746
- ti,bq28z610
- ti,bq28z620
- ti,bq34z100
- ti,bq78z100
@ -91,8 +93,8 @@ allOf:
- ti,bq27742
- ti,bq27545
- ti,bq27411
- ti,bq27z561
- ti,bq28z610
- ti,bq28z620
- ti,bq34z100
- ti,bq78z100
then:

View File

@ -10,12 +10,13 @@ title: AB8500 Charger
maintainers:
- Sebastian Reichel <sre@kernel.org>
allOf:
- $ref: power-supply.yaml#
properties:
compatible:
const: stericsson,ab8500-charger
oneOf:
- const: stericsson,ab8500-charger
- items:
- const: stericsson,ab8505-charger
- const: stericsson,ab8500-charger
monitored-battery: true
@ -28,27 +29,15 @@ properties:
autopower_cfg:
type: boolean
description: automatic poweron after powerloss
description: AB8500 automatic poweron after powerloss
interrupts:
minItems: 9
maxItems: 14
interrupt-names:
items:
- const: MAIN_CH_UNPLUG_DET
- const: MAIN_CHARGE_PLUG_DET
- const: MAIN_EXT_CH_NOT_OK
- const: MAIN_CH_TH_PROT_R
- const: MAIN_CH_TH_PROT_F
- const: VBUS_DET_F
- const: VBUS_DET_R
- const: USB_LINK_STATUS
- const: USB_CH_TH_PROT_R
- const: USB_CH_TH_PROT_F
- const: USB_CHARGER_NOT_OKR
- const: VBUS_OVV
- const: CH_WD_EXP
- const: VBUS_CH_DROP_END
minItems: 9
maxItems: 14
io-channels:
minItems: 2
@ -74,6 +63,53 @@ required:
- io-channels
- io-channel-names
allOf:
- $ref: power-supply.yaml#
- if:
properties:
compatible:
contains:
const: stericsson,ab8505-charger
then:
properties:
interrupts:
minItems: 9
maxItems: 9
interrupt-names:
items:
- const: VBUS_DET_F
- const: VBUS_DET_R
- const: USB_LINK_STATUS
- const: USB_CH_TH_PROT_R
- const: USB_CH_TH_PROT_F
- const: USB_CHARGER_NOT_OKR
- const: VBUS_OVV
- const: CH_WD_EXP
- const: VBUS_CH_DROP_END
else:
properties:
interrupts:
minItems: 14
maxItems: 14
interrupt-names:
items:
- const: MAIN_CH_UNPLUG_DET
- const: MAIN_CHARGE_PLUG_DET
- const: MAIN_EXT_CH_NOT_OK
- const: MAIN_CH_TH_PROT_R
- const: MAIN_CH_TH_PROT_F
- const: VBUS_DET_F
- const: VBUS_DET_R
- const: USB_LINK_STATUS
- const: USB_CH_TH_PROT_R
- const: USB_CH_TH_PROT_F
- const: USB_CHARGER_NOT_OKR
- const: VBUS_OVV
- const: CH_WD_EXP
- const: VBUS_CH_DROP_END
additionalProperties: false
examples:

View File

@ -82,7 +82,8 @@ _NOW
STATUS
this attribute represents operating status (charging, full,
discharging (i.e., powering a load), etc.). This corresponds to
`BATTERY_STATUS_*` values, as defined in battery.h.
`POWER_SUPPLY_STATUS_*` values, as defined in
include/linux/power_supply.h.
CHARGE_TYPE
batteries can typically charge at different rates.
@ -95,8 +96,8 @@ AUTHENTIC
to the platform is authentic(1) or non-authentic(0).
HEALTH
represents health of the battery. Values corresponds to
POWER_SUPPLY_HEALTH_*, defined in battery.h.
represents health of the battery. Values correspond to
POWER_SUPPLY_HEALTH_*, defined in include/linux/power_supply.h.
VOLTAGE_OCV
open circuit voltage of the battery.
@ -202,16 +203,17 @@ TEMP_AMBIENT_ALERT_MIN
TEMP_AMBIENT_ALERT_MAX
maximum ambient temperature alert.
TEMP_MIN
minimum operatable temperature
minimum operable temperature
TEMP_MAX
maximum operatable temperature
maximum operable temperature
TIME_TO_EMPTY
TIME_TO_EMPTY_NOW, TIME_TO_EMPTY_AVG
seconds left for battery to be considered empty
(i.e., while battery powers a load)
TIME_TO_FULL
(i.e., while battery powers a load). *_NOW is a
momentary reading; *_AVG is averaged over a fixed period.
TIME_TO_FULL_NOW, TIME_TO_FULL_AVG
seconds left for battery to be considered full
(i.e., while battery is charging)
(i.e., while battery is charging). Same now/avg distinction.
Battery <-> external power supply interaction
@ -253,9 +255,6 @@ A:
The attributes available currently are the ones currently provided by the
drivers written.
Good candidates to add in future: model/part#, cycle_time, manufacturer,
etc.
Q:
I have some very specific attribute (e.g., battery color). Should I add

View File

@ -112,8 +112,7 @@ static int rsctrl_probe(struct platform_device *pdev)
for (i = 0; i < WDT_MUX_NUMBER; i++) {
ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val);
if (ret == -EOVERFLOW && !i) {
dev_err(dev, "ti,wdt-list property has to contain at"
"least one entry\n");
dev_err(dev, "ti,wdt-list property has to contain at least one entry\n");
return -EINVAL;
} else if (ret) {
break;

View File

@ -221,8 +221,8 @@ static int macsmc_reboot_probe(struct platform_device *pdev)
if (IS_ERR(cell)) {
if (PTR_ERR(cell) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
nvmem_names[i], PTR_ERR(cell));
dev_warn(&pdev->dev, "Missing NVMEM cell %s (%pe)\n",
nvmem_names[i], cell);
/* Non fatal, we'll deal with it */
cell = NULL;
}

View File

@ -850,6 +850,7 @@ config CHARGER_BQ256XX
tristate "TI BQ256XX battery charger driver"
depends on I2C
depends on GPIOLIB || COMPILE_TEST
depends on EXTCON || !EXTCON
select REGMAP_I2C
help
Say Y to enable support for the TI BQ256XX battery chargers. The

View File

@ -151,8 +151,7 @@
#define CH_OP_CUR_LVL_0P9 0x08
#define CH_OP_CUR_LVL_1P4 0x0D
#define CH_OP_CUR_LVL_1P5 0x0E
#define CH_OP_CUR_LVL_1P6 0x0F
#define CH_OP_CUR_LVL_2P 0x3F
#define CH_OP_CUR_LVL_1P5_MAX 0x0F
/* BTEMP High thermal limits */
#define BTEMP_HIGH_TH_57_0 0x00
@ -233,7 +232,7 @@ enum bup_vch_sel {
/* BatCtrl Current Source Constants */
#define BAT_CTRL_7U_ENA 0x01
#define BAT_CTRL_20U_ENA 0x02
#define BAT_CTRL_18U_ENA 0x01
#define BAT_CTRL_8U_ENA 0x01
#define BAT_CTRL_16U_ENA 0x02
#define BAT_CTRL_CMP_ENA 0x04
#define FORCE_BAT_CTRL_CMP_HIGH 0x08
@ -246,7 +245,6 @@ enum bup_vch_sel {
#define AB8505_RTC_PCUT_CTL_STATUS_REG 0x12
#define AB8505_RTC_PCUT_TIME_REG 0x13
#define AB8505_RTC_PCUT_MAX_TIME_REG 0x14
#define AB8505_RTC_PCUT_FLAG_TIME_REG 0x15
#define AB8505_RTC_PCUT_RESTART_REG 0x16
#define AB8505_RTC_PCUT_DEBOUNCE_REG 0x17
@ -287,7 +285,6 @@ struct ab8500_fg;
* battery full while in maintenance, in per cent
* @pcut_enable: Enable power cut feature in ab8505
* @pcut_max_time: Max time threshold
* @pcut_flag_time: Flagtime threshold
* @pcut_max_restart: Max number of restarts
* @pcut_debounce_time: Sets battery debounce time
*/
@ -308,7 +305,6 @@ struct ab8500_fg_parameters {
int maint_thres;
bool pcut_enable;
u8 pcut_max_time;
u8 pcut_flag_time;
u8 pcut_max_restart;
u8 pcut_debounce_time;
};

View File

@ -98,7 +98,6 @@ static const struct ab8500_fg_parameters fg = {
.user_cap_limit = 15,
.pcut_enable = 1,
.pcut_max_time = 127,
.pcut_flag_time = 112,
.pcut_max_restart = 15,
.pcut_debounce_time = 2,
};
@ -174,7 +173,7 @@ int ab8500_bm_of_probe(struct power_supply *psy,
if (bi->constant_charge_voltage_max_uv < 0)
bi->constant_charge_voltage_max_uv = 4100000;
if (bi->charge_term_current_ua)
if (bi->charge_term_current_ua < 0)
/* Charging stops when we drop below this current */
bi->charge_term_current_ua = 200000;

View File

@ -42,7 +42,7 @@
#define BTEMP_BATCTRL_CURR_SRC_20UA 20
#define BTEMP_BATCTRL_CURR_SRC_16UA 16
#define BTEMP_BATCTRL_CURR_SRC_18UA 18
#define BTEMP_BATCTRL_CURR_SRC_8UA 8
#define BTEMP_BATCTRL_CURR_SRC_60UA 60
#define BTEMP_BATCTRL_CURR_SRC_120UA 120
@ -468,39 +468,29 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di,
*/
static int ab8500_btemp_get_temp(struct ab8500_btemp *di)
{
int temp = 0;
int temp = di->bat_temp;
/*
* The BTEMP events are not reliabe on AB8500 cut3.3
* and prior versions
*/
if (is_ab8500_3p3_or_earlier(di->parent)) {
temp = di->bat_temp * 10;
} else {
if (!is_ab8500_3p3_or_earlier(di->parent)) {
if (di->events.btemp_low) {
if (temp > di->btemp_ranges.btemp_low_limit)
temp = di->btemp_ranges.btemp_low_limit * 10;
else
temp = di->bat_temp * 10;
temp = di->btemp_ranges.btemp_low_limit;
} else if (di->events.btemp_high) {
if (temp < di->btemp_ranges.btemp_high_limit)
temp = di->btemp_ranges.btemp_high_limit * 10;
else
temp = di->bat_temp * 10;
temp = di->btemp_ranges.btemp_high_limit;
} else if (di->events.btemp_lowmed) {
if (temp > di->btemp_ranges.btemp_med_limit)
temp = di->btemp_ranges.btemp_med_limit * 10;
else
temp = di->bat_temp * 10;
temp = di->btemp_ranges.btemp_med_limit;
} else if (di->events.btemp_medhigh) {
if (temp < di->btemp_ranges.btemp_med_limit)
temp = di->btemp_ranges.btemp_med_limit * 10;
else
temp = di->bat_temp * 10;
} else
temp = di->bat_temp * 10;
temp = di->btemp_ranges.btemp_med_limit;
}
}
return temp;
return temp * 10;
}
/**

View File

@ -1241,12 +1241,9 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
charger_status = ab8500_chargalg_check_charger_connection(di);
if (is_ab8500(di->parent)) {
ret = ab8500_chargalg_check_charger_enable(di);
if (ret < 0)
dev_err(di->dev, "Checking charger is enabled error"
": Returned Value %d\n", ret);
}
ret = ab8500_chargalg_check_charger_enable(di);
if (ret < 0)
dev_err(di->dev, "Checking charger enable failed: %d\n", ret);
/*
* First check if we have a charger connected.

View File

@ -52,7 +52,8 @@
#define MAIN_CH_DET 0x01
#define MAIN_CH_CV_ON 0x04
#define USB_CH_CV_ON 0x08
#define VBUS_DET_DBNC100 0x02
#define USB_CH_ON 0x04
#define VBUS_DET_DBNC_LONG 0x02
#define VBUS_DET_DBNC1 0x01
#define OTP_ENABLE_WD 0x01
#define DROP_COUNT_RESET 0x01
@ -66,7 +67,7 @@
#define LED_INDICATOR_PWM_ENA 0x01
#define LED_INDICATOR_PWM_DIS 0x00
#define LED_IND_CUR_5MA 0x04
#define LED_INDICATOR_PWM_DUTY_252_256 0xBF
#define LED_INDICATOR_PWM_DUTY_252_256 0xFB
/* HW failure constants */
#define MAIN_CH_TH_PROT 0x02
@ -99,6 +100,8 @@
#define CHARGER_STATUS_POLL 10 /* in ms */
#define AB8505_USB_CHARGER_NOT_OK_RETRIES 3
#define CHG_WD_INTERVAL (60 * HZ)
#define AB8500_SW_CONTROL_FALLBACK 0x03
@ -136,6 +139,33 @@ enum ab8500_charger_link_status {
USB_STAT_ACA_DOCK_CHARGER,
};
/* UsbLink1Status register values used by AB8505 */
enum ab8505_charger_link_status {
AB8505_USB_STAT_OFF,
AB8505_USB_STAT_SDP_NOT_CHARGING,
AB8505_USB_STAT_SDP_CHARGING,
AB8505_USB_STAT_SDP_SUSPENDED,
AB8505_USB_STAT_CDP,
AB8505_USB_STAT_RESERVED_5,
AB8505_USB_STAT_RESERVED_6,
AB8505_USB_STAT_DCP,
AB8505_USB_STAT_ACA_RID_A,
AB8505_USB_STAT_ACA_RID_B,
AB8505_USB_STAT_ACA_RID_C,
AB8505_USB_STAT_RESERVED_11,
AB8505_USB_STAT_RESERVED_12,
AB8505_USB_STAT_UPSTREAM_IDGND,
AB8505_USB_STAT_CHARGER_NOT_OK,
AB8505_USB_STAT_CHARGER_DM_HIGH,
AB8505_USB_STAT_PHY_ENABLED,
AB8505_USB_STAT_UPSTREAM_NO_IDGND,
AB8505_USB_STAT_UPSTREAM_IDGND_VBUS,
AB8505_USB_STAT_CHARGER_SE1,
AB8505_USB_STAT_CARKIT_1,
AB8505_USB_STAT_CARKIT_2,
AB8505_USB_STAT_ACA_DOCK_CHARGER,
};
enum ab8500_usb_state {
AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */
AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */
@ -234,6 +264,8 @@ struct ab8500_charger_max_usb_in_curr {
* @is_aca_rid: Incicate if accessory is ACA type
* @current_stepping_sessions:
* Counter for current stepping sessions
* @usb_charger_not_ok_retries:
* Number of AB8505 USB charger recovery attempts
* @parent: Pointer to the struct ab8500
* @adc_main_charger_v ADC channel for main charger voltage
* @adc_main_charger_c ADC channel for main charger current
@ -287,6 +319,7 @@ struct ab8500_charger {
int invalid_charger_detect_state;
int is_aca_rid;
atomic_t current_stepping_sessions;
unsigned int usb_charger_not_ok_retries;
struct ab8500 *parent;
struct iio_channel *adc_main_charger_v;
struct iio_channel *adc_main_charger_c;
@ -356,6 +389,10 @@ static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di,
u8 bit;
int ret;
/* SwControlFallback is reserved and must remain clear on AB8505. */
if (is_ab8505(di->parent))
return;
dev_dbg(di->dev, "SW Fallback: %d\n", fallback);
if (is_ab8500(di->parent)) {
@ -442,6 +479,9 @@ static void ab8500_power_supply_changed(struct ab8500_charger *di,
static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
bool connected)
{
if (!connected && !di->vbus_detected)
di->usb_charger_not_ok_retries = 0;
if (connected != di->usb.charger_connected) {
dev_dbg(di->dev, "USB connected:%i\n", connected);
di->usb.charger_connected = connected;
@ -661,27 +701,34 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
int ret;
u8 val;
/* Check for AC charger */
ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_CH_STATUS1_REG, &val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
/* AB8505 has no integrated main charger. */
if (!is_ab8505(di->parent)) {
ret = abx500_get_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_CH_STATUS1_REG, &val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
if (val & MAIN_CH_DET)
result = AC_PW_CONN;
if (val & MAIN_CH_DET)
result = AC_PW_CONN;
}
/* Check for USB charger */
if (!probe) {
/*
* AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100
* AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC_LONG
* when disconnecting ACA even though no
* charger was connected. Try waiting a little
* longer than the 100 ms of VBUS_DET_DBNC100...
* longer than its 100 ms debounce. AB8505 uses
* a 300 ms falling debounce for the same bit.
*/
msleep(110);
if (is_ab8505(di->parent))
msleep(310);
else
msleep(110);
}
ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_CH_USBCH_STAT1_REG, &val);
@ -692,27 +739,118 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
dev_dbg(di->dev,
"%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__,
val);
if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC_LONG))
result |= USB_PW_CONN;
return result;
}
/**
* ab8500_charger_max_usb_curr() - get the max curr for the USB type
* ab8505_charger_max_usb_curr() - get the max current for the USB type
* @di: pointer to the ab8500_charger structure
* @link_status: the identified USB type
*
* Get the maximum current that is allowed to be drawn from the host
* based on the USB type.
* Decode AB8505-specific UsbLink1Status values and get the maximum current
* that is allowed to be drawn based on the USB type.
* Returns error code in case of failure else 0 on success
*/
static int ab8505_charger_max_usb_curr(struct ab8500_charger *di,
u8 link_status)
{
int ret = 0;
di->is_aca_rid = 0;
switch (link_status) {
case AB8505_USB_STAT_SDP_NOT_CHARGING:
case AB8505_USB_STAT_SDP_CHARGING:
case AB8505_USB_STAT_SDP_SUSPENDED:
case AB8505_USB_STAT_CDP:
case AB8505_USB_STAT_CHARGER_DM_HIGH:
case AB8505_USB_STAT_UPSTREAM_NO_IDGND:
case AB8505_USB_STAT_UPSTREAM_IDGND_VBUS:
case AB8505_USB_STAT_CHARGER_SE1:
case AB8505_USB_STAT_CARKIT_1:
case AB8505_USB_STAT_CARKIT_2:
case AB8505_USB_STAT_ACA_DOCK_CHARGER:
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P5;
break;
case AB8505_USB_STAT_DCP:
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P5;
break;
case AB8505_USB_STAT_ACA_RID_A:
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P5;
di->is_aca_rid = 1;
break;
case AB8505_USB_STAT_ACA_RID_B:
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P3;
di->is_aca_rid = 1;
break;
case AB8505_USB_STAT_ACA_RID_C:
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P5;
di->is_aca_rid = 1;
break;
case AB8505_USB_STAT_OFF:
if (di->vbus_detected) {
di->usb_device_is_unrecognised = true;
di->max_usb_in_curr.usb_type_max_ua =
USB_CH_IP_CUR_LVL_1P5;
break;
}
fallthrough;
case AB8505_USB_STAT_UPSTREAM_IDGND:
case AB8505_USB_STAT_PHY_ENABLED:
dev_err(di->dev, "USB Type - Charging not allowed\n");
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
ret = -ENXIO;
break;
case AB8505_USB_STAT_CHARGER_NOT_OK:
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
if (di->usb_charger_not_ok_retries <
AB8505_USB_CHARGER_NOT_OK_RETRIES) {
dev_warn(di->dev,
"USB Type - VBUS has collapsed, retrying\n");
queue_delayed_work(di->charger_wq,
&di->check_usbchgnotok_work, 0);
ret = -EAGAIN;
} else {
dev_err(di->dev,
"USB Type - VBUS recovery failed\n");
ret = -ENXIO;
}
break;
case AB8505_USB_STAT_RESERVED_5:
case AB8505_USB_STAT_RESERVED_6:
case AB8505_USB_STAT_RESERVED_11:
case AB8505_USB_STAT_RESERVED_12:
default:
dev_err(di->dev, "USB Type - Unknown\n");
di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05;
ret = -ENXIO;
break;
}
return ret;
}
/**
* ab8500_charger_max_usb_curr() - get the max current for the USB type
* @di: pointer to the ab8500_charger structure
* @link_status: the identified USB type
*
* Get the maximum current that is allowed to be drawn based on the USB type.
* Returns error code in case of failure else 0 on success.
*/
static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
enum ab8500_charger_link_status link_status)
u8 link_status)
{
int ret = 0;
di->usb_device_is_unrecognised = false;
if (is_ab8505(di->parent)) {
ret = ab8505_charger_max_usb_curr(di, link_status);
goto out;
}
/*
* Platform only supports USB 2.0.
@ -721,7 +859,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
* should set USB_CH_IP_CUR_LVL_0P5.
*/
switch (link_status) {
switch ((enum ab8500_charger_link_status)link_status) {
case USB_STAT_STD_HOST_NC:
case USB_STAT_STD_HOST_C_NS:
case USB_STAT_STD_HOST_C_S:
@ -825,6 +963,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
break;
}
out:
di->max_usb_in_curr.set_max_ua = di->max_usb_in_curr.usb_type_max_ua;
dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
link_status, di->max_usb_in_curr.set_max_ua);
@ -866,8 +1005,7 @@ static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
else
val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
ret = ab8500_charger_max_usb_curr(di,
(enum ab8500_charger_link_status) val);
ret = ab8500_charger_max_usb_curr(di, val);
return ret;
}
@ -929,8 +1067,7 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
if (val)
break;
}
ret = ab8500_charger_max_usb_curr(di,
(enum ab8500_charger_link_status) val);
ret = ab8500_charger_max_usb_curr(di, val);
return ret;
}
@ -1741,6 +1878,9 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
int vset_uv, int iset_ua)
{
u8 usbch_ctrl1 = 0;
u8 usbch_status1;
u8 usbch_status2;
bool reenable;
int ret = 0;
struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
@ -1756,7 +1896,52 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
}
dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1);
if (!(usbch_ctrl1 & USB_CH_ENA)) {
reenable = !(usbch_ctrl1 & USB_CH_ENA);
if (is_ab8505(di->parent)) {
ret = abx500_get_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_CH_USBCH_STAT1_REG,
&usbch_status1);
if (ret < 0) {
dev_err(di->dev, "USB charger status read failed\n");
return ret;
}
if (!reenable && (usbch_status1 & USB_CH_ON))
return 0;
if (!(usbch_status1 & VBUS_DET_DBNC_LONG))
return 0;
ret = abx500_get_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_CH_USBCH_STAT2_REG,
&usbch_status2);
if (ret < 0) {
dev_err(di->dev, "USB charger fault status read failed\n");
return ret;
}
if ((usbch_status2 &
(VBUS_CH_NOK | USB_CH_TH_PROT | VBUS_OVV_TH)) ||
di->flags.usbchargernotok || di->flags.usb_thermal_prot ||
di->flags.vbus_ovv || di->usb.wd_expired)
return 0;
if (!reenable) {
dev_info(di->dev,
"USB charger enabled but inactive, cycling it\n");
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG,
USB_CH_ENA, 0);
if (ret < 0)
return ret;
reenable = true;
}
}
if (reenable) {
dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n");
ret = abx500_mask_and_set_register_interruptible(di->dev,
@ -1778,7 +1963,7 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
}
/**
* ab8500_charger_ac_check_enable() - enable usb charging
* ab8500_charger_ac_check_enable() - enable AC charging
* @charger: pointer to the ux500_charger structure
* @vset_uv: charging voltage in microvolt
* @iset_ua: charger output current in micrompere
@ -1817,7 +2002,7 @@ static int ab8500_charger_ac_check_enable(struct ux500_charger *charger,
return ret;
}
ret = ab8500_charger_ac_en(&di->usb_chg, true, vset_uv, iset_ua);
ret = ab8500_charger_ac_en(&di->ac_chg, true, vset_uv, iset_ua);
if (ret < 0) {
dev_err(di->dev, "failed to enable AC charger %d\n",
__LINE__);
@ -2277,6 +2462,7 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
int ret;
u8 val;
u8 link_status;
u8 link_status_mask;
struct ab8500_charger *di = container_of(work,
struct ab8500_charger, usb_link_status_work);
@ -2303,20 +2489,22 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
AB8500_USB_LINK1_STAT_REG, &val);
if (ret >= 0)
dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
else
dev_dbg(di->dev, "Error reading USB link status\n");
if (ret < 0) {
dev_err(di->dev, "Error reading USB link status\n");
return;
}
dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
if (is_ab8500(di->parent))
link_status = AB8500_USB_LINK_STATUS;
link_status_mask = AB8500_USB_LINK_STATUS;
else
link_status = AB8505_USB_LINK_STATUS;
link_status_mask = AB8505_USB_LINK_STATUS;
link_status = (val & link_status_mask) >> USB_LINK_STATUS_SHIFT;
if (detected_chargers & USB_PW_CONN) {
if (((val & link_status) >> USB_LINK_STATUS_SHIFT) ==
USB_STAT_NOT_VALID_LINK &&
di->invalid_charger_detect_state == 0) {
if (is_ab8500(di->parent) &&
link_status == USB_STAT_NOT_VALID_LINK &&
di->invalid_charger_detect_state == 0) {
dev_dbg(di->dev,
"Invalid charger detected, state= 0\n");
/*Enable charger*/
@ -2339,18 +2527,21 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
abx500_mask_and_set_register_interruptible(di->dev,
AB8500_USB, AB8500_USB_LINE_CTRL2_REG,
USB_CH_DET, 0x00);
/*Check link status*/
if (is_ab8500(di->parent))
ret = abx500_get_register_interruptible(di->dev,
AB8500_USB, AB8500_USB_LINE_STAT_REG,
&val);
else
ret = abx500_get_register_interruptible(di->dev,
AB8500_USB, AB8500_USB_LINK1_STAT_REG,
&val);
/* Check link status */
ret = abx500_get_register_interruptible(di->dev,
AB8500_USB,
AB8500_USB_LINE_STAT_REG,
&val);
if (ret < 0) {
dev_err(di->dev,
"Error reading USB link status\n");
return;
}
link_status = (val & link_status_mask) >>
USB_LINK_STATUS_SHIFT;
dev_dbg(di->dev, "USB link status= 0x%02x\n",
(val & link_status) >> USB_LINK_STATUS_SHIFT);
link_status);
di->invalid_charger_detect_state = 2;
}
} else {
@ -2462,6 +2653,48 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
}
}
static int ab8505_charger_retry_usb_detection(struct ab8500_charger *di)
{
u8 usbch_ctrl1;
bool usb_enabled;
int restore_ret;
int ret;
ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG,
&usbch_ctrl1);
if (ret < 0)
return ret;
usb_enabled = usbch_ctrl1 & USB_CH_ENA;
if (usb_enabled) {
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG,
USB_CH_ENA, 0);
if (ret < 0)
return ret;
}
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_CHARGER_CTRL,
DROP_COUNT_RESET,
DROP_COUNT_RESET);
if (usb_enabled) {
restore_ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG,
USB_CH_ENA,
USB_CH_ENA);
if (restore_ret < 0)
return restore_ret;
}
return ret;
}
/**
* ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status
* @work: pointer to the work_struct structure
@ -2471,7 +2704,10 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
{
int ret;
u8 link_status = 0;
u8 reg_value;
bool charger_not_ok;
bool prev_collapse;
bool prev_status;
struct ab8500_charger *di = container_of(work,
@ -2484,19 +2720,61 @@ static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return;
}
prev_status = di->flags.usbchargernotok;
charger_not_ok = reg_value & VBUS_CH_NOK;
if (is_ab8505(di->parent)) {
ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
AB8500_USB_LINK1_STAT_REG,
&link_status);
if (ret < 0) {
dev_err(di->dev, "%s USB link status read failed\n",
__func__);
return;
}
if (reg_value & VBUS_CH_NOK) {
link_status = (link_status & AB8505_USB_LINK_STATUS) >>
USB_LINK_STATUS_SHIFT;
charger_not_ok |=
link_status == AB8505_USB_STAT_CHARGER_NOT_OK;
}
prev_status = di->flags.usbchargernotok;
prev_collapse = di->flags.vbus_collapse;
if (is_ab8505(di->parent) &&
link_status == AB8505_USB_STAT_CHARGER_NOT_OK &&
di->usb_charger_not_ok_retries <
AB8505_USB_CHARGER_NOT_OK_RETRIES) {
di->usb_charger_not_ok_retries++;
di->flags.usbchargernotok = false;
di->flags.vbus_collapse = true;
dev_warn(di->dev, "Retrying USB charger detection (%u/%u)\n",
di->usb_charger_not_ok_retries,
AB8505_USB_CHARGER_NOT_OK_RETRIES);
ret = ab8505_charger_retry_usb_detection(di);
if (ret < 0)
dev_err(di->dev, "USB charger retry failed: %d\n", ret);
queue_delayed_work(di->charger_wq,
&di->check_usbchgnotok_work, HZ);
} else if (charger_not_ok) {
di->flags.usbchargernotok = true;
if (is_ab8505(di->parent) &&
link_status == AB8505_USB_STAT_CHARGER_NOT_OK) {
di->flags.vbus_collapse = false;
ab8500_charger_set_usb_connected(di, false);
}
/* Check again in 1sec */
queue_delayed_work(di->charger_wq,
&di->check_usbchgnotok_work, HZ);
} else {
di->flags.usbchargernotok = false;
di->flags.vbus_collapse = false;
di->usb_charger_not_ok_retries = 0;
}
if (prev_status != di->flags.usbchargernotok)
if (prev_status != di->flags.usbchargernotok ||
prev_collapse != di->flags.vbus_collapse)
ab8500_power_supply_changed(di, di->usb_chg.psy);
}
@ -3063,7 +3341,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
ret = abx500_set_register_interruptible(di->dev,
AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG,
CH_OP_CUR_LVL_1P6);
CH_OP_CUR_LVL_1P5_MAX);
if (ret) {
dev_err(di->dev,
"failed to set CH_OPT_CRNTLVL_MAX_REG\n");
@ -3071,7 +3349,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
}
}
if (is_ab8505_2p0(di->parent))
if (is_ab8505(di->parent))
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_USBCH_CTRL2_REG,
@ -3340,6 +3618,10 @@ static int ab8500_charger_bind(struct device *dev)
}
ch_stat = ab8500_charger_detect_chargers(di, false);
if (ch_stat < 0) {
destroy_workqueue(di->charger_wq);
return ch_stat;
}
if (ch_stat & AC_PW_CONN) {
if (is_ab8500(di->parent))
@ -3374,7 +3656,8 @@ static void ab8500_charger_unbind(struct device *dev)
int ret;
/* Disable AC charging */
ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
if (di->ac_chg.enabled)
ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
/* Disable USB charging */
ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
@ -3420,11 +3703,14 @@ static int ab8500_charger_probe(struct platform_device *pdev)
di->bm = &ab8500_bm_data;
di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
/* get parent data */
di->dev = dev;
di->parent = dev_get_drvdata(pdev->dev.parent);
di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
if (di->autopower_cfg && is_ab8505(di->parent)) {
dev_warn(dev, "autopower is not supported on AB8505\n");
di->autopower_cfg = false;
}
/* Get ADC channels */
if (!is_ab8505(di->parent)) {
@ -3595,7 +3881,13 @@ static int ab8500_charger_probe(struct platform_device *pdev)
}
/* Request interrupts */
for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
/* The first five interrupts belong to the AB8500 main charger. */
if (is_ab8505(di->parent))
i = 5;
else
i = 0;
for (; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
if (irq < 0)
return irq;
@ -3622,6 +3914,10 @@ static int ab8500_charger_probe(struct platform_device *pdev)
/* Identify the connected charger types during startup */
charger_status = ab8500_charger_detect_chargers(di, true);
if (charger_status < 0) {
ret = charger_status;
goto remove_ab8500_bm;
}
if (charger_status & AC_PW_CONN) {
di->ac.charger_connected = 1;
di->ac_conn = true;

View File

@ -451,6 +451,16 @@ static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample)
avg->avg = sample;
}
static u8 ab8500_fg_cc_config(struct ab8500_fg *di)
{
u8 config = CC_PWR_UP_ENA;
if (!is_ab8505(di->parent))
config |= CC_DEEP_SLEEP_ENA;
return config;
}
/**
* ab8500_fg_coulomb_counter() - enable coulomb counter
* @di: pointer to the ab8500_fg structure
@ -481,7 +491,7 @@ static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
/* Start the CC */
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8500_RTC_CC_CONF_REG,
(CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
ab8500_fg_cc_config(di));
if (ret)
goto cc_err;
@ -555,7 +565,7 @@ int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
/* Start the CC */
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8500_RTC_CC_CONF_REG,
(CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
ab8500_fg_cc_config(di));
if (ret)
goto fail;
} else {
@ -934,9 +944,15 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always)
int i = 0;
int vbat_uv = 0;
int rcomp;
int ret;
/* Average the instant current to get a stable current measurement */
ab8500_fg_inst_curr_start(di);
ret = ab8500_fg_inst_curr_start(di);
if (ret) {
dev_err(di->dev, "failed to start instantaneous current measurement\n");
di->vbat_uv = ab8500_fg_bat_voltage(di);
return di->vbat_uv;
}
do {
vbat_uv += ab8500_fg_bat_voltage(di);
@ -945,15 +961,14 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always)
} while (!ab8500_fg_inst_curr_done(di) &&
i <= WAIT_FOR_INST_CURRENT_MAX);
if (i > WAIT_FOR_INST_CURRENT_MAX) {
ret = ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua);
if (ret) {
dev_err(di->dev,
"TIMEOUT: return uncompensated measurement of VBAT\n");
di->vbat_uv = vbat_uv / i;
return di->vbat_uv;
}
ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua);
/*
* If there is too high current dissipation, the compensation cannot be
* trusted so return an error unless we must return something here, as
@ -1387,6 +1402,13 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
}
}
/* Keep the charge properties current between percentage changes. */
if (di->bat_cap.prev_percent == percent &&
di->bat_cap.prev_mah != di->bat_cap.mah) {
di->bat_cap.prev_mah = di->bat_cap.mah;
changed = true;
}
if (changed) {
if (di->bm->capacity_scaling) {
di->bat_cap.cap_scale.scaled_cap =
@ -2205,42 +2227,36 @@ static int ab8500_fg_get_ext_psy_data(struct power_supply *ext, void *data)
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
switch (ext->desc->type) {
case POWER_SUPPLY_TYPE_BATTERY:
switch (ret.intval) {
case POWER_SUPPLY_STATUS_UNKNOWN:
case POWER_SUPPLY_STATUS_DISCHARGING:
case POWER_SUPPLY_STATUS_NOT_CHARGING:
if (!di->flags.charging)
break;
di->flags.charging = false;
di->flags.fully_charged = false;
if (di->bm->capacity_scaling)
ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
switch (ret.intval) {
case POWER_SUPPLY_STATUS_UNKNOWN:
case POWER_SUPPLY_STATUS_DISCHARGING:
case POWER_SUPPLY_STATUS_NOT_CHARGING:
if (!di->flags.charging)
break;
case POWER_SUPPLY_STATUS_FULL:
if (di->flags.fully_charged)
break;
di->flags.fully_charged = true;
di->flags.force_full = true;
/* Save current capacity as maximum */
di->bat_cap.max_mah = di->bat_cap.mah;
queue_work(di->fg_wq, &di->fg_work);
break;
case POWER_SUPPLY_STATUS_CHARGING:
if (di->flags.charging &&
!di->flags.fully_charged)
break;
di->flags.charging = true;
di->flags.fully_charged = false;
if (di->bm->capacity_scaling)
ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
break;
}
di->flags.charging = false;
di->flags.fully_charged = false;
if (di->bm->capacity_scaling)
ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
break;
default:
case POWER_SUPPLY_STATUS_FULL:
if (di->flags.fully_charged)
break;
di->flags.fully_charged = true;
di->flags.force_full = true;
/* Save current capacity as maximum */
di->bat_cap.max_mah = di->bat_cap.mah;
queue_work(di->fg_wq, &di->fg_work);
break;
case POWER_SUPPLY_STATUS_CHARGING:
if (di->flags.charging &&
!di->flags.fully_charged)
break;
di->flags.charging = true;
di->flags.fully_charged = false;
if (di->bm->capacity_scaling)
ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
break;
}
break;
@ -2354,14 +2370,6 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
goto out;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
goto out;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
@ -2568,56 +2576,6 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
return ret;
}
static ssize_t ab8505_powercut_flagtime_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_to_psy(dev);
struct ab8500_fg *di = power_supply_get_drvdata(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n");
goto fail;
}
return sysfs_emit(buf, "%d\n", (reg_value & 0x7F));
fail:
return ret;
}
static ssize_t ab8505_powercut_flagtime_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
int reg_value;
struct power_supply *psy = dev_to_psy(dev);
struct ab8500_fg *di = power_supply_get_drvdata(psy);
if (kstrtoint(buf, 10, &reg_value))
goto fail;
if (reg_value > 0x7F) {
dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n");
goto fail;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value);
if (ret < 0)
dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n");
fail:
return count;
}
static ssize_t ab8505_powercut_maxtime_read(struct device *dev,
struct device_attribute *attr,
char *buf)
@ -2814,30 +2772,6 @@ static ssize_t ab8505_powercut_write(struct device *dev,
return count;
}
static ssize_t ab8505_powercut_flag_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_to_psy(dev);
struct ab8500_fg *di = power_supply_get_drvdata(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
goto fail;
}
return sysfs_emit(buf, "%d\n", ((reg_value & 0x10) >> 4));
fail:
return ret;
}
static ssize_t ab8505_powercut_debounce_read(struct device *dev,
struct device_attribute *attr,
char *buf)
@ -2912,8 +2846,6 @@ static ssize_t ab8505_powercut_enable_status_read(struct device *dev,
}
static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
__ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write),
__ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write),
__ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP),
@ -2923,7 +2855,6 @@ static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
ab8505_powercut_restart_counter_read, NULL),
__ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_read, ab8505_powercut_write),
__ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL),
__ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_debounce_read, ab8505_powercut_debounce_write),
__ATTR(powercut_enable_status, S_IRUGO,

View File

@ -326,9 +326,6 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
const struct axp_data *axp_data;
int i, irq, ret;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
if (!axp20x) {
dev_err(&pdev->dev, "Parent drvdata not set\n");
return -EINVAL;

View File

@ -1097,9 +1097,6 @@ static int axp20x_power_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
int ret;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
axp20x_batt = devm_kzalloc(&pdev->dev, sizeof(*axp20x_batt),
GFP_KERNEL);
if (!axp20x_batt)

View File

@ -918,73 +918,68 @@ static void axp20x_usb_power_parse_dt(struct device *dev,
static int axp20x_usb_power_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct axp20x_dev *axp20x = dev_get_drvdata(dev->parent);
struct power_supply_config psy_cfg = {};
struct axp20x_usb_power *power;
const struct axp_data *axp_data;
int i, irq, ret;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
if (!axp20x)
return dev_err_probe(dev, -EINVAL, "Parent drvdata not set\n");
if (!axp20x) {
dev_err(&pdev->dev, "Parent drvdata not set\n");
return -EINVAL;
}
axp_data = of_device_get_match_data(dev);
axp_data = of_device_get_match_data(&pdev->dev);
power = devm_kzalloc(&pdev->dev,
struct_size(power, irqs, axp_data->num_irq_names),
power = devm_kzalloc(dev, struct_size(power, irqs, axp_data->num_irq_names),
GFP_KERNEL);
if (!power)
return -ENOMEM;
platform_set_drvdata(pdev, power);
power->dev = &pdev->dev;
power->dev = dev;
power->axp_data = axp_data;
power->regmap = axp20x->regmap;
power->num_irqs = axp_data->num_irq_names;
power->curr_lim_fld = devm_regmap_field_alloc(&pdev->dev, power->regmap,
power->curr_lim_fld = devm_regmap_field_alloc(dev, power->regmap,
axp_data->curr_lim_fld);
if (IS_ERR(power->curr_lim_fld))
return PTR_ERR(power->curr_lim_fld);
axp20x_usb_power_parse_dt(&pdev->dev, power);
axp20x_usb_power_parse_dt(dev, power);
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
ret = axp20x_regmap_field_alloc_optional(dev, power->regmap,
axp_data->vbus_valid_bit,
&power->vbus_valid_bit);
if (ret)
return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
ret = axp20x_regmap_field_alloc_optional(dev, power->regmap,
axp_data->vbus_mon_bit,
&power->vbus_mon_bit);
if (ret)
return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
ret = axp20x_regmap_field_alloc_optional(dev, power->regmap,
axp_data->usb_bc_en_bit,
&power->usb_bc_en_bit);
if (ret)
return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
ret = axp20x_regmap_field_alloc_optional(dev, power->regmap,
axp_data->usb_bc_det_fld,
&power->usb_bc_det_fld);
if (ret)
return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
ret = axp20x_regmap_field_alloc_optional(dev, power->regmap,
axp_data->vbus_disable_bit,
&power->vbus_disable_bit);
if (ret)
return ret;
ret = devm_delayed_work_autocancel(&pdev->dev, &power->vbus_detect,
ret = devm_delayed_work_autocancel(dev, &power->vbus_detect,
axp_data->axp20x_read_vbus);
if (ret)
return ret;
@ -1001,7 +996,9 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
ret = axp_data->axp20x_cfg_adc_reg(power);
if (ret)
return ret;
return dev_err_probe(dev, ret,
"Failed to initialize for %s usage\n",
IS_ENABLED(CONFIG_AXP20X_ADC) ? "iio" : "adc");
}
if (power->usb_bc_en_bit) {
@ -1011,26 +1008,29 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
return ret;
}
psy_cfg.fwnode = dev_fwnode(&pdev->dev);
psy_cfg.fwnode = dev_fwnode(dev);
psy_cfg.drv_data = power;
power->supply = devm_power_supply_register(&pdev->dev,
power->supply = devm_power_supply_register(dev,
axp_data->power_desc,
&psy_cfg);
if (IS_ERR(power->supply))
return PTR_ERR(power->supply);
return dev_err_probe(dev, PTR_ERR(power->supply),
"Failed to register power supply\n");
/* Request irqs after registering, as irqs may trigger immediately */
for (i = 0; i < axp_data->num_irq_names; i++) {
irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
if (irq < 0)
/* platform_get_irq_byname() already produced an error message */
return irq;
power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
ret = devm_request_any_context_irq(dev, power->irqs[i],
axp20x_usb_power_irq, 0,
DRVNAME, power);
if (ret < 0)
/* devm_request_any_context_irq() already produced an error message */
return ret;
}

View File

@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/devm-helpers.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeirq.h>
#include <linux/power_supply.h>
#include <linux/power/bq24190_charger.h>
#include <linux/regulator/driver.h>
@ -1294,6 +1295,58 @@ static int bq24190_charger_set_iinlimit(struct bq24190_dev_info *bdi,
ARRAY_SIZE(bq24190_isc_iinlim_values), val->intval);
}
static int bq24190_charger_get_batfet_ctrl(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 regval;
int ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_MOC,
BQ24190_REG_MOC_BATFET_DISABLE_MASK,
BQ24190_REG_MOC_BATFET_DISABLE_SHIFT,
&regval);
if (ret < 0)
return ret;
/*
* Datasheet defines shipping mode as BATFET off _and_ the watchdog
* disabled. Since the watchdog is disabled during the whole lifetime
* (from probe), shipping mode is the only "off" state that can be
* achievable.
*/
val->intval = regval ? POWER_SUPPLY_LOAD_SWITCH_SHIP :
POWER_SUPPLY_LOAD_SWITCH_ON;
return 0;
}
static int
bq24190_charger_set_batfet_ctrl(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
u8 regval;
/*
* Datasheet defines shipping mode as BATFET off _and_ the watchdog
* disabled. Since the watchdog is disabled during the whole lifetime
* (from probe), shipping mode is the only "off" state that can be
* achievable.
*/
switch (val->intval) {
case POWER_SUPPLY_LOAD_SWITCH_ON:
regval = 0;
break;
case POWER_SUPPLY_LOAD_SWITCH_SHIP:
regval = 1;
break;
default:
return -EINVAL;
}
return bq24190_write_mask(bdi, BQ24190_REG_MOC,
BQ24190_REG_MOC_BATFET_DISABLE_MASK,
BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, regval);
}
static int bq24190_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
@ -1350,6 +1403,9 @@ static int bq24190_charger_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
case POWER_SUPPLY_PROP_LOAD_SWITCH:
ret = bq24190_charger_get_batfet_ctrl(bdi, val);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bdi->model_name;
ret = 0;
@ -1400,6 +1456,9 @@ static int bq24190_charger_set_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = bq24190_charger_set_iinlimit(bdi, val);
break;
case POWER_SUPPLY_PROP_LOAD_SWITCH:
ret = bq24190_charger_set_batfet_ctrl(bdi, val);
break;
default:
ret = -EINVAL;
}
@ -1420,6 +1479,7 @@ static int bq24190_charger_property_is_writeable(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return 1;
default:
return 0;
@ -1480,6 +1540,7 @@ static enum power_supply_property bq24190_charger_properties[] = {
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_LOAD_SWITCH,
};
static char *bq24190_charger_supplied_to[] = {
@ -1498,6 +1559,8 @@ static const struct power_supply_desc bq24190_charger_desc = {
.charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_NONE) |
BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) |
BIT(POWER_SUPPLY_CHARGE_TYPE_FAST),
.load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) |
BIT(POWER_SUPPLY_LOAD_SWITCH_SHIP),
};
/* Battery power supply property routines */
@ -2168,7 +2231,13 @@ static int bq24190_probe(struct i2c_client *client)
if (ret < 0)
goto out_charger;
enable_irq_wake(client->irq);
ret = devm_device_init_wakeup(dev);
if (ret < 0)
goto out_charger;
ret = devm_pm_set_wake_irq(dev, client->irq);
if (ret < 0)
goto out_charger;
pm_runtime_put_autosuspend(dev);
@ -2242,25 +2311,6 @@ static __maybe_unused int bq24190_runtime_resume(struct device *dev)
return 0;
}
static __maybe_unused int bq24190_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
int error;
error = pm_runtime_resume_and_get(bdi->dev);
if (error < 0)
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
bq24190_register_reset(bdi);
if (error >= 0) {
pm_runtime_put_autosuspend(bdi->dev);
}
return 0;
}
static __maybe_unused int bq24190_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
@ -2274,7 +2324,14 @@ static __maybe_unused int bq24190_pm_resume(struct device *dev)
if (error < 0)
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
bq24190_register_reset(bdi);
/*
* The chip kept its configuration through the sleep: it is in host
* mode with the i2c watchdog off, so nothing expired and nothing was
* reset. Do not reset it here either -- a userspace setting such as
* EN_HIZ or IINLIM would otherwise be silently lost on every resume.
* Re-applying the probe-time configuration is idempotent and cheap,
* and covers a part that did somehow fall back to default mode.
*/
bq24190_set_config(bdi);
bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
@ -2293,7 +2350,7 @@ static __maybe_unused int bq24190_pm_resume(struct device *dev)
static const struct dev_pm_ops bq24190_pm_ops = {
SET_RUNTIME_PM_OPS(bq24190_runtime_suspend, bq24190_runtime_resume,
NULL)
SET_SYSTEM_SLEEP_PM_OPS(bq24190_pm_suspend, bq24190_pm_resume)
SET_SYSTEM_SLEEP_PM_OPS(NULL, bq24190_pm_resume)
};
static const struct i2c_device_id bq24190_i2c_ids[] = {

View File

@ -295,6 +295,40 @@ static int bq24257_set_input_current_limit(struct bq24257_device *bq,
BQ24257_IILIMIT_MAP_SIZE));
}
static int bq24257_get_sys_switch(struct bq24257_device *bq,
union power_supply_propval *val)
{
int ret;
ret = bq24257_field_read(bq, F_SYSOFF);
if (ret < 0)
return ret;
val->intval = ret ? POWER_SUPPLY_LOAD_SWITCH_OFF :
POWER_SUPPLY_LOAD_SWITCH_ON;
return 0;
}
static int bq24257_set_sys_switch(struct bq24257_device *bq,
const union power_supply_propval *val)
{
u8 regval;
switch (val->intval) {
case POWER_SUPPLY_LOAD_SWITCH_ON:
regval = 0;
break;
case POWER_SUPPLY_LOAD_SWITCH_OFF:
regval = 1;
break;
default:
return -EINVAL;
}
return bq24257_field_write(bq, F_SYSOFF, regval);
}
static int bq24257_power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@ -381,7 +415,8 @@ static int bq24257_power_supply_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return bq24257_get_input_current_limit(bq, val);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return bq24257_get_sys_switch(bq, val);
default:
return -EINVAL;
}
@ -398,6 +433,8 @@ static int bq24257_power_supply_set_property(struct power_supply *psy,
switch (prop) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return bq24257_set_input_current_limit(bq, val);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return bq24257_set_sys_switch(bq, val);
default:
return -EINVAL;
}
@ -408,6 +445,7 @@ static int bq24257_power_supply_property_is_writeable(struct power_supply *psy,
{
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return true;
default:
return false;
@ -740,6 +778,7 @@ static enum power_supply_property bq24257_power_supply_props[] = {
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_LOAD_SWITCH,
};
static char *bq24257_charger_supplied_to[] = {
@ -749,6 +788,8 @@ static char *bq24257_charger_supplied_to[] = {
static const struct power_supply_desc bq24257_power_supply_desc = {
.name = "bq24257-charger",
.type = POWER_SUPPLY_TYPE_USB,
.load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) |
BIT(POWER_SUPPLY_LOAD_SWITCH_OFF),
.properties = bq24257_power_supply_props,
.num_properties = ARRAY_SIZE(bq24257_power_supply_props),
.get_property = bq24257_power_supply_get_property,

View File

@ -420,7 +420,7 @@ static int bq2515x_get_battery_current_now(struct bq2515x_device *bq2515x)
BQ2515X_ICHG_DIVISOR);
}
static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x)
static int bq2515x_get_charge_disable(struct bq2515x_device *bq2515x)
{
int ret;
int ce_pin;
@ -428,6 +428,8 @@ static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x)
int charger_disable;
ce_pin = gpiod_get_value_cansleep(bq2515x->ce_gpio);
if (ce_pin < 0)
return ce_pin;
ret = regmap_read(bq2515x->regmap, BQ2515X_ICCTRL2, &icctrl2);
if (ret)
@ -435,10 +437,7 @@ static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x)
charger_disable = icctrl2 & BQ2515X_CHARGER_DISABLE;
if (charger_disable || ce_pin)
return true;
return false;
return charger_disable || ce_pin;
}
static int bq2515x_set_charge_disable(struct bq2515x_device *bq2515x, int val)
@ -615,7 +614,11 @@ static int bq2515x_charging_status(struct bq2515x_device *bq2515x,
else
status1_no_fault = false;
ce_status = (!bq2515x_get_charge_disable(bq2515x));
ret = bq2515x_get_charge_disable(bq2515x);
if (ret < 0)
return ret;
ce_status = !ret;
/*
* If there are no faults and charging is enabled, then status is

View File

@ -112,6 +112,12 @@
#define BQ25630_ITERM_MAX 1000000
#define BQ25630_ITERM_STEP 10000
/* BATFET control modes. */
#define BQ25630_BATFET_CTRL_IDLE 0x00
#define BQ25630_BATFET_CTRL_SHUTDOWN 0x01
#define BQ25630_BATFET_CTRL_SHIP 0x02
#define BQ25630_BATFET_CTRL_STANDBY 0x03
/* Charge types. */
#define BQ25630_CHG_STAT_NOT_CHARGING 0x00
#define BQ25630_CHG_STAT_TRICKLE_CHARGE 0x01
@ -587,6 +593,71 @@ static int bq25630_read_vbus(struct bq25630_data *data, int *val)
return 0;
}
static int bq25630_read_batfet_ctrl(struct bq25630_data *data, int *val)
{
unsigned int regval;
int ret;
ret = regmap_field_read(data->regfields[BQ25630_REGF_BATFET_CTRL],
&regval);
if (ret) {
dev_err(data->dev, "Could not read BATFET control (%d)\n", ret);
return ret;
}
switch (regval) {
case BQ25630_BATFET_CTRL_IDLE:
*val = POWER_SUPPLY_LOAD_SWITCH_ON;
break;
case BQ25630_BATFET_CTRL_SHUTDOWN:
*val = POWER_SUPPLY_LOAD_SWITCH_OFF;
break;
case BQ25630_BATFET_CTRL_SHIP:
*val = POWER_SUPPLY_LOAD_SWITCH_SHIP;
break;
case BQ25630_BATFET_CTRL_STANDBY:
*val = POWER_SUPPLY_LOAD_SWITCH_STANDBY;
break;
default:
*val = POWER_SUPPLY_LOAD_SWITCH_UNKNOWN;
}
return 0;
}
static int bq25630_write_batfet_ctrl(struct bq25630_data *data, int val)
{
unsigned int regval;
int ret;
switch (val) {
case POWER_SUPPLY_LOAD_SWITCH_ON:
regval = BQ25630_BATFET_CTRL_IDLE;
break;
case POWER_SUPPLY_LOAD_SWITCH_OFF:
regval = BQ25630_BATFET_CTRL_SHUTDOWN;
break;
case POWER_SUPPLY_LOAD_SWITCH_SHIP:
regval = BQ25630_BATFET_CTRL_SHIP;
break;
case POWER_SUPPLY_LOAD_SWITCH_STANDBY:
regval = BQ25630_BATFET_CTRL_STANDBY;
break;
default:
return -EINVAL;
}
ret = regmap_field_write(data->regfields[BQ25630_REGF_BATFET_CTRL],
regval);
if (ret) {
dev_err(data->dev, "Could not write BATFET control (%d)\n",
ret);
return ret;
}
return 0;
}
static int bq25630_get_status(struct bq25630_data *data, int *val)
{
unsigned int regval;
@ -845,6 +916,9 @@ static int bq25630_charger_get_property(struct power_supply *psy,
BQ25630_ITERM_MIN_REGVAL,
&val->intval);
break;
case POWER_SUPPLY_PROP_LOAD_SWITCH:
ret = bq25630_read_batfet_ctrl(data, &val->intval);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = "BQ25630";
break;
@ -913,6 +987,9 @@ static int bq25630_charger_set_property(struct power_supply *psy,
BQ25630_ITERM_MIN_REGVAL,
val->intval);
break;
case POWER_SUPPLY_PROP_LOAD_SWITCH:
ret = bq25630_write_batfet_ctrl(data, val->intval);
break;
default:
return -EINVAL;
}
@ -932,6 +1009,7 @@ static int bq25630_charger_property_is_writeable(struct power_supply *psy,
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return true;
default:
return false;
@ -954,6 +1032,7 @@ static const enum power_supply_property bq25630_charger_properties[] = {
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_LOAD_SWITCH,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
@ -972,6 +1051,11 @@ static const struct power_supply_desc bq25630_charger_psy_desc = {
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
BIT(POWER_SUPPLY_USB_TYPE_C),
.load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_UNKNOWN) |
BIT(POWER_SUPPLY_LOAD_SWITCH_ON) |
BIT(POWER_SUPPLY_LOAD_SWITCH_OFF) |
BIT(POWER_SUPPLY_LOAD_SWITCH_STANDBY) |
BIT(POWER_SUPPLY_LOAD_SWITCH_SHIP),
.properties = bq25630_charger_properties,
.num_properties = ARRAY_SIZE(bq25630_charger_properties),
.get_property = bq25630_charger_get_property,

View File

@ -16,6 +16,7 @@
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include <linux/extcon-provider.h>
#define BQ256XX_MANUFACTURER "Texas Instruments"
@ -209,6 +210,7 @@ enum bq256xx_id {
* @client: i2c client structure
* @regmap: register map structure
* @dev: device structure
* @edev: extcon device registered to report vbus state changes
* @charger: power supply registered for the charger
* @battery: power supply registered for the battery
* @lock: mutex lock structure
@ -229,6 +231,7 @@ enum bq256xx_id {
struct bq256xx_device {
struct i2c_client *client;
struct device *dev;
struct extcon_dev *edev;
struct power_supply *charger;
struct power_supply *battery;
struct mutex lock;
@ -1164,8 +1167,14 @@ static int bq256xx_get_charger_property(struct power_supply *psy,
return ret;
}
static bool bq256xx_state_changed(struct bq256xx_device *bq,
struct bq256xx_state *new_state)
enum bq256xx_state_change {
CHANGED_NONE,
CHANGED_VBUS,
CHANGED_OTHER
};
static enum bq256xx_state_change bq256xx_state_changed(struct bq256xx_device *bq,
struct bq256xx_state *new_state)
{
struct bq256xx_state old_state;
@ -1173,27 +1182,92 @@ static bool bq256xx_state_changed(struct bq256xx_device *bq,
old_state = bq->state;
mutex_unlock(&bq->lock);
return memcmp(&old_state, new_state, sizeof(struct bq256xx_state)) != 0;
if (new_state->online != old_state.online)
return CHANGED_VBUS;
if (memcmp(&old_state, new_state, sizeof(struct bq256xx_state)) != 0)
return CHANGED_OTHER;
return CHANGED_NONE;
}
static const unsigned int bq256xx_usb_extcon_cable[] = {
EXTCON_USB,
EXTCON_NONE,
};
static void bq256xx_extcon_update(struct bq256xx_device *bq, bool online)
{
int ret;
if (!bq->edev)
return;
ret = extcon_set_state_sync(bq->edev, EXTCON_USB, online);
if (ret)
dev_err(bq->dev, "Failed to update extcon state: %d\n", ret);
}
static int bq256xx_extcon_init(struct bq256xx_device *bq)
{
unsigned int charger_status_0;
int ret;
if (!IS_REACHABLE(CONFIG_EXTCON)) {
dev_dbg(bq->dev, "Extcon support is disabled\n");
return 0;
}
bq->edev = devm_extcon_dev_allocate(bq->dev, bq256xx_usb_extcon_cable);
if (IS_ERR(bq->edev)) {
dev_err(bq->dev, "Failed to allocate extcon device\n");
return PTR_ERR(bq->edev);
}
ret = devm_extcon_dev_register(bq->dev, bq->edev);
if (ret < 0) {
dev_err(bq->dev, "Failed to register extcon device\n");
return ret;
}
ret = regmap_read(bq->regmap, BQ256XX_CHARGER_STATUS_0,
&charger_status_0);
if (ret) {
dev_err(bq->dev, "Failed to read charger status\n");
return ret;
}
bq256xx_extcon_update(bq, !!(charger_status_0 & BQ256XX_PG_STAT_MASK));
return 0;
}
static irqreturn_t bq256xx_irq_handler_thread(int irq, void *private)
{
struct bq256xx_device *bq = private;
struct bq256xx_state state;
enum bq256xx_state_change changed;
int ret;
ret = bq256xx_get_state(bq, &state);
if (ret < 0)
goto irq_out;
if (!bq256xx_state_changed(bq, &state))
changed = bq256xx_state_changed(bq, &state);
switch (changed) {
case CHANGED_NONE:
goto irq_out;
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
case CHANGED_VBUS:
bq256xx_extcon_update(bq, state.online);
fallthrough;
case CHANGED_OTHER:
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
power_supply_changed(bq->charger);
power_supply_changed(bq->charger);
}
irq_out:
return IRQ_HANDLED;
@ -1743,6 +1817,12 @@ static int bq256xx_probe(struct i2c_client *client)
if (!IS_ERR_OR_NULL(bq->usb3_phy))
usb_register_notifier(bq->usb3_phy, &bq->usb_nb);
ret = bq256xx_extcon_init(bq);
if (ret) {
dev_err(dev, "Failed to register extcon device\n");
return ret;
}
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq256xx_irq_handler_thread,

View File

@ -54,6 +54,7 @@ struct bq257xx_chip_info {
* @chip: hw specific functions
* @bq: parent MFD device
* @charger: power supply device
* @dev: charger platform device
* @online: charger input is present
* @charging: charger is actively charging the battery
* @fast_charge: charger is in fast charge mode
@ -73,6 +74,7 @@ struct bq257xx_chg {
const struct bq257xx_chip_info *chip;
struct bq257xx_device *bq;
struct power_supply *charger;
struct device *dev;
bool online;
bool charging;
bool fast_charge;
@ -1055,7 +1057,7 @@ static void bq257xx_external_power_changed(struct power_supply *psy)
pdata->chip->bq257xx_get_state(pdata);
pdata->supplied = power_supply_am_i_supplied(pdata->charger);
pdata->supplied = power_supply_am_i_supplied(psy);
if (pdata->supplied < 0)
return;
@ -1112,6 +1114,54 @@ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private)
return IRQ_HANDLED;
}
/**
* bq257xx_charger_init() - Initialization for charger power supply device
* @psy: power supply device
*
* Set device parameters during registration, before the power supply becomes
* accessible to the broader system.
*
* Return: Returns 0 on success or error code on failure.
*/
static int bq257xx_charger_init(struct power_supply *psy)
{
struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
struct power_supply_battery_info *bat_info;
struct device *dev = pdata->dev;
int ret;
ret = power_supply_get_battery_info(psy, &bat_info);
if (ret)
return dev_err_probe(dev, ret,
"Unable to get battery info\n");
if ((bat_info->voltage_min_design_uv <= 0) ||
(bat_info->constant_charge_voltage_max_uv <= 0) ||
(bat_info->constant_charge_current_max_ua <= 0)) {
power_supply_put_battery_info(psy, bat_info);
return dev_err_probe(dev, -EINVAL,
"Required bat info missing or invalid\n");
}
pdata->vsys_min = bat_info->voltage_min_design_uv;
pdata->vbat_max = bat_info->constant_charge_voltage_max_uv;
pdata->ichg_max = bat_info->constant_charge_current_max_ua;
power_supply_put_battery_info(psy, bat_info);
ret = device_property_read_u32(dev,
"input-current-limit-microamp",
&pdata->iindpm_max);
if (ret)
pdata->iindpm_max = pdata->chip->default_iindpm_uA;
ret = pdata->chip->bq257xx_hw_init(pdata);
if (ret)
return dev_err_probe(dev, ret, "Cannot initialize the charger\n");
return 0;
}
static const struct power_supply_desc bq257xx_power_supply_desc = {
.name = "bq257xx-charger",
.type = POWER_SUPPLY_TYPE_USB,
@ -1126,6 +1176,7 @@ static const struct power_supply_desc bq257xx_power_supply_desc = {
.set_property = bq257xx_set_charger_property,
.property_is_writeable = bq257xx_property_is_writeable,
.external_power_changed = bq257xx_external_power_changed,
.init = bq257xx_charger_init,
};
static const struct bq257xx_chip_info bq25703_chip_info = {
@ -1160,58 +1211,12 @@ static const struct bq257xx_chip_info bq25792_chip_info = {
.bq257xx_get_min_vsys = &bq25792_get_min_vsys,
};
/**
* bq257xx_parse_dt() - Parse the device tree for required properties
* @pdata: driver platform data
* @psy_cfg: power supply config data
* @dev: device struct
*
* Read the device tree to identify the minimum system voltage, the
* maximum charge current, the maximum charge voltage, and the maximum
* input current.
*
* Return: Returns 0 on success or error code on error.
*/
static int bq257xx_parse_dt(struct bq257xx_chg *pdata,
struct power_supply_config *psy_cfg, struct device *dev)
{
struct power_supply_battery_info *bat_info;
int ret;
ret = power_supply_get_battery_info(pdata->charger,
&bat_info);
if (ret)
return dev_err_probe(dev, ret,
"Unable to get battery info\n");
if ((bat_info->voltage_min_design_uv <= 0) ||
(bat_info->constant_charge_voltage_max_uv <= 0) ||
(bat_info->constant_charge_current_max_ua <= 0))
return dev_err_probe(dev, -EINVAL,
"Required bat info missing or invalid\n");
pdata->vsys_min = bat_info->voltage_min_design_uv;
pdata->vbat_max = bat_info->constant_charge_voltage_max_uv;
pdata->ichg_max = bat_info->constant_charge_current_max_ua;
power_supply_put_battery_info(pdata->charger, bat_info);
ret = device_property_read_u32(dev,
"input-current-limit-microamp",
&pdata->iindpm_max);
if (ret)
pdata->iindpm_max = pdata->chip->default_iindpm_uA;
return 0;
}
/**
* bq257xx_charger_probe() - Probe routine for charger platform device
* @pdev: platform device
*
* Probe the charger device, allocate driver data structure, select the
* appropriate chip-specific function pointers, register the power supply,
* parse device tree properties for battery limits, initialize hardware,
* and set up the interrupt handler if available.
*
* Return: Returns 0 on success or error code on failure.
@ -1222,7 +1227,6 @@ static int bq257xx_charger_probe(struct platform_device *pdev)
struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent);
struct bq257xx_chg *pdata;
struct power_supply_config psy_cfg = { };
int ret;
device_set_of_node_from_dev(dev, pdev->dev.parent);
@ -1231,6 +1235,7 @@ static int bq257xx_charger_probe(struct platform_device *pdev)
return -ENOMEM;
pdata->bq = bq;
pdata->dev = dev;
switch (bq->type) {
case BQ25703A:
@ -1255,26 +1260,16 @@ static int bq257xx_charger_probe(struct platform_device *pdev)
return dev_err_probe(dev, PTR_ERR(pdata->charger),
"Power supply register charger failed\n");
ret = bq257xx_parse_dt(pdata, &psy_cfg, dev);
if (ret)
return ret;
ret = pdata->chip->bq257xx_hw_init(pdata);
if (ret)
return dev_err_probe(dev, ret, "Cannot initialize the charger\n");
platform_set_drvdata(pdev, pdata);
if (bq->client->irq) {
ret = devm_request_threaded_irq(dev, bq->client->irq, NULL,
bq257xx_irq_handler_thread,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
dev_name(&bq->client->dev), pdata);
return devm_request_threaded_irq(dev, bq->client->irq, NULL,
bq257xx_irq_handler_thread,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
dev_name(&bq->client->dev), pdata);
}
return ret;
return 0;
}
/**

View File

@ -1590,7 +1590,7 @@ static int bq25890_suspend(struct device *dev)
struct bq25890_device *bq = dev_get_drvdata(dev);
/*
* If charger is removed, while in suspend, make sure ADC is diabled
* If charger is removed, while in suspend, make sure ADC is disabled
* since it consumes slightly more power.
*/
return bq25890_field_write(bq, F_CONV_RATE, 0);

View File

@ -37,12 +37,15 @@
* https://www.ti.com/product/bq27441-g1
* https://www.ti.com/product/bq27621-g1
* https://www.ti.com/product/bq27z561
* https://www.ti.com/product/bq27z746
* https://www.ti.com/product/bq28z610
* https://www.ti.com/product/bq28z620
* https://www.ti.com/product/bq34z100-g1
* https://www.ti.com/product/bq78z100
*/
#include <linux/device.h>
#include <linux/devm-helpers.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/param.h>
@ -501,6 +504,7 @@ static u8
[BQ27XXX_REG_AP] = 0x22,
BQ27XXX_DM_REG_ROWS,
},
#define bq27z746_regs bq27z561_regs
bq28z610_regs[BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
@ -522,6 +526,7 @@ static u8
[BQ27XXX_REG_AP] = 0x22,
BQ27XXX_DM_REG_ROWS,
},
#define bq28z620_regs bq28z610_regs
bq34z100_regs[BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x0c,
@ -836,6 +841,8 @@ static enum power_supply_property bq27z561_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
};
#define bq27z746_props bq27z561_props
static enum power_supply_property bq28z610_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
@ -856,6 +863,8 @@ static enum power_supply_property bq28z610_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
};
#define bq28z620_props bq28z610_props
static enum power_supply_property bq34z100_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
@ -994,7 +1003,9 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = {
#endif
#define bq27z561_dm_regs NULL
#define bq27z746_dm_regs NULL
#define bq28z610_dm_regs NULL
#define bq28z620_dm_regs NULL
#define bq34z100_dm_regs NULL
#define bq78z100_dm_regs NULL
@ -1052,7 +1063,9 @@ static struct {
[BQ27441] = BQ27XXX_DATA(bq27441, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
[BQ27621] = BQ27XXX_DATA(bq27621, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
[BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS),
[BQ27Z746] = BQ27XXX_DATA(bq27z746, 0 , BQ27Z561_O_BITS),
[BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS),
[BQ28Z620] = BQ27XXX_DATA(bq28z620, 0 , BQ27Z561_O_BITS),
[BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \
BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM),
[BQ78Z100] = BQ27XXX_DATA(bq78z100, 0 , BQ27Z561_O_BITS),
@ -2234,7 +2247,6 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
};
int ret;
INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
ret = devm_mutex_init(di->dev, &di->lock);
if (ret)
return ret;
@ -2255,6 +2267,12 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
psy_desc->get_property = bq27xxx_battery_get_property;
psy_desc->external_power_changed = bq27xxx_external_power_changed;
/* Cancel the poll work after the power_supply is unregistered. */
ret = devm_delayed_work_autocancel(di->dev, &di->work,
bq27xxx_battery_poll);
if (ret)
return ret;
di->bat = devm_power_supply_register(di->dev, psy_desc, &psy_cfg);
if (IS_ERR(di->bat))
return dev_err_probe(di->dev, PTR_ERR(di->bat),
@ -2281,8 +2299,6 @@ void bq27xxx_battery_teardown(struct bq27xxx_device_info *di)
mutex_lock(&di->lock);
di->removed = true;
mutex_unlock(&di->lock);
cancel_delayed_work_sync(&di->work);
}
EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown);
@ -2291,7 +2307,7 @@ static int bq27xxx_battery_suspend(struct device *dev)
{
struct bq27xxx_device_info *di = dev_get_drvdata(dev);
cancel_delayed_work(&di->work);
cancel_delayed_work_sync(&di->work);
return 0;
}

View File

@ -253,7 +253,9 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
{ .name = "bq27441", .driver_data = BQ27441 },
{ .name = "bq27621", .driver_data = BQ27621 },
{ .name = "bq27z561", .driver_data = BQ27Z561 },
{ .name = "bq27z746", .driver_data = BQ27Z746 },
{ .name = "bq28z610", .driver_data = BQ28Z610 },
{ .name = "bq28z620", .driver_data = BQ28Z620 },
{ .name = "bq34z100", .driver_data = BQ34Z100 },
{ .name = "bq78z100", .driver_data = BQ78Z100 },
{ }
@ -290,7 +292,9 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = {
{ .compatible = "ti,bq27441" },
{ .compatible = "ti,bq27621" },
{ .compatible = "ti,bq27z561" },
{ .compatible = "ti,bq27z746" },
{ .compatible = "ti,bq28z610" },
{ .compatible = "ti,bq28z620" },
{ .compatible = "ti,bq34z100" },
{ .compatible = "ti,bq78z100" },
{},

View File

@ -904,7 +904,7 @@ static bool cm_setup_timer(void)
}
/**
* charger_extcon_work - enable/diable charger according to the state
* charger_extcon_work - enable/disable charger according to the state
* of charger cable
*
* @work: work_struct of the function charger_extcon_work.

View File

@ -808,8 +808,8 @@ static void cpcap_charger_init_optional_gpios(struct cpcap_charger_ddata *ddata)
ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode",
i, GPIOD_OUT_HIGH);
if (IS_ERR(ddata->gpio[i])) {
dev_info(ddata->dev, "no mode change GPIO%i: %li\n",
i, PTR_ERR(ddata->gpio[i]));
dev_info(ddata->dev, "no mode change GPIO%i: %pe\n",
i, ddata->gpio[i]);
ddata->gpio[i] = NULL;
}
}

View File

@ -13,6 +13,7 @@
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/types.h>
#define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
@ -21,12 +22,22 @@
/*
* Semantics of data *returned* from the EC API and Linux sysfs differ
* slightly, also the v1 API can not return any data.
* To match the expected sysfs API, data is never read back from the EC but
* cached in the driver.
* slightly:
* - EC sustainer off is lower=upper=-1
* - Linux "no limit" is start=0, end=100
* Also the v1 API can not return any data.
*
* Changes to the EC bypassing the driver will not be reflected in sysfs.
* Any change to "charge_behaviour" will synchronize the EC with the driver state.
* While the sustainer is active the EC may report IDLE or DISCHARGE as the
* current charge-control mode; that is an internal hold/discharge step, not a
* Linux inhibit-charge / force-discharge request. Valid sustainer limits are
* therefore adopted as AUTO with the reported thresholds.
*
* Sysfs reads come from a driver-side cache. On probe, command versions that
* support GET (v2+) are initialized from the EC so firmware or firmware-setup
* programmed limits and modes are preserved (unlike earlier behaviour that
* always forced AUTO with no limits). v1 still forces a well-known EC state.
* Subsequent sysfs writes keep the cache and EC in sync; changes that bypass
* the driver are not reflected until the next probe.
*/
struct cros_chctl_priv {
@ -44,18 +55,23 @@ struct cros_chctl_priv {
};
static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec,
u8 cmd_version, struct ec_params_charge_control *req)
u8 cmd_version,
struct ec_params_charge_control *req,
struct ec_response_charge_control *resp)
{
int ret;
static const u8 outsizes[] = {
[1] = offsetof(struct ec_params_charge_control, cmd),
[2] = sizeof(struct ec_params_charge_control),
[3] = sizeof(struct ec_params_charge_control),
};
size_t insize = resp ? sizeof(*resp) : 0;
int ret;
if (resp)
*resp = (struct ec_response_charge_control){};
ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req,
outsizes[cmd_version], NULL, 0);
outsizes[cmd_version], resp, insize);
if (ret < 0)
return ret;
@ -94,7 +110,120 @@ static int cros_chctl_configure_ec(struct cros_chctl_priv *priv)
req.sustain_soc.upper = -1;
}
return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, &req);
return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version,
&req, NULL);
}
static int cros_chctl_get_ec_status(struct cros_chctl_priv *priv,
struct ec_response_charge_control *resp)
{
struct ec_params_charge_control req = {
.cmd = EC_CHARGE_CONTROL_CMD_GET,
};
return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version,
&req, resp);
}
static void cros_chctl_set_default_state(struct cros_chctl_priv *priv)
{
lockdep_assert_held(&priv->lock);
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
priv->current_start_threshold = 0;
priv->current_end_threshold = 100;
}
static bool cros_chctl_sustainer_limits_valid(s8 lower, s8 upper)
{
return lower >= 0 && upper >= 0 && lower <= 100 && upper <= 100 && lower <= upper;
}
static int cros_chctl_adopt_ec_mode(struct cros_chctl_priv *priv, u32 mode)
{
lockdep_assert_held(&priv->lock);
switch (mode) {
case CHARGE_CONTROL_NORMAL:
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
return 0;
case CHARGE_CONTROL_IDLE:
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
return 0;
case CHARGE_CONTROL_DISCHARGE:
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
return 0;
default:
dev_warn(priv->dev, "unknown charge control mode %u\n", mode);
return -EINVAL;
}
}
static int cros_chctl_init_state(struct cros_chctl_priv *priv)
{
struct ec_response_charge_control resp;
s8 lower, upper;
int ret;
guard(mutex)(&priv->lock);
/* v1 cannot report current state; force a well-known EC configuration. */
if (priv->cmd_version < 2) {
cros_chctl_set_default_state(priv);
return cros_chctl_configure_ec(priv);
}
ret = cros_chctl_get_ec_status(priv, &resp);
if (ret < 0) {
dev_warn(priv->dev,
"failed to read EC charge state (%pe), applying defaults\n",
ERR_PTR(ret));
goto defaults;
}
lower = resp.sustain_soc.lower;
upper = resp.sustain_soc.upper;
/*
* Valid sustainer limits mean "auto with thresholds". The EC mode may
* be IDLE/DISCHARGE while the sustainer holds or bleeds SoC; do not
* expose that as inhibit-charge / force-discharge.
*/
if (cros_chctl_sustainer_limits_valid(lower, upper)) {
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
priv->current_start_threshold = lower;
priv->current_end_threshold = upper;
} else {
ret = cros_chctl_adopt_ec_mode(priv, resp.mode);
if (ret < 0)
goto defaults;
/*
* Sustainer off is lower=upper=-1 → Linux "no limit" (0/100).
* Any other non-valid pair is unexpected; remap, warn, and
* push the cleaned state back to the EC.
*/
priv->current_start_threshold = 0;
priv->current_end_threshold = 100;
if (!(lower == -1 && upper == -1)) {
dev_warn(priv->dev,
"invalid EC sustainer limits (%d/%d), treating as no limit\n",
lower, upper);
return cros_chctl_configure_ec(priv);
}
}
dev_dbg(priv->dev,
"adopted EC charge state: behaviour=%d start=%u end=%u (ec mode=%u)\n",
priv->current_behaviour, priv->current_start_threshold,
priv->current_end_threshold, resp.mode);
return 0;
defaults:
cros_chctl_set_default_state(priv);
return cros_chctl_configure_ec(priv);
}
static int cros_chctl_psy_ext_get_prop(struct power_supply *psy,
@ -152,7 +281,6 @@ static int cros_chctl_psy_ext_set_threshold(struct cros_chctl_priv *priv,
return 0;
}
static int cros_chctl_psy_ext_set_prop(struct power_supply *psy,
const struct power_supply_ext *ext,
void *data,
@ -293,6 +421,7 @@ static int cros_chctl_probe(struct platform_device *pdev)
priv->dev = dev;
priv->cros_ec = cros_ec;
dev_set_drvdata(dev, priv);
if (priv->cmd_version == 1)
priv->psy_ext = &cros_chctl_psy_ext_v1;
@ -305,19 +434,35 @@ static int cros_chctl_probe(struct platform_device *pdev)
priv->battery_hook.add_battery = cros_chctl_add_battery;
priv->battery_hook.remove_battery = cros_chctl_remove_battery;
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
priv->current_start_threshold = 0;
priv->current_end_threshold = 100;
/* Bring EC into well-known state */
scoped_guard(mutex, &priv->lock)
ret = cros_chctl_configure_ec(priv);
ret = cros_chctl_init_state(priv);
if (ret < 0)
return ret;
return devm_battery_hook_register(dev, &priv->battery_hook);
}
static int cros_chctl_resume(struct device *dev)
{
struct cros_chctl_priv *priv = dev_get_drvdata(dev);
int ret;
/*
* The EC resets its charge control state when the AP powers off, so the
* configuration is lost across hibernation, while the values exposed to
* userspace still describe it. The cached state is the only remaining
* record of that configuration: reprogram the EC from it rather than
* re-reading the EC, which would adopt the reset defaults instead.
*/
guard(mutex)(&priv->lock);
ret = cros_chctl_configure_ec(priv);
if (ret)
dev_err(dev, "failed to restore charge control state: %d\n", ret);
return ret;
}
static DEFINE_SIMPLE_DEV_PM_OPS(cros_chctl_pm_ops, NULL, cros_chctl_resume);
static const struct platform_device_id cros_chctl_id[] = {
{ .name = "cros-charge-control" },
{ }
@ -325,7 +470,10 @@ static const struct platform_device_id cros_chctl_id[] = {
MODULE_DEVICE_TABLE(platform, cros_chctl_id);
static struct platform_driver cros_chctl_driver = {
.driver.name = "cros-charge-control",
.driver = {
.name = "cros-charge-control",
.pm = pm_sleep_ptr(&cros_chctl_pm_ops),
},
.probe = cros_chctl_probe,
.id_table = cros_chctl_id,
};

View File

@ -263,7 +263,7 @@ static void da9030_charger_check_state(struct da9030_charger *charger)
} else if (charger->adc.vbat_res >
charger->thresholds.vbat_low) {
/* we are charging and passed LOW_THRESH,
so upate DA9030 VBAT threshold
so update DA9030 VBAT threshold
*/
da903x_write(charger->master, DA9030_VBATMON,
charger->thresholds.vbat_low);

View File

@ -135,7 +135,7 @@ static void gab_work(struct work_struct *work)
adc_bat = container_of(delayed_work, struct gab, bat_work);
status = adc_bat->status;
if (!power_supply_am_i_supplied(adc_bat->psy))
if (power_supply_am_i_supplied(adc_bat->psy) <= 0)
adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
else if (gab_charge_finished(adc_bat))
adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
@ -243,6 +243,9 @@ static int gab_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, ret, "Failed to register delayed work\n");
adc_bat->charge_finished = devm_gpiod_get_optional(&pdev->dev, "charged", GPIOD_IN);
if (IS_ERR(adc_bat->charge_finished))
return dev_err_probe(&pdev->dev, PTR_ERR(adc_bat->charge_finished),
"failed to get charged GPIO\n");
if (adc_bat->charge_finished) {
int irq;

View File

@ -101,7 +101,10 @@ static int gpio_charger_get_property(struct power_supply *psy,
val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod);
break;
case POWER_SUPPLY_PROP_STATUS:
if (gpiod_get_value_cansleep(gpio_charger->charge_status))
/* Without input power the battery discharges, whatever the status pin says */
if (gpio_charger->gpiod && !gpiod_get_value_cansleep(gpio_charger->gpiod))
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (gpiod_get_value_cansleep(gpio_charger->charge_status))
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;

View File

@ -349,7 +349,7 @@ static int dc_ti_battery_probe(struct platform_device *pdev)
chip->vbat_channel = devm_iio_channel_get(dev, "VBAT");
if (IS_ERR(chip->vbat_channel)) {
dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel));
dev_dbg(dev, "devm_iio_channel_get() ret %pe\n", chip->vbat_channel);
return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n");
}

View File

@ -464,9 +464,11 @@ static int ltc294x_i2c_probe(struct i2c_client *client)
/* r_sense can be negative, when sense+ is connected to the battery
* instead of the sense-. This results in reversed measurements. */
ret = of_property_read_s32(np, "lltc,resistor-sense", &r_sense);
if (ret < 0)
if (ret < 0) {
of_node_put(np);
return dev_err_probe(&client->dev, ret,
"Could not find lltc,resistor-sense in devicetree\n");
}
info->r_sense = r_sense;
ret = of_property_read_u32(np, "lltc,prescaler-exponent",
@ -477,6 +479,8 @@ static int ltc294x_i2c_probe(struct i2c_client *client)
prescaler_exp = LTC2941_MAX_PRESCALER_EXP;
}
of_node_put(np);
if (info->id == LTC2943_ID) {
if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP)
prescaler_exp = LTC2943_MAX_PRESCALER_EXP;

View File

@ -755,6 +755,41 @@ static int ltc4162l_set_term_current(struct ltc4162l_info *info,
BIT(2), BIT(2));
}
static int ltc4162l_get_ship_mode(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, &regval);
if (ret < 0)
return ret;
val->intval = regval == LTC4162L_ARM_SHIP_MODE_MAGIC ?
POWER_SUPPLY_LOAD_SWITCH_SHIP :
POWER_SUPPLY_LOAD_SWITCH_ON;
return 0;
}
static int ltc4162l_set_ship_mode(struct ltc4162l_info *info, int value)
{
unsigned int regval;
switch (value) {
case POWER_SUPPLY_LOAD_SWITCH_ON:
regval = 0;
break;
case POWER_SUPPLY_LOAD_SWITCH_SHIP:
regval = LTC4162L_ARM_SHIP_MODE_MAGIC;
break;
default:
return -EINVAL;
}
return regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE, regval);
}
/* Custom properties */
static const char * const ltc4162l_charge_status_name[] = {
"ilim_reg_active", /* 32 */
@ -890,15 +925,15 @@ static ssize_t arm_ship_mode_show(struct device *dev,
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
unsigned int regval;
union power_supply_propval val;
int ret;
ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, &regval);
if (ret)
ret = ltc4162l_get_ship_mode(info, &val);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%u\n",
regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0);
val.intval == POWER_SUPPLY_LOAD_SWITCH_SHIP ? 1 : 0);
}
static ssize_t arm_ship_mode_store(struct device *dev,
@ -915,8 +950,9 @@ static ssize_t arm_ship_mode_store(struct device *dev,
if (ret < 0)
return ret;
ret = regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE,
value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0);
ret = ltc4162l_set_ship_mode(info,
value ? POWER_SUPPLY_LOAD_SWITCH_SHIP :
POWER_SUPPLY_LOAD_SWITCH_ON);
if (ret < 0)
return ret;
@ -981,6 +1017,8 @@ static int ltc4162l_get_property(struct power_supply *psy,
return chip_info->get_die_temp(info, val);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return ltc4162l_get_term_current(info, val);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return ltc4162l_get_ship_mode(info, val);
default:
return -EINVAL;
}
@ -1003,6 +1041,8 @@ static int ltc4162l_set_property(struct power_supply *psy,
return ltc4162l_set_iin_limit(info, val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return ltc4162l_set_term_current(info, val->intval);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return ltc4162l_set_ship_mode(info, val->intval);
default:
return -EINVAL;
}
@ -1016,6 +1056,7 @@ static int ltc4162l_property_is_writeable(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return 1;
default:
return 0;
@ -1037,10 +1078,13 @@ static enum power_supply_property ltc4162l_properties[] = {
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_LOAD_SWITCH,
};
static const struct power_supply_desc ltc4162l_desc = {
.type = POWER_SUPPLY_TYPE_MAINS,
.load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) |
BIT(POWER_SUPPLY_LOAD_SWITCH_SHIP),
.properties = ltc4162l_properties,
.num_properties = ARRAY_SIZE(ltc4162l_properties),
.get_property = ltc4162l_get_property,

View File

@ -16,6 +16,7 @@
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/math.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/power_supply.h>
@ -57,10 +58,11 @@
#define MAX17042_CURRENT_LSB 1562500ll /* 1.5625µV/Rsense */
#define MAX17042_CAPACITY_LSB 5000000ll /* 5.0µVH/Rsense */
#define MAX17042_TIME_LSB 5625 / 1000 /* s */
#define MAX17042_VOLTAGE_LSB 625 / 8 /* µV */
#define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */
#define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */
#define MAX17042_TIME_LSB(val) mult_frac(val, 5625, 1000) /* s */
#define MAX17042_VOLTAGE_LSB(val) mult_frac(val, 625, 8) /* µV */
#define MAX17042_RESISTANCE_LSB(val) mult_frac(val, 1, 4096) /* Ω */
#define MAX17042_TEMPERATURE_LSB(val) mult_frac(val, 1, 256) /* °C */
#define MAX17055_DQACC_DIV 32
#define MAX17055_DPACC_FACTOR 44138
@ -138,7 +140,7 @@ static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
*temp = sign_extend32(data, 15);
/* The value is converted into deci-centigrade scale */
*temp = *temp * 10 * MAX17042_TEMPERATURE_LSB;
*temp = MAX17042_TEMPERATURE_LSB(*temp * 10);
return 0;
}
@ -214,7 +216,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
goto health_error;
/* bits [0-3] unused */
vavg = val * MAX17042_VOLTAGE_LSB;
vavg = MAX17042_VOLTAGE_LSB(val);
/* Convert to millivolts */
vavg /= 1000;
@ -223,7 +225,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
goto health_error;
/* bits [0-3] unused */
vbatt = val * MAX17042_VOLTAGE_LSB;
vbatt = MAX17042_VOLTAGE_LSB(val);
/* Convert to millivolts */
vbatt /= 1000;
@ -330,21 +332,21 @@ static int max17042_get_property(struct power_supply *psy,
if (ret < 0)
return ret;
val->intval = data * MAX17042_VOLTAGE_LSB;
val->intval = MAX17042_VOLTAGE_LSB(data);
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = regmap_read(map, MAX17042_AvgVCELL, &data);
if (ret < 0)
return ret;
val->intval = data * MAX17042_VOLTAGE_LSB;
val->intval = MAX17042_VOLTAGE_LSB(data);
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = regmap_read(map, MAX17042_OCVInternal, &data);
if (ret < 0)
return ret;
val->intval = data * MAX17042_VOLTAGE_LSB;
val->intval = MAX17042_VOLTAGE_LSB(data);
break;
case POWER_SUPPLY_PROP_CAPACITY:
if (chip->enable_current_sense)
@ -473,7 +475,7 @@ static int max17042_get_property(struct power_supply *psy,
if (data == U16_MAX)
return -ENODATA;
val->intval = data * MAX17042_TIME_LSB;
val->intval = MAX17042_TIME_LSB(data);
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055 &&
@ -488,7 +490,7 @@ static int max17042_get_property(struct power_supply *psy,
if (data == U16_MAX)
return -ENODATA;
val->intval = data * MAX17042_TIME_LSB;
val->intval = MAX17042_TIME_LSB(data);
break;
default:
return -EINVAL;
@ -1080,7 +1082,7 @@ static int max17042_init_chip(struct max17042_chip *chip)
/* update capacity params */
max17042_update_capacity_regs(chip);
/* delay must be atleast 350mS to allow VFSOC
/* delay must be at least 350mS to allow VFSOC
* to be calculated from the new configuration
*/
msleep(350);

View File

@ -293,6 +293,7 @@ static const struct of_device_id mm8013_match_table[] = {
{ .compatible = "mitsumi,mm8013" },
{}
};
MODULE_DEVICE_TABLE(of, mm8013_match_table);
static struct i2c_driver mm8013_i2c_driver = {
.probe = mm8013_probe,

View File

@ -566,6 +566,22 @@ static int pf1550_charger_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, chg);
psy_cfg.drv_data = chg;
chg->charger = devm_power_supply_register(&pdev->dev,
&pf1550_charger_desc,
&psy_cfg);
if (IS_ERR(chg->charger))
return dev_err_probe(&pdev->dev, PTR_ERR(chg->charger),
"failed: power supply register\n");
chg->battery = devm_power_supply_register(&pdev->dev,
&pf1550_battery_desc,
&psy_cfg);
if (IS_ERR(chg->battery))
return dev_err_probe(&pdev->dev, PTR_ERR(chg->battery),
"failed: power supply register\n");
ret = devm_delayed_work_autocancel(chg->dev, &chg->vbus_sense_work,
pf1550_chg_vbus_work);
if (ret)
@ -584,22 +600,6 @@ static int pf1550_charger_probe(struct platform_device *pdev)
return dev_err_probe(chg->dev, ret,
"failed to add battery sense work\n");
psy_cfg.drv_data = chg;
chg->charger = devm_power_supply_register(&pdev->dev,
&pf1550_charger_desc,
&psy_cfg);
if (IS_ERR(chg->charger))
return dev_err_probe(&pdev->dev, PTR_ERR(chg->charger),
"failed: power supply register\n");
chg->battery = devm_power_supply_register(&pdev->dev,
&pf1550_battery_desc,
&psy_cfg);
if (IS_ERR(chg->battery))
return dev_err_probe(&pdev->dev, PTR_ERR(chg->battery),
"failed: power supply register\n");
for (i = 0; i < PF1550_CHARGER_IRQ_NR; i++) {
irq = platform_get_irq(pdev, i);
if (irq < 0)

View File

@ -190,7 +190,35 @@ static void power_supply_deferred_register_work(struct work_struct *work)
device_unlock(psy->dev.parent);
}
#ifdef CONFIG_OF
static int power_supply_check_supplies_by_name(struct power_supply *psy)
{
struct device *parent = psy->dev.parent;
int nval, ret;
if (!parent)
return 0;
nval = device_property_string_array_count(parent, "supplied-from");
if (nval <= 0)
return 0;
psy->supplied_from = devm_kmalloc_array(&psy->dev, nval,
sizeof(*psy->supplied_from),
GFP_KERNEL);
if (!psy->supplied_from)
return -ENOMEM;
ret = device_property_read_string_array(parent, "supplied-from",
(const char **)psy->supplied_from,
nval);
if (ret < 0)
return ret;
psy->num_supplies = nval;
return 0;
}
static int __power_supply_populate_supplied_from(struct power_supply *epsy,
void *data)
{
@ -262,22 +290,21 @@ static int power_supply_find_supply_from_fwnode(struct fwnode_handle *supply_nod
static int power_supply_check_supplies(struct power_supply *psy)
{
struct fwnode_handle *np;
int cnt = 0;
int cnt, ret;
/* If there is already a list honor it */
if (psy->supplied_from && psy->num_supplies > 0)
return 0;
/* No device node found, nothing to do */
if (!psy->dev.fwnode)
return 0;
for (cnt = 0; psy->dev.fwnode; cnt++) {
np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", cnt);
if (IS_ERR(np)) {
ret = PTR_ERR(np);
if (ret != -ENOENT)
return ret;
do {
int ret;
np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", cnt++);
if (IS_ERR(np))
break;
}
ret = power_supply_find_supply_from_fwnode(np);
fwnode_handle_put(np);
@ -286,48 +313,21 @@ static int power_supply_check_supplies(struct power_supply *psy)
dev_dbg(&psy->dev, "Failed to find supply!\n");
return ret;
}
} while (!IS_ERR(np));
}
/* Missing valid "power-supplies" entries */
if (cnt == 1)
return 0;
/* Fall back to the name-based property if no references were specified. */
if (!cnt)
return power_supply_check_supplies_by_name(psy);
/* All supplies found, allocate char * array for filling */
psy->supplied_from = devm_kcalloc(&psy->dev,
cnt - 1, sizeof(*psy->supplied_from),
cnt, sizeof(*psy->supplied_from),
GFP_KERNEL);
if (!psy->supplied_from)
return -ENOMEM;
return power_supply_populate_supplied_from(psy);
}
#else
static int power_supply_check_supplies(struct power_supply *psy)
{
int nval, ret;
if (!psy->dev.parent)
return 0;
nval = device_property_string_array_count(psy->dev.parent, "supplied-from");
if (nval <= 0)
return 0;
psy->supplied_from = devm_kmalloc_array(&psy->dev, nval,
sizeof(char *), GFP_KERNEL);
if (!psy->supplied_from)
return -ENOMEM;
ret = device_property_read_string_array(psy->dev.parent,
"supplied-from", (const char **)psy->supplied_from, nval);
if (ret < 0)
return ret;
psy->num_supplies = nval;
return 0;
}
#endif
struct psy_am_i_supplied_data {
struct power_supply *psy;
@ -907,7 +907,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
u32 *propdata __free(kfree) = kzalloc_objs(*propdata, proplen);
if (!propdata) {
power_supply_put_battery_info(psy, info);
err = -EINVAL;
err = -ENOMEM;
goto out_put_node;
}
err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen);
@ -1422,6 +1422,12 @@ static int __power_supply_get_property(struct power_supply *psy, enum power_supp
int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp,
union power_supply_propval *val)
{
/* Protect driver data from being free'd */
guard(rwsem_read)(&psy->epc_sem);
if (psy->removing)
return -ENODEV;
return __power_supply_get_property(psy, psp, val, true);
}
EXPORT_SYMBOL_GPL(power_supply_get_property);
@ -1442,7 +1448,7 @@ EXPORT_SYMBOL_GPL(power_supply_get_property);
int power_supply_get_property_direct(struct power_supply *psy, enum power_supply_property psp,
union power_supply_propval *val)
{
return __power_supply_get_property(psy, psp, val, false);
return __power_supply_get_property(psy, psp, val, false);
}
EXPORT_SYMBOL_GPL(power_supply_get_property_direct);
@ -1479,6 +1485,12 @@ static int __power_supply_set_property(struct power_supply *psy, enum power_supp
int power_supply_set_property(struct power_supply *psy, enum power_supply_property psp,
const union power_supply_propval *val)
{
/* Protect driver data from being free'd */
guard(rwsem_read)(&psy->epc_sem);
if (psy->removing)
return -ENODEV;
return __power_supply_set_property(psy, psp, val, true);
}
EXPORT_SYMBOL_GPL(power_supply_set_property);
@ -1608,6 +1620,16 @@ int power_supply_register_extension(struct power_supply *psy, const struct power
sysfs_hwmon_failed:
power_supply_sysfs_remove_extension(psy, ext);
list_del(&reg->list_head);
kfree(reg);
/*
* update_sysfs_and_hwmon() may have already torn down hwmon before
* failing to recreate it. Recreate without the failed extension.
*/
if (power_supply_add_hwmon_sysfs(psy))
dev_warn(&psy->dev, "failed to restore hwmon after extension error\n");
return ret;
sysfs_add_failed:
list_del(&reg->list_head);
kfree(reg);

View File

@ -152,6 +152,14 @@ static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = {
[POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge",
};
static const char *const POWER_SUPPLY_LOAD_SWITCH_TEXT[] = {
[POWER_SUPPLY_LOAD_SWITCH_UNKNOWN] = "Unknown",
[POWER_SUPPLY_LOAD_SWITCH_ON] = "On",
[POWER_SUPPLY_LOAD_SWITCH_OFF] = "Off",
[POWER_SUPPLY_LOAD_SWITCH_STANDBY] = "Standby",
[POWER_SUPPLY_LOAD_SWITCH_SHIP] = "Ship",
};
static struct power_supply_attr power_supply_attrs[] __ro_after_init = {
/* Properties of type `int' */
POWER_SUPPLY_ENUM_ATTR(STATUS),
@ -231,6 +239,7 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = {
POWER_SUPPLY_ATTR(MANUFACTURE_DAY),
POWER_SUPPLY_ATTR(INTERNAL_RESISTANCE),
POWER_SUPPLY_ATTR(STATE_OF_HEALTH),
POWER_SUPPLY_ENUM_ATTR(LOAD_SWITCH),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(MODEL_NAME),
POWER_SUPPLY_ATTR(MANUFACTURER),
@ -301,7 +310,8 @@ static ssize_t power_supply_show_enum_with_available(
}
if (!match) {
dev_warn(dev, "driver reporting unavailable enum value %d\n", value);
dev_warn_ratelimited(dev, "driver reporting unavailable enum value %d\n",
value);
return -EINVAL;
}
@ -400,6 +410,14 @@ static ssize_t power_supply_format_property(struct device *dev,
ret = power_supply_show_charge_types(dev, psy,
value.intval, buf);
break;
case POWER_SUPPLY_PROP_LOAD_SWITCH:
if (uevent) /* no possible values in uevents */
goto default_format;
ret = power_supply_show_enum_with_available(
dev, POWER_SUPPLY_LOAD_SWITCH_TEXT,
ARRAY_SIZE(POWER_SUPPLY_LOAD_SWITCH_TEXT),
psy->desc->load_switches, value.intval, buf);
break;
case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
ret = sysfs_emit(buf, "%s\n", value.strval);
break;

View File

@ -830,6 +830,7 @@ static const enum power_supply_property x1e80100_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@ -975,7 +976,7 @@ static const u8 sm8350_usb_prop_map[] = {
[POWER_SUPPLY_PROP_CURRENT_NOW] = USB_CURR_NOW,
[POWER_SUPPLY_PROP_CURRENT_MAX] = USB_CURR_MAX,
[POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT] = USB_INPUT_CURR_LIMIT,
[POWER_SUPPLY_PROP_USB_TYPE] = USB_TYPE,
[POWER_SUPPLY_PROP_USB_TYPE] = USB_ADAP_TYPE,
};
static int qcom_battmgr_usb_sm8350_update(struct qcom_battmgr *battmgr,
@ -1254,11 +1255,29 @@ static unsigned int qcom_battmgr_sc8280xp_convert_temp(unsigned int temperature)
return DIV_ROUND_CLOSEST(temperature, 10);
}
/*
* The charger supplies' online state comes from the battery status response,
* and the firmware sends no notification of its own when it changes, so
* announce the change here. A supply that is not registered yet announces its
* state when it is.
*/
static void qcom_battmgr_sc8280xp_set_online(struct power_supply *psy,
bool *online, bool value)
{
if (*online == value)
return;
*online = value;
if (psy)
power_supply_changed(psy);
}
static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
const struct qcom_battmgr_message *resp,
size_t len)
{
unsigned int opcode = le32_to_cpu(resp->hdr.opcode);
unsigned int power_state;
unsigned int source;
unsigned int state;
size_t payload_len = len - sizeof(struct pmic_glink_hdr);
@ -1309,23 +1328,31 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr,
return;
}
state = le32_to_cpu(resp->status.battery_state);
if (state & BIT(0))
battmgr->status.status = POWER_SUPPLY_STATUS_DISCHARGING;
else if (state & BIT(1))
battmgr->status.status = POWER_SUPPLY_STATUS_CHARGING;
else
battmgr->status.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
battmgr->status.capacity = le32_to_cpu(resp->status.capacity) * 1000;
battmgr->status.power_now = le32_to_cpu(resp->status.rate) * 1000;
battmgr->status.voltage_now = le32_to_cpu(resp->status.battery_voltage) * 1000;
battmgr->status.temperature = qcom_battmgr_sc8280xp_convert_temp(le32_to_cpu(resp->status.temperature));
state = le32_to_cpu(resp->status.battery_state);
power_state = le32_to_cpu(resp->status.power_state);
if (state & BIT(0))
battmgr->status.status = POWER_SUPPLY_STATUS_DISCHARGING;
else if (state & BIT(1))
battmgr->status.status = POWER_SUPPLY_STATUS_CHARGING;
else if ((power_state & BIT(0)) &&
battmgr->info.last_full_capacity &&
battmgr->status.capacity >= battmgr->info.last_full_capacity)
battmgr->status.status = POWER_SUPPLY_STATUS_FULL;
else
battmgr->status.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
source = le32_to_cpu(resp->status.charging_source);
battmgr->ac.online = source == BATTMGR_CHARGING_SOURCE_AC;
battmgr->usb.online = source == BATTMGR_CHARGING_SOURCE_USB;
battmgr->wireless.online = source == BATTMGR_CHARGING_SOURCE_WIRELESS;
qcom_battmgr_sc8280xp_set_online(battmgr->ac_psy, &battmgr->ac.online,
source == BATTMGR_CHARGING_SOURCE_AC);
qcom_battmgr_sc8280xp_set_online(battmgr->usb_psy, &battmgr->usb.online,
source == BATTMGR_CHARGING_SOURCE_USB);
qcom_battmgr_sc8280xp_set_online(battmgr->wls_psy, &battmgr->wireless.online,
source == BATTMGR_CHARGING_SOURCE_WIRELESS);
if (battmgr->info.last_full_capacity != 0) {
/*
* 100 * battmgr->status.capacity can overflow a 32bit
@ -1513,7 +1540,7 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
case USB_INPUT_CURR_LIMIT:
battmgr->usb.current_limit = le32_to_cpu(resp->intval.value);
break;
case USB_TYPE:
case USB_ADAP_TYPE:
battmgr->usb.usb_type = le32_to_cpu(resp->intval.value);
break;
default:

View File

@ -352,6 +352,9 @@
/* pmi8998 registers represent current in increments of 1/40th of an amp */
#define CURRENT_SCALE_FACTOR 25000
#define SMB2_FLOAT_VOLTAGE_MIN_UV 3487500
#define SMB2_FLOAT_VOLTAGE_MAX_UV 4920000
#define SMB2_FLOAT_VOLTAGE_STEP_UV 7500
/* clang-format on */
enum charger_status {
@ -378,6 +381,7 @@ struct smb_init_register {
* @base: Base address for smb registers
* @regmap: Register map
* @batt_info: Battery data from DT
* @initial_charge_enable: Charging enable state before hardware setup
* @status_change_work: Worker to handle plug/unplug events
* @cable_irq: USB plugin IRQ
* @wakeup_enabled: If the cable IRQ will cause a wakeup
@ -391,6 +395,7 @@ struct smb_chip {
unsigned int base;
struct regmap *regmap;
struct power_supply_battery_info *batt_info;
u8 initial_charge_enable;
struct delayed_work status_change_work;
int cable_irq;
@ -613,7 +618,7 @@ static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
}
if (IS_ERR(chan)) {
dev_err(chip->dev, "Failed to chan, err = %li", PTR_ERR(chan));
dev_err(chip->dev, "Failed to chan, err = %pe", chan);
return PTR_ERR(chan);
}
@ -632,26 +637,20 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
return rc;
}
switch (stat) {
case CHARGER_ERROR_STATUS_BAT_OV_BIT:
if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT)
*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
return 0;
case BAT_TEMP_STATUS_TOO_COLD_BIT:
else if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
*val = POWER_SUPPLY_HEALTH_COLD;
return 0;
case BAT_TEMP_STATUS_TOO_HOT_BIT:
else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
*val = POWER_SUPPLY_HEALTH_OVERHEAT;
return 0;
case BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT:
else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
*val = POWER_SUPPLY_HEALTH_COOL;
return 0;
case BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
*val = POWER_SUPPLY_HEALTH_WARM;
return 0;
default:
else
*val = POWER_SUPPLY_HEALTH_GOOD;
return 0;
}
return 0;
}
static int smb_get_property(struct power_supply *psy,
@ -723,16 +722,22 @@ static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data)
{
struct smb_chip *chip = data;
unsigned int status;
int rc;
regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2,
&status);
rc = regmap_read(chip->regmap,
chip->base + BATTERY_CHARGER_STATUS_2, &status);
if (rc < 0) {
dev_err(chip->dev, "Couldn't read charger status: %d\n", rc);
return IRQ_HANDLED;
}
if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
/* The hardware stops charging automatically */
dev_err(chip->dev, "battery overvoltage detected\n");
power_supply_changed(chip->chg_psy);
}
power_supply_changed(chip->chg_psy);
return IRQ_HANDLED;
}
@ -764,7 +769,7 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
power_supply_changed(chip->chg_psy);
rc = regmap_write(chip->regmap, BARK_BITE_WDOG_PET,
rc = regmap_write(chip->regmap, chip->base + BARK_BITE_WDOG_PET,
BARK_BITE_WDOG_PET_BIT);
if (rc < 0)
dev_err(chip->dev, "Couldn't pet the dog rc=%d\n", rc);
@ -814,11 +819,7 @@ static const struct smb_init_register smb_init_seq[] = {
.mask = SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
.val = VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT },
/* Enable charging */
{ .addr = USBIN_OPTIONS_1_CFG, .mask = HVDCP_EN_BIT, .val = 0 },
{ .addr = CHARGING_ENABLE_CMD,
.mask = CHARGING_ENABLE_CMD_BIT,
.val = CHARGING_ENABLE_CMD_BIT },
/*
* Match downstream defaults
* CHG_EN_SRC_BIT - charger enable is controlled by software
@ -839,6 +840,13 @@ static const struct smb_init_register smb_init_seq[] = {
AUTO_RECHG_BIT | EN_ANALOG_DROP_IN_VBATT_BIT |
CHARGER_INHIBIT_BIT,
.val = CHARGER_INHIBIT_BIT },
/*
* Set the enable command only once CHGR_CFG2 has selected software
* control, or a charger its watchdog has disabled stays disabled.
*/
{ .addr = CHARGING_ENABLE_CMD,
.mask = CHARGING_ENABLE_CMD_BIT,
.val = CHARGING_ENABLE_CMD_BIT },
/* STAT pin software override, match downstream. Parallel charging? */
{ .addr = STAT_CFG,
.mask = STAT_SW_OVERRIDE_CFG_BIT,
@ -909,6 +917,19 @@ static int smb_init_hw(struct smb_chip *chip)
return 0;
}
static void smb_restore_charge_enable(void *data)
{
struct smb_chip *chip = data;
int rc;
rc = regmap_update_bits(chip->regmap,
chip->base + CHARGING_ENABLE_CMD,
CHARGING_ENABLE_CMD_BIT,
chip->initial_charge_enable);
if (rc < 0)
dev_err(chip->dev, "Couldn't restore charging state: %d\n", rc);
}
static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
irqreturn_t (*handler)(int irq, void *data))
{
@ -935,6 +956,9 @@ static int smb_probe(struct platform_device *pdev)
struct power_supply_config supply_config = {};
struct power_supply_desc *desc;
struct smb_chip *chip;
unsigned int charge_enable;
unsigned int float_voltage_sel;
int float_voltage_uv;
int rc, irq;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
@ -965,6 +989,18 @@ static int smb_probe(struct platform_device *pdev)
"Couldn't get usbin_i IIO channel\n");
}
rc = regmap_read(chip->regmap, chip->base + CHARGING_ENABLE_CMD,
&charge_enable);
if (rc < 0)
return dev_err_probe(chip->dev, rc,
"Couldn't read charging state\n");
chip->initial_charge_enable = charge_enable & CHARGING_ENABLE_CMD_BIT;
rc = devm_add_action_or_reset(chip->dev, smb_restore_charge_enable, chip);
if (rc)
return dev_err_probe(chip->dev, rc,
"Couldn't register charging state rollback\n");
rc = smb_init_hw(chip);
if (rc < 0)
return rc;
@ -972,7 +1008,7 @@ static int smb_probe(struct platform_device *pdev)
supply_config.drv_data = chip;
supply_config.fwnode = dev_fwnode(&pdev->dev);
desc = devm_kzalloc(chip->dev, sizeof(smb_psy_desc), GFP_KERNEL);
desc = devm_kzalloc(chip->dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
memcpy(desc, &smb_psy_desc, sizeof(smb_psy_desc));
@ -999,12 +1035,35 @@ static int smb_probe(struct platform_device *pdev)
return dev_err_probe(chip->dev, rc,
"Failed to init status change work\n");
rc = (chip->batt_info->voltage_max_design_uv - 3487500) / 7500 + 1;
if (power_supply_battery_info_has_prop(chip->batt_info,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX)) {
float_voltage_uv =
chip->batt_info->constant_charge_voltage_max_uv;
} else if (power_supply_battery_info_has_prop(chip->batt_info,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN)) {
float_voltage_uv = chip->batt_info->voltage_max_design_uv;
} else {
dev_warn(chip->dev, "No battery float voltage; preserving hardware setting\n");
goto skip_float_voltage;
}
if (float_voltage_uv < SMB2_FLOAT_VOLTAGE_MIN_UV ||
float_voltage_uv > SMB2_FLOAT_VOLTAGE_MAX_UV)
return dev_err_probe(chip->dev, -EINVAL,
"float voltage %d uV outside %d-%d uV\n",
float_voltage_uv,
SMB2_FLOAT_VOLTAGE_MIN_UV,
SMB2_FLOAT_VOLTAGE_MAX_UV);
float_voltage_sel =
(float_voltage_uv - SMB2_FLOAT_VOLTAGE_MIN_UV) /
SMB2_FLOAT_VOLTAGE_STEP_UV;
rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG,
FLOAT_VOLTAGE_SETTING_MASK, rc);
FLOAT_VOLTAGE_SETTING_MASK, float_voltage_sel);
if (rc < 0)
return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n");
skip_float_voltage:
rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage);
if (rc < 0)
return rc;
@ -1028,6 +1087,8 @@ static int smb_probe(struct platform_device *pdev)
if (rc < 0)
return dev_err_probe(chip->dev, rc, "Couldn't set wake irq\n");
devm_remove_action(chip->dev, smb_restore_charge_enable, chip);
platform_set_drvdata(pdev, chip);
/* Initialise charger state */

View File

@ -633,6 +633,39 @@ static int rt9467_psy_set_ieoc(struct rt9467_chg_data *data, int microamp)
return ret;
}
static int rt9467_set_batfet(struct rt9467_chg_data *data, int val)
{
unsigned int regval;
switch (val) {
case POWER_SUPPLY_LOAD_SWITCH_ON:
regval = 0;
break;
case POWER_SUPPLY_LOAD_SWITCH_OFF:
regval = 1;
break;
default:
return -EINVAL;
}
return regmap_field_write(data->rm_field[F_SHIP_MODE], regval);
}
static int rt9467_get_batfet(struct rt9467_chg_data *data, int *val)
{
unsigned int regval;
int ret;
ret = regmap_field_read(data->rm_field[F_SHIP_MODE], &regval);
if (ret < 0)
return ret;
*val = regval ? POWER_SUPPLY_LOAD_SWITCH_OFF :
POWER_SUPPLY_LOAD_SWITCH_ON;
return 0;
}
static const enum power_supply_property rt9467_chg_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
@ -648,6 +681,7 @@ static const enum power_supply_property rt9467_chg_properties[] = {
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_LOAD_SWITCH,
};
static int rt9467_psy_get_property(struct power_supply *psy,
@ -711,6 +745,8 @@ static int rt9467_psy_get_property(struct power_supply *psy,
val->intval = data->ieoc_ua;
mutex_unlock(&data->ichg_ieoc_lock);
return 0;
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return rt9467_get_batfet(data, &val->intval);
default:
return -ENODATA;
}
@ -747,6 +783,8 @@ static int rt9467_psy_set_property(struct power_supply *psy,
RT9467_RANGE_IPREC, val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return rt9467_psy_set_ieoc(data, val->intval);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return rt9467_set_batfet(data, val->intval);
default:
return -EINVAL;
}
@ -764,6 +802,7 @@ static int rt9467_chg_prop_is_writeable(struct power_supply *psy,
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return 1;
default:
return 0;
@ -777,6 +816,8 @@ static const struct power_supply_desc rt9467_chg_psy_desc = {
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
.load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) |
BIT(POWER_SUPPLY_LOAD_SWITCH_OFF),
.properties = rt9467_chg_properties,
.num_properties = ARRAY_SIZE(rt9467_chg_properties),
.property_is_writeable = rt9467_chg_prop_is_writeable,
@ -793,14 +834,14 @@ static ssize_t sysoff_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rt9467_chg_data *data = psy_device_to_chip(dev);
unsigned int sysoff_enable;
int val;
int ret;
ret = regmap_field_read(data->rm_field[F_SHIP_MODE], &sysoff_enable);
if (ret)
ret = rt9467_get_batfet(data, &val);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", sysoff_enable);
return sysfs_emit(buf, "%d\n", val == POWER_SUPPLY_LOAD_SWITCH_OFF);
}
static ssize_t sysoff_enable_store(struct device *dev,
@ -815,8 +856,9 @@ static ssize_t sysoff_enable_store(struct device *dev,
if (ret)
return ret;
ret = regmap_field_write(data->rm_field[F_SHIP_MODE], !!tmp);
if (ret)
ret = rt9467_set_batfet(data, tmp ? POWER_SUPPLY_LOAD_SWITCH_OFF :
POWER_SUPPLY_LOAD_SWITCH_ON);
if (ret < 0)
return ret;
return count;

View File

@ -265,6 +265,39 @@ static int rt9471_get_ieoc(struct rt9471_chip *chip, int *microamp)
return rt9471_get_value_by_field_range(chip, F_IEOC_CHG, RT9471_RANGE_IEOC, microamp);
}
static int rt9471_set_batfet(struct rt9471_chip *chip, int val)
{
unsigned int regval;
switch (val) {
case POWER_SUPPLY_LOAD_SWITCH_ON:
regval = 0;
break;
case POWER_SUPPLY_LOAD_SWITCH_OFF:
regval = 1;
break;
default:
return -EINVAL;
}
return regmap_field_write(chip->rm_fields[F_BATFET_DIS], regval);
}
static int rt9471_get_batfet(struct rt9471_chip *chip, int *val)
{
unsigned int regval;
int ret;
ret = regmap_field_read(chip->rm_fields[F_BATFET_DIS], &regval);
if (ret < 0)
return ret;
*val = regval ? POWER_SUPPLY_LOAD_SWITCH_OFF :
POWER_SUPPLY_LOAD_SWITCH_ON;
return 0;
}
static int rt9471_get_status(struct rt9471_chip *chip, int *status)
{
unsigned int ic_stat;
@ -342,6 +375,7 @@ static enum power_supply_property rt9471_charger_properties[] = {
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_LOAD_SWITCH,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
@ -358,6 +392,7 @@ static int rt9471_charger_property_is_writeable(struct power_supply *psy,
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return 1;
default:
return 0;
@ -393,6 +428,8 @@ static int rt9471_charger_set_property(struct power_supply *psy,
chip, F_IPRE_CHG, RT9471_RANGE_IPRE, val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return rt9471_set_ieoc(chip, val->intval);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return rt9471_set_batfet(chip, val->intval);
default:
return -EINVAL;
}
@ -439,6 +476,8 @@ static int rt9471_charger_get_property(struct power_supply *psy,
chip, F_IPRE_CHG, RT9471_RANGE_IPRE, &val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return rt9471_get_ieoc(chip, &val->intval);
case POWER_SUPPLY_PROP_LOAD_SWITCH:
return rt9471_get_batfet(chip, &val->intval);
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = rt9471_model;
return 0;
@ -649,14 +688,14 @@ static ssize_t sysoff_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rt9471_chip *chip = psy_device_to_chip(dev);
unsigned int sysoff_enable;
unsigned int val;
int ret;
ret = regmap_field_read(chip->rm_fields[F_BATFET_DIS], &sysoff_enable);
if (ret)
ret = rt9471_get_batfet(chip, &val);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", sysoff_enable);
return sysfs_emit(buf, "%d\n", val == POWER_SUPPLY_LOAD_SWITCH_OFF);
}
static ssize_t sysoff_enable_store(struct device *dev,
@ -671,8 +710,9 @@ static ssize_t sysoff_enable_store(struct device *dev,
if (ret)
return ret;
ret = regmap_field_write(chip->rm_fields[F_BATFET_DIS], !!tmp);
if (ret)
ret = rt9471_set_batfet(chip, tmp ? POWER_SUPPLY_LOAD_SWITCH_OFF :
POWER_SUPPLY_LOAD_SWITCH_ON);
if (ret < 0)
return ret;
return count;
@ -744,6 +784,8 @@ static int rt9471_register_psy(struct rt9471_chip *chip)
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) |
BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN);
desc->load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) |
BIT(POWER_SUPPLY_LOAD_SWITCH_OFF);
desc->properties = rt9471_charger_properties;
desc->num_properties = ARRAY_SIZE(rt9471_charger_properties);
desc->get_property = rt9471_charger_get_property;

View File

@ -262,7 +262,7 @@ static int s2mu005_fg_i2c_probe(struct i2c_client *client)
ret = devm_mutex_init(dev, &priv->monout_mutex);
if (ret)
dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n");
return dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n");
psy_desc = device_get_match_data(dev);
@ -277,7 +277,7 @@ static int s2mu005_fg_i2c_probe(struct i2c_client *client)
s2mu005_handle_irq, IRQF_ONESHOT,
psy_desc->name, priv);
if (ret)
dev_err_probe(dev, ret, "failed to request IRQ\n");
return dev_err_probe(dev, ret, "failed to request IRQ\n");
return 0;
}

View File

@ -1068,6 +1068,13 @@ static void sbs_alert(struct i2c_client *client, enum i2c_alert_protocol prot,
sbs_supply_changed(i2c_get_clientdata(client));
}
static void sbs_disable_work(void *data)
{
struct sbs_info *chip = data;
disable_delayed_work_sync(&chip->work);
}
static void sbs_external_power_changed(struct power_supply *psy)
{
struct sbs_info *chip = power_supply_get_drvdata(psy);
@ -1208,6 +1215,10 @@ static int sbs_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply),
"Failed to register power supply\n");
rc = devm_add_action_or_reset(&client->dev, sbs_disable_work, chip);
if (rc)
return rc;
if (!chip->gpio_detect)
goto out;

View File

@ -2,10 +2,10 @@
/*
* Copyright (c) 2016, Prodys S.L.
*
* This adds support for sbs-charger compilant chips as defined here:
* This adds support for sbs-charger compliant chips as defined here:
* http://sbs-forum.org/specs/sbc110.pdf
*
* Implemetation based on sbs-battery.c
* Implementation based on sbs-battery.c
*/
#include <linux/init.h>

View File

@ -149,7 +149,7 @@
* @pre_charge_current: current (in uA) to use in pre-charging phase
* @termination_current: current (in uA) used to determine when the
* charging cycle terminates
* @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to
* @pre_to_fast_voltage: voltage (in uV) threshold used for transitioning to
* pre-charge to fast charge mode
* @mains_current_limit: maximum input current drawn from AC/DC input (in uA)
* @usb_hc_current_limit: maximum input high current (in uA) drawn from USB
@ -302,7 +302,7 @@ static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
*
* Function checks whether any power source is connected to the charger and
* updates internal state accordingly. If there is a change to previous state
* function returns %1, otherwise %0 and negative errno in case of errror.
* function returns %1, otherwise %0 and negative errno in case of error.
*/
static int smb347_update_ps_status(struct smb347_charger *smb)
{

View File

@ -33,7 +33,9 @@ enum bq27xxx_chip {
BQ27441,
BQ27621,
BQ27Z561,
BQ27Z746,
BQ28Z610,
BQ28Z620,
BQ34Z100,
BQ78Z100,
};

View File

@ -104,6 +104,14 @@ enum {
POWER_SUPPLY_SCOPE_DEVICE,
};
enum {
POWER_SUPPLY_LOAD_SWITCH_UNKNOWN = 0,
POWER_SUPPLY_LOAD_SWITCH_ON,
POWER_SUPPLY_LOAD_SWITCH_OFF,
POWER_SUPPLY_LOAD_SWITCH_STANDBY,
POWER_SUPPLY_LOAD_SWITCH_SHIP,
};
enum power_supply_property {
/* Properties of type `int' */
POWER_SUPPLY_PROP_STATUS = 0,
@ -182,6 +190,7 @@ enum power_supply_property {
POWER_SUPPLY_PROP_MANUFACTURE_DAY,
POWER_SUPPLY_PROP_INTERNAL_RESISTANCE,
POWER_SUPPLY_PROP_STATE_OF_HEALTH,
POWER_SUPPLY_PROP_LOAD_SWITCH,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
@ -259,9 +268,9 @@ struct power_supply_config {
struct power_supply_desc {
const char *name;
enum power_supply_type type;
u8 charge_behaviours;
u32 charge_types;
u32 usb_types;
u32 load_switches;
const enum power_supply_property *properties;
size_t num_properties;
@ -295,14 +304,17 @@ struct power_supply_desc {
*/
int (*init)(struct power_supply *psy);
/* For APM emulation, think legacy userspace. */
int use_for_apm;
/*
* Set if thermal zone should not be created for this power supply.
* For example for virtual supplies forwarding calls to actual
* sensors or other supplies.
*/
bool no_thermal;
/* For APM emulation, think legacy userspace. */
int use_for_apm;
u8 charge_behaviours;
};
struct power_supply_ext {