power supply and reset changes for the v5.15 series

battery/charger related changes:
  - cros-peripheral-charger: new driver
  - mt6360-charger: new driver
  - simple-battery: support reading chemistry info
  - max17042-battery: add max77849 support
  - sbs-battery: add time_to_empty_now support
  - smb347-charger: prepare USB OTG support
  - rn5t618: add voltage_now support
  - axp288: cleanup & optimizations
  - max17042_battery: cleanups
  - ab8500: cleanups
  - misc minor cleanups and DT binding fixes
 
 reset related changes:
  - tps65086-restart: new driver
  - linkstation-poweroff: support NETGEAR ReadyNAS Duo v2
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmEmUVwACgkQ2O7X88g7
 +poFbA//XimqwjO0MR7xqmm2905l78L3L1cNn7vRPzfdPbcf/kKPg8Jrx8kTn1EK
 wKdbP4ZQJOIyCLIFcI6oURUaNHh485KXj4DFvT13AsbkPw+2xUv0Ha5p8J698QAG
 RPmkTNHk+0M/K+/Z7/GPb6t0B7uQi3cg7/aIZeFo26FYpIP5XekoxP1xoLfE9lO2
 aSrSbDh8oIjOLFPc4nuzm0x4Bcg/MpbUG1nhTBdP8OD8xjuMSmDUnbEvLgQYv4oP
 9PLbi4jxm0VSfFhdECCSZ+x7CO0+wqxLXWCoOGlzDQ1Y2OYp9nG+Xgsj46HGh38c
 11WER/16J7AfzUg1zqfu7NKDOKWad+TsTvQgXVK4GAxDOxpOS8Hz7GuP0/nnZBLx
 PoDAb7ZBtb6QXJDEvCDWoo+yMcZRaULbefQCgR/ys6bWoL+B6wdowxfV/daFGpmx
 fAMDGoSGrmYJhbPkcfAVJrN196zd5EQzbB6pyOfAPu3lJiDz+M/DyPNYwkljagAx
 JzSK80cwtXk07lgZZvC8Z3MJruN5pqqvWX/TA8l7dHpReoguCM3hAPUJ9pdVoIa7
 haavL0UzLwya9C2sK9hcys8EBim5thkXI6GsnpRxiztkXZh0LtsUP9Dydt2srGA6
 Hl4BD/g23W9+zmjkAIAvgMwbBoZ2/SHkD7l3ZqG2N2j6LrQxpwc=
 =UcdD
 -----END PGP SIGNATURE-----

Merge tag 'for-v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply

Pull power supply and reset updates from Sebastian Reichel:
 "Battery/charger related:
   - cros-peripheral-charger: new driver
   - mt6360-charger: new driver
   - simple-battery: support reading chemistry info
   - max17042-battery: add max77849 support
   - sbs-battery: add time_to_empty_now support
   - smb347-charger: prepare USB OTG support
   - rn5t618: add voltage_now support
   - axp288: cleanup & optimizations
   - max17042_battery: cleanups
   - ab8500: cleanups
   - misc minor cleanups and DT binding fixes

  reset related:
   - tps65086-restart: new driver
   - linkstation-poweroff: support NETGEAR ReadyNAS Duo v2"

* tag 'for-v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (51 commits)
  power: supply: core: Fix parsing of battery chemistry/technology
  power: supply: max17042_battery: log SOC threshold using debug log level
  power: supply: max17042_battery: more robust chip type checks
  power: supply: max17042_battery: fix typo in MAx17042_TOFF
  power: supply: max17042_battery: clean up MAX17055_V_empty
  power: supply: smb347-charger: Implement USB VBUS regulator
  power: supply: smb347-charger: Add missing pin control activation
  power: supply: smb347-charger: Utilize generic regmap caching
  power: supply: smb347-charger: Make smb347_set_writable() IRQ-safe
  dt-bindings: power: supply: smb347-charger: Document USB VBUS regulator
  power: reset: Add TPS65086 restart driver
  dt-bindings: power: supply: max17042: describe interrupt
  power: supply: max17042: remove duplicated STATUS bit defines
  power: supply: max17042: handle fails of reading status register
  power: supply: core: Parse battery chemistry/technology
  dt-bindings: power: Extend battery bindings with chemistry
  power: reset: linkstation-poweroff: add new device
  power: reset: linkstation-poweroff: prepare for new devices
  power: supply: bq24735: reorganize ChargeOption command macros
  power: supply: rn5t618: Add voltage_now property
  ...
This commit is contained in:
Linus Torvalds 2021-08-30 11:47:32 -07:00
commit 4520dcbe0d
39 changed files with 2681 additions and 973 deletions

View File

@ -31,6 +31,20 @@ properties:
compatible:
const: simple-battery
device-chemistry:
description: This describes the chemical technology of the battery.
oneOf:
- const: nickel-cadmium
- const: nickel-metal-hydride
- const: lithium-ion
description: This is a blanket type for all lithium-ion batteries,
including those below. If possible, a precise compatible string
from below should be used, but sometimes it is unknown which specific
lithium ion battery is employed and this wide compatible can be used.
- const: lithium-ion-polymer
- const: lithium-ion-iron-phosphate
- const: lithium-ion-manganese-oxide
over-voltage-threshold-microvolt:
description: battery over-voltage limit

View File

@ -19,12 +19,15 @@ properties:
- maxim,max17047
- maxim,max17050
- maxim,max17055
- maxim,max77849-battery
reg:
maxItems: 1
interrupts:
maxItems: 1
description: |
The ALRT pin, an open-drain interrupt.
maxim,rsns-microohm:
$ref: /schemas/types.yaml#/definitions/uint32

View File

@ -0,0 +1,48 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/mt6360_charger.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Battery charger driver for MT6360 PMIC from MediaTek Integrated.
maintainers:
- Gene Chen <gene_chen@richtek.com>
description: |
This module is part of the MT6360 MFD device.
Provides Battery Charger, Boost for OTG devices and BC1.2 detection.
properties:
compatible:
const: mediatek,mt6360-chg
richtek,vinovp-microvolt:
description: Maximum CHGIN regulation voltage in uV.
enum: [ 5500000, 6500000, 11000000, 14500000 ]
usb-otg-vbus-regulator:
type: object
description: OTG boost regulator.
$ref: /schemas/regulator/regulator.yaml#
required:
- compatible
additionalProperties: false
examples:
- |
mt6360_charger: charger {
compatible = "mediatek,mt6360-chg";
richtek,vinovp-microvolt = <14500000>;
otg_vbus_regulator: usb-otg-vbus-regulator {
regulator-compatible = "usb-otg-vbus";
regulator-name = "usb-otg-vbus";
regulator-min-microvolt = <4425000>;
regulator-max-microvolt = <5825000>;
};
};
...

View File

@ -73,6 +73,26 @@ properties:
- 1 # SMB3XX_SOFT_TEMP_COMPENSATE_CURRENT Current compensation
- 2 # SMB3XX_SOFT_TEMP_COMPENSATE_VOLTAGE Voltage compensation
summit,inok-polarity:
description: |
Polarity of INOK signal indicating presence of external power supply.
$ref: /schemas/types.yaml#/definitions/uint32
enum:
- 0 # SMB3XX_SYSOK_INOK_ACTIVE_LOW
- 1 # SMB3XX_SYSOK_INOK_ACTIVE_HIGH
usb-vbus:
$ref: "../../regulator/regulator.yaml#"
type: object
properties:
summit,needs-inok-toggle:
type: boolean
description: INOK signal is fixed and polarity needs to be toggled
in order to enable/disable output mode.
unevaluatedProperties: false
allOf:
- if:
properties:
@ -134,6 +154,7 @@ examples:
reg = <0x7f>;
summit,enable-charge-control = <SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH>;
summit,inok-polarity = <SMB3XX_SYSOK_INOK_ACTIVE_LOW>;
summit,chip-temperature-threshold-celsius = <110>;
summit,mains-current-limit-microamp = <2000000>;
summit,usb-current-limit-microamp = <500000>;
@ -141,6 +162,15 @@ examples:
summit,enable-mains-charging;
monitored-battery = <&battery>;
usb-vbus {
regulator-name = "usb_vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
regulator-min-microamp = <750000>;
regulator-max-microamp = <750000>;
summit,needs-inok-toggle;
};
};
};

View File

@ -21,10 +21,13 @@ allOf:
properties:
compatible:
enum:
- x-powers,axp202-ac-power-supply
- x-powers,axp221-ac-power-supply
- x-powers,axp813-ac-power-supply
oneOf:
- const: x-powers,axp202-ac-power-supply
- const: x-powers,axp221-ac-power-supply
- items:
- const: x-powers,axp803-ac-power-supply
- const: x-powers,axp813-ac-power-supply
- const: x-powers,axp813-ac-power-supply
required:
- compatible

View File

@ -19,10 +19,14 @@ allOf:
properties:
compatible:
enum:
- x-powers,axp209-battery-power-supply
- x-powers,axp221-battery-power-supply
- x-powers,axp813-battery-power-supply
oneOf:
- const: x-powers,axp202-battery-power-supply
- const: x-powers,axp209-battery-power-supply
- const: x-powers,axp221-battery-power-supply
- items:
- const: x-powers,axp803-battery-power-supply
- const: x-powers,axp813-battery-power-supply
- const: x-powers,axp813-battery-power-supply
required:
- compatible

View File

@ -20,11 +20,15 @@ allOf:
properties:
compatible:
enum:
oneOf:
- enum:
- x-powers,axp202-usb-power-supply
- x-powers,axp221-usb-power-supply
- x-powers,axp223-usb-power-supply
- x-powers,axp813-usb-power-supply
- items:
- const: x-powers,axp803-usb-power-supply
- const: x-powers,axp813-usb-power-supply
required:

View File

@ -16,6 +16,8 @@
#include <linux/completion.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/driver.h>
#include <linux/iio/machine.h>
#include <linux/slab.h>
#define RN5T618_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(500))
@ -189,6 +191,19 @@ static const struct iio_chan_spec rn5t618_adc_iio_channels[] = {
RN5T618_ADC_CHANNEL(AIN0, IIO_VOLTAGE, "AIN0")
};
static struct iio_map rn5t618_maps[] = {
IIO_MAP("VADP", "rn5t618-power", "vadp"),
IIO_MAP("VUSB", "rn5t618-power", "vusb"),
{ /* sentinel */ }
};
static void unregister_map(void *data)
{
struct iio_dev *iio_dev = (struct iio_dev *) data;
iio_map_array_unregister(iio_dev);
}
static int rn5t618_adc_probe(struct platform_device *pdev)
{
int ret;
@ -239,6 +254,14 @@ static int rn5t618_adc_probe(struct platform_device *pdev)
return ret;
}
ret = iio_map_array_register(iio_dev, rn5t618_maps);
if (ret < 0)
return ret;
ret = devm_add_action_or_reset(adc->dev, unregister_map, iio_dev);
if (ret < 0)
return ret;
return devm_iio_device_register(adc->dev, iio_dev);
}

View File

@ -204,6 +204,12 @@ config POWER_RESET_ST
help
Reset support for STMicroelectronics boards.
config POWER_RESET_TPS65086
bool "TPS65086 restart driver"
depends on MFD_TPS65086
help
This driver adds support for resetting the TPS65086 PMIC on restart.
config POWER_RESET_VERSATILE
bool "ARM Versatile family reboot driver"
depends on ARM

View File

@ -23,6 +23,7 @@ obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_REGULATOR) += regulator-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
obj-$(CONFIG_POWER_RESET_TPS65086) += tps65086-restart.o
obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o
obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o

View File

