Commit 8649efb2 authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents 5fd09ba6 baf5964e
...@@ -114,15 +114,60 @@ Description: ...@@ -114,15 +114,60 @@ Description:
Access: Read Access: Read
Valid values: Represented in microamps 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 What: /sys/class/power_supply/<supply_name>/charge_type
Date: July 2009 Date: July 2009
Contact: linux-pm@vger.kernel.org Contact: linux-pm@vger.kernel.org
Description: Description:
Represents the type of charging currently being applied to the 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 Access: Read, Write
Valid values: "Unknown", "N/A", "Trickle", "Fast" Valid values: "Unknown", "N/A", "Trickle", "Fast", "Standard",
"Adaptive", "Custom"
What: /sys/class/power_supply/<supply_name>/charge_term_current What: /sys/class/power_supply/<supply_name>/charge_term_current
Date: July 2014 Date: July 2014
......
...@@ -84,7 +84,7 @@ SHDWC SAMA5D2-Compatible Shutdown Controller ...@@ -84,7 +84,7 @@ SHDWC SAMA5D2-Compatible Shutdown Controller
1) shdwc node 1) shdwc node
required properties: 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 - reg: should contain registers location and length
- clocks: phandle to input clock. - clocks: phandle to input clock.
- #address-cells: should be one. The cell is the wake-up input index. - #address-cells: should be one. The cell is the wake-up input index.
...@@ -96,6 +96,9 @@ optional properties: ...@@ -96,6 +96,9 @@ optional properties:
microseconds. It's usually a board-related property. microseconds. It's usually a board-related property.
- atmel,wakeup-rtc-timer: boolean to enable Real-Time Clock wake-up. - 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. The node contains child nodes for each wake-up input that the platform uses.
2) input nodes 2) input nodes
......
...@@ -3,13 +3,20 @@ Generic SYSCON mapped register reset driver ...@@ -3,13 +3,20 @@ Generic SYSCON mapped register reset driver
This is a generic reset driver using syscon to map the reset register. 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 The reset is generally performed with a write to the reset register
defined by the register map pointed by syscon reference plus the offset 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: Required properties:
- compatible: should contain "syscon-reboot" - compatible: should contain "syscon-reboot"
- regmap: this is phandle to the register map node - regmap: this is phandle to the register map node
- offset: offset in the register map for the reboot register (in bytes) - 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. Default will be little endian mode, 32 bit access only.
......
...@@ -4,6 +4,7 @@ Required Properties: ...@@ -4,6 +4,7 @@ Required Properties:
-compatible: One of: "x-powers,axp202-usb-power-supply" -compatible: One of: "x-powers,axp202-usb-power-supply"
"x-powers,axp221-usb-power-supply" "x-powers,axp221-usb-power-supply"
"x-powers,axp223-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 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 variations such as the former being able to set the VBUS power supply max
......
...@@ -14,13 +14,17 @@ Required properties : ...@@ -14,13 +14,17 @@ Required properties :
usb-cdp (USB charging downstream port) usb-cdp (USB charging downstream port)
usb-aca (USB accessory charger adapter) usb-aca (USB accessory charger adapter)
Optional properties:
- charge-status-gpios: GPIO indicating whether a battery is charging.
Example: Example:
usb_charger: charger { usb_charger: charger {
compatible = "gpio-charger"; compatible = "gpio-charger";
charger-type = "usb-sdp"; 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 { battery {
power-supplies = <&usb_charger>; power-supplies = <&usb_charger>;
......
* 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>;
};
ltc3651-charger Analog Devices LT3651 Charger Power Supply bindings: lt3651-charger
Required properties: 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. - lltc,acpr-gpios: Connect to ACPR output. See remark below.
Optional properties: Optional properties:
- lltc,fault-gpios: Connect to FAULT output. See remark below. - lltc,fault-gpios: Connect to FAULT output. See remark below.
- lltc,chrg-gpios: Connect to CHRG 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 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. connected directly, the GPIO flags should be set to active low also.
...@@ -20,7 +22,7 @@ attributes to detect changes. ...@@ -20,7 +22,7 @@ attributes to detect changes.
Example: Example:
charger: battery-charger { charger: battery-charger {
compatible = "lltc,ltc3651-charger"; compatible = "lltc,lt3651-charger";
lltc,acpr-gpios = <&gpio0 68 GPIO_ACTIVE_LOW>; lltc,acpr-gpios = <&gpio0 68 GPIO_ACTIVE_LOW>;
lltc,fault-gpios = <&gpio0 64 GPIO_ACTIVE_LOW>; lltc,fault-gpios = <&gpio0 64 GPIO_ACTIVE_LOW>;
lltc,chrg-gpios = <&gpio0 63 GPIO_ACTIVE_LOW>; lltc,chrg-gpios = <&gpio0 63 GPIO_ACTIVE_LOW>;
......
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 ...@@ -2,4 +2,4 @@ OLPC battery
~~~~~~~~~~~~ ~~~~~~~~~~~~
Required properties: 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) ...@@ -220,10 +220,26 @@ static u32 __init olpc_dt_get_board_revision(void)
return be32_to_cpu(rev); 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) void __init olpc_dt_fixup(void)
{ {
int r;
char buf[64];
phandle node; phandle node;
u32 board_rev; u32 board_rev;
...@@ -231,41 +247,66 @@ void __init olpc_dt_fixup(void) ...@@ -231,41 +247,66 @@ void __init olpc_dt_fixup(void)
if (!node) if (!node)
return; 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(); board_rev = olpc_dt_get_board_revision();
if (!board_rev) if (!board_rev)
return; return;
if (board_rev >= olpc_board_pre(0xd0)) { if (board_rev >= olpc_board_pre(0xd0)) {
/* XO-1.5: add dcon device */ /* XO-1.5 */
olpc_dt_interpret("\" /pci/display@1\" find-device"
" new-device" if (olpc_dt_compatible_match(node, "olpc,xo1.5-battery"))
" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" return;
" finish-device device-end");
/* 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 { } else {
/* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */ /* XO-1 */
olpc_dt_interpret("\" /pci/display@1,1\" find-device"
" new-device" if (olpc_dt_compatible_match(node, "olpc,xo1-battery")) {
" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" /*
" finish-device device-end" * If we have a olpc,xo1-battery compatible, then we're
" \" /rtc\" find-device" * running a new enough firmware that already has
" \" olpc,xo1-rtc\" +compatible" * the dcon and RTC nodes.
" device-end"); */
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) void __init olpc_dt_build_devicetree(void)
......
...@@ -733,11 +733,11 @@ static int iio_channel_read_avail(struct iio_channel *chan, ...@@ -733,11 +733,11 @@ static int iio_channel_read_avail(struct iio_channel *chan,
vals, type, length, info); vals, type, length, info);
} }
int iio_read_avail_channel_raw(struct iio_channel *chan, int iio_read_avail_channel_attribute(struct iio_channel *chan,
const int **vals, int *length) const int **vals, int *type, int *length,
enum iio_chan_info_enum attribute)
{ {
int ret; int ret;
int type;
mutex_lock(&chan->indio_dev->info_exist_lock); mutex_lock(&chan->indio_dev->info_exist_lock);
if (!chan->indio_dev->info) { if (!chan->indio_dev->info) {
...@@ -745,11 +745,23 @@ int iio_read_avail_channel_raw(struct iio_channel *chan, ...@@ -745,11 +745,23 @@ int iio_read_avail_channel_raw(struct iio_channel *chan,
goto err_unlock; goto err_unlock;
} }
ret = iio_channel_read_avail(chan, ret = iio_channel_read_avail(chan, vals, type, length, attribute);
vals, &type, length, IIO_CHAN_INFO_RAW);
err_unlock: err_unlock:
mutex_unlock(&chan->indio_dev->info_exist_lock); 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) if (ret >= 0 && type != IIO_VAL_INT)
/* raw values are assumed to be IIO_VAL_INT */ /* raw values are assumed to be IIO_VAL_INT */
ret = -EINVAL; ret = -EINVAL;
......
...@@ -57,15 +57,21 @@ ...@@ -57,15 +57,21 @@
#define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input)) #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_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_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)), \ #define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \
SLOW_CLOCK_FREQ) SLOW_CLOCK_FREQ)
#define SHDW_CFG_NOT_USED (32)
struct shdwc_config { struct shdwc_config {
u8 wkup_pin_input; u8 wkup_pin_input;
u8 mr_rtcwk_shift; u8 mr_rtcwk_shift;
u8 mr_rttwk_shift;
u8 sr_rtcwk_shift; u8 sr_rtcwk_shift;
u8 sr_rttwk_shift;
}; };
struct shdwc { struct shdwc {
...@@ -104,6 +110,8 @@ static void __init at91_wakeup_status(struct platform_device *pdev) ...@@ -104,6 +110,8 @@ static void __init at91_wakeup_status(struct platform_device *pdev)
reason = "WKUP pin"; reason = "WKUP pin";
else if (SHDW_RTCWK(reg, shdw->cfg)) else if (SHDW_RTCWK(reg, shdw->cfg))
reason = "RTC"; reason = "RTC";
else if (SHDW_RTTWK(reg, shdw->cfg))
reason = "RTT";
pr_info("AT91: Wake-Up source: %s\n", reason); pr_info("AT91: Wake-Up source: %s\n", reason);
} }
...@@ -221,6 +229,9 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) ...@@ -221,6 +229,9 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
mode |= SHDW_RTCWKEN(shdw->cfg); 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); dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);
writel(mode, shdw->shdwc_base + AT91_SHDW_MR); writel(mode, shdw->shdwc_base + AT91_SHDW_MR);
...@@ -231,13 +242,27 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) ...@@ -231,13 +242,27 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
static const struct shdwc_config sama5d2_shdwc_config = { static const struct shdwc_config sama5d2_shdwc_config = {
.wkup_pin_input = 0, .wkup_pin_input = 0,
.mr_rtcwk_shift = 17, .mr_rtcwk_shift = 17,
.mr_rttwk_shift = SHDW_CFG_NOT_USED,
.sr_rtcwk_shift = 5, .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[] = { static const struct of_device_id at91_shdwc_of_match[] = {
{ {
.compatible = "atmel,sama5d2-shdwc", .compatible = "atmel,sama5d2-shdwc",
.data = &sama5d2_shdwc_config, .data = &sama5d2_shdwc_config,
},
{
.compatible = "microchip,sam9x60-shdwc",
.data = &sam9x60_shdwc_config,
}, { }, {
/*sentinel*/ /*sentinel*/
} }
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
struct syscon_reboot_context { struct syscon_reboot_context {
struct regmap *map; struct regmap *map;
u32 offset; u32 offset;
u32 value;
u32 mask; u32 mask;
struct notifier_block restart_handler; struct notifier_block restart_handler;
}; };
...@@ -39,7 +40,7 @@ static int syscon_restart_handle(struct notifier_block *this, ...@@ -39,7 +40,7 @@ static int syscon_restart_handle(struct notifier_block *this,
restart_handler); restart_handler);
/* Issue the reboot */ /* Issue the reboot */
regmap_write(ctx->map, ctx->offset, ctx->mask); regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value);
mdelay(1000); mdelay(1000);
...@@ -51,6 +52,7 @@ static int syscon_reboot_probe(struct platform_device *pdev) ...@@ -51,6 +52,7 @@ static int syscon_reboot_probe(struct platform_device *pdev)
{ {
struct syscon_reboot_context *ctx; struct syscon_reboot_context *ctx;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
int mask_err, value_err;
int err; int err;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
...@@ -64,8 +66,21 @@ static int syscon_reboot_probe(struct platform_device *pdev) ...@@ -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)) if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
return -EINVAL; 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; 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.notifier_call = syscon_restart_handle;
ctx->restart_handler.priority = 192; ctx->restart_handler.priority = 192;
......
...@@ -169,6 +169,17 @@ config BATTERY_COLLIE ...@@ -169,6 +169,17 @@ config BATTERY_COLLIE
Say Y to enable support for the battery on the Sharp Zaurus Say Y to enable support for the battery on the Sharp Zaurus
SL-5500 (collie) models. 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 config BATTERY_IPAQ_MICRO
tristate "iPAQ Atmel Micro ASIC battery driver" tristate "iPAQ Atmel Micro ASIC battery driver"
depends on MFD_IPAQ_MICRO depends on MFD_IPAQ_MICRO
...@@ -475,12 +486,12 @@ config CHARGER_MANAGER ...@@ -475,12 +486,12 @@ config CHARGER_MANAGER
runtime and in suspend-to-RAM by waking up the system periodically runtime and in suspend-to-RAM by waking up the system periodically
with help of suspend_again support. with help of suspend_again support.
config CHARGER_LTC3651 config CHARGER_LT3651
tristate "LTC3651 charger" tristate "Analog Devices LT3651 charger"
depends on GPIOLIB depends on GPIOLIB
help help
Say Y to include support for the LTC3651 battery charger which reports Say Y to include support for the Analog Devices (Linear Technology)
its status via GPIO lines. LT3651 battery charger which reports its status via GPIO lines.
config CHARGER_MAX14577 config CHARGER_MAX14577
tristate "Maxim MAX14577/77836 battery charger driver" tristate "Maxim MAX14577/77836 battery charger driver"
...@@ -667,4 +678,14 @@ config FUEL_GAUGE_SC27XX ...@@ -667,4 +678,14 @@ config FUEL_GAUGE_SC27XX
Say Y here to enable support for fuel gauge with SC27XX Say Y here to enable support for fuel gauge with SC27XX
PMIC chips. 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 endif # POWER_SUPPLY
...@@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o ...@@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
obj-$(CONFIG_BATTERY_COLLIE) += collie_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_IPAQ_MICRO) += ipaq_micro_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
...@@ -67,7 +68,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o ...@@ -67,7 +68,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.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_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
...@@ -88,3 +89,4 @@ obj-$(CONFIG_AXP288_CHARGER) += axp288_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_CROS_USBPD) += cros_usbpd-charger.o
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.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, ...@@ -508,6 +508,7 @@ int ab8500_bm_of_probe(struct device *dev,
btech = of_get_property(battery_node, "stericsson,battery-type", NULL); btech = of_get_property(battery_node, "stericsson,battery-type", NULL);
if (!btech) { if (!btech) {
dev_warn(dev, "missing property battery-name/type\n"); dev_warn(dev, "missing property battery-name/type\n");
of_node_put(battery_node);
return -EINVAL; return -EINVAL;
} }
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/iio/consumer.h> #include <linux/iio/consumer.h>
#include <linux/workqueue.h>
#define DRVNAME "axp20x-usb-power-supply" #define DRVNAME "axp20x-usb-power-supply"
...@@ -36,16 +37,27 @@ ...@@ -36,16 +37,27 @@
#define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3)
#define AXP20X_VBUS_VHOLD_OFFSET 3 #define AXP20X_VBUS_VHOLD_OFFSET 3
#define AXP20X_VBUS_CLIMIT_MASK 3 #define AXP20X_VBUS_CLIMIT_MASK 3
#define AXP20X_VBUC_CLIMIT_900mA 0 #define AXP20X_VBUS_CLIMIT_900mA 0
#define AXP20X_VBUC_CLIMIT_500mA 1 #define AXP20X_VBUS_CLIMIT_500mA 1
#define AXP20X_VBUC_CLIMIT_100mA 2 #define AXP20X_VBUS_CLIMIT_100mA 2
#define AXP20X_VBUC_CLIMIT_NONE 3 #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_CURR BIT(2)
#define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) #define AXP20X_ADC_EN1_VBUS_VOLT BIT(3)
#define AXP20X_VBUS_MON_VBUS_VALID 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 axp20x_usb_power {
struct device_node *np; struct device_node *np;
struct regmap *regmap; struct regmap *regmap;
...@@ -53,6 +65,8 @@ struct axp20x_usb_power { ...@@ -53,6 +65,8 @@ struct axp20x_usb_power {
enum axp20x_variants axp20x_id; enum axp20x_variants axp20x_id;
struct iio_channel *vbus_v; struct iio_channel *vbus_v;
struct iio_channel *vbus_i; 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) 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) ...@@ -64,6 +78,89 @@ static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
return IRQ_HANDLED; 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, static int axp20x_usb_power_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val) 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, ...@@ -102,28 +199,9 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
val->intval = ret * 1700; /* 1 step = 1.7 mV */ val->intval = ret * 1700; /* 1 step = 1.7 mV */
return 0; return 0;
case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_CURRENT_MAX:
ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); if (power->axp20x_id == AXP813_ID)
if (ret) return axp813_get_current_max(power, &val->intval);
return ret; return axp20x_get_current_max(power, &val->intval);
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;
case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_NOW:
if (IS_ENABLED(CONFIG_AXP20X_ADC)) { if (IS_ENABLED(CONFIG_AXP20X_ADC)) {
ret = iio_read_channel_processed(power->vbus_i, 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, ...@@ -214,6 +292,31 @@ static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power,
return -EINVAL; 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, static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power,
int intval) int intval)
{ {
...@@ -248,6 +351,9 @@ static int axp20x_usb_power_set_property(struct power_supply *psy, ...@@ -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); return axp20x_usb_power_set_voltage_min(power, val->intval);
case POWER_SUPPLY_PROP_CURRENT_MAX: 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); return axp20x_usb_power_set_current_max(power, val->intval);
default: default:
...@@ -357,6 +463,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) ...@@ -357,6 +463,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
if (!power) if (!power)
return -ENOMEM; return -ENOMEM;
platform_set_drvdata(pdev, power);
power->axp20x_id = (enum axp20x_variants)of_device_get_match_data( power->axp20x_id = (enum axp20x_variants)of_device_get_match_data(
&pdev->dev); &pdev->dev);
...@@ -382,7 +489,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) ...@@ -382,7 +489,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
usb_power_desc = &axp20x_usb_power_desc; usb_power_desc = &axp20x_usb_power_desc;
irq_names = axp20x_irq_names; irq_names = axp20x_irq_names;
} else if (power->axp20x_id == AXP221_ID || } 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; usb_power_desc = &axp22x_usb_power_desc;
irq_names = axp22x_irq_names; irq_names = axp22x_irq_names;
} else { } else {
...@@ -415,6 +523,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) ...@@ -415,6 +523,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
irq_names[i], ret); 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; return 0;
} }
...@@ -428,12 +549,16 @@ static const struct of_device_id axp20x_usb_power_match[] = { ...@@ -428,12 +549,16 @@ static const struct of_device_id axp20x_usb_power_match[] = {
}, { }, {
.compatible = "x-powers,axp223-usb-power-supply", .compatible = "x-powers,axp223-usb-power-supply",
.data = (void *)AXP223_ID, .data = (void *)AXP223_ID,
}, {
.compatible = "x-powers,axp813-usb-power-supply",
.data = (void *)AXP813_ID,
}, { /* sentinel */ } }, { /* sentinel */ }
}; };
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
static struct platform_driver axp20x_usb_power_driver = { static struct platform_driver axp20x_usb_power_driver = {
.probe = axp20x_usb_power_probe, .probe = axp20x_usb_power_probe,
.remove = axp20x_usb_power_remove,
.driver = { .driver = {
.name = DRVNAME, .name = DRVNAME,
.of_match_table = axp20x_usb_power_match, .of_match_table = axp20x_usb_power_match,
......
...@@ -833,6 +833,10 @@ static int axp288_charger_probe(struct platform_device *pdev) ...@@ -833,6 +833,10 @@ static int axp288_charger_probe(struct platform_device *pdev)
/* Register charger interrupts */ /* Register charger interrupts */
for (i = 0; i < CHRG_INTR_END; i++) { for (i = 0; i < CHRG_INTR_END; i++) {
pirq = platform_get_irq(info->pdev, 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); info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
if (info->irq[i] < 0) { if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev, dev_warn(&info->pdev->dev,
......
...@@ -685,6 +685,26 @@ static void fuel_gauge_init_irq(struct axp288_fg_info *info) ...@@ -685,6 +685,26 @@ static void fuel_gauge_init_irq(struct axp288_fg_info *info)
* detection reports one despite it not being there. * detection reports one despite it not being there.
*/ */
static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = { 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 */ /* Intel Cherry Trail Compute Stick, Windows version */
.matches = { .matches = {
......
...@@ -1612,7 +1612,8 @@ void bq27xxx_battery_update(struct bq27xxx_device_info *di) ...@@ -1612,7 +1612,8 @@ void bq27xxx_battery_update(struct bq27xxx_device_info *di)
di->charge_design_full = bq27xxx_battery_read_dcap(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); power_supply_changed(di->bat);
if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) if (memcmp(&di->cache, &cache, sizeof(cache)) != 0)
......
...@@ -1987,6 +1987,9 @@ static struct platform_driver charger_manager_driver = { ...@@ -1987,6 +1987,9 @@ static struct platform_driver charger_manager_driver = {
static int __init charger_manager_init(void) static int __init charger_manager_init(void)
{ {
cm_wq = create_freezable_workqueue("charger_manager"); cm_wq = create_freezable_workqueue("charger_manager");
if (unlikely(!cm_wq))
return -ENOMEM;
INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller); INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller);
return platform_driver_register(&charger_manager_driver); return platform_driver_register(&charger_manager_driver);
......
...@@ -82,9 +82,9 @@ struct cpcap_battery_config { ...@@ -82,9 +82,9 @@ struct cpcap_battery_config {
}; };
struct cpcap_coulomb_counter_data { struct cpcap_coulomb_counter_data {
s32 sample; /* 24-bits */ s32 sample; /* 24 or 32 bits */
s32 accumulator; s32 accumulator;
s16 offset; /* 10-bits */ s16 offset; /* 9 bits */
}; };
enum cpcap_battery_state { enum cpcap_battery_state {
...@@ -213,7 +213,7 @@ static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata) ...@@ -213,7 +213,7 @@ static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata)
* TI or ST coulomb counter in the PMIC. * TI or ST coulomb counter in the PMIC.
*/ */
static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
u32 sample, s32 accumulator, s32 sample, s32 accumulator,
s16 offset, u32 divider) s16 offset, u32 divider)
{ {
s64 acc; s64 acc;
...@@ -224,9 +224,6 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, ...@@ -224,9 +224,6 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
if (!divider) if (!divider)
return 0; return 0;
sample &= 0xffffff; /* 24-bits, unsigned */
offset &= 0x7ff; /* 10-bits, signed */
switch (ddata->vendor) { switch (ddata->vendor) {
case CPCAP_VENDOR_ST: case CPCAP_VENDOR_ST:
cc_lsb = 95374; /* μAms per LSB */ cc_lsb = 95374; /* μAms per LSB */
...@@ -259,7 +256,7 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, ...@@ -259,7 +256,7 @@ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
/* 3600000μAms = 1μAh */ /* 3600000μAms = 1μAh */
static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata,
u32 sample, s32 accumulator, s32 sample, s32 accumulator,
s16 offset) s16 offset)
{ {
return cpcap_battery_cc_raw_div(ddata, sample, return cpcap_battery_cc_raw_div(ddata, sample,
...@@ -268,7 +265,7 @@ static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, ...@@ -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, static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata,
u32 sample, s32 accumulator, s32 sample, s32 accumulator,
s16 offset) s16 offset)
{ {
return cpcap_battery_cc_raw_div(ddata, sample, return cpcap_battery_cc_raw_div(ddata, sample,
...@@ -312,17 +309,19 @@ cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata, ...@@ -312,17 +309,19 @@ cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata,
/* Sample value CPCAP_REG_CCS1 & 2 */ /* Sample value CPCAP_REG_CCS1 & 2 */
ccd->sample = (buf[1] & 0x0fff) << 16; ccd->sample = (buf[1] & 0x0fff) << 16;
ccd->sample |= buf[0]; ccd->sample |= buf[0];
if (ddata->vendor == CPCAP_VENDOR_TI)
ccd->sample = sign_extend32(24, ccd->sample);
/* Accumulator value CPCAP_REG_CCA1 & 2 */ /* Accumulator value CPCAP_REG_CCA1 & 2 */
ccd->accumulator = ((s16)buf[3]) << 16; ccd->accumulator = ((s16)buf[3]) << 16;
ccd->accumulator |= buf[2]; ccd->accumulator |= buf[2];
/* Offset value CPCAP_REG_CCO */ /*
ccd->offset = buf[5]; * Coulomb counter calibration offset is CPCAP_REG_CCM,
* REG_CCO seems unused
/* Adjust offset based on mode value CPCAP_REG_CCM? */ */
if (buf[4] >= 0x200) ccd->offset = buf[4];
ccd->offset |= 0xfc00; ccd->offset = sign_extend32(ccd->offset, 9);
return cpcap_battery_cc_to_uah(ddata, return cpcap_battery_cc_to_uah(ddata,
ccd->sample, ccd->sample,
...@@ -477,11 +476,11 @@ static int cpcap_battery_get_property(struct power_supply *psy, ...@@ -477,11 +476,11 @@ static int cpcap_battery_get_property(struct power_supply *psy,
val->intval = ddata->config.info.voltage_min_design; val->intval = ddata->config.info.voltage_min_design;
break; break;
case POWER_SUPPLY_PROP_CURRENT_AVG: 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); val->intval = cpcap_battery_cc_get_avg_current(ddata);
break; break;
} }
sample = latest->cc.sample - previous->cc.sample;
accumulator = latest->cc.accumulator - previous->cc.accumulator; accumulator = latest->cc.accumulator - previous->cc.accumulator;
val->intval = cpcap_battery_cc_to_ua(ddata, sample, val->intval = cpcap_battery_cc_to_ua(ddata, sample,
accumulator, accumulator,
...@@ -498,13 +497,13 @@ static int cpcap_battery_get_property(struct power_supply *psy, ...@@ -498,13 +497,13 @@ static int cpcap_battery_get_property(struct power_supply *psy,
val->intval = div64_s64(tmp, 100); val->intval = div64_s64(tmp, 100);
break; break;
case POWER_SUPPLY_PROP_POWER_AVG: 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 = cpcap_battery_cc_get_avg_current(ddata);
tmp *= (latest->voltage / 10000); tmp *= (latest->voltage / 10000);
val->intval = div64_s64(tmp, 100); val->intval = div64_s64(tmp, 100);
break; break;
} }
sample = latest->cc.sample - previous->cc.sample;
accumulator = latest->cc.accumulator - previous->cc.accumulator; accumulator = latest->cc.accumulator - previous->cc.accumulator;
tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator, tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator,
latest->cc.offset); latest->cc.offset);
...@@ -562,11 +561,11 @@ static irqreturn_t cpcap_battery_irq_thread(int irq, void *data) ...@@ -562,11 +561,11 @@ static irqreturn_t cpcap_battery_irq_thread(int irq, void *data)
switch (d->action) { switch (d->action) {
case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW: 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"); dev_warn(ddata->dev, "Battery low at 3.3V!\n");
break; break;
case CPCAP_BATTERY_IRQ_ACTION_POWEROFF: case CPCAP_BATTERY_IRQ_ACTION_POWEROFF:
if (latest->counter_uah >= 0) { if (latest->current_ua >= 0) {
dev_emerg(ddata->dev, dev_emerg(ddata->dev,
"Battery empty at 3.1V, powering off\n"); "Battery empty at 3.1V, powering off\n");
orderly_poweroff(true); orderly_poweroff(true);
...@@ -670,8 +669,9 @@ static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata) ...@@ -670,8 +669,9 @@ static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata)
return 0; return 0;
out_err: out_err:
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", if (error != -EPROBE_DEFER)
error); dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
error);
return error; return error;
} }
......
...@@ -574,8 +574,9 @@ static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata) ...@@ -574,8 +574,9 @@ static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata)
return 0; return 0;
out_err: out_err:
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", if (error != -EPROBE_DEFER)
error); dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
error);
return error; return error;
} }
......
...@@ -29,11 +29,13 @@ ...@@ -29,11 +29,13 @@
struct gpio_charger { struct gpio_charger {
unsigned int irq; unsigned int irq;
unsigned int charge_status_irq;
bool wakeup_enabled; bool wakeup_enabled;
struct power_supply *charger; struct power_supply *charger;
struct power_supply_desc charger_desc; struct power_supply_desc charger_desc;
struct gpio_desc *gpiod; struct gpio_desc *gpiod;
struct gpio_desc *charge_status;
}; };
static irqreturn_t gpio_charger_irq(int irq, void *devid) static irqreturn_t gpio_charger_irq(int irq, void *devid)
...@@ -59,6 +61,12 @@ static int gpio_charger_get_property(struct power_supply *psy, ...@@ -59,6 +61,12 @@ static int gpio_charger_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_ONLINE:
val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod); val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod);
break; 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: default:
return -EINVAL; return -EINVAL;
} }
...@@ -93,8 +101,29 @@ static enum power_supply_type gpio_charger_get_type(struct device *dev) ...@@ -93,8 +101,29 @@ static enum power_supply_type gpio_charger_get_type(struct device *dev)
return POWER_SUPPLY_TYPE_UNKNOWN; 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[] = { static enum power_supply_property gpio_charger_properties[] = {
POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_STATUS /* Must always be last in the array. */
}; };
static int gpio_charger_probe(struct platform_device *pdev) static int gpio_charger_probe(struct platform_device *pdev)
...@@ -104,8 +133,10 @@ 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 power_supply_config psy_cfg = {};
struct gpio_charger *gpio_charger; struct gpio_charger *gpio_charger;
struct power_supply_desc *charger_desc; struct power_supply_desc *charger_desc;
struct gpio_desc *charge_status;
int charge_status_irq;
unsigned long flags; unsigned long flags;
int irq, ret; int ret;
if (!pdata && !dev->of_node) { if (!pdata && !dev->of_node) {
dev_err(dev, "No platform data\n"); dev_err(dev, "No platform data\n");
...@@ -151,9 +182,17 @@ static int gpio_charger_probe(struct platform_device *pdev) ...@@ -151,9 +182,17 @@ static int gpio_charger_probe(struct platform_device *pdev)
return PTR_ERR(gpio_charger->gpiod); 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 = &gpio_charger->charger_desc;
charger_desc->properties = gpio_charger_properties; charger_desc->properties = gpio_charger_properties;
charger_desc->num_properties = ARRAY_SIZE(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; charger_desc->get_property = gpio_charger_get_property;
psy_cfg.of_node = dev->of_node; psy_cfg.of_node = dev->of_node;
...@@ -180,16 +219,12 @@ static int gpio_charger_probe(struct platform_device *pdev) ...@@ -180,16 +219,12 @@ static int gpio_charger_probe(struct platform_device *pdev)
return ret; return ret;
} }
irq = gpiod_to_irq(gpio_charger->gpiod); gpio_charger->irq = gpio_charger_get_irq(dev, gpio_charger->charger,
if (irq > 0) { gpio_charger->gpiod);
ret = devm_request_any_context_irq(dev, irq, gpio_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, charge_status_irq = gpio_charger_get_irq(dev, gpio_charger->charger,
dev_name(dev), gpio_charger->charger); gpio_charger->charge_status);
if (ret < 0) gpio_charger->charge_status_irq = charge_status_irq;
dev_warn(dev, "Failed to request irq: %d\n", ret);
else
gpio_charger->irq = irq;
}
platform_set_drvdata(pdev, gpio_charger); platform_set_drvdata(pdev, gpio_charger);
......
// 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");
// SPDX-License-Identifier: GPL-2.0+
/* /*
* Driver for Analog Devices (Linear Technology) LT3651 charger IC.
* Copyright (C) 2017, Topic Embedded Products * 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> #include <linux/device.h>
...@@ -19,7 +15,7 @@ ...@@ -19,7 +15,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/of.h> #include <linux/of.h>
struct ltc3651_charger { struct lt3651_charger {
struct power_supply *charger; struct power_supply *charger;
struct power_supply_desc charger_desc; struct power_supply_desc charger_desc;
struct gpio_desc *acpr_gpio; struct gpio_desc *acpr_gpio;
...@@ -27,7 +23,7 @@ struct ltc3651_charger { ...@@ -27,7 +23,7 @@ struct ltc3651_charger {
struct gpio_desc *chrg_gpio; 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; struct power_supply *charger = devid;
...@@ -36,37 +32,37 @@ static irqreturn_t ltc3651_charger_irq(int irq, void *devid) ...@@ -36,37 +32,37 @@ static irqreturn_t ltc3651_charger_irq(int irq, void *devid)
return IRQ_HANDLED; 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) struct power_supply *psy)
{ {
return power_supply_get_drvdata(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) 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) { switch (psp) {
case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_STATUS:
if (!ltc3651_charger->chrg_gpio) { if (!lt3651_charger->chrg_gpio) {
val->intval = POWER_SUPPLY_STATUS_UNKNOWN; val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break; break;
} }
if (gpiod_get_value(ltc3651_charger->chrg_gpio)) if (gpiod_get_value(lt3651_charger->chrg_gpio))
val->intval = POWER_SUPPLY_STATUS_CHARGING; val->intval = POWER_SUPPLY_STATUS_CHARGING;
else else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break; break;
case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_ONLINE:
val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio); val->intval = gpiod_get_value(lt3651_charger->acpr_gpio);
break; break;
case POWER_SUPPLY_PROP_HEALTH: case POWER_SUPPLY_PROP_HEALTH:
if (!ltc3651_charger->fault_gpio) { if (!lt3651_charger->fault_gpio) {
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
break; break;
} }
if (!gpiod_get_value(ltc3651_charger->fault_gpio)) { if (!gpiod_get_value(lt3651_charger->fault_gpio)) {
val->intval = POWER_SUPPLY_HEALTH_GOOD; val->intval = POWER_SUPPLY_HEALTH_GOOD;
break; break;
} }
...@@ -74,11 +70,11 @@ static int ltc3651_charger_get_property(struct power_supply *psy, ...@@ -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 * If the fault pin is active, the chrg pin explains the type
* of failure. * of failure.
*/ */
if (!ltc3651_charger->chrg_gpio) { if (!lt3651_charger->chrg_gpio) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break; 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_OVERHEAT :
POWER_SUPPLY_HEALTH_DEAD; POWER_SUPPLY_HEALTH_DEAD;
break; break;
...@@ -89,59 +85,59 @@ static int ltc3651_charger_get_property(struct power_supply *psy, ...@@ -89,59 +85,59 @@ static int ltc3651_charger_get_property(struct power_supply *psy,
return 0; 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_STATUS,
POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH, 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 power_supply_config psy_cfg = {};
struct ltc3651_charger *ltc3651_charger; struct lt3651_charger *lt3651_charger;
struct power_supply_desc *charger_desc; struct power_supply_desc *charger_desc;
int ret; int ret;
ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger), lt3651_charger = devm_kzalloc(&pdev->dev, sizeof(*lt3651_charger),
GFP_KERNEL); GFP_KERNEL);
if (!ltc3651_charger) if (!lt3651_charger)
return -ENOMEM; return -ENOMEM;
ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, lt3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev,
"lltc,acpr", GPIOD_IN); "lltc,acpr", GPIOD_IN);
if (IS_ERR(ltc3651_charger->acpr_gpio)) { if (IS_ERR(lt3651_charger->acpr_gpio)) {
ret = PTR_ERR(ltc3651_charger->acpr_gpio); ret = PTR_ERR(lt3651_charger->acpr_gpio);
dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret); dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret);
return 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); "lltc,fault", GPIOD_IN);
if (IS_ERR(ltc3651_charger->fault_gpio)) { if (IS_ERR(lt3651_charger->fault_gpio)) {
ret = PTR_ERR(ltc3651_charger->fault_gpio); ret = PTR_ERR(lt3651_charger->fault_gpio);
dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret); dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret);
return 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); "lltc,chrg", GPIOD_IN);
if (IS_ERR(ltc3651_charger->chrg_gpio)) { if (IS_ERR(lt3651_charger->chrg_gpio)) {
ret = PTR_ERR(ltc3651_charger->chrg_gpio); ret = PTR_ERR(lt3651_charger->chrg_gpio);
dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret); dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret);
return 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->name = pdev->dev.of_node->name;
charger_desc->type = POWER_SUPPLY_TYPE_MAINS; charger_desc->type = POWER_SUPPLY_TYPE_MAINS;
charger_desc->properties = ltc3651_charger_properties; charger_desc->properties = lt3651_charger_properties;
charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties); charger_desc->num_properties = ARRAY_SIZE(lt3651_charger_properties);
charger_desc->get_property = ltc3651_charger_get_property; charger_desc->get_property = lt3651_charger_get_property;
psy_cfg.of_node = pdev->dev.of_node; 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); charger_desc, &psy_cfg);
if (IS_ERR(ltc3651_charger->charger)) { if (IS_ERR(lt3651_charger->charger)) {
ret = PTR_ERR(ltc3651_charger->charger); ret = PTR_ERR(lt3651_charger->charger);
dev_err(&pdev->dev, "Failed to register power supply: %d\n", dev_err(&pdev->dev, "Failed to register power supply: %d\n",
ret); ret);
return ret; return ret;
...@@ -152,59 +148,60 @@ static int ltc3651_charger_probe(struct platform_device *pdev) ...@@ -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 * support IRQs on these pins, userspace will have to poll the sysfs
* files manually. * files manually.
*/ */
if (ltc3651_charger->acpr_gpio) { if (lt3651_charger->acpr_gpio) {
ret = gpiod_to_irq(ltc3651_charger->acpr_gpio); ret = gpiod_to_irq(lt3651_charger->acpr_gpio);
if (ret >= 0) if (ret >= 0)
ret = devm_request_any_context_irq(&pdev->dev, ret, ret = devm_request_any_context_irq(&pdev->dev, ret,
ltc3651_charger_irq, lt3651_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), ltc3651_charger->charger); dev_name(&pdev->dev), lt3651_charger->charger);
if (ret < 0) if (ret < 0)
dev_warn(&pdev->dev, "Failed to request acpr irq\n"); dev_warn(&pdev->dev, "Failed to request acpr irq\n");
} }
if (ltc3651_charger->fault_gpio) { if (lt3651_charger->fault_gpio) {
ret = gpiod_to_irq(ltc3651_charger->fault_gpio); ret = gpiod_to_irq(lt3651_charger->fault_gpio);
if (ret >= 0) if (ret >= 0)
ret = devm_request_any_context_irq(&pdev->dev, ret, ret = devm_request_any_context_irq(&pdev->dev, ret,
ltc3651_charger_irq, lt3651_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), ltc3651_charger->charger); dev_name(&pdev->dev), lt3651_charger->charger);
if (ret < 0) if (ret < 0)
dev_warn(&pdev->dev, "Failed to request fault irq\n"); dev_warn(&pdev->dev, "Failed to request fault irq\n");
} }
if (ltc3651_charger->chrg_gpio) { if (lt3651_charger->chrg_gpio) {
ret = gpiod_to_irq(ltc3651_charger->chrg_gpio); ret = gpiod_to_irq(lt3651_charger->chrg_gpio);
if (ret >= 0) if (ret >= 0)
ret = devm_request_any_context_irq(&pdev->dev, ret, ret = devm_request_any_context_irq(&pdev->dev, ret,
ltc3651_charger_irq, lt3651_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), ltc3651_charger->charger); dev_name(&pdev->dev), lt3651_charger->charger);
if (ret < 0) if (ret < 0)
dev_warn(&pdev->dev, "Failed to request chrg irq\n"); dev_warn(&pdev->dev, "Failed to request chrg irq\n");
} }
platform_set_drvdata(pdev, ltc3651_charger); platform_set_drvdata(pdev, lt3651_charger);
return 0; return 0;
} }
static const struct of_device_id ltc3651_charger_match[] = { static const struct of_device_id lt3651_charger_match[] = {
{ .compatible = "lltc,ltc3651-charger" }, { .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 = { static struct platform_driver lt3651_charger_driver = {
.probe = ltc3651_charger_probe, .probe = lt3651_charger_probe,
.driver = { .driver = {
.name = "ltc3651-charger", .name = "lt3651-charger",
.of_match_table = ltc3651_charger_match, .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_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
MODULE_DESCRIPTION("Driver for LTC3651 charger"); MODULE_DESCRIPTION("Driver for LT3651 charger");
MODULE_LICENSE("GPL"); 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[] = { ...@@ -240,6 +240,14 @@ static enum power_supply_property max14656_battery_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER, 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, static int max14656_probe(struct i2c_client *client,
const struct i2c_device_id *id) const struct i2c_device_id *id)
{ {
...@@ -278,7 +286,19 @@ static int max14656_probe(struct i2c_client *client, ...@@ -278,7 +286,19 @@ static int max14656_probe(struct i2c_client *client,
if (ret) if (ret)
return -ENODEV; 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); 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, ret = devm_request_irq(dev, chip->irq, max14656_irq,
IRQF_TRIGGER_FALLING, IRQF_TRIGGER_FALLING,
...@@ -289,13 +309,6 @@ static int max14656_probe(struct i2c_client *client, ...@@ -289,13 +309,6 @@ static int max14656_probe(struct i2c_client *client,
} }
enable_irq_wake(chip->irq); 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)); schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000));
return 0; return 0;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/of.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/power_supply.h> #include <linux/power_supply.h>
#include <linux/jiffies.h> #include <linux/jiffies.h>
...@@ -52,6 +53,14 @@ ...@@ -52,6 +53,14 @@
#define BAT_ADDR_MFR_TYPE 0x5F #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 * Power
*********************************************************************/ *********************************************************************/
...@@ -90,13 +99,10 @@ static const struct power_supply_desc olpc_ac_desc = { ...@@ -90,13 +99,10 @@ static const struct power_supply_desc olpc_ac_desc = {
.get_property = olpc_ac_get_prop, .get_property = olpc_ac_get_prop,
}; };
static struct power_supply *olpc_ac; static int olpc_bat_get_status(struct olpc_battery_data *data,
union power_supply_propval *val, uint8_t ec_byte)
static char bat_serial[17]; /* Ick */
static int olpc_bat_get_status(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)) if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE))
val->intval = POWER_SUPPLY_STATUS_CHARGING; val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (ec_byte & BAT_STAT_DISCHARGING) else if (ec_byte & BAT_STAT_DISCHARGING)
...@@ -318,6 +324,14 @@ static int olpc_bat_get_voltage_max_design(union power_supply_propval *val) ...@@ -318,6 +324,14 @@ static int olpc_bat_get_voltage_max_design(union power_supply_propval *val)
return ret; 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 * Battery properties
*********************************************************************/ *********************************************************************/
...@@ -325,8 +339,9 @@ static int olpc_bat_get_property(struct power_supply *psy, ...@@ -325,8 +339,9 @@ static int olpc_bat_get_property(struct power_supply *psy,
enum power_supply_property psp, enum power_supply_property psp,
union power_supply_propval *val) union power_supply_propval *val)
{ {
struct olpc_battery_data *data = power_supply_get_drvdata(psy);
int ret = 0; int ret = 0;
__be16 ec_word; u16 ec_word;
uint8_t ec_byte; uint8_t ec_byte;
__be64 ser_buf; __be64 ser_buf;
...@@ -346,7 +361,7 @@ static int olpc_bat_get_property(struct power_supply *psy, ...@@ -346,7 +361,7 @@ static int olpc_bat_get_property(struct power_supply *psy,
switch (psp) { switch (psp) {
case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_STATUS:
ret = olpc_bat_get_status(val, ec_byte); ret = olpc_bat_get_status(data, val, ec_byte);
if (ret) if (ret)
return ret; return ret;
break; break;
...@@ -389,7 +404,7 @@ static int olpc_bat_get_property(struct power_supply *psy, ...@@ -389,7 +404,7 @@ static int olpc_bat_get_property(struct power_supply *psy,
if (ret) if (ret)
return ret; return ret;
val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; val->intval = ecword_to_cpu(data, ec_word) * 9760L / 32;
break; break;
case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_CURRENT_AVG:
case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_NOW:
...@@ -397,7 +412,7 @@ static int olpc_bat_get_property(struct power_supply *psy, ...@@ -397,7 +412,7 @@ static int olpc_bat_get_property(struct power_supply *psy,
if (ret) if (ret)
return ret; return ret;
val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; val->intval = ecword_to_cpu(data, ec_word) * 15625L / 120;
break; break;
case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_CAPACITY:
ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); 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, ...@@ -428,29 +443,29 @@ static int olpc_bat_get_property(struct power_supply *psy,
if (ret) if (ret)
return ret; return ret;
val->intval = (s16)be16_to_cpu(ec_word) * 10 / 256; val->intval = ecword_to_cpu(data, ec_word) * 10 / 256;
break; break;
case POWER_SUPPLY_PROP_TEMP_AMBIENT: case POWER_SUPPLY_PROP_TEMP_AMBIENT:
ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
if (ret) if (ret)
return 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; break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER: case POWER_SUPPLY_PROP_CHARGE_COUNTER:
ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
if (ret) if (ret)
return ret; return ret;
val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; val->intval = ecword_to_cpu(data, ec_word) * 6250 / 15;
break; break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER: case POWER_SUPPLY_PROP_SERIAL_NUMBER:
ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8);
if (ret) if (ret)
return ret; return ret;
sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); sprintf(data->bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
val->strval = bat_serial; val->strval = data->bat_serial;
break; break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
ret = olpc_bat_get_voltage_max_design(val); 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, ...@@ -536,7 +551,7 @@ static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
return count; return count;
} }
static const struct bin_attribute olpc_bat_eeprom = { static struct bin_attribute olpc_bat_eeprom = {
.attr = { .attr = {
.name = "eeprom", .name = "eeprom",
.mode = S_IRUGO, .mode = S_IRUGO,
...@@ -560,7 +575,7 @@ static ssize_t olpc_bat_error_read(struct device *dev, ...@@ -560,7 +575,7 @@ static ssize_t olpc_bat_error_read(struct device *dev,
return sprintf(buf, "%d\n", ec_byte); return sprintf(buf, "%d\n", ec_byte);
} }
static const struct device_attribute olpc_bat_error = { static struct device_attribute olpc_bat_error = {
.attr = { .attr = {
.name = "error", .name = "error",
.mode = S_IRUGO, .mode = S_IRUGO,
...@@ -568,6 +583,27 @@ static const struct device_attribute olpc_bat_error = { ...@@ -568,6 +583,27 @@ static const struct device_attribute olpc_bat_error = {
.show = olpc_bat_error_read, .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 * Initialisation
*********************************************************************/ *********************************************************************/
...@@ -578,17 +614,17 @@ static struct power_supply_desc olpc_bat_desc = { ...@@ -578,17 +614,17 @@ static struct power_supply_desc olpc_bat_desc = {
.use_for_apm = 1, .use_for_apm = 1,
}; };
static struct power_supply *olpc_bat;
static int olpc_battery_suspend(struct platform_device *pdev, static int olpc_battery_suspend(struct platform_device *pdev,
pm_message_t state) 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); olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
else else
olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR); 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 olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
| EC_SCI_SRC_BATERR); | EC_SCI_SRC_BATERR);
else else
...@@ -600,16 +636,37 @@ static int olpc_battery_suspend(struct platform_device *pdev, ...@@ -600,16 +636,37 @@ static int olpc_battery_suspend(struct platform_device *pdev,
static int olpc_battery_probe(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 status;
uint8_t ecver;
int ret;
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 */
* We've seen a number of EC protocol changes; this driver requires ret = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ecver, 1);
* the latest EC protocol, supported by 0x44 and above. if (ret)
*/ return ret;
if (olpc_platform_info.ecver < 0x44) {
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 " printk(KERN_NOTICE "OLPC EC version 0x%02x too old for "
"battery driver.\n", olpc_platform_info.ecver); "battery driver.\n", ecver);
return -ENXIO; return -ENXIO;
} }
...@@ -619,59 +676,44 @@ static int olpc_battery_probe(struct platform_device *pdev) ...@@ -619,59 +676,44 @@ static int olpc_battery_probe(struct platform_device *pdev)
/* Ignore the status. It doesn't actually matter */ /* Ignore the status. It doesn't actually matter */
olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL); ac_psy_cfg.of_node = pdev->dev.of_node;
if (IS_ERR(olpc_ac)) ac_psy_cfg.drv_data = data;
return PTR_ERR(olpc_ac);
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 (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */ 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.properties = olpc_xo15_bat_props;
olpc_bat_desc.num_properties = ARRAY_SIZE(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.properties = olpc_xo1_bat_props;
olpc_bat_desc.num_properties = ARRAY_SIZE(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); bat_psy_cfg.of_node = pdev->dev.of_node;
if (IS_ERR(olpc_bat)) { bat_psy_cfg.drv_data = data;
ret = PTR_ERR(olpc_bat); bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups;
goto battery_failed;
}
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); data->olpc_bat = devm_power_supply_register(&pdev->dev, &olpc_bat_desc,
if (ret) &bat_psy_cfg);
goto error_failed; if (IS_ERR(data->olpc_bat))
return PTR_ERR(data->olpc_bat);
if (olpc_ec_wakeup_available()) { if (olpc_ec_wakeup_available()) {
device_set_wakeup_capable(&olpc_ac->dev, true); device_set_wakeup_capable(&data->olpc_ac->dev, true);
device_set_wakeup_capable(&olpc_bat->dev, true); device_set_wakeup_capable(&data->olpc_bat->dev, true);
} }
return 0; 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[] = { static const struct of_device_id olpc_battery_ids[] = {
{ .compatible = "olpc,xo1-battery" }, { .compatible = "olpc,xo1-battery" },
{ .compatible = "olpc,xo1.5-battery" },
{} {}
}; };
MODULE_DEVICE_TABLE(of, olpc_battery_ids); MODULE_DEVICE_TABLE(of, olpc_battery_ids);
...@@ -682,7 +724,6 @@ static struct platform_driver olpc_battery_driver = { ...@@ -682,7 +724,6 @@ static struct platform_driver olpc_battery_driver = {
.of_match_table = olpc_battery_ids, .of_match_table = olpc_battery_ids,
}, },
.probe = olpc_battery_probe, .probe = olpc_battery_probe,
.remove = olpc_battery_remove,
.suspend = olpc_battery_suspend, .suspend = olpc_battery_suspend,
}; };
......
...@@ -598,10 +598,12 @@ int power_supply_get_battery_info(struct power_supply *psy, ...@@ -598,10 +598,12 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = of_property_read_string(battery_np, "compatible", &value); err = of_property_read_string(battery_np, "compatible", &value);
if (err) if (err)
return err; goto out_put_node;
if (strcmp("simple-battery", value)) if (strcmp("simple-battery", value)) {
return -ENODEV; err = -ENODEV;
goto out_put_node;
}
/* The property and field names below must correspond to elements /* The property and field names below must correspond to elements
* in enum power_supply_property. For reasoning, see * in enum power_supply_property. For reasoning, see
...@@ -620,19 +622,21 @@ int power_supply_get_battery_info(struct power_supply *psy, ...@@ -620,19 +622,21 @@ int power_supply_get_battery_info(struct power_supply *psy,
&info->precharge_current_ua); &info->precharge_current_ua);
of_property_read_u32(battery_np, "charge-term-current-microamp", of_property_read_u32(battery_np, "charge-term-current-microamp",
&info->charge_term_current_ua); &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); &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); &info->constant_charge_voltage_max_uv);
of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms", of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
&info->factory_internal_resistance_uohm); &info->factory_internal_resistance_uohm);
len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
if (len < 0 && len != -EINVAL) { if (len < 0 && len != -EINVAL) {
return len; err = len;
goto out_put_node;
} else if (len > POWER_SUPPLY_OCV_TEMP_MAX) { } else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
dev_err(&psy->dev, "Too many temperature values\n"); dev_err(&psy->dev, "Too many temperature values\n");
return -EINVAL; err = -EINVAL;
goto out_put_node;
} else if (len > 0) { } else if (len > 0) {
of_property_read_u32_array(battery_np, "ocv-capacity-celsius", of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
info->ocv_temp, len); info->ocv_temp, len);
...@@ -650,7 +654,8 @@ int power_supply_get_battery_info(struct power_supply *psy, ...@@ -650,7 +654,8 @@ int power_supply_get_battery_info(struct power_supply *psy,
dev_err(&psy->dev, "failed to get %s\n", propname); dev_err(&psy->dev, "failed to get %s\n", propname);
kfree(propname); kfree(propname);
power_supply_put_battery_info(psy, info); power_supply_put_battery_info(psy, info);
return -EINVAL; err = -EINVAL;
goto out_put_node;
} }
kfree(propname); kfree(propname);
...@@ -661,16 +666,21 @@ int power_supply_get_battery_info(struct power_supply *psy, ...@@ -661,16 +666,21 @@ int power_supply_get_battery_info(struct power_supply *psy,
devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL); devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
if (!info->ocv_table[index]) { if (!info->ocv_table[index]) {
power_supply_put_battery_info(psy, info); power_supply_put_battery_info(psy, info);
return -ENOMEM; err = -ENOMEM;
goto out_put_node;
} }
for (i = 0; i < tab_len; i++) { for (i = 0; i < tab_len; i++) {
table[i].ocv = be32_to_cpu(*list++); table[i].ocv = be32_to_cpu(*list);
table[i].capacity = 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); 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, ...@@ -899,7 +909,7 @@ static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd,
return ret; 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) unsigned long *state)
{ {
struct power_supply *psy; struct power_supply *psy;
...@@ -934,7 +944,7 @@ static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, ...@@ -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 = { static const struct thermal_cooling_device_ops psy_tcd_ops = {
.get_max_state = ps_get_max_charge_cntl_limit, .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, .set_cur_state = ps_set_cur_charge_cntl_limit,
}; };
......
...@@ -56,13 +56,13 @@ static const char * const power_supply_status_text[] = { ...@@ -56,13 +56,13 @@ static const char * const power_supply_status_text[] = {
}; };
static const char * const power_supply_charge_type_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[] = { static const char * const power_supply_health_text[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage", "Unknown", "Good", "Overheat", "Dead", "Over voltage",
"Unspecified failure", "Cold", "Watchdog timer expire", "Unspecified failure", "Cold", "Watchdog timer expire",
"Safety timer expire" "Safety timer expire", "Over current"
}; };
static const char * const power_supply_technology_text[] = { static const char * const power_supply_technology_text[] = {
...@@ -274,6 +274,8 @@ static struct device_attribute power_supply_attrs[] = { ...@@ -274,6 +274,8 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(constant_charge_voltage_max), POWER_SUPPLY_ATTR(constant_charge_voltage_max),
POWER_SUPPLY_ATTR(charge_control_limit), POWER_SUPPLY_ATTR(charge_control_limit),
POWER_SUPPLY_ATTR(charge_control_limit_max), 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(input_current_limit),
POWER_SUPPLY_ATTR(energy_full_design), POWER_SUPPLY_ATTR(energy_full_design),
POWER_SUPPLY_ATTR(energy_empty_design), POWER_SUPPLY_ATTR(energy_empty_design),
......
// 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 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,
&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");
...@@ -290,6 +290,20 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val); ...@@ -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, int iio_read_avail_channel_raw(struct iio_channel *chan,
const int **vals, int *length); 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 * iio_get_channel_type() - get the type of a channel
* @channel: The channel being queried. * @channel: The channel being queried.
......
...@@ -40,11 +40,15 @@ enum { ...@@ -40,11 +40,15 @@ enum {
POWER_SUPPLY_STATUS_FULL, POWER_SUPPLY_STATUS_FULL,
}; };
/* What algorithm is the charger using? */
enum { enum {
POWER_SUPPLY_CHARGE_TYPE_UNKNOWN = 0, POWER_SUPPLY_CHARGE_TYPE_UNKNOWN = 0,
POWER_SUPPLY_CHARGE_TYPE_NONE, POWER_SUPPLY_CHARGE_TYPE_NONE,
POWER_SUPPLY_CHARGE_TYPE_TRICKLE, POWER_SUPPLY_CHARGE_TYPE_TRICKLE, /* slow speed */
POWER_SUPPLY_CHARGE_TYPE_FAST, 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 { enum {
...@@ -57,6 +61,7 @@ enum { ...@@ -57,6 +61,7 @@ enum {
POWER_SUPPLY_HEALTH_COLD, POWER_SUPPLY_HEALTH_COLD,
POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE, POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE,
POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE,
POWER_SUPPLY_HEALTH_OVERCURRENT,
}; };
enum { enum {
...@@ -121,6 +126,8 @@ enum power_supply_property { ...@@ -121,6 +126,8 @@ enum power_supply_property {
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, 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_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN, POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment