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:
Linus Torvalds 2019-05-15 18:50:40 -07:00
commit 8649efb2f8
33 changed files with 1611 additions and 271 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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>;

View File

@ -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>;
};

View File

@ -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>;

View File

@ -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";
};
};

View File

@ -2,4 +2,4 @@ OLPC battery
~~~~~~~~~~~~
Required properties:
- compatible : "olpc,xo1-battery"
- compatible : "olpc,xo1-battery" or "olpc,xo1.5-battery"

View File

@ -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)

View File

@ -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;

View File

@ -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*/
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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,

View File

@ -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,

View File

@ -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 = {

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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");

View File

@ -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 = &ltc3651_charger->charger_desc;
charger_desc = &lt3651_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");

View File

@ -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;

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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),

View File

@ -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, &reg);
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 /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,
&reg, 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, &reg);
if (ret)
return ret;
current_measurement = reg;
val->intval = 0;
for_each_set_bit(i, &current_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, &reg);
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, &reg);
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, &reg);
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, &reg);
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, &regval);
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, &regmap_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, &regval);
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, &regval);
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,
&regulator_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");

View File

@ -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.

View File

@ -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,