@ -19,6 +19,7 @@
#define MII_MARVELL_PHY_PAGE 22
#define MII_PHY_LED_CTRL 16
#define MII_PHY_LED_POL_CTRL 17
#define MII_88E1318S_PHY_LED_TCR 18
#define MII_88E1318S_PHY_WOL_CTRL 16
#define MII_M1011_IEVENT 19
@ -29,11 +30,23 @@
#define LED2_FORCE_ON (0x8 << 8)
#define LEDMASK GENMASK(11,8)
static struct phy_device *phydev;
#define MII_88E1318S_PHY_LED_POL_LED2 BIT(4)
static void mvphy_reg_intn(u16 data)
struct power_off_cfg {
char *mdio_node_name;
void (*phy_set_reg)(bool restart);
};
static struct phy_device *phydev;
static const struct power_off_cfg *cfg;
static void linkstation_mvphy_reg_intn(bool restart)
{
int rc = 0, saved_page;
u16 data = 0;
if (restart)
data = MII_88E1318S_PHY_LED_TCR_FORCE_INT;
saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
if (saved_page < 0)
@ -66,11 +79,52 @@ err:
dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
}
static void readynas_mvphy_set_reg(bool restart)
{
int rc = 0, saved_page;
u16 data = 0;
if (restart)
data = MII_88E1318S_PHY_LED_POL_LED2;
saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
if (saved_page < 0)
goto err;
/* Set the LED[2].0 Polarity bit to the required state */
__phy_modify(phydev, MII_PHY_LED_POL_CTRL,
MII_88E1318S_PHY_LED_POL_LED2, data);
if (!data) {
/* If WOL was enabled and a magic packet was received before powering
* off, we won't be able to wake up by sending another magic packet.
* Clear WOL status.
*/
__phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE);
__phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
}
err:
rc = phy_restore_page(phydev, saved_page, rc);
if (rc < 0)
dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
}
static const struct power_off_cfg linkstation_power_off_cfg = {
.mdio_node_name = "mdio",
.phy_set_reg = linkstation_mvphy_reg_intn,
};
static const struct power_off_cfg readynas_power_off_cfg = {
.mdio_node_name = "mdio-bus",
.phy_set_reg = readynas_mvphy_set_reg,
};
static int linkstation_reboot_notifier(struct notifier_block *nb,
unsigned long action, void *unused)
{
if (action == SYS_RESTART)
mvphy_reg_intn(MII_88E1318S_PHY_LED_TCR_FORCE_INT);
cfg->phy_set_reg(true);
return NOTIFY_DONE;
}
@ -82,14 +136,21 @@ static struct notifier_block linkstation_reboot_nb = {
static void linkstation_poweroff(void)
{
unregister_reboot_notifier(&linkstation_reboot_nb);
mvphy_reg_intn(0);
cfg->phy_set_reg(false);
kernel_restart("Power off");
}
static const struct of_device_id ls_poweroff_of_match[] = {
{ .compatible = "buffalo,ls421d" },
{ .compatible = "buffalo,ls421de" },
{ .compatible = "buffalo,ls421d",
.data = &linkstation_power_off_cfg,
},
{ .compatible = "buffalo,ls421de",
.data = &linkstation_power_off_cfg,
},
{ .compatible = "netgear,readynas-duo-v2",
.data = &readynas_power_off_cfg,
},
{ },
};
@ -97,13 +158,17 @@ static int __init linkstation_poweroff_init(void)
{
struct mii_bus *bus;
struct device_node *dn;
const struct of_device_id *match;
dn = of_find_matching_node(NULL, ls_poweroff_of_match);
if (!dn)
return -ENODEV;
of_node_put(dn);
dn = of_find_node_by_name(NULL, "mdio");
match = of_match_node(ls_poweroff_of_match, dn);
cfg = match->data;
dn = of_find_node_by_name(NULL, cfg->mdio_node_name);
if (!dn)
return -ENODEV;

View File

@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Emil Renner Berthing
*/
#include <linux/mfd/tps65086.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
struct tps65086_restart {
struct notifier_block handler;
struct device *dev;
};
static int tps65086_restart_notify(struct notifier_block *this,
unsigned long mode, void *cmd)
{
struct tps65086_restart *tps65086_restart =
container_of(this, struct tps65086_restart, handler);
struct tps65086 *tps65086 = dev_get_drvdata(tps65086_restart->dev->parent);
int ret;
ret = regmap_write(tps65086->regmap, TPS65086_FORCESHUTDN, 1);
if (ret) {
dev_err(tps65086_restart->dev, "%s: error writing to tps65086 pmic: %d\n",
__func__, ret);
return NOTIFY_DONE;
}
/* give it a little time */
mdelay(200);
WARN_ON(1);
return NOTIFY_DONE;
}
static int tps65086_restart_probe(struct platform_device *pdev)
{
struct tps65086_restart *tps65086_restart;
int ret;
tps65086_restart = devm_kzalloc(&pdev->dev, sizeof(*tps65086_restart), GFP_KERNEL);
if (!tps65086_restart)
return -ENOMEM;
platform_set_drvdata(pdev, tps65086_restart);
tps65086_restart->handler.notifier_call = tps65086_restart_notify;
tps65086_restart->handler.priority = 192;
tps65086_restart->dev = &pdev->dev;
ret = register_restart_handler(&tps65086_restart->handler);
if (ret) {
dev_err(&pdev->dev, "%s: cannot register restart handler: %d\n",
__func__, ret);
return -ENODEV;
}
return 0;
}
static int tps65086_restart_remove(struct platform_device *pdev)
{
struct tps65086_restart *tps65086_restart = platform_get_drvdata(pdev);
int ret;
ret = unregister_restart_handler(&tps65086_restart->handler);
if (ret) {
dev_err(&pdev->dev, "%s: cannot unregister restart handler: %d\n",
__func__, ret);
return -ENODEV;
}
return 0;
}
static const struct platform_device_id tps65086_restart_id_table[] = {
{ "tps65086-reset", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, tps65086_restart_id_table);
static struct platform_driver tps65086_restart_driver = {
.driver = {
.name = "tps65086-restart",
},
.probe = tps65086_restart_probe,
.remove = tps65086_restart_remove,
.id_table = tps65086_restart_id_table,
};
module_platform_driver(tps65086_restart_driver);
MODULE_AUTHOR("Emil Renner Berthing <kernel@esmil.dk>");
MODULE_DESCRIPTION("TPS65086 restart driver");
MODULE_LICENSE("GPL v2");

View File

@ -358,7 +358,7 @@ config AXP288_CHARGER
config AXP288_FUEL_GAUGE
tristate "X-Powers AXP288 Fuel Gauge"
depends on MFD_AXP20X && IIO
depends on MFD_AXP20X && IIO && IOSF_MBI
help
Say yes here to have support for X-Power power management IC (PMIC)
Fuel Gauge. The device provides battery statistics and status
@ -577,6 +577,17 @@ config CHARGER_MP2629
Battery charger. This driver provides Battery charger power management
functions on the systems.
config CHARGER_MT6360
tristate "Mediatek MT6360 Charger Driver"
depends on MFD_MT6360
depends on REGULATOR
select LINEAR_RANGES
help
Say Y here to enable MT6360 Charger Part.
The device supports High-Accuracy Voltage/Current Regulation,
Average Input Current Regulation, Battery Temperature Sensing,
Over-Temperature Protection, DPDM Detection for BC1.2.
config CHARGER_QCOM_SMBB
tristate "Qualcomm Switch-Mode Battery Charger and Boost"
depends on MFD_SPMI_PMIC || COMPILE_TEST
@ -669,6 +680,7 @@ config CHARGER_BQ256XX
config CHARGER_SMB347
tristate "Summit Microelectronics SMB3XX Battery Charger"
depends on I2C
depends on REGULATOR
select REGMAP_I2C
help
Say Y to include support for Summit Microelectronics SMB345,
@ -736,6 +748,16 @@ config CHARGER_CROS_USBPD
what is connected to USB PD ports from the EC and converts
that into power_supply properties.
config CHARGER_CROS_PCHG
tristate "ChromeOS EC based peripheral charger"
depends on MFD_CROS_EC_DEV
default MFD_CROS_EC_DEV
help
Say Y here to enable ChromeOS EC based peripheral charge driver.
This driver gets various information about the devices connected to
the peripheral charge ports from the EC and converts that into
power_supply properties.
config CHARGER_SC2731
tristate "Spreadtrum SC2731 charger driver"
depends on MFD_SC27XX_PMIC || COMPILE_TEST
@ -782,6 +804,8 @@ config CHARGER_WILCO
config RN5T618_POWER
tristate "RN5T618 charger/fuel gauge support"
depends on MFD_RN5T618
depends on RN5T618_ADC
depends on IIO
help
Say Y here to have support for RN5T618 PMIC family fuel gauge and charger.
This driver can also be built as a module. If so, the module will be

View File

@ -60,7 +60,7 @@ obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
@ -78,6 +78,7 @@ obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o
obj-$(CONFIG_CHARGER_MT6360) += mt6360_charger.o
obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
@ -93,6 +94,7 @@ obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o
obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o

View File

@ -269,43 +269,43 @@ enum bup_vch_sel {
/*
* ADC for the battery thermistor.
* When using the ABx500_ADC_THERM_BATCTRL the battery ID resistor is combined
* When using the AB8500_ADC_THERM_BATCTRL the battery ID resistor is combined
* with a NTC resistor to both identify the battery and to measure its
* temperature. Different phone manufactures uses different techniques to both
* identify the battery and to read its temperature.
*/
enum abx500_adc_therm {
ABx500_ADC_THERM_BATCTRL,
ABx500_ADC_THERM_BATTEMP,
enum ab8500_adc_therm {
AB8500_ADC_THERM_BATCTRL,
AB8500_ADC_THERM_BATTEMP,
};
/**
* struct abx500_res_to_temp - defines one point in a temp to res curve. To
* struct ab8500_res_to_temp - defines one point in a temp to res curve. To
* be used in battery packs that combines the identification resistor with a
* NTC resistor.
* @temp: battery pack temperature in Celsius
* @resist: NTC resistor net total resistance
*/
struct abx500_res_to_temp {
struct ab8500_res_to_temp {
int temp;
int resist;
};
/**
* struct abx500_v_to_cap - Table for translating voltage to capacity
* struct ab8500_v_to_cap - Table for translating voltage to capacity
* @voltage: Voltage in mV
* @capacity: Capacity in percent
*/
struct abx500_v_to_cap {
struct ab8500_v_to_cap {
int voltage;
int capacity;
};
/* Forward declaration */
struct abx500_fg;
struct ab8500_fg;
/**
* struct abx500_fg_parameters - Fuel gauge algorithm parameters, in seconds
* struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds
* if not specified
* @recovery_sleep_timer: Time between measurements while recovering
* @recovery_total_time: Total recovery time
@ -333,7 +333,7 @@ struct abx500_fg;
* @pcut_max_restart: Max number of restarts
* @pcut_debounce_time: Sets battery debounce time
*/
struct abx500_fg_parameters {
struct ab8500_fg_parameters {
int recovery_sleep_timer;
int recovery_total_time;
int init_timer;
@ -357,13 +357,13 @@ struct abx500_fg_parameters {
};
/**
* struct abx500_charger_maximization - struct used by the board config.
* struct ab8500_charger_maximization - struct used by the board config.
* @use_maxi: Enable maximization for this battery type
* @maxi_chg_curr: Maximum charger current allowed
* @maxi_wait_cycles: cycles to wait before setting charger current
* @charger_curr_step delta between two charger current settings (mA)
*/
struct abx500_maxim_parameters {
struct ab8500_maxim_parameters {
bool ena_maxi;
int chg_curr;
int wait_cycles;
@ -371,7 +371,7 @@ struct abx500_maxim_parameters {
};
/**
* struct abx500_battery_type - different batteries supported
* struct ab8500_battery_type - different batteries supported
* @name: battery technology
* @resis_high: battery upper resistance limit
* @resis_low: battery lower resistance limit
@ -400,7 +400,7 @@ struct abx500_maxim_parameters {
* @n_batres_tbl_elements number of elements in the batres_tbl
* @batres_tbl battery internal resistance vs temperature table
*/
struct abx500_battery_type {
struct ab8500_battery_type {
int name;
int resis_high;
int resis_low;
@ -421,210 +421,13 @@ struct abx500_battery_type {
int low_high_vol_lvl;
int battery_resistance;
int n_temp_tbl_elements;
const struct abx500_res_to_temp *r_to_t_tbl;
const struct ab8500_res_to_temp *r_to_t_tbl;
int n_v_cap_tbl_elements;
const struct abx500_v_to_cap *v_to_cap_tbl;
const struct ab8500_v_to_cap *v_to_cap_tbl;
int n_batres_tbl_elements;
const struct batres_vs_temp *batres_tbl;
};
/**
* struct abx500_bm_capacity_levels - abx500 capacity level data
* @critical: critical capacity level in percent
* @low: low capacity level in percent
* @normal: normal capacity level in percent
* @high: high capacity level in percent
* @full: full capacity level in percent
*/
struct abx500_bm_capacity_levels {
int critical;
int low;
int normal;
int high;
int full;
};
/**
* struct abx500_bm_charger_parameters - Charger specific parameters
* @usb_volt_max: maximum allowed USB charger voltage in mV
* @usb_curr_max: maximum allowed USB charger current in mA
* @ac_volt_max: maximum allowed AC charger voltage in mV
* @ac_curr_max: maximum allowed AC charger current in mA
*/
struct abx500_bm_charger_parameters {
int usb_volt_max;
int usb_curr_max;
int ac_volt_max;
int ac_curr_max;
};
/**
* struct abx500_bm_data - abx500 battery management data
* @temp_under under this temp, charging is stopped
* @temp_low between this temp and temp_under charging is reduced
* @temp_high between this temp and temp_over charging is reduced
* @temp_over over this temp, charging is stopped
* @temp_now present battery temperature
* @temp_interval_chg temperature measurement interval in s when charging
* @temp_interval_nochg temperature measurement interval in s when not charging
* @main_safety_tmr_h safety timer for main charger
* @usb_safety_tmr_h safety timer for usb charger
* @bkup_bat_v voltage which we charge the backup battery with
* @bkup_bat_i current which we charge the backup battery with
* @no_maintenance indicates that maintenance charging is disabled
* @capacity_scaling indicates whether capacity scaling is to be used
* @abx500_adc_therm placement of thermistor, batctrl or battemp adc
* @chg_unknown_bat flag to enable charging of unknown batteries
* @enable_overshoot flag to enable VBAT overshoot control
* @auto_trig flag to enable auto adc trigger
* @fg_res resistance of FG resistor in 0.1mOhm
* @n_btypes number of elements in array bat_type
* @batt_id index of the identified battery in array bat_type
* @interval_charging charge alg cycle period time when charging (sec)
* @interval_not_charging charge alg cycle period time when not charging (sec)
* @temp_hysteresis temperature hysteresis
* @gnd_lift_resistance Battery ground to phone ground resistance (mOhm)
* @n_chg_out_curr number of elements in array chg_output_curr
* @n_chg_in_curr number of elements in array chg_input_curr
* @chg_output_curr charger output current level map
* @chg_input_curr charger input current level map
* @maxi maximization parameters
* @cap_levels capacity in percent for the different capacity levels
* @bat_type table of supported battery types
* @chg_params charger parameters
* @fg_params fuel gauge parameters
*/
struct abx500_bm_data {
int temp_under;
int temp_low;
int temp_high;
int temp_over;
int temp_now;
int temp_interval_chg;
int temp_interval_nochg;
int main_safety_tmr_h;
int usb_safety_tmr_h;
int bkup_bat_v;
int bkup_bat_i;
bool no_maintenance;
bool capacity_scaling;
bool chg_unknown_bat;
bool enable_overshoot;
bool auto_trig;
enum abx500_adc_therm adc_therm;
int fg_res;
int n_btypes;
int batt_id;
int interval_charging;
int interval_not_charging;
int temp_hysteresis;
int gnd_lift_resistance;
int n_chg_out_curr;
int n_chg_in_curr;
int *chg_output_curr;
int *chg_input_curr;
const struct abx500_maxim_parameters *maxi;
const struct abx500_bm_capacity_levels *cap_levels;
struct abx500_battery_type *bat_type;
const struct abx500_bm_charger_parameters *chg_params;
const struct abx500_fg_parameters *fg_params;
};
enum {
NTC_EXTERNAL = 0,
NTC_INTERNAL,
};
/**
* struct res_to_temp - defines one point in a temp to res curve. To
* be used in battery packs that combines the identification resistor with a
* NTC resistor.
* @temp: battery pack temperature in Celsius
* @resist: NTC resistor net total resistance
*/
struct res_to_temp {
int temp;
int resist;
};
/**
* struct batres_vs_temp - defines one point in a temp vs battery internal
* resistance curve.
* @temp: battery pack temperature in Celsius
* @resist: battery internal reistance in mOhm
*/
struct batres_vs_temp {
int temp;
int resist;
};
/* Forward declaration */
struct ab8500_fg;
/**
* struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds
* if not specified
* @recovery_sleep_timer: Time between measurements while recovering
* @recovery_total_time: Total recovery time
* @init_timer: Measurement interval during startup
* @init_discard_time: Time we discard voltage measurement at startup
* @init_total_time: Total init time during startup
* @high_curr_time: Time current has to be high to go to recovery
* @accu_charging: FG accumulation time while charging
* @accu_high_curr: FG accumulation time in high current mode
* @high_curr_threshold: High current threshold, in mA
* @lowbat_threshold: Low battery threshold, in mV
* @battok_falling_th_sel0 Threshold in mV for battOk signal sel0
* Resolution in 50 mV step.
* @battok_raising_th_sel1 Threshold in mV for battOk signal sel1
* Resolution in 50 mV step.
* @user_cap_limit Capacity reported from user must be within this
* limit to be considered as sane, in percentage
* points.
* @maint_thres This is the threshold where we stop reporting
* battery full while in maintenance, in per cent
* @pcut_enable: Enable power cut feature in ab8505
* @pcut_max_time: Max time threshold
* @pcut_flag_time: Flagtime threshold
* @pcut_max_restart: Max number of restarts
* @pcut_debunce_time: Sets battery debounce time
*/
struct ab8500_fg_parameters {
int recovery_sleep_timer;
int recovery_total_time;
int init_timer;
int init_discard_time;
int init_total_time;
int high_curr_time;
int accu_charging;
int accu_high_curr;
int high_curr_threshold;
int lowbat_threshold;
int battok_falling_th_sel0;
int battok_raising_th_sel1;
int user_cap_limit;
int maint_thres;
bool pcut_enable;
u8 pcut_max_time;
u8 pcut_flag_time;
u8 pcut_max_restart;
u8 pcut_debunce_time;
};
/**
* struct ab8500_charger_maximization - struct used by the board config.
* @use_maxi: Enable maximization for this battery type
* @maxi_chg_curr: Maximum charger current allowed
* @maxi_wait_cycles: cycles to wait before setting charger current
* @charger_curr_step delta between two charger current settings (mA)
*/
struct ab8500_maxim_parameters {
bool ena_maxi;
int chg_curr;
int wait_cycles;
int charger_curr_step;
};
/**
* struct ab8500_bm_capacity_levels - ab8500 capacity level data
* @critical: critical capacity level in percent
@ -661,6 +464,7 @@ struct ab8500_bm_charger_parameters {
* @temp_low between this temp and temp_under charging is reduced
* @temp_high between this temp and temp_over charging is reduced
* @temp_over over this temp, charging is stopped
* @temp_now present battery temperature
* @temp_interval_chg temperature measurement interval in s when charging
* @temp_interval_nochg temperature measurement interval in s when not charging
* @main_safety_tmr_h safety timer for main charger
@ -669,9 +473,10 @@ struct ab8500_bm_charger_parameters {
* @bkup_bat_i current which we charge the backup battery with
* @no_maintenance indicates that maintenance charging is disabled
* @capacity_scaling indicates whether capacity scaling is to be used
* @adc_therm placement of thermistor, batctrl or battemp adc
* @ab8500_adc_therm placement of thermistor, batctrl or battemp adc
* @chg_unknown_bat flag to enable charging of unknown batteries
* @enable_overshoot flag to enable VBAT overshoot control
* @auto_trig flag to enable auto adc trigger
* @fg_res resistance of FG resistor in 0.1mOhm
* @n_btypes number of elements in array bat_type
* @batt_id index of the identified battery in array bat_type
@ -679,7 +484,11 @@ struct ab8500_bm_charger_parameters {
* @interval_not_charging charge alg cycle period time when not charging (sec)
* @temp_hysteresis temperature hysteresis
* @gnd_lift_resistance Battery ground to phone ground resistance (mOhm)
* @maxi: maximization parameters
* @n_chg_out_curr number of elements in array chg_output_curr
* @n_chg_in_curr number of elements in array chg_input_curr
* @chg_output_curr charger output current level map
* @chg_input_curr charger input current level map
* @maxi maximization parameters
* @cap_levels capacity in percent for the different capacity levels
* @bat_type table of supported battery types
* @chg_params charger parameters
@ -690,6 +499,7 @@ struct ab8500_bm_data {
int temp_low;
int temp_high;
int temp_over;
int temp_now;
int temp_interval_chg;
int temp_interval_nochg;
int main_safety_tmr_h;
@ -700,7 +510,8 @@ struct ab8500_bm_data {
bool capacity_scaling;
bool chg_unknown_bat;
bool enable_overshoot;
enum abx500_adc_therm adc_therm;
bool auto_trig;
enum ab8500_adc_therm adc_therm;
int fg_res;
int n_btypes;
int batt_id;
@ -708,13 +519,49 @@ struct ab8500_bm_data {
int interval_not_charging;
int temp_hysteresis;
int gnd_lift_resistance;
int n_chg_out_curr;
int n_chg_in_curr;
int *chg_output_curr;
int *chg_input_curr;
const struct ab8500_maxim_parameters *maxi;
const struct ab8500_bm_capacity_levels *cap_levels;
struct ab8500_battery_type *bat_type;
const struct ab8500_bm_charger_parameters *chg_params;
const struct ab8500_fg_parameters *fg_params;
};
extern struct abx500_bm_data ab8500_bm_data;
enum {
NTC_EXTERNAL = 0,
NTC_INTERNAL,
};
/**
* struct res_to_temp - defines one point in a temp to res curve. To
* be used in battery packs that combines the identification resistor with a
* NTC resistor.
* @temp: battery pack temperature in Celsius
* @resist: NTC resistor net total resistance
*/
struct res_to_temp {
int temp;
int resist;
};
/**
* struct batres_vs_temp - defines one point in a temp vs battery internal
* resistance curve.
* @temp: battery pack temperature in Celsius
* @resist: battery internal reistance in mOhm
*/
struct batres_vs_temp {
int temp;
int resist;
};
/* Forward declaration */
struct ab8500_fg;
extern struct ab8500_bm_data ab8500_bm_data;
void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA);
struct ab8500_fg *ab8500_fg_get(void);
@ -725,10 +572,10 @@ int ab8500_fg_inst_curr_started(struct ab8500_fg *di);
int ab8500_fg_inst_curr_done(struct ab8500_fg *di);
int ab8500_bm_of_probe(struct device *dev,
struct device_node *np,
struct abx500_bm_data *bm);
struct ab8500_bm_data *bm);
extern struct platform_driver ab8500_fg_driver;
extern struct platform_driver ab8500_btemp_driver;
extern struct platform_driver abx500_chargalg_driver;
extern struct platform_driver ab8500_chargalg_driver;
#endif /* _AB8500_CHARGER_H_ */

View File

@ -2,8 +2,6 @@
#include <linux/export.h>
#include <linux/power_supply.h>
#include <linux/of.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500.h>
#include "ab8500-bm.h"
@ -13,7 +11,7 @@
* Note that the res_to_temp table must be strictly sorted by falling resistance
* values to work.
*/
const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = {
const struct ab8500_res_to_temp ab8500_temp_tbl_a_thermistor[] = {
{-5, 53407},
{ 0, 48594},
{ 5, 43804},
@ -35,7 +33,7 @@ EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor);
const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor);
EXPORT_SYMBOL(ab8500_temp_tbl_a_size);
const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = {
const struct ab8500_res_to_temp ab8500_temp_tbl_b_thermistor[] = {
{-5, 200000},
{ 0, 159024},
{ 5, 151921},
@ -57,7 +55,7 @@ EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor);
const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor);
EXPORT_SYMBOL(ab8500_temp_tbl_b_size);
static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = {
static const struct ab8500_v_to_cap cap_tbl_a_thermistor[] = {
{4171, 100},
{4114, 95},
{4009, 83},
@ -80,7 +78,7 @@ static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = {
{3247, 0},
};
static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = {
static const struct ab8500_v_to_cap cap_tbl_b_thermistor[] = {
{4161, 100},
{4124, 98},
{4044, 90},
@ -103,7 +101,7 @@ static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = {
{3250, 0},
};
static const struct abx500_v_to_cap cap_tbl[] = {
static const struct ab8500_v_to_cap cap_tbl[] = {
{4186, 100},
{4163, 99},
{4114, 95},
@ -134,7 +132,7 @@ static const struct abx500_v_to_cap cap_tbl[] = {
* Note that the res_to_temp table must be strictly sorted by falling
* resistance values to work.
*/
static const struct abx500_res_to_temp temp_tbl[] = {
static const struct ab8500_res_to_temp temp_tbl[] = {
{-5, 214834},
{ 0, 162943},
{ 5, 124820},
@ -191,7 +189,7 @@ static const struct batres_vs_temp temp_to_batres_tbl_9100[] = {
{-20, 180},
};
static struct abx500_battery_type bat_type_thermistor[] = {
static struct ab8500_battery_type bat_type_thermistor[] = {
[BATTERY_UNKNOWN] = {
/* First element always represent the UNKNOWN battery */
.name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN,
@ -277,7 +275,7 @@ static struct abx500_battery_type bat_type_thermistor[] = {
},
};
static struct abx500_battery_type bat_type_ext_thermistor[] = {
static struct ab8500_battery_type bat_type_ext_thermistor[] = {
[BATTERY_UNKNOWN] = {
/* First element always represent the UNKNOWN battery */
.name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN,
@ -394,7 +392,7 @@ static struct abx500_battery_type bat_type_ext_thermistor[] = {
},
};
static const struct abx500_bm_capacity_levels cap_levels = {
static const struct ab8500_bm_capacity_levels cap_levels = {
.critical = 2,
.low = 10,
.normal = 70,
@ -402,7 +400,7 @@ static const struct abx500_bm_capacity_levels cap_levels = {
.full = 100,
};
static const struct abx500_fg_parameters fg = {
static const struct ab8500_fg_parameters fg = {
.recovery_sleep_timer = 10,
.recovery_total_time = 100,
.init_timer = 1,
@ -424,14 +422,14 @@ static const struct abx500_fg_parameters fg = {
.pcut_debounce_time = 2,
};
static const struct abx500_maxim_parameters ab8500_maxi_params = {
static const struct ab8500_maxim_parameters ab8500_maxi_params = {
.ena_maxi = true,
.chg_curr = 910,
.wait_cycles = 10,
.charger_curr_step = 100,
};
static const struct abx500_bm_charger_parameters chg = {
static const struct ab8500_bm_charger_parameters chg = {
.usb_volt_max = 5500,
.usb_curr_max = 1500,
.ac_volt_max = 7500,
@ -456,7 +454,7 @@ static int ab8500_charge_input_curr_map[] = {
700, 800, 900, 1000, 1100, 1300, 1400, 1500,
};
struct abx500_bm_data ab8500_bm_data = {
struct ab8500_bm_data ab8500_bm_data = {
.temp_under = 3,
.temp_low = 8,
.temp_high = 43,
@ -469,7 +467,7 @@ struct abx500_bm_data ab8500_bm_data = {
.bkup_bat_i = BUP_ICH_SEL_150UA,
.no_maintenance = false,
.capacity_scaling = false,
.adc_therm = ABx500_ADC_THERM_BATCTRL,
.adc_therm = AB8500_ADC_THERM_BATCTRL,
.chg_unknown_bat = false,
.enable_overshoot = false,
.fg_res = 100,
@ -492,7 +490,7 @@ struct abx500_bm_data ab8500_bm_data = {
int ab8500_bm_of_probe(struct device *dev,
struct device_node *np,
struct abx500_bm_data *bm)
struct ab8500_bm_data *bm)
{
const struct batres_vs_temp *tmp_batres_tbl;
struct device_node *battery_node;
@ -531,7 +529,7 @@ int ab8500_bm_of_probe(struct device *dev,
} else {
bm->n_btypes = 4;
bm->bat_type = bat_type_ext_thermistor;
bm->adc_therm = ABx500_ADC_THERM_BATTEMP;
bm->adc_therm = AB8500_ADC_THERM_BATTEMP;
tmp_batres_tbl = temp_to_batres_tbl_ext_thermistor;
}

View File

@ -27,6 +27,7 @@
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500.h>
#include <linux/iio/consumer.h>
#include <linux/fixp-arith.h>
#include "ab8500-bm.h"
@ -102,7 +103,7 @@ struct ab8500_btemp {
struct iio_channel *btemp_ball;
struct iio_channel *bat_ctrl;
struct ab8500_fg *fg;
struct abx500_bm_data *bm;
struct ab8500_bm_data *bm;
struct power_supply *btemp_psy;
struct ab8500_btemp_events events;
struct ab8500_btemp_ranges btemp_ranges;
@ -144,7 +145,7 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
return (450000 * (v_batctrl)) / (1800 - v_batctrl);
}
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) {
if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) {
/*
* If the battery has internal NTC, we use the current
* source to calculate the resistance.
@ -206,7 +207,7 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
return 0;
/* Only do this for batteries with internal NTC */
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL && enable) {
if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
curr = BAT_CTRL_7U_ENA;
@ -239,7 +240,7 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
__func__);
goto disable_curr_source;
}
} else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
} else if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL && !enable) {
dev_dbg(di->dev, "Disable BATCTRL curr source\n");
/* Write 0 to the curr bits */
@ -417,7 +418,7 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
* based on the NTC resistance.
*/
static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
const struct abx500_res_to_temp *tbl, int tbl_size, int res)
const struct ab8500_res_to_temp *tbl, int tbl_size, int res)
{
int i;
/*
@ -437,8 +438,9 @@ static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
i++;
}
return tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) *
(res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
return fixp_linear_interpolate(tbl[i].resist, tbl[i].temp,
tbl[i + 1].resist, tbl[i + 1].temp,
res);
}
/**
@ -456,7 +458,7 @@ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
id = di->bm->batt_id;
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL &&
id != BATTERY_UNKNOWN) {
rbat = ab8500_btemp_get_batctrl_res(di);
@ -525,7 +527,7 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
dev_dbg(di->dev, "Battery detected on %s"
" low %d < res %d < high: %d"
" index: %d\n",
di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL ?
di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL ?
"BATCTRL" : "BATTEMP",
di->bm->bat_type[i].resis_low, res,
di->bm->bat_type[i].resis_high, i);
@ -545,7 +547,7 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
* We only have to change current source if the
* detected type is Type 1.
*/
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL &&
di->bm->batt_id == 1) {
dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;

View File

@ -292,7 +292,7 @@ struct ab8500_charger {
struct iio_channel *adc_main_charger_c;
struct iio_channel *adc_vbus_v;
struct iio_channel *adc_usb_charger_c;
struct abx500_bm_data *bm;
struct ab8500_bm_data *bm;
struct ab8500_charger_event_flags flags;
struct ab8500_charger_usb_state usb_state;
struct ab8500_charger_max_usb_in_curr max_usb_in_curr;
@ -3388,7 +3388,7 @@ static const struct component_master_ops ab8500_charger_comp_ops = {
static struct platform_driver *const ab8500_charger_component_drivers[] = {
&ab8500_fg_driver,
&ab8500_btemp_driver,
&abx500_chargalg_driver,
&ab8500_chargalg_driver,
};
static int ab8500_charger_compare_dev(struct device *dev, void *data)

View File

@ -34,6 +34,7 @@
#include <linux/mfd/abx500/ab8500.h>
#include <linux/iio/consumer.h>
#include <linux/kernel.h>
#include <linux/fixp-arith.h>
#include "ab8500-bm.h"
@ -56,9 +57,6 @@
/* FG constants */
#define BATT_OVV 0x01
#define interpolate(x, x1, y1, x2, y2) \
((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1))));
/**
* struct ab8500_fg_interrupts - ab8500 fg interrupts
* @name: name of the interrupt
@ -227,7 +225,7 @@ struct ab8500_fg {
struct ab8500_fg_avg_cap avg_cap;
struct ab8500 *parent;
struct iio_channel *main_bat_v;
struct abx500_bm_data *bm;
struct ab8500_bm_data *bm;
struct power_supply *fg_psy;
struct workqueue_struct *fg_wq;
struct delayed_work fg_periodic_work;
@ -856,7 +854,7 @@ static int ab8500_fg_bat_voltage(struct ab8500_fg *di)
static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
{
int i, tbl_size;
const struct abx500_v_to_cap *tbl;
const struct ab8500_v_to_cap *tbl;
int cap = 0;
tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl;
@ -868,11 +866,12 @@ static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
}
if ((i > 0) && (i < tbl_size)) {
cap = interpolate(voltage,
cap = fixp_linear_interpolate(
tbl[i].voltage,
tbl[i].capacity * 10,
tbl[i-1].voltage,
tbl[i-1].capacity * 10);
tbl[i-1].capacity * 10,
voltage);
} else if (i == 0) {
cap = 1000;
} else {
@ -920,11 +919,12 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
}
if ((i > 0) && (i < tbl_size)) {
resist = interpolate(di->bat_temp / 10,
resist = fixp_linear_interpolate(
tbl[i].temp,
tbl[i].resist,
tbl[i-1].temp,
tbl[i-1].resist);
tbl[i-1].resist,
di->bat_temp / 10);
} else if (i == 0) {
resist = tbl[0].resist;
} else {
@ -2235,7 +2235,7 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
case POWER_SUPPLY_TYPE_BATTERY:
if (!di->flags.batt_id_received &&
di->bm->batt_id != BATTERY_UNKNOWN) {
const struct abx500_battery_type *b;
const struct ab8500_battery_type *b;
b = &(di->bm->bat_type[di->bm->batt_id]);

View File

@ -813,7 +813,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
if (val == 0)
return -ENODEV;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
@ -823,7 +823,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
if (info->cable.edev == NULL) {
dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n",
dev_dbg(dev, "%s is not ready, probe deferred\n",
AXP288_EXTCON_DEV_NAME);
return -EPROBE_DEFER;
}
@ -834,8 +834,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
dev_dbg(dev, "EXTCON_USB_HOST is not ready, probe deferred\n");
return -EPROBE_DEFER;
}
dev_info(&pdev->dev,
"Using " USB_HOST_EXTCON_HID " extcon for usb-id\n");
dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n");
}
platform_set_drvdata(pdev, info);
@ -874,7 +873,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
if (info->otg.cable) {
ret = devm_extcon_register_notifier(&pdev->dev, info->otg.cable,
ret = devm_extcon_register_notifier(dev, info->otg.cable,
EXTCON_USB_HOST, &info->otg.id_nb);
if (ret) {
dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n");
@ -899,7 +898,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
NULL, axp288_charger_irq_thread_handler,
IRQF_ONESHOT, info->pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "failed to request interrupt=%d\n",
dev_err(dev, "failed to request interrupt=%d\n",
info->irq[i]);
return ret;
}

View File

@ -2,7 +2,8 @@
/*
* axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver
*
* Copyright (C) 2016-2017 Hans de Goede <hdegoede@redhat.com>
* Copyright (C) 2020-2021 Andrejus Basovas <xxx@yyy.tld>
* Copyright (C) 2016-2021 Hans de Goede <hdegoede@redhat.com>
* Copyright (C) 2014 Intel Corporation
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -19,9 +20,8 @@
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/iio/consumer.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <asm/unaligned.h>
#include <asm/iosf_mbi.h>
#define PS_STAT_VBUS_TRIGGER (1 << 0)
#define PS_STAT_BAT_CHRG_DIR (1 << 2)
@ -78,7 +78,6 @@
#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */
#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */
#define NR_RETRY_CNT 3
#define DEV_NAME "axp288_fuel_gauge"
/* 1.1mV per LSB expressed in uV */
@ -87,6 +86,7 @@
#define PROP_VOLT(a) ((a) * 1000)
#define PROP_CURR(a) ((a) * 1000)
#define AXP288_REG_UPDATE_INTERVAL (60 * HZ)
#define AXP288_FG_INTR_NUM 6
enum {
QWBTU_IRQ = 0,
@ -98,9 +98,6 @@ enum {
};
enum {
BAT_TEMP = 0,
PMIC_TEMP,
SYSTEM_TEMP,
BAT_CHRG_CURR,
BAT_D_CURR,
BAT_VOLT,
@ -108,7 +105,7 @@ enum {
};
struct axp288_fg_info {
struct platform_device *pdev;
struct device *dev;
struct regmap *regmap;
struct regmap_irq_chip_data *regmap_irqc;
int irq[AXP288_FG_INTR_NUM];
@ -117,7 +114,21 @@ struct axp288_fg_info {
struct mutex lock;
int status;
int max_volt;
int pwr_op;
int low_cap;
struct dentry *debug_file;
char valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */
int pwr_stat;
int fg_res;
int bat_volt;
int d_curr;
int c_curr;
int ocv;
int fg_cc_mtr1;
int fg_des_cap1;
};
static enum power_supply_property fuel_gauge_props[] = {
@ -137,17 +148,12 @@ static enum power_supply_property fuel_gauge_props[] = {
static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
{
int ret, i;
unsigned int val;
int ret;
for (i = 0; i < NR_RETRY_CNT; i++) {
ret = regmap_read(info->regmap, reg, &val);
if (ret != -EBUSY)
break;
}
if (ret < 0) {
dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);
dev_err(info->dev, "Error reading reg 0x%02x err: %d\n", reg, ret);
return ret;
}
@ -161,7 +167,7 @@ static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val)
ret = regmap_write(info->regmap, reg, (unsigned int)val);
if (ret < 0)
dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret);
dev_err(info->dev, "Error writing reg 0x%02x err: %d\n", reg, ret);
return ret;
}
@ -173,15 +179,13 @@ static int fuel_gauge_read_15bit_word(struct axp288_fg_info *info, int reg)
ret = regmap_bulk_read(info->regmap, reg, buf, 2);
if (ret < 0) {
dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
reg, ret);
dev_err(info->dev, "Error reading reg 0x%02x err: %d\n", reg, ret);
return ret;
}
ret = get_unaligned_be16(buf);
if (!(ret & FG_15BIT_WORD_VALID)) {
dev_err(&info->pdev->dev, "Error reg 0x%02x contents not valid\n",
reg);
dev_err(info->dev, "Error reg 0x%02x contents not valid\n", reg);
return -ENXIO;
}
@ -195,8 +199,7 @@ static int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg)
ret = regmap_bulk_read(info->regmap, reg, buf, 2);
if (ret < 0) {
dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
reg, ret);
dev_err(info->dev, "Error reading reg 0x%02x err: %d\n", reg, ret);
return ret;
}
@ -204,139 +207,78 @@ static int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg)
return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f);
}
#ifdef CONFIG_DEBUG_FS
static int fuel_gauge_debug_show(struct seq_file *s, void *data)
static int fuel_gauge_update_registers(struct axp288_fg_info *info)
{
struct axp288_fg_info *info = s->private;
int raw_val, ret;
seq_printf(s, " PWR_STATUS[%02x] : %02x\n",
AXP20X_PWR_INPUT_STATUS,
fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS));
seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n",
AXP20X_PWR_OP_MODE,
fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE));
seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n",
AXP20X_CHRG_CTRL1,
fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1));
seq_printf(s, " VLTF[%02x] : %02x\n",
AXP20X_V_LTF_DISCHRG,
fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG));
seq_printf(s, " VHTF[%02x] : %02x\n",
AXP20X_V_HTF_DISCHRG,
fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG));
seq_printf(s, " CC_CTRL[%02x] : %02x\n",
AXP20X_CC_CTRL,
fuel_gauge_reg_readb(info, AXP20X_CC_CTRL));
seq_printf(s, "BATTERY CAP[%02x] : %02x\n",
AXP20X_FG_RES,
fuel_gauge_reg_readb(info, AXP20X_FG_RES));
seq_printf(s, " FG_RDC1[%02x] : %02x\n",
AXP288_FG_RDC1_REG,
fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG));
seq_printf(s, " FG_RDC0[%02x] : %02x\n",
AXP288_FG_RDC0_REG,
fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));
seq_printf(s, " FG_OCV[%02x] : %04x\n",
AXP288_FG_OCVH_REG,
fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG));
seq_printf(s, " FG_DES_CAP[%02x] : %04x\n",
AXP288_FG_DES_CAP1_REG,
fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG));
seq_printf(s, " FG_CC_MTR[%02x] : %04x\n",
AXP288_FG_CC_MTR1_REG,
fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG));
seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",
AXP288_FG_OCV_CAP_REG,
fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));
seq_printf(s, " FG_CC_CAP[%02x] : %02x\n",
AXP288_FG_CC_CAP_REG,
fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG));
seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n",
AXP288_FG_LOW_CAP_REG,
fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG));
seq_printf(s, "TUNING_CTL0[%02x] : %02x\n",
AXP288_FG_TUNE0,
fuel_gauge_reg_readb(info, AXP288_FG_TUNE0));
seq_printf(s, "TUNING_CTL1[%02x] : %02x\n",
AXP288_FG_TUNE1,
fuel_gauge_reg_readb(info, AXP288_FG_TUNE1));
seq_printf(s, "TUNING_CTL2[%02x] : %02x\n",
AXP288_FG_TUNE2,
fuel_gauge_reg_readb(info, AXP288_FG_TUNE2));
seq_printf(s, "TUNING_CTL3[%02x] : %02x\n",
AXP288_FG_TUNE3,
fuel_gauge_reg_readb(info, AXP288_FG_TUNE3));
seq_printf(s, "TUNING_CTL4[%02x] : %02x\n",
AXP288_FG_TUNE4,
fuel_gauge_reg_readb(info, AXP288_FG_TUNE4));
seq_printf(s, "TUNING_CTL5[%02x] : %02x\n",
AXP288_FG_TUNE5,
fuel_gauge_reg_readb(info, AXP288_FG_TUNE5));
ret = iio_read_channel_raw(info->iio_channel[BAT_TEMP], &raw_val);
if (ret >= 0)
seq_printf(s, "axp288-batttemp : %d\n", raw_val);
ret = iio_read_channel_raw(info->iio_channel[PMIC_TEMP], &raw_val);
if (ret >= 0)
seq_printf(s, "axp288-pmictemp : %d\n", raw_val);
ret = iio_read_channel_raw(info->iio_channel[SYSTEM_TEMP], &raw_val);
if (ret >= 0)
seq_printf(s, "axp288-systtemp : %d\n", raw_val);
ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &raw_val);
if (ret >= 0)
seq_printf(s, "axp288-chrgcurr : %d\n", raw_val);
ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &raw_val);
if (ret >= 0)
seq_printf(s, "axp288-dchrgcur : %d\n", raw_val);
ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &raw_val);
if (ret >= 0)
seq_printf(s, "axp288-battvolt : %d\n", raw_val);
int ret;
if (info->valid && time_before(jiffies, info->last_updated + AXP288_REG_UPDATE_INTERVAL))
return 0;
dev_dbg(info->dev, "Fuel Gauge updating register values...\n");
ret = iosf_mbi_block_punit_i2c_access();
if (ret < 0)
return ret;
ret = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);
if (ret < 0)
goto out;
info->pwr_stat = ret;
ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
if (ret < 0)
goto out;
info->fg_res = ret;
ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &info->bat_volt);
if (ret < 0)
goto out;
if (info->pwr_stat & PS_STAT_BAT_CHRG_DIR) {
info->d_curr = 0;
ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &info->c_curr);
if (ret < 0)
goto out;
} else {
info->c_curr = 0;
ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &info->d_curr);
if (ret < 0)
goto out;
}
DEFINE_SHOW_ATTRIBUTE(fuel_gauge_debug);
ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
if (ret < 0)
goto out;
info->ocv = ret;
static void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
{
info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL,
info, &fuel_gauge_debug_fops);
}
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG);
if (ret < 0)
goto out;
info->fg_cc_mtr1 = ret;
static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
{
debugfs_remove(info->debug_file);
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0)
goto out;
info->fg_des_cap1 = ret;
info->last_updated = jiffies;
info->valid = 1;
ret = 0;
out:
iosf_mbi_unblock_punit_i2c_access();
return ret;
}
#else
static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
{
}
static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
{
}
#endif
static void fuel_gauge_get_status(struct axp288_fg_info *info)
{
int pwr_stat, fg_res, curr, ret;
pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);
if (pwr_stat < 0) {
dev_err(&info->pdev->dev,
"PWR STAT read failed:%d\n", pwr_stat);
return;
}
int pwr_stat = info->pwr_stat;
int fg_res = info->fg_res;
int curr = info->d_curr;
/* Report full if Vbus is valid and the reported capacity is 100% */
if (!(pwr_stat & PS_STAT_VBUS_VALID))
goto not_full;
fg_res = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
if (fg_res < 0) {
dev_err(&info->pdev->dev, "FG RES read failed: %d\n", fg_res);
return;
}
if (!(fg_res & FG_REP_CAP_VALID))
goto not_full;
@ -354,11 +296,6 @@ static void fuel_gauge_get_status(struct axp288_fg_info *info)
if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR))
goto not_full;
ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &curr);
if (ret < 0) {
dev_err(&info->pdev->dev, "FG get current failed: %d\n", ret);
return;
}
if (curr == 0) {
info->status = POWER_SUPPLY_STATUS_FULL;
return;
@ -371,61 +308,16 @@ not_full:
info->status = POWER_SUPPLY_STATUS_DISCHARGING;
}
static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt)
{
int ret = 0, raw_val;
ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &raw_val);
if (ret < 0)
goto vbatt_read_fail;
*vbatt = VOLTAGE_FROM_ADC(raw_val);
vbatt_read_fail:
return ret;
}
static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)
{
int ret, discharge;
/* First check discharge current, so that we do only 1 read on bat. */
ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &discharge);
if (ret < 0)
return ret;
if (discharge > 0) {
*cur = -1 * discharge;
return 0;
}
return iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], cur);
}
static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)
{
int ret;
ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
if (ret >= 0)
*vocv = VOLTAGE_FROM_ADC(ret);
return ret;
}
static int fuel_gauge_battery_health(struct axp288_fg_info *info)
{
int ret, vocv, health = POWER_SUPPLY_HEALTH_UNKNOWN;
ret = fuel_gauge_get_vocv(info, &vocv);
if (ret < 0)
goto health_read_fail;
int vocv = VOLTAGE_FROM_ADC(info->ocv);
int health = POWER_SUPPLY_HEALTH_UNKNOWN;
if (vocv > info->max_volt)
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else
health = POWER_SUPPLY_HEALTH_GOOD;
health_read_fail:
return health;
}
@ -434,9 +326,14 @@ static int fuel_gauge_get_property(struct power_supply *ps,
union power_supply_propval *val)
{
struct axp288_fg_info *info = power_supply_get_drvdata(ps);
int ret = 0, value;
int ret, value;
mutex_lock(&info->lock);
ret = fuel_gauge_update_registers(info);
if (ret < 0)
goto out;
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
fuel_gauge_get_status(info);
@ -446,78 +343,52 @@ static int fuel_gauge_get_property(struct power_supply *ps,
val->intval = fuel_gauge_battery_health(info);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = fuel_gauge_get_vbatt(info, &value);
if (ret < 0)
goto fuel_gauge_read_err;
value = VOLTAGE_FROM_ADC(info->bat_volt);
val->intval = PROP_VOLT(value);
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = fuel_gauge_get_vocv(info, &value);
if (ret < 0)
goto fuel_gauge_read_err;
value = VOLTAGE_FROM_ADC(info->ocv);
val->intval = PROP_VOLT(value);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = fuel_gauge_get_current(info, &value);
if (ret < 0)
goto fuel_gauge_read_err;
if (info->d_curr > 0)
value = -1 * info->d_curr;
else
value = info->c_curr;
val->intval = PROP_CURR(value);
break;
case POWER_SUPPLY_PROP_PRESENT:
ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
if (ret < 0)
goto fuel_gauge_read_err;
if (ret & CHRG_STAT_BAT_PRESENT)
if (info->pwr_op & CHRG_STAT_BAT_PRESENT)
val->intval = 1;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_CAPACITY:
ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
if (ret < 0)
goto fuel_gauge_read_err;
if (!(ret & FG_REP_CAP_VALID))
dev_err(&info->pdev->dev,
"capacity measurement not valid\n");
val->intval = (ret & FG_REP_CAP_VAL_MASK);
if (!(info->fg_res & FG_REP_CAP_VALID))
dev_err(info->dev, "capacity measurement not valid\n");
val->intval = (info->fg_res & FG_REP_CAP_VAL_MASK);
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
if (ret < 0)
goto fuel_gauge_read_err;
val->intval = (ret & 0x0f);
val->intval = (info->low_cap & 0x0f);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG);
if (ret < 0)
goto fuel_gauge_read_err;
val->intval = ret * FG_DES_CAP_RES_LSB;
val->intval = info->fg_cc_mtr1 * FG_DES_CAP_RES_LSB;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0)
goto fuel_gauge_read_err;
val->intval = ret * FG_DES_CAP_RES_LSB;
val->intval = info->fg_des_cap1 * FG_DES_CAP_RES_LSB;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = PROP_VOLT(info->max_volt);
break;
default:
mutex_unlock(&info->lock);
return -EINVAL;
ret = -EINVAL;
}
mutex_unlock(&info->lock);
return 0;
fuel_gauge_read_err:
out:
mutex_unlock(&info->lock);
return ret;
}
@ -527,7 +398,7 @@ static int fuel_gauge_set_property(struct power_supply *ps,
const union power_supply_propval *val)
{
struct axp288_fg_info *info = power_supply_get_drvdata(ps);
int ret = 0;
int new_low_cap, ret = 0;
mutex_lock(&info->lock);
switch (prop) {
@ -536,12 +407,12 @@ static int fuel_gauge_set_property(struct power_supply *ps,
ret = -EINVAL;
break;
}
ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
if (ret < 0)
break;
ret &= 0xf0;
ret |= (val->intval & 0xf);
ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret);
new_low_cap = info->low_cap;
new_low_cap &= 0xf0;
new_low_cap |= (val->intval & 0xf);
ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, new_low_cap);
if (ret == 0)
info->low_cap = new_low_cap;
break;
default:
ret = -EINVAL;
@ -579,37 +450,35 @@ static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)
}
if (i >= AXP288_FG_INTR_NUM) {
dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
dev_warn(info->dev, "spurious interrupt!!\n");
return IRQ_NONE;
}
switch (i) {
case QWBTU_IRQ:
dev_info(&info->pdev->dev,
"Quit Battery under temperature in work mode IRQ (QWBTU)\n");
dev_info(info->dev, "Quit Battery under temperature in work mode IRQ (QWBTU)\n");
break;
case WBTU_IRQ:
dev_info(&info->pdev->dev,
"Battery under temperature in work mode IRQ (WBTU)\n");
dev_info(info->dev, "Battery under temperature in work mode IRQ (WBTU)\n");
break;
case QWBTO_IRQ:
dev_info(&info->pdev->dev,
"Quit Battery over temperature in work mode IRQ (QWBTO)\n");
dev_info(info->dev, "Quit Battery over temperature in work mode IRQ (QWBTO)\n");
break;
case WBTO_IRQ:
dev_info(&info->pdev->dev,
"Battery over temperature in work mode IRQ (WBTO)\n");
dev_info(info->dev, "Battery over temperature in work mode IRQ (WBTO)\n");
break;
case WL2_IRQ:
dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");
dev_info(info->dev, "Low Batt Warning(2) INTR\n");
break;
case WL1_IRQ:
dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");
dev_info(info->dev, "Low Batt Warning(1) INTR\n");
break;
default:
dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
dev_warn(info->dev, "Spurious Interrupt!!!\n");
}
info->valid = 0; /* Force updating of the cached registers */
power_supply_changed(info->bat);
return IRQ_HANDLED;
}
@ -618,6 +487,7 @@ static void fuel_gauge_external_power_changed(struct power_supply *psy)
{
struct axp288_fg_info *info = power_supply_get_drvdata(psy);
info->valid = 0; /* Force updating of the cached registers */
power_supply_changed(info->bat);
}
@ -632,16 +502,15 @@ static const struct power_supply_desc fuel_gauge_desc = {
.external_power_changed = fuel_gauge_external_power_changed,
};
static void fuel_gauge_init_irq(struct axp288_fg_info *info)
static void fuel_gauge_init_irq(struct axp288_fg_info *info, struct platform_device *pdev)
{
int ret, i, pirq;
for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
pirq = platform_get_irq(info->pdev, i);
pirq = platform_get_irq(pdev, i);
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev,
"regmap_irq get virq failed for IRQ %d: %d\n",
dev_warn(info->dev, "regmap_irq get virq failed for IRQ %d: %d\n",
pirq, info->irq[i]);
info->irq[i] = -1;
goto intr_failed;
@ -650,14 +519,10 @@ static void fuel_gauge_init_irq(struct axp288_fg_info *info)
NULL, fuel_gauge_thread_handler,
IRQF_ONESHOT, DEV_NAME, info);
if (ret) {
dev_warn(&info->pdev->dev,
"request irq failed for IRQ %d: %d\n",
dev_warn(info->dev, "request irq failed for IRQ %d: %d\n",
pirq, info->irq[i]);
info->irq[i] = -1;
goto intr_failed;
} else {
dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n",
pirq, info->irq[i]);
}
}
return;
@ -753,9 +618,6 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
static const char * const iio_chan_name[] = {
[BAT_TEMP] = "axp288-batt-temp",
[PMIC_TEMP] = "axp288-pmic-temp",
[SYSTEM_TEMP] = "axp288-system-temp",
[BAT_CHRG_CURR] = "axp288-chrg-curr",
[BAT_D_CURR] = "axp288-chrg-d-curr",
[BAT_VOLT] = "axp288-batt-volt",
@ -765,24 +627,15 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
if (dmi_check_system(axp288_no_battery_list))
return -ENODEV;
/*
* On some devices the fuelgauge and charger parts of the axp288 are
* not used, check that the fuelgauge is enabled (CC_CTRL != 0).
*/
ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val);
if (ret < 0)
return ret;
if (val == 0)
return -ENODEV;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->pdev = pdev;
info->dev = &pdev->dev;
info->regmap = axp20x->regmap;
info->regmap_irqc = axp20x->regmap_irqc;
info->status = POWER_SUPPLY_STATUS_UNKNOWN;
info->valid = 0;
platform_set_drvdata(pdev, info);
@ -808,19 +661,35 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
}
}
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
ret = iosf_mbi_block_punit_i2c_access();
if (ret < 0)
goto out_free_iio_chan;
/*
* On some devices the fuelgauge and charger parts of the axp288 are
* not used, check that the fuelgauge is enabled (CC_CTRL != 0).
*/
ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val);
if (ret < 0)
goto unblock_punit_i2c_access;
if (val == 0) {
ret = -ENODEV;
goto unblock_punit_i2c_access;
}
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0)
goto unblock_punit_i2c_access;
if (!(ret & FG_DES_CAP1_VALID)) {
dev_err(&pdev->dev, "axp288 not configured by firmware\n");
ret = -ENODEV;
goto out_free_iio_chan;
goto unblock_punit_i2c_access;
}
ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
if (ret < 0)
goto out_free_iio_chan;
goto unblock_punit_i2c_access;
switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) {
case CHRG_CCCV_CV_4100MV:
info->max_volt = 4100;
@ -836,6 +705,22 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
break;
}
ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
if (ret < 0)
goto unblock_punit_i2c_access;
info->pwr_op = ret;
ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
if (ret < 0)
goto unblock_punit_i2c_access;
info->low_cap = ret;
unblock_punit_i2c_access:
iosf_mbi_unblock_punit_i2c_access();
/* In case we arrive here by goto because of a register access error */
if (ret < 0)
goto out_free_iio_chan;
psy_cfg.drv_data = info;
info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
if (IS_ERR(info->bat)) {
@ -844,8 +729,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
goto out_free_iio_chan;
}
fuel_gauge_create_debugfs(info);
fuel_gauge_init_irq(info);
fuel_gauge_init_irq(info, pdev);
return 0;
@ -869,7 +753,6 @@ static int axp288_fuel_gauge_remove(struct platform_device *pdev)
int i;
power_supply_unregister(info->bat);
fuel_gauge_remove_debugfs(info);
for (i = 0; i < AXP288_FG_INTR_NUM; i++)
if (info->irq[i] >= 0)

