aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-07-26 19:49:09 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2016-07-26 19:49:09 -0700
commitf7816ad0f878dacd5f0120476f9b836ccf8699ea (patch)
tree43c81a5697976af080e58e41ec84c7f5c7c29fbe /drivers
parentMerge tag 'regulator-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator (diff)
parentpower: reset: add reboot mode driver (diff)
downloadlinux-dev-f7816ad0f878dacd5f0120476f9b836ccf8699ea.tar.xz
linux-dev-f7816ad0f878dacd5f0120476f9b836ccf8699ea.zip
Merge tag 'for-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel: - introduce reboot mode driver - add DT support to max8903 - add power supply support for axp221 - misc fixes * tag 'for-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: power: reset: add reboot mode driver dt-bindings: power: reset: add document for reboot-mode driver power_supply: fix return value of get_property power: qcom_smbb: Make an extcon for usb cable detection max8903: adds support for initiation via device tree max8903: adds documentation for device tree bindings. max8903: remove unnecessary 'out of memory' error message. max8903: removes non zero validity checks on gpios. max8903: adds requesting of gpios. max8903: cleans up confusing relationship between dc_valid, dok and dcm. max8903: store pointer to pdata instead of copying it. power_supply: bq27xxx_battery: Group register mappings into one table docs: Move brcm,bcm21664-resetmgr.txt power/reset: make syscon_poweroff() static power: axp20x_usb: Add support for usb power-supply on axp22x pmics power_supply: bq27xxx_battery: Index register numbers by enum power_supply: bq27xxx_battery: Fix copy/paste error in header comment MAINTAINERS: Add file patterns for power supply device tree bindings power: reset: keystone: Enable COMPILE_TEST
Diffstat (limited to 'drivers')
-rw-r--r--drivers/power/Kconfig1
-rw-r--r--drivers/power/axp20x_usb_power.c92
-rw-r--r--drivers/power/bq27xxx_battery.c284
-rw-r--r--drivers/power/bq27xxx_battery_i2c.c2
-rw-r--r--drivers/power/max8903_charger.c239
-rw-r--r--drivers/power/power_supply_core.c6
-rw-r--r--drivers/power/power_supply_sysfs.c2
-rw-r--r--drivers/power/qcom_smbb.c21
-rw-r--r--drivers/power/reset/Kconfig17
-rw-r--r--drivers/power/reset/Makefile2
-rw-r--r--drivers/power/reset/reboot-mode.c140
-rw-r--r--drivers/power/reset/reboot-mode.h14
-rw-r--r--drivers/power/reset/syscon-poweroff.c2
-rw-r--r--drivers/power/reset/syscon-reboot-mode.c99
14 files changed, 682 insertions, 239 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 0f11a0f4c369..acd4a1524a1e 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -394,6 +394,7 @@ config CHARGER_QCOM_SMBB
tristate "Qualcomm Switch-Mode Battery Charger and Boost"
depends on MFD_SPMI_PMIC || COMPILE_TEST
depends on OF
+ depends on EXTCON
help
Say Y to include support for the Switch-Mode Battery Charger and
Boost (SMBB) hardware found in Qualcomm PM8941 PMICs. The charger
diff --git a/drivers/power/axp20x_usb_power.c b/drivers/power/axp20x_usb_power.c
index 421a90b83567..6af6feb7058d 100644
--- a/drivers/power/axp20x_usb_power.c
+++ b/drivers/power/axp20x_usb_power.c
@@ -42,6 +42,7 @@
#define AXP20X_VBUS_MON_VBUS_VALID BIT(3)
struct axp20x_usb_power {
+ struct device_node *np;
struct regmap *regmap;
struct power_supply *supply;
};
@@ -85,7 +86,12 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
case AXP20X_VBUC_CLIMIT_100mA:
- val->intval = 100000;
+ if (of_device_is_compatible(power->np,
+ "x-powers,axp202-usb-power-supply")) {
+ val->intval = 100000;
+ } else {
+ val->intval = -1; /* No 100mA limit */
+ }
break;
case AXP20X_VBUC_CLIMIT_500mA:
val->intval = 500000;
@@ -122,16 +128,19 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
break;
}
- ret = regmap_read(power->regmap, AXP20X_USB_OTG_STATUS, &v);
- if (ret)
- return ret;
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
- if (!(v & AXP20X_USB_STATUS_VBUS_VALID)) {
- val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
- break;
- }
+ if (of_device_is_compatible(power->np,
+ "x-powers,axp202-usb-power-supply")) {
+ ret = regmap_read(power->regmap,
+ AXP20X_USB_OTG_STATUS, &v);
+ if (ret)
+ return ret;
- val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ if (!(v & AXP20X_USB_STATUS_VBUS_VALID))
+ val->intval =
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ }
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT);
@@ -156,6 +165,14 @@ static enum power_supply_property axp20x_usb_power_properties[] = {
POWER_SUPPLY_PROP_CURRENT_NOW,
};
+static enum power_supply_property axp22x_usb_power_properties[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
static const struct power_supply_desc axp20x_usb_power_desc = {
.name = "axp20x-usb",
.type = POWER_SUPPLY_TYPE_USB,
@@ -164,13 +181,25 @@ static const struct power_supply_desc axp20x_usb_power_desc = {
.get_property = axp20x_usb_power_get_property,
};
+static const struct power_supply_desc axp22x_usb_power_desc = {
+ .name = "axp20x-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = axp22x_usb_power_properties,
+ .num_properties = ARRAY_SIZE(axp22x_usb_power_properties),
+ .get_property = axp20x_usb_power_get_property,
+};
+
static int axp20x_usb_power_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
struct axp20x_usb_power *power;
- static const char * const irq_names[] = { "VBUS_PLUGIN",
- "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID" };
+ static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN",
+ "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL };
+ static const char * const axp22x_irq_names[] = {
+ "VBUS_PLUGIN", "VBUS_REMOVAL", NULL };
+ static const char * const *irq_names;
+ const struct power_supply_desc *usb_power_desc;
int i, irq, ret;
if (!of_device_is_available(pdev->dev.of_node))
@@ -185,31 +214,47 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
if (!power)
return -ENOMEM;
+ power->np = pdev->dev.of_node;
power->regmap = axp20x->regmap;
- /* Enable vbus valid checking */
- ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON,
- AXP20X_VBUS_MON_VBUS_VALID, AXP20X_VBUS_MON_VBUS_VALID);
- if (ret)
- return ret;
+ if (of_device_is_compatible(power->np,
+ "x-powers,axp202-usb-power-supply")) {
+ /* Enable vbus valid checking */
+ ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON,
+ AXP20X_VBUS_MON_VBUS_VALID,
+ AXP20X_VBUS_MON_VBUS_VALID);
+ if (ret)
+ return ret;
- /* Enable vbus voltage and current measurement */
- ret = regmap_update_bits(power->regmap, AXP20X_ADC_EN1,
+ /* Enable vbus voltage and current measurement */
+ ret = regmap_update_bits(power->regmap, AXP20X_ADC_EN1,
AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT,
AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT);
- if (ret)
- return ret;
+ if (ret)
+ return ret;
+
+ usb_power_desc = &axp20x_usb_power_desc;
+ irq_names = axp20x_irq_names;
+ } else if (of_device_is_compatible(power->np,
+ "x-powers,axp221-usb-power-supply")) {
+ usb_power_desc = &axp22x_usb_power_desc;
+ irq_names = axp22x_irq_names;
+ } else {
+ dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n",
+ axp20x->variant);
+ return -EINVAL;
+ }
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = power;
- power->supply = devm_power_supply_register(&pdev->dev,
- &axp20x_usb_power_desc, &psy_cfg);
+ power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc,
+ &psy_cfg);
if (IS_ERR(power->supply))
return PTR_ERR(power->supply);
/* Request irqs after registering, as irqs may trigger immediately */
- for (i = 0; i < ARRAY_SIZE(irq_names); i++) {
+ for (i = 0; irq_names[i]; i++) {
irq = platform_get_irq_byname(pdev, irq_names[i]);
if (irq < 0) {
dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
@@ -229,6 +274,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
static const struct of_device_id axp20x_usb_power_match[] = {
{ .compatible = "x-powers,axp202-usb-power-supply" },
+ { .compatible = "x-powers,axp221-usb-power-supply" },
{ }
};
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c
index e90b3f307e0f..323d05a12f9b 100644
--- a/drivers/power/bq27xxx_battery.c
+++ b/drivers/power/bq27xxx_battery.c
@@ -82,6 +82,7 @@
*
* These are indexes into a device's register mapping array.
*/
+
enum bq27xxx_reg_index {
BQ27XXX_REG_CTRL = 0, /* Control */
BQ27XXX_REG_TEMP, /* Temperature */
@@ -100,157 +101,144 @@ enum bq27xxx_reg_index {
BQ27XXX_REG_SOC, /* State-of-Charge */
BQ27XXX_REG_DCAP, /* Design Capacity */
BQ27XXX_REG_AP, /* Average Power */
+ BQ27XXX_REG_MAX, /* sentinel */
};
/* Register mappings */
-static u8 bq27000_regs[] = {
- 0x00, /* CONTROL */
- 0x06, /* TEMP */
- INVALID_REG_ADDR, /* INT TEMP - NA*/
- 0x08, /* VOLT */
- 0x14, /* AVG CURR */
- 0x0a, /* FLAGS */
- 0x16, /* TTE */
- 0x18, /* TTF */
- 0x1c, /* TTES */
- 0x26, /* TTECP */
- 0x0c, /* NAC */
- 0x12, /* LMD(FCC) */
- 0x2a, /* CYCT */
- 0x22, /* AE */
- 0x0b, /* SOC(RSOC) */
- 0x76, /* DCAP(ILMD) */
- 0x24, /* AP */
-};
-
-static u8 bq27010_regs[] = {
- 0x00, /* CONTROL */
- 0x06, /* TEMP */
- INVALID_REG_ADDR, /* INT TEMP - NA*/
- 0x08, /* VOLT */
- 0x14, /* AVG CURR */
- 0x0a, /* FLAGS */
- 0x16, /* TTE */
- 0x18, /* TTF */
- 0x1c, /* TTES */
- 0x26, /* TTECP */
- 0x0c, /* NAC */
- 0x12, /* LMD(FCC) */
- 0x2a, /* CYCT */
- INVALID_REG_ADDR, /* AE - NA */
- 0x0b, /* SOC(RSOC) */
- 0x76, /* DCAP(ILMD) */
- INVALID_REG_ADDR, /* AP - NA */
-};
-
-static u8 bq27500_regs[] = {
- 0x00, /* CONTROL */
- 0x06, /* TEMP */
- 0x28, /* INT TEMP */
- 0x08, /* VOLT */
- 0x14, /* AVG CURR */
- 0x0a, /* FLAGS */
- 0x16, /* TTE */
- INVALID_REG_ADDR, /* TTF - NA */
- 0x1a, /* TTES */
- INVALID_REG_ADDR, /* TTECP - NA */
- 0x0c, /* NAC */
- 0x12, /* LMD(FCC) */
- 0x2a, /* CYCT */
- INVALID_REG_ADDR, /* AE - NA */
- 0x2c, /* SOC(RSOC) */
- 0x3c, /* DCAP(ILMD) */
- INVALID_REG_ADDR, /* AP - NA */
-};
-
-static u8 bq27530_regs[] = {
- 0x00, /* CONTROL */
- 0x06, /* TEMP */
- 0x32, /* INT TEMP */
- 0x08, /* VOLT */
- 0x14, /* AVG CURR */
- 0x0a, /* FLAGS */
- 0x16, /* TTE */
- INVALID_REG_ADDR, /* TTF - NA */
- INVALID_REG_ADDR, /* TTES - NA */
- INVALID_REG_ADDR, /* TTECP - NA */
- 0x0c, /* NAC */
- 0x12, /* LMD(FCC) */
- 0x2a, /* CYCT */
- INVALID_REG_ADDR, /* AE - NA */
- 0x2c, /* SOC(RSOC) */
- INVALID_REG_ADDR, /* DCAP - NA */
- 0x24, /* AP */
-};
-
-static u8 bq27541_regs[] = {
- 0x00, /* CONTROL */
- 0x06, /* TEMP */
- 0x28, /* INT TEMP */
- 0x08, /* VOLT */
- 0x14, /* AVG CURR */
- 0x0a, /* FLAGS */
- 0x16, /* TTE */
- INVALID_REG_ADDR, /* TTF - NA */
- INVALID_REG_ADDR, /* TTES - NA */
- INVALID_REG_ADDR, /* TTECP - NA */
- 0x0c, /* NAC */
- 0x12, /* LMD(FCC) */
- 0x2a, /* CYCT */
- INVALID_REG_ADDR, /* AE - NA */
- 0x2c, /* SOC(RSOC) */
- 0x3c, /* DCAP */
- 0x24, /* AP */
-};
-
-static u8 bq27545_regs[] = {
- 0x00, /* CONTROL */
- 0x06, /* TEMP */
- 0x28, /* INT TEMP */
- 0x08, /* VOLT */
- 0x14, /* AVG CURR */
- 0x0a, /* FLAGS */
- 0x16, /* TTE */
- INVALID_REG_ADDR, /* TTF - NA */
- INVALID_REG_ADDR, /* TTES - NA */
- INVALID_REG_ADDR, /* TTECP - NA */
- 0x0c, /* NAC */
- 0x12, /* LMD(FCC) */
- 0x2a, /* CYCT */
- INVALID_REG_ADDR, /* AE - NA */
- 0x2c, /* SOC(RSOC) */
- INVALID_REG_ADDR, /* DCAP - NA */
- 0x24, /* AP */
-};
-
-static u8 bq27421_regs[] = {
- 0x00, /* CONTROL */
- 0x02, /* TEMP */
- 0x1e, /* INT TEMP */
- 0x04, /* VOLT */
- 0x10, /* AVG CURR */
- 0x06, /* FLAGS */
- INVALID_REG_ADDR, /* TTE - NA */
- INVALID_REG_ADDR, /* TTF - NA */
- INVALID_REG_ADDR, /* TTES - NA */
- INVALID_REG_ADDR, /* TTECP - NA */
- 0x08, /* NAC */
- 0x0e, /* FCC */
- INVALID_REG_ADDR, /* CYCT - NA */
- INVALID_REG_ADDR, /* AE - NA */
- 0x1c, /* SOC */
- 0x3c, /* DCAP */
- 0x18, /* AP */
-};
-
-static u8 *bq27xxx_regs[] = {
- [BQ27000] = bq27000_regs,
- [BQ27010] = bq27010_regs,
- [BQ27500] = bq27500_regs,
- [BQ27530] = bq27530_regs,
- [BQ27541] = bq27541_regs,
- [BQ27545] = bq27545_regs,
- [BQ27421] = bq27421_regs,
+static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
+ [BQ27000] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = 0x18,
+ [BQ27XXX_REG_TTES] = 0x1c,
+ [BQ27XXX_REG_TTECP] = 0x26,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = 0x22,
+ [BQ27XXX_REG_SOC] = 0x0b,
+ [BQ27XXX_REG_DCAP] = 0x76,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27010] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = 0x18,
+ [BQ27XXX_REG_TTES] = 0x1c,
+ [BQ27XXX_REG_TTECP] = 0x26,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x0b,
+ [BQ27XXX_REG_DCAP] = 0x76,
+ [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ },
+ [BQ27500] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x28,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = 0x1a,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = 0x3c,
+ [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ },
+ [BQ27530] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x32,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27541] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x28,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = 0x3c,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27545] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x28,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27421] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x02,
+ [BQ27XXX_REG_INT_TEMP] = 0x1e,
+ [BQ27XXX_REG_VOLT] = 0x04,
+ [BQ27XXX_REG_AI] = 0x10,
+ [BQ27XXX_REG_FLAGS] = 0x06,
+ [BQ27XXX_REG_TTE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x08,
+ [BQ27XXX_REG_FCC] = 0x0e,
+ [BQ27XXX_REG_CYCT] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x1c,
+ [BQ27XXX_REG_DCAP] = 0x3c,
+ [BQ27XXX_REG_AP] = 0x18,
+ },
};
static enum power_supply_property bq27000_battery_props[] = {
diff --git a/drivers/power/bq27xxx_battery_i2c.c b/drivers/power/bq27xxx_battery_i2c.c
index b8f8d3ade31b..85d4ea2a9c20 100644
--- a/drivers/power/bq27xxx_battery_i2c.c
+++ b/drivers/power/bq27xxx_battery_i2c.c
@@ -1,5 +1,5 @@
/*
- * SCI Reset driver for Keystone based devices
+ * BQ27xxx battery monitor I2C driver
*
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
* Andrew F. Davis <afd@ti.com>
diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c
index 17876caf31e5..fdc73d686153 100644
--- a/drivers/power/max8903_charger.c
+++ b/drivers/power/max8903_charger.c
@@ -23,13 +23,16 @@
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/power/max8903_charger.h>
struct max8903_data {
- struct max8903_pdata pdata;
+ struct max8903_pdata *pdata;
struct device *dev;
struct power_supply *psy;
struct power_supply_desc psy_desc;
@@ -53,8 +56,8 @@ static int max8903_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
- if (data->pdata.chg) {
- if (gpio_get_value(data->pdata.chg) == 0)
+ if (gpio_is_valid(data->pdata->chg)) {
+ if (gpio_get_value(data->pdata->chg) == 0)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (data->usb_in || data->ta_in)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
@@ -75,13 +78,14 @@ static int max8903_get_property(struct power_supply *psy,
default:
return -EINVAL;
}
+
return 0;
}
static irqreturn_t max8903_dcin(int irq, void *_data)
{
struct max8903_data *data = _data;
- struct max8903_pdata *pdata = &data->pdata;
+ struct max8903_pdata *pdata = data->pdata;
bool ta_in;
enum power_supply_type old_type;
@@ -93,11 +97,11 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
data->ta_in = ta_in;
/* Set Current-Limit-Mode 1:DC 0:USB */
- if (pdata->dcm)
+ if (gpio_is_valid(pdata->dcm))
gpio_set_value(pdata->dcm, ta_in ? 1 : 0);
/* Charger Enable / Disable (cen is negated) */
- if (pdata->cen)
+ if (gpio_is_valid(pdata->cen))
gpio_set_value(pdata->cen, ta_in ? 0 :
(data->usb_in ? 0 : 1));
@@ -122,7 +126,7 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
static irqreturn_t max8903_usbin(int irq, void *_data)
{
struct max8903_data *data = _data;
- struct max8903_pdata *pdata = &data->pdata;
+ struct max8903_pdata *pdata = data->pdata;
bool usb_in;
enum power_supply_type old_type;
@@ -136,7 +140,7 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
/* Do not touch Current-Limit-Mode */
/* Charger Enable / Disable (cen is negated) */
- if (pdata->cen)
+ if (gpio_is_valid(pdata->cen))
gpio_set_value(pdata->cen, usb_in ? 0 :
(data->ta_in ? 0 : 1));
@@ -161,7 +165,7 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
static irqreturn_t max8903_fault(int irq, void *_data)
{
struct max8903_data *data = _data;
- struct max8903_pdata *pdata = &data->pdata;
+ struct max8903_pdata *pdata = data->pdata;
bool fault;
fault = gpio_get_value(pdata->flt) ? false : true;
@@ -179,57 +183,109 @@ static irqreturn_t max8903_fault(int irq, void *_data)
return IRQ_HANDLED;
}
-static int max8903_probe(struct platform_device *pdev)
+static struct max8903_pdata *max8903_parse_dt_data(struct device *dev)
{
- struct max8903_data *data;
+ struct device_node *np = dev->of_node;
+ struct max8903_pdata *pdata = NULL;
+
+ if (!np)
+ return NULL;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ pdata->dc_valid = false;
+ pdata->usb_valid = false;
+
+ pdata->cen = of_get_named_gpio(np, "cen-gpios", 0);
+ if (!gpio_is_valid(pdata->cen))
+ pdata->cen = -EINVAL;
+
+ pdata->chg = of_get_named_gpio(np, "chg-gpios", 0);
+ if (!gpio_is_valid(pdata->chg))
+ pdata->chg = -EINVAL;
+
+ pdata->flt = of_get_named_gpio(np, "flt-gpios", 0);
+ if (!gpio_is_valid(pdata->flt))
+ pdata->flt = -EINVAL;
+
+ pdata->usus = of_get_named_gpio(np, "usus-gpios", 0);
+ if (!gpio_is_valid(pdata->usus))
+ pdata->usus = -EINVAL;
+
+ pdata->dcm = of_get_named_gpio(np, "dcm-gpios", 0);
+ if (!gpio_is_valid(pdata->dcm))
+ pdata->dcm = -EINVAL;
+
+ pdata->dok = of_get_named_gpio(np, "dok-gpios", 0);
+ if (!gpio_is_valid(pdata->dok))
+ pdata->dok = -EINVAL;
+ else
+ pdata->dc_valid = true;
+
+ pdata->uok = of_get_named_gpio(np, "uok-gpios", 0);
+ if (!gpio_is_valid(pdata->uok))
+ pdata->uok = -EINVAL;
+ else
+ pdata->usb_valid = true;
+
+ return pdata;
+}
+
+static int max8903_setup_gpios(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct max8903_pdata *pdata = pdev->dev.platform_data;
- struct power_supply_config psy_cfg = {};
int ret = 0;
int gpio;
int ta_in = 0;
int usb_in = 0;
- data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);
- if (data == NULL) {
- dev_err(dev, "Cannot allocate memory.\n");
- return -ENOMEM;
- }
- memcpy(&data->pdata, pdata, sizeof(struct max8903_pdata));
- data->dev = dev;
- platform_set_drvdata(pdev, data);
-
- if (pdata->dc_valid == false && pdata->usb_valid == false) {
- dev_err(dev, "No valid power sources.\n");
- return -EINVAL;
- }
-
if (pdata->dc_valid) {
- if (pdata->dok && gpio_is_valid(pdata->dok) &&
- pdata->dcm && gpio_is_valid(pdata->dcm)) {
+ if (gpio_is_valid(pdata->dok)) {
+ ret = devm_gpio_request(dev, pdata->dok,
+ data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for dok: %d err %d\n",
+ pdata->dok, ret);
+ return ret;
+ }
+
gpio = pdata->dok; /* PULL_UPed Interrupt */
ta_in = gpio_get_value(gpio) ? 0 : 1;
-
- gpio = pdata->dcm; /* Output */
- gpio_set_value(gpio, ta_in);
} else {
- dev_err(dev, "When DC is wired, DOK and DCM should"
- " be wired as well.\n");
+ dev_err(dev, "When DC is wired, DOK should be wired as well.\n");
return -EINVAL;
}
- } else {
- if (pdata->dcm) {
- if (gpio_is_valid(pdata->dcm))
- gpio_set_value(pdata->dcm, 0);
- else {
- dev_err(dev, "Invalid pin: dcm.\n");
- return -EINVAL;
- }
+ }
+
+ if (gpio_is_valid(pdata->dcm)) {
+ ret = devm_gpio_request(dev, pdata->dcm, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for dcm: %d err %d\n",
+ pdata->dcm, ret);
+ return ret;
}
+
+ gpio = pdata->dcm; /* Output */
+ gpio_set_value(gpio, ta_in);
}
if (pdata->usb_valid) {
- if (pdata->uok && gpio_is_valid(pdata->uok)) {
+ if (gpio_is_valid(pdata->uok)) {
+ ret = devm_gpio_request(dev, pdata->uok,
+ data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for uok: %d err %d\n",
+ pdata->uok, ret);
+ return ret;
+ }
+
gpio = pdata->uok;
usb_in = gpio_get_value(gpio) ? 0 : 1;
} else {
@@ -239,33 +295,45 @@ static int max8903_probe(struct platform_device *pdev)
}
}
- if (pdata->cen) {
- if (gpio_is_valid(pdata->cen)) {
- gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1);
- } else {
- dev_err(dev, "Invalid pin: cen.\n");
- return -EINVAL;
+ if (gpio_is_valid(pdata->cen)) {
+ ret = devm_gpio_request(dev, pdata->cen, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for cen: %d err %d\n",
+ pdata->cen, ret);
+ return ret;
}
+
+ gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1);
}
- if (pdata->chg) {
- if (!gpio_is_valid(pdata->chg)) {
- dev_err(dev, "Invalid pin: chg.\n");
- return -EINVAL;
+ if (gpio_is_valid(pdata->chg)) {
+ ret = devm_gpio_request(dev, pdata->chg, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for chg: %d err %d\n",
+ pdata->chg, ret);
+ return ret;
}
}
- if (pdata->flt) {
- if (!gpio_is_valid(pdata->flt)) {
- dev_err(dev, "Invalid pin: flt.\n");
- return -EINVAL;
+ if (gpio_is_valid(pdata->flt)) {
+ ret = devm_gpio_request(dev, pdata->flt, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for flt: %d err %d\n",
+ pdata->flt, ret);
+ return ret;
}
}
- if (pdata->usus) {
- if (!gpio_is_valid(pdata->usus)) {
- dev_err(dev, "Invalid pin: usus.\n");
- return -EINVAL;
+ if (gpio_is_valid(pdata->usus)) {
+ ret = devm_gpio_request(dev, pdata->usus, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for usus: %d err %d\n",
+ pdata->usus, ret);
+ return ret;
}
}
@@ -273,14 +341,52 @@ static int max8903_probe(struct platform_device *pdev)
data->ta_in = ta_in;
data->usb_in = usb_in;
+ return 0;
+}
+
+static int max8903_probe(struct platform_device *pdev)
+{
+ struct max8903_data *data;
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ int ret = 0;
+
+ data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (IS_ENABLED(CONFIG_OF) && !pdata && dev->of_node)
+ pdata = max8903_parse_dt_data(dev);
+
+ if (!pdata) {
+ dev_err(dev, "No platform data.\n");
+ return -EINVAL;
+ }
+
+ pdev->dev.platform_data = pdata;
+ data->pdata = pdata;
+ data->dev = dev;
+ platform_set_drvdata(pdev, data);
+
+ if (pdata->dc_valid == false && pdata->usb_valid == false) {
+ dev_err(dev, "No valid power sources.\n");
+ return -EINVAL;
+ }
+
+ ret = max8903_setup_gpios(pdev);
+ if (ret)
+ return ret;
+
data->psy_desc.name = "max8903_charger";
- data->psy_desc.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS :
- ((usb_in) ? POWER_SUPPLY_TYPE_USB :
+ data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS :
+ ((data->usb_in) ? POWER_SUPPLY_TYPE_USB :
POWER_SUPPLY_TYPE_BATTERY);
data->psy_desc.get_property = max8903_get_property;
data->psy_desc.properties = max8903_charger_props;
data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props);
+ psy_cfg.of_node = dev->of_node;
psy_cfg.drv_data = data;
data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg);
@@ -315,7 +421,7 @@ static int max8903_probe(struct platform_device *pdev)
}
}
- if (pdata->flt) {
+ if (gpio_is_valid(pdata->flt)) {
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt),
NULL, max8903_fault,
IRQF_TRIGGER_FALLING |
@@ -331,10 +437,17 @@ static int max8903_probe(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id max8903_match_ids[] = {
+ { .compatible = "maxim,max8903", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max8903_match_ids);
+
static struct platform_driver max8903_driver = {
.probe = max8903_probe,
.driver = {
.name = "max8903-charger",
+ .of_match_table = max8903_match_ids
},
};
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index b13cd074c52a..a74d8ca383a1 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -491,8 +491,11 @@ int power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
- if (atomic_read(&psy->use_cnt) <= 0)
+ if (atomic_read(&psy->use_cnt) <= 0) {
+ if (!psy->initialized)
+ return -EAGAIN;
return -ENODEV;
+ }
return psy->desc->get_property(psy, psp, val);
}
@@ -785,6 +788,7 @@ __power_supply_register(struct device *parent,
* after calling power_supply_register()).
*/
atomic_inc(&psy->use_cnt);
+ psy->initialized = true;
queue_delayed_work(system_power_efficient_wq,
&psy->deferred_register_work,
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index 80fed98832f9..bcde8d13476a 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -83,7 +83,7 @@ static ssize_t power_supply_show_property(struct device *dev,
if (ret == -ENODATA)
dev_dbg(dev, "driver has no data for `%s' property\n",
attr->attr.name);
- else if (ret != -ENODEV)
+ else if (ret != -ENODEV && ret != -EAGAIN)
dev_err(dev, "driver failed to report `%s' property: %zd\n",
attr->attr.name, ret);
return ret;
diff --git a/drivers/power/qcom_smbb.c b/drivers/power/qcom_smbb.c
index 5eb1e9e543e2..b5896ba2a602 100644
--- a/drivers/power/qcom_smbb.c
+++ b/drivers/power/qcom_smbb.c
@@ -34,6 +34,7 @@
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/extcon.h>
#define SMBB_CHG_VMAX 0x040
#define SMBB_CHG_VSAFE 0x041
@@ -111,6 +112,7 @@ struct smbb_charger {
unsigned int revision;
unsigned int addr;
struct device *dev;
+ struct extcon_dev *edev;
bool dc_disabled;
bool jeita_ext_temp;
@@ -125,6 +127,11 @@ struct smbb_charger {
struct regmap *regmap;
};
+static const unsigned int smbb_usb_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_NONE,
+};
+
static int smbb_vbat_weak_fn(unsigned int index)
{
return 2100000 + index * 100000;
@@ -371,6 +378,8 @@ static irqreturn_t smbb_usb_valid_handler(int irq, void *_data)
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_USBIN_VALID);
+ extcon_set_cable_state_(chg->edev, EXTCON_USB,
+ chg->status & STATUS_USBIN_VALID);
power_supply_changed(chg->usb_psy);
return IRQ_HANDLED;
@@ -849,6 +858,18 @@ static int smbb_charger_probe(struct platform_device *pdev)
return PTR_ERR(chg->usb_psy);
}
+ chg->edev = devm_extcon_dev_allocate(&pdev->dev, smbb_usb_extcon_cable);
+ if (IS_ERR(chg->edev)) {
+ dev_err(&pdev->dev, "failed to allocate extcon device\n");
+ return -ENOMEM;
+ }
+
+ rc = devm_extcon_dev_register(&pdev->dev, chg->edev);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "failed to register extcon device\n");
+ return rc;
+ }
+
if (!chg->dc_disabled) {
dc_cfg.drv_data = chg;
dc_cfg.supplied_to = smbb_bif;
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 9bb2622c23bf..7053abced0bc 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -148,7 +148,8 @@ config POWER_RESET_XGENE
config POWER_RESET_KEYSTONE
bool "Keystone reset driver"
- depends on ARCH_KEYSTONE
+ depends on ARCH_KEYSTONE || COMPILE_TEST
+ depends on HAS_IOMEM
select MFD_SYSCON
help
Reboot support for the KEYSTONE SoCs.
@@ -183,5 +184,19 @@ config POWER_RESET_ZX
help
Reboot support for ZTE SoCs.
+config REBOOT_MODE
+ tristate
+
+config SYSCON_REBOOT_MODE
+ tristate "Generic SYSCON regmap reboot mode driver"
+ depends on OF
+ select REBOOT_MODE
+ select MFD_SYSCON
+ help
+ Say y here will enable reboot mode driver. This will
+ get reboot mode arguments and store it in SYSCON mapped
+ register, then the bootloader can read it to take different
+ action according to the mode.
+
endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index ab7aa8614d1f..d6b2560d5c4a 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -21,3 +21,5 @@ obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
obj-$(CONFIG_POWER_RESET_ZX) += zx-reboot.o
+obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
+obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
new file mode 100644
index 000000000000..2dfbbce0f817
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/reboot.h>
+#include "reboot-mode.h"
+
+#define PREFIX "mode-"
+
+struct mode_info {
+ const char *mode;
+ u32 magic;
+ struct list_head list;
+};
+
+static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
+ const char *cmd)
+{
+ const char *normal = "normal";
+ int magic = 0;
+ struct mode_info *info;
+
+ if (!cmd)
+ cmd = normal;
+
+ list_for_each_entry(info, &reboot->head, list) {
+ if (!strcmp(info->mode, cmd)) {
+ magic = info->magic;
+ break;
+ }
+ }
+
+ return magic;
+}
+
+static int reboot_mode_notify(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct reboot_mode_driver *reboot;
+ unsigned int magic;
+
+ reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
+ magic = get_reboot_mode_magic(reboot, cmd);
+ if (magic)
+ reboot->write(reboot, magic);
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * reboot_mode_register - register a reboot mode driver
+ * @reboot: reboot mode driver
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int reboot_mode_register(struct reboot_mode_driver *reboot)
+{
+ struct mode_info *info;
+ struct property *prop;
+ struct device_node *np = reboot->dev->of_node;
+ size_t len = strlen(PREFIX);
+ int ret;
+
+ INIT_LIST_HEAD(&reboot->head);
+
+ for_each_property_of_node(np, prop) {
+ if (strncmp(prop->name, PREFIX, len))
+ continue;
+
+ info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (of_property_read_u32(np, prop->name, &info->magic)) {
+ dev_err(reboot->dev, "reboot mode %s without magic number\n",
+ info->mode);
+ devm_kfree(reboot->dev, info);
+ continue;
+ }
+
+ info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
+ if (!info->mode) {
+ ret = -ENOMEM;
+ goto error;
+ } else if (info->mode[0] == '\0') {
+ kfree_const(info->mode);
+ ret = -EINVAL;
+ dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
+ prop->name);
+ goto error;
+ }
+
+ list_add_tail(&info->list, &reboot->head);
+ }
+
+ reboot->reboot_notifier.notifier_call = reboot_mode_notify;
+ register_reboot_notifier(&reboot->reboot_notifier);
+
+ return 0;
+
+error:
+ list_for_each_entry(info, &reboot->head, list)
+ kfree_const(info->mode);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_register);
+
+/**
+ * reboot_mode_unregister - unregister a reboot mode driver
+ * @reboot: reboot mode driver
+ */
+int reboot_mode_unregister(struct reboot_mode_driver *reboot)
+{
+ struct mode_info *info;
+
+ unregister_reboot_notifier(&reboot->reboot_notifier);
+
+ list_for_each_entry(info, &reboot->head, list)
+ kfree_const(info->mode);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_unregister);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
+MODULE_DESCRIPTION("System reboot mode core library");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/reboot-mode.h b/drivers/power/reset/reboot-mode.h
new file mode 100644
index 000000000000..2491bb71f591
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.h
@@ -0,0 +1,14 @@
+#ifndef __REBOOT_MODE_H__
+#define __REBOOT_MODE_H__
+
+struct reboot_mode_driver {
+ struct device *dev;
+ struct list_head head;
+ int (*write)(struct reboot_mode_driver *reboot, unsigned int magic);
+ struct notifier_block reboot_notifier;
+};
+
+int reboot_mode_register(struct reboot_mode_driver *reboot);
+int reboot_mode_unregister(struct reboot_mode_driver *reboot);
+
+#endif
diff --git a/drivers/power/reset/syscon-poweroff.c b/drivers/power/reset/syscon-poweroff.c
index 5560b0dbc180..b68338399e5e 100644
--- a/drivers/power/reset/syscon-poweroff.c
+++ b/drivers/power/reset/syscon-poweroff.c
@@ -30,7 +30,7 @@ static struct regmap *map;
static u32 offset;
static u32 mask;
-void syscon_poweroff(void)
+static void syscon_poweroff(void)
{
/* Issue the poweroff */
regmap_write(map, offset, mask);
diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c
new file mode 100644
index 000000000000..9e1cba5dd58e
--- /dev/null
+++ b/drivers/power/reset/syscon-reboot-mode.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include "reboot-mode.h"
+
+struct syscon_reboot_mode {
+ struct regmap *map;
+ struct reboot_mode_driver reboot;
+ u32 offset;
+ u32 mask;
+};
+
+static int syscon_reboot_mode_write(struct reboot_mode_driver *reboot,
+ unsigned int magic)
+{
+ struct syscon_reboot_mode *syscon_rbm;
+ int ret;
+
+ syscon_rbm = container_of(reboot, struct syscon_reboot_mode, reboot);
+
+ ret = regmap_update_bits(syscon_rbm->map, syscon_rbm->offset,
+ syscon_rbm->mask, magic);
+ if (ret < 0)
+ dev_err(reboot->dev, "update reboot mode bits failed\n");
+
+ return ret;
+}
+
+static int syscon_reboot_mode_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct syscon_reboot_mode *syscon_rbm;
+
+ syscon_rbm = devm_kzalloc(&pdev->dev, sizeof(*syscon_rbm), GFP_KERNEL);
+ if (!syscon_rbm)
+ return -ENOMEM;
+
+ syscon_rbm->reboot.dev = &pdev->dev;
+ syscon_rbm->reboot.write = syscon_reboot_mode_write;
+ syscon_rbm->mask = 0xffffffff;
+
+ dev_set_drvdata(&pdev->dev, syscon_rbm);
+
+ syscon_rbm->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(syscon_rbm->map))
+ return PTR_ERR(syscon_rbm->map);
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset",
+ &syscon_rbm->offset))
+ return -EINVAL;
+
+ of_property_read_u32(pdev->dev.of_node, "mask", &syscon_rbm->mask);
+
+ ret = reboot_mode_register(&syscon_rbm->reboot);
+ if (ret)
+ dev_err(&pdev->dev, "can't register reboot mode\n");
+
+ return ret;
+}
+
+static int syscon_reboot_mode_remove(struct platform_device *pdev)
+{
+ struct syscon_reboot_mode *syscon_rbm = dev_get_drvdata(&pdev->dev);
+
+ return reboot_mode_unregister(&syscon_rbm->reboot);
+}
+
+static const struct of_device_id syscon_reboot_mode_of_match[] = {
+ { .compatible = "syscon-reboot-mode" },
+ {}
+};
+
+static struct platform_driver syscon_reboot_mode_driver = {
+ .probe = syscon_reboot_mode_probe,
+ .remove = syscon_reboot_mode_remove,
+ .driver = {
+ .name = "syscon-reboot-mode",
+ .of_match_table = syscon_reboot_mode_of_match,
+ },
+};
+module_platform_driver(syscon_reboot_mode_driver);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
+MODULE_DESCRIPTION("SYSCON reboot mode driver");
+MODULE_LICENSE("GPL v2");