mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Merge tag 'for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel:
"Power-supply drivers:
- S2MU005: new battery fuel gauge driver
- macsmc-power: new driver for Apple Silicon
- qcom_battmgr: Add support for Glymur and Kaanapali
- max17042: add support for max77759
- qcom_smbx: allow disabling charging
- bd71828: add input current limit support
- multiple drivers: use new device managed workqueue allocation
function
- misc small cleanups and fixes
Reset core:
- Expose sysfs for registered reboot_modes
Reset drivers
- misc small cleanups and fixes"
* tag 'for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (36 commits)
power: supply: qcom_smbx: allow disabling charging
power: reset: drop unneeded dependencies on OF_GPIO
power: supply: bd71828: add input current limit property
dt-bindings: power: reset: cortina,gemini-power-controller: convert to DT schema
power: supply: add support for S2MU005 battery fuel gauge device
dt-bindings: power: supply: document Samsung S2MU005 battery fuel gauge
power: reset: reboot-mode: fix -Wformat-security warning
power: supply: ipaq_micro: Simplify with devm
power: supply: mt6370: Simplify with devm_alloc_ordered_workqueue()
power: supply: max77705: Free allocated workqueue and fix removal order
power: supply: max77705: Drop duplicated IRQ error message
power: supply: cw2015: Free allocated workqueue
power: reset: keystone: Use register_sys_off_handler(SYS_OFF_MODE_RESTART)
power: supply: twl4030_madc: Drop unused header includes
power: supply: bq24190: Avoid rescheduling after cancelling work
power: supply: axp288_charger: Simplify returns of dev_err_probe()
power: supply: axp288_charger: Do not cancel work before initializing it
power: supply: cpcap-battery: pass static battery cell data from device tree
dt-bindings: power: supply: cpcap-battery: document monitored-battery property
power: supply: qcom_battmgr: Add support for Glymur and Kaanapali
...
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
What: /sys/class/reboot-mode/<driver>/reboot_modes
|
||||
Date: March 2026(TBD)
|
||||
KernelVersion: TBD
|
||||
Contact: linux-pm@vger.kernel.org
|
||||
Description:
|
||||
This interface exposes the reboot-mode arguments
|
||||
registered with the reboot-mode framework. It is
|
||||
a read-only interface and provides a space
|
||||
separated list of reboot-mode arguments supported
|
||||
on the current platform.
|
||||
Example:
|
||||
recovery fastboot bootloader
|
||||
|
||||
The exact sysfs path may vary depending on the
|
||||
name of the driver that registers the arguments.
|
||||
Example:
|
||||
/sys/class/reboot-mode/nvmem-reboot-mode/reboot_modes
|
||||
/sys/class/reboot-mode/syscon-reboot-mode/reboot_modes
|
||||
/sys/class/reboot-mode/qcom-pon/reboot_modes
|
||||
|
||||
The supported arguments can be used by userspace to
|
||||
invoke device reset using the standard reboot() system
|
||||
call interface, with the "argument" as string to "*arg"
|
||||
parameter along with LINUX_REBOOT_CMD_RESTART2.
|
||||
|
||||
A driver can expose the supported arguments by
|
||||
registering them with the reboot-mode framework
|
||||
using the property names that follow the
|
||||
mode-<argument> format.
|
||||
Example:
|
||||
mode-bootloader, mode-recovery.
|
||||
|
||||
This attribute is useful for scripts or initramfs
|
||||
logic that need to programmatically determine
|
||||
which reboot-mode arguments are valid before
|
||||
triggering a reboot.
|
||||
@@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/power/reset/cortina,gemini-power-controller.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Cortina Systems Gemini Poweroff Controller
|
||||
|
||||
maintainers:
|
||||
- Linus Walleij <linusw@kernel.org>
|
||||
|
||||
description: |
|
||||
The Gemini power controller is a dedicated IP block in the Cortina Gemini SoC that
|
||||
controls system power-down operations.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: cortina,gemini-power-controller
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
poweroff@4b000000 {
|
||||
compatible = "cortina,gemini-power-controller";
|
||||
reg = <0x4b000000 0x100>;
|
||||
interrupts = <26 IRQ_TYPE_EDGE_FALLING>;
|
||||
};
|
||||
...
|
||||
@@ -1,17 +0,0 @@
|
||||
* Device-Tree bindings for Cortina Systems Gemini Poweroff
|
||||
|
||||
This is a special IP block in the Cortina Gemini SoC that only
|
||||
deals with different ways to power the system down.
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "cortina,gemini-power-controller"
|
||||
- reg: should contain the physical memory base and size
|
||||
- interrupts: should contain the power management interrupt
|
||||
|
||||
Example:
|
||||
|
||||
power-controller@4b000000 {
|
||||
compatible = "cortina,gemini-power-controller";
|
||||
reg = <0x4b000000 0x100>;
|
||||
interrupts = <26 IRQ_TYPE_EDGE_FALLING>;
|
||||
};
|
||||
@@ -55,6 +55,7 @@ properties:
|
||||
- const: chg_isense
|
||||
- const: batti
|
||||
|
||||
monitored-battery: true
|
||||
power-supplies: true
|
||||
|
||||
required:
|
||||
|
||||
@@ -20,6 +20,7 @@ properties:
|
||||
- maxim,max17050
|
||||
- maxim,max17055
|
||||
- maxim,max77705-battery
|
||||
- maxim,max77759-fg
|
||||
- maxim,max77849-battery
|
||||
|
||||
reg:
|
||||
@@ -27,36 +28,42 @@ properties:
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
description: |
|
||||
The ALRT pin, an open-drain interrupt.
|
||||
description:
|
||||
The ALRT pin (or FG_INTB pin on MAX77759), an open-drain interrupt.
|
||||
|
||||
shunt-resistor-micro-ohms:
|
||||
description:
|
||||
Resistance of rsns resistor in micro Ohms (datasheet-recommended value is 10000).
|
||||
Defining this property enables current-sense functionality.
|
||||
|
||||
maxim,rsns-microohm:
|
||||
deprecated: true
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
description:
|
||||
Resistance of rsns resistor in micro Ohms (datasheet-recommended value is 10000).
|
||||
Defining this property enables current-sense functionality.
|
||||
|
||||
maxim,cold-temp:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
description:
|
||||
Temperature threshold to report battery as cold (in tenths of degree Celsius).
|
||||
Default is not to report cold events.
|
||||
|
||||
maxim,over-heat-temp:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
description:
|
||||
Temperature threshold to report battery as over heated (in tenths of degree Celsius).
|
||||
Default is not to report over heating events.
|
||||
|
||||
maxim,dead-volt:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
description:
|
||||
Voltage threshold to report battery as dead (in mV).
|
||||
Default is not to report dead battery events.
|
||||
|
||||
maxim,over-volt:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
description:
|
||||
Voltage threshold to report battery as over voltage (in mV).
|
||||
Default is not to report over-voltage events.
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/power/supply/samsung,s2mu005-fuel-gauge.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Battery Fuel Gauge for Samsung S2M series PMICs
|
||||
|
||||
maintainers:
|
||||
- Kaustabh Chakraborty <kauschluss@disroot.org>
|
||||
|
||||
allOf:
|
||||
- $ref: power-supply.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- samsung,s2mu005-fuel-gauge
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
fuel-gauge@3b {
|
||||
compatible = "samsung,s2mu005-fuel-gauge";
|
||||
reg = <0x3b>;
|
||||
|
||||
interrupt-parent = <&gpa0>;
|
||||
interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
|
||||
|
||||
monitored-battery = <&battery>;
|
||||
};
|
||||
};
|
||||
@@ -2547,6 +2547,7 @@ F: drivers/nvmem/apple-spmi-nvmem.c
|
||||
F: drivers/phy/apple/
|
||||
F: drivers/pinctrl/pinctrl-apple-gpio.c
|
||||
F: drivers/power/reset/macsmc-reboot.c
|
||||
F: drivers/power/supply/macsmc-power.c
|
||||
F: drivers/pwm/pwm-apple.c
|
||||
F: drivers/rtc/rtc-macsmc.c
|
||||
F: drivers/soc/apple/*
|
||||
|
||||
@@ -97,7 +97,7 @@ config POWER_RESET_GEMINI_POWEROFF
|
||||
|
||||
config POWER_RESET_GPIO
|
||||
bool "GPIO power-off driver"
|
||||
depends on OF_GPIO
|
||||
depends on OF
|
||||
help
|
||||
This driver supports turning off your board via a GPIO line.
|
||||
If your board needs a GPIO high/low to power down, say Y and
|
||||
@@ -105,7 +105,7 @@ config POWER_RESET_GPIO
|
||||
|
||||
config POWER_RESET_GPIO_RESTART
|
||||
bool "GPIO restart driver"
|
||||
depends on OF_GPIO
|
||||
depends on OF
|
||||
help
|
||||
This driver supports restarting your board via a GPIO line.
|
||||
If your board needs a GPIO high/low to restart, say Y and
|
||||
@@ -181,7 +181,7 @@ config POWER_RESET_PIIX4_POWEROFF
|
||||
|
||||
config POWER_RESET_LTC2952
|
||||
bool "LTC2952 PowerPath power-off driver"
|
||||
depends on OF_GPIO
|
||||
depends on OF
|
||||
help
|
||||
This driver supports an external powerdown trigger and board power
|
||||
down via the LTC2952. Bindings are made in the device tree.
|
||||
@@ -198,7 +198,7 @@ config POWER_RESET_MT6323
|
||||
|
||||
config POWER_RESET_QNAP
|
||||
bool "QNAP power-off driver"
|
||||
depends on OF_GPIO && PLAT_ORION
|
||||
depends on PLAT_ORION
|
||||
help
|
||||
This driver supports turning off QNAP NAS devices by sending
|
||||
commands to the microcontroller which controls the main power.
|
||||
|
||||
@@ -48,8 +48,7 @@ static inline int rsctrl_enable_rspll_write(void)
|
||||
RSCTRL_KEY_MASK, RSCTRL_KEY);
|
||||
}
|
||||
|
||||
static int rsctrl_restart_handler(struct notifier_block *this,
|
||||
unsigned long mode, void *cmd)
|
||||
static int rsctrl_restart_handler(struct sys_off_data *data)
|
||||
{
|
||||
/* enable write access to RSTCTRL */
|
||||
rsctrl_enable_rspll_write();
|
||||
@@ -61,11 +60,6 @@ static int rsctrl_restart_handler(struct notifier_block *this,
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block rsctrl_restart_nb = {
|
||||
.notifier_call = rsctrl_restart_handler,
|
||||
.priority = 128,
|
||||
};
|
||||
|
||||
static const struct of_device_id rsctrl_of_match[] = {
|
||||
{.compatible = "ti,keystone-reset", },
|
||||
{},
|
||||
@@ -140,7 +134,8 @@ static int rsctrl_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = register_restart_handler(&rsctrl_restart_nb);
|
||||
ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, 128,
|
||||
rsctrl_restart_handler, NULL);
|
||||
if (ret)
|
||||
dev_err(dev, "cannot register restart handler (err=%d)\n", ret);
|
||||
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/reboot-mode.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#define PREFIX "mode-"
|
||||
|
||||
@@ -19,6 +23,54 @@ struct mode_info {
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct reboot_mode_sysfs_data {
|
||||
struct device *reboot_mode_device;
|
||||
struct list_head head;
|
||||
};
|
||||
|
||||
static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv)
|
||||
{
|
||||
struct mode_info *info;
|
||||
struct mode_info *next;
|
||||
|
||||
list_for_each_entry_safe(info, next, &priv->head, list) {
|
||||
list_del(&info->list);
|
||||
kfree_const(info->mode);
|
||||
kfree(info);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct reboot_mode_sysfs_data *priv;
|
||||
struct mode_info *sysfs_info;
|
||||
ssize_t size = 0;
|
||||
|
||||
priv = dev_get_drvdata(dev);
|
||||
if (!priv)
|
||||
return -ENODATA;
|
||||
|
||||
list_for_each_entry(sysfs_info, &priv->head, list)
|
||||
size += sysfs_emit_at(buf, size, "%s ", sysfs_info->mode);
|
||||
|
||||
if (!size)
|
||||
return -ENODATA;
|
||||
|
||||
return size + sysfs_emit_at(buf, size - 1, "\n");
|
||||
}
|
||||
static DEVICE_ATTR_RO(reboot_modes);
|
||||
|
||||
static struct attribute *reboot_mode_attrs[] = {
|
||||
&dev_attr_reboot_modes.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(reboot_mode);
|
||||
|
||||
static const struct class reboot_mode_class = {
|
||||
.name = "reboot-mode",
|
||||
.dev_groups = reboot_mode_groups,
|
||||
};
|
||||
|
||||
static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
|
||||
const char *cmd)
|
||||
{
|
||||
@@ -62,6 +114,52 @@ static int reboot_mode_notify(struct notifier_block *this,
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int reboot_mode_create_device(struct reboot_mode_driver *reboot)
|
||||
{
|
||||
struct reboot_mode_sysfs_data *priv;
|
||||
struct mode_info *sysfs_info;
|
||||
struct mode_info *info;
|
||||
int ret;
|
||||
|
||||
priv = kzalloc_obj(*priv, GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&priv->head);
|
||||
|
||||
list_for_each_entry(info, &reboot->head, list) {
|
||||
sysfs_info = kzalloc_obj(*sysfs_info, GFP_KERNEL);
|
||||
if (!sysfs_info) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
sysfs_info->mode = kstrdup_const(info->mode, GFP_KERNEL);
|
||||
if (!sysfs_info->mode) {
|
||||
kfree(sysfs_info);
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
list_add_tail(&sysfs_info->list, &priv->head);
|
||||
}
|
||||
|
||||
priv->reboot_mode_device = device_create(&reboot_mode_class, NULL, 0,
|
||||
(void *)priv, "%s",
|
||||
reboot->dev->driver->name);
|
||||
if (IS_ERR(priv->reboot_mode_device)) {
|
||||
ret = PTR_ERR(priv->reboot_mode_device);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
reboot_mode_release_list(priv);
|
||||
kfree(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* reboot_mode_register - register a reboot mode driver
|
||||
* @reboot: reboot mode driver
|
||||
@@ -113,16 +211,49 @@ int reboot_mode_register(struct reboot_mode_driver *reboot)
|
||||
reboot->reboot_notifier.notifier_call = reboot_mode_notify;
|
||||
register_reboot_notifier(&reboot->reboot_notifier);
|
||||
|
||||
ret = reboot_mode_create_device(reboot);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
list_for_each_entry(info, &reboot->head, list)
|
||||
kfree_const(info->mode);
|
||||
|
||||
reboot_mode_unregister(reboot);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(reboot_mode_register);
|
||||
|
||||
static int reboot_mode_match_by_name(struct device *dev, const void *data)
|
||||
{
|
||||
const char *name = data;
|
||||
|
||||
if (!dev || !data)
|
||||
return 0;
|
||||
|
||||
return dev_name(dev) && strcmp(dev_name(dev), name) == 0;
|
||||
}
|
||||
|
||||
static inline void reboot_mode_unregister_device(struct reboot_mode_driver *reboot)
|
||||
{
|
||||
struct reboot_mode_sysfs_data *priv;
|
||||
struct device *reboot_mode_device;
|
||||
|
||||
reboot_mode_device = class_find_device(&reboot_mode_class, NULL, reboot->dev->driver->name,
|
||||
reboot_mode_match_by_name);
|
||||
|
||||
if (!reboot_mode_device)
|
||||
return;
|
||||
|
||||
priv = dev_get_drvdata(reboot_mode_device);
|
||||
device_unregister(reboot_mode_device);
|
||||
|
||||
if (!priv)
|
||||
return;
|
||||
|
||||
reboot_mode_release_list(priv);
|
||||
kfree(priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* reboot_mode_unregister - unregister a reboot mode driver
|
||||
* @reboot: reboot mode driver
|
||||
@@ -132,6 +263,7 @@ int reboot_mode_unregister(struct reboot_mode_driver *reboot)
|
||||
struct mode_info *info;
|
||||
|
||||
unregister_reboot_notifier(&reboot->reboot_notifier);
|
||||
reboot_mode_unregister_device(reboot);
|
||||
|
||||
list_for_each_entry(info, &reboot->head, list)
|
||||
kfree_const(info->mode);
|
||||
@@ -199,6 +331,19 @@ void devm_reboot_mode_unregister(struct device *dev,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);
|
||||
|
||||
static int __init reboot_mode_init(void)
|
||||
{
|
||||
return class_register(&reboot_mode_class);
|
||||
}
|
||||
|
||||
static void __exit reboot_mode_exit(void)
|
||||
{
|
||||
class_unregister(&reboot_mode_class);
|
||||
}
|
||||
|
||||
subsys_initcall(reboot_mode_init);
|
||||
module_exit(reboot_mode_exit);
|
||||
|
||||
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
|
||||
MODULE_DESCRIPTION("System reboot mode core library");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -229,6 +229,17 @@ config BATTERY_SAMSUNG_SDI
|
||||
Say Y to enable support for Samsung SDI battery data.
|
||||
These batteries are used in Samsung mobile phones.
|
||||
|
||||
config BATTERY_S2MU005
|
||||
tristate "Samsung S2MU005 PMIC fuel gauge driver"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for the Samsung S2MU005 PMIC integrated
|
||||
fuel gauge, which works indepenently of the PMIC battery charger
|
||||
counterpart, and reports battery metrics.
|
||||
|
||||
This driver, if built as a module, will be called s2mu005-fuel-gauge.
|
||||
|
||||
config BATTERY_COLLIE
|
||||
tristate "Sharp SL-5500 (collie) battery"
|
||||
depends on SA1100_COLLIE && MCP_UCB1200
|
||||
@@ -1132,4 +1143,15 @@ config FUEL_GAUGE_MM8013
|
||||
the state of charge, temperature, cycle count, actual and design
|
||||
capacity, etc.
|
||||
|
||||
config MACSMC_POWER
|
||||
tristate "Apple SMC Battery and Power Driver"
|
||||
depends on MFD_MACSMC
|
||||
help
|
||||
This driver provides support for the battery and AC adapter on
|
||||
Apple Silicon machines. It exposes battery telemetry (voltage,
|
||||
current, health) and AC adapter status through the standard Linux
|
||||
power supply framework.
|
||||
|
||||
Say Y or M here if you have an Apple Silicon based Mac.
|
||||
|
||||
endif # POWER_SUPPLY
|
||||
|
||||
@@ -40,6 +40,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
|
||||
obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o
|
||||
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
|
||||
obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o
|
||||
obj-$(CONFIG_BATTERY_S2MU005) += s2mu005-battery.o
|
||||
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
|
||||
obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
|
||||
obj-$(CONFIG_BATTERY_INTEL_DC_TI) += intel_dc_ti_battery.o
|
||||
@@ -128,3 +129,4 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
|
||||
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
|
||||
obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o
|
||||
obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o
|
||||
obj-$(CONFIG_MACSMC_POWER) += macsmc-power.o
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/workqueue.h>
|
||||
@@ -821,14 +822,6 @@ static int charger_init_hw_regs(struct axp288_chrg_info *info)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void axp288_charger_cancel_work(void *data)
|
||||
{
|
||||
struct axp288_chrg_info *info = data;
|
||||
|
||||
cancel_work_sync(&info->otg.work);
|
||||
cancel_work_sync(&info->cable.work);
|
||||
}
|
||||
|
||||
static int axp288_charger_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret, i, pirq;
|
||||
@@ -866,12 +859,10 @@ static int axp288_charger_probe(struct platform_device *pdev)
|
||||
info->regmap_irqc = axp20x->regmap_irqc;
|
||||
|
||||
info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
|
||||
if (IS_ERR(info->cable.edev)) {
|
||||
dev_err_probe(dev, PTR_ERR(info->cable.edev),
|
||||
"extcon_get_extcon_dev(%s) failed\n",
|
||||
AXP288_EXTCON_DEV_NAME);
|
||||
return PTR_ERR(info->cable.edev);
|
||||
}
|
||||
if (IS_ERR(info->cable.edev))
|
||||
return dev_err_probe(dev, PTR_ERR(info->cable.edev),
|
||||
"extcon_get_extcon_dev(%s) failed\n",
|
||||
AXP288_EXTCON_DEV_NAME);
|
||||
|
||||
/*
|
||||
* On devices with broken ACPI GPIO event handlers there also is no ACPI
|
||||
@@ -885,12 +876,11 @@ static int axp288_charger_probe(struct platform_device *pdev)
|
||||
|
||||
if (extcon_name) {
|
||||
info->otg.cable = extcon_get_extcon_dev(extcon_name);
|
||||
if (IS_ERR(info->otg.cable)) {
|
||||
dev_err_probe(dev, PTR_ERR(info->otg.cable),
|
||||
"extcon_get_extcon_dev(%s) failed\n",
|
||||
USB_HOST_EXTCON_NAME);
|
||||
return PTR_ERR(info->otg.cable);
|
||||
}
|
||||
if (IS_ERR(info->otg.cable))
|
||||
return dev_err_probe(dev, PTR_ERR(info->otg.cable),
|
||||
"extcon_get_extcon_dev(%s) failed\n",
|
||||
USB_HOST_EXTCON_NAME);
|
||||
|
||||
dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n");
|
||||
}
|
||||
|
||||
@@ -904,38 +894,39 @@ static int axp288_charger_probe(struct platform_device *pdev)
|
||||
charger_cfg.drv_data = info;
|
||||
info->psy_usb = devm_power_supply_register(dev, &axp288_charger_desc,
|
||||
&charger_cfg);
|
||||
if (IS_ERR(info->psy_usb)) {
|
||||
ret = PTR_ERR(info->psy_usb);
|
||||
dev_err(dev, "failed to register power supply: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (IS_ERR(info->psy_usb))
|
||||
return dev_err_probe(dev, PTR_ERR(info->psy_usb),
|
||||
"failed to register power supply: %d\n", ret);
|
||||
|
||||
/* Cancel our work on cleanup, register this before the notifiers */
|
||||
ret = devm_add_action(dev, axp288_charger_cancel_work, info);
|
||||
ret = devm_work_autocancel(dev, &info->cable.work,
|
||||
axp288_charger_extcon_evt_worker);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Register for extcon notification */
|
||||
INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
|
||||
info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
|
||||
ret = devm_extcon_register_notifier_all(dev, info->cable.edev,
|
||||
&info->cable.nb);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register cable extcon notifier\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to register cable extcon notifier\n");
|
||||
|
||||
schedule_work(&info->cable.work);
|
||||
|
||||
ret = devm_work_autocancel(dev, &info->otg.work,
|
||||
axp288_charger_otg_evt_worker);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Register for OTG notification */
|
||||
INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
|
||||
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
|
||||
if (info->otg.cable) {
|
||||
ret = devm_extcon_register_notifier(dev, info->otg.cable,
|
||||
EXTCON_USB_HOST, &info->otg.id_nb);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to register EXTCON_USB_HOST notifier\n");
|
||||
|
||||
schedule_work(&info->otg.work);
|
||||
}
|
||||
|
||||
@@ -954,11 +945,9 @@ static int axp288_charger_probe(struct platform_device *pdev)
|
||||
ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
|
||||
NULL, axp288_charger_irq_thread_handler,
|
||||
IRQF_ONESHOT, info->pdev->name, info);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request interrupt=%d\n",
|
||||
info->irq[i]);
|
||||
return ret;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to request interrupt=%d\n",
|
||||
info->irq[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define BD7182x_MASK_CONF_PON BIT(0)
|
||||
#define BD71815_MASK_CONF_XSTB BIT(1)
|
||||
#define BD7182x_MASK_BAT_STAT 0x3f
|
||||
#define BD7182x_MASK_ILIM 0x3f
|
||||
#define BD7182x_MASK_DCIN_STAT 0x07
|
||||
|
||||
#define BD7182x_MASK_WDT_AUTO 0x40
|
||||
@@ -48,9 +49,11 @@ struct pwr_regs {
|
||||
unsigned int vbat_avg;
|
||||
unsigned int ibat;
|
||||
unsigned int ibat_avg;
|
||||
unsigned int ilim_stat;
|
||||
unsigned int btemp_vth;
|
||||
unsigned int chg_state;
|
||||
unsigned int bat_temp;
|
||||
unsigned int dcin_set;
|
||||
unsigned int dcin_stat;
|
||||
unsigned int dcin_online_mask;
|
||||
unsigned int dcin_collapse_limit;
|
||||
@@ -66,9 +69,11 @@ static const struct pwr_regs pwr_regs_bd71828 = {
|
||||
.vbat_avg = BD71828_REG_VBAT_U,
|
||||
.ibat = BD71828_REG_IBAT_U,
|
||||
.ibat_avg = BD71828_REG_IBAT_AVG_U,
|
||||
.ilim_stat = BD71828_REG_ILIM_STAT,
|
||||
.btemp_vth = BD71828_REG_VM_BTMP_U,
|
||||
.chg_state = BD71828_REG_CHG_STATE,
|
||||
.bat_temp = BD71828_REG_BAT_TEMP,
|
||||
.dcin_set = BD71828_REG_DCIN_SET,
|
||||
.dcin_stat = BD71828_REG_DCIN_STAT,
|
||||
.dcin_online_mask = BD7182x_MASK_DCIN_DET,
|
||||
.dcin_collapse_limit = BD71828_REG_DCIN_CLPS,
|
||||
@@ -441,6 +446,7 @@ static int bd71828_charger_get_property(struct power_supply *psy,
|
||||
struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
|
||||
u32 vot;
|
||||
u16 tmp;
|
||||
int t;
|
||||
int online;
|
||||
int ret;
|
||||
|
||||
@@ -459,6 +465,20 @@ static int bd71828_charger_get_property(struct power_supply *psy,
|
||||
vot = tmp;
|
||||
/* 5 milli volt steps */
|
||||
val->intval = 5000 * vot;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
||||
if (!pwr->regs->ilim_stat)
|
||||
return -ENODATA;
|
||||
|
||||
ret = regmap_read(pwr->regmap, pwr->regs->ilim_stat, &t);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
t++;
|
||||
val->intval = (t & BD7182x_MASK_ILIM) * 50000;
|
||||
if (val->intval > 2000000)
|
||||
val->intval = 2000000;
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@@ -467,6 +487,45 @@ static int bd71828_charger_get_property(struct power_supply *psy,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bd71828_charger_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
||||
if (val->intval > 2000000)
|
||||
return -EINVAL;
|
||||
|
||||
if (val->intval < 50000)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pwr->regs->dcin_set)
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(pwr->regmap, pwr->regs->dcin_set,
|
||||
BD7182x_MASK_ILIM,
|
||||
val->intval / 50000 - 1);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int bd71828_charger_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
||||
return !!(pwr->regs->dcin_set);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int bd71828_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
@@ -571,6 +630,7 @@ static int bd71828_battery_property_is_writeable(struct power_supply *psy,
|
||||
|
||||
/** @brief ac properties */
|
||||
static const enum power_supply_property bd71828_charger_props[] = {
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
};
|
||||
@@ -600,6 +660,8 @@ static const struct power_supply_desc bd71828_ac_desc = {
|
||||
.properties = bd71828_charger_props,
|
||||
.num_properties = ARRAY_SIZE(bd71828_charger_props),
|
||||
.get_property = bd71828_charger_get_property,
|
||||
.set_property = bd71828_charger_set_property,
|
||||
.property_is_writeable = bd71828_charger_property_is_writeable,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc bd71828_bat_desc = {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/power/bq24190_charger.h>
|
||||
@@ -2087,8 +2088,11 @@ static int bq24190_probe(struct i2c_client *client)
|
||||
bdi->charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
||||
bdi->f_reg = 0;
|
||||
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
|
||||
INIT_DELAYED_WORK(&bdi->input_current_limit_work,
|
||||
bq24190_input_current_limit_work);
|
||||
|
||||
ret = devm_delayed_work_autocancel(dev, &bdi->input_current_limit_work,
|
||||
bq24190_input_current_limit_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2c_set_clientdata(client, bdi);
|
||||
|
||||
@@ -2198,7 +2202,6 @@ static void bq24190_remove(struct i2c_client *client)
|
||||
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
|
||||
int error;
|
||||
|
||||
cancel_delayed_work_sync(&bdi->input_current_limit_work);
|
||||
error = pm_runtime_resume_and_get(bdi->dev);
|
||||
if (error < 0)
|
||||
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
|
||||
|
||||
@@ -387,7 +387,7 @@ static const struct cpcap_battery_config cpcap_battery_bw8x_data = {
|
||||
* Safe values for any lipo battery likely to fit into a mapphone
|
||||
* battery bay.
|
||||
*/
|
||||
static const struct cpcap_battery_config cpcap_battery_unkown_data = {
|
||||
static const struct cpcap_battery_config cpcap_battery_unknown_data = {
|
||||
.cd_factor = 0x3cc,
|
||||
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
|
||||
.info.voltage_max_design = 4200000,
|
||||
@@ -404,6 +404,30 @@ static int cpcap_battery_match_nvmem(struct device *dev, const void *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cpcap_battery_update_battery_data(struct cpcap_battery_ddata *ddata)
|
||||
{
|
||||
struct power_supply_battery_info *info;
|
||||
|
||||
if (power_supply_get_battery_info(ddata->psy, &info) < 0)
|
||||
return;
|
||||
|
||||
if (info->technology > 0)
|
||||
ddata->config.info.technology = info->technology;
|
||||
|
||||
if (info->voltage_max_design_uv > 0)
|
||||
ddata->config.info.voltage_max_design = info->voltage_max_design_uv;
|
||||
|
||||
if (info->voltage_min_design_uv > 0)
|
||||
ddata->config.info.voltage_min_design = info->voltage_min_design_uv;
|
||||
|
||||
if (info->charge_full_design_uah > 0)
|
||||
ddata->config.info.charge_full_design = info->charge_full_design_uah;
|
||||
|
||||
if (info->constant_charge_voltage_max_uv > 0)
|
||||
ddata->config.bat.constant_charge_voltage_max_uv =
|
||||
info->constant_charge_voltage_max_uv;
|
||||
}
|
||||
|
||||
static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata)
|
||||
{
|
||||
struct nvmem_device *nvmem;
|
||||
@@ -429,8 +453,11 @@ static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata)
|
||||
ddata->config = cpcap_battery_bw8x_data;
|
||||
break;
|
||||
default:
|
||||
ddata->config = cpcap_battery_unkown_data;
|
||||
ddata->config = cpcap_battery_unknown_data;
|
||||
}
|
||||
|
||||
if (ddata->psy)
|
||||
cpcap_battery_update_battery_data(ddata);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -694,7 +694,8 @@ static int cw_bat_probe(struct i2c_client *client)
|
||||
"No monitored battery, some properties will be missing\n");
|
||||
}
|
||||
|
||||
cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery");
|
||||
cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev,
|
||||
"rk_battery", 0);
|
||||
if (!cw_bat->battery_workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* Author : Linus Walleij <linus.walleij@linaro.org>
|
||||
*/
|
||||
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
@@ -232,49 +233,31 @@ static int micro_batt_probe(struct platform_device *pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
mb->micro = dev_get_drvdata(pdev->dev.parent);
|
||||
mb->wq = alloc_workqueue("ipaq-battery-wq",
|
||||
WQ_MEM_RECLAIM | WQ_PERCPU, 0);
|
||||
mb->wq = devm_alloc_workqueue(&pdev->dev, "ipaq-battery-wq",
|
||||
WQ_MEM_RECLAIM | WQ_PERCPU, 0);
|
||||
if (!mb->wq)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_DELAYED_WORK(&mb->update, micro_battery_work);
|
||||
ret = devm_delayed_work_autocancel(&pdev->dev, &mb->update, micro_battery_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, mb);
|
||||
queue_delayed_work(mb->wq, &mb->update, 1);
|
||||
|
||||
micro_batt_power = power_supply_register(&pdev->dev,
|
||||
µ_batt_power_desc, NULL);
|
||||
if (IS_ERR(micro_batt_power)) {
|
||||
ret = PTR_ERR(micro_batt_power);
|
||||
goto batt_err;
|
||||
}
|
||||
micro_batt_power = devm_power_supply_register(&pdev->dev,
|
||||
µ_batt_power_desc,
|
||||
NULL);
|
||||
if (IS_ERR(micro_batt_power))
|
||||
return PTR_ERR(micro_batt_power);
|
||||
|
||||
micro_ac_power = power_supply_register(&pdev->dev,
|
||||
µ_ac_power_desc, NULL);
|
||||
if (IS_ERR(micro_ac_power)) {
|
||||
ret = PTR_ERR(micro_ac_power);
|
||||
goto ac_err;
|
||||
}
|
||||
micro_ac_power = devm_power_supply_register(&pdev->dev,
|
||||
µ_ac_power_desc, NULL);
|
||||
if (IS_ERR(micro_ac_power))
|
||||
return PTR_ERR(micro_ac_power);
|
||||
|
||||
dev_info(&pdev->dev, "iPAQ micro battery driver\n");
|
||||
return 0;
|
||||
|
||||
ac_err:
|
||||
power_supply_unregister(micro_batt_power);
|
||||
batt_err:
|
||||
cancel_delayed_work_sync(&mb->update);
|
||||
destroy_workqueue(mb->wq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void micro_batt_remove(struct platform_device *pdev)
|
||||
|
||||
{
|
||||
struct micro_battery *mb = platform_get_drvdata(pdev);
|
||||
|
||||
power_supply_unregister(micro_ac_power);
|
||||
power_supply_unregister(micro_batt_power);
|
||||
cancel_delayed_work_sync(&mb->update);
|
||||
destroy_workqueue(mb->wq);
|
||||
}
|
||||
|
||||
static int __maybe_unused micro_batt_suspend(struct device *dev)
|
||||
@@ -303,7 +286,6 @@ static struct platform_driver micro_batt_device_driver = {
|
||||
.pm = µ_batt_dev_pm_ops,
|
||||
},
|
||||
.probe = micro_batt_probe,
|
||||
.remove = micro_batt_remove,
|
||||
};
|
||||
module_platform_driver(micro_batt_device_driver);
|
||||
|
||||
|
||||
855
drivers/power/supply/macsmc-power.c
Normal file
855
drivers/power/supply/macsmc-power.c
Normal file
@@ -0,0 +1,855 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
/*
|
||||
* Apple SMC Power/Battery Management Driver
|
||||
*
|
||||
* This driver exposes battery telemetry (voltage, current, temperature, health)
|
||||
* and AC adapter status provided by the Apple SMC (System Management Controller)
|
||||
* on Apple Silicon systems.
|
||||
*
|
||||
* Copyright The Asahi Linux Contributors
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mfd/macsmc.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define MAX_STRING_LENGTH 256
|
||||
|
||||
/*
|
||||
* The SMC reports charge in mAh (Coulombs) but energy in mWh (Joules).
|
||||
* We lack a register for "Nominal Voltage" or "Energy Accumulator".
|
||||
* We use a fixed 3.8V/cell constant to approximate energy stats for userspace,
|
||||
* derived from empirical data across supported MacBook models.
|
||||
*/
|
||||
#define MACSMC_NOMINAL_CELL_VOLTAGE_MV 3800
|
||||
|
||||
/* SMC Key Flags */
|
||||
#define CHNC_BATTERY_FULL BIT(0)
|
||||
#define CHNC_NO_CHARGER BIT(7)
|
||||
#define CHNC_NOCHG_CH0C BIT(14)
|
||||
#define CHNC_NOCHG_CH0B_CH0K BIT(15)
|
||||
#define CHNC_BATTERY_FULL_2 BIT(18)
|
||||
#define CHNC_BMS_BUSY BIT(23)
|
||||
#define CHNC_CHLS_LIMIT BIT(24)
|
||||
#define CHNC_NOAC_CH0J BIT(53)
|
||||
#define CHNC_NOAC_CH0I BIT(54)
|
||||
|
||||
#define CH0R_LOWER_FLAGS GENMASK(15, 0)
|
||||
#define CH0R_NOAC_CH0I BIT(0)
|
||||
#define CH0R_NOAC_DISCONNECTED BIT(4)
|
||||
#define CH0R_NOAC_CH0J BIT(5)
|
||||
#define CH0R_BMS_BUSY BIT(8)
|
||||
#define CH0R_NOAC_CH0K BIT(9)
|
||||
#define CH0R_NOAC_CHWA BIT(11)
|
||||
|
||||
#define CH0X_CH0C BIT(0)
|
||||
#define CH0X_CH0B BIT(1)
|
||||
|
||||
#define ACSt_CAN_BOOT_AP BIT(2)
|
||||
#define ACSt_CAN_BOOT_IBOOT BIT(1)
|
||||
|
||||
#define CHWA_CHLS_FIXED_START_OFFSET 5
|
||||
#define CHLS_MIN_END_THRESHOLD 10
|
||||
#define CHLS_FORCE_DISCHARGE 0x100
|
||||
#define CHWA_FIXED_END_THRESHOLD 80
|
||||
#define CHWA_PROP_WRITE_THRESHOLD 95
|
||||
|
||||
#define MACSMC_MAX_BATT_PROPS 50
|
||||
#define MACSMC_MAX_AC_PROPS 10
|
||||
|
||||
struct macsmc_power {
|
||||
struct device *dev;
|
||||
struct apple_smc *smc;
|
||||
|
||||
struct power_supply_desc ac_desc;
|
||||
struct power_supply_desc batt_desc;
|
||||
|
||||
struct power_supply *batt;
|
||||
struct power_supply *ac;
|
||||
|
||||
char model_name[MAX_STRING_LENGTH];
|
||||
char serial_number[MAX_STRING_LENGTH];
|
||||
char mfg_date[MAX_STRING_LENGTH];
|
||||
|
||||
/* Supported feature flags based on SMC key presence */
|
||||
bool has_chwa; /* Charge limit (Modern firmware) */
|
||||
bool has_chls; /* Charge limit (Older firmware) */
|
||||
bool has_ch0i; /* Force discharge (Older firmware) */
|
||||
bool has_ch0c; /* Inhibit charge (Older firmware) */
|
||||
bool has_chte; /* Inhibit charge (Modern firmware) */
|
||||
|
||||
u8 num_cells;
|
||||
int nominal_voltage_mv;
|
||||
|
||||
struct notifier_block nb;
|
||||
struct work_struct critical_work;
|
||||
bool emergency_shutdown_triggered;
|
||||
bool orderly_shutdown_triggered;
|
||||
};
|
||||
|
||||
static int macsmc_battery_get_status(struct macsmc_power *power)
|
||||
{
|
||||
u64 nocharge_flags;
|
||||
u32 nopower_flags;
|
||||
u16 ac_current;
|
||||
int charge_limit = 0;
|
||||
bool limited = false;
|
||||
bool flag;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* B0AV (Voltage) is fundamental. If we can't read it, we assume the
|
||||
* battery is gone. CHCE (Hardware charger present) / CHCC (Hardware
|
||||
* charger capable) are fundamental status flags.
|
||||
* BSFC (System full charge) / CHSC (System charging) are fundamental
|
||||
* status flags.
|
||||
*/
|
||||
|
||||
/* Check if power input is inhibited (e.g. BMS balancing cycle) */
|
||||
ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags);
|
||||
if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY))
|
||||
return POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
/* Check if charger is present */
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE), &flag);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!flag)
|
||||
return POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
/* Check if AC is charge capable */
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC), &flag);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!flag)
|
||||
return POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
/* Check if AC input limit is too low */
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current);
|
||||
if (!ret && ac_current < 100)
|
||||
return POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
/* Check if battery is full */
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (flag)
|
||||
return POWER_SUPPLY_STATUS_FULL;
|
||||
|
||||
/* Check for user-defined charge limits */
|
||||
if (power->has_chls) {
|
||||
u16 vu16;
|
||||
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
|
||||
if (ret == 0 && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD)
|
||||
charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET;
|
||||
} else if (power->has_chwa) {
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag);
|
||||
if (ret == 0 && flag)
|
||||
charge_limit = CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET;
|
||||
}
|
||||
|
||||
if (charge_limit > 0) {
|
||||
u8 buic = 0;
|
||||
|
||||
if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
|
||||
buic >= charge_limit)
|
||||
limited = true;
|
||||
}
|
||||
|
||||
/* Check charging inhibitors */
|
||||
ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags);
|
||||
if (!ret) {
|
||||
if (nocharge_flags & CHNC_BATTERY_FULL)
|
||||
return POWER_SUPPLY_STATUS_FULL;
|
||||
/* BMS busy shows up as inhibit, but we treat it as charging */
|
||||
else if (nocharge_flags == CHNC_BMS_BUSY && !limited)
|
||||
return POWER_SUPPLY_STATUS_CHARGING;
|
||||
else if (nocharge_flags)
|
||||
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
else
|
||||
return POWER_SUPPLY_STATUS_CHARGING;
|
||||
}
|
||||
|
||||
/* Fallback: System charging flag */
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC), &flag);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!flag)
|
||||
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
|
||||
return POWER_SUPPLY_STATUS_CHARGING;
|
||||
}
|
||||
|
||||
static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
|
||||
{
|
||||
int ret;
|
||||
u8 val8;
|
||||
u8 chte_buf[4];
|
||||
|
||||
if (power->has_ch0i) {
|
||||
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (val8 & CH0R_NOAC_CH0I)
|
||||
return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
|
||||
}
|
||||
|
||||
if (power->has_chte) {
|
||||
ret = apple_smc_read(power->smc, SMC_KEY(CHTE), chte_buf, 4);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (chte_buf[0] == 0x01)
|
||||
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
|
||||
} else if (power->has_ch0c) {
|
||||
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (val8 & CH0X_CH0C)
|
||||
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
|
||||
}
|
||||
|
||||
return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
|
||||
}
|
||||
|
||||
static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (val) {
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
|
||||
/* Reset all inhibitors to a known-good 'auto' state */
|
||||
if (power->has_ch0i) {
|
||||
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (power->has_chte) {
|
||||
ret = apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (power->has_ch0c) {
|
||||
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
|
||||
if (power->has_chte)
|
||||
return apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 1);
|
||||
else if (power->has_ch0c)
|
||||
return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1);
|
||||
else
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
|
||||
if (!power->has_ch0i)
|
||||
return -EOPNOTSUPP;
|
||||
return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int macsmc_battery_get_date(const char *s, int *out)
|
||||
{
|
||||
if (!isdigit(s[0]) || !isdigit(s[1]))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
*out = (s[0] - '0') * 10 + s[1] - '0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
|
||||
{
|
||||
bool flag;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/* Check for emergency shutdown condition */
|
||||
if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
||||
|
||||
/* Check AC status for whether we could boot in this state */
|
||||
if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) {
|
||||
if (!(val & ACSt_CAN_BOOT_IBOOT))
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
||||
|
||||
if (!(val & ACSt_CAN_BOOT_AP))
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
||||
}
|
||||
|
||||
/* BSFC = Battery System Full Charge */
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag);
|
||||
if (ret < 0)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
|
||||
|
||||
if (flag)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
|
||||
else
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
||||
}
|
||||
|
||||
static int macsmc_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct macsmc_power *power = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
u8 vu8;
|
||||
u16 vu16;
|
||||
s16 vs16;
|
||||
s32 vs32;
|
||||
s64 vs64;
|
||||
bool flag;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
val->intval = macsmc_battery_get_status(power);
|
||||
ret = val->intval < 0 ? val->intval : 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
|
||||
val->intval = macsmc_battery_get_charge_behaviour(power);
|
||||
ret = val->intval < 0 ? val->intval : 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
|
||||
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
|
||||
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
|
||||
val->intval = vu8;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
||||
val->intval = macsmc_battery_get_capacity_level(power);
|
||||
ret = val->intval < 0 ? val->intval : 0;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
|
||||
val->intval = vs16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_POWER_NOW:
|
||||
ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
|
||||
val->intval = vs32 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||||
/* Calculate total max design voltage from per-cell maximum voltage */
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16);
|
||||
val->intval = vu16 * 1000 * power->num_cells;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
||||
/* Lifetime min */
|
||||
ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPM), &vs16);
|
||||
val->intval = vs16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
||||
/* Lifetime max */
|
||||
ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16);
|
||||
val->intval = vs16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_FULL:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_NOW:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
|
||||
/* B0RM is Big Endian, likely pass through from TI gas gauge */
|
||||
val->intval = (s16)swab16(vu16) * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
|
||||
val->intval = vu16 * power->nominal_voltage_mv;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ENERGY_FULL:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
|
||||
val->intval = vu16 * power->nominal_voltage_mv;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ENERGY_NOW:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
|
||||
/* B0RM is Big Endian, likely pass through from TI gas gauge */
|
||||
val->intval = (s16)swab16(vu16) * power->nominal_voltage_mv;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
|
||||
val->intval = vu16 - 2732; /* Kelvin x10 to Celsius x10 */
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
|
||||
ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
|
||||
val->intval = vs64;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CYCLE_COUNT:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
|
||||
val->intval = vu16;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
flag = false;
|
||||
ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD), &flag);
|
||||
val->intval = flag ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||
val->strval = power->model_name;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
|
||||
val->strval = power->serial_number;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
|
||||
ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval);
|
||||
/* The SMC reports the manufacture year as an offset from 1992. */
|
||||
val->intval += 1992;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
|
||||
ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
|
||||
ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int macsmc_battery_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct macsmc_power *power = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
|
||||
return macsmc_battery_set_charge_behaviour(power, val->intval);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int macsmc_battery_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct power_supply_desc macsmc_battery_desc_template = {
|
||||
.name = "macsmc-battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.get_property = macsmc_battery_get_property,
|
||||
.set_property = macsmc_battery_set_property,
|
||||
.property_is_writeable = macsmc_battery_property_is_writeable,
|
||||
};
|
||||
|
||||
static int macsmc_ac_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct macsmc_power *power = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
u16 vu16;
|
||||
u32 vu32;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
|
||||
val->intval = !!vu32;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
||||
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16);
|
||||
val->intval = vu16 * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
|
||||
ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32);
|
||||
val->intval = vu32 * 1000;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct power_supply_desc macsmc_ac_desc_template = {
|
||||
.name = "macsmc-ac",
|
||||
.type = POWER_SUPPLY_TYPE_MAINS,
|
||||
.get_property = macsmc_ac_get_property,
|
||||
};
|
||||
|
||||
static void macsmc_power_critical_work(struct work_struct *wrk)
|
||||
{
|
||||
struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
|
||||
u16 bitv, b0av;
|
||||
u32 bcf0;
|
||||
|
||||
if (!power->batt)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Avoid duplicate atempts at emergency shutdown
|
||||
*/
|
||||
if (power->emergency_shutdown_triggered || system_state > SYSTEM_RUNNING)
|
||||
return;
|
||||
|
||||
/*
|
||||
* EMERGENCY: Check voltage vs design minimum.
|
||||
* If we are below BITV, the battery is physically exhausted.
|
||||
* We must shut down NOW to protect the filesystem.
|
||||
*/
|
||||
if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 &&
|
||||
apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 &&
|
||||
b0av < bitv) {
|
||||
power->emergency_shutdown_triggered = true;
|
||||
dev_emerg(power->dev,
|
||||
"Battery voltage (%d mV) below design minimum (%d mV)! Emergency shutdown.\n",
|
||||
b0av, bitv);
|
||||
|
||||
/*
|
||||
* Shutdown is now imminent. Kick userspace again and give it some
|
||||
* brief time to (hopefully) flush what's needed, before forcing.
|
||||
*/
|
||||
hw_protection_trigger("Battery voltage below design minimum", 1500);
|
||||
}
|
||||
|
||||
/*
|
||||
* Avoid duplicate attempts at orderly shutdown.
|
||||
* Voltage check is above this as we may want to
|
||||
* "upgrade" an orderly shutdown to a critical power
|
||||
* off if voltage drops.
|
||||
*/
|
||||
if (power->orderly_shutdown_triggered || system_state > SYSTEM_RUNNING)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Check if SMC flagged the battery as empty.
|
||||
* We trigger a graceful shutdown to let the OS save data.
|
||||
*/
|
||||
if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0) == 0 && bcf0 != 0) {
|
||||
power->orderly_shutdown_triggered = true;
|
||||
dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n");
|
||||
orderly_poweroff(true);
|
||||
}
|
||||
}
|
||||
|
||||
static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
|
||||
{
|
||||
struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
|
||||
|
||||
/*
|
||||
* SMC Event IDs are correlated to physical events (e.g. charger
|
||||
* connect/disconnect) but the exact meaning of each ID is predicted.
|
||||
* 0x71... indicates power/battery events.
|
||||
*/
|
||||
if ((event & 0xffffff00) == 0x71010100 || /* Charger status change */
|
||||
(event & 0xffff0000) == 0x71060000 || /* Port charge state change */
|
||||
(event & 0xffff0000) == 0x71130000) { /* Connector insert/remove event */
|
||||
if (power->batt)
|
||||
power_supply_changed(power->batt);
|
||||
if (power->ac)
|
||||
power_supply_changed(power->ac);
|
||||
return NOTIFY_OK;
|
||||
} else if (event == 0x71020000) {
|
||||
/* Critical battery warning */
|
||||
if (power->batt)
|
||||
schedule_work(&power->critical_work);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int macsmc_power_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct macsmc_power *power;
|
||||
bool has_battery = false;
|
||||
bool has_ac_adapter = false;
|
||||
int ret = -ENODEV;
|
||||
bool flag;
|
||||
u16 vu16;
|
||||
u32 val32;
|
||||
enum power_supply_property *props;
|
||||
size_t nprops;
|
||||
|
||||
if (!smc)
|
||||
return -ENODEV;
|
||||
|
||||
power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL);
|
||||
if (!power)
|
||||
return -ENOMEM;
|
||||
|
||||
power->dev = dev;
|
||||
power->smc = smc;
|
||||
dev_set_drvdata(dev, power);
|
||||
|
||||
INIT_WORK(&power->critical_work, macsmc_power_critical_work);
|
||||
ret = devm_work_autocancel(dev, &power->critical_work, macsmc_power_critical_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Check for battery presence.
|
||||
* B0AV is a fundamental key.
|
||||
*/
|
||||
if (apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16) == 0 &&
|
||||
macsmc_battery_get_status(power) > POWER_SUPPLY_STATUS_UNKNOWN)
|
||||
has_battery = true;
|
||||
|
||||
/*
|
||||
* Check for AC adapter presence.
|
||||
* CHIS is a fundamental key.
|
||||
*/
|
||||
if (apple_smc_key_exists(smc, SMC_KEY(CHIS)))
|
||||
has_ac_adapter = true;
|
||||
|
||||
if (!has_battery && !has_ac_adapter)
|
||||
return -ENODEV;
|
||||
|
||||
if (has_battery) {
|
||||
power->batt_desc = macsmc_battery_desc_template;
|
||||
props = devm_kcalloc(dev, MACSMC_MAX_BATT_PROPS,
|
||||
sizeof(enum power_supply_property),
|
||||
GFP_KERNEL);
|
||||
if (!props)
|
||||
return -ENOMEM;
|
||||
|
||||
nprops = 0;
|
||||
|
||||
/* Fundamental properties */
|
||||
props[nprops++] = POWER_SUPPLY_PROP_STATUS;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_PRESENT;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CAPACITY;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_TEMP;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_HEALTH;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_SCOPE;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_MODEL_NAME;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_SERIAL_NUMBER;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_YEAR;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_MONTH;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_DAY;
|
||||
|
||||
/* Extended properties usually present */
|
||||
props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_COUNTER;
|
||||
|
||||
/* Detect features based on key availability */
|
||||
if (apple_smc_key_exists(smc, SMC_KEY(CHTE)))
|
||||
power->has_chte = true;
|
||||
if (apple_smc_key_exists(smc, SMC_KEY(CH0C)))
|
||||
power->has_ch0c = true;
|
||||
if (apple_smc_key_exists(smc, SMC_KEY(CH0I)))
|
||||
power->has_ch0i = true;
|
||||
|
||||
/* Reset "Optimised Battery Charging" flags to default state */
|
||||
if (power->has_chte)
|
||||
apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
|
||||
else if (power->has_ch0c)
|
||||
apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);
|
||||
|
||||
if (power->has_ch0i)
|
||||
apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);
|
||||
|
||||
apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
|
||||
apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
|
||||
|
||||
/* Configure charge behaviour if supported */
|
||||
if (power->has_ch0i || power->has_ch0c || power->has_chte) {
|
||||
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
|
||||
|
||||
power->batt_desc.charge_behaviours =
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
|
||||
|
||||
if (power->has_ch0i)
|
||||
power->batt_desc.charge_behaviours |=
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
|
||||
|
||||
if (power->has_chte || power->has_ch0c)
|
||||
power->batt_desc.charge_behaviours |=
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
|
||||
}
|
||||
|
||||
/* Detect charge limit method (CHWA vs CHLS) */
|
||||
if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0)
|
||||
power->has_chwa = true;
|
||||
else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0)
|
||||
power->has_chls = true;
|
||||
|
||||
if (nprops > MACSMC_MAX_BATT_PROPS)
|
||||
return -ENOMEM;
|
||||
|
||||
power->batt_desc.properties = props;
|
||||
power->batt_desc.num_properties = nprops;
|
||||
|
||||
/* Fetch identity strings */
|
||||
apple_smc_read(smc, SMC_KEY(BMDN), power->model_name,
|
||||
sizeof(power->model_name) - 1);
|
||||
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number,
|
||||
sizeof(power->serial_number) - 1);
|
||||
apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date,
|
||||
sizeof(power->mfg_date) - 1);
|
||||
|
||||
apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
|
||||
power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
|
||||
|
||||
/* Enable critical shutdown notifications by reading status once */
|
||||
apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32);
|
||||
|
||||
psy_cfg.drv_data = power;
|
||||
power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg);
|
||||
if (IS_ERR(power->batt)) {
|
||||
dev_err_probe(dev, PTR_ERR(power->batt),
|
||||
"Failed to register battery\n");
|
||||
/* Don't return failure yet; try AC registration first */
|
||||
power->batt = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_ac_adapter) {
|
||||
power->ac_desc = macsmc_ac_desc_template;
|
||||
props = devm_kcalloc(dev, MACSMC_MAX_AC_PROPS,
|
||||
sizeof(enum power_supply_property),
|
||||
GFP_KERNEL);
|
||||
if (!props)
|
||||
return -ENOMEM;
|
||||
|
||||
nprops = 0;
|
||||
|
||||
/* Online status is fundamental */
|
||||
props[nprops++] = POWER_SUPPLY_PROP_ONLINE;
|
||||
|
||||
/* Input power limits are usually available */
|
||||
if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW)))
|
||||
props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT;
|
||||
|
||||
/* macOS 15.4+ firmware dropped legacy AC keys (AC-n, AC-i) */
|
||||
if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) >= 0) {
|
||||
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
|
||||
props[nprops++] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
|
||||
}
|
||||
|
||||
if (nprops > MACSMC_MAX_AC_PROPS)
|
||||
return -ENOMEM;
|
||||
|
||||
power->ac_desc.properties = props;
|
||||
power->ac_desc.num_properties = nprops;
|
||||
|
||||
psy_cfg.drv_data = power;
|
||||
power->ac = devm_power_supply_register(dev, &power->ac_desc, &psy_cfg);
|
||||
if (IS_ERR(power->ac)) {
|
||||
dev_err_probe(dev, PTR_ERR(power->ac),
|
||||
"Failed to register AC adapter\n");
|
||||
power->ac = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Final check: did we register anything? */
|
||||
if (!power->batt && !power->ac)
|
||||
return -ENODEV;
|
||||
|
||||
power->nb.notifier_call = macsmc_power_event;
|
||||
blocking_notifier_chain_register(&smc->event_handlers, &power->nb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void macsmc_power_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb);
|
||||
}
|
||||
|
||||
static const struct platform_device_id macsmc_power_id[] = {
|
||||
{ "macsmc-power" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, macsmc_power_id);
|
||||
|
||||
static struct platform_driver macsmc_power_driver = {
|
||||
.driver = {
|
||||
.name = "macsmc-power",
|
||||
},
|
||||
.id_table = macsmc_power_id,
|
||||
.probe = macsmc_power_probe,
|
||||
.remove = macsmc_power_remove,
|
||||
};
|
||||
module_platform_driver(macsmc_power_driver);
|
||||
|
||||
MODULE_LICENSE("Dual MIT/GPL");
|
||||
MODULE_DESCRIPTION("Apple SMC battery and power management driver");
|
||||
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
|
||||
MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");
|
||||
@@ -61,6 +61,7 @@ struct max17042_chip {
|
||||
struct work_struct work;
|
||||
int init_complete;
|
||||
int irq;
|
||||
int task_period;
|
||||
};
|
||||
|
||||
static enum power_supply_property max17042_battery_props[] = {
|
||||
@@ -88,6 +89,7 @@ static enum power_supply_property max17042_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
|
||||
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
|
||||
// these two have to be at the end on the list
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_AVG,
|
||||
@@ -131,7 +133,7 @@ static int max17042_get_status(struct max17042_chip *chip, int *status)
|
||||
* FullCAP to match RepCap when it detects end of charging.
|
||||
*
|
||||
* When this cycle the battery gets charged to a higher (calculated)
|
||||
* capacity then the previous cycle then FullCAP will get updated
|
||||
* capacity than the previous cycle then FullCAP will get updated
|
||||
* continuously once end-of-charge detection kicks in, so allow the
|
||||
* 2 to differ a bit.
|
||||
*/
|
||||
@@ -201,7 +203,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
|
||||
if (vbatt > size_add(chip->pdata->vmax, MAX17042_VMAX_TOLERANCE)) {
|
||||
*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
goto out;
|
||||
}
|
||||
@@ -331,6 +333,8 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
return ret;
|
||||
|
||||
data64 = data * 5000000ll;
|
||||
data64 *= chip->task_period;
|
||||
do_div(data64, MAX17042_DEFAULT_TASK_PERIOD);
|
||||
do_div(data64, chip->pdata->r_sns);
|
||||
val->intval = data64;
|
||||
break;
|
||||
@@ -340,6 +344,8 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
return ret;
|
||||
|
||||
data64 = data * 5000000ll;
|
||||
data64 *= chip->task_period;
|
||||
do_div(data64, MAX17042_DEFAULT_TASK_PERIOD);
|
||||
do_div(data64, chip->pdata->r_sns);
|
||||
val->intval = data64;
|
||||
break;
|
||||
@@ -349,6 +355,8 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
return ret;
|
||||
|
||||
data64 = data * 5000000ll;
|
||||
data64 *= chip->task_period;
|
||||
do_div(data64, MAX17042_DEFAULT_TASK_PERIOD);
|
||||
do_div(data64, chip->pdata->r_sns);
|
||||
val->intval = data64;
|
||||
break;
|
||||
@@ -358,6 +366,8 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
return ret;
|
||||
|
||||
data64 = sign_extend64(data, 15) * 5000000ll;
|
||||
data64 *= chip->task_period;
|
||||
data64 = div_s64(data64, MAX17042_DEFAULT_TASK_PERIOD);
|
||||
val->intval = div_s64(data64, chip->pdata->r_sns);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
@@ -430,6 +440,25 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* when charging, the value is not meaningful */
|
||||
if (data == U16_MAX)
|
||||
return -ENODATA;
|
||||
|
||||
val->intval = data * 5625 / 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
|
||||
if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055 &&
|
||||
chip->chip_type != MAXIM_DEVICE_TYPE_MAX77759)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_read(map, MAX17055_TTF, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* when discharging, the value is not meaningful */
|
||||
if (data == U16_MAX)
|
||||
return -ENODATA;
|
||||
|
||||
val->intval = data * 5625 / 1000;
|
||||
break;
|
||||
default:
|
||||
@@ -646,7 +675,8 @@ static void max17042_write_config_regs(struct max17042_chip *chip)
|
||||
regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
|
||||
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
|
||||
chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 ||
|
||||
chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
|
||||
chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 ||
|
||||
chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)
|
||||
regmap_write(map, MAX17047_FullSOCThr,
|
||||
config->full_soc_thresh);
|
||||
}
|
||||
@@ -783,7 +813,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
|
||||
|
||||
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) ||
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)) {
|
||||
max17042_override_por(map, MAX17042_IAvg_empty, config->iavg_empty);
|
||||
max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
|
||||
max17042_override_por(map, MAX17042_TempLim, config->temp_lim);
|
||||
@@ -792,7 +823,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
|
||||
|
||||
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) ||
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)) {
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) ||
|
||||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)) {
|
||||
max17042_override_por(map, MAX17047_V_empty, config->vempty);
|
||||
}
|
||||
}
|
||||
@@ -921,8 +953,12 @@ max17042_get_of_pdata(struct max17042_chip *chip)
|
||||
/*
|
||||
* Require current sense resistor value to be specified for
|
||||
* current-sense functionality to be enabled at all.
|
||||
* maxim,rsns-microohm is the property name used by older DTs and kept
|
||||
* for compatibility.
|
||||
*/
|
||||
if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
|
||||
if ((of_property_read_u32(np, "shunt-resistor-micro-ohms",
|
||||
&prop) == 0) ||
|
||||
(of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0)) {
|
||||
pdata->r_sns = prop;
|
||||
pdata->enable_current_sense = true;
|
||||
}
|
||||
@@ -1011,6 +1047,45 @@ static const struct regmap_config max17042_regmap_config = {
|
||||
.val_format_endian = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_fg_registers[] = {
|
||||
regmap_reg_range(MAX17042_STATUS, MAX77759_MixAtFull),
|
||||
regmap_reg_range(MAX17042_VFSOC0Enable, MAX17042_VFSOC0Enable),
|
||||
regmap_reg_range(MAX17042_MLOCKReg1, MAX17042_MLOCKReg2),
|
||||
regmap_reg_range(MAX17042_MODELChrTbl, MAX17055_TimerH),
|
||||
regmap_reg_range(MAX77759_IIn, MAX77759_IIn),
|
||||
regmap_reg_range(MAX17055_AtQResidual, MAX17055_AtAvCap),
|
||||
regmap_reg_range(MAX17042_OCVInternal, MAX17042_OCVInternal),
|
||||
regmap_reg_range(MAX17042_VFSOC, MAX17042_VFSOC),
|
||||
};
|
||||
|
||||
static const struct regmap_range max77759_fg_ro_registers[] = {
|
||||
regmap_reg_range(MAX17042_FSTAT, MAX17042_FSTAT),
|
||||
regmap_reg_range(MAX17042_OCVInternal, MAX17042_OCVInternal),
|
||||
regmap_reg_range(MAX17042_VFSOC, MAX17042_VFSOC),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_fg_write_table = {
|
||||
.yes_ranges = max77759_fg_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_fg_registers),
|
||||
.no_ranges = max77759_fg_ro_registers,
|
||||
.n_no_ranges = ARRAY_SIZE(max77759_fg_ro_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max77759_fg_rd_table = {
|
||||
.yes_ranges = max77759_fg_registers,
|
||||
.n_yes_ranges = ARRAY_SIZE(max77759_fg_registers),
|
||||
};
|
||||
|
||||
static const struct regmap_config max77759_fg_regmap_cfg = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.max_register = 0xff,
|
||||
.wr_table = &max77759_fg_write_table,
|
||||
.rd_table = &max77759_fg_rd_table,
|
||||
.val_format_endian = REGMAP_ENDIAN_NATIVE,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc max17042_psy_desc = {
|
||||
.name = "max170xx_battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
@@ -1037,6 +1112,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
const struct power_supply_desc *max17042_desc = &max17042_psy_desc;
|
||||
const struct regmap_config *regmap_config;
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct max17042_chip *chip;
|
||||
int ret;
|
||||
@@ -1052,17 +1128,20 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
|
||||
|
||||
chip->dev = dev;
|
||||
chip->chip_type = chip_type;
|
||||
chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config);
|
||||
if (IS_ERR(chip->regmap)) {
|
||||
dev_err(dev, "Failed to initialize regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759)
|
||||
regmap_config = &max77759_fg_regmap_cfg;
|
||||
else
|
||||
regmap_config = &max17042_regmap_config;
|
||||
chip->regmap = devm_regmap_init_i2c(client, regmap_config);
|
||||
if (IS_ERR(chip->regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(chip->regmap),
|
||||
"Failed to initialize regmap\n");
|
||||
|
||||
chip->pdata = max17042_get_pdata(chip);
|
||||
if (!chip->pdata) {
|
||||
dev_err(dev, "no platform data provided\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!chip->pdata)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"no platform data provided\n");
|
||||
|
||||
dev_set_drvdata(dev, chip);
|
||||
psy_cfg.drv_data = chip;
|
||||
@@ -1088,12 +1167,22 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
|
||||
regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
|
||||
}
|
||||
|
||||
chip->task_period = MAX17042_DEFAULT_TASK_PERIOD;
|
||||
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX77759) {
|
||||
ret = regmap_read(chip->regmap, MAX17042_TaskPeriod, &val);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"failed to read task period\n");
|
||||
chip->task_period = val;
|
||||
}
|
||||
dev_dbg(dev, "task period: %#.4x (%d)\n", chip->task_period,
|
||||
chip->task_period);
|
||||
|
||||
chip->battery = devm_power_supply_register(dev, max17042_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(chip->battery)) {
|
||||
dev_err(dev, "failed: power supply register\n");
|
||||
return PTR_ERR(chip->battery);
|
||||
}
|
||||
if (IS_ERR(chip->battery))
|
||||
return dev_err_probe(dev, PTR_ERR(chip->battery),
|
||||
"failed: power supply register\n");
|
||||
|
||||
if (irq) {
|
||||
unsigned int flags = IRQF_ONESHOT | IRQF_SHARED | IRQF_PROBE_SHARED;
|
||||
@@ -1236,6 +1325,8 @@ static const struct of_device_id max17042_dt_match[] __used = {
|
||||
.data = (void *) MAXIM_DEVICE_TYPE_MAX17055 },
|
||||
{ .compatible = "maxim,max77705-battery",
|
||||
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
|
||||
{ .compatible = "maxim,max77759-fg",
|
||||
.data = (void *) MAXIM_DEVICE_TYPE_MAX77759 },
|
||||
{ .compatible = "maxim,max77849-battery",
|
||||
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
|
||||
{ },
|
||||
@@ -1248,6 +1339,7 @@ static const struct i2c_device_id max17042_id[] = {
|
||||
{ "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
|
||||
{ "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
|
||||
{ "max17055", MAXIM_DEVICE_TYPE_MAX17055 },
|
||||
{ "max77759-fg", MAXIM_DEVICE_TYPE_MAX77759 },
|
||||
{ "max77849-battery", MAXIM_DEVICE_TYPE_MAX17047 },
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -646,51 +646,37 @@ static int max77705_charger_probe(struct i2c_client *i2c)
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to add irq chip\n");
|
||||
|
||||
chg->wqueue = create_singlethread_workqueue(dev_name(dev));
|
||||
chg->wqueue = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(dev));
|
||||
if (!chg->wqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work);
|
||||
if (ret) {
|
||||
dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
|
||||
goto destroy_wq;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
|
||||
|
||||
ret = max77705_charger_initialize(chg);
|
||||
if (ret) {
|
||||
dev_err_probe(dev, ret, "failed to initialize charger IC\n");
|
||||
goto destroy_wq;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to initialize charger IC\n");
|
||||
|
||||
ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_CHGIN_I),
|
||||
NULL, max77705_chgin_irq,
|
||||
IRQF_TRIGGER_NONE,
|
||||
"chgin-irq", chg);
|
||||
if (ret) {
|
||||
dev_err_probe(dev, ret, "Failed to Request chgin IRQ\n");
|
||||
goto destroy_wq;
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_AICL_I),
|
||||
NULL, max77705_aicl_irq,
|
||||
IRQF_TRIGGER_NONE,
|
||||
"aicl-irq", chg);
|
||||
if (ret) {
|
||||
dev_err_probe(dev, ret, "Failed to Request aicl IRQ\n");
|
||||
goto destroy_wq;
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = max77705_charger_enable(chg);
|
||||
if (ret) {
|
||||
dev_err_probe(dev, ret, "failed to enable charge\n");
|
||||
goto destroy_wq;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to enable charge\n");
|
||||
|
||||
return devm_add_action_or_reset(dev, max77705_charger_disable, chg);
|
||||
|
||||
destroy_wq:
|
||||
destroy_workqueue(chg->wqueue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id max77705_charger_of_match[] = {
|
||||
|
||||
@@ -761,13 +761,6 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv)
|
||||
return PTR_ERR_OR_ZERO(priv->psy);
|
||||
}
|
||||
|
||||
static void mt6370_chg_destroy_wq(void *data)
|
||||
{
|
||||
struct workqueue_struct *wq = data;
|
||||
|
||||
destroy_workqueue(wq);
|
||||
}
|
||||
|
||||
static irqreturn_t mt6370_attach_i_handler(int irq, void *data)
|
||||
{
|
||||
struct mt6370_priv *priv = data;
|
||||
@@ -893,14 +886,10 @@ static int mt6370_chg_probe(struct platform_device *pdev)
|
||||
|
||||
priv->attach = MT6370_ATTACH_STAT_DETACH;
|
||||
|
||||
priv->wq = create_singlethread_workqueue(dev_name(priv->dev));
|
||||
priv->wq = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(priv->dev));
|
||||
if (!priv->wq)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_wq, priv->wq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_work_autocancel(dev, &priv->bc12_work, mt6370_chg_bc12_work_func);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to init bc12 work\n");
|
||||
|
||||
@@ -1611,6 +1611,8 @@ static void qcom_battmgr_pdr_notify(void *priv, int state)
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_battmgr_of_variants[] = {
|
||||
{ .compatible = "qcom,glymur-pmic-glink", .data = (void *)QCOM_BATTMGR_X1E80100 },
|
||||
{ .compatible = "qcom,kaanapali-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 },
|
||||
{ .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
|
||||
{ .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP },
|
||||
{ .compatible = "qcom,sm8550-pmic-glink", .data = (void *)QCOM_BATTMGR_SM8550 },
|
||||
|
||||
@@ -134,6 +134,9 @@
|
||||
#define OCP_CHARGER_BIT BIT(1)
|
||||
#define SDP_CHARGER_BIT BIT(0)
|
||||
|
||||
#define USBIN_CMD_IL 0x340
|
||||
#define USBIN_SUSPEND_BIT BIT(0)
|
||||
|
||||
#define TYPE_C_STATUS_1 0x30B
|
||||
#define UFP_TYPEC_MASK GENMASK(7, 5)
|
||||
#define UFP_TYPEC_RDSTD_BIT BIT(7)
|
||||
@@ -693,6 +696,9 @@ static int smb_set_property(struct power_supply *psy,
|
||||
struct smb_chip *chip = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
return regmap_update_bits(chip->regmap, chip->base + USBIN_CMD_IL,
|
||||
USBIN_SUSPEND_BIT, !val->intval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
return smb_set_current_limit(chip, val->intval);
|
||||
default:
|
||||
@@ -705,6 +711,7 @@ static int smb_property_is_writable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
return 1;
|
||||
default:
|
||||
|
||||
307
drivers/power/supply/s2mu005-battery.c
Normal file
307
drivers/power/supply/s2mu005-battery.c
Normal file
@@ -0,0 +1,307 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Battery Fuel Gauge Driver for Samsung S2MU005 PMIC.
|
||||
*
|
||||
* Copyright (C) 2015 Samsung Electronics
|
||||
* Copyright (C) 2023 Yassine Oudjana <y.oudjana@protonmail.com>
|
||||
* Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org>
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#define S2MU005_FG_REG_STATUS 0x00
|
||||
#define S2MU005_FG_REG_IRQ 0x02
|
||||
#define S2MU005_FG_REG_RVBAT 0x04
|
||||
#define S2MU005_FG_REG_RCURCC 0x06
|
||||
#define S2MU005_FG_REG_RSOC 0x08
|
||||
#define S2MU005_FG_REG_MONOUT 0x0a
|
||||
#define S2MU005_FG_REG_MONOUTSEL 0x0c
|
||||
#define S2MU005_FG_REG_RBATCAP 0x0e
|
||||
#define S2MU005_FG_REG_RZADJ 0x12
|
||||
#define S2MU005_FG_REG_RBATZ0 0x16
|
||||
#define S2MU005_FG_REG_RBATZ1 0x18
|
||||
#define S2MU005_FG_REG_IRQLVL 0x1a
|
||||
#define S2MU005_FG_REG_START 0x1e
|
||||
|
||||
#define S2MU005_FG_MONOUTSEL_AVGCURRENT 0x26
|
||||
#define S2MU005_FG_MONOUTSEL_AVGVOLTAGE 0x27
|
||||
|
||||
struct s2mu005_fg {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct power_supply *psy;
|
||||
struct mutex monout_mutex;
|
||||
};
|
||||
|
||||
static const struct regmap_config s2mu005_fg_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.val_format_endian = REGMAP_ENDIAN_LITTLE,
|
||||
};
|
||||
|
||||
static irqreturn_t s2mu005_handle_irq(int irq, void *data)
|
||||
{
|
||||
struct s2mu005_fg *priv = data;
|
||||
|
||||
msleep(100);
|
||||
power_supply_changed(priv->psy);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int s2mu005_fg_get_voltage_now(struct s2mu005_fg *priv, int *value)
|
||||
{
|
||||
struct regmap *regmap = priv->regmap;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, S2MU005_FG_REG_RVBAT, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to read voltage register (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*value = (val * MICRO) >> 13;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s2mu005_fg_get_voltage_avg(struct s2mu005_fg *priv, int *value)
|
||||
{
|
||||
struct regmap *regmap = priv->regmap;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&priv->monout_mutex);
|
||||
|
||||
ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
|
||||
S2MU005_FG_MONOUTSEL_AVGVOLTAGE);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to enable average voltage monitoring (%d)\n",
|
||||
ret);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to read current register (%d)\n", ret);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
*value = (val * MICRO) >> 12;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&priv->monout_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static int s2mu005_fg_get_current_now(struct s2mu005_fg *priv, int *value)
|
||||
{
|
||||
struct regmap *regmap = priv->regmap;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, S2MU005_FG_REG_RCURCC, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to read current register (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*value = -((s16)val * MICRO) >> 12;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s2mu005_fg_get_current_avg(struct s2mu005_fg *priv, int *value)
|
||||
{
|
||||
struct regmap *regmap = priv->regmap;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&priv->monout_mutex);
|
||||
|
||||
ret = regmap_write(regmap, S2MU005_FG_REG_MONOUTSEL,
|
||||
S2MU005_FG_MONOUTSEL_AVGCURRENT);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to enable average current monitoring (%d)\n",
|
||||
ret);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = regmap_read(regmap, S2MU005_FG_REG_MONOUT, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to read current register (%d)\n", ret);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
*value = -((s16)val * MICRO) >> 12;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&priv->monout_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s2mu005_fg_get_capacity(struct s2mu005_fg *priv, int *value)
|
||||
{
|
||||
struct regmap *regmap = priv->regmap;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, S2MU005_FG_REG_RSOC, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(priv->dev, "failed to read capacity register (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*value = (val * CENTI) >> 14;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s2mu005_fg_get_status(struct s2mu005_fg *priv, int *value)
|
||||
{
|
||||
int current_now, current_avg, capacity;
|
||||
int ret;
|
||||
|
||||
ret = s2mu005_fg_get_current_now(priv, ¤t_now);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = s2mu005_fg_get_current_avg(priv, ¤t_avg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Verify both current values reported to reduce inaccuracies due to
|
||||
* internal hysteresis.
|
||||
*/
|
||||
if (current_now < 0 && current_avg < 0) {
|
||||
*value = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
} else if (current_now == 0) {
|
||||
*value = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
} else {
|
||||
*value = POWER_SUPPLY_STATUS_CHARGING;
|
||||
|
||||
ret = s2mu005_fg_get_capacity(priv, &capacity);
|
||||
if (!ret && capacity > 98)
|
||||
*value = POWER_SUPPLY_STATUS_FULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum power_supply_property s2mu005_fg_properties[] = {
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_AVG,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_AVG,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
};
|
||||
|
||||
static int s2mu005_fg_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct s2mu005_fg *priv = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
return s2mu005_fg_get_voltage_now(priv, &val->intval);
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
|
||||
return s2mu005_fg_get_voltage_avg(priv, &val->intval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
return s2mu005_fg_get_current_now(priv, &val->intval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_AVG:
|
||||
return s2mu005_fg_get_current_avg(priv, &val->intval);
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
return s2mu005_fg_get_capacity(priv, &val->intval);
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
return s2mu005_fg_get_status(priv, &val->intval);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct power_supply_desc s2mu005_fg_desc = {
|
||||
.name = "s2mu005-fuel-gauge",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.properties = s2mu005_fg_properties,
|
||||
.num_properties = ARRAY_SIZE(s2mu005_fg_properties),
|
||||
.get_property = s2mu005_fg_get_property,
|
||||
};
|
||||
|
||||
static int s2mu005_fg_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct s2mu005_fg *priv;
|
||||
struct power_supply_config psy_cfg = {};
|
||||
const struct power_supply_desc *psy_desc;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, priv);
|
||||
priv->dev = dev;
|
||||
|
||||
priv->regmap = devm_regmap_init_i2c(client, &s2mu005_fg_regmap_config);
|
||||
if (IS_ERR(priv->regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->regmap),
|
||||
"failed to initialize regmap\n");
|
||||
|
||||
ret = devm_mutex_init(dev, &priv->monout_mutex);
|
||||
if (ret)
|
||||
dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n");
|
||||
|
||||
psy_desc = device_get_match_data(dev);
|
||||
|
||||
psy_cfg.drv_data = priv;
|
||||
psy_cfg.fwnode = dev_fwnode(dev);
|
||||
priv->psy = devm_power_supply_register(priv->dev, psy_desc, &psy_cfg);
|
||||
if (IS_ERR(priv->psy))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->psy),
|
||||
"failed to register power supply subsystem\n");
|
||||
|
||||
ret = devm_request_threaded_irq(priv->dev, client->irq, NULL,
|
||||
s2mu005_handle_irq, IRQF_ONESHOT,
|
||||
psy_desc->name, priv);
|
||||
if (ret)
|
||||
dev_err_probe(dev, ret, "failed to request IRQ\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id s2mu005_fg_of_match_table[] = {
|
||||
{
|
||||
.compatible = "samsung,s2mu005-fuel-gauge",
|
||||
.data = &s2mu005_fg_desc,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, s2mu005_fg_of_match_table);
|
||||
|
||||
static struct i2c_driver s2mu005_fg_i2c_driver = {
|
||||
.probe = s2mu005_fg_i2c_probe,
|
||||
.driver = {
|
||||
.name = "s2mu005-fuel-gauge",
|
||||
.of_match_table = s2mu005_fg_of_match_table,
|
||||
},
|
||||
};
|
||||
module_i2c_driver(s2mu005_fg_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Samsung S2MU005 PMIC Battery Fuel Gauge Driver");
|
||||
MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
|
||||
MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -199,7 +199,7 @@ static int sbsm_gpio_get_value(struct gpio_chip *gc, unsigned int off)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret & BIT(off);
|
||||
return !!(ret & BIT(off));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/param.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#define MAX17042_DEFAULT_VMAX (4500) /* LiHV cell max */
|
||||
#define MAX17042_DEFAULT_TEMP_MIN (0) /* For sys without temp sensor */
|
||||
#define MAX17042_DEFAULT_TEMP_MAX (700) /* 70 degrees Celcius */
|
||||
#define MAX17042_DEFAULT_TASK_PERIOD (5760)
|
||||
|
||||
/* Consider RepCap which is less then 10 units below FullCAP full */
|
||||
#define MAX17042_FULL_THRESHOLD 10
|
||||
@@ -105,7 +106,7 @@ enum max17042_register {
|
||||
|
||||
MAX17042_OCV = 0xEE,
|
||||
|
||||
MAX17042_OCVInternal = 0xFB, /* MAX17055 VFOCV */
|
||||
MAX17042_OCVInternal = 0xFB, /* MAX17055/77759 VFOCV */
|
||||
|
||||
MAX17042_VFSOC = 0xFF,
|
||||
};
|
||||
@@ -156,7 +157,7 @@ enum max17055_register {
|
||||
MAX17055_AtAvCap = 0xDF,
|
||||
};
|
||||
|
||||
/* Registers specific to max17047/50/55 */
|
||||
/* Registers specific to max17047/50/55/77759 */
|
||||
enum max17047_register {
|
||||
MAX17047_QRTbl00 = 0x12,
|
||||
MAX17047_FullSOCThr = 0x13,
|
||||
@@ -167,12 +168,32 @@ enum max17047_register {
|
||||
MAX17047_QRTbl30 = 0x42,
|
||||
};
|
||||
|
||||
enum max77759_register {
|
||||
MAX77759_AvgTA0 = 0x26,
|
||||
MAX77759_AtTTF = 0x33,
|
||||
MAX77759_Tconvert = 0x34,
|
||||
MAX77759_AvgCurrent0 = 0x3B,
|
||||
MAX77759_THMHOT = 0x40,
|
||||
MAX77759_CTESample = 0x41,
|
||||
MAX77759_ISys = 0x43,
|
||||
MAX77759_AvgVCell0 = 0x44,
|
||||
MAX77759_RlxSOC = 0x47,
|
||||
MAX77759_AvgISys = 0x4B,
|
||||
MAX77759_QH0 = 0x4C,
|
||||
MAX77759_MixAtFull = 0x4F,
|
||||
MAX77759_VSys = 0xB1,
|
||||
MAX77759_TAlrtTh2 = 0xB2,
|
||||
MAX77759_VByp = 0xB3,
|
||||
MAX77759_IIn = 0xD0,
|
||||
};
|
||||
|
||||
enum max170xx_chip_type {
|
||||
MAXIM_DEVICE_TYPE_UNKNOWN = 0,
|
||||
MAXIM_DEVICE_TYPE_MAX17042,
|
||||
MAXIM_DEVICE_TYPE_MAX17047,
|
||||
MAXIM_DEVICE_TYPE_MAX17050,
|
||||
MAXIM_DEVICE_TYPE_MAX17055,
|
||||
MAXIM_DEVICE_TYPE_MAX77759,
|
||||
|
||||
MAXIM_DEVICE_TYPE_NUM
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user