View File

@ -31,9 +31,8 @@
#include <linux/power/bq24735-charger.h>
#define BQ24735_CHG_OPT 0x12
#define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0)
#define BQ24735_CHG_OPT_AC_PRESENT (1 << 4)
/* BQ24735 available commands and their respective masks */
#define BQ24735_CHARGE_OPT 0x12
#define BQ24735_CHARGE_CURRENT 0x14
#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0
#define BQ24735_CHARGE_VOLTAGE 0x15
@ -43,6 +42,10 @@
#define BQ24735_MANUFACTURER_ID 0xfe
#define BQ24735_DEVICE_ID 0xff
/* ChargeOptions bits of interest */
#define BQ24735_CHARGE_OPT_CHG_DISABLE (1 << 0)
#define BQ24735_CHARGE_OPT_AC_PRESENT (1 << 4)
struct bq24735 {
struct power_supply *charger;
struct power_supply_desc charger_desc;
@ -167,8 +170,8 @@ static inline int bq24735_enable_charging(struct bq24735 *charger)
if (ret)
return ret;
return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
BQ24735_CHG_OPT_CHARGE_DISABLE, 0);
return bq24735_update_word(charger->client, BQ24735_CHARGE_OPT,
BQ24735_CHARGE_OPT_CHG_DISABLE, 0);
}
static inline int bq24735_disable_charging(struct bq24735 *charger)
@ -176,9 +179,9 @@ static inline int bq24735_disable_charging(struct bq24735 *charger)
if (charger->pdata->ext_control)
return 0;
return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
BQ24735_CHG_OPT_CHARGE_DISABLE,
BQ24735_CHG_OPT_CHARGE_DISABLE);
return bq24735_update_word(charger->client, BQ24735_CHARGE_OPT,
BQ24735_CHARGE_OPT_CHG_DISABLE,
BQ24735_CHARGE_OPT_CHG_DISABLE);
}
static bool bq24735_charger_is_present(struct bq24735 *charger)
@ -188,14 +191,14 @@ static bool bq24735_charger_is_present(struct bq24735 *charger)
} else {
int ac = 0;
ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT);
ac = bq24735_read_word(charger->client, BQ24735_CHARGE_OPT);
if (ac < 0) {
dev_dbg(&charger->client->dev,
"Failed to read charger options : %d\n",
ac);
return false;
}
return (ac & BQ24735_CHG_OPT_AC_PRESENT) ? true : false;
return (ac & BQ24735_CHARGE_OPT_AC_PRESENT) ? true : false;
}
return false;
@ -208,11 +211,11 @@ static int bq24735_charger_is_charging(struct bq24735 *charger)
if (!bq24735_charger_is_present(charger))
return 0;
ret = bq24735_read_word(charger->client, BQ24735_CHG_OPT);
ret = bq24735_read_word(charger->client, BQ24735_CHARGE_OPT);
if (ret < 0)
return ret;
return !(ret & BQ24735_CHG_OPT_CHARGE_DISABLE);
return !(ret & BQ24735_CHARGE_OPT_CHG_DISABLE);
}
static void bq24735_update(struct bq24735 *charger)

