mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 11:56:58 +00:00
Merge tag 'for-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel: "Power-supply core: - documentation fixes power-supply drivers: - add BD71828 charger driver - add Richtek RT9756 driver - max77705: add adaptive input current support - max77705: add support for multiple devices - misc small fixes reset drivers: - add spacemit-p1 poweroff/reboot driver" * tag 'for-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: Revert "power: supply: qcom_battmgr: support disabling charge control" Documentation: power: rt9756: Document exported sysfs entries power: supply: rt9756: Add Richtek RT9756 smart cap divider charger dt-bindings: power: supply: Add Richtek RT9756 smart cap divider charger driver: reset: spacemit-p1: add driver for poweroff/reboot power: supply: apm_power: only unset own apm_get_power_status power: supply: use ktime_divns() to avoid 64-bit division docs: power: clean up power_supply_class.rst power: supply: qcom_battmgr: support disabling charge control power: supply: qcom_battmgr: clamp charge control thresholds power: supply: wm831x: Check wm831x_set_bits() return value power: supply: rt9467: Prevent using uninitialized local variable in rt9467_set_value_from_ranges() power: supply: rt9467: Return error on failure in rt9467_set_value_from_ranges() power: supply: max17040: Check iio_read_channel_processed() return code power: supply: cw2015: Check devm_delayed_work_autocancel() return code power: supply: rt5033_charger: Fix device node reference leaks power: supply: max77705: Fix potential IRQ chip conflict when probing two devices power: supply: max77705_charger: implement aicl feature MAINTAINERS: Add entry for BD71828 charger power: supply: Add bd718(15/28/78) charger driver
This commit is contained in:
30
Documentation/ABI/testing/sysfs-class-power-rt9756
Normal file
30
Documentation/ABI/testing/sysfs-class-power-rt9756
Normal file
@@ -0,0 +1,30 @@
|
||||
What: /sys/class/power_supply/rt9756-*/watchdog_timer
|
||||
Date: Dec 2025
|
||||
KernelVersion: 6.19
|
||||
Contact: ChiYuan Huang <cy_huang@richtek.com>
|
||||
Description:
|
||||
This entry shows and sets the watchdog timer when rt9756 charger
|
||||
operates in charging mode. When the timer expires, the device
|
||||
will disable the charging. To prevent the timer expires, any
|
||||
host communication can make the timer restarted.
|
||||
|
||||
Access: Read, Write
|
||||
|
||||
Valid values:
|
||||
- 500, 1000, 5000, 30000, 40000, 80000, 128000 or 255000 (milliseconds),
|
||||
- 0: disabled
|
||||
|
||||
What: /sys/class/power_supply/rt9756-*/operation_mode
|
||||
Date: Dec 2025
|
||||
KernelVersion: 6.19
|
||||
Contact: ChiYuan Huang <cy_huang@richtek.com>
|
||||
Description:
|
||||
This entry shows and set the operation mode when rt9756 charger
|
||||
operates in charging phase. If 'bypass' mode is used, internal
|
||||
path will connect vbus directly to vbat. Else, default 'div2'
|
||||
mode for the switch-cap charging.
|
||||
|
||||
Access: Read, Write
|
||||
|
||||
Valid values:
|
||||
- 'bypass' or 'div2'
|
||||
@@ -0,0 +1,72 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/power/supply/richtek,rt9756.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Richtek RT9756 Smart Cap Divider Charger
|
||||
|
||||
maintainers:
|
||||
- ChiYuan Huang <cy_huang@richtek.com>
|
||||
|
||||
description: |
|
||||
The RT9756/RT9757 is a high efficiency and high charge current charger.
|
||||
|
||||
The efficiency is up to 98.2% when VBAT = 4V, IBAT = 2A in DIV2 mode and 99.1%
|
||||
when VBAT=4V, IBAT=1A in bypass mode. The maximum charger current is up to 8A
|
||||
in DIV2 mode and 5A in bypass mode. The device integrates smart cap divider
|
||||
topology, direct charging mode, external over-voltage protection control, an
|
||||
input reverse blocking NFET and 2-way regulation, a dual phase charge pump
|
||||
core, 8-Channel high speed ADCs and USB BC 1.2 detection.
|
||||
|
||||
RT9770 is almost the same with RT9756/57, only BC 1.2 detection function is
|
||||
removed to shrink the die size.
|
||||
|
||||
allOf:
|
||||
- $ref: power-supply.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- enum:
|
||||
- richtek,rt9756
|
||||
- richtek,rt9770
|
||||
- items:
|
||||
- enum:
|
||||
- richtek,rt9757
|
||||
- const: richtek,rt9756
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
wakeup-source: true
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
shunt-resistor-micro-ohms:
|
||||
description: Battery current sense resistor mounted.
|
||||
default: 2000
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
charger@6f {
|
||||
compatible = "richtek,rt9756";
|
||||
reg = <0x6f>;
|
||||
wakeup-source;
|
||||
interrupts-extended = <&gpio_intc 32 IRQ_TYPE_EDGE_FALLING>;
|
||||
shunt-resistor-micro-ohms = <5000>;
|
||||
};
|
||||
};
|
||||
@@ -7,35 +7,35 @@ Synopsis
|
||||
Power supply class used to represent battery, UPS, AC or DC power supply
|
||||
properties to user-space.
|
||||
|
||||
It defines core set of attributes, which should be applicable to (almost)
|
||||
It defines a core set of attributes which should be applicable to (almost)
|
||||
every power supply out there. Attributes are available via sysfs and uevent
|
||||
interfaces.
|
||||
|
||||
Each attribute has well defined meaning, up to unit of measure used. While
|
||||
Each attribute has a well-defined meaning, up to the unit of measure used. While
|
||||
the attributes provided are believed to be universally applicable to any
|
||||
power supply, specific monitoring hardware may not be able to provide them
|
||||
all, so any of them may be skipped.
|
||||
|
||||
Power supply class is extensible, and allows to define drivers own attributes.
|
||||
The core attribute set is subject to the standard Linux evolution (i.e.
|
||||
if it will be found that some attribute is applicable to many power supply
|
||||
types or their drivers, it can be added to the core set).
|
||||
The power supply class is extensible and allows drivers to define their own
|
||||
attributes. The core attribute set is subject to the standard Linux evolution
|
||||
(i.e., if some attribute is found to be applicable to many power
|
||||
supply types or their drivers, it can be added to the core set).
|
||||
|
||||
It also integrates with LED framework, for the purpose of providing
|
||||
It also integrates with the LED framework, for the purpose of providing
|
||||
typically expected feedback of battery charging/fully charged status and
|
||||
AC/USB power supply online status. (Note that specific details of the
|
||||
indication (including whether to use it at all) are fully controllable by
|
||||
user and/or specific machine defaults, per design principles of LED
|
||||
framework).
|
||||
user and/or specific machine defaults, per design principles of the LED
|
||||
framework.)
|
||||
|
||||
|
||||
Attributes/properties
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Power supply class has predefined set of attributes, this eliminates code
|
||||
duplication across drivers. Power supply class insist on reusing its
|
||||
The power supply class has a predefined set of attributes. This eliminates code
|
||||
duplication across drivers. The power supply class insists on reusing its
|
||||
predefined attributes *and* their units.
|
||||
|
||||
So, userspace gets predictable set of attributes and their units for any
|
||||
So, userspace gets a predictable set of attributes and their units for any
|
||||
kind of power supply, and can process/present them to a user in consistent
|
||||
manner. Results for different power supplies and machines are also directly
|
||||
comparable.
|
||||
@@ -61,7 +61,7 @@ Attributes/properties detailed
|
||||
| **Charge/Energy/Capacity - how to not confuse** |
|
||||
+--------------------------------------------------------------------------+
|
||||
| **Because both "charge" (µAh) and "energy" (µWh) represents "capacity" |
|
||||
| of battery, this class distinguish these terms. Don't mix them!** |
|
||||
| of battery, this class distinguishes these terms. Don't mix them!** |
|
||||
| |
|
||||
| - `CHARGE_*` |
|
||||
| attributes represents capacity in µAh only. |
|
||||
@@ -81,7 +81,7 @@ _NOW
|
||||
|
||||
STATUS
|
||||
this attribute represents operating status (charging, full,
|
||||
discharging (i.e. powering a load), etc.). This corresponds to
|
||||
discharging (i.e., powering a load), etc.). This corresponds to
|
||||
`BATTERY_STATUS_*` values, as defined in battery.h.
|
||||
|
||||
CHARGE_TYPE
|
||||
@@ -92,10 +92,10 @@ CHARGE_TYPE
|
||||
|
||||
AUTHENTIC
|
||||
indicates the power supply (battery or charger) connected
|
||||
to the platform is authentic(1) or non authentic(0).
|
||||
to the platform is authentic(1) or non-authentic(0).
|
||||
|
||||
HEALTH
|
||||
represents health of the battery, values corresponds to
|
||||
represents health of the battery. Values corresponds to
|
||||
POWER_SUPPLY_HEALTH_*, defined in battery.h.
|
||||
|
||||
VOLTAGE_OCV
|
||||
@@ -103,11 +103,11 @@ VOLTAGE_OCV
|
||||
|
||||
VOLTAGE_MAX_DESIGN, VOLTAGE_MIN_DESIGN
|
||||
design values for maximal and minimal power supply voltages.
|
||||
Maximal/minimal means values of voltages when battery considered
|
||||
Maximal/minimal means values of voltages when battery is considered
|
||||
"full"/"empty" at normal conditions. Yes, there is no direct relation
|
||||
between voltage and battery capacity, but some dumb
|
||||
batteries use voltage for very approximated calculation of capacity.
|
||||
Battery driver also can use this attribute just to inform userspace
|
||||
A battery driver also can use this attribute just to inform userspace
|
||||
about maximal and minimal voltage thresholds of a given battery.
|
||||
|
||||
VOLTAGE_MAX, VOLTAGE_MIN
|
||||
@@ -122,16 +122,16 @@ CURRENT_BOOT
|
||||
Reports the current measured during boot
|
||||
|
||||
CHARGE_FULL_DESIGN, CHARGE_EMPTY_DESIGN
|
||||
design charge values, when battery considered full/empty.
|
||||
design charge values, when battery is considered full/empty.
|
||||
|
||||
ENERGY_FULL_DESIGN, ENERGY_EMPTY_DESIGN
|
||||
same as above but for energy.
|
||||
|
||||
CHARGE_FULL, CHARGE_EMPTY
|
||||
These attributes means "last remembered value of charge when battery
|
||||
became full/empty". It also could mean "value of charge when battery
|
||||
These attributes mean "last remembered value of charge when battery
|
||||
became full/empty". They also could mean "value of charge when battery is
|
||||
considered full/empty at given conditions (temperature, age)".
|
||||
I.e. these attributes represents real thresholds, not design values.
|
||||
I.e., these attributes represents real thresholds, not design values.
|
||||
|
||||
ENERGY_FULL, ENERGY_EMPTY
|
||||
same as above but for energy.
|
||||
@@ -153,12 +153,12 @@ CHARGE_TERM_CURRENT
|
||||
CONSTANT_CHARGE_CURRENT
|
||||
constant charge current programmed by charger.
|
||||
|
||||
|
||||
CONSTANT_CHARGE_CURRENT_MAX
|
||||
maximum charge current supported by the power supply object.
|
||||
|
||||
CONSTANT_CHARGE_VOLTAGE
|
||||
constant charge voltage programmed by charger.
|
||||
|
||||
CONSTANT_CHARGE_VOLTAGE_MAX
|
||||
maximum charge voltage supported by the power supply object.
|
||||
|
||||
@@ -208,10 +208,10 @@ TEMP_MAX
|
||||
|
||||
TIME_TO_EMPTY
|
||||
seconds left for battery to be considered empty
|
||||
(i.e. while battery powers a load)
|
||||
(i.e., while battery powers a load)
|
||||
TIME_TO_FULL
|
||||
seconds left for battery to be considered full
|
||||
(i.e. while battery is charging)
|
||||
(i.e., while battery is charging)
|
||||
|
||||
|
||||
Battery <-> external power supply interaction
|
||||
@@ -220,13 +220,13 @@ Often power supplies are acting as supplies and supplicants at the same
|
||||
time. Batteries are good example. So, batteries usually care if they're
|
||||
externally powered or not.
|
||||
|
||||
For that case, power supply class implements notification mechanism for
|
||||
For that case, the power supply class implements a notification mechanism for
|
||||
batteries.
|
||||
|
||||
External power supply (AC) lists supplicants (batteries) names in
|
||||
An external power supply (AC) lists supplicants (batteries) names in
|
||||
"supplied_to" struct member, and each power_supply_changed() call
|
||||
issued by external power supply will notify supplicants via
|
||||
external_power_changed callback.
|
||||
issued by an external power supply will notify supplicants via
|
||||
the external_power_changed callback.
|
||||
|
||||
|
||||
Devicetree battery characteristics
|
||||
@@ -241,14 +241,14 @@ battery node have names corresponding to elements in enum power_supply_property,
|
||||
for naming consistency between sysfs attributes and battery node properties.
|
||||
|
||||
|
||||
QA
|
||||
~~
|
||||
Q&A
|
||||
~~~
|
||||
|
||||
Q:
|
||||
Where is POWER_SUPPLY_PROP_XYZ attribute?
|
||||
A:
|
||||
If you cannot find attribute suitable for your driver needs, feel free
|
||||
to add it and send patch along with your driver.
|
||||
If you cannot find an attribute suitable for your driver needs, feel free
|
||||
to add it and send a patch along with your driver.
|
||||
|
||||
The attributes available currently are the ones currently provided by the
|
||||
drivers written.
|
||||
@@ -258,18 +258,18 @@ A:
|
||||
|
||||
|
||||
Q:
|
||||
I have some very specific attribute (e.g. battery color), should I add
|
||||
I have some very specific attribute (e.g., battery color). Should I add
|
||||
this attribute to standard ones?
|
||||
A:
|
||||
Most likely, no. Such attribute can be placed in the driver itself, if
|
||||
it is useful. Of course, if the attribute in question applicable to
|
||||
large set of batteries, provided by many drivers, and/or comes from
|
||||
it is useful. Of course, if the attribute in question is applicable to
|
||||
a large set of batteries, provided by many drivers, and/or comes from
|
||||
some general battery specification/standard, it may be a candidate to
|
||||
be added to the core attribute set.
|
||||
|
||||
|
||||
Q:
|
||||
Suppose, my battery monitoring chip/firmware does not provides capacity
|
||||
Suppose my battery monitoring chip/firmware does not provide capacity
|
||||
in percents, but provides charge_{now,full,empty}. Should I calculate
|
||||
percentage capacity manually, inside the driver, and register CAPACITY
|
||||
attribute? The same question about time_to_empty/time_to_full.
|
||||
@@ -278,11 +278,11 @@ A:
|
||||
directly measurable by the specific hardware available.
|
||||
|
||||
Inferring not available properties using some heuristics or mathematical
|
||||
model is not subject of work for a battery driver. Such functionality
|
||||
model is not a subject of work for a battery driver. Such functionality
|
||||
should be factored out, and in fact, apm_power, the driver to serve
|
||||
legacy APM API on top of power supply class, uses a simple heuristic of
|
||||
legacy APM API on top of the power supply class, uses a simple heuristic of
|
||||
approximating remaining battery capacity based on its charge, current,
|
||||
voltage and so on. But full-fledged battery model is likely not subject
|
||||
for kernel at all, as it would require floating point calculation to deal
|
||||
with things like differential equations and Kalman filters. This is
|
||||
voltage and so on. But a full-fledged battery model is likely not a subject
|
||||
for the kernel at all, as it would require floating point calculations to
|
||||
deal with things like differential equations and Kalman filters. This is
|
||||
better be handled by batteryd/libbattery, yet to be written.
|
||||
|
||||
@@ -22479,6 +22479,12 @@ L: linux-serial@vger.kernel.org
|
||||
S: Odd Fixes
|
||||
F: drivers/tty/serial/rp2.*
|
||||
|
||||
ROHM BD71828 CHARGER
|
||||
M: Andreas Kemnade <andreas@kemnade.info>
|
||||
M: Matti Vaittinen <mazziesaccount@gmail.com>
|
||||
S: Maintained
|
||||
F: drivers/power/supply/bd71828-charger.c
|
||||
|
||||
ROHM BD79703 DAC
|
||||
M: Matti Vaittinen <mazziesaccount@gmail.com>
|
||||
S: Supported
|
||||
|
||||
@@ -283,6 +283,15 @@ config POWER_RESET_KEYSTONE
|
||||
help
|
||||
Reboot support for the KEYSTONE SoCs.
|
||||
|
||||
config POWER_RESET_SPACEMIT_P1
|
||||
tristate "SpacemiT P1 poweroff and reset driver"
|
||||
depends on ARCH_SPACEMIT || COMPILE_TEST
|
||||
depends on MFD_SPACEMIT_P1
|
||||
default MFD_SPACEMIT_P1
|
||||
help
|
||||
This driver supports power-off and reset operations for the SpacemiT
|
||||
P1 PMIC.
|
||||
|
||||
config POWER_RESET_SYSCON
|
||||
bool "Generic SYSCON regmap reset driver"
|
||||
depends on OF
|
||||
|
||||
@@ -24,6 +24,7 @@ obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
|
||||
obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
|
||||
obj-$(CONFIG_POWER_RESET_REGULATOR) += regulator-poweroff.o
|
||||
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
|
||||
obj-$(CONFIG_POWER_RESET_SPACEMIT_P1) += spacemit-p1-reboot.o
|
||||
obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
|
||||
obj-$(CONFIG_POWER_RESET_TH1520_AON) += th1520-aon-reboot.o
|
||||
obj-$(CONFIG_POWER_RESET_TORADEX_EC) += tdx-ec-poweroff.o
|
||||
|
||||
88
drivers/power/reset/spacemit-p1-reboot.c
Normal file
88
drivers/power/reset/spacemit-p1-reboot.c
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2025 by Aurelien Jarno
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reboot.h>
|
||||
|
||||
/* Power Control Register 2 */
|
||||
#define PWR_CTRL2 0x7e
|
||||
#define PWR_CTRL2_SHUTDOWN BIT(2) /* Shutdown request */
|
||||
#define PWR_CTRL2_RST BIT(1) /* Reset request */
|
||||
|
||||
static int spacemit_p1_pwroff_handler(struct sys_off_data *data)
|
||||
{
|
||||
struct regmap *regmap = data->cb_data;
|
||||
int ret;
|
||||
|
||||
/* Put the PMIC into shutdown state */
|
||||
ret = regmap_set_bits(regmap, PWR_CTRL2, PWR_CTRL2_SHUTDOWN);
|
||||
if (ret) {
|
||||
dev_err(data->dev, "shutdown failed: %d\n", ret);
|
||||
return notifier_from_errno(ret);
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int spacemit_p1_restart_handler(struct sys_off_data *data)
|
||||
{
|
||||
struct regmap *regmap = data->cb_data;
|
||||
int ret;
|
||||
|
||||
/* Put the PMIC into reset state */
|
||||
ret = regmap_set_bits(regmap, PWR_CTRL2, PWR_CTRL2_RST);
|
||||
if (ret) {
|
||||
dev_err(data->dev, "restart failed: %d\n", ret);
|
||||
return notifier_from_errno(ret);
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int spacemit_p1_reboot_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!regmap)
|
||||
return -ENODEV;
|
||||
|
||||
ret = devm_register_power_off_handler(dev, &spacemit_p1_pwroff_handler,
|
||||
regmap);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to register power off handler\n");
|
||||
|
||||
ret = devm_register_restart_handler(dev, spacemit_p1_restart_handler,
|
||||
regmap);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to register restart handler\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id spacemit_p1_reboot_id_table[] = {
|
||||
{ "spacemit-p1-reboot", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, spacemit_p1_reboot_id_table);
|
||||
|
||||
static struct platform_driver spacemit_p1_reboot_driver = {
|
||||
.driver = {
|
||||
.name = "spacemit-p1-reboot",
|
||||
},
|
||||
.probe = spacemit_p1_reboot_probe,
|
||||
.id_table = spacemit_p1_reboot_id_table,
|
||||
};
|
||||
module_platform_driver(spacemit_p1_reboot_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SpacemiT P1 reboot/poweroff driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -942,6 +942,21 @@ config CHARGER_RT9471
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called rt9471.
|
||||
|
||||
config CHARGER_RT9756
|
||||
tristate "Richtek RT9756 smart cap divider charger driver"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select LINEAR_RANGES
|
||||
help
|
||||
This adds support for Richtek RT9756 smart cap divider charger driver.
|
||||
It's a high efficiency and high charge current charger. the device
|
||||
integrates smart cap divider topology with 9-channel high speed
|
||||
ADCs that can provide input and output voltage, current and
|
||||
temperature monitoring.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called rt9756.
|
||||
|
||||
config CHARGER_CROS_USBPD
|
||||
tristate "ChromeOS EC based USBPD charger"
|
||||
depends on CROS_USBPD_NOTIFY
|
||||
@@ -1007,6 +1022,15 @@ config CHARGER_UCS1002
|
||||
Say Y to enable support for Microchip UCS1002 Programmable
|
||||
USB Port Power Controller with Charger Emulation.
|
||||
|
||||
config CHARGER_BD71828
|
||||
tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC"
|
||||
depends on MFD_ROHM_BD71828
|
||||
help
|
||||
Say Y here to enable support for charger and battery
|
||||
in ROHM BD71815, BD71817, ROHM BD71828 power management
|
||||
ICs. This driver gets various bits of information about battery
|
||||
and charger states.
|
||||
|
||||
config CHARGER_BD99954
|
||||
tristate "ROHM bd99954 charger driver"
|
||||
depends on I2C
|
||||
|
||||
@@ -64,6 +64,7 @@ obj-$(CONFIG_CHARGER_RT5033) += rt5033_charger.o
|
||||
obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o
|
||||
obj-$(CONFIG_CHARGER_RT9467) += rt9467-charger.o
|
||||
obj-$(CONFIG_CHARGER_RT9471) += rt9471.o
|
||||
obj-$(CONFIG_CHARGER_RT9756) += rt9756.o
|
||||
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
|
||||
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
|
||||
obj-$(CONFIG_CHARGER_PF1550) += pf1550-charger.o
|
||||
@@ -117,6 +118,7 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
|
||||
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
|
||||
obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o
|
||||
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
|
||||
obj-$(CONFIG_CHARGER_BD71828) += bd71828-power.o
|
||||
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
|
||||
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
|
||||
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
|
||||
|
||||
@@ -364,7 +364,8 @@ static int __init apm_battery_init(void)
|
||||
|
||||
static void __exit apm_battery_exit(void)
|
||||
{
|
||||
apm_get_power_status = NULL;
|
||||
if (apm_get_power_status == apm_battery_apm_get_power_status)
|
||||
apm_get_power_status = NULL;
|
||||
}
|
||||
|
||||
module_init(apm_battery_init);
|
||||
|
||||
1049
drivers/power/supply/bd71828-power.c
Normal file
1049
drivers/power/supply/bd71828-power.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -699,7 +699,13 @@ static int cw_bat_probe(struct i2c_client *client)
|
||||
if (!cw_bat->battery_workqueue)
|
||||
return -ENOMEM;
|
||||
|
||||
devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work);
|
||||
ret = devm_delayed_work_autocancel(&client->dev, &cw_bat->battery_delay_work, cw_bat_work);
|
||||
if (ret) {
|
||||
dev_err_probe(&client->dev, ret,
|
||||
"Failed to register delayed work\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
queue_delayed_work(cw_bat->battery_workqueue,
|
||||
&cw_bat->battery_delay_work, msecs_to_jiffies(10));
|
||||
return 0;
|
||||
|
||||
@@ -388,6 +388,7 @@ static int max17040_get_property(struct power_supply *psy,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct max17040_chip *chip = power_supply_get_drvdata(psy);
|
||||
int ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
@@ -410,7 +411,10 @@ static int max17040_get_property(struct power_supply *psy,
|
||||
if (!chip->channel_temp)
|
||||
return -ENODATA;
|
||||
|
||||
iio_read_channel_processed(chip->channel_temp, &val->intval);
|
||||
ret = iio_read_channel_processed(chip->channel_temp, &val->intval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval /= 100; /* Convert from milli- to deci-degree */
|
||||
|
||||
break;
|
||||
|
||||
@@ -40,6 +40,39 @@ static enum power_supply_property max77705_charger_props[] = {
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
||||
};
|
||||
|
||||
static irqreturn_t max77705_aicl_irq(int irq, void *irq_drv_data)
|
||||
{
|
||||
struct max77705_charger_data *chg = irq_drv_data;
|
||||
unsigned int regval, irq_status;
|
||||
int err;
|
||||
|
||||
err = regmap_read(chg->regmap, MAX77705_CHG_REG_INT_OK, &irq_status);
|
||||
if (err < 0)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
// irq is fiered at the end of current decrease sequence too
|
||||
// early check AICL_I bit to guard against that excess irq call
|
||||
while (!(irq_status & BIT(MAX77705_AICL_I))) {
|
||||
err = regmap_field_read(chg->rfield[MAX77705_CHG_CHGIN_LIM], ®val);
|
||||
if (err < 0)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
regval--;
|
||||
|
||||
err = regmap_field_write(chg->rfield[MAX77705_CHG_CHGIN_LIM], regval);
|
||||
if (err < 0)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
msleep(AICL_WORK_DELAY_MS);
|
||||
|
||||
err = regmap_read(chg->regmap, MAX77705_CHG_REG_INT_OK, &irq_status);
|
||||
if (err < 0)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t max77705_chgin_irq(int irq, void *irq_drv_data)
|
||||
{
|
||||
struct max77705_charger_data *chg = irq_drv_data;
|
||||
@@ -60,7 +93,7 @@ static const struct regmap_irq max77705_charger_irqs[] = {
|
||||
REGMAP_IRQ_REG_LINE(MAX77705_AICL_I, BITS_PER_BYTE),
|
||||
};
|
||||
|
||||
static struct regmap_irq_chip max77705_charger_irq_chip = {
|
||||
static const struct regmap_irq_chip max77705_charger_irq_chip = {
|
||||
.name = "max77705-charger",
|
||||
.status_base = MAX77705_CHG_REG_INT,
|
||||
.mask_base = MAX77705_CHG_REG_INT_MASK,
|
||||
@@ -567,6 +600,7 @@ static int max77705_charger_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct power_supply_config pscfg = {};
|
||||
struct max77705_charger_data *chg;
|
||||
struct regmap_irq_chip *chip_desc;
|
||||
struct device *dev;
|
||||
struct regmap_irq_chip_data *irq_data;
|
||||
int ret;
|
||||
@@ -580,6 +614,13 @@ static int max77705_charger_probe(struct i2c_client *i2c)
|
||||
chg->dev = dev;
|
||||
i2c_set_clientdata(i2c, chg);
|
||||
|
||||
chip_desc = devm_kmemdup(dev, &max77705_charger_irq_chip,
|
||||
sizeof(max77705_charger_irq_chip),
|
||||
GFP_KERNEL);
|
||||
if (!chip_desc)
|
||||
return -ENOMEM;
|
||||
chip_desc->irq_drv_data = chg;
|
||||
|
||||
chg->regmap = devm_regmap_init_i2c(i2c, &max77705_chg_regmap_config);
|
||||
if (IS_ERR(chg->regmap))
|
||||
return PTR_ERR(chg->regmap);
|
||||
@@ -599,11 +640,9 @@ static int max77705_charger_probe(struct i2c_client *i2c)
|
||||
if (IS_ERR(chg->psy_chg))
|
||||
return PTR_ERR(chg->psy_chg);
|
||||
|
||||
max77705_charger_irq_chip.irq_drv_data = chg;
|
||||
ret = devm_regmap_add_irq_chip(chg->dev, chg->regmap, i2c->irq,
|
||||
IRQF_ONESHOT, 0,
|
||||
&max77705_charger_irq_chip,
|
||||
&irq_data);
|
||||
chip_desc, &irq_data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to add irq chip\n");
|
||||
|
||||
@@ -632,6 +671,15 @@ static int max77705_charger_probe(struct i2c_client *i2c)
|
||||
goto destroy_wq;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ret = max77705_charger_enable(chg);
|
||||
if (ret) {
|
||||
dev_err_probe(dev, ret, "failed to enable charge\n");
|
||||
|
||||
@@ -678,12 +678,7 @@ static int qcom_battmgr_set_charge_start_threshold(struct qcom_battmgr *battmgr,
|
||||
u32 target_soc, delta_soc;
|
||||
int ret;
|
||||
|
||||
if (start_soc < CHARGE_CTRL_START_THR_MIN ||
|
||||
start_soc > CHARGE_CTRL_START_THR_MAX) {
|
||||
dev_err(battmgr->dev, "charge control start threshold exceed range: [%u - %u]\n",
|
||||
CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX);
|
||||
return -EINVAL;
|
||||
}
|
||||
start_soc = clamp(start_soc, CHARGE_CTRL_START_THR_MIN, CHARGE_CTRL_START_THR_MAX);
|
||||
|
||||
/*
|
||||
* If the new start threshold is larger than the old end threshold,
|
||||
@@ -716,12 +711,7 @@ static int qcom_battmgr_set_charge_end_threshold(struct qcom_battmgr *battmgr, i
|
||||
u32 delta_soc = CHARGE_CTRL_DELTA_SOC;
|
||||
int ret;
|
||||
|
||||
if (end_soc < CHARGE_CTRL_END_THR_MIN ||
|
||||
end_soc > CHARGE_CTRL_END_THR_MAX) {
|
||||
dev_err(battmgr->dev, "charge control end threshold exceed range: [%u - %u]\n",
|
||||
CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX);
|
||||
return -EINVAL;
|
||||
}
|
||||
end_soc = clamp(end_soc, CHARGE_CTRL_END_THR_MIN, CHARGE_CTRL_END_THR_MAX);
|
||||
|
||||
if (battmgr->info.charge_ctrl_start && end_soc > battmgr->info.charge_ctrl_start)
|
||||
delta_soc = end_soc - battmgr->info.charge_ctrl_start;
|
||||
|
||||
@@ -701,6 +701,8 @@ static int rt5033_charger_probe(struct platform_device *pdev)
|
||||
np_conn = of_parse_phandle(pdev->dev.of_node, "richtek,usb-connector", 0);
|
||||
np_edev = of_get_parent(np_conn);
|
||||
charger->edev = extcon_find_edev_by_node(np_edev);
|
||||
of_node_put(np_edev);
|
||||
of_node_put(np_conn);
|
||||
if (IS_ERR(charger->edev)) {
|
||||
dev_warn(charger->dev, "no extcon device found in device-tree\n");
|
||||
goto out;
|
||||
|
||||
@@ -376,7 +376,7 @@ static int rt9467_set_value_from_ranges(struct rt9467_chg_data *data,
|
||||
if (rsel == RT9467_RANGE_VMIVR) {
|
||||
ret = linear_range_get_selector_high(range, value, &sel, &found);
|
||||
if (ret)
|
||||
value = range->max_sel;
|
||||
sel = range->max_sel;
|
||||
} else {
|
||||
linear_range_get_selector_within(range, value, &sel);
|
||||
}
|
||||
@@ -588,6 +588,10 @@ static int rt9467_run_aicl(struct rt9467_chg_data *data)
|
||||
aicl_vth = mivr_vth + RT9467_AICLVTH_GAP_uV;
|
||||
ret = rt9467_set_value_from_ranges(data, F_AICL_VTH,
|
||||
RT9467_RANGE_AICL_VTH, aicl_vth);
|
||||
if (ret) {
|
||||
dev_err(data->dev, "Failed to set AICL VTH\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Trigger AICL function */
|
||||
ret = regmap_field_write(data->rm_field[F_AICL_MEAS], 1);
|
||||
|
||||
955
drivers/power/supply/rt9756.c
Normal file
955
drivers/power/supply/rt9756.c
Normal file
@@ -0,0 +1,955 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
//
|
||||
// Copyright (C) 2025 Richtek Technology Corp.
|
||||
//
|
||||
// Authors: ChiYuan Huang <cy_huang@richtek.com>
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/linear_range.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
#define RT9756_REG_INTFLAG1 0x0B
|
||||
#define RT9756_REG_INTFLAG2 0x0D
|
||||
#define RT9756_REG_INTFLAG3 0x0F
|
||||
#define RT9756_REG_ADCCTL 0x11
|
||||
#define RT9756_REG_VBUSADC 0x12
|
||||
#define RT9756_REG_BC12FLAG 0x45
|
||||
#define RT9756_REG_INTFLAG4 0x49
|
||||
|
||||
/* Flag1 */
|
||||
#define RT9756_EVT_BUSOVP BIT(3)
|
||||
#define RT9756_EVT_BUSOCP BIT(2)
|
||||
#define RT9756_EVT_BUSUCP BIT(0)
|
||||
/* Flag2 */
|
||||
#define RT9756_EVT_BATOVP BIT(7)
|
||||
#define RT9756_EVT_BATOCP BIT(6)
|
||||
#define RT9756_EVT_TDIEOTP BIT(3)
|
||||
#define RT9756_EVT_VBUSLOW_ERR BIT(2)
|
||||
#define RT9756_EVT_VAC_INSERT BIT(0)
|
||||
/* Flag3 */
|
||||
#define RT9756_EVT_WDT BIT(5)
|
||||
#define RT9756_EVT_VAC_UVLO BIT(4)
|
||||
/* ADCCTL */
|
||||
#define RT9756_ADCEN_MASK BIT(7)
|
||||
#define RT9756_ADCONCE_MASK BIT(6)
|
||||
/* Bc12_flag */
|
||||
#define RT9756_EVT_BC12_DONE BIT(3)
|
||||
/* Flag4 */
|
||||
#define RT9756_EVT_OUTOVP BIT(0)
|
||||
|
||||
#define RICHTEK_DEVID 7
|
||||
#define RT9756_REVID 0
|
||||
#define RT9756A_REVID 1
|
||||
#define RT9757_REVID 2
|
||||
#define RT9757A_REVID 3
|
||||
#define RT9756_ADC_CONVTIME 1200
|
||||
#define RT9756_ADC_MAXWAIT 16000
|
||||
|
||||
enum rt9756_model {
|
||||
MODEL_RT9756 = 0,
|
||||
MODEL_RT9757,
|
||||
MODEL_RT9770,
|
||||
MODEL_MAX
|
||||
};
|
||||
|
||||
enum rt9756_adc_chan {
|
||||
ADC_VBUS = 0,
|
||||
ADC_IBUS,
|
||||
ADC_VBAT,
|
||||
ADC_IBAT,
|
||||
ADC_TDIE,
|
||||
ADC_MAX_CHANNEL
|
||||
};
|
||||
|
||||
enum rt9756_usb_type {
|
||||
USB_NO_VBUS = 0,
|
||||
USB_SDP = 2,
|
||||
USB_NSTD,
|
||||
USB_DCP,
|
||||
USB_CDP,
|
||||
MAX_USB_TYPE
|
||||
};
|
||||
|
||||
enum rt9756_fields {
|
||||
F_VBATOVP = 0,
|
||||
F_VBATOVP_EN,
|
||||
F_IBATOCP,
|
||||
F_IBATOCP_EN,
|
||||
F_VBUSOVP,
|
||||
F_VBUSOVP_EN,
|
||||
F_IBUSOCP,
|
||||
F_IBUSOCP_EN,
|
||||
F_SWITCHING,
|
||||
F_REG_RST,
|
||||
F_CHG_EN,
|
||||
F_OP_MODE,
|
||||
F_WDT_DIS,
|
||||
F_WDT_TMR,
|
||||
F_DEV_ID,
|
||||
F_BC12_EN,
|
||||
F_USB_STATE,
|
||||
F_VBUS_STATE,
|
||||
F_IBAT_RSEN,
|
||||
F_REVISION,
|
||||
F_MAX_FIELD
|
||||
};
|
||||
|
||||
enum rt9756_ranges {
|
||||
R_VBATOVP = 0,
|
||||
R_IBATOCP,
|
||||
R_VBUSOVP,
|
||||
R_IBUSOCP,
|
||||
R_MAX_RANGE
|
||||
};
|
||||
|
||||
static const struct reg_field rt9756_chg_fields[F_MAX_FIELD] = {
|
||||
[F_VBATOVP] = REG_FIELD(0x08, 0, 4),
|
||||
[F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7),
|
||||
[F_IBATOCP] = REG_FIELD(0x09, 0, 5),
|
||||
[F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7),
|
||||
[F_VBUSOVP] = REG_FIELD(0x06, 0, 5),
|
||||
[F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7),
|
||||
[F_IBUSOCP] = REG_FIELD(0x07, 0, 4),
|
||||
[F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5),
|
||||
[F_SWITCHING] = REG_FIELD(0x5c, 7, 7),
|
||||
[F_REG_RST] = REG_FIELD(0x00, 7, 7),
|
||||
[F_CHG_EN] = REG_FIELD(0x00, 6, 6),
|
||||
[F_OP_MODE] = REG_FIELD(0x00, 5, 5),
|
||||
[F_WDT_DIS] = REG_FIELD(0x00, 3, 3),
|
||||
[F_WDT_TMR] = REG_FIELD(0x00, 0, 2),
|
||||
[F_DEV_ID] = REG_FIELD(0x03, 0, 3),
|
||||
[F_BC12_EN] = REG_FIELD(0x44, 7, 7),
|
||||
[F_USB_STATE] = REG_FIELD(0x46, 5, 7),
|
||||
[F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0),
|
||||
[F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1),
|
||||
[F_REVISION] = REG_FIELD(0x62, 0, 1),
|
||||
};
|
||||
|
||||
static const struct reg_field rt9770_chg_fields[F_MAX_FIELD] = {
|
||||
[F_VBATOVP] = REG_FIELD(0x08, 0, 4),
|
||||
[F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7),
|
||||
[F_IBATOCP] = REG_FIELD(0x09, 0, 5),
|
||||
[F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7),
|
||||
[F_VBUSOVP] = REG_FIELD(0x06, 0, 5),
|
||||
[F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7),
|
||||
[F_IBUSOCP] = REG_FIELD(0x07, 0, 4),
|
||||
[F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5),
|
||||
[F_SWITCHING] = REG_FIELD(0x5c, 7, 7),
|
||||
[F_REG_RST] = REG_FIELD(0x00, 7, 7),
|
||||
[F_CHG_EN] = REG_FIELD(0x00, 6, 6),
|
||||
[F_OP_MODE] = REG_FIELD(0x00, 5, 5),
|
||||
[F_WDT_DIS] = REG_FIELD(0x00, 3, 3),
|
||||
[F_WDT_TMR] = REG_FIELD(0x00, 0, 2),
|
||||
[F_DEV_ID] = REG_FIELD(0x60, 0, 3),
|
||||
[F_BC12_EN] = REG_FIELD(0x03, 7, 7),
|
||||
[F_USB_STATE] = REG_FIELD(0x02, 5, 7),
|
||||
[F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0),
|
||||
[F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1),
|
||||
[F_REVISION] = REG_FIELD(0x62, 3, 7),
|
||||
};
|
||||
|
||||
/* All converted to microvolt or microamp */
|
||||
static const struct linear_range rt9756_chg_ranges[R_MAX_RANGE] = {
|
||||
LINEAR_RANGE_IDX(R_VBATOVP, 4200000, 0, 31, 25000),
|
||||
LINEAR_RANGE_IDX(R_IBATOCP, 2000000, 0, 63, 100000),
|
||||
LINEAR_RANGE_IDX(R_VBUSOVP, 3000000, 0, 63, 50000),
|
||||
LINEAR_RANGE_IDX(R_IBUSOCP, 1000000, 0, 31, 250000),
|
||||
};
|
||||
|
||||
struct charger_event {
|
||||
unsigned int flag1;
|
||||
unsigned int flag2;
|
||||
unsigned int flag3;
|
||||
unsigned int flag4;
|
||||
};
|
||||
|
||||
struct rt9756_data {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct regmap_field *rm_fields[F_MAX_FIELD];
|
||||
struct power_supply *psy;
|
||||
struct power_supply *bat_psy;
|
||||
struct mutex adc_lock;
|
||||
struct power_supply_desc psy_desc;
|
||||
struct power_supply_desc bat_psy_desc;
|
||||
struct charger_event chg_evt;
|
||||
unsigned int rg_resistor;
|
||||
unsigned int real_resistor;
|
||||
enum rt9756_model model;
|
||||
atomic_t usb_type;
|
||||
};
|
||||
|
||||
struct rt975x_dev_data {
|
||||
const struct regmap_config *regmap_config;
|
||||
const struct reg_field *reg_fields;
|
||||
const struct reg_sequence *init_regs;
|
||||
size_t num_init_regs;
|
||||
int (*check_device_model)(struct rt9756_data *data);
|
||||
};
|
||||
|
||||
static int rt9756_get_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field,
|
||||
enum rt9756_fields field, enum rt9756_ranges rsel, int *val)
|
||||
{
|
||||
const struct linear_range *range = rt9756_chg_ranges + rsel;
|
||||
unsigned int enable, selector, value;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[en_field], &enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!enable) {
|
||||
*val = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[field], &selector);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = linear_range_get_value(range, selector, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = (int)value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rt9756_set_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field,
|
||||
enum rt9756_fields field, enum rt9756_ranges rsel, int val)
|
||||
{
|
||||
const struct linear_range *range = rt9756_chg_ranges + rsel;
|
||||
unsigned int selector, value;
|
||||
int ret;
|
||||
|
||||
if (!val)
|
||||
return regmap_field_write(data->rm_fields[en_field], 0);
|
||||
|
||||
value = (unsigned int)val;
|
||||
linear_range_get_selector_within(range, value, &selector);
|
||||
ret = regmap_field_write(data->rm_fields[field], selector);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_field_write(data->rm_fields[en_field], 1);
|
||||
}
|
||||
|
||||
static int rt9756_get_adc(struct rt9756_data *data, enum rt9756_adc_chan chan,
|
||||
int *val)
|
||||
{
|
||||
struct regmap *regmap = data->regmap;
|
||||
unsigned int reg_addr = RT9756_REG_VBUSADC + chan * 2;
|
||||
unsigned int mask = RT9756_ADCEN_MASK | RT9756_ADCONCE_MASK;
|
||||
unsigned int shift = 0, adc_cntl;
|
||||
__be16 raws;
|
||||
int scale, offset = 0, ret;
|
||||
|
||||
guard(mutex)(&data->adc_lock);
|
||||
|
||||
ret = regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(regmap, RT9756_REG_ADCCTL, adc_cntl,
|
||||
!(adc_cntl & RT9756_ADCEN_MASK),
|
||||
RT9756_ADC_CONVTIME, RT9756_ADC_MAXWAIT);
|
||||
if (ret && ret != -ETIMEDOUT)
|
||||
return ret;
|
||||
|
||||
ret = regmap_raw_read(regmap, reg_addr, &raws, sizeof(raws));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* TDIE LSB 1'c, others LSB 1000uV or 1000uA.
|
||||
* Rsense ratio is needed for IBAT channel
|
||||
*/
|
||||
if (chan == ADC_TDIE) {
|
||||
scale = 10;
|
||||
shift = 8;
|
||||
offset = -40;
|
||||
} else if (chan == ADC_IBAT)
|
||||
scale = 1000 * data->rg_resistor / data->real_resistor;
|
||||
else
|
||||
scale = 1000;
|
||||
|
||||
*val = ((be16_to_cpu(raws) >> shift) + offset) * scale;
|
||||
|
||||
return regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, 0);
|
||||
}
|
||||
|
||||
static int rt9756_get_switching_state(struct rt9756_data *data, int *status)
|
||||
{
|
||||
unsigned int switching_state;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[F_SWITCHING], &switching_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (switching_state)
|
||||
*status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else
|
||||
*status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rt9756_get_charger_health(struct rt9756_data *data)
|
||||
{
|
||||
struct charger_event *evt = &data->chg_evt;
|
||||
|
||||
if (evt->flag2 & RT9756_EVT_VBUSLOW_ERR)
|
||||
return POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
|
||||
|
||||
if (evt->flag1 & RT9756_EVT_BUSOVP || evt->flag2 & RT9756_EVT_BATOVP ||
|
||||
evt->flag4 & RT9756_EVT_OUTOVP)
|
||||
return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
|
||||
if (evt->flag1 & RT9756_EVT_BUSOCP || evt->flag2 & RT9756_EVT_BATOCP)
|
||||
return POWER_SUPPLY_HEALTH_OVERCURRENT;
|
||||
|
||||
if (evt->flag1 & RT9756_EVT_BUSUCP)
|
||||
return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
|
||||
if (evt->flag2 & RT9756_EVT_TDIEOTP)
|
||||
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
|
||||
if (evt->flag3 & RT9756_EVT_WDT)
|
||||
return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE;
|
||||
|
||||
return POWER_SUPPLY_HEALTH_GOOD;
|
||||
}
|
||||
|
||||
static int rt9756_get_charger_online(struct rt9756_data *data, int *val)
|
||||
{
|
||||
unsigned int online;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[F_VBUS_STATE], &online);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = !!online;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rt9756_get_vbus_ovp(struct rt9756_data *data, int *val)
|
||||
{
|
||||
unsigned int opmode;
|
||||
int ovpval, ret;
|
||||
|
||||
/* operating mode -> 0 bypass, 1 div2 */
|
||||
ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = rt9756_get_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP, &ovpval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = opmode ? ovpval * 2 : ovpval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rt9756_set_vbus_ovp(struct rt9756_data *data, int val)
|
||||
{
|
||||
unsigned int opmode;
|
||||
int ret;
|
||||
|
||||
/* operating mode -> 0 bypass, 1 div2 */
|
||||
ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return rt9756_set_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP,
|
||||
opmode ? val / 2 : val);
|
||||
}
|
||||
|
||||
static const char * const rt9756_manufacturer = "Richtek Technology Corp.";
|
||||
static const char * const rt9756_model[MODEL_MAX] = { "RT9756", "RT9757", "RT9770" };
|
||||
|
||||
static int rt9756_psy_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
int *pval = &val->intval;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
return rt9756_get_switching_state(data, pval);
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
*pval = rt9756_get_charger_health(data);
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
return rt9756_get_charger_online(data, pval);
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
||||
return rt9756_get_vbus_ovp(data, pval);
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
return rt9756_get_adc(data, ADC_VBUS, pval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
return rt9756_get_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP, pval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
return rt9756_get_adc(data, ADC_IBUS, pval);
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
return rt9756_get_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP, pval);
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
return rt9756_get_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP, pval);
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
return rt9756_get_adc(data, ADC_TDIE, pval);
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
*pval = atomic_read(&data->usb_type);
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||
val->strval = rt9756_model[data->model];
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = rt9756_manufacturer;
|
||||
return 0;
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
static int rt9756_psy_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
int intval = val->intval;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
memset(&data->chg_evt, 0, sizeof(data->chg_evt));
|
||||
return regmap_field_write(data->rm_fields[F_CHG_EN], !!intval);
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
||||
return rt9756_set_vbus_ovp(data, intval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
return rt9756_set_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP,
|
||||
intval);
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
return rt9756_set_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP,
|
||||
intval);
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
return rt9756_set_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP,
|
||||
intval);
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
return regmap_field_write(data->rm_fields[F_BC12_EN], !!intval);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const enum power_supply_property rt9756_psy_properties[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_TEMP,
|
||||
POWER_SUPPLY_PROP_USB_TYPE,
|
||||
POWER_SUPPLY_PROP_MODEL_NAME,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static int rt9756_bat_psy_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
int *pval = &val->intval;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
||||
*pval = POWER_SUPPLY_TECHNOLOGY_LION;
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
return rt9756_get_adc(data, ADC_VBAT, pval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
return rt9756_get_adc(data, ADC_IBAT, pval);
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
}
|
||||
|
||||
static const enum power_supply_property rt9756_bat_psy_properties[] = {
|
||||
POWER_SUPPLY_PROP_TECHNOLOGY,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
};
|
||||
|
||||
static int rt9756_psy_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const unsigned int rt9756_wdt_millisecond[] = {
|
||||
500, 1000, 5000, 30000, 40000, 80000, 128000, 255000
|
||||
};
|
||||
|
||||
static ssize_t watchdog_timer_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
unsigned int wdt_tmr_now = 0, wdt_sel, wdt_dis;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[F_WDT_DIS], &wdt_dis);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!wdt_dis) {
|
||||
ret = regmap_field_read(data->rm_fields[F_WDT_TMR], &wdt_sel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
wdt_tmr_now = rt9756_wdt_millisecond[wdt_sel];
|
||||
}
|
||||
|
||||
return sysfs_emit(buf, "%d\n", wdt_tmr_now);
|
||||
}
|
||||
|
||||
static ssize_t watchdog_timer_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
unsigned int wdt_set, wdt_sel;
|
||||
int ret;
|
||||
|
||||
ret = kstrtouint(buf, 10, &wdt_set);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
wdt_sel = find_closest(wdt_set, rt9756_wdt_millisecond,
|
||||
ARRAY_SIZE(rt9756_wdt_millisecond));
|
||||
|
||||
ret = regmap_field_write(data->rm_fields[F_WDT_TMR], wdt_sel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (wdt_set) {
|
||||
ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const char * const rt9756_opmode_str[] = { "bypass", "div2" };
|
||||
|
||||
static ssize_t operation_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
unsigned int opmode;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%s\n", rt9756_opmode_str[opmode]);
|
||||
}
|
||||
|
||||
static ssize_t operation_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct power_supply *psy = to_power_supply(dev);
|
||||
struct rt9756_data *data = power_supply_get_drvdata(psy);
|
||||
int index, ret;
|
||||
|
||||
index = sysfs_match_string(rt9756_opmode_str, buf);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
ret = regmap_field_write(data->rm_fields[F_OP_MODE], index);
|
||||
|
||||
return ret ?: count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(watchdog_timer);
|
||||
static DEVICE_ATTR_RW(operation_mode);
|
||||
|
||||
static struct attribute *rt9756_sysfs_attrs[] = {
|
||||
&dev_attr_watchdog_timer.attr,
|
||||
&dev_attr_operation_mode.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(rt9756_sysfs);
|
||||
|
||||
static int rt9756_register_psy(struct rt9756_data *data)
|
||||
{
|
||||
struct power_supply_desc *desc = &data->psy_desc;
|
||||
struct power_supply_desc *bat_desc = &data->bat_psy_desc;
|
||||
struct power_supply_config cfg = {}, bat_cfg = {};
|
||||
struct device *dev = data->dev;
|
||||
char *psy_name, *bat_psy_name, **supplied_to;
|
||||
|
||||
bat_cfg.drv_data = data;
|
||||
bat_cfg.fwnode = dev_fwnode(dev);
|
||||
|
||||
bat_psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s-battery", dev_name(dev));
|
||||
if (!bat_psy_name)
|
||||
return -ENOMEM;
|
||||
|
||||
bat_desc->name = bat_psy_name;
|
||||
bat_desc->type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
bat_desc->properties = rt9756_bat_psy_properties;
|
||||
bat_desc->num_properties = ARRAY_SIZE(rt9756_bat_psy_properties);
|
||||
bat_desc->get_property = rt9756_bat_psy_get_property;
|
||||
|
||||
data->bat_psy = devm_power_supply_register(dev, bat_desc, &bat_cfg);
|
||||
if (IS_ERR(data->bat_psy))
|
||||
return dev_err_probe(dev, PTR_ERR(data->bat_psy), "Failed to register battery\n");
|
||||
|
||||
supplied_to = devm_kzalloc(dev, sizeof(*supplied_to), GFP_KERNEL);
|
||||
if (!supplied_to)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Link charger psy to battery psy */
|
||||
supplied_to[0] = bat_psy_name;
|
||||
|
||||
cfg.drv_data = data;
|
||||
cfg.fwnode = dev_fwnode(dev);
|
||||
cfg.attr_grp = rt9756_sysfs_groups;
|
||||
cfg.supplied_to = supplied_to;
|
||||
cfg.num_supplicants = 1;
|
||||
|
||||
psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s", dev_name(dev));
|
||||
if (!psy_name)
|
||||
return -ENOMEM;
|
||||
|
||||
desc->name = psy_name;
|
||||
desc->type = POWER_SUPPLY_TYPE_USB;
|
||||
desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) |
|
||||
BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_CDP);
|
||||
desc->properties = rt9756_psy_properties;
|
||||
desc->num_properties = ARRAY_SIZE(rt9756_psy_properties);
|
||||
desc->property_is_writeable = rt9756_psy_property_is_writeable;
|
||||
desc->get_property = rt9756_psy_get_property;
|
||||
desc->set_property = rt9756_psy_set_property;
|
||||
|
||||
data->psy = devm_power_supply_register(dev, desc, &cfg);
|
||||
|
||||
return PTR_ERR_OR_ZERO(data->psy);
|
||||
}
|
||||
|
||||
static int rt9756_get_usb_type(struct rt9756_data *data)
|
||||
{
|
||||
unsigned int type;
|
||||
int report_type, ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[F_USB_STATE], &type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (type) {
|
||||
case USB_SDP:
|
||||
case USB_NSTD:
|
||||
report_type = POWER_SUPPLY_USB_TYPE_SDP;
|
||||
break;
|
||||
case USB_DCP:
|
||||
report_type = POWER_SUPPLY_USB_TYPE_DCP;
|
||||
break;
|
||||
case USB_CDP:
|
||||
report_type = POWER_SUPPLY_USB_TYPE_CDP;
|
||||
break;
|
||||
case USB_NO_VBUS:
|
||||
default:
|
||||
report_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
atomic_set(&data->usb_type, report_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t rt9756_irq_handler(int irq, void *devid)
|
||||
{
|
||||
struct rt9756_data *data = devid;
|
||||
struct regmap *regmap = data->regmap;
|
||||
struct charger_event *evt = &data->chg_evt;
|
||||
unsigned int bc12_flag = 0;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, RT9756_REG_INTFLAG1, &evt->flag1);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
|
||||
ret = regmap_read(regmap, RT9756_REG_INTFLAG2, &evt->flag2);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
|
||||
ret = regmap_read(regmap, RT9756_REG_INTFLAG3, &evt->flag3);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (data->model != MODEL_RT9770) {
|
||||
ret = regmap_read(regmap, RT9756_REG_INTFLAG4, &evt->flag4);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
|
||||
ret = regmap_read(regmap, RT9756_REG_BC12FLAG, &bc12_flag);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
dev_dbg(data->dev, "events: 0x%02x,%02x,%02x,%02x,%02x\n", evt->flag1, evt->flag2,
|
||||
evt->flag3, evt->flag4, bc12_flag);
|
||||
|
||||
if (evt->flag2 & RT9756_EVT_VAC_INSERT) {
|
||||
ret = regmap_field_write(data->rm_fields[F_BC12_EN], 1);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (evt->flag3 & RT9756_EVT_VAC_UVLO)
|
||||
atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN);
|
||||
|
||||
if (bc12_flag & RT9756_EVT_BC12_DONE) {
|
||||
ret = rt9756_get_usb_type(data);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
power_supply_changed(data->psy);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int rt9756_config_batsense_resistor(struct rt9756_data *data)
|
||||
{
|
||||
unsigned int shunt_resistor_uohms = 2000, rsense_sel;
|
||||
|
||||
device_property_read_u32(data->dev, "shunt-resistor-micro-ohms", &shunt_resistor_uohms);
|
||||
|
||||
if (!shunt_resistor_uohms || shunt_resistor_uohms > 5000)
|
||||
return -EINVAL;
|
||||
|
||||
data->real_resistor = shunt_resistor_uohms;
|
||||
|
||||
/* Always choose the larger or equal one to prevent false ocp alarm */
|
||||
if (shunt_resistor_uohms <= 1000) {
|
||||
rsense_sel = 0;
|
||||
data->rg_resistor = 1000;
|
||||
} else if (shunt_resistor_uohms <= 2000) {
|
||||
rsense_sel = 1;
|
||||
data->rg_resistor = 2000;
|
||||
} else {
|
||||
rsense_sel = 2;
|
||||
data->rg_resistor = 5000;
|
||||
}
|
||||
|
||||
return regmap_field_write(data->rm_fields[F_IBAT_RSEN], rsense_sel);
|
||||
}
|
||||
|
||||
static const struct reg_sequence rt9756_init_regs[] = {
|
||||
REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */
|
||||
REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */
|
||||
REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */
|
||||
REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */
|
||||
REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */
|
||||
REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */
|
||||
REG_SEQ0(0x44, 0xa0), /* BC12_EN */
|
||||
REG_SEQ0(0x47, 0x07), /* MASK BC12FLAG */
|
||||
REG_SEQ0(0x4a, 0xfe), /* MASK FLAG4 */
|
||||
REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */
|
||||
REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */
|
||||
};
|
||||
|
||||
static const struct reg_sequence rt9770_init_regs[] = {
|
||||
REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */
|
||||
REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */
|
||||
REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */
|
||||
REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */
|
||||
REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */
|
||||
REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */
|
||||
REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */
|
||||
REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */
|
||||
};
|
||||
|
||||
static const struct regmap_config rt9756_regmap_config = {
|
||||
.name = "rt9756",
|
||||
.reg_bits = 16,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x1ff,
|
||||
};
|
||||
|
||||
static const struct regmap_config rt9770_regmap_config = {
|
||||
.name = "rt9770",
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0xff,
|
||||
};
|
||||
|
||||
static int rt9756_check_device_model(struct rt9756_data *data)
|
||||
{
|
||||
struct device *dev = data->dev;
|
||||
unsigned int revid;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(data->rm_fields[F_REVISION], &revid);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to read revid\n");
|
||||
|
||||
if (revid == RT9757_REVID || revid == RT9757A_REVID)
|
||||
data->model = MODEL_RT9757;
|
||||
else if (revid == RT9756_REVID || revid == RT9756A_REVID)
|
||||
data->model = MODEL_RT9756;
|
||||
else
|
||||
return dev_err_probe(dev, -EINVAL, "Unknown revision %d\n", revid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rt9770_check_device_model(struct rt9756_data *data)
|
||||
{
|
||||
data->model = MODEL_RT9770;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rt9756_probe(struct i2c_client *i2c)
|
||||
{
|
||||
const struct rt975x_dev_data *dev_data;
|
||||
struct device *dev = &i2c->dev;
|
||||
struct rt9756_data *data;
|
||||
struct regmap *regmap;
|
||||
unsigned int devid;
|
||||
int ret;
|
||||
|
||||
dev_data = device_get_match_data(dev);
|
||||
if (!dev_data)
|
||||
return dev_err_probe(dev, -EINVAL, "No device data found\n");
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dev = dev;
|
||||
mutex_init(&data->adc_lock);
|
||||
atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN);
|
||||
i2c_set_clientdata(i2c, data);
|
||||
|
||||
regmap = devm_regmap_init_i2c(i2c, dev_data->regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n");
|
||||
|
||||
data->regmap = regmap;
|
||||
|
||||
ret = devm_regmap_field_bulk_alloc(dev, regmap, data->rm_fields, dev_data->reg_fields,
|
||||
F_MAX_FIELD);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to alloc regmap fields\n");
|
||||
|
||||
/* Richtek Device ID check */
|
||||
ret = regmap_field_read(data->rm_fields[F_DEV_ID], &devid);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to read devid\n");
|
||||
|
||||
if (devid != RICHTEK_DEVID)
|
||||
return dev_err_probe(dev, -ENODEV, "Incorrect VID 0x%02x\n", devid);
|
||||
|
||||
/* Get specific model */
|
||||
ret = dev_data->check_device_model(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_register_patch(regmap, dev_data->init_regs, dev_data->num_init_regs);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to init registers\n");
|
||||
|
||||
ret = rt9756_config_batsense_resistor(data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to config batsense resistor\n");
|
||||
|
||||
ret = rt9756_register_psy(data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to init power supply\n");
|
||||
|
||||
return devm_request_threaded_irq(dev, i2c->irq, NULL, rt9756_irq_handler, IRQF_ONESHOT,
|
||||
dev_name(dev), data);
|
||||
}
|
||||
|
||||
static void rt9756_shutdown(struct i2c_client *i2c)
|
||||
{
|
||||
struct rt9756_data *data = i2c_get_clientdata(i2c);
|
||||
|
||||
regmap_field_write(data->rm_fields[F_REG_RST], 1);
|
||||
}
|
||||
|
||||
static const struct rt975x_dev_data rt9756_dev_data = {
|
||||
.regmap_config = &rt9756_regmap_config,
|
||||
.reg_fields = rt9756_chg_fields,
|
||||
.init_regs = rt9756_init_regs,
|
||||
.num_init_regs = ARRAY_SIZE(rt9756_init_regs),
|
||||
.check_device_model = rt9756_check_device_model,
|
||||
};
|
||||
|
||||
static const struct rt975x_dev_data rt9770_dev_data = {
|
||||
.regmap_config = &rt9770_regmap_config,
|
||||
.reg_fields = rt9770_chg_fields,
|
||||
.init_regs = rt9770_init_regs,
|
||||
.num_init_regs = ARRAY_SIZE(rt9770_init_regs),
|
||||
.check_device_model = rt9770_check_device_model,
|
||||
};
|
||||
|
||||
static const struct of_device_id rt9756_device_match_table[] = {
|
||||
{ .compatible = "richtek,rt9756", .data = &rt9756_dev_data },
|
||||
{ .compatible = "richtek,rt9770", .data = &rt9770_dev_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rt9756_device_match_table);
|
||||
|
||||
static struct i2c_driver rt9756_charger_driver = {
|
||||
.driver = {
|
||||
.name = "rt9756",
|
||||
.of_match_table = rt9756_device_match_table,
|
||||
},
|
||||
.probe = rt9756_probe,
|
||||
.shutdown = rt9756_shutdown,
|
||||
};
|
||||
module_i2c_driver(rt9756_charger_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Richtek RT9756 charger driver");
|
||||
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -144,6 +144,7 @@ static int wm831x_usb_limit_change(struct notifier_block *nb,
|
||||
struct wm831x_power,
|
||||
usb_notify);
|
||||
unsigned int i, best;
|
||||
int ret;
|
||||
|
||||
/* Find the highest supported limit */
|
||||
best = 0;
|
||||
@@ -156,8 +157,13 @@ static int wm831x_usb_limit_change(struct notifier_block *nb,
|
||||
dev_dbg(wm831x_power->wm831x->dev,
|
||||
"Limiting USB current to %umA", wm831x_usb_limits[best]);
|
||||
|
||||
wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE,
|
||||
WM831X_USB_ILIM_MASK, best);
|
||||
ret = wm831x_set_bits(wm831x_power->wm831x, WM831X_POWER_STATE,
|
||||
WM831X_USB_ILIM_MASK, best);
|
||||
if (ret < 0) {
|
||||
dev_err(wm831x_power->wm831x->dev,
|
||||
"Failed to set USB current limit: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
#define MAX77705_DISABLE_SKIP 1
|
||||
#define MAX77705_AUTO_SKIP 0
|
||||
|
||||
#define AICL_WORK_DELAY_MS 100
|
||||
|
||||
/* uA */
|
||||
#define MAX77705_CURRENT_CHGIN_STEP 25000
|
||||
#define MAX77705_CURRENT_CHG_STEP 50000
|
||||
|
||||
Reference in New Issue
Block a user