diff options
Diffstat (limited to 'drivers/power')
26 files changed, 2081 insertions, 474 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e6f92b45091..ba697512307 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -196,6 +196,7 @@ config BATTERY_MAX17040 config BATTERY_MAX17042 tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge" depends on I2C + select REGMAP_I2C help MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries in handheld and portable equipment. The MAX17042 is configured @@ -316,6 +317,13 @@ config CHARGER_MANAGER runtime and in suspend-to-RAM by waking up the system periodically with help of suspend_again support. +config CHARGER_MAX14577 + tristate "Maxim MAX14577 MUIC battery charger driver" + depends on MFD_MAX14577 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX14577 MUICs. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 @@ -346,6 +354,12 @@ config CHARGER_BQ24190 help Say Y to enable support for the TI BQ24190 battery charger. +config CHARGER_BQ24735 + tristate "TI BQ24735 battery charger support" + depends on I2C && GPIOLIB + help + Say Y to enable support for the TI BQ24735 battery charger. + config CHARGER_SMB347 tristate "Summit Microelectronics SMB347 Battery Charger" depends on I2C @@ -370,6 +384,7 @@ config AB8500_BM config BATTERY_GOLDFISH tristate "Goldfish battery driver" depends on GOLDFISH || COMPILE_TEST + depends on HAS_IOMEM help Say Y to enable support for the battery and AC power in the Goldfish emulator. diff --git a/drivers/power/Makefile b/drivers/power/Makefile index a4b74177706..ee54a3e4c90 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -48,10 +48,12 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o +obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o +obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index a4c4a10b3a4..19110aa613a 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -766,7 +766,6 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, ret = -ENXIO; break; } - break; case USB_STAT_CARKIT_1: case USB_STAT_CARKIT_2: case USB_STAT_ACA_DOCK_CHARGER: @@ -1387,8 +1386,12 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger, * the GPADC module independant of the AB8500 chargers */ if (!di->vddadc_en_ac) { - regulator_enable(di->regu); - di->vddadc_en_ac = true; + ret = regulator_enable(di->regu); + if (ret) + dev_warn(di->dev, + "Failed to enable regulator\n"); + else + di->vddadc_en_ac = true; } /* Check if the requested voltage or current is valid */ @@ -1556,8 +1559,12 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, * the GPADC module independant of the AB8500 chargers */ if (!di->vddadc_en_usb) { - regulator_enable(di->regu); - di->vddadc_en_usb = true; + ret = regulator_enable(di->regu); + if (ret) + dev_warn(di->dev, + "Failed to enable regulator\n"); + else + di->vddadc_en_usb = true; } /* Enable USB charging */ diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 754970717c3..3cb4178e397 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -574,8 +574,8 @@ int ab8500_fg_inst_curr_start(struct ab8500_fg *di) } /* Return and WFI */ - INIT_COMPLETION(di->ab8500_fg_started); - INIT_COMPLETION(di->ab8500_fg_complete); + reinit_completion(&di->ab8500_fg_started); + reinit_completion(&di->ab8500_fg_complete); enable_irq(di->irq); /* Note: cc_lock is still locked */ diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c index 0727f925613..79a37f6d330 100644 --- a/drivers/power/bq2415x_charger.c +++ b/drivers/power/bq2415x_charger.c @@ -1,7 +1,7 @@ /* * bq2415x charger driver * - * Copyright (C) 2011-2012 Pali Rohár <pali.rohar@gmail.com> + * Copyright (C) 2011-2013 Pali Rohár <pali.rohar@gmail.com> * * 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 @@ -170,6 +170,8 @@ struct bq2415x_device { struct bq2415x_platform_data init_data; struct power_supply charger; struct delayed_work work; + struct power_supply *notify_psy; + struct notifier_block nb; enum bq2415x_mode reported_mode;/* mode reported by hook function */ enum bq2415x_mode mode; /* current configured mode */ enum bq2415x_chip chip; @@ -605,9 +607,13 @@ static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, { int val = (mV/10 - 350) / 2; + /* + * According to datasheet, maximum battery regulation voltage is + * 4440mV which is b101111 = 47. + */ if (val < 0) val = 0; - else if (val > 94) /* FIXME: Max is 94 or 122 ? Set max value ? */ + else if (val > 47) return -EINVAL; return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val, @@ -791,24 +797,53 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode) } -/* hook function called by other driver which set reported mode */ -static void bq2415x_hook_function(enum bq2415x_mode mode, void *data) +static int bq2415x_notifier_call(struct notifier_block *nb, + unsigned long val, void *v) { - struct bq2415x_device *bq = data; + struct bq2415x_device *bq = + container_of(nb, struct bq2415x_device, nb); + struct power_supply *psy = v; + enum bq2415x_mode mode; + union power_supply_propval prop; + int ret; + int mA; - if (!bq) - return; + if (val != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (psy != bq->notify_psy) + return NOTIFY_OK; + + dev_dbg(bq->dev, "notifier call was called\n"); + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (ret != 0) + return NOTIFY_OK; + + mA = prop.intval; + + if (mA == 0) + mode = BQ2415X_MODE_OFF; + else if (mA < 500) + mode = BQ2415X_MODE_NONE; + else if (mA < 1800) + mode = BQ2415X_MODE_HOST_CHARGER; + else + mode = BQ2415X_MODE_DEDICATED_CHARGER; + + if (bq->reported_mode == mode) + return NOTIFY_OK; - dev_dbg(bq->dev, "hook function was called\n"); bq->reported_mode = mode; /* if automode is not enabled do not tell about reported_mode */ if (bq->automode < 1) - return; + return NOTIFY_OK; sysfs_notify(&bq->charger.dev->kobj, NULL, "reported_mode"); bq2415x_set_mode(bq, bq->reported_mode); + return NOTIFY_OK; } /**** timer functions ****/ @@ -1508,9 +1543,11 @@ static int bq2415x_probe(struct i2c_client *client, int num; char *name; struct bq2415x_device *bq; + struct device_node *np = client->dev.of_node; + struct bq2415x_platform_data *pdata = client->dev.platform_data; - if (!client->dev.platform_data) { - dev_err(&client->dev, "platform data not set\n"); + if (!np && !pdata) { + dev_err(&client->dev, "platform data missing\n"); return -ENODEV; } @@ -1535,6 +1572,17 @@ static int bq2415x_probe(struct i2c_client *client, goto error_2; } + if (np) { + bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection"); + + if (!bq->notify_psy) + return -EPROBE_DEFER; + } + else if (pdata->notify_device) + bq->notify_psy = power_supply_get_by_name(pdata->notify_device); + else + bq->notify_psy = NULL; + i2c_set_clientdata(client, bq); bq->id = num; @@ -1546,8 +1594,34 @@ static int bq2415x_probe(struct i2c_client *client, bq->autotimer = 0; bq->automode = 0; - memcpy(&bq->init_data, client->dev.platform_data, - sizeof(bq->init_data)); + if (np) { + ret = of_property_read_u32(np, "ti,current-limit", + &bq->init_data.current_limit); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,weak-battery-voltage", + &bq->init_data.weak_battery_voltage); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,battery-regulation-voltage", + &bq->init_data.battery_regulation_voltage); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,charge-current", + &bq->init_data.charge_current); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,termination-current", + &bq->init_data.termination_current); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,resistor-sense", + &bq->init_data.resistor_sense); + if (ret) + return ret; + } else { + memcpy(&bq->init_data, pdata, sizeof(bq->init_data)); + } bq2415x_reset_chip(bq); @@ -1569,16 +1643,20 @@ static int bq2415x_probe(struct i2c_client *client, goto error_4; } - if (bq->init_data.set_mode_hook) { - if (bq->init_data.set_mode_hook( - bq2415x_hook_function, bq)) { - bq->automode = 1; - bq2415x_set_mode(bq, bq->reported_mode); - dev_info(bq->dev, "automode enabled\n"); - } else { - bq->automode = -1; - dev_info(bq->dev, "automode failed\n"); + if (bq->notify_psy) { + bq->nb.notifier_call = bq2415x_notifier_call; + ret = power_supply_reg_notifier(&bq->nb); + if (ret) { + dev_err(bq->dev, "failed to reg notifier: %d\n", ret); + goto error_5; } + + /* Query for initial reported_mode and set it */ + bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy); + bq2415x_set_mode(bq, bq->reported_mode); + + bq->automode = 1; + dev_info(bq->dev, "automode enabled\n"); } else { bq->automode = -1; dev_info(bq->dev, "automode not supported\n"); @@ -1590,6 +1668,7 @@ static int bq2415x_probe(struct i2c_client *client, dev_info(bq->dev, "driver registered\n"); return 0; +error_5: error_4: bq2415x_sysfs_exit(bq); error_3: @@ -1610,8 +1689,8 @@ static int bq2415x_remove(struct i2c_client *client) { struct bq2415x_device *bq = i2c_get_clientdata(client); - if (bq->init_data.set_mode_hook) - bq->init_data.set_mode_hook(NULL, NULL); + if (bq->notify_psy) + power_supply_unreg_notifier(&bq->nb); bq2415x_sysfs_exit(bq); bq2415x_power_supply_exit(bq); diff --git a/drivers/power/bq24735-charger.c b/drivers/power/bq24735-charger.c new file mode 100644 index 00000000000..d022b823305 --- /dev/null +++ b/drivers/power/bq24735-charger.c @@ -0,0 +1,419 @@ +/* + * Battery charger driver for TI BQ24735 + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * 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; + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#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) +#define BQ24735_CHARGE_CURRENT 0x14 +#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0 +#define BQ24735_CHARGE_VOLTAGE 0x15 +#define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0 +#define BQ24735_INPUT_CURRENT 0x3f +#define BQ24735_INPUT_CURRENT_MASK 0x1f80 +#define BQ24735_MANUFACTURER_ID 0xfe +#define BQ24735_DEVICE_ID 0xff + +struct bq24735 { + struct power_supply charger; + struct i2c_client *client; + struct bq24735_platform *pdata; +}; + +static inline struct bq24735 *to_bq24735(struct power_supply *psy) +{ + return container_of(psy, struct bq24735, charger); +} + +static enum power_supply_property bq24735_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static inline int bq24735_write_word(struct i2c_client *client, u8 reg, + u16 value) +{ + return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value)); +} + +static inline int bq24735_read_word(struct i2c_client *client, u8 reg) +{ + s32 ret = i2c_smbus_read_word_data(client, reg); + + return ret < 0 ? ret : le16_to_cpu(ret); +} + +static int bq24735_update_word(struct i2c_client *client, u8 reg, + u16 mask, u16 value) +{ + unsigned int tmp; + int ret; + + ret = bq24735_read_word(client, reg); + if (ret < 0) + return ret; + + tmp = ret & ~mask; + tmp |= value & mask; + + return bq24735_write_word(client, reg, tmp); +} + +static inline int bq24735_enable_charging(struct bq24735 *charger) +{ + return bq24735_update_word(charger->client, BQ24735_CHG_OPT, + BQ24735_CHG_OPT_CHARGE_DISABLE, + ~BQ24735_CHG_OPT_CHARGE_DISABLE); +} + +static inline int bq24735_disable_charging(struct bq24735 *charger) +{ + return bq24735_update_word(charger->client, BQ24735_CHG_OPT, + BQ24735_CHG_OPT_CHARGE_DISABLE, + BQ24735_CHG_OPT_CHARGE_DISABLE); +} + +static int bq24735_config_charger(struct bq24735 *charger) +{ + struct bq24735_platform *pdata = charger->pdata; + int ret; + u16 value; + + if (pdata->charge_current) { + value = pdata->charge_current & BQ24735_CHARGE_CURRENT_MASK; + + ret = bq24735_write_word(charger->client, + BQ24735_CHARGE_CURRENT, value); + if (ret < 0) { + dev_err(&charger->client->dev, + "Failed to write charger current : %d\n", + ret); + return ret; + } + } + + if (pdata->charge_voltage) { + value = pdata->charge_voltage & BQ24735_CHARGE_VOLTAGE_MASK; + + ret = bq24735_write_word(charger->client, + BQ24735_CHARGE_VOLTAGE, value); + if (ret < 0) { + dev_err(&charger->client->dev, + "Failed to write charger voltage : %d\n", + ret); + return ret; + } + } + + if (pdata->input_current) { + value = pdata->input_current & BQ24735_INPUT_CURRENT_MASK; + + ret = bq24735_write_word(charger->client, + BQ24735_INPUT_CURRENT, value); + if (ret < 0) { + dev_err(&charger->client->dev, + "Failed to write input current : %d\n", + ret); + return ret; + } + } + + return 0; +} + +static bool bq24735_charger_is_present(struct bq24735 *charger) +{ + struct bq24735_platform *pdata = charger->pdata; + int ret; + + if (pdata->status_gpio_valid) { + ret = gpio_get_value_cansleep(pdata->status_gpio); + return ret ^= pdata->status_gpio_active_low == 0; + } else { + int ac = 0; + + ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT); + if (ac < 0) { + dev_err(&charger->client->dev, + "Failed to read charger options : %d\n", + ac); + return false; + } + return (ac & BQ24735_CHG_OPT_AC_PRESENT) ? true : false; + } + + return false; +} + +static irqreturn_t bq24735_charger_isr(int irq, void *devid) +{ + struct power_supply *psy = devid; + struct bq24735 *charger = to_bq24735(psy); + + if (bq24735_charger_is_present(charger)) + bq24735_enable_charging(charger); + else + bq24735_disable_charging(charger); + + power_supply_changed(psy); + + return IRQ_HANDLED; +} + +static int bq24735_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq24735 *charger; + + charger = container_of(psy, struct bq24735, charger); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = bq24735_charger_is_present(charger) ? 1 : 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client) +{ + struct bq24735_platform *pdata; + struct device_node *np = client->dev.of_node; + u32 val; + int ret; + enum of_gpio_flags flags; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, + "Memory alloc for bq24735 pdata failed\n"); + return NULL; + } + + pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios", + 0, &flags); + + if (flags & OF_GPIO_ACTIVE_LOW) + pdata->status_gpio_active_low = 1; + + ret = of_property_read_u32(np, "ti,charge-current", &val); + if (!ret) + pdata->charge_current = val; + + ret = of_property_read_u32(np, "ti,charge-voltage", &val); + if (!ret) + pdata->charge_voltage = val; + + ret = of_property_read_u32(np, "ti,input-current", &val); + if (!ret) + pdata->input_current = val; + + return pdata; +} + +static int bq24735_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct bq24735 *charger; + struct power_supply *supply; + char *name; + + charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->pdata = client->dev.platform_data; + + if (IS_ENABLED(CONFIG_OF) && !charger->pdata && client->dev.of_node) + charger->pdata = bq24735_parse_dt_data(client); + + if (!charger->pdata) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + name = (char *)charger->pdata->name; + if (!name) { + name = kasprintf(GFP_KERNEL, "bq24735@%s", + dev_name(&client->dev)); + if (!name) { + dev_err(&client->dev, "Failed to alloc device name\n"); + return -ENOMEM; + } + } + + charger->client = client; + + supply = &charger->charger; + + supply->name = name; + supply->type = POWER_SUPPLY_TYPE_MAINS; + supply->properties = bq24735_charger_properties; + supply->num_properties = ARRAY_SIZE(bq24735_charger_properties); + supply->get_property = bq24735_charger_get_property; + supply->supplied_to = charger->pdata->supplied_to; + supply->num_supplicants = charger->pdata->num_supplicants; + supply->of_node = client->dev.of_node; + + i2c_set_clientdata(client, charger); + + ret = bq24735_read_word(client, BQ24735_MANUFACTURER_ID); + if (ret < 0) { + dev_err(&client->dev, "Failed to read manufacturer id : %d\n", + ret); + goto err_free_name; + } else if (ret != 0x0040) { + dev_err(&client->dev, + "manufacturer id mismatch. 0x0040 != 0x%04x\n", ret); + ret = -ENODEV; + goto err_free_name; + } + + ret = bq24735_read_word(client, BQ24735_DEVICE_ID); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device id : %d\n", ret); + goto err_free_name; + } else if (ret != 0x000B) { + dev_err(&client->dev, + "device id mismatch. 0x000b != 0x%04x\n", ret); + ret = -ENODEV; + goto err_free_name; + } + + if (gpio_is_valid(charger->pdata->status_gpio)) { + ret = devm_gpio_request(&client->dev, + charger->pdata->status_gpio, + name); + if (ret) { + dev_err(&client->dev, + "Failed GPIO request for GPIO %d: %d\n", + charger->pdata->status_gpio, ret); + } + + charger->pdata->status_gpio_valid = !ret; + } + + ret = bq24735_config_charger(charger); + if (ret < 0) { + dev_err(&client->dev, "failed in configuring charger"); + goto err_free_name; + } + + /* check for AC adapter presence */ + if (bq24735_charger_is_present(charger)) { + ret = bq24735_enable_charging(charger); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable charging\n"); + goto err_free_name; + } + } + + ret = power_supply_register(&client->dev, supply); + if (ret < 0) { + dev_err(&client->dev, "Failed to register power supply: %d\n", + ret); + goto err_free_name; + } + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, bq24735_charger_isr, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + supply->name, supply); + if (ret) { + dev_err(&client->dev, + "Unable to register IRQ %d err %d\n", + client->irq, ret); + goto err_unregister_supply; + } + } + + return 0; +err_unregister_supply: + power_supply_unregister(supply); +err_free_name: + if (name != charger->pdata->name) + kfree(name); + + return ret; +} + +static int bq24735_charger_remove(struct i2c_client *client) +{ + struct bq24735 *charger = i2c_get_clientdata(client); + + if (charger->client->irq) + devm_free_irq(&charger->client->dev, charger->client->irq, + &charger->charger); + + power_supply_unregister(&charger->charger); + + if (charger->charger.name != charger->pdata->name) + kfree(charger->charger.name); + + return 0; +} + +static const struct i2c_device_id bq24735_charger_id[] = { + { "bq24735-charger", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, bq24735_charger_id); + +static const struct of_device_id bq24735_match_ids[] = { + { .compatible = "ti,bq24735", }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, bq24735_match_ids); + +static struct i2c_driver bq24735_charger_driver = { + .driver = { + .name = "bq24735-charger", + .owner = THIS_MODULE, + .of_match_table = bq24735_match_ids, + }, + .probe = bq24735_charger_probe, + .remove = bq24735_charger_remove, + .id_table = bq24735_charger_id, +}; + +module_i2c_driver(bq24735_charger_driver); + +MODULE_DESCRIPTION("bq24735 battery charging driver"); +MODULE_AUTHOR("Darbha Sriharsha <dsriharsha@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index e30e847600b..9e4dab46eef 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -25,12 +25,23 @@ #include <linux/power/charger-manager.h> #include <linux/regulator/consumer.h> #include <linux/sysfs.h> +#include <linux/of.h> +#include <linux/thermal.h> + +/* + * Default termperature threshold for charging. + * Every temperature units are in tenth of centigrade. + */ +#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50 +#define CM_DEFAULT_CHARGE_TEMP_MAX 500 static const char * const default_event_names[] = { [CM_EVENT_UNKNOWN] = "Unknown", [CM_EVENT_BATT_FULL] = "Battery Full", [CM_EVENT_BATT_IN] = "Battery Inserted", [CM_EVENT_BATT_OUT] = "Battery Pulled Out", + [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat", + [CM_EVENT_BATT_COLD] = "Battery Cold", [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", [CM_EVENT_OTHERS] = "Other battery events" @@ -518,7 +529,7 @@ static int check_charging_duration(struct charger_manager *cm) duration = curr - cm->charging_start_time; if (duration > desc->charging_max_duration_ms) { - dev_info(cm->dev, "Charging duration exceed %lldms\n", + dev_info(cm->dev, "Charging duration exceed %ums\n", desc->charging_max_duration_ms); uevent_notify(cm, "Discharging"); try_charger_enable(cm, false); @@ -529,7 +540,7 @@ static int check_charging_duration(struct charger_manager *cm) if (duration > desc->charging_max_duration_ms && is_ext_pwr_online(cm)) { - dev_info(cm->dev, "Discharging duration exceed %lldms\n", + dev_info(cm->dev, "Discharging duration exceed %ums\n", desc->discharging_max_duration_ms); uevent_notify(cm, "Recharging"); try_charger_enable(cm, true); @@ -540,6 +551,60 @@ static int check_charging_duration(struct charger_manager *cm) return ret; } +static int cm_get_battery_temperature(struct charger_manager *cm, + int *temp) +{ + int ret; + + if (!cm->desc->measure_battery_temp) + return -ENODEV; + +#ifdef CONFIG_THERMAL + ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp); + if (!ret) + /* Calibrate temperature unit */ + *temp /= 100; +#else + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_TEMP, + (union power_supply_propval *)temp); +#endif + return ret; +} + +static int cm_check_thermal_status(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + int temp, upper_limit, lower_limit; + int ret = 0; + + ret = cm_get_battery_temperature(cm, &temp); + if (ret) { + /* FIXME: + * No information of battery temperature might + * occur hazadous result. We have to handle it + * depending on battery type. + */ + dev_err(cm->dev, "Failed to get battery temperature\n"); + return 0; + } + + upper_limit = desc->temp_max; + lower_limit = desc->temp_min; + + if (cm->emergency_stop) { + upper_limit -= desc->temp_diff; + lower_limit += desc->temp_diff; + } + + if (temp > upper_limit) + ret = CM_EVENT_BATT_OVERHEAT; + else if (temp < lower_limit) + ret = CM_EVENT_BATT_COLD; + + return ret; +} + /** * _cm_monitor - Monitor the temperature and return true for exceptions. * @cm: the Charger Manager representing the battery. @@ -549,28 +614,22 @@ static int check_charging_duration(struct charger_manager *cm) */ static bool _cm_monitor(struct charger_manager *cm) { - struct charger_desc *desc = cm->desc; - int temp = desc->temperature_out_of_range(&cm->last_temp_mC); + int temp_alrt; - dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n", - cm->last_temp_mC / 1000, cm->last_temp_mC % 1000); + temp_alrt = cm_check_thermal_status(cm); /* It has been stopped already */ - if (temp && cm->emergency_stop) + if (temp_alrt && cm->emergency_stop) return false; /* * Check temperature whether overheat or cold. * If temperature is out of range normal state, stop charging. */ - if (temp) { - cm->emergency_stop = temp; - if (!try_charger_enable(cm, false)) { - if (temp > 0) - uevent_notify(cm, "OVERHEAT"); - else - uevent_notify(cm, "COLD"); - } + if (temp_alrt) { + cm->emergency_stop = temp_alrt; + if (!try_charger_enable(cm, false)) + uevent_notify(cm, default_event_names[temp_alrt]); /* * Check whole charging duration and discharing duration @@ -802,21 +861,8 @@ static int charger_get_property(struct power_supply *psy, POWER_SUPPLY_PROP_CURRENT_NOW, val); break; case POWER_SUPPLY_PROP_TEMP: - /* in thenth of centigrade */ - if (cm->last_temp_mC == INT_MIN) - desc->temperature_out_of_range(&cm->last_temp_mC); - val->intval = cm->last_temp_mC / 100; - if (!desc->measure_battery_temp) - ret = -ENODEV; - break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: - /* in thenth of centigrade */ - if (cm->last_temp_mC == INT_MIN) - desc->temperature_out_of_range(&cm->last_temp_mC); - val->intval = cm->last_temp_mC / 100; - if (desc->measure_battery_temp) - ret = -ENODEV; - break; + return cm_get_battery_temperature(cm, &val->intval); case POWER_SUPPLY_PROP_CAPACITY: if (!cm->fuel_gauge) { ret = -ENODEV; @@ -1378,7 +1424,8 @@ static int charger_manager_register_sysfs(struct charger_manager *cm) charger = &desc->charger_regulators[i]; snprintf(buf, 10, "charger.%d", i); - str = kzalloc(sizeof(char) * (strlen(buf) + 1), GFP_KERNEL); + str = devm_kzalloc(cm->dev, + sizeof(char) * (strlen(buf) + 1), GFP_KERNEL); if (!str) { ret = -ENOMEM; goto err; @@ -1438,9 +1485,183 @@ err: return ret; } +static int cm_init_thermal_data(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + union power_supply_propval val; + int ret; + + /* Verify whether fuel gauge provides battery temperature */ + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_TEMP, &val); + + if (!ret) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + cm->desc->measure_battery_temp = true; + } +#ifdef CONFIG_THERMAL + cm->tzd_batt = cm->fuel_gauge->tzd; + + if (ret && desc->thermal_zone) { + cm->tzd_batt = + thermal_zone_get_zone_by_name(desc->thermal_zone); + if (IS_ERR(cm->tzd_batt)) + return PTR_ERR(cm->tzd_batt); + + /* Use external thermometer */ + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.num_properties++; + cm->desc->measure_battery_temp = true; + ret = 0; + } +#endif + if (cm->desc->measure_battery_temp) { + /* NOTICE : Default allowable minimum charge temperature is 0 */ + if (!desc->temp_max) + desc->temp_max = CM_DEFAULT_CHARGE_TEMP_MAX; + if (!desc->temp_diff) + desc->temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF; + } + + return ret; +} + +static struct of_device_id charger_manager_match[] = { + { + .compatible = "charger-manager", + }, + {}, +}; + +static struct charger_desc *of_cm_parse_desc(struct device *dev) +{ + struct charger_desc *desc; + struct device_node *np = dev->of_node; + u32 poll_mode = CM_POLL_DISABLE; + u32 battery_stat = CM_NO_BATTERY; + int num_chgs = 0; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + of_property_read_string(np, "cm-name", &desc->psy_name); + + of_property_read_u32(np, "cm-poll-mode", &poll_mode); + desc->polling_mode = poll_mode; + + of_property_read_u32(np, "cm-poll-interval", + &desc->polling_interval_ms); + + of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms", + &desc->fullbatt_vchkdrop_ms); + of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt", + &desc->fullbatt_vchkdrop_uV); + of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV); + of_property_read_u32(np, "cm-fullbatt-soc", &desc->fullbatt_soc); + of_property_read_u32(np, "cm-fullbatt-capacity", + &desc->fullbatt_full_capacity); + + of_property_read_u32(np, "cm-battery-stat", &battery_stat); + desc->battery_present = battery_stat; + + /* chargers */ + of_property_read_u32(np, "cm-num-chargers", &num_chgs); + if (num_chgs) { + /* Allocate empty bin at the tail of array */ + desc->psy_charger_stat = devm_kzalloc(dev, sizeof(char *) + * (num_chgs + 1), GFP_KERNEL); + if (desc->psy_charger_stat) { + int i; + for (i = 0; i < num_chgs; i++) + of_property_read_string_index(np, "cm-chargers", + i, &desc->psy_charger_stat[i]); + } else { + return ERR_PTR(-ENOMEM); + } + } + + of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge); + + of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone); + + of_property_read_u32(np, "cm-battery-cold", &desc->temp_min); + if (of_get_property(np, "cm-battery-cold-in-minus", NULL)) + desc->temp_min *= -1; + of_property_read_u32(np, "cm-battery-hot", &desc->temp_max); + of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff); + + of_property_read_u32(np, "cm-charging-max", + &desc->charging_max_duration_ms); + of_property_read_u32(np, "cm-discharging-max", + &desc->discharging_max_duration_ms); + + /* battery charger regualtors */ + desc->num_charger_regulators = of_get_child_count(np); + if (desc->num_charger_regulators) { + struct charger_regulator *chg_regs; + struct device_node *child; + + chg_regs = devm_kzalloc(dev, sizeof(*chg_regs) + * desc->num_charger_regulators, + GFP_KERNEL); + if (!chg_regs) + return ERR_PTR(-ENOMEM); + + desc->charger_regulators = chg_regs; + + for_each_child_of_node(np, child) { + struct charger_cable *cables; + struct device_node *_child; + + of_property_read_string(child, "cm-regulator-name", + &chg_regs->regulator_name); + + /* charger cables */ + chg_regs->num_cables = of_get_child_count(child); + if (chg_regs->num_cables) { + cables = devm_kzalloc(dev, sizeof(*cables) + * chg_regs->num_cables, + GFP_KERNEL); + if (!cables) + return ERR_PTR(-ENOMEM); + + chg_regs->cables = cables; + + for_each_child_of_node(child, _child) { + of_property_read_string(_child, + "cm-cable-name", &cables->name); + of_property_read_string(_child, + "cm-cable-extcon", + &cables->extcon_name); + of_property_read_u32(_child, + "cm-cable-min", + &cables->min_uA); + of_property_read_u32(_child, + "cm-cable-max", + &cables->max_uA); + cables++; + } + } + chg_regs++; + } + } + return desc; +} + +static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) +{ + if (pdev->dev.of_node) + return of_cm_parse_desc(&pdev->dev); + return (struct charger_desc *)dev_get_platdata(&pdev->dev); +} + static int charger_manager_probe(struct platform_device *pdev) { - struct charger_desc *desc = dev_get_platdata(&pdev->dev); + struct charger_desc *desc = cm_get_drv_data(pdev); struct charger_manager *cm; int ret = 0, i = 0; int j = 0; @@ -1452,31 +1673,23 @@ static int charger_manager_probe(struct platform_device *pdev) rtc_dev = NULL; dev_err(&pdev->dev, "Cannot get RTC %s\n", g_desc->rtc_name); - ret = -ENODEV; - goto err_alloc; + return -ENODEV; } } if (!desc) { dev_err(&pdev->dev, "No platform data (desc) found\n"); - ret = -ENODEV; - goto err_alloc; + return -ENODEV; } - cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL); - if (!cm) { - ret = -ENOMEM; - goto err_alloc; - } + cm = devm_kzalloc(&pdev->dev, + sizeof(struct charger_manager), GFP_KERNEL); + if (!cm) + return -ENOMEM; /* Basic Values. Unspecified are Null or 0 */ cm->dev = &pdev->dev; - cm->desc = kmemdup(desc, sizeof(struct charger_desc), GFP_KERNEL); - if (!cm->desc) { - ret = -ENOMEM; - goto err_alloc_desc; - } - cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ + cm->desc = desc; /* * The following two do not need to be errors. @@ -1498,27 +1711,23 @@ static int charger_manager_probe(struct platform_device *pdev) } if (!desc->charger_regulators || desc->num_charger_regulators < 1) { - ret = -EINVAL; dev_err(&pdev->dev, "charger_regulators undefined\n"); - goto err_no_charger; + return -EINVAL; } if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) { dev_err(&pdev->dev, "No power supply defined\n"); - ret = -EINVAL; - goto err_no_charger_stat; + return -EINVAL; } /* Counting index only */ while (desc->psy_charger_stat[i]) i++; - cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1), - GFP_KERNEL); - if (!cm->charger_stat) { - ret = -ENOMEM; - goto err_no_charger_stat; - } + cm->charger_stat = devm_kzalloc(&pdev->dev, + sizeof(struct power_supply *) * i, GFP_KERNEL); + if (!cm->charger_stat) + return -ENOMEM; for (i = 0; desc->psy_charger_stat[i]; i++) { cm->charger_stat[i] = power_supply_get_by_name( @@ -1526,8 +1735,7 @@ static int charger_manager_probe(struct platform_device *pdev) if (!cm->charger_stat[i]) { dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", desc->psy_charger_stat[i]); - ret = -ENODEV; - goto err_chg_stat; + return -ENODEV; } } @@ -1535,21 +1743,13 @@ static int charger_manager_probe(struct platform_device *pdev) if (!cm->fuel_gauge) { dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", desc->psy_fuel_gauge); - ret = -ENODEV; - goto err_chg_stat; + return -ENODEV; } if (desc->polling_interval_ms == 0 || msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) { dev_err(&pdev->dev, "polling_interval_ms is too small\n"); - ret = -EINVAL; - goto err_chg_stat; - } - - if (!desc->temperature_out_of_range) { - dev_err(&pdev->dev, "there is no temperature_out_of_range\n"); - ret = -EINVAL; - goto err_chg_stat; + return -EINVAL; } if (!desc->charging_max_duration_ms || @@ -1570,14 +1770,13 @@ static int charger_manager_probe(struct platform_device *pdev) cm->charger_psy.name = cm->psy_name_buf; /* Allocate for psy properties because they may vary */ - cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property) + cm->charger_psy.properties = devm_kzalloc(&pdev->dev, + sizeof(enum power_supply_property) * (ARRAY_SIZE(default_charger_props) + - NUM_CHARGER_PSY_OPTIONAL), - GFP_KERNEL); - if (!cm->charger_psy.properties) { - ret = -ENOMEM; - goto err_chg_stat; - } + NUM_CHARGER_PSY_OPTIONAL), GFP_KERNEL); + if (!cm->charger_psy.properties) + return -ENOMEM; + memcpy(cm->charger_psy.properties, default_charger_props, sizeof(enum power_supply_property) * ARRAY_SIZE(default_charger_props)); @@ -1598,14 +1797,10 @@ static int charger_manager_probe(struct platform_device *pdev) cm->charger_psy.num_properties++; } - if (desc->measure_battery_temp) { - cm->charger_psy.properties[cm->charger_psy.num_properties] = - POWER_SUPPLY_PROP_TEMP; - cm->charger_psy.num_properties++; - } else { - cm->charger_psy.properties[cm->charger_psy.num_properties] = - POWER_SUPPLY_PROP_TEMP_AMBIENT; - cm->charger_psy.num_properties++; + ret = cm_init_thermal_data(cm); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize thermal data\n"); + cm->desc->measure_battery_temp = false; } INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); @@ -1614,7 +1809,7 @@ static int charger_manager_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", cm->charger_psy.name); - goto err_register; + return ret; } /* Register extcon device for charger cable */ @@ -1655,8 +1850,6 @@ err_reg_sysfs: charger = &desc->charger_regulators[i]; sysfs_remove_group(&cm->charger_psy.dev->kobj, &charger->attr_g); - - kfree(charger->attr_g.name); } err_reg_extcon: for (i = 0; i < desc->num_charger_regulators; i++) { @@ -1674,16 +1867,7 @@ err_reg_extcon: } power_supply_unregister(&cm->charger_psy); -err_register: - kfree(cm->charger_psy.properties); -err_chg_stat: - kfree(cm->charger_stat); -err_no_charger_stat: -err_no_charger: - kfree(cm->desc); -err_alloc_desc: - kfree(cm); -err_alloc: + return ret; } @@ -1718,11 +1902,6 @@ static int charger_manager_remove(struct platform_device *pdev) try_charger_enable(cm, false); - kfree(cm->charger_psy.properties); - kfree(cm->charger_stat); - kfree(cm->desc); - kfree(cm); - return 0; } @@ -1839,6 +2018,7 @@ static struct platform_driver charger_manager_driver = { .name = "charger-manager", .owner = THIS_MODULE, .pm = &charger_manager_pm, + .of_match_table = charger_manager_match, }, .probe = charger_manager_probe, .remove = charger_manager_remove, diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c index 563174891c9..041f9b638d2 100644 --- a/drivers/power/ds2782_battery.c +++ b/drivers/power/ds2782_battery.c @@ -192,7 +192,7 @@ static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) /* * Voltage is measured in units of 1.22mV. The voltage is stored as - * a 10-bit number plus sign, in the upper bits of a 16-bit register + * a 12-bit number plus sign, in the upper bits of a 16-bit register */ err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw); if (err) diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index 4e858a23568..a0024b25219 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -28,6 +28,7 @@ struct gpio_charger { const struct gpio_charger_platform_data *pdata; unsigned int irq; + bool wakeup_enabled; struct power_supply charger; }; @@ -136,6 +137,8 @@ static int gpio_charger_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gpio_charger); + device_init_wakeup(&pdev->dev, 1); + return 0; err_gpio_free: @@ -159,18 +162,32 @@ static int gpio_charger_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP +static int gpio_charger_suspend(struct device *dev) +{ + struct gpio_charger *gpio_charger = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + gpio_charger->wakeup_enabled = + enable_irq_wake(gpio_charger->irq); + + return 0; +} + static int gpio_charger_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + if (gpio_charger->wakeup_enabled) + disable_irq_wake(gpio_charger->irq); power_supply_changed(&gpio_charger->charger); return 0; } #endif -static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, NULL, gpio_charger_resume); +static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, + gpio_charger_suspend, gpio_charger_resume); static struct platform_driver gpio_charger_driver = { .probe = gpio_charger_probe, diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index fc04d191579..0b4cf9d6329 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c @@ -2,6 +2,7 @@ * ISP1704 USB Charger Detection driver * * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2012 - 2013 Pali Rohár <pali.rohar@gmail.com> * * 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 @@ -28,6 +29,8 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #include <linux/usb/otg.h> #include <linux/usb/ulpi.h> @@ -65,10 +68,6 @@ struct isp1704_charger { unsigned present:1; unsigned online:1; unsigned current_max; - - /* temp storage variables */ - unsigned long event; - unsigned max_power; }; static inline int isp1704_read(struct isp1704_charger *isp, u32 reg) @@ -91,6 +90,8 @@ static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) if (board && board->set_power) board->set_power(on); + else if (board) + gpio_set_value(board->enable_gpio, on); } /* @@ -231,56 +232,59 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp) return ret; } +static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp) +{ + if (isp1704_charger_detect(isp) && + isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP) + return true; + else + return false; +} + static void isp1704_charger_work(struct work_struct *data) { - int detect; - unsigned long event; - unsigned power; struct isp1704_charger *isp = container_of(data, struct isp1704_charger, work); static DEFINE_MUTEX(lock); - event = isp->event; - power = isp->max_power; - mutex_lock(&lock); - if (event != USB_EVENT_NONE) - isp1704_charger_set_power(isp, 1); - - switch (event) { + switch (isp->phy->last_event) { case USB_EVENT_VBUS: - isp->online = true; - - /* detect charger */ - detect = isp1704_charger_detect(isp); + /* do not call wall charger detection more times */ + if (!isp->present) { + isp->online = true; + isp->present = 1; + isp1704_charger_set_power(isp, 1); + + /* detect wall charger */ + if (isp1704_charger_detect_dcp(isp)) { + isp->psy.type = POWER_SUPPLY_TYPE_USB_DCP; + isp->current_max = 1800; + } else { + isp->psy.type = POWER_SUPPLY_TYPE_USB; + isp->current_max = 500; + } - if (detect) { - isp->present = detect; - isp->psy.type = isp1704_charger_type(isp); + /* enable data pullups */ + if (isp->phy->otg->gadget) + usb_gadget_connect(isp->phy->otg->gadget); } - switch (isp->psy.type) { - case POWER_SUPPLY_TYPE_USB_DCP: - isp->current_max = 1800; - break; - case POWER_SUPPLY_TYPE_USB_CDP: + if (isp->psy.type != POWER_SUPPLY_TYPE_USB_DCP) { /* * Only 500mA here or high speed chirp * handshaking may break */ - isp->current_max = 500; - /* FALLTHROUGH */ - case POWER_SUPPLY_TYPE_USB: - default: - /* enable data pullups */ - if (isp->phy->otg->gadget) - usb_gadget_connect(isp->phy->otg->gadget); + if (isp->current_max > 500) + isp->current_max = 500; + + if (isp->current_max > 100) + isp->psy.type = POWER_SUPPLY_TYPE_USB_CDP; } break; case USB_EVENT_NONE: isp->online = false; - isp->current_max = 0; isp->present = 0; isp->current_max = 0; isp->psy.type = POWER_SUPPLY_TYPE_USB; @@ -298,12 +302,6 @@ static void isp1704_charger_work(struct work_struct *data) isp1704_charger_set_power(isp, 0); break; - case USB_EVENT_ENUMERATED: - if (isp->present) - isp->current_max = 1800; - else - isp->current_max = power; - break; default: goto out; } @@ -314,16 +312,11 @@ out: } static int isp1704_notifier_call(struct notifier_block *nb, - unsigned long event, void *power) + unsigned long val, void *v) { struct isp1704_charger *isp = container_of(nb, struct isp1704_charger, nb); - isp->event = event; - - if (power) - isp->max_power = *((unsigned *)power); - schedule_work(&isp->work); return NOTIFY_OK; @@ -411,13 +404,46 @@ static int isp1704_charger_probe(struct platform_device *pdev) struct isp1704_charger *isp; int ret = -ENODEV; + struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + + if (np) { + int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0); + + if (gpio < 0) + return gpio; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct isp1704_charger_data), GFP_KERNEL); + pdata->enable_gpio = gpio; + + dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio); + + ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio, + GPIOF_OUT_INIT_HIGH, "isp1704_reset"); + if (ret) + goto fail0; + } + + if (!pdata) { + dev_err(&pdev->dev, "missing platform data!\n"); + return -ENODEV; + } + + isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; - isp->phy = usb_get_phy(USB_PHY_TYPE_USB2); - if (IS_ERR_OR_NULL(isp->phy)) + if (np) + isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + else + isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + + if (IS_ERR(isp->phy)) { + ret = PTR_ERR(isp->phy); goto fail0; + } isp->dev = &pdev->dev; platform_set_drvdata(pdev, isp); @@ -462,20 +488,19 @@ static int isp1704_charger_probe(struct platform_device *pdev) if (isp->phy->otg->gadget) usb_gadget_disconnect(isp->phy->otg->gadget); + if (isp->phy->last_event == USB_EVENT_NONE) + isp1704_charger_set_power(isp, 0); + /* Detect charger if VBUS is valid (the cable was already plugged). */ - ret = isp1704_read(isp, ULPI_USB_INT_STS); - isp1704_charger_set_power(isp, 0); - if ((ret & ULPI_INT_VBUS_VALID) && !isp->phy->otg->default_a) { - isp->event = USB_EVENT_VBUS; + if (isp->phy->last_event == USB_EVENT_VBUS && + !isp->phy->otg->default_a) schedule_work(&isp->work); - } return 0; fail2: power_supply_unregister(&isp->psy); fail1: isp1704_charger_set_power(isp, 0); - usb_put_phy(isp->phy); fail0: dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); @@ -488,15 +513,23 @@ static int isp1704_charger_remove(struct platform_device *pdev) usb_unregister_notifier(isp->phy, &isp->nb); power_supply_unregister(&isp->psy); - usb_put_phy(isp->phy); isp1704_charger_set_power(isp, 0); return 0; } +#ifdef CONFIG_OF +static const struct of_device_id omap_isp1704_of_match[] = { + { .compatible = "nxp,isp1704", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_isp1704_of_match); +#endif + static struct platform_driver isp1704_charger_driver = { .driver = { .name = "isp1704_charger", + .of_match_table = of_match_ptr(omap_isp1704_of_match), }, .probe = isp1704_charger_probe, .remove = isp1704_charger_remove, diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c index d9686aa9270..6c8931d4ad6 100644 --- a/drivers/power/jz4740-battery.c +++ b/drivers/power/jz4740-battery.c @@ -73,7 +73,7 @@ static long jz_battery_read_voltage(struct jz_battery *battery) mutex_lock(&battery->lock); - INIT_COMPLETION(battery->read_completion); + reinit_completion(&battery->read_completion); enable_irq(battery->irq); battery->cell->enable(battery->pdev); diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c new file mode 100644 index 00000000000..fad2a75b360 --- /dev/null +++ b/drivers/power/max14577_charger.c @@ -0,0 +1,311 @@ +/* + * Battery charger driver for the Maxim 14577 + * + * Copyright (C) 2013 Samsung Electronics + * Krzysztof Kozlowski <k.kozlowski@samsung.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/max14577-private.h> + +struct max14577_charger { + struct device *dev; + struct max14577 *max14577; + struct power_supply charger; + + unsigned int charging_state; + unsigned int battery_state; +}; + +static int max14577_get_charger_state(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + int state = POWER_SUPPLY_STATUS_DISCHARGING; + u8 reg_data; + + /* + * Charging occurs only if: + * - CHGCTRL2/MBCHOSTEN == 1 + * - STATUS2/CGMBC == 1 + * + * TODO: + * - handle FULL after Top-off timer (EOC register may be off + * and the charger won't be charging although MBCHOSTEN is on) + * - handle properly dead-battery charging (respect timer) + * - handle timers (fast-charge and prequal) /MBCCHGERR/ + */ + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) + goto state_set; + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (reg_data & STATUS3_CGMBC_MASK) { + /* Charger or USB-cable is connected */ + if (reg_data & STATUS3_EOC_MASK) + state = POWER_SUPPLY_STATUS_FULL; + else + state = POWER_SUPPLY_STATUS_CHARGING; + goto state_set; + } + +state_set: + chg->charging_state = state; + return state; +} + +/* + * Supported charge types: + * - POWER_SUPPLY_CHARGE_TYPE_NONE + * - POWER_SUPPLY_CHARGE_TYPE_FAST + */ +static int max14577_get_charge_type(struct max14577_charger *chg) +{ + /* + * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)? + * As spec says: + * [after reaching EOC interrupt] + * "When the battery is fully charged, the 30-minute (typ) + * top-off timer starts. The device continues to trickle + * charge the battery until the top-off timer runs out." + */ + if (max14577_get_charger_state(chg) == POWER_SUPPLY_STATUS_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int max14577_get_online(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + + max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + switch (reg_data) { + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + return 1; + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_RESERVED: + default: + return 0; + } +} + +/* + * Supported health statuses: + * - POWER_SUPPLY_HEALTH_DEAD + * - POWER_SUPPLY_HEALTH_OVERVOLTAGE + * - POWER_SUPPLY_HEALTH_GOOD + */ +static int max14577_get_battery_health(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + int state = POWER_SUPPLY_HEALTH_GOOD; + u8 reg_data; + + max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + state = POWER_SUPPLY_HEALTH_DEAD; + goto state_set; + } + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (reg_data & STATUS3_OVP_MASK) { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto state_set; + } + +state_set: + chg->battery_state = state; + return state; +} + +/* + * Always returns 1. + * The max14577 chip doesn't report any status of battery presence. + * Lets assume that it will always be used with some battery. + */ +static int max14577_get_present(struct max14577_charger *chg) +{ + return 1; +} + +/* + * Sets charger registers to proper and safe default values. + * Some of these values are equal to defaults in MAX14577E + * data sheet but there are minor differences. + */ +static void max14577_charger_reg_init(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + + /* + * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) + * Charger-Detection Enable, default on (set CHGDETEN to 1) + * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit + */ + reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT; + max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1, + CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, + reg_data); + + /* Battery Fast-Charge Timer, from SM-V700: 6hrs */ + reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data); + + /* + * Wall-Adapter Rapid Charge, default on + * Battery-Charger, default on + */ + reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT; + reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); + + /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */ + reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data); + + /* + * Fast Battery-Charge Current Low, default 200-950mA + * Fast Battery-Charge Current High, from SM-V700: 450mA + */ + reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data); + + /* End-of-Charge Current, from SM-V700: 50mA */ + reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); + + /* Auto Charging Stop, default off */ + reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); + + /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ + reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); +} + +/* Support property from charger */ +static enum power_supply_property max14577_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const char *model_name = "MAX14577"; +static const char *manufacturer = "Maxim Integrated"; + +static int max14577_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max14577_charger *chg = container_of(psy, + struct max14577_charger, + charger); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max14577_get_charger_state(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = max14577_get_charge_type(chg); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max14577_get_battery_health(chg); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max14577_get_present(chg); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max14577_get_online(chg); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int max14577_charger_probe(struct platform_device *pdev) +{ + struct max14577_charger *chg; + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); + int ret; + + chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = &pdev->dev; + chg->max14577 = max14577; + + max14577_charger_reg_init(chg); + + chg->charger.name = "max14577-charger", + chg->charger.type = POWER_SUPPLY_TYPE_BATTERY, + chg->charger.properties = max14577_charger_props, + chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props), + chg->charger.get_property = max14577_charger_get_property, + + ret = power_supply_register(&pdev->dev, &chg->charger); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return ret; + } + + return 0; +} + +static int max14577_charger_remove(struct platform_device *pdev) +{ + struct max14577_charger *chg = platform_get_drvdata(pdev); + + power_supply_unregister(&chg->charger); + + return 0; +} + +static struct platform_driver max14577_charger_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max14577-charger", + }, + .probe = max14577_charger_probe, + .remove = max14577_charger_remove, +}; +module_platform_driver(max14577_charger_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); +MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index c7ff6d67f15..0fbac861080 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -148,7 +148,7 @@ static void max17040_get_online(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - if (chip->pdata->battery_online) + if (chip->pdata && chip->pdata->battery_online) chip->online = chip->pdata->battery_online(); else chip->online = 1; @@ -158,7 +158,8 @@ static void max17040_get_status(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - if (!chip->pdata->charger_online || !chip->pdata->charger_enable) { + if (!chip->pdata || !chip->pdata->charger_online + || !chip->pdata->charger_enable) { chip->status = POWER_SUPPLY_STATUS_UNKNOWN; return; } diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index d664ef58afa..66da691c41c 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -33,6 +33,7 @@ #include <linux/power_supply.h> #include <linux/power/max17042_battery.h> #include <linux/of.h> +#include <linux/regmap.h> /* Status register bits */ #define STATUS_POR_BIT (1 << 1) @@ -67,6 +68,7 @@ struct max17042_chip { struct i2c_client *client; + struct regmap *regmap; struct power_supply battery; enum max170xx_chip_type chip_type; struct max17042_platform_data *pdata; @@ -74,35 +76,6 @@ struct max17042_chip { int init_complete; }; -static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value) -{ - int ret = i2c_smbus_write_word_data(client, reg, value); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; -} - -static int max17042_read_reg(struct i2c_client *client, u8 reg) -{ - int ret = i2c_smbus_read_word_data(client, reg); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; -} - -static void max17042_set_reg(struct i2c_client *client, - struct max17042_reg_data *data, int size) -{ - int i; - - for (i = 0; i < size; i++) - max17042_write_reg(client, data[i].addr, data[i].data); -} - static enum power_supply_property max17042_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CYCLE_COUNT, @@ -125,96 +98,98 @@ static int max17042_get_property(struct power_supply *psy, { struct max17042_chip *chip = container_of(psy, struct max17042_chip, battery); + struct regmap *map = chip->regmap; int ret; + u32 data; if (!chip->init_complete) return -EAGAIN; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: - ret = max17042_read_reg(chip->client, MAX17042_STATUS); + ret = regmap_read(map, MAX17042_STATUS, &data); if (ret < 0) return ret; - if (ret & MAX17042_STATUS_BattAbsent) + if (data & MAX17042_STATUS_BattAbsent) val->intval = 0; else val->intval = 1; break; case POWER_SUPPLY_PROP_CYCLE_COUNT: - ret = max17042_read_reg(chip->client, MAX17042_Cycles); + ret = regmap_read(map, MAX17042_Cycles, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: - ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt); + ret = regmap_read(map, MAX17042_MinMaxVolt, &data); if (ret < 0) return ret; - val->intval = ret >> 8; + val->intval = data >> 8; val->intval *= 20000; /* Units of LSB = 20mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: if (chip->chip_type == MAX17042) - ret = max17042_read_reg(chip->client, MAX17042_V_empty); + ret = regmap_read(map, MAX17042_V_empty, &data); else - ret = max17042_read_reg(chip->client, MAX17047_V_empty); + ret = regmap_read(map, MAX17047_V_empty, &data); if (ret < 0) return ret; - val->intval = ret >> 7; + val->intval = data >> 7; val->intval *= 10000; /* Units of LSB = 10mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = max17042_read_reg(chip->client, MAX17042_VCELL); + ret = regmap_read(map, MAX17042_VCELL, &data); if (ret < 0) return ret; - val->intval = ret * 625 / 8; + val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: - ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL); + ret = regmap_read(map, MAX17042_AvgVCELL, &data); if (ret < 0) return ret; - val->intval = ret * 625 / 8; + val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_VOLTAGE_OCV: - ret = max17042_read_reg(chip->client, MAX17042_OCVInternal); + ret = regmap_read(map, MAX17042_OCVInternal, &data); if (ret < 0) return ret; - val->intval = ret * 625 / 8; + val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_CAPACITY: - ret = max17042_read_reg(chip->client, MAX17042_RepSOC); + ret = regmap_read(map, MAX17042_RepSOC, &data); if (ret < 0) return ret; - val->intval = ret >> 8; + val->intval = data >> 8; break; case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = max17042_read_reg(chip->client, MAX17042_FullCAP); + ret = regmap_read(map, MAX17042_FullCAP, &data); if (ret < 0) return ret; - val->intval = ret * 1000 / 2; + val->intval = data * 1000 / 2; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER: - ret = max17042_read_reg(chip->client, MAX17042_QH); + ret = regmap_read(map, MAX17042_QH, &data); if (ret < 0) return ret; - val->intval = ret * 1000 / 2; + val->intval = data * 1000 / 2; break; case POWER_SUPPLY_PROP_TEMP: - ret = max17042_read_reg(chip->client, MAX17042_TEMP); + ret = regmap_read(map, MAX17042_TEMP, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; /* The value is signed. */ if (val->intval & 0x8000) { val->intval = (0x7fff & ~val->intval) + 1; @@ -226,11 +201,11 @@ static int max17042_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (chip->pdata->enable_current_sense) { - ret = max17042_read_reg(chip->client, MAX17042_Current); + ret = regmap_read(map, MAX17042_Current, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; if (val->intval & 0x8000) { /* Negative */ val->intval = ~val->intval & 0x7fff; @@ -244,12 +219,11 @@ static int max17042_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CURRENT_AVG: if (chip->pdata->enable_current_sense) { - ret = max17042_read_reg(chip->client, - MAX17042_AvgCurrent); + ret = regmap_read(map, MAX17042_AvgCurrent, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; if (val->intval & 0x8000) { /* Negative */ val->intval = ~val->intval & 0x7fff; @@ -267,16 +241,15 @@ static int max17042_get_property(struct power_supply *psy, return 0; } -static int max17042_write_verify_reg(struct i2c_client *client, - u8 reg, u16 value) +static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) { int retries = 8; int ret; - u16 read_value; + u32 read_value; do { - ret = i2c_smbus_write_word_data(client, reg, value); - read_value = max17042_read_reg(client, reg); + ret = regmap_write(map, reg, value); + regmap_read(map, reg, &read_value); if (read_value != value) { ret = -EIO; retries--; @@ -284,50 +257,51 @@ static int max17042_write_verify_reg(struct i2c_client *client, } while (retries && read_value != value); if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); + pr_err("%s: err %d\n", __func__, ret); return ret; } -static inline void max17042_override_por( - struct i2c_client *client, u8 reg, u16 value) +static inline void max17042_override_por(struct regmap *map, + u8 reg, u16 value) { if (value) - max17042_write_reg(client, reg, value); + regmap_write(map, reg, value); } static inline void max10742_unlock_model(struct max17042_chip *chip) { - struct i2c_client *client = chip->client; - max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_UNLOCK1); - max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_UNLOCK2); + struct regmap *map = chip->regmap; + regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1); + regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2); } static inline void max10742_lock_model(struct max17042_chip *chip) { - struct i2c_client *client = chip->client; - max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_LOCK1); - max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_LOCK2); + struct regmap *map = chip->regmap; + + regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1); + regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2); } static inline void max17042_write_model_data(struct max17042_chip *chip, u8 addr, int size) { - struct i2c_client *client = chip->client; + struct regmap *map = chip->regmap; int i; for (i = 0; i < size; i++) - max17042_write_reg(client, addr + i, - chip->pdata->config_data->cell_char_tbl[i]); + regmap_write(map, addr + i, + chip->pdata->config_data->cell_char_tbl[i]); } static inline void max17042_read_model_data(struct max17042_chip *chip, - u8 addr, u16 *data, int size) + u8 addr, u32 *data, int size) { - struct i2c_client *client = chip->client; + struct regmap *map = chip->regmap; int i; for (i = 0; i < size; i++) - data[i] = max17042_read_reg(client, addr + i); + regmap_read(map, addr + i, &data[i]); } static inline int max17042_model_data_compare(struct max17042_chip *chip, @@ -350,7 +324,7 @@ static int max17042_init_model(struct max17042_chip *chip) { int ret; int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); - u16 *temp_data; + u32 *temp_data; temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); if (!temp_data) @@ -365,7 +339,7 @@ static int max17042_init_model(struct max17042_chip *chip) ret = max17042_model_data_compare( chip, chip->pdata->config_data->cell_char_tbl, - temp_data, + (u16 *)temp_data, table_size); max10742_lock_model(chip); @@ -378,7 +352,7 @@ static int max17042_verify_model_lock(struct max17042_chip *chip) { int i; int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); - u16 *temp_data; + u32 *temp_data; int ret = 0; temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); @@ -398,40 +372,38 @@ static int max17042_verify_model_lock(struct max17042_chip *chip) static void max17042_write_config_regs(struct max17042_chip *chip) { struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; - max17042_write_reg(chip->client, MAX17042_CONFIG, config->config); - max17042_write_reg(chip->client, MAX17042_LearnCFG, config->learn_cfg); - max17042_write_reg(chip->client, MAX17042_FilterCFG, + regmap_write(map, MAX17042_CONFIG, config->config); + regmap_write(map, MAX17042_LearnCFG, config->learn_cfg); + regmap_write(map, MAX17042_FilterCFG, config->filter_cfg); - max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg); + regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg); if (chip->chip_type == MAX17047) - max17042_write_reg(chip->client, MAX17047_FullSOCThr, + regmap_write(map, MAX17047_FullSOCThr, config->full_soc_thresh); } static void max17042_write_custom_regs(struct max17042_chip *chip) { struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; - max17042_write_verify_reg(chip->client, MAX17042_RCOMP0, - config->rcomp0); - max17042_write_verify_reg(chip->client, MAX17042_TempCo, - config->tcompc0); - max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm, - config->ichgt_term); + max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0); + max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0); + max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term); if (chip->chip_type == MAX17042) { - max17042_write_reg(chip->client, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_write_verify_reg(chip->client, MAX17042_K_empty0, + regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco); + max17042_write_verify_reg(map, MAX17042_K_empty0, config->kempty0); } else { - max17042_write_verify_reg(chip->client, MAX17047_QRTbl00, + max17042_write_verify_reg(map, MAX17047_QRTbl00, config->qrtbl00); - max17042_write_verify_reg(chip->client, MAX17047_QRTbl10, + max17042_write_verify_reg(map, MAX17047_QRTbl10, config->qrtbl10); - max17042_write_verify_reg(chip->client, MAX17047_QRTbl20, + max17042_write_verify_reg(map, MAX17047_QRTbl20, config->qrtbl20); - max17042_write_verify_reg(chip->client, MAX17047_QRTbl30, + max17042_write_verify_reg(map, MAX17047_QRTbl30, config->qrtbl30); } } @@ -439,58 +411,60 @@ static void max17042_write_custom_regs(struct max17042_chip *chip) static void max17042_update_capacity_regs(struct max17042_chip *chip) { struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; - max17042_write_verify_reg(chip->client, MAX17042_FullCAP, + max17042_write_verify_reg(map, MAX17042_FullCAP, config->fullcap); - max17042_write_reg(chip->client, MAX17042_DesignCap, - config->design_cap); - max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, + regmap_write(map, MAX17042_DesignCap, config->design_cap); + max17042_write_verify_reg(map, MAX17042_FullCAPNom, config->fullcapnom); } static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) { - u16 vfSoc; + unsigned int vfSoc; + struct regmap *map = chip->regmap; - vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); - max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); - max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, vfSoc); - max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK); + regmap_read(map, MAX17042_VFSOC, &vfSoc); + regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); + max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc); + regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK); } static void max17042_load_new_capacity_params(struct max17042_chip *chip) { - u16 full_cap0, rep_cap, dq_acc, vfSoc; + u32 full_cap0, rep_cap, dq_acc, vfSoc; u32 rem_cap; struct max17042_config_data *config = chip->pdata->config_data; + struct regmap *map = chip->regmap; - full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0); - vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); + regmap_read(map, MAX17042_FullCAP0, &full_cap0); + regmap_read(map, MAX17042_VFSOC, &vfSoc); /* fg_vfSoc needs to shifted by 8 bits to get the * perc in 1% accuracy, to get the right rem_cap multiply * full_cap0, fg_vfSoc and devide by 100 */ rem_cap = ((vfSoc >> 8) * full_cap0) / 100; - max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap); + max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap); - rep_cap = (u16)rem_cap; - max17042_write_verify_reg(chip->client, MAX17042_RepCap, rep_cap); + rep_cap = rem_cap; + max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap); /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */ dq_acc = config->fullcap / dQ_ACC_DIV; - max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc); - max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200); + max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc); + max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200); - max17042_write_verify_reg(chip->client, MAX17042_FullCAP, + max17042_write_verify_reg(map, MAX17042_FullCAP, config->fullcap); - max17042_write_reg(chip->client, MAX17042_DesignCap, + regmap_write(map, MAX17042_DesignCap, config->design_cap); - max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, + max17042_write_verify_reg(map, MAX17042_FullCAPNom, config->fullcapnom); /* Update SOC register with new SOC */ - max17042_write_reg(chip->client, MAX17042_RepSOC, vfSoc); + regmap_write(map, MAX17042_RepSOC, vfSoc); } /* @@ -500,59 +474,60 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip) */ static inline void max17042_override_por_values(struct max17042_chip *chip) { - struct i2c_client *client = chip->client; + struct regmap *map = chip->regmap; struct max17042_config_data *config = chip->pdata->config_data; - max17042_override_por(client, MAX17042_TGAIN, config->tgain); - max17042_override_por(client, MAx17042_TOFF, config->toff); - max17042_override_por(client, MAX17042_CGAIN, config->cgain); - max17042_override_por(client, MAX17042_COFF, config->coff); - - max17042_override_por(client, MAX17042_VALRT_Th, config->valrt_thresh); - max17042_override_por(client, MAX17042_TALRT_Th, config->talrt_thresh); - max17042_override_por(client, MAX17042_SALRT_Th, - config->soc_alrt_thresh); - max17042_override_por(client, MAX17042_CONFIG, config->config); - max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer); - - max17042_override_por(client, MAX17042_DesignCap, config->design_cap); - max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term); - - max17042_override_por(client, MAX17042_AtRate, config->at_rate); - max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg); - max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg); - max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg); - max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg); - max17042_override_por(client, MAX17042_MaskSOC, config->masksoc); - - max17042_override_por(client, MAX17042_FullCAP, config->fullcap); - max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom); + max17042_override_por(map, MAX17042_TGAIN, config->tgain); + max17042_override_por(map, MAx17042_TOFF, config->toff); + max17042_override_por(map, MAX17042_CGAIN, config->cgain); + max17042_override_por(map, MAX17042_COFF, config->coff); + + max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh); + max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh); + max17042_override_por(map, MAX17042_SALRT_Th, + config->soc_alrt_thresh); + max17042_override_por(map, MAX17042_CONFIG, config->config); + max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer); + + max17042_override_por(map, MAX17042_DesignCap, config->design_cap); + max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term); + + max17042_override_por(map, MAX17042_AtRate, config->at_rate); + max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg); + 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 == MAX17042) - max17042_override_por(client, MAX17042_SOC_empty, + max17042_override_por(map, MAX17042_SOC_empty, config->socempty); - max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty); - max17042_override_por(client, MAX17042_dQacc, config->dqacc); - max17042_override_por(client, MAX17042_dPacc, config->dpacc); + 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 == MAX17042) - max17042_override_por(client, MAX17042_V_empty, config->vempty); + max17042_override_por(map, MAX17042_V_empty, config->vempty); else - max17042_override_por(client, MAX17047_V_empty, config->vempty); - max17042_override_por(client, MAX17042_TempNom, config->temp_nom); - max17042_override_por(client, MAX17042_TempLim, config->temp_lim); - max17042_override_por(client, MAX17042_FCTC, config->fctc); - max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0); - max17042_override_por(client, MAX17042_TempCo, config->tcompc0); + max17042_override_por(map, MAX17047_V_empty, config->vempty); + 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) { - max17042_override_por(client, MAX17042_EmptyTempCo, - config->empty_tempco); - max17042_override_por(client, MAX17042_K_empty0, - config->kempty0); + max17042_override_por(map, MAX17042_EmptyTempCo, + config->empty_tempco); + max17042_override_por(map, MAX17042_K_empty0, + config->kempty0); } } static int max17042_init_chip(struct max17042_chip *chip) { + struct regmap *map = chip->regmap; int ret; int val; @@ -597,31 +572,32 @@ static int max17042_init_chip(struct max17042_chip *chip) max17042_load_new_capacity_params(chip); /* Init complete, Clear the POR bit */ - val = max17042_read_reg(chip->client, MAX17042_STATUS); - max17042_write_reg(chip->client, MAX17042_STATUS, - val & (~STATUS_POR_BIT)); + regmap_read(map, MAX17042_STATUS, &val); + regmap_write(map, MAX17042_STATUS, val & (~STATUS_POR_BIT)); return 0; } static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) { - u16 soc, soc_tr; + struct regmap *map = chip->regmap; + u32 soc, soc_tr; /* program interrupt thesholds such that we should * get interrupt for every 'off' perc change in the soc */ - soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8; + regmap_read(map, MAX17042_RepSOC, &soc); + soc >>= 8; soc_tr = (soc + off) << 8; soc_tr |= (soc - off); - max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr); + regmap_write(map, MAX17042_SALRT_Th, soc_tr); } static irqreturn_t max17042_thread_handler(int id, void *dev) { struct max17042_chip *chip = dev; - u16 val; + u32 val; - val = max17042_read_reg(chip->client, MAX17042_STATUS); + 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"); @@ -682,13 +658,20 @@ max17042_get_pdata(struct device *dev) } #endif +static struct regmap_config max17042_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + static int max17042_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct max17042_chip *chip; int ret; - int reg; + int i; + u32 val; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) return -EIO; @@ -698,6 +681,12 @@ static int max17042_probe(struct i2c_client *client, return -ENOMEM; chip->client = client; + chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "Failed to initialize regmap\n"); + return -EINVAL; + } + chip->pdata = max17042_get_pdata(&client->dev); if (!chip->pdata) { dev_err(&client->dev, "no platform data provided\n"); @@ -706,15 +695,15 @@ static int max17042_probe(struct i2c_client *client, i2c_set_clientdata(client, chip); - ret = max17042_read_reg(chip->client, MAX17042_DevName); - if (ret == MAX17042_IC_VERSION) { + regmap_read(chip->regmap, MAX17042_DevName, &val); + if (val == MAX17042_IC_VERSION) { dev_dbg(&client->dev, "chip type max17042 detected\n"); chip->chip_type = MAX17042; - } else if (ret == MAX17047_IC_VERSION) { + } else if (val == MAX17047_IC_VERSION) { dev_dbg(&client->dev, "chip type max17047/50 detected\n"); chip->chip_type = MAX17047; } else { - dev_err(&client->dev, "device version mismatch: %x\n", ret); + dev_err(&client->dev, "device version mismatch: %x\n", val); return -EIO; } @@ -733,13 +722,15 @@ static int max17042_probe(struct i2c_client *client, chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; if (chip->pdata->init_data) - max17042_set_reg(client, chip->pdata->init_data, - chip->pdata->num_init_data); + for (i = 0; i < chip->pdata->num_init_data; i++) + regmap_write(chip->regmap, + chip->pdata->init_data[i].addr, + chip->pdata->init_data[i].data); if (!chip->pdata->enable_current_sense) { - max17042_write_reg(client, MAX17042_CGAIN, 0x0000); - max17042_write_reg(client, MAX17042_MiscCFG, 0x0003); - max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); + regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000); + regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003); + regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); } ret = power_supply_register(&client->dev, &chip->battery); @@ -750,13 +741,13 @@ static int max17042_probe(struct i2c_client *client, if (client->irq) { ret = request_threaded_irq(client->irq, NULL, - max17042_thread_handler, - IRQF_TRIGGER_FALLING, - chip->battery.name, chip); + max17042_thread_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + chip->battery.name, chip); if (!ret) { - reg = max17042_read_reg(client, MAX17042_CONFIG); - reg |= CONFIG_ALRT_BIT_ENBL; - max17042_write_reg(client, MAX17042_CONFIG, reg); + regmap_read(chip->regmap, MAX17042_CONFIG, &val); + val |= CONFIG_ALRT_BIT_ENBL; + regmap_write(chip->regmap, MAX17042_CONFIG, val); max17042_set_soc_threshold(chip, 1); } else { client->irq = 0; @@ -765,8 +756,8 @@ static int max17042_probe(struct i2c_client *client, } } - reg = max17042_read_reg(chip->client, MAX17042_STATUS); - if (reg & STATUS_POR_BIT) { + regmap_read(chip->regmap, MAX17042_STATUS, &val); + if (val & STATUS_POR_BIT) { INIT_WORK(&chip->work, max17042_init_worker); schedule_work(&chip->work); } else { @@ -786,7 +777,7 @@ static int max17042_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP static int max17042_suspend(struct device *dev) { struct max17042_chip *chip = dev_get_drvdata(dev); @@ -816,17 +807,11 @@ static int max17042_resume(struct device *dev) return 0; } - -static const struct dev_pm_ops max17042_pm_ops = { - .suspend = max17042_suspend, - .resume = max17042_resume, -}; - -#define MAX17042_PM_OPS (&max17042_pm_ops) -#else -#define MAX17042_PM_OPS NULL #endif +static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend, + max17042_resume); + #ifdef CONFIG_OF static const struct of_device_id max17042_dt_match[] = { { .compatible = "maxim,max17042" }, @@ -849,7 +834,7 @@ static struct i2c_driver max17042_i2c_driver = { .driver = { .name = "max17042", .of_match_table = of_match_ptr(max17042_dt_match), - .pm = MAX17042_PM_OPS, + .pm = &max17042_pm_ops, }, .probe = max17042_probe, .remove = max17042_remove, diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c index ffa10ed83eb..62c15af58c9 100644 --- a/drivers/power/pm2301_charger.c +++ b/drivers/power/pm2301_charger.c @@ -205,7 +205,7 @@ static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) } -int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) +static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) { queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); @@ -722,8 +722,12 @@ static int pm2xxx_charger_ac_en(struct ux500_charger *charger, dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset); if (!pm2->vddadc_en_ac) { - regulator_enable(pm2->regu); - pm2->vddadc_en_ac = true; + ret = regulator_enable(pm2->regu); + if (ret) + dev_warn(pm2->dev, + "Failed to enable vddadc regulator\n"); + else + pm2->vddadc_en_ac = true; } ret = pm2xxx_charging_init(pm2); @@ -953,37 +957,24 @@ static int pm2xxx_runtime_suspend(struct device *dev) { struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); struct pm2xxx_charger *pm2; - int ret = 0; pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); - if (!pm2) { - dev_err(pm2->dev, "no pm2xxx_charger data supplied\n"); - ret = -EINVAL; - return ret; - } - clear_lpn_pin(pm2); - return ret; + return 0; } static int pm2xxx_runtime_resume(struct device *dev) { struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); struct pm2xxx_charger *pm2; - int ret = 0; pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); - if (!pm2) { - dev_err(pm2->dev, "no pm2xxx_charger data supplied\n"); - ret = -EINVAL; - return ret; - } if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0) set_lpn_pin(pm2); - return ret; + return 0; } #endif diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 00e66729636..5a5a24e7d43 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -15,6 +15,7 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/device.h> +#include <linux/notifier.h> #include <linux/err.h> #include <linux/power_supply.h> #include <linux/thermal.h> @@ -24,6 +25,9 @@ struct class *power_supply_class; EXPORT_SYMBOL_GPL(power_supply_class); +ATOMIC_NOTIFIER_HEAD(power_supply_notifier); +EXPORT_SYMBOL_GPL(power_supply_notifier); + static struct device_type power_supply_dev_type; static bool __power_supply_is_supplied_by(struct power_supply *supplier, @@ -80,6 +84,8 @@ static void power_supply_changed_work(struct work_struct *work) class_for_each_device(power_supply_class, NULL, psy, __power_supply_changed_work); power_supply_update_leds(psy); + atomic_notifier_call_chain(&power_supply_notifier, + PSY_EVENT_PROP_CHANGED, psy); kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); spin_lock_irqsave(&psy->changed_lock, flags); } @@ -335,6 +341,32 @@ struct power_supply *power_supply_get_by_name(const char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +#ifdef CONFIG_OF +static int power_supply_match_device_node(struct device *dev, const void *data) +{ + return dev->parent && dev->parent->of_node == data; +} + +struct power_supply *power_supply_get_by_phandle(struct device_node *np, + const char *property) +{ + struct device_node *power_supply_np; + struct device *dev; + + power_supply_np = of_parse_phandle(np, property, 0); + if (!power_supply_np) + return ERR_PTR(-ENODEV); + + dev = class_find_device(power_supply_class, NULL, power_supply_np, + power_supply_match_device_node); + + of_node_put(power_supply_np); + + return dev ? dev_get_drvdata(dev) : NULL; +} +EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); +#endif /* CONFIG_OF */ + int power_supply_powers(struct power_supply *psy, struct device *dev) { return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers"); @@ -347,6 +379,18 @@ static void power_supply_dev_release(struct device *dev) kfree(dev); } +int power_supply_reg_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&power_supply_notifier, nb); +} +EXPORT_SYMBOL_GPL(power_supply_reg_notifier); + +void power_supply_unreg_notifier(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&power_supply_notifier, nb); +} +EXPORT_SYMBOL_GPL(power_supply_unreg_notifier); + #ifdef CONFIG_THERMAL static int power_supply_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) @@ -493,7 +537,7 @@ static void psy_unregister_cooler(struct power_supply *psy) } #endif -int power_supply_register(struct device *parent, struct power_supply *psy) +int __power_supply_register(struct device *parent, struct power_supply *psy, bool ws) { struct device *dev; int rc; @@ -511,6 +555,10 @@ int power_supply_register(struct device *parent, struct power_supply *psy) dev_set_drvdata(dev, psy); psy->dev = dev; + rc = dev_set_name(dev, "%s", psy->name); + if (rc) + goto dev_set_name_failed; + INIT_WORK(&psy->changed_work, power_supply_changed_work); rc = power_supply_check_supplies(psy); @@ -520,14 +568,10 @@ int power_supply_register(struct device *parent, struct power_supply *psy) } spin_lock_init(&psy->changed_lock); - rc = device_init_wakeup(dev, true); + rc = device_init_wakeup(dev, ws); if (rc) goto wakeup_init_failed; - rc = kobject_set_name(&dev->kobj, "%s", psy->name); - if (rc) - goto kobject_set_name_failed; - rc = device_add(dev); if (rc) goto device_add_failed; @@ -553,17 +597,28 @@ create_triggers_failed: register_cooler_failed: psy_unregister_thermal(psy); register_thermal_failed: -wakeup_init_failed: device_del(dev); -kobject_set_name_failed: device_add_failed: +wakeup_init_failed: check_supplies_failed: +dev_set_name_failed: put_device(dev); success: return rc; } + +int power_supply_register(struct device *parent, struct power_supply *psy) +{ + return __power_supply_register(parent, psy, true); +} EXPORT_SYMBOL_GPL(power_supply_register); +int power_supply_register_no_ws(struct device *parent, struct power_supply *psy) +{ + return __power_supply_register(parent, psy, false); +} +EXPORT_SYMBOL_GPL(power_supply_register_no_ws); + void power_supply_unregister(struct power_supply *psy) { cancel_work_sync(&psy->changed_work); diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 9b3ea535b47..bdcf5173e37 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -6,6 +6,20 @@ menuconfig POWER_RESET Say Y here to enable board reset and power off +config POWER_RESET_AS3722 + bool "ams AS3722 power-off driver" + depends on MFD_AS3722 && POWER_RESET + help + This driver supports turning off board via a ams AS3722 power-off. + +config POWER_RESET_AXXIA + bool "LSI Axxia reset driver" + depends on POWER_RESET && ARCH_AXXIA + help + This driver supports restart for Axxia SoC. + + Say Y if you have an Axxia family SoC. + config POWER_RESET_GPIO bool "GPIO power-off driver" depends on OF_GPIO && POWER_RESET @@ -16,7 +30,7 @@ config POWER_RESET_GPIO config POWER_RESET_MSM bool "Qualcomm MSM power-off driver" - depends on POWER_RESET && ARCH_MSM + depends on POWER_RESET && ARCH_QCOM help Power off and restart support for Qualcomm boards. @@ -37,6 +51,13 @@ config POWER_RESET_RESTART Instead they restart, and u-boot holds the SoC until the user presses a key. u-boot then boots into Linux. +config POWER_RESET_SUN6I + bool "Allwinner A31 SoC reset driver" + depends on ARCH_SUNXI + depends on POWER_RESET + help + Reboot support for the Allwinner A31 SoCs. + config POWER_RESET_VEXPRESS bool "ARM Versatile Express power-off and reset driver" depends on ARM || ARM64 @@ -51,3 +72,11 @@ config POWER_RESET_XGENE depends on POWER_RESET help Reboot support for the APM SoC X-Gene Eval boards. + +config POWER_RESET_KEYSTONE + bool "Keystone reset driver" + depends on ARCH_KEYSTONE + select MFD_SYSCON + help + Reboot support for the KEYSTONE SoCs. + diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 3e6ed88725a..dde2e8bbac5 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -1,6 +1,10 @@ +obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o +obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o +obj-$(CONFIG_POWER_RESET_SUN6I) += sun6i-reboot.o obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o +obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c new file mode 100644 index 00000000000..684971199bd --- /dev/null +++ b/drivers/power/reset/as3722-poweroff.c @@ -0,0 +1,96 @@ +/* + * Power off driver for ams AS3722 device. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * Author: Laxman Dewangan <ldewangan@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/mfd/as3722.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct as3722_poweroff { + struct device *dev; + struct as3722 *as3722; +}; + +static struct as3722_poweroff *as3722_pm_poweroff; + +static void as3722_pm_power_off(void) +{ + int ret; + + if (!as3722_pm_poweroff) { + pr_err("AS3722 poweroff is not initialised\n"); + return; + } + + ret = as3722_update_bits(as3722_pm_poweroff->as3722, + AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF); + if (ret < 0) + dev_err(as3722_pm_poweroff->dev, + "RESET_CONTROL_REG update failed, %d\n", ret); +} + +static int as3722_poweroff_probe(struct platform_device *pdev) +{ + struct as3722_poweroff *as3722_poweroff; + struct device_node *np = pdev->dev.parent->of_node; + + if (!np) + return -EINVAL; + + if (!of_property_read_bool(np, "ams,system-power-controller")) + return 0; + + as3722_poweroff = devm_kzalloc(&pdev->dev, sizeof(*as3722_poweroff), + GFP_KERNEL); + if (!as3722_poweroff) + return -ENOMEM; + + as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent); + as3722_poweroff->dev = &pdev->dev; + as3722_pm_poweroff = as3722_poweroff; + if (!pm_power_off) + pm_power_off = as3722_pm_power_off; + + return 0; +} + +static int as3722_poweroff_remove(struct platform_device *pdev) +{ + if (pm_power_off == as3722_pm_power_off) + pm_power_off = NULL; + as3722_pm_poweroff = NULL; + + return 0; +} + +static struct platform_driver as3722_poweroff_driver = { + .driver = { + .name = "as3722-power-off", + .owner = THIS_MODULE, + }, + .probe = as3722_poweroff_probe, + .remove = as3722_poweroff_remove, +}; + +module_platform_driver(as3722_poweroff_driver); + +MODULE_DESCRIPTION("Power off driver for ams AS3722 PMIC Device"); +MODULE_ALIAS("platform:as3722-power-off"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/axxia-reset.c b/drivers/power/reset/axxia-reset.c new file mode 100644 index 00000000000..3b1f8d60178 --- /dev/null +++ b/drivers/power/reset/axxia-reset.c @@ -0,0 +1,88 @@ +/* + * Reset driver for Axxia devices + * + * Copyright (C) 2014 LSI + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/init.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/regmap.h> + +#include <asm/system_misc.h> + + +#define SC_CRIT_WRITE_KEY 0x1000 +#define SC_LATCH_ON_RESET 0x1004 +#define SC_RESET_CONTROL 0x1008 +#define RSTCTL_RST_ZERO (1<<3) +#define RSTCTL_RST_FAB (1<<2) +#define RSTCTL_RST_CHIP (1<<1) +#define RSTCTL_RST_SYS (1<<0) +#define SC_EFUSE_INT_STATUS 0x180c +#define EFUSE_READ_DONE (1<<31) + +static struct regmap *syscon; + +static void do_axxia_restart(enum reboot_mode reboot_mode, const char *cmd) +{ + /* Access Key (0xab) */ + regmap_write(syscon, SC_CRIT_WRITE_KEY, 0xab); + /* Select internal boot from 0xffff0000 */ + regmap_write(syscon, SC_LATCH_ON_RESET, 0x00000040); + /* Assert ResetReadDone (to avoid hanging in boot ROM) */ + regmap_write(syscon, SC_EFUSE_INT_STATUS, EFUSE_READ_DONE); + /* Assert chip reset */ + regmap_update_bits(syscon, SC_RESET_CONTROL, + RSTCTL_RST_CHIP, RSTCTL_RST_CHIP); +} + +static int axxia_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); + if (IS_ERR(syscon)) { + pr_err("%s: syscon lookup failed\n", dev->of_node->name); + return PTR_ERR(syscon); + } + + arm_pm_restart = do_axxia_restart; + + return 0; +} + +static const struct of_device_id of_axxia_reset_match[] = { + { .compatible = "lsi,axm55xx-reset", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_axxia_reset_match); + +static struct platform_driver axxia_reset_driver = { + .probe = axxia_reset_probe, + .driver = { + .name = "axxia-reset", + .of_match_table = of_match_ptr(of_axxia_reset_match), + }, +}; + +static int __init axxia_reset_init(void) +{ + return platform_driver_register(&axxia_reset_driver); +} +device_initcall(axxia_reset_init); diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c new file mode 100644 index 00000000000..408a18fd91c --- /dev/null +++ b/drivers/power/reset/keystone-reset.c @@ -0,0 +1,166 @@ +/* + * TI keystone reboot driver + * + * Copyright (C) 2014 Texas Instruments Incorporated. http://www.ti.com/ + * + * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/regmap.h> +#include <asm/system_misc.h> +#include <linux/mfd/syscon.h> +#include <linux/of_platform.h> + +#define RSTYPE_RG 0x0 +#define RSCTRL_RG 0x4 +#define RSCFG_RG 0x8 +#define RSISO_RG 0xc + +#define RSCTRL_KEY_MASK 0x0000ffff +#define RSCTRL_RESET_MASK BIT(16) +#define RSCTRL_KEY 0x5a69 + +#define RSMUX_OMODE_MASK 0xe +#define RSMUX_OMODE_RESET_ON 0xa +#define RSMUX_OMODE_RESET_OFF 0x0 +#define RSMUX_LOCK_MASK 0x1 +#define RSMUX_LOCK_SET 0x1 + +#define RSCFG_RSTYPE_SOFT 0x300f +#define RSCFG_RSTYPE_HARD 0x0 + +#define WDT_MUX_NUMBER 0x4 + +static int rspll_offset; +static struct regmap *pllctrl_regs; + +/** + * rsctrl_enable_rspll_write - enable access to RSCTRL, RSCFG + * To be able to access to RSCTRL, RSCFG registers + * we have to write a key before + */ +static inline int rsctrl_enable_rspll_write(void) +{ + return regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG, + RSCTRL_KEY_MASK, RSCTRL_KEY); +} + +static void rsctrl_restart(enum reboot_mode mode, const char *cmd) +{ + /* enable write access to RSTCTRL */ + rsctrl_enable_rspll_write(); + + /* reset the SOC */ + regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG, + RSCTRL_RESET_MASK, 0); +} + +static struct of_device_id rsctrl_of_match[] = { + {.compatible = "ti,keystone-reset", }, + {}, +}; + +static int rsctrl_probe(struct platform_device *pdev) +{ + int i; + int ret; + u32 val; + unsigned int rg; + u32 rsmux_offset; + struct regmap *devctrl_regs; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + if (!np) + return -ENODEV; + + /* get regmaps */ + pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll"); + if (IS_ERR(pllctrl_regs)) + return PTR_ERR(pllctrl_regs); + + devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); + if (IS_ERR(devctrl_regs)) + return PTR_ERR(devctrl_regs); + + ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset); + if (ret) { + dev_err(dev, "couldn't read the reset pll offset!\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset); + if (ret) { + dev_err(dev, "couldn't read the rsmux offset!\n"); + return -EINVAL; + } + + /* set soft/hard reset */ + val = of_property_read_bool(np, "ti,soft-reset"); + val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD; + + ret = rsctrl_enable_rspll_write(); + if (ret) + return ret; + + ret = regmap_write(pllctrl_regs, rspll_offset + RSCFG_RG, val); + if (ret) + return ret; + + arm_pm_restart = rsctrl_restart; + + /* disable a reset isolation for all module clocks */ + ret = regmap_write(pllctrl_regs, rspll_offset + RSISO_RG, 0); + if (ret) + return ret; + + /* enable a reset for watchdogs from wdt-list */ + for (i = 0; i < WDT_MUX_NUMBER; i++) { + ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val); + if (ret == -EOVERFLOW && !i) { + dev_err(dev, "ti,wdt-list property has to contain at" + "least one entry\n"); + return -EINVAL; + } else if (ret) { + break; + } + + if (val >= WDT_MUX_NUMBER) { + dev_err(dev, "ti,wdt-list property can contain" + "only numbers < 4\n"); + return -EINVAL; + } + + rg = rsmux_offset + val * 4; + + ret = regmap_update_bits(devctrl_regs, rg, RSMUX_OMODE_MASK, + RSMUX_OMODE_RESET_ON | + RSMUX_LOCK_SET); + if (ret) + return ret; + } + + return 0; +} + +static struct platform_driver rsctrl_driver = { + .probe = rsctrl_probe, + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .of_match_table = rsctrl_of_match, + }, +}; +module_platform_driver(rsctrl_driver); + +MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>"); +MODULE_DESCRIPTION("Texas Instruments keystone reset driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); diff --git a/drivers/power/reset/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c index 37f56f7ee92..a75db7f8a92 100644 --- a/drivers/power/reset/qnap-poweroff.c +++ b/drivers/power/reset/qnap-poweroff.c @@ -1,5 +1,5 @@ /* - * QNAP Turbo NAS Board power off + * QNAP Turbo NAS Board power off. Can also be used on Synology devices. * * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch> * @@ -25,17 +25,43 @@ #define UART1_REG(x) (base + ((UART_##x) << 2)) +struct power_off_cfg { + u32 baud; + char cmd; +}; + +static const struct power_off_cfg qnap_power_off_cfg = { + .baud = 19200, + .cmd = 'A', +}; + +static const struct power_off_cfg synology_power_off_cfg = { + .baud = 9600, + .cmd = '1', +}; + +static const struct of_device_id qnap_power_off_of_match_table[] = { + { .compatible = "qnap,power-off", + .data = &qnap_power_off_cfg, + }, + { .compatible = "synology,power-off", + .data = &synology_power_off_cfg, + }, + {} +}; +MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table); + static void __iomem *base; static unsigned long tclk; +static const struct power_off_cfg *cfg; static void qnap_power_off(void) { - /* 19200 baud divisor */ - const unsigned divisor = ((tclk + (8 * 19200)) / (16 * 19200)); + const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud)); pr_err("%s: triggering power-off...\n", __func__); - /* hijack UART1 and reset into sane state (19200,8n1) */ + /* hijack UART1 and reset into sane state */ writel(0x83, UART1_REG(LCR)); writel(divisor & 0xff, UART1_REG(DLL)); writel((divisor >> 8) & 0xff, UART1_REG(DLM)); @@ -44,16 +70,21 @@ static void qnap_power_off(void) writel(0x00, UART1_REG(FCR)); writel(0x00, UART1_REG(MCR)); - /* send the power-off command 'A' to PIC */ - writel('A', UART1_REG(TX)); + /* send the power-off command to PIC */ + writel(cfg->cmd, UART1_REG(TX)); } static int qnap_power_off_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct resource *res; struct clk *clk; char symname[KSYM_NAME_LEN]; + const struct of_device_id *match = + of_match_node(qnap_power_off_of_match_table, np); + cfg = match->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Missing resource"); @@ -94,12 +125,6 @@ static int qnap_power_off_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id qnap_power_off_of_match_table[] = { - { .compatible = "qnap,power-off", }, - {} -}; -MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table); - static struct platform_driver qnap_power_off_driver = { .probe = qnap_power_off_probe, .remove = qnap_power_off_remove, diff --git a/drivers/power/reset/sun6i-reboot.c b/drivers/power/reset/sun6i-reboot.c new file mode 100644 index 00000000000..af2cd7ff2fe --- /dev/null +++ b/drivers/power/reset/sun6i-reboot.c @@ -0,0 +1,85 @@ +/* + * Allwinner A31 SoCs reset code + * + * Copyright (C) 2012-2014 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +#include <asm/system_misc.h> + +#define SUN6I_WATCHDOG1_IRQ_REG 0x00 +#define SUN6I_WATCHDOG1_CTRL_REG 0x10 +#define SUN6I_WATCHDOG1_CTRL_RESTART BIT(0) +#define SUN6I_WATCHDOG1_CONFIG_REG 0x14 +#define SUN6I_WATCHDOG1_CONFIG_RESTART BIT(0) +#define SUN6I_WATCHDOG1_CONFIG_IRQ BIT(1) +#define SUN6I_WATCHDOG1_MODE_REG 0x18 +#define SUN6I_WATCHDOG1_MODE_ENABLE BIT(0) + +static void __iomem *wdt_base; + +static void sun6i_wdt_restart(enum reboot_mode mode, const char *cmd) +{ + if (!wdt_base) + return; + + /* Disable interrupts */ + writel(0, wdt_base + SUN6I_WATCHDOG1_IRQ_REG); + + /* We want to disable the IRQ and just reset the whole system */ + writel(SUN6I_WATCHDOG1_CONFIG_RESTART, + wdt_base + SUN6I_WATCHDOG1_CONFIG_REG); + + /* Enable timer. The default and lowest interval value is 0.5s */ + writel(SUN6I_WATCHDOG1_MODE_ENABLE, + wdt_base + SUN6I_WATCHDOG1_MODE_REG); + + /* Restart the watchdog. */ + writel(SUN6I_WATCHDOG1_CTRL_RESTART, + wdt_base + SUN6I_WATCHDOG1_CTRL_REG); + + while (1) { + mdelay(5); + writel(SUN6I_WATCHDOG1_MODE_ENABLE, + wdt_base + SUN6I_WATCHDOG1_MODE_REG); + } +} + +static int sun6i_reboot_probe(struct platform_device *pdev) +{ + wdt_base = of_iomap(pdev->dev.of_node, 0); + if (!wdt_base) { + WARN(1, "failed to map watchdog base address"); + return -ENODEV; + } + + arm_pm_restart = sun6i_wdt_restart; + + return 0; +} + +static struct of_device_id sun6i_reboot_of_match[] = { + { .compatible = "allwinner,sun6i-a31-wdt" }, + {} +}; + +static struct platform_driver sun6i_reboot_driver = { + .probe = sun6i_reboot_probe, + .driver = { + .name = "sun6i-reboot", + .of_match_table = sun6i_reboot_of_match, + }, +}; +module_platform_driver(sun6i_reboot_driver); diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c index 476aa495c11..4dc102e2b23 100644 --- a/drivers/power/reset/vexpress-poweroff.c +++ b/drivers/power/reset/vexpress-poweroff.c @@ -11,7 +11,7 @@ * Copyright (C) 2012 ARM Limited */ -#include <linux/jiffies.h> +#include <linux/delay.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> @@ -23,17 +23,12 @@ static void vexpress_reset_do(struct device *dev, const char *what) { int err = -ENOENT; - struct vexpress_config_func *func = - vexpress_config_func_get_by_dev(dev); + struct regmap *reg = dev_get_drvdata(dev); - if (func) { - unsigned long timeout; - - err = vexpress_config_write(func, 0, 0); - - timeout = jiffies + HZ; - while (time_before(jiffies, timeout)) - cpu_relax(); + if (reg) { + err = regmap_write(reg, 0, 0); + if (!err) + mdelay(1000); } dev_emerg(dev, "Unable to %s (%d)\n", what, err); @@ -96,12 +91,18 @@ static int vexpress_reset_probe(struct platform_device *pdev) enum vexpress_reset_func func; const struct of_device_id *match = of_match_device(vexpress_reset_of_match, &pdev->dev); + struct regmap *regmap; if (match) func = (enum vexpress_reset_func)match->data; else func = pdev->id_entry->driver_data; + regmap = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + dev_set_drvdata(&pdev->dev, regmap); + switch (func) { case FUNC_SHUTDOWN: vexpress_power_off_device = &pdev->dev; diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c index bdd7b9b2546..1685f63b9e5 100644 --- a/drivers/power/tps65090-charger.c +++ b/drivers/power/tps65090-charger.c @@ -15,27 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <linux/delay.h> #include <linux/err.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/slab.h> -#include <linux/delay.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/power_supply.h> -#include <linux/mfd/tps65090.h> +#include <linux/slab.h> -#define TPS65090_REG_INTR_STS 0x00 -#define TPS65090_REG_INTR_MASK 0x02 -#define TPS65090_REG_CG_CTRL0 0x04 -#define TPS65090_REG_CG_CTRL1 0x05 -#define TPS65090_REG_CG_CTRL2 0x06 -#define TPS65090_REG_CG_CTRL3 0x07 -#define TPS65090_REG_CG_CTRL4 0x08 -#define TPS65090_REG_CG_CTRL5 0x09 -#define TPS65090_REG_CG_STATUS1 0x0a -#define TPS65090_REG_CG_STATUS2 0x0b +#include <linux/mfd/tps65090.h> #define TPS65090_CHARGER_ENABLE BIT(0) #define TPS65090_VACG BIT(1) @@ -185,10 +176,6 @@ static irqreturn_t tps65090_charger_isr(int irq, void *dev_id) return IRQ_HANDLED; } -#if defined(CONFIG_OF) - -#include <linux/of_device.h> - static struct tps65090_platform_data * tps65090_parse_dt_charger_data(struct platform_device *pdev) { @@ -210,13 +197,6 @@ static struct tps65090_platform_data * return pdata; } -#else -static struct tps65090_platform_data * - tps65090_parse_dt_charger_data(struct platform_device *pdev) -{ - return NULL; -} -#endif static int tps65090_charger_probe(struct platform_device *pdev) { @@ -228,7 +208,7 @@ static int tps65090_charger_probe(struct platform_device *pdev) pdata = dev_get_platdata(pdev->dev.parent); - if (!pdata && pdev->dev.of_node) + if (IS_ENABLED(CONFIG_OF) && !pdata && pdev->dev.of_node) pdata = tps65090_parse_dt_charger_data(pdev); if (!pdata) { @@ -277,13 +257,13 @@ static int tps65090_charger_probe(struct platform_device *pdev) if (ret) { dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq, ret); - goto fail_free_irq; + goto fail_unregister_supply; } ret = tps65090_config_charger(cdata); if (ret < 0) { dev_err(&pdev->dev, "charger config failed, err %d\n", ret); - goto fail_free_irq; + goto fail_unregister_supply; } /* Check for charger presence */ @@ -292,14 +272,14 @@ static int tps65090_charger_probe(struct platform_device *pdev) if (ret < 0) { dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__, TPS65090_REG_CG_STATUS1); - goto fail_free_irq; + goto fail_unregister_supply; } if (status1 != 0) { ret = tps65090_enable_charging(cdata); if (ret < 0) { dev_err(cdata->dev, "error enabling charger\n"); - goto fail_free_irq; + goto fail_unregister_supply; } cdata->ac_online = 1; power_supply_changed(&cdata->ac); @@ -307,8 +287,6 @@ static int tps65090_charger_probe(struct platform_device *pdev) return 0; -fail_free_irq: - devm_free_irq(cdata->dev, irq, cdata); fail_unregister_supply: power_supply_unregister(&cdata->ac); @@ -319,7 +297,6 @@ static int tps65090_charger_remove(struct platform_device *pdev) { struct tps65090_charger *cdata = platform_get_drvdata(pdev); - devm_free_irq(cdata->dev, cdata->irq, cdata); power_supply_unregister(&cdata->ac); return 0; diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index d98abe911e3..f14108844e1 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -495,10 +495,38 @@ static enum power_supply_property twl4030_charger_props[] = { POWER_SUPPLY_PROP_CURRENT_NOW, }; +#ifdef CONFIG_OF +static const struct twl4030_bci_platform_data * +twl4030_bci_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct twl4030_bci_platform_data *pdata; + u32 num; + + if (!np) + return NULL; + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return pdata; + + if (of_property_read_u32(np, "ti,bb-uvolt", &num) == 0) + pdata->bb_uvolt = num; + if (of_property_read_u32(np, "ti,bb-uamp", &num) == 0) + pdata->bb_uamp = num; + return pdata; +} +#else +static inline const struct twl4030_bci_platform_data * +twl4030_bci_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + static int __init twl4030_bci_probe(struct platform_device *pdev) { struct twl4030_bci *bci; - struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; + const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; int ret; u32 reg; @@ -506,6 +534,9 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) if (bci == NULL) return -ENOMEM; + if (!pdata) + pdata = twl4030_bci_parse_dt(&pdev->dev); + bci->dev = &pdev->dev; bci->irq_chg = platform_get_irq(pdev, 0); bci->irq_bci = platform_get_irq(pdev, 1); @@ -581,8 +612,11 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_ac(true); twl4030_charger_enable_usb(bci, true); - twl4030_charger_enable_backup(pdata->bb_uvolt, - pdata->bb_uamp); + if (pdata) + twl4030_charger_enable_backup(pdata->bb_uvolt, + pdata->bb_uamp); + else + twl4030_charger_enable_backup(0, 0); return 0; @@ -631,10 +665,17 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id twl_bci_of_match[] = { + {.compatible = "ti,twl4030-bci", }, + { } +}; +MODULE_DEVICE_TABLE(of, twl_bci_of_match); + static struct platform_driver twl4030_bci_driver = { .driver = { .name = "twl4030_bci", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl_bci_of_match), }, .remove = __exit_p(twl4030_bci_remove), }; |