View File

@ -0,0 +1,386 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Power supply driver for ChromeOS EC based Peripheral Device Charger.
*
* Copyright 2020 Google LLC.
*/
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/stringify.h>
#include <linux/types.h>
#define DRV_NAME "cros-ec-pchg"
#define PCHG_DIR_PREFIX "peripheral"
#define PCHG_DIR_NAME PCHG_DIR_PREFIX "%d"
#define PCHG_DIR_NAME_LENGTH \
sizeof(PCHG_DIR_PREFIX __stringify(EC_PCHG_MAX_PORTS))
#define PCHG_CACHE_UPDATE_DELAY msecs_to_jiffies(500)
struct port_data {
int port_number;
char name[PCHG_DIR_NAME_LENGTH];
struct power_supply *psy;
struct power_supply_desc psy_desc;
int psy_status;
int battery_percentage;
int charge_type;
struct charger_data *charger;
unsigned long last_update;
};
struct charger_data {
struct device *dev;
struct cros_ec_dev *ec_dev;
struct cros_ec_device *ec_device;
int num_registered_psy;
struct port_data *ports[EC_PCHG_MAX_PORTS];
struct notifier_block notifier;
};
static enum power_supply_property cros_pchg_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_SCOPE,
};
static int cros_pchg_ec_command(const struct charger_data *charger,
unsigned int version,
unsigned int command,
const void *outdata,
unsigned int outsize,
void *indata,
unsigned int insize)
{
struct cros_ec_dev *ec_dev = charger->ec_dev;
struct cros_ec_command *msg;
int ret;
msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->version = version;
msg->command = ec_dev->cmd_offset + command;
msg->outsize = outsize;
msg->insize = insize;
if (outsize)
memcpy(msg->data, outdata, outsize);
ret = cros_ec_cmd_xfer_status(charger->ec_device, msg);
if (ret >= 0 && insize)
memcpy(indata, msg->data, insize);
kfree(msg);
return ret;
}
static const unsigned int pchg_cmd_version = 1;
static bool cros_pchg_cmd_ver_check(const struct charger_data *charger)
{
struct ec_params_get_cmd_versions_v1 req;
struct ec_response_get_cmd_versions rsp;
int ret;
req.cmd = EC_CMD_PCHG;
ret = cros_pchg_ec_command(charger, 1, EC_CMD_GET_CMD_VERSIONS,
&req, sizeof(req), &rsp, sizeof(rsp));
if (ret < 0) {
dev_warn(charger->dev,
"Unable to get versions of EC_CMD_PCHG (err:%d)\n",
ret);
return false;
}
return !!(rsp.version_mask & BIT(pchg_cmd_version));
}
static int cros_pchg_port_count(const struct charger_data *charger)
{
struct ec_response_pchg_count rsp;
int ret;
ret = cros_pchg_ec_command(charger, 0, EC_CMD_PCHG_COUNT,
NULL, 0, &rsp, sizeof(rsp));
if (ret < 0) {
dev_warn(charger->dev,
"Unable to get number or ports (err:%d)\n", ret);
return ret;
}
return rsp.port_count;
}
static int cros_pchg_get_status(struct port_data *port)
{
struct charger_data *charger = port->charger;
struct ec_params_pchg req;
struct ec_response_pchg rsp;
struct device *dev = charger->dev;
int old_status = port->psy_status;
int old_percentage = port->battery_percentage;
int ret;
req.port = port->port_number;
ret = cros_pchg_ec_command(charger, pchg_cmd_version, EC_CMD_PCHG,
&req, sizeof(req), &rsp, sizeof(rsp));
if (ret < 0) {
dev_err(dev, "Unable to get port.%d status (err:%d)\n",
port->port_number, ret);
return ret;
}
switch (rsp.state) {
case PCHG_STATE_RESET:
case PCHG_STATE_INITIALIZED:
case PCHG_STATE_ENABLED:
default:
port->psy_status = POWER_SUPPLY_STATUS_UNKNOWN;
port->charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
case PCHG_STATE_DETECTED:
port->psy_status = POWER_SUPPLY_STATUS_CHARGING;
port->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case PCHG_STATE_CHARGING:
port->psy_status = POWER_SUPPLY_STATUS_CHARGING;
port->charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
break;
case PCHG_STATE_FULL:
port->psy_status = POWER_SUPPLY_STATUS_FULL;
port->charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
}
port->battery_percentage = rsp.battery_percentage;
if (port->psy_status != old_status ||
port->battery_percentage != old_percentage)
power_supply_changed(port->psy);
dev_dbg(dev,
"Port %d: state=%d battery=%d%%\n",
port->port_number, rsp.state, rsp.battery_percentage);
return 0;
}
static int cros_pchg_get_port_status(struct port_data *port, bool ratelimit)
{
int ret;
if (ratelimit &&
time_is_after_jiffies(port->last_update + PCHG_CACHE_UPDATE_DELAY))
return 0;
ret = cros_pchg_get_status(port);
if (ret < 0)
return ret;
port->last_update = jiffies;
return ret;
}
static int cros_pchg_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct port_data *port = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_CHARGE_TYPE:
cros_pchg_get_port_status(port, true);
break;
default:
break;
}
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = port->psy_status;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = port->battery_percentage;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = port->charge_type;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
default:
return -EINVAL;
}
return 0;
}
static int cros_pchg_event(const struct charger_data *charger,
unsigned long host_event)
{
int i;
for (i = 0; i < charger->num_registered_psy; i++)
cros_pchg_get_port_status(charger->ports[i], false);
return NOTIFY_OK;
}
static u32 cros_get_device_event(const struct charger_data *charger)
{
struct ec_params_device_event req;
struct ec_response_device_event rsp;
struct device *dev = charger->dev;
int ret;
req.param = EC_DEVICE_EVENT_PARAM_GET_CURRENT_EVENTS;
ret = cros_pchg_ec_command(charger, 0, EC_CMD_DEVICE_EVENT,
&req, sizeof(req), &rsp, sizeof(rsp));
if (ret < 0) {
dev_warn(dev, "Unable to get device events (err:%d)\n", ret);
return 0;
}
return rsp.event_mask;
}
static int cros_ec_notify(struct notifier_block *nb,
unsigned long queued_during_suspend,
void *data)
{
struct cros_ec_device *ec_dev = (struct cros_ec_device *)data;
u32 host_event = cros_ec_get_host_event(ec_dev);
struct charger_data *charger =
container_of(nb, struct charger_data, notifier);
u32 device_event_mask;
if (!host_event)
return NOTIFY_DONE;
if (!(host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_DEVICE)))
return NOTIFY_DONE;
/*
* todo: Retrieve device event mask in common place
* (e.g. cros_ec_proto.c).
*/
device_event_mask = cros_get_device_event(charger);
if (!(device_event_mask & EC_DEVICE_EVENT_MASK(EC_DEVICE_EVENT_WLC)))
return NOTIFY_DONE;
return cros_pchg_event(charger, host_event);
}
static int cros_pchg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
struct cros_ec_device *ec_device = ec_dev->ec_dev;
struct power_supply_desc *psy_desc;
struct charger_data *charger;
struct power_supply *psy;
struct port_data *port;
struct notifier_block *nb;
int num_ports;
int ret;
int i;
charger = devm_kzalloc(dev, sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
charger->dev = dev;
charger->ec_dev = ec_dev;
charger->ec_device = ec_device;
ret = cros_pchg_port_count(charger);
if (ret <= 0) {
/*
* This feature is enabled by the EC and the kernel driver is
* included by default for CrOS devices. Don't need to be loud
* since this error can be normal.
*/
dev_info(dev, "No peripheral charge ports (err:%d)\n", ret);
return -ENODEV;
}
if (!cros_pchg_cmd_ver_check(charger)) {
dev_err(dev, "EC_CMD_PCHG version %d isn't available.\n",
pchg_cmd_version);
return -EOPNOTSUPP;
}
num_ports = ret;
if (num_ports > EC_PCHG_MAX_PORTS) {
dev_err(dev, "Too many peripheral charge ports (%d)\n",
num_ports);
return -ENOBUFS;
}
dev_info(dev, "%d peripheral charge ports found\n", num_ports);
for (i = 0; i < num_ports; i++) {
struct power_supply_config psy_cfg = {};
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
port->charger = charger;
port->port_number = i;
snprintf(port->name, sizeof(port->name), PCHG_DIR_NAME, i);
psy_desc = &port->psy_desc;
psy_desc->name = port->name;
psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
psy_desc->get_property = cros_pchg_get_prop;
psy_desc->external_power_changed = NULL;
psy_desc->properties = cros_pchg_props;
psy_desc->num_properties = ARRAY_SIZE(cros_pchg_props);
psy_cfg.drv_data = port;
psy = devm_power_supply_register(dev, psy_desc, &psy_cfg);
if (IS_ERR(psy))
return dev_err_probe(dev, PTR_ERR(psy),
"Failed to register power supply\n");
port->psy = psy;
charger->ports[charger->num_registered_psy++] = port;
}
if (!charger->num_registered_psy)
return -ENODEV;
nb = &charger->notifier;
nb->notifier_call = cros_ec_notify;
ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier,
nb);
if (ret < 0)
dev_err(dev, "Failed to register notifier (err:%d)\n", ret);
return 0;
}
static struct platform_driver cros_pchg_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = cros_pchg_probe
};
module_platform_driver(cros_pchg_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ChromeOS EC peripheral device charger");
MODULE_ALIAS("platform:" DRV_NAME);

View File

@ -679,7 +679,9 @@ static int cw_bat_probe(struct i2c_client *client)
&cw2015_bat_desc,
&psy_cfg);
if (IS_ERR(cw_bat->rk_bat)) {
dev_err(cw_bat->dev, "Failed to register power supply\n");
/* try again if this happens */
dev_err_probe(&client->dev, PTR_ERR(cw_bat->rk_bat),
"Failed to register power supply\n");
return PTR_ERR(cw_bat->rk_bat);
}

