power supply and reset changes for the v5.2 series
Core: * Add over-current health state * Add standard, adaptive and custom charge types * Add new properties for start/end charge threshold New Drivers / Hardware: * UCS1002 Programmable USB Port Power Controller * Ingenic JZ47xx Battery Fuel Gauge * AXP20x USB Power: Add AXP813 support * AT91 poweroff: Add SAM9X60 support * OLPC battery: Add XO-1.5 and XO-1.75 support Misc. Changes: * syscon-reboot: support mask property * AXP288 fuel gauge: Blacklist ACEPC T8/T11 - Looks like some vendor thought it's a good idea to build a desktop system with a fuel gauge, that slowly "discharges"... * cpcap-battery: Fix calculation errors * misc. fixes -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAlzbPpUACgkQ2O7X88g7 +ppU9w/9GDMAHh5LelpuKosuWfdoZMOiMqtyp+GH+Tg4t/cYksTpUFcupKE8sIEU HG+YHNZdD56rHYz7fF6/SRAWfj1o77+Hr2s7XQlLayReFYuxltPIM+MX+xXpj4Qt OJcSWnk9233UqfodPAyvC/Tj+I0SgElOUmkhhe5fqNtktQeJgvDO1Gs2oNBZOuMG +ySTT+8Dba2YbXAHYXYdyzMG1YuDZLbkvSpkYzRBH4CyfDrcTH2zkkfQSu0pAYPk VwdeWw05yKRNZtWhwS+eUefIXmdu8ZH2BNrYk5PobTeDhhMYx+QzoTuxyhIY+Mbq I1tabHrIOMy1Xyw0QsbB2/ujrt5SzNv6SLxgKaPvgPSr1uPz3Ogl3+SRziNY3zvN SmxSedAL5qx/TBTL+rKSKCO66aU8jAdGzvnRfwWcCoQhE+EZF5r0vSn5zIhR2Fxh fKKph8ZZv7426jPBuXTOurQVRs8daa+DmwHauebq4MNnhftJM1PfTb8SFOwrDTMD Es4M5BXgn/1RKfqjh0gKTYkbRBCtUhnHUAPmzAKFCbEENc0eC439P3wQ8lP0EzFT QHpdpPxeMor24HjVldfi0K4hXqNPGEnTlZwq7Asu6NAp0HcgdqIGXiLqQP3/s5ds gMUqOLNRAywupdpMT7db7JadnVmDRK1sHZnhk4wTAPt4Q6gqcE8= =qicd -----END PGP SIGNATURE----- Merge tag 'for-v5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply Pull power supply and reset updates from Sebastian Reichel: "Core: - Add over-current health state - Add standard, adaptive and custom charge types - Add new properties for start/end charge threshold New Drivers / Hardware: - UCS1002 Programmable USB Port Power Controller - Ingenic JZ47xx Battery Fuel Gauge - AXP20x USB Power: Add AXP813 support - AT91 poweroff: Add SAM9X60 support - OLPC battery: Add XO-1.5 and XO-1.75 support Misc Changes: - syscon-reboot: support mask property - AXP288 fuel gauge: Blacklist ACEPC T8/T11. Looks like some vendor thought it's a good idea to build a desktop system with a fuel gauge, that slowly "discharges"... - cpcap-battery: Fix calculation errors - misc fixes" * tag 'for-v5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (54 commits) power: supply: olpc_battery: force the le/be casts power: supply: ucs1002: Fix build error without CONFIG_REGULATOR power: supply: ucs1002: Fix wrong return value checking power: supply: Add driver for Microchip UCS1002 dt-bindings: power: supply: Add bindings for Microchip UCS1002 power: supply: core: Add POWER_SUPPLY_HEALTH_OVERCURRENT constant power: supply: core: fix clang -Wunsequenced power: supply: core: Add missing documentation for CHARGE_CONTROL_* properties power: supply: core: Add CHARGE_CONTROL_{START_THRESHOLD,END_THRESHOLD} properties power: supply: core: Add Standard, Adaptive, and Custom charge types power: supply: axp288_fuel_gauge: Add ACEPC T8 and T11 mini PCs to the blacklist power: supply: bq27xxx_battery: Notify also about status changes power: supply: olpc_battery: Have the framework register sysfs files for us power: supply: olpc_battery: Add OLPC XO 1.75 support power: supply: olpc_battery: Avoid using platform_info power: supply: olpc_battery: Use devm_power_supply_register() power: supply: olpc_battery: Move priv data to a struct power: supply: olpc_battery: Use DT to get battery version x86/platform/olpc: Use a correct version when making up a battery node x86/platform/olpc: Trivial code move in DT fixup ...
This commit is contained in:
commit
8649efb2f8
|
@ -114,15 +114,60 @@ Description:
|
|||
Access: Read
|
||||
Valid values: Represented in microamps
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_control_limit
|
||||
Date: Oct 2012
|
||||
Contact: linux-pm@vger.kernel.org
|
||||
Description:
|
||||
Maximum allowable charging current. Used for charge rate
|
||||
throttling for thermal cooling or improving battery health.
|
||||
|
||||
Access: Read, Write
|
||||
Valid values: Represented in microamps
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_control_limit_max
|
||||
Date: Oct 2012
|
||||
Contact: linux-pm@vger.kernel.org
|
||||
Description:
|
||||
Maximum legal value for the charge_control_limit property.
|
||||
|
||||
Access: Read
|
||||
Valid values: Represented in microamps
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_control_start_threshold
|
||||
Date: April 2019
|
||||
Contact: linux-pm@vger.kernel.org
|
||||
Description:
|
||||
Represents a battery percentage level, below which charging will
|
||||
begin.
|
||||
|
||||
Access: Read, Write
|
||||
Valid values: 0 - 100 (percent)
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_control_end_threshold
|
||||
Date: April 2019
|
||||
Contact: linux-pm@vger.kernel.org
|
||||
Description:
|
||||
Represents a battery percentage level, above which charging will
|
||||
stop.
|
||||
|
||||
Access: Read, Write
|
||||
Valid values: 0 - 100 (percent)
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_type
|
||||
Date: July 2009
|
||||
Contact: linux-pm@vger.kernel.org
|
||||
Description:
|
||||
Represents the type of charging currently being applied to the
|
||||
battery.
|
||||
battery. "Trickle", "Fast", and "Standard" all mean different
|
||||
charging speeds. "Adaptive" means that the charger uses some
|
||||
algorithm to adjust the charge rate dynamically, without
|
||||
any user configuration required. "Custom" means that the charger
|
||||
uses the charge_control_* properties as configuration for some
|
||||
different algorithm.
|
||||
|
||||
Access: Read
|
||||
Valid values: "Unknown", "N/A", "Trickle", "Fast"
|
||||
Access: Read, Write
|
||||
Valid values: "Unknown", "N/A", "Trickle", "Fast", "Standard",
|
||||
"Adaptive", "Custom"
|
||||
|
||||
What: /sys/class/power_supply/<supply_name>/charge_term_current
|
||||
Date: July 2014
|
||||
|
|
|
@ -84,7 +84,7 @@ SHDWC SAMA5D2-Compatible Shutdown Controller
|
|||
1) shdwc node
|
||||
|
||||
required properties:
|
||||
- compatible: should be "atmel,sama5d2-shdwc".
|
||||
- compatible: should be "atmel,sama5d2-shdwc" or "microchip,sam9x60-shdwc".
|
||||
- reg: should contain registers location and length
|
||||
- clocks: phandle to input clock.
|
||||
- #address-cells: should be one. The cell is the wake-up input index.
|
||||
|
@ -96,6 +96,9 @@ optional properties:
|
|||
microseconds. It's usually a board-related property.
|
||||
- atmel,wakeup-rtc-timer: boolean to enable Real-Time Clock wake-up.
|
||||
|
||||
optional microchip,sam9x60-shdwc properties:
|
||||
- atmel,wakeup-rtt-timer: boolean to enable Real-time Timer Wake-up.
|
||||
|
||||
The node contains child nodes for each wake-up input that the platform uses.
|
||||
|
||||
2) input nodes
|
||||
|
|
|
@ -3,13 +3,20 @@ Generic SYSCON mapped register reset driver
|
|||
This is a generic reset driver using syscon to map the reset register.
|
||||
The reset is generally performed with a write to the reset register
|
||||
defined by the register map pointed by syscon reference plus the offset
|
||||
with the mask defined in the reboot node.
|
||||
with the value and mask defined in the reboot node.
|
||||
|
||||
Required properties:
|
||||
- compatible: should contain "syscon-reboot"
|
||||
- regmap: this is phandle to the register map node
|
||||
- offset: offset in the register map for the reboot register (in bytes)
|
||||
- mask: the reset value written to the reboot register (32 bit access)
|
||||
- value: the reset value written to the reboot register (32 bit access)
|
||||
|
||||
Optional properties:
|
||||
- mask: update only the register bits defined by the mask (32 bit)
|
||||
|
||||
Legacy usage:
|
||||
If a node doesn't contain a value property but contains a mask property, the
|
||||
mask property is used as the value.
|
||||
|
||||
Default will be little endian mode, 32 bit access only.
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Required Properties:
|
|||
-compatible: One of: "x-powers,axp202-usb-power-supply"
|
||||
"x-powers,axp221-usb-power-supply"
|
||||
"x-powers,axp223-usb-power-supply"
|
||||
"x-powers,axp813-usb-power-supply"
|
||||
|
||||
The AXP223 PMIC shares most of its behaviour with the AXP221 but has slight
|
||||
variations such as the former being able to set the VBUS power supply max
|
||||
|
|
|
@ -14,13 +14,17 @@ Required properties :
|
|||
usb-cdp (USB charging downstream port)
|
||||
usb-aca (USB accessory charger adapter)
|
||||
|
||||
Optional properties:
|
||||
- charge-status-gpios: GPIO indicating whether a battery is charging.
|
||||
|
||||
Example:
|
||||
|
||||
usb_charger: charger {
|
||||
compatible = "gpio-charger";
|
||||
charger-type = "usb-sdp";
|
||||
gpios = <&gpf0 2 0 0 0>;
|
||||
}
|
||||
gpios = <&gpd 28 GPIO_ACTIVE_LOW>;
|
||||
charge-status-gpios = <&gpc 27 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
battery {
|
||||
power-supplies = <&usb_charger>;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
* Ingenic JZ47xx battery bindings
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Must be "ingenic,jz4740-battery".
|
||||
- io-channels: phandle and IIO specifier pair to the IIO device.
|
||||
Format described in iio-bindings.txt.
|
||||
- monitored-battery: phandle to a "simple-battery" compatible node.
|
||||
|
||||
The "monitored-battery" property must be a phandle to a node using the format
|
||||
described in battery.txt, with the following properties being required:
|
||||
|
||||
- voltage-min-design-microvolt: Drained battery voltage.
|
||||
- voltage-max-design-microvolt: Fully charged battery voltage.
|
||||
|
||||
Example:
|
||||
|
||||
#include <dt-bindings/iio/adc/ingenic,adc.h>
|
||||
|
||||
simple_battery: battery {
|
||||
compatible = "simple-battery";
|
||||
voltage-min-design-microvolt = <3600000>;
|
||||
voltage-max-design-microvolt = <4200000>;
|
||||
};
|
||||
|
||||
ingenic_battery {
|
||||
compatible = "ingenic,jz4740-battery";
|
||||
io-channels = <&adc INGENIC_ADC_BATTERY>;
|
||||
io-channel-names = "battery";
|
||||
monitored-battery = <&simple_battery>;
|
||||
};
|
|
@ -1,14 +1,16 @@
|
|||
ltc3651-charger
|
||||
Analog Devices LT3651 Charger Power Supply bindings: lt3651-charger
|
||||
|
||||
Required properties:
|
||||
- compatible: "lltc,ltc3651-charger"
|
||||
- compatible: Should contain one of the following:
|
||||
* "lltc,ltc3651-charger", (DEPRECATED: Use "lltc,lt3651-charger")
|
||||
* "lltc,lt3651-charger"
|
||||
- lltc,acpr-gpios: Connect to ACPR output. See remark below.
|
||||
|
||||
Optional properties:
|
||||
- lltc,fault-gpios: Connect to FAULT output. See remark below.
|
||||
- lltc,chrg-gpios: Connect to CHRG output. See remark below.
|
||||
|
||||
The ltc3651 outputs are open-drain type and active low. The driver assumes the
|
||||
The lt3651 outputs are open-drain type and active low. The driver assumes the
|
||||
GPIO reports "active" when the output is asserted, so if the pins have been
|
||||
connected directly, the GPIO flags should be set to active low also.
|
||||
|
||||
|
@ -20,7 +22,7 @@ attributes to detect changes.
|
|||
Example:
|
||||
|
||||
charger: battery-charger {
|
||||
compatible = "lltc,ltc3651-charger";
|
||||
compatible = "lltc,lt3651-charger";
|
||||
lltc,acpr-gpios = <&gpio0 68 GPIO_ACTIVE_LOW>;
|
||||
lltc,fault-gpios = <&gpio0 64 GPIO_ACTIVE_LOW>;
|
||||
lltc,chrg-gpios = <&gpio0 63 GPIO_ACTIVE_LOW>;
|
|
@ -0,0 +1,27 @@
|
|||
Microchip UCS1002 USB Port Power Controller
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be "microchip,ucs1002";
|
||||
- reg : I2C slave address
|
||||
|
||||
Optional properties:
|
||||
- interrupts : A list of interrupts lines present (could be either
|
||||
corresponding to A_DET# pin, ALERT# pin, or both)
|
||||
- interrupt-names : A list of interrupt names. Should contain (if
|
||||
present):
|
||||
- "a_det" for line connected to A_DET# pin
|
||||
- "alert" for line connected to ALERT# pin
|
||||
Both are expected to be IRQ_TYPE_EDGE_BOTH
|
||||
Example:
|
||||
|
||||
&i2c3 {
|
||||
charger@32 {
|
||||
compatible = "microchip,ucs1002";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_ucs1002_pins>;
|
||||
reg = <0x32>;
|
||||
interrupts-extended = <&gpio5 2 IRQ_TYPE_EDGE_BOTH>,
|
||||
<&gpio3 21 IRQ_TYPE_EDGE_BOTH>;
|
||||
interrupt-names = "a_det", "alert";
|
||||
};
|
||||
};
|
|
@ -2,4 +2,4 @@ OLPC battery
|
|||
~~~~~~~~~~~~
|
||||
|
||||
Required properties:
|
||||
- compatible : "olpc,xo1-battery"
|
||||
- compatible : "olpc,xo1-battery" or "olpc,xo1.5-battery"
|
||||
|
|
|
@ -220,10 +220,26 @@ static u32 __init olpc_dt_get_board_revision(void)
|
|||
return be32_to_cpu(rev);
|
||||
}
|
||||
|
||||
int olpc_dt_compatible_match(phandle node, const char *compat)
|
||||
{
|
||||
char buf[64], *p;
|
||||
int plen, len;
|
||||
|
||||
plen = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf));
|
||||
if (plen <= 0)
|
||||
return 0;
|
||||
|
||||
len = strlen(compat);
|
||||
for (p = buf; p < buf + plen; p += strlen(p) + 1) {
|
||||
if (strcmp(p, compat) == 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __init olpc_dt_fixup(void)
|
||||
{
|
||||
int r;
|
||||
char buf[64];
|
||||
phandle node;
|
||||
u32 board_rev;
|
||||
|
||||
|
@ -231,41 +247,66 @@ void __init olpc_dt_fixup(void)
|
|||
if (!node)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If the battery node has a compatible property, we are running a new
|
||||
* enough firmware and don't have fixups to make.
|
||||
*/
|
||||
r = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf));
|
||||
if (r > 0)
|
||||
return;
|
||||
|
||||
pr_info("PROM DT: Old firmware detected, applying fixes\n");
|
||||
|
||||
/* Add olpc,xo1-battery compatible marker to battery node */
|
||||
olpc_dt_interpret("\" /battery@0\" find-device"
|
||||
" \" olpc,xo1-battery\" +compatible"
|
||||
" device-end");
|
||||
|
||||
board_rev = olpc_dt_get_board_revision();
|
||||
if (!board_rev)
|
||||
return;
|
||||
|
||||
if (board_rev >= olpc_board_pre(0xd0)) {
|
||||
/* XO-1.5: add dcon device */
|
||||
olpc_dt_interpret("\" /pci/display@1\" find-device"
|
||||
" new-device"
|
||||
" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible"
|
||||
" finish-device device-end");
|
||||
/* XO-1.5 */
|
||||
|
||||
if (olpc_dt_compatible_match(node, "olpc,xo1.5-battery"))
|
||||
return;
|
||||
|
||||
/* Add olpc,xo1.5-battery compatible marker to battery node */
|
||||
olpc_dt_interpret("\" /battery@0\" find-device");
|
||||
olpc_dt_interpret(" \" olpc,xo1.5-battery\" +compatible");
|
||||
olpc_dt_interpret("device-end");
|
||||
|
||||
if (olpc_dt_compatible_match(node, "olpc,xo1-battery")) {
|
||||
/*
|
||||
* If we have a olpc,xo1-battery compatible, then we're
|
||||
* running a new enough firmware that already has
|
||||
* the dcon node.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add dcon device */
|
||||
olpc_dt_interpret("\" /pci/display@1\" find-device");
|
||||
olpc_dt_interpret(" new-device");
|
||||
olpc_dt_interpret(" \" dcon\" device-name");
|
||||
olpc_dt_interpret(" \" olpc,xo1-dcon\" +compatible");
|
||||
olpc_dt_interpret(" finish-device");
|
||||
olpc_dt_interpret("device-end");
|
||||
} else {
|
||||
/* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */
|
||||
olpc_dt_interpret("\" /pci/display@1,1\" find-device"
|
||||
" new-device"
|
||||
" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible"
|
||||
" finish-device device-end"
|
||||
" \" /rtc\" find-device"
|
||||
" \" olpc,xo1-rtc\" +compatible"
|
||||
" device-end");
|
||||
/* XO-1 */
|
||||
|
||||
if (olpc_dt_compatible_match(node, "olpc,xo1-battery")) {
|
||||
/*
|
||||
* If we have a olpc,xo1-battery compatible, then we're
|
||||
* running a new enough firmware that already has
|
||||
* the dcon and RTC nodes.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add dcon device, mark RTC as olpc,xo1-rtc */
|
||||
olpc_dt_interpret("\" /pci/display@1,1\" find-device");
|
||||
olpc_dt_interpret(" new-device");
|
||||
olpc_dt_interpret(" \" dcon\" device-name");
|
||||
olpc_dt_interpret(" \" olpc,xo1-dcon\" +compatible");
|
||||
olpc_dt_interpret(" finish-device");
|
||||
olpc_dt_interpret("device-end");
|
||||
|
||||
olpc_dt_interpret("\" /rtc\" find-device");
|
||||
olpc_dt_interpret(" \" olpc,xo1-rtc\" +compatible");
|
||||
olpc_dt_interpret("device-end");
|
||||
}
|
||||
|
||||
/* Add olpc,xo1-battery compatible marker to battery node */
|
||||
olpc_dt_interpret("\" /battery@0\" find-device");
|
||||
olpc_dt_interpret(" \" olpc,xo1-battery\" +compatible");
|
||||
olpc_dt_interpret("device-end");
|
||||
}
|
||||
|
||||
void __init olpc_dt_build_devicetree(void)
|
||||
|
|
|
@ -733,11 +733,11 @@ static int iio_channel_read_avail(struct iio_channel *chan,
|
|||
vals, type, length, info);
|
||||
}
|
||||
|
||||
int iio_read_avail_channel_raw(struct iio_channel *chan,
|
||||
const int **vals, int *length)
|
||||
int iio_read_avail_channel_attribute(struct iio_channel *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
enum iio_chan_info_enum attribute)
|
||||
{
|
||||
int ret;
|
||||
int type;
|
||||
|
||||
mutex_lock(&chan->indio_dev->info_exist_lock);
|
||||
if (!chan->indio_dev->info) {
|
||||
|
@ -745,11 +745,23 @@ int iio_read_avail_channel_raw(struct iio_channel *chan,
|
|||
goto err_unlock;
|
||||
}
|
||||
|
||||
ret = iio_channel_read_avail(chan,
|
||||
vals, &type, length, IIO_CHAN_INFO_RAW);
|
||||
ret = iio_channel_read_avail(chan, vals, type, length, attribute);
|
||||
err_unlock:
|
||||
mutex_unlock(&chan->indio_dev->info_exist_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_read_avail_channel_attribute);
|
||||
|
||||
int iio_read_avail_channel_raw(struct iio_channel *chan,
|
||||
const int **vals, int *length)
|
||||
{
|
||||
int ret;
|
||||
int type;
|
||||
|
||||
ret = iio_read_avail_channel_attribute(chan, vals, &type, length,
|
||||
IIO_CHAN_INFO_RAW);
|
||||
|
||||
if (ret >= 0 && type != IIO_VAL_INT)
|
||||
/* raw values are assumed to be IIO_VAL_INT */
|
||||
ret = -EINVAL;
|
||||
|
|
|
@ -57,15 +57,21 @@
|
|||
|
||||
#define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input))
|
||||
#define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1)
|
||||
#define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1)
|
||||
#define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift))
|
||||
#define SHDW_RTTWKEN(cfg) (1 << ((cfg)->mr_rttwk_shift))
|
||||
|
||||
#define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \
|
||||
SLOW_CLOCK_FREQ)
|
||||
|
||||
#define SHDW_CFG_NOT_USED (32)
|
||||
|
||||
struct shdwc_config {
|
||||
u8 wkup_pin_input;
|
||||
u8 mr_rtcwk_shift;
|
||||
u8 mr_rttwk_shift;
|
||||
u8 sr_rtcwk_shift;
|
||||
u8 sr_rttwk_shift;
|
||||
};
|
||||
|
||||
struct shdwc {
|
||||
|
@ -104,6 +110,8 @@ static void __init at91_wakeup_status(struct platform_device *pdev)
|
|||
reason = "WKUP pin";
|
||||
else if (SHDW_RTCWK(reg, shdw->cfg))
|
||||
reason = "RTC";
|
||||
else if (SHDW_RTTWK(reg, shdw->cfg))
|
||||
reason = "RTT";
|
||||
|
||||
pr_info("AT91: Wake-Up source: %s\n", reason);
|
||||
}
|
||||
|
@ -221,6 +229,9 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
|
|||
if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
|
||||
mode |= SHDW_RTCWKEN(shdw->cfg);
|
||||
|
||||
if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
|
||||
mode |= SHDW_RTTWKEN(shdw->cfg);
|
||||
|
||||
dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);
|
||||
writel(mode, shdw->shdwc_base + AT91_SHDW_MR);
|
||||
|
||||
|
@ -231,13 +242,27 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
|
|||
static const struct shdwc_config sama5d2_shdwc_config = {
|
||||
.wkup_pin_input = 0,
|
||||
.mr_rtcwk_shift = 17,
|
||||
.mr_rttwk_shift = SHDW_CFG_NOT_USED,
|
||||
.sr_rtcwk_shift = 5,
|
||||
.sr_rttwk_shift = SHDW_CFG_NOT_USED,
|
||||
};
|
||||
|
||||
static const struct shdwc_config sam9x60_shdwc_config = {
|
||||
.wkup_pin_input = 0,
|
||||
.mr_rtcwk_shift = 17,
|
||||
.mr_rttwk_shift = 16,
|
||||
.sr_rtcwk_shift = 5,
|
||||
.sr_rttwk_shift = 4,
|
||||
};
|
||||
|
||||
static const struct of_device_id at91_shdwc_of_match[] = {
|
||||
{
|
||||
.compatible = "atmel,sama5d2-shdwc",
|
||||
.data = &sama5d2_shdwc_config,
|
||||
},
|
||||
{
|
||||
.compatible = "microchip,sam9x60-shdwc",
|
||||
.data = &sam9x60_shdwc_config,
|
||||
}, {
|
||||
/*sentinel*/
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
struct syscon_reboot_context {
|
||||
struct regmap *map;
|
||||
u32 offset;
|
||||
u32 value;
|
||||
u32 mask;
|
||||
struct notifier_block restart_handler;
|
||||
};
|
||||
|
@ -39,7 +40,7 @@ static int syscon_restart_handle(struct notifier_block *this,
|
|||
restart_handler);
|
||||
|
||||
/* Issue the reboot */
|
||||
regmap_write(ctx->map, ctx->offset, ctx->mask);
|
||||
regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value);
|
||||
|
||||
mdelay(1000);
|
||||
|
||||
|
@ -51,6 +52,7 @@ static int syscon_reboot_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct syscon_reboot_context *ctx;
|
||||
struct device *dev = &pdev->dev;
|
||||
int mask_err, value_err;
|
||||
int err;
|
||||
|
||||
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
|
||||
|
@ -64,8 +66,21 @@ static int syscon_reboot_probe(struct platform_device *pdev)
|
|||
if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
|
||||
return -EINVAL;
|
||||
|
||||
if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask))
|
||||
value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value);
|
||||
mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask);
|
||||
if (value_err && mask_err) {
|
||||
dev_err(dev, "unable to read 'value' and 'mask'");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (value_err) {
|
||||
/* support old binding */
|
||||
ctx->value = ctx->mask;
|
||||
ctx->mask = 0xFFFFFFFF;
|
||||
} else if (mask_err) {
|
||||
/* support value without mask*/
|
||||
ctx->mask = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
ctx->restart_handler.notifier_call = syscon_restart_handle;
|
||||
ctx->restart_handler.priority = 192;
|
||||
|
|
|
@ -169,6 +169,17 @@ config BATTERY_COLLIE
|
|||
Say Y to enable support for the battery on the Sharp Zaurus
|
||||
SL-5500 (collie) models.
|
||||
|
||||
config BATTERY_INGENIC
|
||||
tristate "Ingenic JZ47xx SoCs battery driver"
|
||||
depends on MIPS || COMPILE_TEST
|
||||
depends on INGENIC_ADC
|
||||
help
|
||||
Choose this option if you want to monitor battery status on
|
||||
Ingenic JZ47xx SoC based devices.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called ingenic-battery.
|
||||
|
||||
config BATTERY_IPAQ_MICRO
|
||||
tristate "iPAQ Atmel Micro ASIC battery driver"
|
||||
depends on MFD_IPAQ_MICRO
|
||||
|
@ -475,12 +486,12 @@ config CHARGER_MANAGER
|
|||
runtime and in suspend-to-RAM by waking up the system periodically
|
||||
with help of suspend_again support.
|
||||
|
||||
config CHARGER_LTC3651
|
||||
tristate "LTC3651 charger"
|
||||
config CHARGER_LT3651
|
||||
tristate "Analog Devices LT3651 charger"
|
||||
depends on GPIOLIB
|
||||
help
|
||||
Say Y to include support for the LTC3651 battery charger which reports
|
||||
its status via GPIO lines.
|
||||
Say Y to include support for the Analog Devices (Linear Technology)
|
||||
LT3651 battery charger which reports its status via GPIO lines.
|
||||
|
||||
config CHARGER_MAX14577
|
||||
tristate "Maxim MAX14577/77836 battery charger driver"
|
||||
|
@ -667,4 +678,14 @@ config FUEL_GAUGE_SC27XX
|
|||
Say Y here to enable support for fuel gauge with SC27XX
|
||||
PMIC chips.
|
||||
|
||||
config CHARGER_UCS1002
|
||||
tristate "Microchip UCS1002 USB Port Power Controller"
|
||||
depends on I2C
|
||||
depends on OF
|
||||
depends on REGULATOR
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for Microchip UCS1002 Programmable
|
||||
USB Port Power Controller with Charger Emulation.
|
||||
|
||||
endif # POWER_SUPPLY
|
||||
|
|
|
@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
|
|||
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
|
||||
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
|
||||
obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
|
||||
obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o
|
||||
obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
|
||||
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
|
||||
obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
|
||||
|
@ -67,7 +68,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
|
|||
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
|
||||
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
|
||||
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
|
||||
obj-$(CONFIG_CHARGER_LTC3651) += ltc3651-charger.o
|
||||
obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o
|
||||
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
|
||||
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
|
||||
obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
|
||||
|
@ -88,3 +89,4 @@ obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
|
|||
obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o
|
||||
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
|
||||
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
|
||||
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
|
||||
|
|
|
@ -508,6 +508,7 @@ int ab8500_bm_of_probe(struct device *dev,
|
|||
btech = of_get_property(battery_node, "stericsson,battery-type", NULL);
|
||||
if (!btech) {
|
||||
dev_warn(dev, "missing property battery-name/type\n");
|
||||
of_node_put(battery_node);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define DRVNAME "axp20x-usb-power-supply"
|
||||
|
||||
|
@ -36,16 +37,27 @@
|
|||
#define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3)
|
||||
#define AXP20X_VBUS_VHOLD_OFFSET 3
|
||||
#define AXP20X_VBUS_CLIMIT_MASK 3
|
||||
#define AXP20X_VBUC_CLIMIT_900mA 0
|
||||
#define AXP20X_VBUC_CLIMIT_500mA 1
|
||||
#define AXP20X_VBUC_CLIMIT_100mA 2
|
||||
#define AXP20X_VBUC_CLIMIT_NONE 3
|
||||
#define AXP20X_VBUS_CLIMIT_900mA 0
|
||||
#define AXP20X_VBUS_CLIMIT_500mA 1
|
||||
#define AXP20X_VBUS_CLIMIT_100mA 2
|
||||
#define AXP20X_VBUS_CLIMIT_NONE 3
|
||||
|
||||
#define AXP813_VBUS_CLIMIT_900mA 0
|
||||
#define AXP813_VBUS_CLIMIT_1500mA 1
|
||||
#define AXP813_VBUS_CLIMIT_2000mA 2
|
||||
#define AXP813_VBUS_CLIMIT_2500mA 3
|
||||
|
||||
#define AXP20X_ADC_EN1_VBUS_CURR BIT(2)
|
||||
#define AXP20X_ADC_EN1_VBUS_VOLT BIT(3)
|
||||
|
||||
#define AXP20X_VBUS_MON_VBUS_VALID BIT(3)
|
||||
|
||||
/*
|
||||
* Note do not raise the debounce time, we must report Vusb high within
|
||||
* 100ms otherwise we get Vbus errors in musb.
|
||||
*/
|
||||
#define DEBOUNCE_TIME msecs_to_jiffies(50)
|
||||
|
||||
struct axp20x_usb_power {
|
||||
struct device_node *np;
|
||||
struct regmap *regmap;
|
||||
|
@ -53,6 +65,8 @@ struct axp20x_usb_power {
|
|||
enum axp20x_variants axp20x_id;
|
||||
struct iio_channel *vbus_v;
|
||||
struct iio_channel *vbus_i;
|
||||
struct delayed_work vbus_detect;
|
||||
unsigned int old_status;
|
||||
};
|
||||
|
||||
static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
|
||||
|
@ -64,6 +78,89 @@ static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void axp20x_usb_power_poll_vbus(struct work_struct *work)
|
||||
{
|
||||
struct axp20x_usb_power *power =
|
||||
container_of(work, struct axp20x_usb_power, vbus_detect.work);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &val);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
val &= (AXP20X_PWR_STATUS_VBUS_PRESENT | AXP20X_PWR_STATUS_VBUS_USED);
|
||||
if (val != power->old_status)
|
||||
power_supply_changed(power->supply);
|
||||
|
||||
power->old_status = val;
|
||||
|
||||
out:
|
||||
mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
|
||||
}
|
||||
|
||||
static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
|
||||
{
|
||||
if (power->axp20x_id >= AXP221_ID)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val)
|
||||
{
|
||||
unsigned int v;
|
||||
int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
|
||||
case AXP20X_VBUS_CLIMIT_100mA:
|
||||
if (power->axp20x_id == AXP221_ID)
|
||||
*val = -1; /* No 100mA limit */
|
||||
else
|
||||
*val = 100000;
|
||||
break;
|
||||
case AXP20X_VBUS_CLIMIT_500mA:
|
||||
*val = 500000;
|
||||
break;
|
||||
case AXP20X_VBUS_CLIMIT_900mA:
|
||||
*val = 900000;
|
||||
break;
|
||||
case AXP20X_VBUS_CLIMIT_NONE:
|
||||
*val = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int axp813_get_current_max(struct axp20x_usb_power *power, int *val)
|
||||
{
|
||||
unsigned int v;
|
||||
int ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
|
||||
case AXP813_VBUS_CLIMIT_900mA:
|
||||
*val = 900000;
|
||||
break;
|
||||
case AXP813_VBUS_CLIMIT_1500mA:
|
||||
*val = 1500000;
|
||||
break;
|
||||
case AXP813_VBUS_CLIMIT_2000mA:
|
||||
*val = 2000000;
|
||||
break;
|
||||
case AXP813_VBUS_CLIMIT_2500mA:
|
||||
*val = 2500000;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int axp20x_usb_power_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp, union power_supply_propval *val)
|
||||
{
|
||||
|
@ -102,28 +199,9 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
|
|||
val->intval = ret * 1700; /* 1 step = 1.7 mV */
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
|
||||
case AXP20X_VBUC_CLIMIT_100mA:
|
||||
if (power->axp20x_id == AXP221_ID)
|
||||
val->intval = -1; /* No 100mA limit */
|
||||
else
|
||||
val->intval = 100000;
|
||||
break;
|
||||
case AXP20X_VBUC_CLIMIT_500mA:
|
||||
val->intval = 500000;
|
||||
break;
|
||||
case AXP20X_VBUC_CLIMIT_900mA:
|
||||
val->intval = 900000;
|
||||
break;
|
||||
case AXP20X_VBUC_CLIMIT_NONE:
|
||||
val->intval = -1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
if (power->axp20x_id == AXP813_ID)
|
||||
return axp813_get_current_max(power, &val->intval);
|
||||
return axp20x_get_current_max(power, &val->intval);
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
if (IS_ENABLED(CONFIG_AXP20X_ADC)) {
|
||||
ret = iio_read_channel_processed(power->vbus_i,
|
||||
|
@ -214,6 +292,31 @@ static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int axp813_usb_power_set_current_max(struct axp20x_usb_power *power,
|
||||
int intval)
|
||||
{
|
||||
int val;
|
||||
|
||||
switch (intval) {
|
||||
case 900000:
|
||||
return regmap_update_bits(power->regmap,
|
||||
AXP20X_VBUS_IPSOUT_MGMT,
|
||||
AXP20X_VBUS_CLIMIT_MASK,
|
||||
AXP813_VBUS_CLIMIT_900mA);
|
||||
case 1500000:
|
||||
case 2000000:
|
||||
case 2500000:
|
||||
val = (intval - 1000000) / 500000;
|
||||
return regmap_update_bits(power->regmap,
|
||||
AXP20X_VBUS_IPSOUT_MGMT,
|
||||
AXP20X_VBUS_CLIMIT_MASK, val);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power,
|
||||
int intval)
|
||||
{
|
||||
|
@ -248,6 +351,9 @@ static int axp20x_usb_power_set_property(struct power_supply *psy,
|
|||
return axp20x_usb_power_set_voltage_min(power, val->intval);
|
||||
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
if (power->axp20x_id == AXP813_ID)
|
||||
return axp813_usb_power_set_current_max(power,
|
||||
val->intval);
|
||||
return axp20x_usb_power_set_current_max(power, val->intval);
|
||||
|
||||
default:
|
||||
|
@ -357,6 +463,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
|
|||
if (!power)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, power);
|
||||
power->axp20x_id = (enum axp20x_variants)of_device_get_match_data(
|
||||
&pdev->dev);
|
||||
|
||||
|
@ -382,7 +489,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
|
|||
usb_power_desc = &axp20x_usb_power_desc;
|
||||
irq_names = axp20x_irq_names;
|
||||
} else if (power->axp20x_id == AXP221_ID ||
|
||||
power->axp20x_id == AXP223_ID) {
|
||||
power->axp20x_id == AXP223_ID ||
|
||||
power->axp20x_id == AXP813_ID) {
|
||||
usb_power_desc = &axp22x_usb_power_desc;
|
||||
irq_names = axp22x_irq_names;
|
||||
} else {
|
||||
|
@ -415,6 +523,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
|
|||
irq_names[i], ret);
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
|
||||
if (axp20x_usb_vbus_needs_polling(power))
|
||||
queue_delayed_work(system_wq, &power->vbus_detect, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int axp20x_usb_power_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct axp20x_usb_power *power = platform_get_drvdata(pdev);
|
||||
|
||||
cancel_delayed_work_sync(&power->vbus_detect);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -428,12 +549,16 @@ static const struct of_device_id axp20x_usb_power_match[] = {
|
|||
}, {
|
||||
.compatible = "x-powers,axp223-usb-power-supply",
|
||||
.data = (void *)AXP223_ID,
|
||||
}, {
|
||||
.compatible = "x-powers,axp813-usb-power-supply",
|
||||
.data = (void *)AXP813_ID,
|
||||
}, { /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
|
||||
|
||||
static struct platform_driver axp20x_usb_power_driver = {
|
||||
.probe = axp20x_usb_power_probe,
|
||||
.remove = axp20x_usb_power_remove,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.of_match_table = axp20x_usb_power_match,
|
||||
|
|
|
@ -833,6 +833,10 @@ static int axp288_charger_probe(struct platform_device *pdev)
|
|||
/* Register charger interrupts */
|
||||
for (i = 0; i < CHRG_INTR_END; i++) {
|
||||
pirq = platform_get_irq(info->pdev, i);
|
||||
if (pirq < 0) {
|
||||
dev_err(&pdev->dev, "Failed to get IRQ: %d\n", pirq);
|
||||
return pirq;
|
||||
}
|
||||
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
|
||||
if (info->irq[i] < 0) {
|
||||
dev_warn(&info->pdev->dev,
|
||||
|
|
|
@ -685,6 +685,26 @@ intr_failed:
|
|||
* detection reports one despite it not being there.
|
||||
*/
|
||||
static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
|
||||
{
|
||||
/* ACEPC T8 Cherry Trail Z8350 mini PC */
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T8"),
|
||||
/* also match on somewhat unique bios-version */
|
||||
DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* ACEPC T11 Cherry Trail Z8350 mini PC */
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T11"),
|
||||
/* also match on somewhat unique bios-version */
|
||||
DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Intel Cherry Trail Compute Stick, Windows version */
|
||||
.matches = {
|
||||
|
|
|
@ -1612,7 +1612,8 @@ void bq27xxx_battery_update(struct bq27xxx_device_info *di)
|
|||
di->charge_design_full = bq27xxx_battery_read_dcap(di);
|
||||
}
|
||||
|
||||
if (di->cache.capacity != cache.capacity)
|
||||
if ((di->cache.capacity != cache.capacity) ||
|
||||
(di->cache.flags != cache.flags))
|
||||
power_supply_changed(di->bat);
|
||||
|
||||
if (memcmp(&di->cache, &cache, sizeof(cache)) != 0)
|
||||
|
|
|
@ -1987,6 +1987,9 @@ static struct platform_driver charger_manager_driver = {
|
|||
static int __init charger_manager_init(void)
|
||||
{
|
||||
cm_wq = create_freezable_workqueue("charger_manager");
|
||||
if (unlikely(!cm_wq))
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller);
|
||||
|
||||
return platform_driver_register(&charger_manager_driver);
|
||||
|
|
|
@ -82,9 +82,9 @@ struct cpcap_battery_config {
|
|||
};
|
||||
|
||||
struct cpcap_coulomb_counter_data {
|
||||
s32 sample; /* 24-bits */
|
||||
s32 sample; /* 24 or 32 bits */
|
||||
s32 accumulator;
|
||||
s16 offset; /* 10-bits */
|
||||
s16 offset; /* 9 bits */
|
||||
};
|
||||
|
||||
enum cpcap_battery_state {
|
||||
|
@ -213,7 +213,7 @@ static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata)
|
|||
* TI or ST coulomb counter in the PMIC.
|
||||
*/
|
||||
static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
|
||||
u32 sample, s32 accumulator,
|
||||
s32 sample, s32 accumulator,
|
||||
s16 offset, u32 divider)
|
||||
{
|
||||
s64 acc;
|
||||
|
@ -224,9 +224,6 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
|
|||
if (!divider)
|
||||
return 0;
|
||||
|
||||
sample &= 0xffffff; /* 24-bits, unsigned */
|
||||
offset &= 0x7ff; /* 10-bits, signed */
|
||||
|
||||
switch (ddata->vendor) {
|
||||
case CPCAP_VENDOR_ST:
|
||||
cc_lsb = 95374; /* μAms per LSB */
|
||||
|
@ -259,7 +256,7 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
|
|||
|
||||
/* 3600000μAms = 1μAh */
|
||||
static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata,
|
||||
u32 sample, s32 accumulator,
|
||||
s32 sample, s32 accumulator,
|
||||
s16 offset)
|
||||
{
|
||||
return cpcap_battery_cc_raw_div(ddata, sample,
|
||||
|
@ -268,7 +265,7 @@ static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata,
|
|||
}
|
||||
|
||||
static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata,
|
||||
u32 sample, s32 accumulator,
|
||||
s32 sample, s32 accumulator,
|
||||
s16 offset)
|
||||
{
|
||||
return cpcap_battery_cc_raw_div(ddata, sample,
|
||||
|
@ -312,17 +309,19 @@ cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata,
|
|||
/* Sample value CPCAP_REG_CCS1 & 2 */
|
||||
ccd->sample = (buf[1] & 0x0fff) << 16;
|
||||
ccd->sample |= buf[0];
|
||||
if (ddata->vendor == CPCAP_VENDOR_TI)
|
||||
ccd->sample = sign_extend32(24, ccd->sample);
|
||||
|
||||
/* Accumulator value CPCAP_REG_CCA1 & 2 */
|
||||
ccd->accumulator = ((s16)buf[3]) << 16;
|
||||
ccd->accumulator |= buf[2];
|
||||
|
||||
/* Offset value CPCAP_REG_CCO */
|
||||
ccd->offset = buf[5];
|
||||
|
||||
/* Adjust offset based on mode value CPCAP_REG_CCM? */
|
||||
if (buf[4] >= 0x200)
|
||||
ccd->offset |= 0xfc00;
|
||||
/*
|
||||
* Coulomb counter calibration offset is CPCAP_REG_CCM,
|
||||
* REG_CCO seems unused
|
||||
*/
|
||||
ccd->offset = buf[4];
|
||||
ccd->offset = sign_extend32(ccd->offset, 9);
|
||||
|
||||
return cpcap_battery_cc_to_uah(ddata,
|
||||
ccd->sample,
|
||||
|
@ -477,11 +476,11 @@ static int cpcap_battery_get_property(struct power_supply *psy,
|
|||
val->intval = ddata->config.info.voltage_min_design;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_AVG:
|
||||
if (cached) {
|
||||
sample = latest->cc.sample - previous->cc.sample;
|
||||
if (!sample) {
|
||||
val->intval = cpcap_battery_cc_get_avg_current(ddata);
|
||||
break;
|
||||
}
|
||||
sample = latest->cc.sample - previous->cc.sample;
|
||||
accumulator = latest->cc.accumulator - previous->cc.accumulator;
|
||||
val->intval = cpcap_battery_cc_to_ua(ddata, sample,
|
||||
accumulator,
|
||||
|
@ -498,13 +497,13 @@ static int cpcap_battery_get_property(struct power_supply *psy,
|
|||
val->intval = div64_s64(tmp, 100);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_POWER_AVG:
|
||||
if (cached) {
|
||||
sample = latest->cc.sample - previous->cc.sample;
|
||||
if (!sample) {
|
||||
tmp = cpcap_battery_cc_get_avg_current(ddata);
|
||||
tmp *= (latest->voltage / 10000);
|
||||
val->intval = div64_s64(tmp, 100);
|
||||
break;
|
||||
}
|
||||
sample = latest->cc.sample - previous->cc.sample;
|
||||
accumulator = latest->cc.accumulator - previous->cc.accumulator;
|
||||
tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator,
|
||||
latest->cc.offset);
|
||||
|
@ -562,11 +561,11 @@ static irqreturn_t cpcap_battery_irq_thread(int irq, void *data)
|
|||
|
||||
switch (d->action) {
|
||||
case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW:
|
||||
if (latest->counter_uah >= 0)
|
||||
if (latest->current_ua >= 0)
|
||||
dev_warn(ddata->dev, "Battery low at 3.3V!\n");
|
||||
break;
|
||||
case CPCAP_BATTERY_IRQ_ACTION_POWEROFF:
|
||||
if (latest->counter_uah >= 0) {
|
||||
if (latest->current_ua >= 0) {
|
||||
dev_emerg(ddata->dev,
|
||||
"Battery empty at 3.1V, powering off\n");
|
||||
orderly_poweroff(true);
|
||||
|
@ -670,8 +669,9 @@ static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata)
|
|||
return 0;
|
||||
|
||||
out_err:
|
||||
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
|
||||
error);
|
||||
if (error != -EPROBE_DEFER)
|
||||
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
|
||||
error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
|
|
@ -574,8 +574,9 @@ static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata)
|
|||
return 0;
|
||||
|
||||
out_err:
|
||||
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
|
||||
error);
|
||||
if (error != -EPROBE_DEFER)
|
||||
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
|
||||
error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
|
|
@ -29,11 +29,13 @@
|
|||
|
||||
struct gpio_charger {
|
||||
unsigned int irq;
|
||||
unsigned int charge_status_irq;
|
||||
bool wakeup_enabled;
|
||||
|
||||
struct power_supply *charger;
|
||||
struct power_supply_desc charger_desc;
|
||||
struct gpio_desc *gpiod;
|
||||
struct gpio_desc *charge_status;
|
||||
};
|
||||
|
||||
static irqreturn_t gpio_charger_irq(int irq, void *devid)
|
||||
|
@ -59,6 +61,12 @@ static int gpio_charger_get_property(struct power_supply *psy,
|
|||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (gpiod_get_value_cansleep(gpio_charger->charge_status))
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -93,8 +101,29 @@ static enum power_supply_type gpio_charger_get_type(struct device *dev)
|
|||
return POWER_SUPPLY_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
static int gpio_charger_get_irq(struct device *dev, void *dev_id,
|
||||
struct gpio_desc *gpio)
|
||||
{
|
||||
int ret, irq = gpiod_to_irq(gpio);
|
||||
|
||||
if (irq > 0) {
|
||||
ret = devm_request_any_context_irq(dev, irq, gpio_charger_irq,
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING,
|
||||
dev_name(dev),
|
||||
dev_id);
|
||||
if (ret < 0) {
|
||||
dev_warn(dev, "Failed to request irq: %d\n", ret);
|
||||
irq = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return irq;
|
||||
}
|
||||
|
||||
static enum power_supply_property gpio_charger_properties[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_STATUS /* Must always be last in the array. */
|
||||
};
|
||||
|
||||
static int gpio_charger_probe(struct platform_device *pdev)
|
||||
|
@ -104,8 +133,10 @@ static int gpio_charger_probe(struct platform_device *pdev)
|
|||
struct power_supply_config psy_cfg = {};
|
||||
struct gpio_charger *gpio_charger;
|
||||
struct power_supply_desc *charger_desc;
|
||||
struct gpio_desc *charge_status;
|
||||
int charge_status_irq;
|
||||
unsigned long flags;
|
||||
int irq, ret;
|
||||
int ret;
|
||||
|
||||
if (!pdata && !dev->of_node) {
|
||||
dev_err(dev, "No platform data\n");
|
||||
|
@ -151,9 +182,17 @@ static int gpio_charger_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(gpio_charger->gpiod);
|
||||
}
|
||||
|
||||
charge_status = devm_gpiod_get_optional(dev, "charge-status", GPIOD_IN);
|
||||
gpio_charger->charge_status = charge_status;
|
||||
if (IS_ERR(gpio_charger->charge_status))
|
||||
return PTR_ERR(gpio_charger->charge_status);
|
||||
|
||||
charger_desc = &gpio_charger->charger_desc;
|
||||
charger_desc->properties = gpio_charger_properties;
|
||||
charger_desc->num_properties = ARRAY_SIZE(gpio_charger_properties);
|
||||
/* Remove POWER_SUPPLY_PROP_STATUS from the supported properties. */
|
||||
if (!gpio_charger->charge_status)
|
||||
charger_desc->num_properties -= 1;
|
||||
charger_desc->get_property = gpio_charger_get_property;
|
||||
|
||||
psy_cfg.of_node = dev->of_node;
|
||||
|
@ -180,16 +219,12 @@ static int gpio_charger_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
irq = gpiod_to_irq(gpio_charger->gpiod);
|
||||
if (irq > 0) {
|
||||
ret = devm_request_any_context_irq(dev, irq, gpio_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(dev), gpio_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(dev, "Failed to request irq: %d\n", ret);
|
||||
else
|
||||
gpio_charger->irq = irq;
|
||||
}
|
||||
gpio_charger->irq = gpio_charger_get_irq(dev, gpio_charger->charger,
|
||||
gpio_charger->gpiod);
|
||||
|
||||
charge_status_irq = gpio_charger_get_irq(dev, gpio_charger->charger,
|
||||
gpio_charger->charge_status);
|
||||
gpio_charger->charge_status_irq = charge_status_irq;
|
||||
|
||||
platform_set_drvdata(pdev, gpio_charger);
|
||||
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Battery driver for the Ingenic JZ47xx SoCs
|
||||
* Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu>
|
||||
*
|
||||
* based on drivers/power/supply/jz4740-battery.c
|
||||
*/
|
||||
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/property.h>
|
||||
|
||||
struct ingenic_battery {
|
||||
struct device *dev;
|
||||
struct iio_channel *channel;
|
||||
struct power_supply_desc desc;
|
||||
struct power_supply *battery;
|
||||
struct power_supply_battery_info info;
|
||||
};
|
||||
|
||||
static int ingenic_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct ingenic_battery *bat = power_supply_get_drvdata(psy);
|
||||
struct power_supply_battery_info *info = &bat->info;
|
||||
int ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
ret = iio_read_channel_processed(bat->channel, &val->intval);
|
||||
val->intval *= 1000;
|
||||
if (val->intval < info->voltage_min_design_uv)
|
||||
val->intval = POWER_SUPPLY_HEALTH_DEAD;
|
||||
else if (val->intval > info->voltage_max_design_uv)
|
||||
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
return ret;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
ret = iio_read_channel_processed(bat->channel, &val->intval);
|
||||
val->intval *= 1000;
|
||||
return ret;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||||
val->intval = info->voltage_min_design_uv;
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||||
val->intval = info->voltage_max_design_uv;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
};
|
||||
}
|
||||
|
||||
/* Set the most appropriate IIO channel voltage reference scale
|
||||
* based on the battery's max voltage.
|
||||
*/
|
||||
static int ingenic_battery_set_scale(struct ingenic_battery *bat)
|
||||
{
|
||||
const int *scale_raw;
|
||||
int scale_len, scale_type, best_idx = -1, best_mV, max_raw, i, ret;
|
||||
u64 max_mV;
|
||||
|
||||
ret = iio_read_max_channel_raw(bat->channel, &max_raw);
|
||||
if (ret) {
|
||||
dev_err(bat->dev, "Unable to read max raw channel value\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_read_avail_channel_attribute(bat->channel, &scale_raw,
|
||||
&scale_type, &scale_len,
|
||||
IIO_CHAN_INFO_SCALE);
|
||||
if (ret < 0) {
|
||||
dev_err(bat->dev, "Unable to read channel avail scale\n");
|
||||
return ret;
|
||||
}
|
||||
if (ret != IIO_AVAIL_LIST || scale_type != IIO_VAL_FRACTIONAL_LOG2)
|
||||
return -EINVAL;
|
||||
|
||||
max_mV = bat->info.voltage_max_design_uv / 1000;
|
||||
|
||||
for (i = 0; i < scale_len; i += 2) {
|
||||
u64 scale_mV = (max_raw * scale_raw[i]) >> scale_raw[i + 1];
|
||||
|
||||
if (scale_mV < max_mV)
|
||||
continue;
|
||||
|
||||
if (best_idx >= 0 && scale_mV > best_mV)
|
||||
continue;
|
||||
|
||||
best_mV = scale_mV;
|
||||
best_idx = i;
|
||||
}
|
||||
|
||||
if (best_idx < 0) {
|
||||
dev_err(bat->dev, "Unable to find matching voltage scale\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return iio_write_channel_attribute(bat->channel,
|
||||
scale_raw[best_idx],
|
||||
scale_raw[best_idx + 1],
|
||||
IIO_CHAN_INFO_SCALE);
|
||||
}
|
||||
|
||||
static enum power_supply_property ingenic_battery_properties[] = {
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
||||
};
|
||||
|
||||
static int ingenic_battery_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct ingenic_battery *bat;
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct power_supply_desc *desc;
|
||||
int ret;
|
||||
|
||||
bat = devm_kzalloc(dev, sizeof(*bat), GFP_KERNEL);
|
||||
if (!bat)
|
||||
return -ENOMEM;
|
||||
|
||||
bat->dev = dev;
|
||||
bat->channel = devm_iio_channel_get(dev, "battery");
|
||||
if (IS_ERR(bat->channel))
|
||||
return PTR_ERR(bat->channel);
|
||||
|
||||
desc = &bat->desc;
|
||||
desc->name = "jz-battery";
|
||||
desc->type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
desc->properties = ingenic_battery_properties;
|
||||
desc->num_properties = ARRAY_SIZE(ingenic_battery_properties);
|
||||
desc->get_property = ingenic_battery_get_property;
|
||||
psy_cfg.drv_data = bat;
|
||||
psy_cfg.of_node = dev->of_node;
|
||||
|
||||
bat->battery = devm_power_supply_register(dev, desc, &psy_cfg);
|
||||
if (IS_ERR(bat->battery)) {
|
||||
dev_err(dev, "Unable to register battery\n");
|
||||
return PTR_ERR(bat->battery);
|
||||
}
|
||||
|
||||
ret = power_supply_get_battery_info(bat->battery, &bat->info);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to get battery info: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (bat->info.voltage_min_design_uv < 0) {
|
||||
dev_err(dev, "Unable to get voltage min design\n");
|
||||
return bat->info.voltage_min_design_uv;
|
||||
}
|
||||
if (bat->info.voltage_max_design_uv < 0) {
|
||||
dev_err(dev, "Unable to get voltage max design\n");
|
||||
return bat->info.voltage_max_design_uv;
|
||||
}
|
||||
|
||||
return ingenic_battery_set_scale(bat);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id ingenic_battery_of_match[] = {
|
||||
{ .compatible = "ingenic,jz4740-battery", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ingenic_battery_of_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver ingenic_battery_driver = {
|
||||
.driver = {
|
||||
.name = "ingenic-battery",
|
||||
.of_match_table = of_match_ptr(ingenic_battery_of_match),
|
||||
},
|
||||
.probe = ingenic_battery_probe,
|
||||
};
|
||||
module_platform_driver(ingenic_battery_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs");
|
||||
MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,11 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Driver for Analog Devices (Linear Technology) LT3651 charger IC.
|
||||
* Copyright (C) 2017, Topic Embedded Products
|
||||
* Driver for LTC3651 charger IC.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
|
@ -19,7 +15,7 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
struct ltc3651_charger {
|
||||
struct lt3651_charger {
|
||||
struct power_supply *charger;
|
||||
struct power_supply_desc charger_desc;
|
||||
struct gpio_desc *acpr_gpio;
|
||||
|
@ -27,7 +23,7 @@ struct ltc3651_charger {
|
|||
struct gpio_desc *chrg_gpio;
|
||||
};
|
||||
|
||||
static irqreturn_t ltc3651_charger_irq(int irq, void *devid)
|
||||
static irqreturn_t lt3651_charger_irq(int irq, void *devid)
|
||||
{
|
||||
struct power_supply *charger = devid;
|
||||
|
||||
|
@ -36,37 +32,37 @@ static irqreturn_t ltc3651_charger_irq(int irq, void *devid)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static inline struct ltc3651_charger *psy_to_ltc3651_charger(
|
||||
static inline struct lt3651_charger *psy_to_lt3651_charger(
|
||||
struct power_supply *psy)
|
||||
{
|
||||
return power_supply_get_drvdata(psy);
|
||||
}
|
||||
|
||||
static int ltc3651_charger_get_property(struct power_supply *psy,
|
||||
static int lt3651_charger_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp, union power_supply_propval *val)
|
||||
{
|
||||
struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy);
|
||||
struct lt3651_charger *lt3651_charger = psy_to_lt3651_charger(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (!ltc3651_charger->chrg_gpio) {
|
||||
if (!lt3651_charger->chrg_gpio) {
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
if (gpiod_get_value(ltc3651_charger->chrg_gpio))
|
||||
if (gpiod_get_value(lt3651_charger->chrg_gpio))
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio);
|
||||
val->intval = gpiod_get_value(lt3651_charger->acpr_gpio);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
if (!ltc3651_charger->fault_gpio) {
|
||||
if (!lt3651_charger->fault_gpio) {
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
if (!gpiod_get_value(ltc3651_charger->fault_gpio)) {
|
||||
if (!gpiod_get_value(lt3651_charger->fault_gpio)) {
|
||||
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
break;
|
||||
}
|
||||
|
@ -74,11 +70,11 @@ static int ltc3651_charger_get_property(struct power_supply *psy,
|
|||
* If the fault pin is active, the chrg pin explains the type
|
||||
* of failure.
|
||||
*/
|
||||
if (!ltc3651_charger->chrg_gpio) {
|
||||
if (!lt3651_charger->chrg_gpio) {
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
break;
|
||||
}
|
||||
val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ?
|
||||
val->intval = gpiod_get_value(lt3651_charger->chrg_gpio) ?
|
||||
POWER_SUPPLY_HEALTH_OVERHEAT :
|
||||
POWER_SUPPLY_HEALTH_DEAD;
|
||||
break;
|
||||
|
@ -89,59 +85,59 @@ static int ltc3651_charger_get_property(struct power_supply *psy,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static enum power_supply_property ltc3651_charger_properties[] = {
|
||||
static enum power_supply_property lt3651_charger_properties[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
};
|
||||
|
||||
static int ltc3651_charger_probe(struct platform_device *pdev)
|
||||
static int lt3651_charger_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct power_supply_config psy_cfg = {};
|
||||
struct ltc3651_charger *ltc3651_charger;
|
||||
struct lt3651_charger *lt3651_charger;
|
||||
struct power_supply_desc *charger_desc;
|
||||
int ret;
|
||||
|
||||
ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger),
|
||||
lt3651_charger = devm_kzalloc(&pdev->dev, sizeof(*lt3651_charger),
|
||||
GFP_KERNEL);
|
||||
if (!ltc3651_charger)
|
||||
if (!lt3651_charger)
|
||||
return -ENOMEM;
|
||||
|
||||
ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev,
|
||||
lt3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev,
|
||||
"lltc,acpr", GPIOD_IN);
|
||||
if (IS_ERR(ltc3651_charger->acpr_gpio)) {
|
||||
ret = PTR_ERR(ltc3651_charger->acpr_gpio);
|
||||
if (IS_ERR(lt3651_charger->acpr_gpio)) {
|
||||
ret = PTR_ERR(lt3651_charger->acpr_gpio);
|
||||
dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
lt3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
"lltc,fault", GPIOD_IN);
|
||||
if (IS_ERR(ltc3651_charger->fault_gpio)) {
|
||||
ret = PTR_ERR(ltc3651_charger->fault_gpio);
|
||||
if (IS_ERR(lt3651_charger->fault_gpio)) {
|
||||
ret = PTR_ERR(lt3651_charger->fault_gpio);
|
||||
dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
lt3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
"lltc,chrg", GPIOD_IN);
|
||||
if (IS_ERR(ltc3651_charger->chrg_gpio)) {
|
||||
ret = PTR_ERR(ltc3651_charger->chrg_gpio);
|
||||
if (IS_ERR(lt3651_charger->chrg_gpio)) {
|
||||
ret = PTR_ERR(lt3651_charger->chrg_gpio);
|
||||
dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
charger_desc = <c3651_charger->charger_desc;
|
||||
charger_desc = <3651_charger->charger_desc;
|
||||
charger_desc->name = pdev->dev.of_node->name;
|
||||
charger_desc->type = POWER_SUPPLY_TYPE_MAINS;
|
||||
charger_desc->properties = ltc3651_charger_properties;
|
||||
charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties);
|
||||
charger_desc->get_property = ltc3651_charger_get_property;
|
||||
charger_desc->properties = lt3651_charger_properties;
|
||||
charger_desc->num_properties = ARRAY_SIZE(lt3651_charger_properties);
|
||||
charger_desc->get_property = lt3651_charger_get_property;
|
||||
psy_cfg.of_node = pdev->dev.of_node;
|
||||
psy_cfg.drv_data = ltc3651_charger;
|
||||
psy_cfg.drv_data = lt3651_charger;
|
||||
|
||||
ltc3651_charger->charger = devm_power_supply_register(&pdev->dev,
|
||||
lt3651_charger->charger = devm_power_supply_register(&pdev->dev,
|
||||
charger_desc, &psy_cfg);
|
||||
if (IS_ERR(ltc3651_charger->charger)) {
|
||||
ret = PTR_ERR(ltc3651_charger->charger);
|
||||
if (IS_ERR(lt3651_charger->charger)) {
|
||||
ret = PTR_ERR(lt3651_charger->charger);
|
||||
dev_err(&pdev->dev, "Failed to register power supply: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
|
@ -152,59 +148,60 @@ static int ltc3651_charger_probe(struct platform_device *pdev)
|
|||
* support IRQs on these pins, userspace will have to poll the sysfs
|
||||
* files manually.
|
||||
*/
|
||||
if (ltc3651_charger->acpr_gpio) {
|
||||
ret = gpiod_to_irq(ltc3651_charger->acpr_gpio);
|
||||
if (lt3651_charger->acpr_gpio) {
|
||||
ret = gpiod_to_irq(lt3651_charger->acpr_gpio);
|
||||
if (ret >= 0)
|
||||
ret = devm_request_any_context_irq(&pdev->dev, ret,
|
||||
ltc3651_charger_irq,
|
||||
lt3651_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(&pdev->dev), ltc3651_charger->charger);
|
||||
dev_name(&pdev->dev), lt3651_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "Failed to request acpr irq\n");
|
||||
}
|
||||
if (ltc3651_charger->fault_gpio) {
|
||||
ret = gpiod_to_irq(ltc3651_charger->fault_gpio);
|
||||
if (lt3651_charger->fault_gpio) {
|
||||
ret = gpiod_to_irq(lt3651_charger->fault_gpio);
|
||||
if (ret >= 0)
|
||||
ret = devm_request_any_context_irq(&pdev->dev, ret,
|
||||
ltc3651_charger_irq,
|
||||
lt3651_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(&pdev->dev), ltc3651_charger->charger);
|
||||
dev_name(&pdev->dev), lt3651_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "Failed to request fault irq\n");
|
||||
}
|
||||
if (ltc3651_charger->chrg_gpio) {
|
||||
ret = gpiod_to_irq(ltc3651_charger->chrg_gpio);
|
||||
if (lt3651_charger->chrg_gpio) {
|
||||
ret = gpiod_to_irq(lt3651_charger->chrg_gpio);
|
||||
if (ret >= 0)
|
||||
ret = devm_request_any_context_irq(&pdev->dev, ret,
|
||||
ltc3651_charger_irq,
|
||||
lt3651_charger_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
dev_name(&pdev->dev), ltc3651_charger->charger);
|
||||
dev_name(&pdev->dev), lt3651_charger->charger);
|
||||
if (ret < 0)
|
||||
dev_warn(&pdev->dev, "Failed to request chrg irq\n");
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ltc3651_charger);
|
||||
platform_set_drvdata(pdev, lt3651_charger);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ltc3651_charger_match[] = {
|
||||
{ .compatible = "lltc,ltc3651-charger" },
|
||||
static const struct of_device_id lt3651_charger_match[] = {
|
||||
{ .compatible = "lltc,ltc3651-charger" }, /* DEPRECATED */
|
||||
{ .compatible = "lltc,lt3651-charger" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ltc3651_charger_match);
|
||||
MODULE_DEVICE_TABLE(of, lt3651_charger_match);
|
||||
|
||||
static struct platform_driver ltc3651_charger_driver = {
|
||||
.probe = ltc3651_charger_probe,
|
||||
static struct platform_driver lt3651_charger_driver = {
|
||||
.probe = lt3651_charger_probe,
|
||||
.driver = {
|
||||
.name = "ltc3651-charger",
|
||||
.of_match_table = ltc3651_charger_match,
|
||||
.name = "lt3651-charger",
|
||||
.of_match_table = lt3651_charger_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ltc3651_charger_driver);
|
||||
module_platform_driver(lt3651_charger_driver);
|
||||
|
||||
MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
|
||||
MODULE_DESCRIPTION("Driver for LTC3651 charger");
|
||||
MODULE_DESCRIPTION("Driver for LT3651 charger");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:ltc3651-charger");
|
||||
MODULE_ALIAS("platform:lt3651-charger");
|
|
@ -240,6 +240,14 @@ static enum power_supply_property max14656_battery_props[] = {
|
|||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
};
|
||||
|
||||
static void stop_irq_work(void *data)
|
||||
{
|
||||
struct max14656_chip *chip = data;
|
||||
|
||||
cancel_delayed_work_sync(&chip->irq_work);
|
||||
}
|
||||
|
||||
|
||||
static int max14656_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -278,7 +286,19 @@ static int max14656_probe(struct i2c_client *client,
|
|||
if (ret)
|
||||
return -ENODEV;
|
||||
|
||||
chip->detect_psy = devm_power_supply_register(dev,
|
||||
&chip->psy_desc, &psy_cfg);
|
||||
if (IS_ERR(chip->detect_psy)) {
|
||||
dev_err(dev, "power_supply_register failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&chip->irq_work, max14656_irq_worker);
|
||||
ret = devm_add_action(dev, stop_irq_work, chip);
|
||||
if (ret) {
|
||||
dev_err(dev, "devm_add_action %d failed\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, chip->irq, max14656_irq,
|
||||
IRQF_TRIGGER_FALLING,
|
||||
|
@ -289,13 +309,6 @@ static int max14656_probe(struct i2c_client *client,
|
|||
}
|
||||
enable_irq_wake(chip->irq);
|
||||
|
||||
chip->detect_psy = devm_power_supply_register(dev,
|
||||
&chip->psy_desc, &psy_cfg);
|
||||
if (IS_ERR(chip->detect_psy)) {
|
||||
dev_err(dev, "power_supply_register failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000));
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
@ -52,6 +53,14 @@
|
|||
|
||||
#define BAT_ADDR_MFR_TYPE 0x5F
|
||||
|
||||
struct olpc_battery_data {
|
||||
struct power_supply *olpc_ac;
|
||||
struct power_supply *olpc_bat;
|
||||
char bat_serial[17];
|
||||
bool new_proto;
|
||||
bool little_endian;
|
||||
};
|
||||
|
||||
/*********************************************************************
|
||||
* Power
|
||||
*********************************************************************/
|
||||
|
@ -90,13 +99,10 @@ static const struct power_supply_desc olpc_ac_desc = {
|
|||
.get_property = olpc_ac_get_prop,
|
||||
};
|
||||
|
||||
static struct power_supply *olpc_ac;
|
||||
|
||||
static char bat_serial[17]; /* Ick */
|
||||
|
||||
static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte)
|
||||
static int olpc_bat_get_status(struct olpc_battery_data *data,
|
||||
union power_supply_propval *val, uint8_t ec_byte)
|
||||
{
|
||||
if (olpc_platform_info.ecver > 0x44) {
|
||||
if (data->new_proto) {
|
||||
if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE))
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else if (ec_byte & BAT_STAT_DISCHARGING)
|
||||
|
@ -318,6 +324,14 @@ static int olpc_bat_get_voltage_max_design(union power_supply_propval *val)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static u16 ecword_to_cpu(struct olpc_battery_data *data, u16 ec_word)
|
||||
{
|
||||
if (data->little_endian)
|
||||
return le16_to_cpu((__force __le16)ec_word);
|
||||
else
|
||||
return be16_to_cpu((__force __be16)ec_word);
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* Battery properties
|
||||
*********************************************************************/
|
||||
|
@ -325,8 +339,9 @@ static int olpc_bat_get_property(struct power_supply *psy,
|
|||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct olpc_battery_data *data = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
__be16 ec_word;
|
||||
u16 ec_word;
|
||||
uint8_t ec_byte;
|
||||
__be64 ser_buf;
|
||||
|
||||
|
@ -346,7 +361,7 @@ static int olpc_bat_get_property(struct power_supply *psy,
|
|||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
ret = olpc_bat_get_status(val, ec_byte);
|
||||
ret = olpc_bat_get_status(data, val, ec_byte);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
|
@ -389,7 +404,7 @@ static int olpc_bat_get_property(struct power_supply *psy,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32;
|
||||
val->intval = ecword_to_cpu(data, ec_word) * 9760L / 32;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_AVG:
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
|
@ -397,7 +412,7 @@ static int olpc_bat_get_property(struct power_supply *psy,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120;
|
||||
val->intval = ecword_to_cpu(data, ec_word) * 15625L / 120;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1);
|
||||
|
@ -428,29 +443,29 @@ static int olpc_bat_get_property(struct power_supply *psy,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = (s16)be16_to_cpu(ec_word) * 10 / 256;
|
||||
val->intval = ecword_to_cpu(data, ec_word) * 10 / 256;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
|
||||
ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = (int)be16_to_cpu(ec_word) * 10 / 256;
|
||||
val->intval = (int)ecword_to_cpu(data, ec_word) * 10 / 256;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
|
||||
ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15;
|
||||
val->intval = ecword_to_cpu(data, ec_word) * 6250 / 15;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
|
||||
ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
|
||||
val->strval = bat_serial;
|
||||
sprintf(data->bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
|
||||
val->strval = data->bat_serial;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||||
ret = olpc_bat_get_voltage_max_design(val);
|
||||
|
@ -536,7 +551,7 @@ static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
|
|||
return count;
|
||||
}
|
||||
|
||||
static const struct bin_attribute olpc_bat_eeprom = {
|
||||
static struct bin_attribute olpc_bat_eeprom = {
|
||||
.attr = {
|
||||
.name = "eeprom",
|
||||
.mode = S_IRUGO,
|
||||
|
@ -560,7 +575,7 @@ static ssize_t olpc_bat_error_read(struct device *dev,
|
|||
return sprintf(buf, "%d\n", ec_byte);
|
||||
}
|
||||
|
||||
static const struct device_attribute olpc_bat_error = {
|
||||
static struct device_attribute olpc_bat_error = {
|
||||
.attr = {
|
||||
.name = "error",
|
||||
.mode = S_IRUGO,
|
||||
|
@ -568,6 +583,27 @@ static const struct device_attribute olpc_bat_error = {
|
|||
.show = olpc_bat_error_read,
|
||||
};
|
||||
|
||||
static struct attribute *olpc_bat_sysfs_attrs[] = {
|
||||
&olpc_bat_error.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct bin_attribute *olpc_bat_sysfs_bin_attrs[] = {
|
||||
&olpc_bat_eeprom,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group olpc_bat_sysfs_group = {
|
||||
.attrs = olpc_bat_sysfs_attrs,
|
||||
.bin_attrs = olpc_bat_sysfs_bin_attrs,
|
||||
|
||||
};
|
||||
|
||||
static const struct attribute_group *olpc_bat_sysfs_groups[] = {
|
||||
&olpc_bat_sysfs_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
/*********************************************************************
|
||||
* Initialisation
|
||||
*********************************************************************/
|
||||
|
@ -578,17 +614,17 @@ static struct power_supply_desc olpc_bat_desc = {
|
|||
.use_for_apm = 1,
|
||||
};
|
||||
|
||||
static struct power_supply *olpc_bat;
|
||||
|
||||
static int olpc_battery_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
if (device_may_wakeup(&olpc_ac->dev))
|
||||
struct olpc_battery_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
if (device_may_wakeup(&data->olpc_ac->dev))
|
||||
olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
|
||||
else
|
||||
olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR);
|
||||
|
||||
if (device_may_wakeup(&olpc_bat->dev))
|
||||
if (device_may_wakeup(&data->olpc_bat->dev))
|
||||
olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
|
||||
| EC_SCI_SRC_BATERR);
|
||||
else
|
||||
|
@ -600,16 +636,37 @@ static int olpc_battery_suspend(struct platform_device *pdev,
|
|||
|
||||
static int olpc_battery_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct power_supply_config bat_psy_cfg = {};
|
||||
struct power_supply_config ac_psy_cfg = {};
|
||||
struct olpc_battery_data *data;
|
||||
uint8_t status;
|
||||
uint8_t ecver;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We've seen a number of EC protocol changes; this driver requires
|
||||
* the latest EC protocol, supported by 0x44 and above.
|
||||
*/
|
||||
if (olpc_platform_info.ecver < 0x44) {
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
/* See if the EC is already there and get the EC revision */
|
||||
ret = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ecver, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (of_find_compatible_node(NULL, NULL, "olpc,xo1.75-ec")) {
|
||||
/* XO 1.75 */
|
||||
data->new_proto = true;
|
||||
data->little_endian = true;
|
||||
} else if (ecver > 0x44) {
|
||||
/* XO 1 or 1.5 with a new EC firmware. */
|
||||
data->new_proto = true;
|
||||
} else if (ecver < 0x44) {
|
||||
/*
|
||||
* We've seen a number of EC protocol changes; this driver
|
||||
* requires the latest EC protocol, supported by 0x44 and above.
|
||||
*/
|
||||
printk(KERN_NOTICE "OLPC EC version 0x%02x too old for "
|
||||
"battery driver.\n", olpc_platform_info.ecver);
|
||||
"battery driver.\n", ecver);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
|
@ -619,59 +676,44 @@ static int olpc_battery_probe(struct platform_device *pdev)
|
|||
|
||||
/* Ignore the status. It doesn't actually matter */
|
||||
|
||||
olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL);
|
||||
if (IS_ERR(olpc_ac))
|
||||
return PTR_ERR(olpc_ac);
|
||||
ac_psy_cfg.of_node = pdev->dev.of_node;
|
||||
ac_psy_cfg.drv_data = data;
|
||||
|
||||
if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */
|
||||
data->olpc_ac = devm_power_supply_register(&pdev->dev, &olpc_ac_desc,
|
||||
&ac_psy_cfg);
|
||||
if (IS_ERR(data->olpc_ac))
|
||||
return PTR_ERR(data->olpc_ac);
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "olpc,xo1.5-battery")) {
|
||||
/* XO-1.5 */
|
||||
olpc_bat_desc.properties = olpc_xo15_bat_props;
|
||||
olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props);
|
||||
} else { /* XO-1 */
|
||||
} else {
|
||||
/* XO-1 */
|
||||
olpc_bat_desc.properties = olpc_xo1_bat_props;
|
||||
olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
|
||||
}
|
||||
|
||||
olpc_bat = power_supply_register(&pdev->dev, &olpc_bat_desc, NULL);
|
||||
if (IS_ERR(olpc_bat)) {
|
||||
ret = PTR_ERR(olpc_bat);
|
||||
goto battery_failed;
|
||||
}
|
||||
bat_psy_cfg.of_node = pdev->dev.of_node;
|
||||
bat_psy_cfg.drv_data = data;
|
||||
bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups;
|
||||
|
||||
ret = device_create_bin_file(&olpc_bat->dev, &olpc_bat_eeprom);
|
||||
if (ret)
|
||||
goto eeprom_failed;
|
||||
|
||||
ret = device_create_file(&olpc_bat->dev, &olpc_bat_error);
|
||||
if (ret)
|
||||
goto error_failed;
|
||||
data->olpc_bat = devm_power_supply_register(&pdev->dev, &olpc_bat_desc,
|
||||
&bat_psy_cfg);
|
||||
if (IS_ERR(data->olpc_bat))
|
||||
return PTR_ERR(data->olpc_bat);
|
||||
|
||||
if (olpc_ec_wakeup_available()) {
|
||||
device_set_wakeup_capable(&olpc_ac->dev, true);
|
||||
device_set_wakeup_capable(&olpc_bat->dev, true);
|
||||
device_set_wakeup_capable(&data->olpc_ac->dev, true);
|
||||
device_set_wakeup_capable(&data->olpc_bat->dev, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_failed:
|
||||
device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom);
|
||||
eeprom_failed:
|
||||
power_supply_unregister(olpc_bat);
|
||||
battery_failed:
|
||||
power_supply_unregister(olpc_ac);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int olpc_battery_remove(struct platform_device *pdev)
|
||||
{
|
||||
device_remove_file(&olpc_bat->dev, &olpc_bat_error);
|
||||
device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom);
|
||||
power_supply_unregister(olpc_bat);
|
||||
power_supply_unregister(olpc_ac);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id olpc_battery_ids[] = {
|
||||
{ .compatible = "olpc,xo1-battery" },
|
||||
{ .compatible = "olpc,xo1.5-battery" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, olpc_battery_ids);
|
||||
|
@ -682,7 +724,6 @@ static struct platform_driver olpc_battery_driver = {
|
|||
.of_match_table = olpc_battery_ids,
|
||||
},
|
||||
.probe = olpc_battery_probe,
|
||||
.remove = olpc_battery_remove,
|
||||
.suspend = olpc_battery_suspend,
|
||||
};
|
||||
|
||||
|
|
|
@ -598,10 +598,12 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
|||
|
||||
err = of_property_read_string(battery_np, "compatible", &value);
|
||||
if (err)
|
||||
return err;
|
||||
goto out_put_node;
|
||||
|
||||
if (strcmp("simple-battery", value))
|
||||
return -ENODEV;
|
||||
if (strcmp("simple-battery", value)) {
|
||||
err = -ENODEV;
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
/* The property and field names below must correspond to elements
|
||||
* in enum power_supply_property. For reasoning, see
|
||||
|
@ -620,19 +622,21 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
|||
&info->precharge_current_ua);
|
||||
of_property_read_u32(battery_np, "charge-term-current-microamp",
|
||||
&info->charge_term_current_ua);
|
||||
of_property_read_u32(battery_np, "constant_charge_current_max_microamp",
|
||||
of_property_read_u32(battery_np, "constant-charge-current-max-microamp",
|
||||
&info->constant_charge_current_max_ua);
|
||||
of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt",
|
||||
of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt",
|
||||
&info->constant_charge_voltage_max_uv);
|
||||
of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
|
||||
&info->factory_internal_resistance_uohm);
|
||||
|
||||
len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
|
||||
if (len < 0 && len != -EINVAL) {
|
||||
return len;
|
||||
err = len;
|
||||
goto out_put_node;
|
||||
} else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
|
||||
dev_err(&psy->dev, "Too many temperature values\n");
|
||||
return -EINVAL;
|
||||
err = -EINVAL;
|
||||
goto out_put_node;
|
||||
} else if (len > 0) {
|
||||
of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
|
||||
info->ocv_temp, len);
|
||||
|
@ -650,7 +654,8 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
|||
dev_err(&psy->dev, "failed to get %s\n", propname);
|
||||
kfree(propname);
|
||||
power_supply_put_battery_info(psy, info);
|
||||
return -EINVAL;
|
||||
err = -EINVAL;
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
kfree(propname);
|
||||
|
@ -661,16 +666,21 @@ int power_supply_get_battery_info(struct power_supply *psy,
|
|||
devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
|
||||
if (!info->ocv_table[index]) {
|
||||
power_supply_put_battery_info(psy, info);
|
||||
return -ENOMEM;
|
||||
err = -ENOMEM;
|
||||
goto out_put_node;
|
||||
}
|
||||
|
||||
for (i = 0; i < tab_len; i++) {
|
||||
table[i].ocv = be32_to_cpu(*list++);
|
||||
table[i].capacity = be32_to_cpu(*list++);
|
||||
table[i].ocv = be32_to_cpu(*list);
|
||||
list++;
|
||||
table[i].capacity = be32_to_cpu(*list);
|
||||
list++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_put_node:
|
||||
of_node_put(battery_np);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
|
||||
|
||||
|
@ -899,7 +909,7 @@ static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd,
|
||||
static int ps_get_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
|
||||
unsigned long *state)
|
||||
{
|
||||
struct power_supply *psy;
|
||||
|
@ -934,7 +944,7 @@ static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
|
|||
|
||||
static const struct thermal_cooling_device_ops psy_tcd_ops = {
|
||||
.get_max_state = ps_get_max_charge_cntl_limit,
|
||||
.get_cur_state = ps_get_cur_chrage_cntl_limit,
|
||||
.get_cur_state = ps_get_cur_charge_cntl_limit,
|
||||
.set_cur_state = ps_set_cur_charge_cntl_limit,
|
||||
};
|
||||
|
||||
|
|
|
@ -56,13 +56,13 @@ static const char * const power_supply_status_text[] = {
|
|||
};
|
||||
|
||||
static const char * const power_supply_charge_type_text[] = {
|
||||
"Unknown", "N/A", "Trickle", "Fast"
|
||||
"Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom"
|
||||
};
|
||||
|
||||
static const char * const power_supply_health_text[] = {
|
||||
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
|
||||
"Unspecified failure", "Cold", "Watchdog timer expire",
|
||||
"Safety timer expire"
|
||||
"Safety timer expire", "Over current"
|
||||
};
|
||||
|
||||
static const char * const power_supply_technology_text[] = {
|
||||
|
@ -274,6 +274,8 @@ static struct device_attribute power_supply_attrs[] = {
|
|||
POWER_SUPPLY_ATTR(constant_charge_voltage_max),
|
||||
POWER_SUPPLY_ATTR(charge_control_limit),
|
||||
POWER_SUPPLY_ATTR(charge_control_limit_max),
|
||||
POWER_SUPPLY_ATTR(charge_control_start_threshold),
|
||||
POWER_SUPPLY_ATTR(charge_control_end_threshold),
|
||||
POWER_SUPPLY_ATTR(input_current_limit),
|
||||
POWER_SUPPLY_ATTR(energy_full_design),
|
||||
POWER_SUPPLY_ATTR(energy_empty_design),
|
||||
|
|
|
@ -0,0 +1,646 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Driver for UCS1002 Programmable USB Port Power Controller
|
||||
*
|
||||
* Copyright (C) 2019 Zodiac Inflight Innovations
|
||||
*/
|
||||
#include <linux/bits.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
|
||||
/* UCS1002 Registers */
|
||||
#define UCS1002_REG_CURRENT_MEASUREMENT 0x00
|
||||
|
||||
/*
|
||||
* The Total Accumulated Charge registers store the total accumulated
|
||||
* charge delivered from the VS source to a portable device. The total
|
||||
* value is calculated using four registers, from 01h to 04h. The bit
|
||||
* weighting of the registers is given in mA/hrs.
|
||||
*/
|
||||
#define UCS1002_REG_TOTAL_ACC_CHARGE 0x01
|
||||
|
||||
/* Other Status Register */
|
||||
#define UCS1002_REG_OTHER_STATUS 0x0f
|
||||
# define F_ADET_PIN BIT(4)
|
||||
# define F_CHG_ACT BIT(3)
|
||||
|
||||
/* Interrupt Status */
|
||||
#define UCS1002_REG_INTERRUPT_STATUS 0x10
|
||||
# define F_DISCHARGE_ERR BIT(6)
|
||||
# define F_RESET BIT(5)
|
||||
# define F_MIN_KEEP_OUT BIT(4)
|
||||
# define F_TSD BIT(3)
|
||||
# define F_OVER_VOLT BIT(2)
|
||||
# define F_BACK_VOLT BIT(1)
|
||||
# define F_OVER_ILIM BIT(0)
|
||||
|
||||
/* Pin Status Register */
|
||||
#define UCS1002_REG_PIN_STATUS 0x14
|
||||
# define UCS1002_PWR_STATE_MASK 0x03
|
||||
# define F_PWR_EN_PIN BIT(6)
|
||||
# define F_M2_PIN BIT(5)
|
||||
# define F_M1_PIN BIT(4)
|
||||
# define F_EM_EN_PIN BIT(3)
|
||||
# define F_SEL_PIN BIT(2)
|
||||
# define F_ACTIVE_MODE_MASK GENMASK(5, 3)
|
||||
# define F_ACTIVE_MODE_PASSTHROUGH F_M2_PIN
|
||||
# define F_ACTIVE_MODE_DEDICATED F_EM_EN_PIN
|
||||
# define F_ACTIVE_MODE_BC12_DCP (F_M2_PIN | F_EM_EN_PIN)
|
||||
# define F_ACTIVE_MODE_BC12_SDP F_M1_PIN
|
||||
# define F_ACTIVE_MODE_BC12_CDP (F_M1_PIN | F_M2_PIN | F_EM_EN_PIN)
|
||||
|
||||
/* General Configuration Register */
|
||||
#define UCS1002_REG_GENERAL_CFG 0x15
|
||||
# define F_RATION_EN BIT(3)
|
||||
|
||||
/* Emulation Configuration Register */
|
||||
#define UCS1002_REG_EMU_CFG 0x16
|
||||
|
||||
/* Switch Configuration Register */
|
||||
#define UCS1002_REG_SWITCH_CFG 0x17
|
||||
# define F_PIN_IGNORE BIT(7)
|
||||
# define F_EM_EN_SET BIT(5)
|
||||
# define F_M2_SET BIT(4)
|
||||
# define F_M1_SET BIT(3)
|
||||
# define F_S0_SET BIT(2)
|
||||
# define F_PWR_EN_SET BIT(1)
|
||||
# define F_LATCH_SET BIT(0)
|
||||
# define V_SET_ACTIVE_MODE_MASK GENMASK(5, 3)
|
||||
# define V_SET_ACTIVE_MODE_PASSTHROUGH F_M2_SET
|
||||
# define V_SET_ACTIVE_MODE_DEDICATED F_EM_EN_SET
|
||||
# define V_SET_ACTIVE_MODE_BC12_DCP (F_M2_SET | F_EM_EN_SET)
|
||||
# define V_SET_ACTIVE_MODE_BC12_SDP F_M1_SET
|
||||
# define V_SET_ACTIVE_MODE_BC12_CDP (F_M1_SET | F_M2_SET | F_EM_EN_SET)
|
||||
|
||||
/* Current Limit Register */
|
||||
#define UCS1002_REG_ILIMIT 0x19
|
||||
# define UCS1002_ILIM_SW_MASK GENMASK(3, 0)
|
||||
|
||||
/* Product ID */
|
||||
#define UCS1002_REG_PRODUCT_ID 0xfd
|
||||
# define UCS1002_PRODUCT_ID 0x4e
|
||||
|
||||
/* Manufacture name */
|
||||
#define UCS1002_MANUFACTURER "SMSC"
|
||||
|
||||
struct ucs1002_info {
|
||||
struct power_supply *charger;
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
struct regulator_desc *regulator_descriptor;
|
||||
bool present;
|
||||
};
|
||||
|
||||
static enum power_supply_property ucs1002_props[] = {
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_CHARGE_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_PRESENT, /* the presence of PED */
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
POWER_SUPPLY_PROP_USB_TYPE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
};
|
||||
|
||||
static int ucs1002_get_online(struct ucs1002_info *info,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_OTHER_STATUS, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = !!(reg & F_CHG_ACT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ucs1002_get_charge(struct ucs1002_info *info,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
/*
|
||||
* To fit within 32 bits some values are rounded (uA/h)
|
||||
*
|
||||
* For Total Accumulated Charge Middle Low Byte register, addr
|
||||
* 03h, byte 2
|
||||
*
|
||||
* B0: 0.01084 mA/h rounded to 11 uA/h
|
||||
* B1: 0.02169 mA/h rounded to 22 uA/h
|
||||
* B2: 0.04340 mA/h rounded to 43 uA/h
|
||||
* B3: 0.08676 mA/h rounded to 87 uA/h
|
||||
* B4: 0.17350 mA/h rounded to 173 uÁ/h
|
||||
*
|
||||
* For Total Accumulated Charge Low Byte register, addr 04h,
|
||||
* byte 3
|
||||
*
|
||||
* B6: 0.00271 mA/h rounded to 3 uA/h
|
||||
* B7: 0.005422 mA/h rounded to 5 uA/h
|
||||
*/
|
||||
static const int bit_weights_uAh[BITS_PER_TYPE(u32)] = {
|
||||
/*
|
||||
* Bit corresponding to low byte (offset 0x04)
|
||||
* B0 B1 B2 B3 B4 B5 B6 B7
|
||||
*/
|
||||
0, 0, 0, 0, 0, 0, 3, 5,
|
||||
/*
|
||||
* Bit corresponding to middle low byte (offset 0x03)
|
||||
* B0 B1 B2 B3 B4 B5 B6 B7
|
||||
*/
|
||||
11, 22, 43, 87, 173, 347, 694, 1388,
|
||||
/*
|
||||
* Bit corresponding to middle high byte (offset 0x02)
|
||||
* B0 B1 B2 B3 B4 B5 B6 B7
|
||||
*/
|
||||
2776, 5552, 11105, 22210, 44420, 88840, 177700, 355400,
|
||||
/*
|
||||
* Bit corresponding to high byte (offset 0x01)
|
||||
* B0 B1 B2 B3 B4 B5 B6 B7
|
||||
*/
|
||||
710700, 1421000, 2843000, 5685000, 11371000, 22742000,
|
||||
45484000, 90968000,
|
||||
};
|
||||
unsigned long total_acc_charger;
|
||||
unsigned int reg;
|
||||
int i, ret;
|
||||
|
||||
ret = regmap_bulk_read(info->regmap, UCS1002_REG_TOTAL_ACC_CHARGE,
|
||||
®, sizeof(u32));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
total_acc_charger = be32_to_cpu(reg); /* BE as per offsets above */
|
||||
val->intval = 0;
|
||||
|
||||
for_each_set_bit(i, &total_acc_charger, ARRAY_SIZE(bit_weights_uAh))
|
||||
val->intval += bit_weights_uAh[i];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ucs1002_get_current(struct ucs1002_info *info,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
/*
|
||||
* The Current Measurement register stores the measured
|
||||
* current value delivered to the portable device. The range
|
||||
* is from 9.76 mA to 2.5 A.
|
||||
*/
|
||||
static const int bit_weights_uA[BITS_PER_TYPE(u8)] = {
|
||||
9760, 19500, 39000, 78100, 156200, 312300, 624600, 1249300,
|
||||
};
|
||||
unsigned long current_measurement;
|
||||
unsigned int reg;
|
||||
int i, ret;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_CURRENT_MEASUREMENT, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
current_measurement = reg;
|
||||
val->intval = 0;
|
||||
|
||||
for_each_set_bit(i, ¤t_measurement, ARRAY_SIZE(bit_weights_uA))
|
||||
val->intval += bit_weights_uA[i];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Current Limit register stores the maximum current used by the
|
||||
* port switch. The range is from 500mA to 2.5 A.
|
||||
*/
|
||||
static const u32 ucs1002_current_limit_uA[] = {
|
||||
500000, 900000, 1000000, 1200000, 1500000, 1800000, 2000000, 2500000,
|
||||
};
|
||||
|
||||
static int ucs1002_get_max_current(struct ucs1002_info *info,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = ucs1002_current_limit_uA[reg & UCS1002_ILIM_SW_MASK];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret, idx;
|
||||
|
||||
for (idx = 0; idx < ARRAY_SIZE(ucs1002_current_limit_uA); idx++) {
|
||||
if (val == ucs1002_current_limit_uA[idx])
|
||||
break;
|
||||
}
|
||||
|
||||
if (idx == ARRAY_SIZE(ucs1002_current_limit_uA))
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_write(info->regmap, UCS1002_REG_ILIMIT, idx);
|
||||
if (ret)
|
||||
return ret;
|
||||
/*
|
||||
* Any current limit setting exceeding the one set via ILIM
|
||||
* pin will be rejected, so we read out freshly changed limit
|
||||
* to make sure that it took effect.
|
||||
*/
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (reg != idx)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum power_supply_usb_type ucs1002_usb_types[] = {
|
||||
POWER_SUPPLY_USB_TYPE_PD,
|
||||
POWER_SUPPLY_USB_TYPE_SDP,
|
||||
POWER_SUPPLY_USB_TYPE_DCP,
|
||||
POWER_SUPPLY_USB_TYPE_CDP,
|
||||
POWER_SUPPLY_USB_TYPE_UNKNOWN,
|
||||
};
|
||||
|
||||
static int ucs1002_set_usb_type(struct ucs1002_info *info, int val)
|
||||
{
|
||||
unsigned int mode;
|
||||
|
||||
if (val < 0 || val >= ARRAY_SIZE(ucs1002_usb_types))
|
||||
return -EINVAL;
|
||||
|
||||
switch (ucs1002_usb_types[val]) {
|
||||
case POWER_SUPPLY_USB_TYPE_PD:
|
||||
mode = V_SET_ACTIVE_MODE_DEDICATED;
|
||||
break;
|
||||
case POWER_SUPPLY_USB_TYPE_SDP:
|
||||
mode = V_SET_ACTIVE_MODE_BC12_SDP;
|
||||
break;
|
||||
case POWER_SUPPLY_USB_TYPE_DCP:
|
||||
mode = V_SET_ACTIVE_MODE_BC12_DCP;
|
||||
break;
|
||||
case POWER_SUPPLY_USB_TYPE_CDP:
|
||||
mode = V_SET_ACTIVE_MODE_BC12_CDP;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return regmap_update_bits(info->regmap, UCS1002_REG_SWITCH_CFG,
|
||||
V_SET_ACTIVE_MODE_MASK, mode);
|
||||
}
|
||||
|
||||
static int ucs1002_get_usb_type(struct ucs1002_info *info,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
enum power_supply_usb_type type;
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_PIN_STATUS, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (reg & F_ACTIVE_MODE_MASK) {
|
||||
default:
|
||||
type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
|
||||
break;
|
||||
case F_ACTIVE_MODE_DEDICATED:
|
||||
type = POWER_SUPPLY_USB_TYPE_PD;
|
||||
break;
|
||||
case F_ACTIVE_MODE_BC12_SDP:
|
||||
type = POWER_SUPPLY_USB_TYPE_SDP;
|
||||
break;
|
||||
case F_ACTIVE_MODE_BC12_DCP:
|
||||
type = POWER_SUPPLY_USB_TYPE_DCP;
|
||||
break;
|
||||
case F_ACTIVE_MODE_BC12_CDP:
|
||||
type = POWER_SUPPLY_USB_TYPE_CDP;
|
||||
break;
|
||||
};
|
||||
|
||||
val->intval = type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ucs1002_get_health(struct ucs1002_info *info,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret, health;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (reg & F_TSD)
|
||||
health = POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
else if (reg & (F_OVER_VOLT | F_BACK_VOLT))
|
||||
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
else if (reg & F_OVER_ILIM)
|
||||
health = POWER_SUPPLY_HEALTH_OVERCURRENT;
|
||||
else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT))
|
||||
health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
else
|
||||
health = POWER_SUPPLY_HEALTH_GOOD;
|
||||
|
||||
val->intval = health;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ucs1002_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct ucs1002_info *info = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
return ucs1002_get_online(info, val);
|
||||
case POWER_SUPPLY_PROP_CHARGE_NOW:
|
||||
return ucs1002_get_charge(info, val);
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
return ucs1002_get_current(info, val);
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
return ucs1002_get_max_current(info, val);
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
return ucs1002_get_usb_type(info, val);
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
return ucs1002_get_health(info, val);
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = info->present;
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = UCS1002_MANUFACTURER;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ucs1002_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct ucs1002_info *info = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
return ucs1002_set_max_current(info, val->intval);
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
return ucs1002_set_usb_type(info, val->intval);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ucs1002_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||||
case POWER_SUPPLY_PROP_USB_TYPE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct power_supply_desc ucs1002_charger_desc = {
|
||||
.name = "ucs1002",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.usb_types = ucs1002_usb_types,
|
||||
.num_usb_types = ARRAY_SIZE(ucs1002_usb_types),
|
||||
.get_property = ucs1002_get_property,
|
||||
.set_property = ucs1002_set_property,
|
||||
.property_is_writeable = ucs1002_property_is_writeable,
|
||||
.properties = ucs1002_props,
|
||||
.num_properties = ARRAY_SIZE(ucs1002_props),
|
||||
};
|
||||
|
||||
static irqreturn_t ucs1002_charger_irq(int irq, void *data)
|
||||
{
|
||||
int ret, regval;
|
||||
bool present;
|
||||
struct ucs1002_info *info = data;
|
||||
|
||||
present = info->present;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_OTHER_STATUS, ®val);
|
||||
if (ret)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/* update attached status */
|
||||
info->present = regval & F_ADET_PIN;
|
||||
|
||||
/* notify the change */
|
||||
if (present != info->present)
|
||||
power_supply_changed(info->charger);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t ucs1002_alert_irq(int irq, void *data)
|
||||
{
|
||||
struct ucs1002_info *info = data;
|
||||
|
||||
power_supply_changed(info->charger);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct regulator_ops ucs1002_regulator_ops = {
|
||||
.is_enabled = regulator_is_enabled_regmap,
|
||||
.enable = regulator_enable_regmap,
|
||||
.disable = regulator_disable_regmap,
|
||||
};
|
||||
|
||||
static const struct regulator_desc ucs1002_regulator_descriptor = {
|
||||
.name = "ucs1002-vbus",
|
||||
.ops = &ucs1002_regulator_ops,
|
||||
.type = REGULATOR_VOLTAGE,
|
||||
.owner = THIS_MODULE,
|
||||
.enable_reg = UCS1002_REG_SWITCH_CFG,
|
||||
.enable_mask = F_PWR_EN_SET,
|
||||
.enable_val = F_PWR_EN_SET,
|
||||
.fixed_uV = 5000000,
|
||||
.n_voltages = 1,
|
||||
};
|
||||
|
||||
static int ucs1002_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *dev_id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct power_supply_config charger_config = {};
|
||||
const struct regmap_config regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
struct regulator_config regulator_config = {};
|
||||
int irq_a_det, irq_alert, ret;
|
||||
struct regulator_dev *rdev;
|
||||
struct ucs1002_info *info;
|
||||
unsigned int regval;
|
||||
|
||||
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->regmap = devm_regmap_init_i2c(client, ®map_config);
|
||||
ret = PTR_ERR_OR_ZERO(info->regmap);
|
||||
if (ret) {
|
||||
dev_err(dev, "Regmap initialization failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
info->client = client;
|
||||
|
||||
irq_a_det = of_irq_get_byname(dev->of_node, "a_det");
|
||||
irq_alert = of_irq_get_byname(dev->of_node, "alert");
|
||||
|
||||
charger_config.of_node = dev->of_node;
|
||||
charger_config.drv_data = info;
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_PRODUCT_ID, ®val);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read product ID: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (regval != UCS1002_PRODUCT_ID) {
|
||||
dev_err(dev,
|
||||
"Product ID does not match (0x%02x != 0x%02x)\n",
|
||||
regval, UCS1002_PRODUCT_ID);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Enable charge rationing by default */
|
||||
ret = regmap_update_bits(info->regmap, UCS1002_REG_GENERAL_CFG,
|
||||
F_RATION_EN, F_RATION_EN);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read general config: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore the M1, M2, PWR_EN, and EM_EN pin states. Set active
|
||||
* mode selection to BC1.2 CDP.
|
||||
*/
|
||||
ret = regmap_update_bits(info->regmap, UCS1002_REG_SWITCH_CFG,
|
||||
V_SET_ACTIVE_MODE_MASK | F_PIN_IGNORE,
|
||||
V_SET_ACTIVE_MODE_BC12_CDP | F_PIN_IGNORE);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to configure default mode: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* Be safe and set initial current limit to 500mA
|
||||
*/
|
||||
ret = ucs1002_set_max_current(info, 500000);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to set max current default: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
info->charger = devm_power_supply_register(dev, &ucs1002_charger_desc,
|
||||
&charger_config);
|
||||
ret = PTR_ERR_OR_ZERO(info->charger);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register power supply: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_read(info->regmap, UCS1002_REG_PIN_STATUS, ®val);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read pin status: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
info->regulator_descriptor =
|
||||
devm_kmemdup(dev, &ucs1002_regulator_descriptor,
|
||||
sizeof(ucs1002_regulator_descriptor),
|
||||
GFP_KERNEL);
|
||||
if (!info->regulator_descriptor)
|
||||
return -ENOMEM;
|
||||
|
||||
info->regulator_descriptor->enable_is_inverted = !(regval & F_SEL_PIN);
|
||||
|
||||
regulator_config.dev = dev;
|
||||
regulator_config.of_node = dev->of_node;
|
||||
regulator_config.regmap = info->regmap;
|
||||
|
||||
rdev = devm_regulator_register(dev, info->regulator_descriptor,
|
||||
®ulator_config);
|
||||
ret = PTR_ERR_OR_ZERO(rdev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register VBUS regulator: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (irq_a_det > 0) {
|
||||
ret = devm_request_threaded_irq(dev, irq_a_det, NULL,
|
||||
ucs1002_charger_irq,
|
||||
IRQF_ONESHOT,
|
||||
"ucs1002-a_det", info);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request A_DET threaded irq: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (irq_alert > 0) {
|
||||
ret = devm_request_threaded_irq(dev, irq_alert, NULL,
|
||||
ucs1002_alert_irq,
|
||||
IRQF_ONESHOT,
|
||||
"ucs1002-alert", info);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request ALERT threaded irq: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ucs1002_of_match[] = {
|
||||
{ .compatible = "microchip,ucs1002", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ucs1002_of_match);
|
||||
|
||||
static struct i2c_driver ucs1002_driver = {
|
||||
.driver = {
|
||||
.name = "ucs1002",
|
||||
.of_match_table = ucs1002_of_match,
|
||||
},
|
||||
.probe = ucs1002_probe,
|
||||
};
|
||||
module_i2c_driver(ucs1002_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Microchip UCS1002 Programmable USB Port Power Controller");
|
||||
MODULE_AUTHOR("Enric Balletbo Serra <enric.balletbo@collabora.com>");
|
||||
MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -290,6 +290,20 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val);
|
|||
int iio_read_avail_channel_raw(struct iio_channel *chan,
|
||||
const int **vals, int *length);
|
||||
|
||||
/**
|
||||
* iio_read_avail_channel_attribute() - read available channel attribute values
|
||||
* @chan: The channel being queried.
|
||||
* @vals: Available values read back.
|
||||
* @type: Type of values read back.
|
||||
* @length: Number of entries in vals.
|
||||
* @attribute: info attribute to be read back.
|
||||
*
|
||||
* Returns an error code, IIO_AVAIL_RANGE or IIO_AVAIL_LIST.
|
||||
*/
|
||||
int iio_read_avail_channel_attribute(struct iio_channel *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
enum iio_chan_info_enum attribute);
|
||||
|
||||
/**
|
||||
* iio_get_channel_type() - get the type of a channel
|
||||
* @channel: The channel being queried.
|
||||
|
|
|
@ -40,11 +40,15 @@ enum {
|
|||
POWER_SUPPLY_STATUS_FULL,
|
||||
};
|
||||
|
||||
/* What algorithm is the charger using? */
|
||||
enum {
|
||||
POWER_SUPPLY_CHARGE_TYPE_UNKNOWN = 0,
|
||||
POWER_SUPPLY_CHARGE_TYPE_NONE,
|
||||
POWER_SUPPLY_CHARGE_TYPE_TRICKLE,
|
||||
POWER_SUPPLY_CHARGE_TYPE_FAST,
|
||||
POWER_SUPPLY_CHARGE_TYPE_TRICKLE, /* slow speed */
|
||||
POWER_SUPPLY_CHARGE_TYPE_FAST, /* fast speed */
|
||||
POWER_SUPPLY_CHARGE_TYPE_STANDARD, /* normal speed */
|
||||
POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE, /* dynamically adjusted speed */
|
||||
POWER_SUPPLY_CHARGE_TYPE_CUSTOM, /* use CHARGE_CONTROL_* props */
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -57,6 +61,7 @@ enum {
|
|||
POWER_SUPPLY_HEALTH_COLD,
|
||||
POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE,
|
||||
POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE,
|
||||
POWER_SUPPLY_HEALTH_OVERCURRENT,
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -121,6 +126,8 @@ enum power_supply_property {
|
|||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
|
||||
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
|
||||
POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, /* in percents! */
|
||||
POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, /* in percents! */
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
||||
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
|
||||
POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
|
||||
|
|
Loading…
Reference in New Issue