diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 5641f1fd5fd6..671786d4600d 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -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//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//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 diff --git a/Documentation/ABI/testing/sysfs-class-power-ltc4162l b/Documentation/ABI/testing/sysfs-class-power-ltc4162l index ba30db93052b..ad46ad834af0 100644 --- a/Documentation/ABI/testing/sysfs-class-power-ltc4162l +++ b/Documentation/ABI/testing/sysfs-class-power-ltc4162l @@ -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) diff --git a/Documentation/ABI/testing/sysfs-class-power-mp2629 b/Documentation/ABI/testing/sysfs-class-power-mp2629 index 914d67caac0d..a85f926ce9c2 100644 --- a/Documentation/ABI/testing/sysfs-class-power-mp2629 +++ b/Documentation/ABI/testing/sysfs-class-power-mp2629 @@ -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: diff --git a/Documentation/ABI/testing/sysfs-class-power-rt9467 b/Documentation/ABI/testing/sysfs-class-power-rt9467 index 619b7c45d145..bfbfd78ec1c1 100644 --- a/Documentation/ABI/testing/sysfs-class-power-rt9467 +++ b/Documentation/ABI/testing/sysfs-class-power-rt9467 @@ -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 diff --git a/Documentation/ABI/testing/sysfs-class-power-rt9471 b/Documentation/ABI/testing/sysfs-class-power-rt9471 index 0a390ee5ac21..90cf94c1cbf6 100644 --- a/Documentation/ABI/testing/sysfs-class-power-rt9471 +++ b/Documentation/ABI/testing/sysfs-class-power-rt9471 @@ -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 diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml index 8ebf05d9497c..9575ee1491bb 100644 --- a/Documentation/devicetree/bindings/power/supply/battery.yaml +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml @@ -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 diff --git a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml index bc05400186cf..be0f1f7292c8 100644 --- a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml +++ b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml @@ -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: diff --git a/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml b/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml index 4f19744844e9..dcffcecaea13 100644 --- a/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml +++ b/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml @@ -10,12 +10,13 @@ title: AB8500 Charger maintainers: - Sebastian Reichel -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: diff --git a/Documentation/power/power_supply_class.rst b/Documentation/power/power_supply_class.rst index 6d11f8c594a0..0072b41d4d45 100644 --- a/Documentation/power/power_supply_class.rst +++ b/Documentation/power/power_supply_class.rst @@ -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 diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c index 3c44cd6cee0a..491ba490b8ea 100644 --- a/drivers/power/reset/keystone-reset.c +++ b/drivers/power/reset/keystone-reset.c @@ -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; diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c index 9fc36fa68676..7b8460a55e9d 100644 --- a/drivers/power/reset/macsmc-reboot.c +++ b/drivers/power/reset/macsmc-reboot.c @@ -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; } diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index b89ef40df7f8..ee7dc5f2617a 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -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 diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 180a016b3662..209744c77c3d 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -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; }; diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 19ed52852804..0f04893d6bf9 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -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; diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index 90a61d9db72c..d8845fd2f084 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -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; } /** diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index d2b9880b0770..71d82c6c193e 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -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. diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index ff38254d06ef..19cc56624ee4 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -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; diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index e6b5efdf5dc9..069e7fee1d16 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -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, ®_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, ®_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, ®_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, diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index d9892b351621..46889567bc18 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -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; diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index ee8701a6e907..c119014b319a 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -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) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index e951b2b64b25..7625366104bc 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -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; } diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 4bea6fd83c36..7a5867dd74c2 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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, + ®val); + 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[] = { diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 944a7aa6f187..8eea7d0395ce 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -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, diff --git a/drivers/power/supply/bq2515x_charger.c b/drivers/power/supply/bq2515x_charger.c index 0208358ebbe4..c2b17562cf8b 100644 --- a/drivers/power/supply/bq2515x_charger.c +++ b/drivers/power/supply/bq2515x_charger.c @@ -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 diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c index 200f74f8eab6..bf54ff557a40 100644 --- a/drivers/power/supply/bq25630_charger.c +++ b/drivers/power/supply/bq25630_charger.c @@ -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], + ®val); + 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, diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 4b1f81b1ed86..454bc497b0d8 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -16,6 +16,7 @@ #include #include #include +#include #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, diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index b735bb02a8e0..569608bc0d6a 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -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; } /** diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 741a45cc5f35..f7510ada6b73 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -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); diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 01c9ec1116fa..09272249b4f7 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -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 +#include #include #include #include @@ -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; } diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index c4e7a9521d08..715ce8b6e401 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -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" }, {}, diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index fd4cd9cd2ce2..8e8c5109401a 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -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. diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 34a57dd5c3cf..d423a8bdc0c2 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -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; } } diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c index e0f168624807..e5841b40563e 100644 --- a/drivers/power/supply/cros_charge-control.c +++ b/drivers/power/supply/cros_charge-control.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #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, }; diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c index d25279c26030..8c4daec0a6b6 100644 --- a/drivers/power/supply/da9030_battery.c +++ b/drivers/power/supply/da9030_battery.c @@ -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); diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index f11a5c794e7e..7a1f8188bd27 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -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; diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 2504190eba82..1dad49acc424 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -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; diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c index cb6fa8d88b43..5819df23a543 100644 --- a/drivers/power/supply/intel_dc_ti_battery.c +++ b/drivers/power/supply/intel_dc_ti_battery.c @@ -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"); } diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index d1be314f36a0..6ebb555f203d 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -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; diff --git a/drivers/power/supply/ltc4162-l-charger.c b/drivers/power/supply/ltc4162-l-charger.c index 5c09e0368e6f..01b0511da9ae 100644 --- a/drivers/power/supply/ltc4162-l-charger.c +++ b/drivers/power/supply/ltc4162-l-charger.c @@ -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, ®val); + 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, ®val); - 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, diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 505a68c26463..8138b52e05e1 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/drivers/power/supply/mm8013.c b/drivers/power/supply/mm8013.c index 819667a27cad..c38f48338d42 100644 --- a/drivers/power/supply/mm8013.c +++ b/drivers/power/supply/mm8013.c @@ -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, diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c index 2ead1df60e65..e3d4e9817f91 100644 --- a/drivers/power/supply/pf1550-charger.c +++ b/drivers/power/supply/pf1550-charger.c @@ -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) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 1279785645fb..de15314ef954 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -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(®->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(®->list_head); kfree(reg); diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 9d6b24856c8b..9704557ffe1d 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -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; diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 7716eb9e1aff..636d6f6acecc 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -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: diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index 67fdb4335338..52bb5a6eb145 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -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 */ diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index a9a83b263e18..f0d10f44b109 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -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], ®val); + 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; diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index b67f5abaa993..b16d39a774dd 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -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], ®val); + 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; diff --git a/drivers/power/supply/s2mu005-battery.c b/drivers/power/supply/s2mu005-battery.c index 53de6605b492..a76154e5a091 100644 --- a/drivers/power/supply/s2mu005-battery.c +++ b/drivers/power/supply/s2mu005-battery.c @@ -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; } diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 501c8e069755..6714313bb3a3 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -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; diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index e7b4e10dcfd5..bc40bbb429e6 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -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 diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index c1b67dac8ef6..72dfa15b3d80 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -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) { diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index d56e1276aafe..731ea837aac3 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -33,7 +33,9 @@ enum bq27xxx_chip { BQ27441, BQ27621, BQ27Z561, + BQ27Z746, BQ28Z610, + BQ28Z620, BQ34Z100, BQ78Z100, }; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 0a4ea490d5ea..cce61137aef4 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -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 {