View File

@ -36,8 +36,6 @@
/* Interrupt mask bits */
#define CONFIG_ALRT_BIT_ENBL (1 << 2)
#define STATUS_INTR_SOCMIN_BIT (1 << 10)
#define STATUS_INTR_SOCMAX_BIT (1 << 14)
#define VFSOC0_LOCK 0x0000
#define VFSOC0_UNLOCK 0x0080
@ -285,8 +283,6 @@ static int max17042_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
ret = regmap_read(map, MAX17042_V_empty, &data);
else if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
ret = regmap_read(map, MAX17055_V_empty, &data);
else
ret = regmap_read(map, MAX17047_V_empty, &data);
if (ret < 0)
@ -748,7 +744,7 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
struct max17042_config_data *config = chip->pdata->config_data;
max17042_override_por(map, MAX17042_TGAIN, config->tgain);
max17042_override_por(map, MAx17042_TOFF, config->toff);
max17042_override_por(map, MAX17042_TOFF, config->toff);
max17042_override_por(map, MAX17042_CGAIN, config->cgain);
max17042_override_por(map, MAX17042_COFF, config->coff);
@ -767,36 +763,36 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg);
max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg);
max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg);
max17042_override_por(map, MAX17042_MaskSOC, config->masksoc);
max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
max17042_override_por(map, MAX17042_SOC_empty,
config->socempty);
max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty);
max17042_override_por(map, MAX17042_dQacc, config->dqacc);
max17042_override_por(map, MAX17042_dPacc, config->dpacc);
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) {
max17042_override_por(map, MAX17042_MaskSOC, config->masksoc);
max17042_override_por(map, MAX17042_SOC_empty, config->socempty);
max17042_override_por(map, MAX17042_V_empty, config->vempty);
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
max17042_override_por(map, MAX17055_V_empty, config->vempty);
else
max17042_override_por(map, MAX17047_V_empty, config->vempty);
max17042_override_por(map, MAX17042_EmptyTempCo, config->empty_tempco);
max17042_override_por(map, MAX17042_K_empty0, config->kempty0);
}
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty);
max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
max17042_override_por(map, MAX17042_TempLim, config->temp_lim);
max17042_override_por(map, MAX17042_FCTC, config->fctc);
max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
if (chip->chip_type &&
((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050))) {
max17042_override_por(map, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_override_por(map, MAX17042_K_empty0,
config->kempty0);
}
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) ||
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)) {
max17042_override_por(map, MAX17047_V_empty, config->vempty);
}
}
@ -869,11 +865,14 @@ static irqreturn_t max17042_thread_handler(int id, void *dev)
{
struct max17042_chip *chip = dev;
u32 val;
int ret;
regmap_read(chip->regmap, MAX17042_STATUS, &val);
if ((val & STATUS_INTR_SOCMIN_BIT) ||
(val & STATUS_INTR_SOCMAX_BIT)) {
dev_info(&chip->client->dev, "SOC threshold INTR\n");
ret = regmap_read(chip->regmap, MAX17042_STATUS, &val);
if (ret)
return IRQ_HANDLED;
if ((val & STATUS_SMN_BIT) || (val & STATUS_SMX_BIT)) {
dev_dbg(&chip->client->dev, "SOC threshold INTR\n");
max17042_set_soc_threshold(chip, 1);
}
@ -1196,6 +1195,7 @@ static const struct of_device_id max17042_dt_match[] = {
{ .compatible = "maxim,max17047" },
{ .compatible = "maxim,max17050" },
{ .compatible = "maxim,max17055" },
{ .compatible = "maxim,max77849-battery" },
{ },
};
MODULE_DEVICE_TABLE(of, max17042_dt_match);
@ -1206,6 +1206,7 @@ static const struct i2c_device_id max17042_id[] = {
{ "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
{ "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
{ "max17055", MAXIM_DEVICE_TYPE_MAX17055 },
{ "max77849-battery", MAXIM_DEVICE_TYPE_MAX17047 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max17042_id);

View File

@ -0,0 +1,867 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 MediaTek Inc.
*/
#include <linux/devm-helpers.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/linear_range.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#define MT6360_PMU_CHG_CTRL1 0x311
#define MT6360_PMU_CHG_CTRL2 0x312
#define MT6360_PMU_CHG_CTRL3 0x313
#define MT6360_PMU_CHG_CTRL4 0x314
#define MT6360_PMU_CHG_CTRL5 0x315
#define MT6360_PMU_CHG_CTRL6 0x316
#define MT6360_PMU_CHG_CTRL7 0x317
#define MT6360_PMU_CHG_CTRL8 0x318
#define MT6360_PMU_CHG_CTRL9 0x319
#define MT6360_PMU_CHG_CTRL10 0x31A
#define MT6360_PMU_DEVICE_TYPE 0x322
#define MT6360_PMU_USB_STATUS1 0x327
#define MT6360_PMU_CHG_STAT 0x34A
#define MT6360_PMU_CHG_CTRL19 0x361
#define MT6360_PMU_FOD_STAT 0x3E7
/* MT6360_PMU_CHG_CTRL1 */
#define MT6360_FSLP_SHFT (3)
#define MT6360_FSLP_MASK BIT(MT6360_FSLP_SHFT)
#define MT6360_OPA_MODE_SHFT (0)
#define MT6360_OPA_MODE_MASK BIT(MT6360_OPA_MODE_SHFT)
/* MT6360_PMU_CHG_CTRL2 */
#define MT6360_IINLMTSEL_SHFT (2)
#define MT6360_IINLMTSEL_MASK GENMASK(3, 2)
/* MT6360_PMU_CHG_CTRL3 */
#define MT6360_IAICR_SHFT (2)
#define MT6360_IAICR_MASK GENMASK(7, 2)
#define MT6360_ILIM_EN_MASK BIT(0)
/* MT6360_PMU_CHG_CTRL4 */
#define MT6360_VOREG_SHFT (1)
#define MT6360_VOREG_MASK GENMASK(7, 1)
/* MT6360_PMU_CHG_CTRL5 */
#define MT6360_VOBST_MASK GENMASK(7, 2)
/* MT6360_PMU_CHG_CTRL6 */
#define MT6360_VMIVR_SHFT (1)
#define MT6360_VMIVR_MASK GENMASK(7, 1)
/* MT6360_PMU_CHG_CTRL7 */
#define MT6360_ICHG_SHFT (2)
#define MT6360_ICHG_MASK GENMASK(7, 2)
/* MT6360_PMU_CHG_CTRL8 */
#define MT6360_IPREC_SHFT (0)
#define MT6360_IPREC_MASK GENMASK(3, 0)
/* MT6360_PMU_CHG_CTRL9 */
#define MT6360_IEOC_SHFT (4)
#define MT6360_IEOC_MASK GENMASK(7, 4)
/* MT6360_PMU_CHG_CTRL10 */
#define MT6360_OTG_OC_MASK GENMASK(3, 0)
/* MT6360_PMU_DEVICE_TYPE */
#define MT6360_USBCHGEN_MASK BIT(7)
/* MT6360_PMU_USB_STATUS1 */
#define MT6360_USB_STATUS_SHFT (4)
#define MT6360_USB_STATUS_MASK GENMASK(6, 4)
/* MT6360_PMU_CHG_STAT */
#define MT6360_CHG_STAT_SHFT (6)
#define MT6360_CHG_STAT_MASK GENMASK(7, 6)
#define MT6360_VBAT_LVL_MASK BIT(5)
/* MT6360_PMU_CHG_CTRL19 */
#define MT6360_VINOVP_SHFT (5)
#define MT6360_VINOVP_MASK GENMASK(6, 5)
/* MT6360_PMU_FOD_STAT */
#define MT6360_CHRDET_EXT_MASK BIT(4)
/* uV */
#define MT6360_VMIVR_MIN 3900000
#define MT6360_VMIVR_MAX 13400000
#define MT6360_VMIVR_STEP 100000
/* uA */
#define MT6360_ICHG_MIN 100000
#define MT6360_ICHG_MAX 5000000
#define MT6360_ICHG_STEP 100000
/* uV */
#define MT6360_VOREG_MIN 3900000
#define MT6360_VOREG_MAX 4710000
#define MT6360_VOREG_STEP 10000
/* uA */
#define MT6360_AICR_MIN 100000
#define MT6360_AICR_MAX 3250000
#define MT6360_AICR_STEP 50000
/* uA */
#define MT6360_IPREC_MIN 100000
#define MT6360_IPREC_MAX 850000
#define MT6360_IPREC_STEP 50000
/* uA */
#define MT6360_IEOC_MIN 100000
#define MT6360_IEOC_MAX 850000
#define MT6360_IEOC_STEP 50000
enum {
MT6360_RANGE_VMIVR,
MT6360_RANGE_ICHG,
MT6360_RANGE_VOREG,
MT6360_RANGE_AICR,
MT6360_RANGE_IPREC,
MT6360_RANGE_IEOC,
MT6360_RANGE_MAX,
};
#define MT6360_LINEAR_RANGE(idx, _min, _min_sel, _max_sel, _step) \
[idx] = REGULATOR_LINEAR_RANGE(_min, _min_sel, _max_sel, _step)
static const struct linear_range mt6360_chg_range[MT6360_RANGE_MAX] = {
MT6360_LINEAR_RANGE(MT6360_RANGE_VMIVR, 3900000, 0, 0x5F, 100000),
MT6360_LINEAR_RANGE(MT6360_RANGE_ICHG, 100000, 0, 0x31, 100000),
MT6360_LINEAR_RANGE(MT6360_RANGE_VOREG, 3900000, 0, 0x51, 10000),
MT6360_LINEAR_RANGE(MT6360_RANGE_AICR, 100000, 0, 0x3F, 50000),
MT6360_LINEAR_RANGE(MT6360_RANGE_IPREC, 100000, 0, 0x0F, 50000),
MT6360_LINEAR_RANGE(MT6360_RANGE_IEOC, 100000, 0, 0x0F, 50000),
};
struct mt6360_chg_info {
struct device *dev;
struct regmap *regmap;
struct power_supply_desc psy_desc;
struct power_supply *psy;
struct regulator_dev *otg_rdev;
struct mutex chgdet_lock;
u32 vinovp;
bool pwr_rdy;
bool bc12_en;
int psy_usb_type;
struct work_struct chrdet_work;
};
enum mt6360_iinlmtsel {
MT6360_IINLMTSEL_AICR_3250 = 0,
MT6360_IINLMTSEL_CHG_TYPE,
MT6360_IINLMTSEL_AICR,
MT6360_IINLMTSEL_LOWER_LEVEL,
};
enum mt6360_pmu_chg_type {
MT6360_CHG_TYPE_NOVBUS = 0,
MT6360_CHG_TYPE_UNDER_GOING,
MT6360_CHG_TYPE_SDP,
MT6360_CHG_TYPE_SDPNSTD,
MT6360_CHG_TYPE_DCP,
MT6360_CHG_TYPE_CDP,
MT6360_CHG_TYPE_DISABLE_BC12,
MT6360_CHG_TYPE_MAX,
};
static enum power_supply_usb_type mt6360_charger_usb_types[] = {
POWER_SUPPLY_USB_TYPE_UNKNOWN,
POWER_SUPPLY_USB_TYPE_SDP,
POWER_SUPPLY_USB_TYPE_DCP,
POWER_SUPPLY_USB_TYPE_CDP,
};
static int mt6360_get_chrdet_ext_stat(struct mt6360_chg_info *mci,
bool *pwr_rdy)
{
int ret;
unsigned int regval;
ret = regmap_read(mci->regmap, MT6360_PMU_FOD_STAT, &regval);
if (ret < 0)
return ret;
*pwr_rdy = (regval & MT6360_CHRDET_EXT_MASK) ? true : false;
return 0;
}
static int mt6360_charger_get_online(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
bool pwr_rdy;
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
if (ret < 0)
return ret;
val->intval = pwr_rdy ? true : false;
return 0;
}
static int mt6360_charger_get_status(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int status, ret;
unsigned int regval;
bool pwr_rdy;
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
if (ret < 0)
return ret;
if (!pwr_rdy) {
status = POWER_SUPPLY_STATUS_DISCHARGING;
goto out;
}
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT, &regval);
if (ret < 0)
return ret;
regval &= MT6360_CHG_STAT_MASK;
regval >>= MT6360_CHG_STAT_SHFT;
switch (regval) {
case 0x0:
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case 0x1:
status = POWER_SUPPLY_STATUS_CHARGING;
break;
case 0x2:
status = POWER_SUPPLY_STATUS_FULL;
break;
default:
ret = -EIO;
}
out:
if (!ret)
val->intval = status;
return ret;
}
static int mt6360_charger_get_charge_type(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int type, ret;
unsigned int regval;
u8 chg_stat;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_STAT, &regval);
if (ret < 0)
return ret;
chg_stat = (regval & MT6360_CHG_STAT_MASK) >> MT6360_CHG_STAT_SHFT;
switch (chg_stat) {
case 0x01: /* Charge in Progress */
if (regval & MT6360_VBAT_LVL_MASK)
type = POWER_SUPPLY_CHARGE_TYPE_FAST;
else
type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case 0x00: /* Not Charging */
case 0x02: /* Charge Done */
case 0x03: /* Charge Fault */
default:
type = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
}
val->intval = type;
return 0;
}
static int mt6360_charger_get_ichg(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
u32 sel, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL7, &sel);
if (ret < 0)
return ret;
sel = (sel & MT6360_ICHG_MASK) >> MT6360_ICHG_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_ICHG], sel, &value);
if (!ret)
val->intval = value;
return ret;
}
static int mt6360_charger_get_max_ichg(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
val->intval = MT6360_ICHG_MAX;
return 0;
}
static int mt6360_charger_get_cv(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
u32 sel, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL4, &sel);
if (ret < 0)
return ret;
sel = (sel & MT6360_VOREG_MASK) >> MT6360_VOREG_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_VOREG], sel, &value);
if (!ret)
val->intval = value;
return ret;
}
static int mt6360_charger_get_max_cv(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
val->intval = MT6360_VOREG_MAX;
return 0;
}
static int mt6360_charger_get_aicr(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
u32 sel, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL3, &sel);
if (ret < 0)
return ret;
sel = (sel & MT6360_IAICR_MASK) >> MT6360_IAICR_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_AICR], sel, &value);
if (!ret)
val->intval = value;
return ret;
}
static int mt6360_charger_get_mivr(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
u32 sel, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL6, &sel);
if (ret < 0)
return ret;
sel = (sel & MT6360_VMIVR_MASK) >> MT6360_VMIVR_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_VMIVR], sel, &value);
if (!ret)
val->intval = value;
return ret;
}
static int mt6360_charger_get_iprechg(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
u32 sel, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL8, &sel);
if (ret < 0)
return ret;
sel = (sel & MT6360_IPREC_MASK) >> MT6360_IPREC_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_IPREC], sel, &value);
if (!ret)
val->intval = value;
return ret;
}
static int mt6360_charger_get_ieoc(struct mt6360_chg_info *mci,
union power_supply_propval *val)
{
int ret;
u32 sel, value;
ret = regmap_read(mci->regmap, MT6360_PMU_CHG_CTRL9, &sel);
if (ret < 0)
return ret;
sel = (sel & MT6360_IEOC_MASK) >> MT6360_IEOC_SHFT;
ret = linear_range_get_value(&mt6360_chg_range[MT6360_RANGE_IEOC], sel, &value);
if (!ret)
val->intval = value;
return ret;
}
static int mt6360_charger_set_online(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u8 force_sleep = val->intval ? 0 : 1;
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL1,
MT6360_FSLP_MASK,
force_sleep << MT6360_FSLP_SHFT);
}
static int mt6360_charger_set_ichg(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u32 sel;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_ICHG], val->intval, &sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL7,
MT6360_ICHG_MASK,
sel << MT6360_ICHG_SHFT);
}
static int mt6360_charger_set_cv(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u32 sel;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_VOREG], val->intval, &sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL4,
MT6360_VOREG_MASK,
sel << MT6360_VOREG_SHFT);
}
static int mt6360_charger_set_aicr(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u32 sel;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_AICR], val->intval, &sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL3,
MT6360_IAICR_MASK,
sel << MT6360_IAICR_SHFT);
}
static int mt6360_charger_set_mivr(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u32 sel;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_VMIVR], val->intval, &sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL3,
MT6360_VMIVR_MASK,
sel << MT6360_VMIVR_SHFT);
}
static int mt6360_charger_set_iprechg(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u32 sel;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_IPREC], val->intval, &sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL8,
MT6360_IPREC_MASK,
sel << MT6360_IPREC_SHFT);
}
static int mt6360_charger_set_ieoc(struct mt6360_chg_info *mci,
const union power_supply_propval *val)
{
u32 sel;
linear_range_get_selector_within(&mt6360_chg_range[MT6360_RANGE_IEOC], val->intval, &sel);
return regmap_update_bits(mci->regmap,
MT6360_PMU_CHG_CTRL9,
MT6360_IEOC_MASK,
sel << MT6360_IEOC_SHFT);
}
static int mt6360_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct mt6360_chg_info *mci = power_supply_get_drvdata(psy);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = mt6360_charger_get_online(mci, val);
break;
case POWER_SUPPLY_PROP_STATUS:
ret = mt6360_charger_get_status(mci, val);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = mt6360_charger_get_charge_type(mci, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = mt6360_charger_get_ichg(mci, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
ret = mt6360_charger_get_max_ichg(mci, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = mt6360_charger_get_cv(mci, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
ret = mt6360_charger_get_max_cv(mci, val);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = mt6360_charger_get_aicr(mci, val);
break;
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
ret = mt6360_charger_get_mivr(mci, val);
break;
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
ret = mt6360_charger_get_iprechg(mci, val);
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = mt6360_charger_get_ieoc(mci, val);
break;
case POWER_SUPPLY_PROP_USB_TYPE:
val->intval = mci->psy_usb_type;
break;
default:
ret = -ENODATA;
}
return ret;
}
static int mt6360_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct mt6360_chg_info *mci = power_supply_get_drvdata(psy);
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = mt6360_charger_set_online(mci, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = mt6360_charger_set_ichg(mci, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = mt6360_charger_set_cv(mci, val);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = mt6360_charger_set_aicr(mci, val);
break;
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
ret = mt6360_charger_set_mivr(mci, val);
break;
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
ret = mt6360_charger_set_iprechg(mci, val);
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = mt6360_charger_set_ieoc(mci, val);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int mt6360_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return 1;
default:
return 0;
}
}
static enum power_supply_property mt6360_charger_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_USB_TYPE,
};
static const struct power_supply_desc mt6360_charger_desc = {
.type = POWER_SUPPLY_TYPE_USB,
.properties = mt6360_charger_properties,
.num_properties = ARRAY_SIZE(mt6360_charger_properties),
.get_property = mt6360_charger_get_property,
.set_property = mt6360_charger_set_property,
.property_is_writeable = mt6360_charger_property_is_writeable,
.usb_types = mt6360_charger_usb_types,
.num_usb_types = ARRAY_SIZE(mt6360_charger_usb_types),
};
static const struct regulator_ops mt6360_chg_otg_ops = {
.list_voltage = regulator_list_voltage_linear,
.enable = regulator_enable_regmap,
.disable = regulator_disable_regmap,
.is_enabled = regulator_is_enabled_regmap,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
};
static const struct regulator_desc mt6360_otg_rdesc = {
.of_match = "usb-otg-vbus",
.name = "usb-otg-vbus",
.ops = &mt6360_chg_otg_ops,
.owner = THIS_MODULE,
.type = REGULATOR_VOLTAGE,
.min_uV = 4425000,
.uV_step = 25000,
.n_voltages = 57,
.vsel_reg = MT6360_PMU_CHG_CTRL5,
.vsel_mask = MT6360_VOBST_MASK,
.enable_reg = MT6360_PMU_CHG_CTRL1,
.enable_mask = MT6360_OPA_MODE_MASK,
};
static irqreturn_t mt6360_pmu_attach_i_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
int ret;
unsigned int usb_status;
int last_usb_type;
mutex_lock(&mci->chgdet_lock);
if (!mci->bc12_en) {
dev_warn(mci->dev, "Received attach interrupt, bc12 disabled, ignore irq\n");
goto out;
}
last_usb_type = mci->psy_usb_type;
/* Plug in */
ret = regmap_read(mci->regmap, MT6360_PMU_USB_STATUS1, &usb_status);
if (ret < 0)
goto out;
usb_status &= MT6360_USB_STATUS_MASK;
usb_status >>= MT6360_USB_STATUS_SHFT;
switch (usb_status) {
case MT6360_CHG_TYPE_NOVBUS:
dev_dbg(mci->dev, "Received attach interrupt, no vbus\n");
goto out;
case MT6360_CHG_TYPE_UNDER_GOING:
dev_dbg(mci->dev, "Received attach interrupt, under going...\n");
goto out;
case MT6360_CHG_TYPE_SDP:
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
break;
case MT6360_CHG_TYPE_SDPNSTD:
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
break;
case MT6360_CHG_TYPE_CDP:
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP;
break;
case MT6360_CHG_TYPE_DCP:
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
break;
case MT6360_CHG_TYPE_DISABLE_BC12:
dev_dbg(mci->dev, "Received attach interrupt, bc12 detect not enable\n");
goto out;
default:
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
dev_dbg(mci->dev, "Received attach interrupt, reserved address\n");
goto out;
}
dev_dbg(mci->dev, "Received attach interrupt, chg_type = %d\n", mci->psy_usb_type);
if (last_usb_type != mci->psy_usb_type)
power_supply_changed(mci->psy);
out:
mutex_unlock(&mci->chgdet_lock);
return IRQ_HANDLED;
}
static void mt6360_handle_chrdet_ext_evt(struct mt6360_chg_info *mci)
{
int ret;
bool pwr_rdy;
mutex_lock(&mci->chgdet_lock);
ret = mt6360_get_chrdet_ext_stat(mci, &pwr_rdy);
if (ret < 0)
goto out;
if (mci->pwr_rdy == pwr_rdy) {
dev_dbg(mci->dev, "Received vbus interrupt, pwr_rdy is same(%d)\n", pwr_rdy);
goto out;
}
mci->pwr_rdy = pwr_rdy;
dev_dbg(mci->dev, "Received vbus interrupt, pwr_rdy = %d\n", pwr_rdy);
if (!pwr_rdy) {
mci->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
power_supply_changed(mci->psy);
}
ret = regmap_update_bits(mci->regmap,
MT6360_PMU_DEVICE_TYPE,
MT6360_USBCHGEN_MASK,
pwr_rdy ? MT6360_USBCHGEN_MASK : 0);
if (ret < 0)
goto out;
mci->bc12_en = pwr_rdy;
out:
mutex_unlock(&mci->chgdet_lock);
}
static void mt6360_chrdet_work(struct work_struct *work)
{
struct mt6360_chg_info *mci = (struct mt6360_chg_info *)container_of(
work, struct mt6360_chg_info, chrdet_work);
mt6360_handle_chrdet_ext_evt(mci);
}
static irqreturn_t mt6360_pmu_chrdet_ext_evt_handler(int irq, void *data)
{
struct mt6360_chg_info *mci = data;
mt6360_handle_chrdet_ext_evt(mci);
return IRQ_HANDLED;
}
static int mt6360_chg_irq_register(struct platform_device *pdev)
{
const struct {
const char *name;
irq_handler_t handler;
} irq_descs[] = {
{ "attach_i", mt6360_pmu_attach_i_handler },
{ "chrdet_ext_evt", mt6360_pmu_chrdet_ext_evt_handler }
};
int i, ret;
for (i = 0; i < ARRAY_SIZE(irq_descs); i++) {
ret = platform_get_irq_byname(pdev, irq_descs[i].name);
if (ret < 0)
return ret;
ret = devm_request_threaded_irq(&pdev->dev, ret, NULL,
irq_descs[i].handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
irq_descs[i].name,
platform_get_drvdata(pdev));
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to request %s irq\n",
irq_descs[i].name);
}
return 0;
}
static u32 mt6360_vinovp_trans_to_sel(u32 val)
{
u32 vinovp_tbl[] = { 5500000, 6500000, 11000000, 14500000 };
int i;
/* Select the smaller and equal supported value */
for (i = 0; i < ARRAY_SIZE(vinovp_tbl)-1; i++) {
if (val < vinovp_tbl[i+1])
break;
}
return i;
}
static int mt6360_chg_init_setting(struct mt6360_chg_info *mci)
{
int ret;
u32 sel;
sel = mt6360_vinovp_trans_to_sel(mci->vinovp);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL19,
MT6360_VINOVP_MASK, sel << MT6360_VINOVP_SHFT);
if (ret)
return dev_err_probe(mci->dev, ret, "%s: Failed to apply vinovp\n", __func__);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_DEVICE_TYPE,
MT6360_USBCHGEN_MASK, 0);
if (ret)
return dev_err_probe(mci->dev, ret, "%s: Failed to disable bc12\n", __func__);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL2,
MT6360_IINLMTSEL_MASK,
MT6360_IINLMTSEL_AICR <<
MT6360_IINLMTSEL_SHFT);
if (ret)
return dev_err_probe(mci->dev, ret,
"%s: Failed to switch iinlmtsel to aicr\n", __func__);
usleep_range(5000, 6000);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL3,
MT6360_ILIM_EN_MASK, 0);
if (ret)
return dev_err_probe(mci->dev, ret,
"%s: Failed to disable ilim\n", __func__);
ret = regmap_update_bits(mci->regmap, MT6360_PMU_CHG_CTRL10,
MT6360_OTG_OC_MASK, MT6360_OTG_OC_MASK);
if (ret)
return dev_err_probe(mci->dev, ret,
"%s: Failed to config otg oc to 3A\n", __func__);
return 0;
}
static int mt6360_charger_probe(struct platform_device *pdev)
{
struct mt6360_chg_info *mci;
struct power_supply_config charger_cfg = {};
struct regulator_config config = { };
int ret;
mci = devm_kzalloc(&pdev->dev, sizeof(*mci), GFP_KERNEL);
if (!mci)
return -ENOMEM;
mci->dev = &pdev->dev;
mci->vinovp = 6500000;
mutex_init(&mci->chgdet_lock);
platform_set_drvdata(pdev, mci);
devm_work_autocancel(&pdev->dev, &mci->chrdet_work, mt6360_chrdet_work);
ret = device_property_read_u32(&pdev->dev, "richtek,vinovp-microvolt", &mci->vinovp);
if (ret)
dev_warn(&pdev->dev, "Failed to parse vinovp in DT, keep default 6.5v\n");
mci->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!mci->regmap)
return dev_err_probe(&pdev->dev, -ENODEV, "Failed to get parent regmap\n");
ret = mt6360_chg_init_setting(mci);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to initial setting\n");
memcpy(&mci->psy_desc, &mt6360_charger_desc, sizeof(mci->psy_desc));
mci->psy_desc.name = dev_name(&pdev->dev);
charger_cfg.drv_data = mci;
charger_cfg.of_node = pdev->dev.of_node;
mci->psy = devm_power_supply_register(&pdev->dev,
&mci->psy_desc, &charger_cfg);
if (IS_ERR(mci->psy))
return dev_err_probe(&pdev->dev, PTR_ERR(mci->psy),
"Failed to register power supply dev\n");
ret = mt6360_chg_irq_register(pdev);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to register irqs\n");
config.dev = &pdev->dev;
config.regmap = mci->regmap;
mci->otg_rdev = devm_regulator_register(&pdev->dev, &mt6360_otg_rdesc,
&config);
if (IS_ERR(mci->otg_rdev))
return PTR_ERR(mci->otg_rdev);
schedule_work(&mci->chrdet_work);
return 0;
}
static const struct of_device_id __maybe_unused mt6360_charger_of_id[] = {
{ .compatible = "mediatek,mt6360-chg", },
{},
};
MODULE_DEVICE_TABLE(of, mt6360_charger_of_id);
static const struct platform_device_id mt6360_charger_id[] = {
{ "mt6360-chg", 0 },
{},
};
MODULE_DEVICE_TABLE(platform, mt6360_charger_id);
static struct platform_driver mt6360_charger_driver = {
.driver = {
.name = "mt6360-chg",
.of_match_table = of_match_ptr(mt6360_charger_of_id),
},
.probe = mt6360_charger_probe,
.id_table = mt6360_charger_id,
};
module_platform_driver(mt6360_charger_driver);
MODULE_AUTHOR("Gene Chen <gene_chen@richtek.com>");
MODULE_DESCRIPTION("MT6360 Charger Driver");
MODULE_LICENSE("GPL");

View File

@ -571,6 +571,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
int err, len, index;
const __be32 *list;
info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
info->energy_full_design_uwh = -EINVAL;
info->charge_full_design_uah = -EINVAL;
info->voltage_min_design_uv = -EINVAL;
@ -618,6 +619,24 @@ int power_supply_get_battery_info(struct power_supply *psy,
* Documentation/power/power_supply_class.rst.
*/
if (!of_property_read_string(battery_np, "device-chemistry", &value)) {
if (!strcmp("nickel-cadmium", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
else if (!strcmp("nickel-metal-hydride", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
else if (!strcmp("lithium-ion", value))
/* Imprecise lithium-ion type */
info->technology = POWER_SUPPLY_TECHNOLOGY_LION;
else if (!strcmp("lithium-ion-polymer", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_LIPO;
else if (!strcmp("lithium-ion-iron-phosphate", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_LiFe;
else if (!strcmp("lithium-ion-manganese-oxide", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
else
dev_warn(&psy->dev, "%s unknown battery type\n", value);
}
of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
&info->energy_full_design_uwh);
of_property_read_u32(battery_np, "charge-full-design-microamp-hours",

View File

@ -929,11 +929,8 @@ static int smbb_charger_probe(struct platform_device *pdev)
int irq;
irq = platform_get_irq_byname(pdev, smbb_charger_irqs[i].name);
if (irq < 0) {
dev_err(&pdev->dev, "failed to get irq '%s'\n",
smbb_charger_irqs[i].name);
if (irq < 0)
return irq;
}
smbb_charger_irqs[i].handler(irq, chg);

View File

@ -9,10 +9,12 @@
#include <linux/device.h>
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/iio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mfd/rn5t618.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
@ -64,6 +66,8 @@ struct rn5t618_power_info {
struct power_supply *battery;
struct power_supply *usb;
struct power_supply *adp;
struct iio_channel *channel_vusb;
struct iio_channel *channel_vadp;
int irq;
};
@ -77,6 +81,7 @@ static enum power_supply_usb_type rn5t618_usb_types[] = {
static enum power_supply_property rn5t618_usb_props[] = {
/* input current limit is not very accurate */
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_ONLINE,
@ -85,6 +90,7 @@ static enum power_supply_property rn5t618_usb_props[] = {
static enum power_supply_property rn5t618_adp_props[] = {
/* input current limit is not very accurate */
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
};
@ -463,6 +469,15 @@ static int rn5t618_adp_get_property(struct power_supply *psy,
return ret;
val->intval = FROM_CUR_REG(regval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
if (!info->channel_vadp)
return -ENODATA;
ret = iio_read_channel_processed_scale(info->channel_vadp, &val->intval, 1000);
if (ret < 0)
return ret;
break;
default:
return -EINVAL;
@ -588,6 +603,15 @@ static int rn5t618_usb_get_property(struct power_supply *psy,
val->intval = FROM_CUR_REG(regval);
}
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
if (!info->channel_vusb)
return -ENODATA;
ret = iio_read_channel_processed_scale(info->channel_vusb, &val->intval, 1000);
if (ret < 0)
return ret;
break;
default:
return -EINVAL;
@ -711,6 +735,20 @@ static int rn5t618_power_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, info);
info->channel_vusb = devm_iio_channel_get(&pdev->dev, "vusb");
if (IS_ERR(info->channel_vusb)) {
if (PTR_ERR(info->channel_vusb) == -ENODEV)
return -EPROBE_DEFER;
return PTR_ERR(info->channel_vusb);
}
info->channel_vadp = devm_iio_channel_get(&pdev->dev, "vadp");
if (IS_ERR(info->channel_vadp)) {
if (PTR_ERR(info->channel_vadp) == -ENODEV)
return -EPROBE_DEFER;
return PTR_ERR(info->channel_vadp);
}
ret = regmap_read(info->rn5t618->regmap, RN5T618_CONTROL, &v);
if (ret)
return ret;

View File

@ -31,8 +31,9 @@ enum {
REG_CURRENT_AVG,
REG_MAX_ERR,
REG_CAPACITY,
REG_TIME_TO_EMPTY,
REG_TIME_TO_FULL,
REG_TIME_TO_EMPTY_NOW,
REG_TIME_TO_EMPTY_AVG,
REG_TIME_TO_FULL_AVG,
REG_STATUS,
REG_CAPACITY_LEVEL,
REG_CYCLE_COUNT,
@ -102,7 +103,7 @@ static const struct chip_data {
[REG_TEMPERATURE] =
SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535),
[REG_VOLTAGE] =
SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000),
SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 65535),
[REG_CURRENT_NOW] =
SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767),
[REG_CURRENT_AVG] =
@ -119,9 +120,11 @@ static const struct chip_data {
SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535),
[REG_FULL_CHARGE_CAPACITY_CHARGE] =
SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535),
[REG_TIME_TO_EMPTY] =
[REG_TIME_TO_EMPTY_NOW] =
SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 0x11, 0, 65535),
[REG_TIME_TO_EMPTY_AVG] =
SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535),
[REG_TIME_TO_FULL] =
[REG_TIME_TO_FULL_AVG] =
SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535),
[REG_CHARGE_CURRENT] =
SBS_DATA(POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, 0x14, 0, 65535),
@ -165,6 +168,7 @@ static const enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
@ -748,6 +752,7 @@ static void sbs_unit_adjustment(struct i2c_client *client,
val->intval -= TEMP_KELVIN_TO_CELSIUS;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
/* sbs provides time to empty and time to full in minutes.
@ -966,6 +971,7 @@ static int sbs_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG:
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:

View File

@ -1229,10 +1229,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev)
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "no irq resource specified\n");
if (irq < 0)
return irq;
}
ret = devm_request_threaded_irq(data->dev, irq, NULL,
sc27xx_fgu_interrupt,

View File

@ -18,6 +18,7 @@
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <dt-bindings/power/summit,smb347-charger.h>
@ -55,6 +56,7 @@
#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60
#define CFG_PIN_EN_APSD_IRQ BIT(1)
#define CFG_PIN_EN_CHARGER_ERROR BIT(2)
#define CFG_PIN_EN_CTRL BIT(4)
#define CFG_THERM 0x07
#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03
#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0
@ -62,12 +64,15 @@
#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2
#define CFG_THERM_MONITOR_DISABLED BIT(4)
#define CFG_SYSOK 0x08
#define CFG_SYSOK_INOK_ACTIVE_HIGH BIT(0)
#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2)
#define CFG_OTHER 0x09
#define CFG_OTHER_RID_MASK 0xc0
#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0
#define CFG_OTG 0x0a
#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30
#define CFG_OTG_CURRENT_LIMIT_250mA BIT(2)
#define CFG_OTG_CURRENT_LIMIT_750mA BIT(3)
#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4
#define CFG_OTG_CC_COMPENSATION_MASK 0xc0
#define CFG_OTG_CC_COMPENSATION_SHIFT 6
@ -91,6 +96,7 @@
#define CMD_A 0x30
#define CMD_A_CHG_ENABLED BIT(1)
#define CMD_A_SUSPEND_ENABLED BIT(2)
#define CMD_A_OTG_ENABLED BIT(4)
#define CMD_A_ALLOW_WRITE BIT(7)
#define CMD_B 0x31
#define CMD_C 0x33
@ -132,11 +138,12 @@
* @regmap: pointer to driver regmap
* @mains: power_supply instance for AC/DC power
* @usb: power_supply instance for USB power
* @usb_rdev: USB VBUS regulator device
* @id: SMB charger ID
* @mains_online: is AC/DC input connected
* @usb_online: is USB input connected
* @charging_enabled: is charging enabled
* @irq_unsupported: is interrupt unsupported by SMB hardware
* @usb_vbus_enabled: is USB VBUS powered by SMB charger
* @max_charge_current: maximum current (in uA) the battery can be charged
* @max_charge_voltage: maximum voltage (in uV) the battery can be charged
* @pre_charge_current: current (in uA) to use in pre-charging phase
@ -167,6 +174,8 @@
* @use_usb_otg: USB OTG output can be used (not implemented yet)
* @enable_control: how charging enable/disable is controlled
* (driver/pin controls)
* @inok_polarity: polarity of INOK signal which denotes presence of external
* power supply
*
* @use_main, @use_usb, and @use_usb_otg are means to enable/disable
* hardware support for these. This is useful when we want to have for
@ -189,11 +198,12 @@ struct smb347_charger {
struct regmap *regmap;
struct power_supply *mains;
struct power_supply *usb;
struct regulator_dev *usb_rdev;
unsigned int id;
bool mains_online;
bool usb_online;
bool charging_enabled;
bool irq_unsupported;
bool usb_vbus_enabled;
unsigned int max_charge_current;
unsigned int max_charge_voltage;
@ -214,6 +224,7 @@ struct smb347_charger {
bool use_usb;
bool use_usb_otg;
unsigned int enable_control;
unsigned int inok_polarity;
};
enum smb_charger_chipid {
@ -358,21 +369,18 @@ static int smb347_charging_status(struct smb347_charger *smb)
static int smb347_charging_set(struct smb347_charger *smb, bool enable)
{
int ret = 0;
if (smb->enable_control != SMB3XX_CHG_ENABLE_SW) {
dev_dbg(smb->dev, "charging enable/disable in SW disabled\n");
return 0;
}
if (smb->charging_enabled != enable) {
ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED,
enable ? CMD_A_CHG_ENABLED : 0);
if (!ret)
smb->charging_enabled = enable;
if (enable && smb->usb_vbus_enabled) {
dev_dbg(smb->dev, "charging not enabled because USB is in host mode\n");
return 0;
}
return ret;
return regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED,
enable ? CMD_A_CHG_ENABLED : 0);
}
static inline int smb347_charging_enable(struct smb347_charger *smb)
@ -671,10 +679,22 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
*
* Returns %0 on success and negative errno in case of failure.
*/
static int smb347_set_writable(struct smb347_charger *smb, bool writable)
static int smb347_set_writable(struct smb347_charger *smb, bool writable,
bool irq_toggle)
{
return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE,
struct i2c_client *client = to_i2c_client(smb->dev);
int ret;
if (writable && irq_toggle && !smb->irq_unsupported)
disable_irq(client->irq);
ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE,
writable ? CMD_A_ALLOW_WRITE : 0);
if ((!writable || ret) && irq_toggle && !smb->irq_unsupported)
enable_irq(client->irq);
return ret;
}
static int smb347_hw_init(struct smb347_charger *smb)
@ -682,7 +702,7 @@ static int smb347_hw_init(struct smb347_charger *smb)
unsigned int val;
int ret;
ret = smb347_set_writable(smb, true);
ret = smb347_set_writable(smb, true, false);
if (ret < 0)
return ret;
@ -724,6 +744,15 @@ static int smb347_hw_init(struct smb347_charger *smb)
if (ret < 0)
goto fail;
/* Activate pin control, making it writable. */
switch (smb->enable_control) {
case SMB3XX_CHG_ENABLE_PIN_ACTIVE_LOW:
case SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH:
ret = regmap_set_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL);
if (ret < 0)
goto fail;
}
/*
* Make the charging functionality controllable by a write to the
* command register unless pin control is specified in the platform
@ -758,7 +787,7 @@ static int smb347_hw_init(struct smb347_charger *smb)
ret = smb347_start_stop_charging(smb);
fail:
smb347_set_writable(smb, false);
smb347_set_writable(smb, false, false);
return ret;
}
@ -866,7 +895,7 @@ static int smb347_irq_set(struct smb347_charger *smb, bool enable)
if (smb->irq_unsupported)
return 0;
ret = smb347_set_writable(smb, true);
ret = smb347_set_writable(smb, true, true);
if (ret < 0)
return ret;
@ -891,7 +920,7 @@ static int smb347_irq_set(struct smb347_charger *smb, bool enable)
ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR,
enable ? CFG_PIN_EN_CHARGER_ERROR : 0);
fail:
smb347_set_writable(smb, false);
smb347_set_writable(smb, false, true);
return ret;
}
@ -919,7 +948,7 @@ static int smb347_irq_init(struct smb347_charger *smb,
if (!client->irq)
return 0;
ret = smb347_set_writable(smb, true);
ret = smb347_set_writable(smb, true, false);
if (ret < 0)
return ret;
@ -931,7 +960,7 @@ static int smb347_irq_init(struct smb347_charger *smb,
CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED,
CFG_STAT_DISABLED);
smb347_set_writable(smb, false);
smb347_set_writable(smb, false, false);
if (ret < 0) {
dev_warn(smb->dev, "failed to initialize IRQ: %d\n", ret);
@ -1241,6 +1270,13 @@ static void smb347_dt_parse_dev_info(struct smb347_charger *smb)
/* Select charging control */
device_property_read_u32(dev, "summit,enable-charge-control",
&smb->enable_control);
/*
* Polarity of INOK signal indicating presence of external power
* supply connected to the charger.
*/
device_property_read_u32(dev, "summit,inok-polarity",
&smb->inok_polarity);
}
static int smb347_get_battery_info(struct smb347_charger *smb)
@ -1292,12 +1328,176 @@ static int smb347_get_battery_info(struct smb347_charger *smb)
return 0;
}
static int smb347_usb_vbus_get_current_limit(struct regulator_dev *rdev)
{
struct smb347_charger *smb = rdev_get_drvdata(rdev);
unsigned int val;
int ret;
ret = regmap_read(smb->regmap, CFG_OTG, &val);
if (ret < 0)
return ret;
/*
* It's unknown what happens if this bit is unset due to lack of
* access to the datasheet, assume it's limit-enable.
*/
if (!(val & CFG_OTG_CURRENT_LIMIT_250mA))
return 0;
return val & CFG_OTG_CURRENT_LIMIT_750mA ? 750000 : 250000;
}
static int smb347_usb_vbus_set_new_current_limit(struct smb347_charger *smb,
int max_uA)
{
const unsigned int mask = CFG_OTG_CURRENT_LIMIT_750mA |
CFG_OTG_CURRENT_LIMIT_250mA;
unsigned int val = CFG_OTG_CURRENT_LIMIT_250mA;
int ret;
if (max_uA >= 750000)
val |= CFG_OTG_CURRENT_LIMIT_750mA;
ret = regmap_update_bits(smb->regmap, CFG_OTG, mask, val);
if (ret < 0)
dev_err(smb->dev, "failed to change USB current limit\n");
return ret;
}
static int smb347_usb_vbus_set_current_limit(struct regulator_dev *rdev,
int min_uA, int max_uA)
{
struct smb347_charger *smb = rdev_get_drvdata(rdev);
int ret;
ret = smb347_set_writable(smb, true, true);
if (ret < 0)
return ret;
ret = smb347_usb_vbus_set_new_current_limit(smb, max_uA);
smb347_set_writable(smb, false, true);
return ret;
}
static int smb347_usb_vbus_regulator_enable(struct regulator_dev *rdev)
{
struct smb347_charger *smb = rdev_get_drvdata(rdev);
int ret, max_uA;
ret = smb347_set_writable(smb, true, true);
if (ret < 0)
return ret;
smb347_charging_disable(smb);
if (device_property_read_bool(&rdev->dev, "summit,needs-inok-toggle")) {
unsigned int sysok = 0;
if (smb->inok_polarity == SMB3XX_SYSOK_INOK_ACTIVE_LOW)
sysok = CFG_SYSOK_INOK_ACTIVE_HIGH;
/*
* VBUS won't be powered if INOK is active, so we need to
* manually disable INOK on some platforms.
*/
ret = regmap_update_bits(smb->regmap, CFG_SYSOK,
CFG_SYSOK_INOK_ACTIVE_HIGH, sysok);
if (ret < 0) {
dev_err(smb->dev, "failed to disable INOK\n");
goto done;
}
}
ret = smb347_usb_vbus_get_current_limit(rdev);
if (ret < 0) {
dev_err(smb->dev, "failed to get USB VBUS current limit\n");
goto done;
}
max_uA = ret;
ret = smb347_usb_vbus_set_new_current_limit(smb, 250000);
if (ret < 0) {
dev_err(smb->dev, "failed to preset USB VBUS current limit\n");
goto done;
}
ret = regmap_set_bits(smb->regmap, CMD_A, CMD_A_OTG_ENABLED);
if (ret < 0) {
dev_err(smb->dev, "failed to enable USB VBUS\n");
goto done;
}
smb->usb_vbus_enabled = true;
ret = smb347_usb_vbus_set_new_current_limit(smb, max_uA);
if (ret < 0) {
dev_err(smb->dev, "failed to restore USB VBUS current limit\n");
goto done;
}
done:
smb347_set_writable(smb, false, true);
return ret;
}
static int smb347_usb_vbus_regulator_disable(struct regulator_dev *rdev)
{
struct smb347_charger *smb = rdev_get_drvdata(rdev);
int ret;
ret = smb347_set_writable(smb, true, true);
if (ret < 0)
return ret;
ret = regmap_clear_bits(smb->regmap, CMD_A, CMD_A_OTG_ENABLED);
if (ret < 0) {
dev_err(smb->dev, "failed to disable USB VBUS\n");
goto done;
}
smb->usb_vbus_enabled = false;
if (device_property_read_bool(&rdev->dev, "summit,needs-inok-toggle")) {
unsigned int sysok = 0;
if (smb->inok_polarity == SMB3XX_SYSOK_INOK_ACTIVE_HIGH)
sysok = CFG_SYSOK_INOK_ACTIVE_HIGH;
ret = regmap_update_bits(smb->regmap, CFG_SYSOK,
CFG_SYSOK_INOK_ACTIVE_HIGH, sysok);
if (ret < 0) {
dev_err(smb->dev, "failed to enable INOK\n");
goto done;
}
}
smb347_start_stop_charging(smb);
done:
smb347_set_writable(smb, false, true);
return ret;
}
static const struct regmap_config smb347_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = SMB347_MAX_REGISTER,
.volatile_reg = smb347_volatile_reg,
.readable_reg = smb347_readable_reg,
.cache_type = REGCACHE_FLAT,
.num_reg_defaults_raw = SMB347_MAX_REGISTER,
};
static const struct regulator_ops smb347_usb_vbus_regulator_ops = {
.is_enabled = regulator_is_enabled_regmap,
.enable = smb347_usb_vbus_regulator_enable,
.disable = smb347_usb_vbus_regulator_disable,
.get_current_limit = smb347_usb_vbus_get_current_limit,
.set_current_limit = smb347_usb_vbus_set_current_limit,
};
static const struct power_supply_desc smb347_mains_desc = {
@ -1316,10 +1516,24 @@ static const struct power_supply_desc smb347_usb_desc = {
.num_properties = ARRAY_SIZE(smb347_properties),
};
static const struct regulator_desc smb347_usb_vbus_regulator_desc = {
.name = "smb347-usb-vbus",
.of_match = of_match_ptr("usb-vbus"),
.ops = &smb347_usb_vbus_regulator_ops,
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
.enable_reg = CMD_A,
.enable_mask = CMD_A_OTG_ENABLED,
.enable_val = CMD_A_OTG_ENABLED,
.fixed_uV = 5000000,
.n_voltages = 1,
};
static int smb347_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct power_supply_config mains_usb_cfg = {};
struct regulator_config usb_rdev_cfg = {};
struct device *dev = &client->dev;
struct smb347_charger *smb;
int ret;
@ -1367,6 +1581,18 @@ static int smb347_probe(struct i2c_client *client,
if (ret)
return ret;
usb_rdev_cfg.dev = dev;
usb_rdev_cfg.driver_data = smb;
usb_rdev_cfg.regmap = smb->regmap;
smb->usb_rdev = devm_regulator_register(dev,
&smb347_usb_vbus_regulator_desc,
&usb_rdev_cfg);
if (IS_ERR(smb->usb_rdev)) {
smb347_irq_disable(smb);
return PTR_ERR(smb->usb_rdev);
}
return 0;
}
@ -1374,11 +1600,17 @@ static int smb347_remove(struct i2c_client *client)
{
struct smb347_charger *smb = i2c_get_clientdata(client);
smb347_usb_vbus_regulator_disable(smb->usb_rdev);
smb347_irq_disable(smb);
return 0;
}
static void smb347_shutdown(struct i2c_client *client)
{
smb347_remove(client);
}
static const struct i2c_device_id smb347_id[] = {
{ "smb345", SMB345 },
{ "smb347", SMB347 },
@ -1402,6 +1634,7 @@ static struct i2c_driver smb347_driver = {
},
.probe = smb347_probe,
.remove = smb347_remove,
.shutdown = smb347_shutdown,
.id_table = smb347_id,
};
module_i2c_driver(smb347_driver);

View File

@ -16,4 +16,8 @@
#define SMB3XX_CHG_ENABLE_PIN_ACTIVE_LOW 1
#define SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH 2
/* Polarity of INOK signal */
#define SMB3XX_SYSOK_INOK_ACTIVE_LOW 0
#define SMB3XX_SYSOK_INOK_ACTIVE_HIGH 1
#endif

View File

@ -41,6 +41,8 @@ int linear_range_get_selector_low(const struct linear_range *r,
int linear_range_get_selector_high(const struct linear_range *r,
unsigned int val, unsigned int *selector,
bool *found);
void linear_range_get_selector_within(const struct linear_range *r,
unsigned int val, unsigned int *selector);
int linear_range_get_selector_low_array(const struct linear_range *r,
int ranges, unsigned int val,
unsigned int *selector, bool *found);

View File

@ -4228,6 +4228,7 @@ enum ec_device_event {
EC_DEVICE_EVENT_TRACKPAD,
EC_DEVICE_EVENT_DSP,
EC_DEVICE_EVENT_WIFI,
EC_DEVICE_EVENT_WLC,
};
enum ec_device_event_param {
@ -5460,6 +5461,72 @@ struct ec_response_rollback_info {
/* Issue AP reset */
#define EC_CMD_AP_RESET 0x0125
/**
* Get the number of peripheral charge ports
*/
#define EC_CMD_PCHG_COUNT 0x0134
#define EC_PCHG_MAX_PORTS 8
struct ec_response_pchg_count {
uint8_t port_count;
} __ec_align1;
/**
* Get the status of a peripheral charge port
*/
#define EC_CMD_PCHG 0x0135
struct ec_params_pchg {
uint8_t port;
} __ec_align1;
struct ec_response_pchg {
uint32_t error; /* enum pchg_error */
uint8_t state; /* enum pchg_state state */
uint8_t battery_percentage;
uint8_t unused0;
uint8_t unused1;
/* Fields added in version 1 */
uint32_t fw_version;
uint32_t dropped_event_count;
} __ec_align2;
enum pchg_state {
/* Charger is reset and not initialized. */
PCHG_STATE_RESET = 0,
/* Charger is initialized or disabled. */
PCHG_STATE_INITIALIZED,
/* Charger is enabled and ready to detect a device. */
PCHG_STATE_ENABLED,
/* Device is in proximity. */
PCHG_STATE_DETECTED,
/* Device is being charged. */
PCHG_STATE_CHARGING,
/* Device is fully charged. It implies DETECTED (& not charging). */
PCHG_STATE_FULL,
/* In download (a.k.a. firmware update) mode */
PCHG_STATE_DOWNLOAD,
/* In download mode. Ready for receiving data. */
PCHG_STATE_DOWNLOADING,
/* Device is ready for data communication. */
PCHG_STATE_CONNECTED,
/* Put no more entry below */
PCHG_STATE_COUNT,
};
#define EC_PCHG_STATE_TEXT { \
[PCHG_STATE_RESET] = "RESET", \
[PCHG_STATE_INITIALIZED] = "INITIALIZED", \
[PCHG_STATE_ENABLED] = "ENABLED", \
[PCHG_STATE_DETECTED] = "DETECTED", \
[PCHG_STATE_CHARGING] = "CHARGING", \
[PCHG_STATE_FULL] = "FULL", \
[PCHG_STATE_DOWNLOAD] = "DOWNLOAD", \
[PCHG_STATE_DOWNLOADING] = "DOWNLOADING", \
[PCHG_STATE_CONNECTED] = "CONNECTED", \
}
/*****************************************************************************/
/* Voltage regulator controls */

View File

@ -69,7 +69,7 @@ enum max17042_register {
MAX17042_RelaxCFG = 0x2A,
MAX17042_MiscCFG = 0x2B,
MAX17042_TGAIN = 0x2C,
MAx17042_TOFF = 0x2D,
MAX17042_TOFF = 0x2D,
MAX17042_CGAIN = 0x2E,
MAX17042_COFF = 0x2F,
@ -110,13 +110,14 @@ enum max17042_register {
MAX17042_VFSOC = 0xFF,
};
/* Registers specific to max17055 only */
enum max17055_register {
MAX17055_QRes = 0x0C,
MAX17055_RCell = 0x14,
MAX17055_TTF = 0x20,
MAX17055_V_empty = 0x3A,
MAX17055_TIMER = 0x3E,
MAX17055_DieTemp = 0x34,
MAX17055_USER_MEM = 0x40,
MAX17055_RGAIN = 0x42,
MAX17055_RGAIN = 0x43,
MAX17055_ConvgCfg = 0x49,
MAX17055_VFRemCap = 0x4A,
@ -155,13 +156,14 @@ enum max17055_register {
MAX17055_AtAvCap = 0xDF,
};
/* Registers specific to max17047/50 */
/* Registers specific to max17047/50/55 */
enum max17047_register {
MAX17047_QRTbl00 = 0x12,
MAX17047_FullSOCThr = 0x13,
MAX17047_QRTbl10 = 0x22,
MAX17047_QRTbl20 = 0x32,
MAX17047_V_empty = 0x3A,
MAX17047_TIMER = 0x3E,
MAX17047_QRTbl30 = 0x42,
};

View File

@ -352,6 +352,7 @@ struct power_supply_resistance_temp_table {
*/
struct power_supply_battery_info {
unsigned int technology; /* from the enum above */
int energy_full_design_uwh; /* microWatt-hours */
int charge_full_design_uah; /* microAmp-hours */
int voltage_min_design_uv; /* microVolts */

View File

@ -241,5 +241,36 @@ int linear_range_get_selector_high(const struct linear_range *r,
}
EXPORT_SYMBOL_GPL(linear_range_get_selector_high);
/**
* linear_range_get_selector_within - return linear range selector for value
* @r: pointer to linear range where selector is looked from
* @val: value for which the selector is searched
* @selector: address where found selector value is updated
*
* Return selector for which range value is closest match for given
* input value. Value is matching if it is equal or lower than given
* value. But return maximum selector if given value is higher than
* maximum value.
*/
void linear_range_get_selector_within(const struct linear_range *r,
unsigned int val, unsigned int *selector)
{
if (r->min > val) {
*selector = r->min_sel;
return;
}
if (linear_range_get_max_value(r) < val) {
*selector = r->max_sel;
return;
}
if (r->step == 0)
*selector = r->min_sel;
else
*selector = (val - r->min) / r->step + r->min_sel;
}
EXPORT_SYMBOL_GPL(linear_range_get_selector_within);
MODULE_DESCRIPTION("linear-ranges helper");
MODULE_LICENSE("GPL");