diff options
Diffstat (limited to 'drivers/power')
26 files changed, 1983 insertions, 566 deletions
diff --git a/drivers/power/88pm860x_charger.c b/drivers/power/88pm860x_charger.c index 650930e4fa79..734ec4afa14d 100644 --- a/drivers/power/88pm860x_charger.c +++ b/drivers/power/88pm860x_charger.c @@ -711,6 +711,7 @@ static int pm860x_charger_probe(struct platform_device *pdev) return 0; out_irq: + power_supply_unregister(&info->usb); while (--i >= 0) free_irq(info->irq[i], info); out: diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0108c2af005b..27b751b995fb 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -315,7 +315,7 @@ config CHARGER_GPIO config CHARGER_MANAGER bool "Battery charger manager for multiple chargers" - depends on REGULATOR && RTC_CLASS + depends on REGULATOR select EXTCON help Say Y to enable charger-manager support, which allows multiple @@ -327,11 +327,16 @@ config CHARGER_MANAGER config CHARGER_MAX14577 tristate "Maxim MAX14577/77836 battery charger driver" depends on MFD_MAX14577 - depends on SYSFS help Say Y to enable support for the battery charger control sysfs and platform data of MAX14577/77836 MUICs. +config CHARGER_MAX77693 + tristate "Maxim MAX77693 battery charger driver" + depends on MFD_MAX77693 + help + Say Y to enable support for the Maxim MAX77693 battery charger. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 @@ -383,6 +388,14 @@ config CHARGER_TPS65090 Say Y here to enable support for battery charging with TPS65090 PMIC chips. +config BATTERY_GAUGE_LTC2941 + tristate "LTC2941/LTC2943 Battery Gauge Driver" + depends on I2C + help + Say Y here to include support for LTC2941 and LTC2943 Battery + Gauge IC. The driver reports the charge count continuously, and + measures the voltage and temperature every 10 seconds. + config AB8500_BM bool "AB8500 Battery Management Driver" depends on AB8500_CORE && AB8500_GPADC @@ -397,6 +410,14 @@ config BATTERY_GOLDFISH Say Y to enable support for the battery and AC power in the Goldfish emulator. +config BATTERY_RT5033 + tristate "RT5033 fuel gauge support" + depends on MFD_RT5033 + help + This adds support for battery fuel gauge in Richtek RT5033 PMIC. + The fuelgauge calculates and determines the battery state of charge + according to battery open circuit voltage. + source "drivers/power/reset/Kconfig" endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index dfa894273926..36f9e0d10111 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o +obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o @@ -34,6 +35,7 @@ obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o obj-$(CONFIG_BATTERY_Z2) += z2_battery.o +obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o @@ -50,6 +52,7 @@ 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_MAX77693) += max77693_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 69b80bcaa9e7..c908658aa31a 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -2435,20 +2435,6 @@ static void ab8500_fg_reinit_work(struct work_struct *work) } } -/** - * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values - * - * This function can be used to force the FG algorithm to recalculate a new - * voltage based battery capacity. - */ -void ab8500_fg_reinit(void) -{ - struct ab8500_fg *di = ab8500_fg_get(); - /* User won't be notified if a null pointer returned. */ - if (di != NULL) - queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0); -} - /* Exposure to the sysfs interface */ struct ab8500_fg_sysfs_entry { diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c index ad3ff8fbfbbb..d0e8236a6404 100644 --- a/drivers/power/bq24190_charger.c +++ b/drivers/power/bq24190_charger.c @@ -929,7 +929,7 @@ static void bq24190_charger_init(struct power_supply *charger) charger->properties = bq24190_charger_properties; charger->num_properties = ARRAY_SIZE(bq24190_charger_properties); charger->supplied_to = bq24190_charger_supplied_to; - charger->num_supplies = ARRAY_SIZE(bq24190_charger_supplied_to); + charger->num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to); charger->get_property = bq24190_charger_get_property; charger->set_property = bq24190_charger_set_property; charger->property_is_writeable = bq24190_charger_property_is_writeable; @@ -1208,7 +1208,7 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) { struct bq24190_dev_info *bdi = data; bool alert_userspace = false; - u8 ss_reg, f_reg; + u8 ss_reg = 0, f_reg = 0; int ret; pm_runtime_get_sync(bdi->dev); diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index a78ac201828e..b72ba7c1bd69 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -76,7 +76,8 @@ /* bq27425 register addresses are same as bq27x00 addresses minus 4 */ #define BQ27425_REG_OFFSET 0x04 -#define BQ27425_REG_SOC 0x18 /* Register address plus offset */ +#define BQ27425_REG_SOC (0x1C + BQ27425_REG_OFFSET) +#define BQ27425_REG_DCAP (0x3C + BQ27425_REG_OFFSET) #define BQ27000_RS 20 /* Resistor sense */ #define BQ27x00_POWER_CONSTANT (256 * 29200 / 1000) @@ -282,9 +283,12 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) { int ilmd; - if (bq27xxx_is_chip_version_higher(di)) - ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false); - else + if (bq27xxx_is_chip_version_higher(di)) { + if (di->chip == BQ27425) + ilmd = bq27x00_read(di, BQ27425_REG_DCAP, false); + else + ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false); + } else ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true); if (ilmd < 0) { @@ -493,10 +497,11 @@ static void bq27x00_update(struct bq27x00_device_info *di) di->charge_design_full = bq27x00_battery_read_ilmd(di); } - if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) { - di->cache = cache; + if (di->cache.capacity != cache.capacity) power_supply_changed(&di->bat); - } + + if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) + di->cache = cache; di->last_update = jiffies; } diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 649052e1f2d9..14b0d85318eb 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -69,16 +69,10 @@ static LIST_HEAD(cm_list); static DEFINE_MUTEX(cm_list_mtx); /* About in-suspend (suspend-again) monitoring */ -static struct rtc_device *rtc_dev; -/* - * Backup RTC alarm - * Save the wakeup alarm before entering suspend-to-RAM - */ -static struct rtc_wkalrm rtc_wkalarm_save; -/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */ -static unsigned long rtc_wkalarm_save_time; +static struct alarm *cm_timer; + static bool cm_suspended; -static bool cm_rtc_set; +static bool cm_timer_set; static unsigned long cm_suspend_duration_ms; /* About normal (not suspended) monitoring */ @@ -87,9 +81,6 @@ static unsigned long next_polling; /* Next appointed polling time */ static struct workqueue_struct *cm_wq; /* init at driver add */ static struct delayed_work cm_monitor_work; /* init at driver add */ -/* Global charger-manager description */ -static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ - /** * is_batt_present - See if the battery presents in place. * @cm: the Charger Manager representing the battery. @@ -1047,10 +1038,13 @@ static bool cm_setup_timer(void) { struct charger_manager *cm; unsigned int wakeup_ms = UINT_MAX; - bool ret = false; + int timer_req = 0; - mutex_lock(&cm_list_mtx); + if (time_after(next_polling, jiffies)) + CM_MIN_VALID(wakeup_ms, + jiffies_to_msecs(next_polling - jiffies)); + mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { unsigned int fbchk_ms = 0; @@ -1070,162 +1064,38 @@ static bool cm_setup_timer(void) /* Skip if polling is not required for this CM */ if (!is_polling_required(cm) && !cm->emergency_stop) continue; + timer_req++; if (cm->desc->polling_interval_ms == 0) continue; CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms); } - mutex_unlock(&cm_list_mtx); - if (wakeup_ms < UINT_MAX && wakeup_ms > 0) { - pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms); - if (rtc_dev) { - struct rtc_wkalrm tmp; - unsigned long time, now; - unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000); - - /* - * Set alarm with the polling interval (wakeup_ms) - * except when rtc_wkalarm_save comes first. - * However, the alarm time should be NOW + - * CM_RTC_SMALL or later. - */ - tmp.enabled = 1; - rtc_read_time(rtc_dev, &tmp.time); - rtc_tm_to_time(&tmp.time, &now); - if (add < CM_RTC_SMALL) - add = CM_RTC_SMALL; - time = now + add; + if (timer_req && cm_timer) { + ktime_t now, add; - ret = true; + /* + * Set alarm with the polling interval (wakeup_ms) + * The alarm time should be NOW + CM_RTC_SMALL or later. + */ + if (wakeup_ms == UINT_MAX || + wakeup_ms < CM_RTC_SMALL * MSEC_PER_SEC) + wakeup_ms = 2 * CM_RTC_SMALL * MSEC_PER_SEC; - if (rtc_wkalarm_save.enabled && - rtc_wkalarm_save_time && - rtc_wkalarm_save_time < time) { - if (rtc_wkalarm_save_time < now + CM_RTC_SMALL) - time = now + CM_RTC_SMALL; - else - time = rtc_wkalarm_save_time; + pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms); - /* The timer is not appointed by CM */ - ret = false; - } + now = ktime_get_boottime(); + add = ktime_set(wakeup_ms / MSEC_PER_SEC, + (wakeup_ms % MSEC_PER_SEC) * NSEC_PER_MSEC); + alarm_start(cm_timer, ktime_add(now, add)); - pr_info("Waking up after %lu secs\n", time - now); + cm_suspend_duration_ms = wakeup_ms; - rtc_time_to_tm(time, &tmp.time); - rtc_set_alarm(rtc_dev, &tmp); - cm_suspend_duration_ms += wakeup_ms; - return ret; - } + return true; } - - if (rtc_dev) - rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); return false; } -static void _cm_fbchk_in_suspend(struct charger_manager *cm) -{ - unsigned long jiffy_now = jiffies; - - if (!cm->fullbatt_vchk_jiffies_at) - return; - - if (g_desc && g_desc->assume_timer_stops_in_suspend) - jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms); - - /* Execute now if it's going to be executed not too long after */ - jiffy_now += CM_JIFFIES_SMALL; - - if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) - fullbatt_vchk(&cm->fullbatt_vchk_work.work); -} - -/** - * cm_suspend_again - Determine whether suspend again or not - * - * Returns true if the system should be suspended again - * Returns false if the system should be woken up - */ -bool cm_suspend_again(void) -{ - struct charger_manager *cm; - bool ret = false; - - if (!g_desc || !g_desc->rtc_only_wakeup || !g_desc->rtc_only_wakeup() || - !cm_rtc_set) - return false; - - if (cm_monitor()) - goto out; - - ret = true; - mutex_lock(&cm_list_mtx); - list_for_each_entry(cm, &cm_list, entry) { - _cm_fbchk_in_suspend(cm); - - if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || - cm->status_save_batt != is_batt_present(cm)) { - ret = false; - break; - } - } - mutex_unlock(&cm_list_mtx); - - cm_rtc_set = cm_setup_timer(); -out: - /* It's about the time when the non-CM appointed timer goes off */ - if (rtc_wkalarm_save.enabled) { - unsigned long now; - struct rtc_time tmp; - - rtc_read_time(rtc_dev, &tmp); - rtc_tm_to_time(&tmp, &now); - - if (rtc_wkalarm_save_time && - now + CM_RTC_SMALL >= rtc_wkalarm_save_time) - return false; - } - return ret; -} -EXPORT_SYMBOL_GPL(cm_suspend_again); - -/** - * setup_charger_manager - initialize charger_global_desc data - * @gd: pointer to instance of charger_global_desc - */ -int setup_charger_manager(struct charger_global_desc *gd) -{ - if (!gd) - return -EINVAL; - - if (rtc_dev) - rtc_class_close(rtc_dev); - rtc_dev = NULL; - g_desc = NULL; - - if (!gd->rtc_only_wakeup) { - pr_err("The callback rtc_only_wakeup is not given\n"); - return -EINVAL; - } - - if (gd->rtc_name) { - rtc_dev = rtc_class_open(gd->rtc_name); - if (IS_ERR_OR_NULL(rtc_dev)) { - rtc_dev = NULL; - /* Retry at probe. RTC may be not registered yet */ - } - } else { - pr_warn("No wakeup timer is given for charger manager. " - "In-suspend monitoring won't work.\n"); - } - - g_desc = gd; - return 0; -} -EXPORT_SYMBOL_GPL(setup_charger_manager); - /** * charger_extcon_work - enable/diable charger according to the state * of charger cable @@ -1719,6 +1589,12 @@ static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) return dev_get_platdata(&pdev->dev); } +static enum alarmtimer_restart cm_timer_func(struct alarm *alarm, ktime_t now) +{ + cm_timer_set = false; + return ALARMTIMER_NORESTART; +} + static int charger_manager_probe(struct platform_device *pdev) { struct charger_desc *desc = cm_get_drv_data(pdev); @@ -1728,16 +1604,6 @@ static int charger_manager_probe(struct platform_device *pdev) union power_supply_propval val; struct power_supply *fuel_gauge; - if (g_desc && !rtc_dev && g_desc->rtc_name) { - rtc_dev = rtc_class_open(g_desc->rtc_name); - if (IS_ERR_OR_NULL(rtc_dev)) { - rtc_dev = NULL; - dev_err(&pdev->dev, "Cannot get RTC %s\n", - g_desc->rtc_name); - return -ENODEV; - } - } - if (IS_ERR(desc)) { dev_err(&pdev->dev, "No platform data (desc) found\n"); return -ENODEV; @@ -1752,6 +1618,12 @@ static int charger_manager_probe(struct platform_device *pdev) cm->dev = &pdev->dev; cm->desc = desc; + /* Initialize alarm timer */ + if (alarmtimer_get_rtcdev()) { + cm_timer = devm_kzalloc(cm->dev, sizeof(*cm_timer), GFP_KERNEL); + alarm_init(cm_timer, ALARM_BOOTTIME, cm_timer_func); + } + /* * The following two do not need to be errors. * Users may intentionally ignore those two features. @@ -1993,38 +1865,41 @@ static int cm_suspend_noirq(struct device *dev) return ret; } +static bool cm_need_to_awake(void) +{ + struct charger_manager *cm; + + if (cm_timer) + return false; + + mutex_lock(&cm_list_mtx); + list_for_each_entry(cm, &cm_list, entry) { + if (is_charging(cm)) { + mutex_unlock(&cm_list_mtx); + return true; + } + } + mutex_unlock(&cm_list_mtx); + + return false; +} + static int cm_suspend_prepare(struct device *dev) { struct charger_manager *cm = dev_get_drvdata(dev); - if (!cm_suspended) { - if (rtc_dev) { - struct rtc_time tmp; - unsigned long now; - - rtc_read_alarm(rtc_dev, &rtc_wkalarm_save); - rtc_read_time(rtc_dev, &tmp); + if (cm_need_to_awake()) + return -EBUSY; - if (rtc_wkalarm_save.enabled) { - rtc_tm_to_time(&rtc_wkalarm_save.time, - &rtc_wkalarm_save_time); - rtc_tm_to_time(&tmp, &now); - if (now > rtc_wkalarm_save_time) - rtc_wkalarm_save_time = 0; - } else { - rtc_wkalarm_save_time = 0; - } - } + if (!cm_suspended) cm_suspended = true; - } - cancel_delayed_work(&cm->fullbatt_vchk_work); - cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); - cm->status_save_batt = is_batt_present(cm); + cm_timer_set = cm_setup_timer(); - if (!cm_rtc_set) { - cm_suspend_duration_ms = 0; - cm_rtc_set = cm_setup_timer(); + if (cm_timer_set) { + cancel_work_sync(&setup_polling); + cancel_delayed_work_sync(&cm_monitor_work); + cancel_delayed_work(&cm->fullbatt_vchk_work); } return 0; @@ -2034,18 +1909,21 @@ static void cm_suspend_complete(struct device *dev) { struct charger_manager *cm = dev_get_drvdata(dev); - if (cm_suspended) { - if (rtc_dev) { - struct rtc_wkalrm tmp; - - rtc_read_alarm(rtc_dev, &tmp); - rtc_wkalarm_save.pending = tmp.pending; - rtc_set_alarm(rtc_dev, &rtc_wkalarm_save); - } + if (cm_suspended) cm_suspended = false; - cm_rtc_set = false; + + if (cm_timer_set) { + ktime_t remain; + + alarm_cancel(cm_timer); + cm_timer_set = false; + remain = alarm_expires_remaining(cm_timer); + cm_suspend_duration_ms -= ktime_to_ms(remain); + schedule_work(&setup_polling); } + _cm_monitor(cm); + /* Re-enqueue delayed work (fullbatt_vchk_work) */ if (cm->fullbatt_vchk_jiffies_at) { unsigned long delay = 0; @@ -2060,21 +1938,18 @@ static void cm_suspend_complete(struct device *dev) } /* - * Account for cm_suspend_duration_ms if - * assume_timer_stops_in_suspend is active + * Account for cm_suspend_duration_ms with assuming that + * timer stops in suspend. */ - if (g_desc && g_desc->assume_timer_stops_in_suspend) { - if (delay > cm_suspend_duration_ms) - delay -= cm_suspend_duration_ms; - else - delay = 0; - } + if (delay > cm_suspend_duration_ms) + delay -= cm_suspend_duration_ms; + else + delay = 0; queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, msecs_to_jiffies(delay)); } device_set_wakeup_capable(cm->dev, false); - uevent_notify(cm, NULL); } static const struct dev_pm_ops charger_manager_pm = { diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c index d02ae02a7590..594e4dbc2d51 100644 --- a/drivers/power/collie_battery.c +++ b/drivers/power/collie_battery.c @@ -26,6 +26,7 @@ static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ static struct work_struct bat_work; static struct ucb1x00 *ucb; +static int wakeup_enabled; struct collie_bat { int status; @@ -291,11 +292,21 @@ static int collie_bat_suspend(struct ucb1x00_dev *dev) { /* flush all pending status updates */ flush_work(&bat_work); + + if (device_may_wakeup(&dev->ucb->dev) && + collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING) + wakeup_enabled = !enable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); + else + wakeup_enabled = 0; + return 0; } static int collie_bat_resume(struct ucb1x00_dev *dev) { + if (wakeup_enabled) + disable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); + /* things may have changed while we were away */ schedule_work(&bat_work); return 0; @@ -334,10 +345,15 @@ static int collie_bat_probe(struct ucb1x00_dev *dev) collie_bat_gpio_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "main full", &collie_bat_main); - if (!ret) { - schedule_work(&bat_work); - return 0; - } + if (ret) + goto err_irq; + + device_init_wakeup(&ucb->dev, 1); + schedule_work(&bat_work); + + return 0; + +err_irq: power_supply_unregister(&collie_bat_bu.psy); err_psy_reg_bu: power_supply_unregister(&collie_bat_main.psy); diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index aef74bdf7ab3..b7424c8501f1 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -229,7 +229,7 @@ static int gpio_charger_suspend(struct device *dev) if (device_may_wakeup(dev)) gpio_charger->wakeup_enabled = - enable_irq_wake(gpio_charger->irq); + !enable_irq_wake(gpio_charger->irq); return 0; } @@ -239,7 +239,7 @@ 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) + if (device_may_wakeup(dev) && gpio_charger->wakeup_enabled) disable_irq_wake(gpio_charger->irq); power_supply_changed(&gpio_charger->charger); diff --git a/drivers/power/ltc2941-battery-gauge.c b/drivers/power/ltc2941-battery-gauge.c new file mode 100644 index 000000000000..e31c927a6d16 --- /dev/null +++ b/drivers/power/ltc2941-battery-gauge.c @@ -0,0 +1,547 @@ +/* + * I2C client/driver for the Linear Technology LTC2941 and LTC2943 + * Battery Gas Gauge IC + * + * Copyright (C) 2014 Topic Embedded Systems + * + * Author: Auryn Verwegen + * Author: Mike Looijmans + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/swab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/idr.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#define I16_MSB(x) ((x >> 8) & 0xFF) +#define I16_LSB(x) (x & 0xFF) + +#define LTC294X_WORK_DELAY 10 /* Update delay in seconds */ + +#define LTC294X_MAX_VALUE 0xFFFF +#define LTC294X_MID_SUPPLY 0x7FFF + +#define LTC2941_MAX_PRESCALER_EXP 7 +#define LTC2943_MAX_PRESCALER_EXP 6 + +enum ltc294x_reg { + LTC294X_REG_STATUS = 0x00, + LTC294X_REG_CONTROL = 0x01, + LTC294X_REG_ACC_CHARGE_MSB = 0x02, + LTC294X_REG_ACC_CHARGE_LSB = 0x03, + LTC294X_REG_THRESH_HIGH_MSB = 0x04, + LTC294X_REG_THRESH_HIGH_LSB = 0x05, + LTC294X_REG_THRESH_LOW_MSB = 0x06, + LTC294X_REG_THRESH_LOW_LSB = 0x07, + LTC294X_REG_VOLTAGE_MSB = 0x08, + LTC294X_REG_VOLTAGE_LSB = 0x09, + LTC294X_REG_CURRENT_MSB = 0x0E, + LTC294X_REG_CURRENT_LSB = 0x0F, + LTC294X_REG_TEMPERATURE_MSB = 0x14, + LTC294X_REG_TEMPERATURE_LSB = 0x15, +}; + +#define LTC2943_REG_CONTROL_MODE_MASK (BIT(7) | BIT(6)) +#define LTC2943_REG_CONTROL_MODE_SCAN BIT(7) +#define LTC294X_REG_CONTROL_PRESCALER_MASK (BIT(5) | BIT(4) | BIT(3)) +#define LTC294X_REG_CONTROL_SHUTDOWN_MASK (BIT(0)) +#define LTC294X_REG_CONTROL_PRESCALER_SET(x) \ + ((x << 3) & LTC294X_REG_CONTROL_PRESCALER_MASK) +#define LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED 0 + +#define LTC2941_NUM_REGS 0x08 +#define LTC2943_NUM_REGS 0x18 + +struct ltc294x_info { + struct i2c_client *client; /* I2C Client pointer */ + struct power_supply supply; /* Supply pointer */ + struct delayed_work work; /* Work scheduler */ + int num_regs; /* Number of registers (chip type) */ + int id; /* Identifier of ltc294x chip */ + int charge; /* Last charge register content */ + int r_sense; /* mOhm */ + int Qlsb; /* nAh */ +}; + +static DEFINE_IDR(ltc294x_id); +static DEFINE_MUTEX(ltc294x_lock); + +static inline int convert_bin_to_uAh( + const struct ltc294x_info *info, int Q) +{ + return ((Q * (info->Qlsb / 10))) / 100; +} + +static inline int convert_uAh_to_bin( + const struct ltc294x_info *info, int uAh) +{ + int Q; + + Q = (uAh * 100) / (info->Qlsb/10); + return (Q < LTC294X_MAX_VALUE) ? Q : LTC294X_MAX_VALUE; +} + +static int ltc294x_read_regs(struct i2c_client *client, + enum ltc294x_reg reg, u8 *buf, int num_regs) +{ + int ret; + struct i2c_msg msgs[2] = { }; + u8 reg_start = reg; + + msgs[0].addr = client->addr; + msgs[0].len = 1; + msgs[0].buf = ®_start; + + msgs[1].addr = client->addr; + msgs[1].len = num_regs; + msgs[1].buf = buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, &msgs[0], 2); + if (ret < 0) { + dev_err(&client->dev, "ltc2941 read_reg failed!\n"); + return ret; + } + + dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n", + __func__, reg, num_regs, *buf); + + return 0; +} + +static int ltc294x_write_regs(struct i2c_client *client, + enum ltc294x_reg reg, const u8 *buf, int num_regs) +{ + int ret; + u8 reg_start = reg; + + ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf); + if (ret < 0) { + dev_err(&client->dev, "ltc2941 write_reg failed!\n"); + return ret; + } + + dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n", + __func__, reg, num_regs, *buf); + + return 0; +} + +static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp) +{ + int ret; + u8 value; + u8 control; + + /* Read status and control registers */ + ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1); + if (ret < 0) { + dev_err(&info->client->dev, + "Could not read registers from device\n"); + goto error_exit; + } + + control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) | + LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED; + /* Put the 2943 into "monitor" mode, so it measures every 10 sec */ + if (info->num_regs == LTC2943_NUM_REGS) + control |= LTC2943_REG_CONTROL_MODE_SCAN; + + if (value != control) { + ret = ltc294x_write_regs(info->client, + LTC294X_REG_CONTROL, &control, 1); + if (ret < 0) { + dev_err(&info->client->dev, + "Could not write register\n"); + goto error_exit; + } + } + + return 0; + +error_exit: + return ret; +} + +static int ltc294x_read_charge_register(const struct ltc294x_info *info) +{ + int ret; + u8 datar[2]; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_ACC_CHARGE_MSB, &datar[0], 2); + if (ret < 0) + return ret; + return (datar[0] << 8) + datar[1]; +} + +static int ltc294x_get_charge_now(const struct ltc294x_info *info, int *val) +{ + int value = ltc294x_read_charge_register(info); + + if (value < 0) + return value; + /* When r_sense < 0, this counts up when the battery discharges */ + if (info->Qlsb < 0) + value -= 0xFFFF; + *val = convert_bin_to_uAh(info, value); + return 0; +} + +static int ltc294x_set_charge_now(const struct ltc294x_info *info, int val) +{ + int ret; + u8 dataw[2]; + u8 ctrl_reg; + s32 value; + + value = convert_uAh_to_bin(info, val); + /* Direction depends on how sense+/- were connected */ + if (info->Qlsb < 0) + value += 0xFFFF; + if ((value < 0) || (value > 0xFFFF)) /* input validation */ + return -EINVAL; + + /* Read control register */ + ret = ltc294x_read_regs(info->client, + LTC294X_REG_CONTROL, &ctrl_reg, 1); + if (ret < 0) + return ret; + /* Disable analog section */ + ctrl_reg |= LTC294X_REG_CONTROL_SHUTDOWN_MASK; + ret = ltc294x_write_regs(info->client, + LTC294X_REG_CONTROL, &ctrl_reg, 1); + if (ret < 0) + return ret; + /* Set new charge value */ + dataw[0] = I16_MSB(value); + dataw[1] = I16_LSB(value); + ret = ltc294x_write_regs(info->client, + LTC294X_REG_ACC_CHARGE_MSB, &dataw[0], 2); + if (ret < 0) + goto error_exit; + /* Enable analog section */ +error_exit: + ctrl_reg &= ~LTC294X_REG_CONTROL_SHUTDOWN_MASK; + ret = ltc294x_write_regs(info->client, + LTC294X_REG_CONTROL, &ctrl_reg, 1); + + return ret < 0 ? ret : 0; +} + +static int ltc294x_get_charge_counter( + const struct ltc294x_info *info, int *val) +{ + int value = ltc294x_read_charge_register(info); + + if (value < 0) + return value; + value -= LTC294X_MID_SUPPLY; + *val = convert_bin_to_uAh(info, value); + return 0; +} + +static int ltc294x_get_voltage(const struct ltc294x_info *info, int *val) +{ + int ret; + u8 datar[2]; + u32 value; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_VOLTAGE_MSB, &datar[0], 2); + value = (datar[0] << 8) | datar[1]; + *val = ((value * 23600) / 0xFFFF) * 1000; /* in uV */ + return ret; +} + +static int ltc294x_get_current(const struct ltc294x_info *info, int *val) +{ + int ret; + u8 datar[2]; + s32 value; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_CURRENT_MSB, &datar[0], 2); + value = (datar[0] << 8) | datar[1]; + value -= 0x7FFF; + /* Value is in range -32k..+32k, r_sense is usually 10..50 mOhm, + * the formula below keeps everything in s32 range while preserving + * enough digits */ + *val = 1000 * ((60000 * value) / (info->r_sense * 0x7FFF)); /* in uA */ + return ret; +} + +static int ltc294x_get_temperature(const struct ltc294x_info *info, int *val) +{ + int ret; + u8 datar[2]; + u32 value; + + ret = ltc294x_read_regs(info->client, + LTC294X_REG_TEMPERATURE_MSB, &datar[0], 2); + value = (datar[0] << 8) | datar[1]; + /* Full-scale is 510 Kelvin, convert to centidegrees */ + *val = (((51000 * value) / 0xFFFF) - 27215); + return ret; +} + +static int ltc294x_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct ltc294x_info *info = + container_of(psy, struct ltc294x_info, supply); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_NOW: + return ltc294x_get_charge_now(info, &val->intval); + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + return ltc294x_get_charge_counter(info, &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return ltc294x_get_voltage(info, &val->intval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return ltc294x_get_current(info, &val->intval); + case POWER_SUPPLY_PROP_TEMP: + return ltc294x_get_temperature(info, &val->intval); + default: + return -EINVAL; + } +} + +static int ltc294x_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ltc294x_info *info = + container_of(psy, struct ltc294x_info, supply); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_NOW: + return ltc294x_set_charge_now(info, val->intval); + default: + return -EPERM; + } +} + +static int ltc294x_property_is_writeable( + struct power_supply *psy, enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_NOW: + return 1; + default: + return 0; + } +} + +static void ltc294x_update(struct ltc294x_info *info) +{ + int charge = ltc294x_read_charge_register(info); + + if (charge != info->charge) { + info->charge = charge; + power_supply_changed(&info->supply); + } +} + +static void ltc294x_work(struct work_struct *work) +{ + struct ltc294x_info *info; + + info = container_of(work, struct ltc294x_info, work.work); + ltc294x_update(info); + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); +} + +static enum power_supply_property ltc294x_properties[] = { + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static int ltc294x_i2c_remove(struct i2c_client *client) +{ + struct ltc294x_info *info = i2c_get_clientdata(client); + + cancel_delayed_work(&info->work); + power_supply_unregister(&info->supply); + kfree(info->supply.name); + mutex_lock(<c294x_lock); + idr_remove(<c294x_id, info->id); + mutex_unlock(<c294x_lock); + return 0; +} + +static int ltc294x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ltc294x_info *info; + int ret; + int num; + u32 prescaler_exp; + s32 r_sense; + struct device_node *np; + + mutex_lock(<c294x_lock); + ret = idr_alloc(<c294x_id, client, 0, 0, GFP_KERNEL); + mutex_unlock(<c294x_lock); + if (ret < 0) + goto fail_id; + + num = ret; + + info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + ret = -ENOMEM; + goto fail_info; + } + + i2c_set_clientdata(client, info); + + info->num_regs = id->driver_data; + info->supply.name = kasprintf(GFP_KERNEL, "%s-%d", client->name, num); + if (!info->supply.name) { + ret = -ENOMEM; + goto fail_name; + } + + np = of_node_get(client->dev.of_node); + + /* r_sense can be negative, when sense+ is connected to the battery + * instead of the sense-. This results in reversed measurements. */ + ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense); + if (ret < 0) { + dev_err(&client->dev, + "Could not find lltc,resistor-sense in devicetree\n"); + goto fail_name; + } + info->r_sense = r_sense; + + ret = of_property_read_u32(np, "lltc,prescaler-exponent", + &prescaler_exp); + if (ret < 0) { + dev_warn(&client->dev, + "lltc,prescaler-exponent not in devicetree\n"); + prescaler_exp = LTC2941_MAX_PRESCALER_EXP; + } + + if (info->num_regs == LTC2943_NUM_REGS) { + if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP) + prescaler_exp = LTC2943_MAX_PRESCALER_EXP; + info->Qlsb = ((340 * 50000) / r_sense) / + (4096 / (1 << (2*prescaler_exp))); + } else { + if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP) + prescaler_exp = LTC2941_MAX_PRESCALER_EXP; + info->Qlsb = ((58 * 50000) / r_sense) / + (128 / (1 << prescaler_exp)); + } + + info->client = client; + info->id = num; + info->supply.type = POWER_SUPPLY_TYPE_BATTERY; + info->supply.properties = ltc294x_properties; + if (info->num_regs >= LTC294X_REG_TEMPERATURE_LSB) + info->supply.num_properties = + ARRAY_SIZE(ltc294x_properties); + else if (info->num_regs >= LTC294X_REG_CURRENT_LSB) + info->supply.num_properties = + ARRAY_SIZE(ltc294x_properties) - 1; + else if (info->num_regs >= LTC294X_REG_VOLTAGE_LSB) + info->supply.num_properties = + ARRAY_SIZE(ltc294x_properties) - 2; + else + info->supply.num_properties = + ARRAY_SIZE(ltc294x_properties) - 3; + info->supply.get_property = ltc294x_get_property; + info->supply.set_property = ltc294x_set_property; + info->supply.property_is_writeable = ltc294x_property_is_writeable; + info->supply.external_power_changed = NULL; + + INIT_DELAYED_WORK(&info->work, ltc294x_work); + + ret = ltc294x_reset(info, prescaler_exp); + if (ret < 0) { + dev_err(&client->dev, "Communication with chip failed\n"); + goto fail_comm; + } + + ret = power_supply_register(&client->dev, &info->supply); + if (ret) { + dev_err(&client->dev, "failed to register ltc2941\n"); + goto fail_register; + } else { + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); + } + + return 0; + +fail_register: + kfree(info->supply.name); +fail_comm: +fail_name: +fail_info: + mutex_lock(<c294x_lock); + idr_remove(<c294x_id, num); + mutex_unlock(<c294x_lock); +fail_id: + return ret; +} + +#ifdef CONFIG_PM_SLEEP + +static int ltc294x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc294x_info *info = i2c_get_clientdata(client); + + cancel_delayed_work(&info->work); + return 0; +} + +static int ltc294x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc294x_info *info = i2c_get_clientdata(client); + + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); + return 0; +} + +static SIMPLE_DEV_PM_OPS(ltc294x_pm_ops, ltc294x_suspend, ltc294x_resume); +#define LTC294X_PM_OPS (<c294x_pm_ops) + +#else +#define LTC294X_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + + +static const struct i2c_device_id ltc294x_i2c_id[] = { + {"ltc2941", LTC2941_NUM_REGS}, + {"ltc2943", LTC2943_NUM_REGS}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id); + +static struct i2c_driver ltc294x_driver = { + .driver = { + .name = "LTC2941", + .pm = LTC294X_PM_OPS, + }, + .probe = ltc294x_i2c_probe, + .remove = ltc294x_i2c_remove, + .id_table = ltc294x_i2c_id, +}; +module_i2c_driver(ltc294x_driver); + +MODULE_AUTHOR("Auryn Verwegen, Topic Embedded Systems"); +MODULE_AUTHOR("Mike Looijmans, Topic Embedded Products"); +MODULE_DESCRIPTION("LTC2941/LTC2943 Battery Gas Gauge IC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 66da691c41cf..1da6c5fbdff5 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -658,7 +658,7 @@ max17042_get_pdata(struct device *dev) } #endif -static struct regmap_config max17042_regmap_config = { +static const struct regmap_config max17042_regmap_config = { .reg_bits = 8, .val_bits = 16, .val_format_endian = REGMAP_ENDIAN_NATIVE, diff --git a/drivers/power/max77693_charger.c b/drivers/power/max77693_charger.c new file mode 100644 index 000000000000..b042970fdeaf --- /dev/null +++ b/drivers/power/max77693_charger.c @@ -0,0 +1,753 @@ +/* + * max77693_charger.c - Battery charger driver for the Maxim 77693 + * + * Copyright (C) 2014 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/regmap.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> + +static const char *max77693_charger_name = "max77693-charger"; +static const char *max77693_charger_model = "MAX77693"; +static const char *max77693_charger_manufacturer = "Maxim Integrated"; + +struct max77693_charger { + struct device *dev; + struct max77693_dev *max77693; + struct power_supply charger; + + u32 constant_volt; + u32 min_system_volt; + u32 thermal_regulation_temp; + u32 batttery_overcurrent; + u32 charge_input_threshold_volt; +}; + +static int max77693_get_charger_state(struct regmap *regmap) +{ + int state; + unsigned int data; + + if (regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data) < 0) + return POWER_SUPPLY_STATUS_UNKNOWN; + + data &= CHG_DETAILS_01_CHG_MASK; + data >>= CHG_DETAILS_01_CHG_SHIFT; + + switch (data) { + case MAX77693_CHARGING_PREQUALIFICATION: + case MAX77693_CHARGING_FAST_CONST_CURRENT: + case MAX77693_CHARGING_FAST_CONST_VOLTAGE: + case MAX77693_CHARGING_TOP_OFF: + /* In high temp the charging current is reduced, but still charging */ + case MAX77693_CHARGING_HIGH_TEMP: + state = POWER_SUPPLY_STATUS_CHARGING; + break; + case MAX77693_CHARGING_DONE: + state = POWER_SUPPLY_STATUS_FULL; + break; + case MAX77693_CHARGING_TIMER_EXPIRED: + case MAX77693_CHARGING_THERMISTOR_SUSPEND: + state = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case MAX77693_CHARGING_OFF: + case MAX77693_CHARGING_OVER_TEMP: + case MAX77693_CHARGING_WATCHDOG_EXPIRED: + state = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case MAX77693_CHARGING_RESERVED: + default: + state = POWER_SUPPLY_STATUS_UNKNOWN; + } + + return state; +} + +static int max77693_get_charge_type(struct regmap *regmap) +{ + int state; + unsigned int data; + + if (regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data) < 0) + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + + data &= CHG_DETAILS_01_CHG_MASK; + data >>= CHG_DETAILS_01_CHG_SHIFT; + + switch (data) { + case MAX77693_CHARGING_PREQUALIFICATION: + /* + * Top-off: trickle or fast? In top-off the current varies between + * 100 and 250 mA. It is higher than prequalification current. + */ + case MAX77693_CHARGING_TOP_OFF: + state = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case MAX77693_CHARGING_FAST_CONST_CURRENT: + case MAX77693_CHARGING_FAST_CONST_VOLTAGE: + /* In high temp the charging current is reduced, but still charging */ + case MAX77693_CHARGING_HIGH_TEMP: + state = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case MAX77693_CHARGING_DONE: + case MAX77693_CHARGING_TIMER_EXPIRED: + case MAX77693_CHARGING_THERMISTOR_SUSPEND: + case MAX77693_CHARGING_OFF: + case MAX77693_CHARGING_OVER_TEMP: + case MAX77693_CHARGING_WATCHDOG_EXPIRED: + state = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case MAX77693_CHARGING_RESERVED: + default: + state = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return state; +} + +/* + * Supported health statuses: + * - POWER_SUPPLY_HEALTH_DEAD + * - POWER_SUPPLY_HEALTH_GOOD + * - POWER_SUPPLY_HEALTH_OVERVOLTAGE + * - POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE + * - POWER_SUPPLY_HEALTH_UNKNOWN + * - POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + */ +static int max77693_get_battery_health(struct regmap *regmap) +{ + int state; + unsigned int data; + + if (regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data) < 0) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + data &= CHG_DETAILS_01_BAT_MASK; + data >>= CHG_DETAILS_01_BAT_SHIFT; + + switch (data) { + case MAX77693_BATTERY_NOBAT: + state = POWER_SUPPLY_HEALTH_DEAD; + break; + case MAX77693_BATTERY_PREQUALIFICATION: + case MAX77693_BATTERY_GOOD: + case MAX77693_BATTERY_LOWVOLTAGE: + state = POWER_SUPPLY_HEALTH_GOOD; + break; + case MAX77693_BATTERY_TIMER_EXPIRED: + /* + * Took longer to charge than expected, charging suspended. + * Damaged battery? + */ + state = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + case MAX77693_BATTERY_OVERVOLTAGE: + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + case MAX77693_BATTERY_OVERCURRENT: + state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case MAX77693_BATTERY_RESERVED: + default: + state = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + return state; +} + +static int max77693_get_present(struct regmap *regmap) +{ + unsigned int data; + + /* + * Read CHG_INT_OK register. High DETBAT bit here should be + * equal to value 0x0 in CHG_DETAILS_01/BAT field. + */ + regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data); + if (data & CHG_INT_OK_DETBAT_MASK) + return 0; + return 1; +} + +static int max77693_get_online(struct regmap *regmap) +{ + unsigned int data; + + regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data); + if (data & CHG_INT_OK_CHGIN_MASK) + return 1; + return 0; +} + +static enum power_supply_property max77693_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 int max77693_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77693_charger *chg = container_of(psy, + struct max77693_charger, + charger); + struct regmap *regmap = chg->max77693->regmap; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max77693_get_charger_state(regmap); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = max77693_get_charge_type(regmap); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max77693_get_battery_health(regmap); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max77693_get_present(regmap); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max77693_get_online(regmap); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = max77693_charger_model; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max77693_charger_manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static ssize_t device_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count, + int (*fn)(struct max77693_charger *, unsigned long)) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = fn(chg, val); + if (ret) + return ret; + + return count; +} + +static ssize_t fast_charge_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned int data, val; + int ret; + + ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_01, + &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_01_FCHGTIME_MASK; + data >>= CHG_CNFG_01_FCHGTIME_SHIFT; + switch (data) { + case 0x1 ... 0x7: + /* Starting from 4 hours, step by 2 hours */ + val = 4 + (data - 1) * 2; + break; + case 0x0: + default: + val = 0; + break; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int max77693_set_fast_charge_timer(struct max77693_charger *chg, + unsigned long hours) +{ + unsigned int data; + + /* + * 0x00 - disable + * 0x01 - 4h + * 0x02 - 6h + * ... + * 0x07 - 16h + * Round down odd values. + */ + switch (hours) { + case 4 ... 16: + data = (hours - 4) / 2 + 1; + break; + case 0: + /* Disable */ + data = 0; + break; + default: + return -EINVAL; + } + data <<= CHG_CNFG_01_FCHGTIME_SHIFT; + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_01, + CHG_CNFG_01_FCHGTIME_MASK, data); +} + +static ssize_t fast_charge_timer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return device_attr_store(dev, attr, buf, count, + max77693_set_fast_charge_timer); +} + +static ssize_t top_off_threshold_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned int data, val; + int ret; + + ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03, + &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_03_TOITH_MASK; + data >>= CHG_CNFG_03_TOITH_SHIFT; + + if (data <= 0x04) + val = 100000 + data * 25000; + else + val = data * 50000; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int max77693_set_top_off_threshold_current(struct max77693_charger *chg, + unsigned long uamp) +{ + unsigned int data; + + if (uamp < 100000 || uamp > 350000) + return -EINVAL; + + if (uamp <= 200000) + data = (uamp - 100000) / 25000; + else + /* (200000, 350000> */ + data = uamp / 50000; + + data <<= CHG_CNFG_03_TOITH_SHIFT; + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_03, + CHG_CNFG_03_TOITH_MASK, data); +} + +static ssize_t top_off_threshold_current_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return device_attr_store(dev, attr, buf, count, + max77693_set_top_off_threshold_current); +} + +static ssize_t top_off_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77693_charger *chg = dev_get_drvdata(dev); + unsigned int data, val; + int ret; + + ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03, + &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_03_TOTIME_MASK; + data >>= CHG_CNFG_03_TOTIME_SHIFT; + + val = data * 10; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int max77693_set_top_off_timer(struct max77693_charger *chg, + unsigned long minutes) +{ + unsigned int data; + + if (minutes > 70) + return -EINVAL; + + data = minutes / 10; + data <<= CHG_CNFG_03_TOTIME_SHIFT; + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_03, + CHG_CNFG_03_TOTIME_MASK, data); +} + +static ssize_t top_off_timer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return device_attr_store(dev, attr, buf, count, + max77693_set_top_off_timer); +} + +static DEVICE_ATTR_RW(fast_charge_timer); +static DEVICE_ATTR_RW(top_off_threshold_current); +static DEVICE_ATTR_RW(top_off_timer); + +static int max77693_set_constant_volt(struct max77693_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + /* + * 0x00 - 3.650 V + * 0x01 - 3.675 V + * ... + * 0x1b - 4.325 V + * 0x1c - 4.340 V + * 0x1d - 4.350 V + * 0x1e - 4.375 V + * 0x1f - 4.400 V + */ + if (uvolt >= 3650000 && uvolt < 4340000) + data = (uvolt - 3650000) / 25000; + else if (uvolt >= 4340000 && uvolt < 4350000) + data = 0x1c; + else if (uvolt >= 4350000 && uvolt <= 4400000) + data = 0x1d + (uvolt - 4350000) / 25000; + else { + dev_err(chg->dev, "Wrong value for charging constant voltage\n"); + return -EINVAL; + } + + data <<= CHG_CNFG_04_CHGCVPRM_SHIFT; + + dev_dbg(chg->dev, "Charging constant voltage: %u (0x%x)\n", uvolt, + data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_04, + CHG_CNFG_04_CHGCVPRM_MASK, data); +} + +static int max77693_set_min_system_volt(struct max77693_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + if (uvolt < 3000000 || uvolt > 3700000) { + dev_err(chg->dev, "Wrong value for minimum system regulation voltage\n"); + return -EINVAL; + } + + data = (uvolt - 3000000) / 100000; + + data <<= CHG_CNFG_04_MINVSYS_SHIFT; + + dev_dbg(chg->dev, "Minimum system regulation voltage: %u (0x%x)\n", + uvolt, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_04, + CHG_CNFG_04_MINVSYS_MASK, data); +} + +static int max77693_set_thermal_regulation_temp(struct max77693_charger *chg, + unsigned int cels) +{ + unsigned int data; + + switch (cels) { + case 70: + case 85: + case 100: + case 115: + data = (cels - 70) / 15; + break; + default: + dev_err(chg->dev, "Wrong value for thermal regulation loop temperature\n"); + return -EINVAL; + } + + data <<= CHG_CNFG_07_REGTEMP_SHIFT; + + dev_dbg(chg->dev, "Thermal regulation loop temperature: %u (0x%x)\n", + cels, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_07, + CHG_CNFG_07_REGTEMP_MASK, data); +} + +static int max77693_set_batttery_overcurrent(struct max77693_charger *chg, + unsigned int uamp) +{ + unsigned int data; + + if (uamp && (uamp < 2000000 || uamp > 3500000)) { + dev_err(chg->dev, "Wrong value for battery overcurrent\n"); + return -EINVAL; + } + + if (uamp) + data = ((uamp - 2000000) / 250000) + 1; + else + data = 0; /* disable */ + + data <<= CHG_CNFG_12_B2SOVRC_SHIFT; + + dev_dbg(chg->dev, "Battery overcurrent: %u (0x%x)\n", uamp, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_12, + CHG_CNFG_12_B2SOVRC_MASK, data); +} + +static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg, + unsigned int uvolt) +{ + unsigned int data; + + switch (uvolt) { + case 4300000: + data = 0x0; + break; + case 4700000: + case 4800000: + case 4900000: + data = (uvolt - 4700000) / 100000; + default: + dev_err(chg->dev, "Wrong value for charge input voltage regulation threshold\n"); + return -EINVAL; + } + + data <<= CHG_CNFG_12_VCHGINREG_SHIFT; + + dev_dbg(chg->dev, "Charge input voltage regulation threshold: %u (0x%x)\n", + uvolt, data); + + return regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_12, + CHG_CNFG_12_VCHGINREG_MASK, data); +} + +/* + * Sets charger registers to proper and safe default values. + */ +static int max77693_reg_init(struct max77693_charger *chg) +{ + int ret; + unsigned int data; + + /* Unlock charger register protection */ + data = (0x3 << CHG_CNFG_06_CHGPROT_SHIFT); + ret = regmap_update_bits(chg->max77693->regmap, + MAX77693_CHG_REG_CHG_CNFG_06, + CHG_CNFG_06_CHGPROT_MASK, data); + if (ret) { + dev_err(chg->dev, "Error unlocking registers: %d\n", ret); + return ret; + } + + ret = max77693_set_fast_charge_timer(chg, DEFAULT_FAST_CHARGE_TIMER); + if (ret) + return ret; + + ret = max77693_set_top_off_threshold_current(chg, + DEFAULT_TOP_OFF_THRESHOLD_CURRENT); + if (ret) + return ret; + + ret = max77693_set_top_off_timer(chg, DEFAULT_TOP_OFF_TIMER); + if (ret) + return ret; + + ret = max77693_set_constant_volt(chg, chg->constant_volt); + if (ret) + return ret; + + ret = max77693_set_min_system_volt(chg, chg->min_system_volt); + if (ret) + return ret; + + ret = max77693_set_thermal_regulation_temp(chg, + chg->thermal_regulation_temp); + if (ret) + return ret; + + ret = max77693_set_batttery_overcurrent(chg, chg->batttery_overcurrent); + if (ret) + return ret; + + return max77693_set_charge_input_threshold_volt(chg, + chg->charge_input_threshold_volt); +} + +#ifdef CONFIG_OF +static int max77693_dt_init(struct device *dev, struct max77693_charger *chg) +{ + struct device_node *np = dev->of_node; + + if (!np) { + dev_err(dev, "no charger OF node\n"); + return -EINVAL; + } + + if (of_property_read_u32(np, "maxim,constant-microvolt", + &chg->constant_volt)) + chg->constant_volt = DEFAULT_CONSTANT_VOLT; + + if (of_property_read_u32(np, "maxim,min-system-microvolt", + &chg->min_system_volt)) + chg->min_system_volt = DEFAULT_MIN_SYSTEM_VOLT; + + if (of_property_read_u32(np, "maxim,thermal-regulation-celsius", + &chg->thermal_regulation_temp)) + chg->thermal_regulation_temp = DEFAULT_THERMAL_REGULATION_TEMP; + + if (of_property_read_u32(np, "maxim,battery-overcurrent-microamp", + &chg->batttery_overcurrent)) + chg->batttery_overcurrent = DEFAULT_BATTERY_OVERCURRENT; + + if (of_property_read_u32(np, "maxim,charge-input-threshold-microvolt", + &chg->charge_input_threshold_volt)) + chg->charge_input_threshold_volt = + DEFAULT_CHARGER_INPUT_THRESHOLD_VOLT; + + return 0; +} +#else /* CONFIG_OF */ +static int max77693_dt_init(struct device *dev, struct max77693_charger *chg) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int max77693_charger_probe(struct platform_device *pdev) +{ + struct max77693_charger *chg; + struct max77693_dev *max77693 = 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->max77693 = max77693; + + ret = max77693_dt_init(&pdev->dev, chg); + if (ret) + return ret; + + ret = max77693_reg_init(chg); + if (ret) + return ret; + + chg->charger.name = max77693_charger_name; + chg->charger.type = POWER_SUPPLY_TYPE_BATTERY; + chg->charger.properties = max77693_charger_props; + chg->charger.num_properties = ARRAY_SIZE(max77693_charger_props); + chg->charger.get_property = max77693_charger_get_property; + + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create fast charge timer sysfs entry\n"); + goto err; + } + + ret = device_create_file(&pdev->dev, + &dev_attr_top_off_threshold_current); + if (ret) { + dev_err(&pdev->dev, "failed: create top off current sysfs entry\n"); + goto err; + } + + ret = device_create_file(&pdev->dev, &dev_attr_top_off_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create top off timer sysfs entry\n"); + goto err; + } + + ret = power_supply_register(&pdev->dev, &chg->charger); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + goto err; + } + + return 0; + +err: + device_remove_file(&pdev->dev, &dev_attr_top_off_timer); + device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + return ret; +} + +static int max77693_charger_remove(struct platform_device *pdev) +{ + struct max77693_charger *chg = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_top_off_timer); + device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + power_supply_unregister(&chg->charger); + + return 0; +} + +static const struct platform_device_id max77693_charger_id[] = { + { "max77693-charger", 0, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77693_charger_id); + +static struct platform_driver max77693_charger_driver = { + .driver = { + .name = "max77693-charger", + }, + .probe = max77693_charger_probe, + .remove = max77693_charger_remove, + .id_table = max77693_charger_id, +}; +module_platform_driver(max77693_charger_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); +MODULE_DESCRIPTION("Maxim 77693 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 028e76504519..27f6646731b0 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -39,14 +39,13 @@ config POWER_RESET_AXXIA Say Y if you have an Axxia family SoC. config POWER_RESET_BRCMSTB - bool "Broadcom STB reset driver" if COMPILE_TEST - depends on ARM + bool "Broadcom STB reset driver" + depends on ARM || MIPS || COMPILE_TEST default ARCH_BRCMSTB help - This driver provides restart support for ARM-based Broadcom STB - boards. + This driver provides restart support for Broadcom STB boards. - Say Y here if you have an ARM-based Broadcom STB board and you wish + Say Y here if you have a Broadcom STB board and you wish to have restart support. config POWER_RESET_GPIO @@ -104,23 +103,16 @@ config POWER_RESET_QNAP config POWER_RESET_RESTART bool "Restart power-off driver" - depends on ARM help Some boards don't actually have the ability to power off. 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 - help - Reboot support for the Allwinner A31 SoCs. - config POWER_RESET_ST - bool "ST restart power-off driver" + bool "ST restart driver" depends on ARCH_STI help - Power off and reset support for STMicroelectronics boards. + Reset support for STMicroelectronics boards. config POWER_RESET_VERSATILE bool "ARM Versatile family reboot driver" @@ -159,5 +151,11 @@ config POWER_RESET_SYSCON help Reboot support for generic SYSCON mapped register reset. +config POWER_RESET_RMOBILE + tristate "Renesas R-Mobile reset driver" + depends on ARCH_RMOBILE || COMPILE_TEST + help + Reboot support for Renesas R-Mobile and SH-Mobile SoCs. + endif diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 1d4804d6b323..11de15bae52e 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -11,10 +11,10 @@ obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-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_ST) += st-poweroff.o obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o +obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o diff --git a/drivers/power/reset/arm-versatile-reboot.c b/drivers/power/reset/arm-versatile-reboot.c index 5b08bffcf1a8..b208073c887d 100644 --- a/drivers/power/reset/arm-versatile-reboot.c +++ b/drivers/power/reset/arm-versatile-reboot.c @@ -13,16 +13,22 @@ #include <linux/reboot.h> #include <linux/regmap.h> #include <linux/of.h> -#include <asm/system_misc.h> + +#define INTEGRATOR_HDR_CTRL_OFFSET 0x0C +#define INTEGRATOR_HDR_LOCK_OFFSET 0x14 +#define INTEGRATOR_CM_CTRL_RESET (1 << 3) #define REALVIEW_SYS_LOCK_OFFSET 0x20 -#define REALVIEW_SYS_LOCK_VAL 0xA05F #define REALVIEW_SYS_RESETCTL_OFFSET 0x40 +/* Magic unlocking token used on all Versatile boards */ +#define VERSATILE_LOCK_VAL 0xA05F + /* * We detect the different syscon types from the compatible strings. */ enum versatile_reboot { + INTEGRATOR_REBOOT_CM, REALVIEW_REBOOT_EB, REALVIEW_REBOOT_PB1176, REALVIEW_REBOOT_PB11MP, @@ -36,6 +42,10 @@ static enum versatile_reboot versatile_reboot_type; static const struct of_device_id versatile_reboot_of_match[] = { { + .compatible = "arm,core-module-integrator", + .data = (void *)INTEGRATOR_REBOOT_CM + }, + { .compatible = "arm,realview-eb-syscon", .data = (void *)REALVIEW_REBOOT_EB, }, @@ -55,31 +65,47 @@ static const struct of_device_id versatile_reboot_of_match[] = { .compatible = "arm,realview-pbx-syscon", .data = (void *)REALVIEW_REBOOT_PBX, }, + {}, }; -static void versatile_reboot(enum reboot_mode mode, const char *cmd) +static int versatile_reboot(struct notifier_block *this, unsigned long mode, + void *cmd) { /* Unlock the reset register */ - regmap_write(syscon_regmap, REALVIEW_SYS_LOCK_OFFSET, - REALVIEW_SYS_LOCK_VAL); /* Then hit reset on the different machines */ switch (versatile_reboot_type) { + case INTEGRATOR_REBOOT_CM: + regmap_write(syscon_regmap, INTEGRATOR_HDR_LOCK_OFFSET, + VERSATILE_LOCK_VAL); + regmap_update_bits(syscon_regmap, + INTEGRATOR_HDR_CTRL_OFFSET, + INTEGRATOR_CM_CTRL_RESET, + INTEGRATOR_CM_CTRL_RESET); + break; case REALVIEW_REBOOT_EB: + regmap_write(syscon_regmap, REALVIEW_SYS_LOCK_OFFSET, + VERSATILE_LOCK_VAL); regmap_write(syscon_regmap, REALVIEW_SYS_RESETCTL_OFFSET, 0x0008); break; case REALVIEW_REBOOT_PB1176: + regmap_write(syscon_regmap, REALVIEW_SYS_LOCK_OFFSET, + VERSATILE_LOCK_VAL); regmap_write(syscon_regmap, REALVIEW_SYS_RESETCTL_OFFSET, 0x0100); break; case REALVIEW_REBOOT_PB11MP: case REALVIEW_REBOOT_PBA8: + regmap_write(syscon_regmap, REALVIEW_SYS_LOCK_OFFSET, + VERSATILE_LOCK_VAL); regmap_write(syscon_regmap, REALVIEW_SYS_RESETCTL_OFFSET, 0x0000); regmap_write(syscon_regmap, REALVIEW_SYS_RESETCTL_OFFSET, 0x0004); break; case REALVIEW_REBOOT_PBX: + regmap_write(syscon_regmap, REALVIEW_SYS_LOCK_OFFSET, + VERSATILE_LOCK_VAL); regmap_write(syscon_regmap, REALVIEW_SYS_RESETCTL_OFFSET, 0x00f0); regmap_write(syscon_regmap, REALVIEW_SYS_RESETCTL_OFFSET, @@ -87,12 +113,20 @@ static void versatile_reboot(enum reboot_mode mode, const char *cmd) break; } dsb(); + + return NOTIFY_DONE; } +static struct notifier_block versatile_reboot_nb = { + .notifier_call = versatile_reboot, + .priority = 192, +}; + static int __init versatile_reboot_probe(void) { const struct of_device_id *reboot_id; struct device_node *np; + int err; np = of_find_matching_node_and_match(NULL, versatile_reboot_of_match, &reboot_id); @@ -104,7 +138,10 @@ static int __init versatile_reboot_probe(void) if (IS_ERR(syscon_regmap)) return PTR_ERR(syscon_regmap); - arm_pm_restart = versatile_reboot; + err = register_restart_handler(&versatile_reboot_nb); + if (err) + return err; + pr_info("versatile reboot driver registered\n"); return 0; } diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c index c61000333bb9..4b72ea51c364 100644 --- a/drivers/power/reset/at91-poweroff.c +++ b/drivers/power/reset/at91-poweroff.c @@ -71,10 +71,11 @@ static void at91_poweroff(void) writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR); } -const enum wakeup_type at91_poweroff_get_wakeup_mode(struct device_node *np) +static int at91_poweroff_get_wakeup_mode(struct device_node *np) { const char *pm; - int err, i; + unsigned int i; + int err; err = of_property_read_string(np, "atmel,wakeup-mode", &pm); if (err < 0) @@ -90,7 +91,7 @@ const enum wakeup_type at91_poweroff_get_wakeup_mode(struct device_node *np) static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - enum wakeup_type wakeup_mode; + int wakeup_mode; u32 mode = 0, tmp; wakeup_mode = at91_poweroff_get_wakeup_mode(np); diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index 69a75d99ae92..13584e24736a 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -17,8 +17,6 @@ #include <linux/platform_device.h> #include <linux/reboot.h> -#include <asm/system_misc.h> - #include <soc/at91/at91sam9_ddrsdr.h> #include <soc/at91/at91sam9_sdramc.h> @@ -54,7 +52,8 @@ static void __iomem *at91_ramc_base[2], *at91_rstc_base; * reset register it can be left driving the data bus and * killing the chance of a subsequent boot from NAND */ -static void at91sam9260_restart(enum reboot_mode mode, const char *cmd) +static int at91sam9260_restart(struct notifier_block *this, unsigned long mode, + void *cmd) { asm volatile( /* Align to cache lines */ @@ -76,9 +75,12 @@ static void at91sam9260_restart(enum reboot_mode mode, const char *cmd) "r" (1), "r" (AT91_SDRAMC_LPCB_POWER_DOWN), "r" (AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST)); + + return NOTIFY_DONE; } -static void at91sam9g45_restart(enum reboot_mode mode, const char *cmd) +static int at91sam9g45_restart(struct notifier_block *this, unsigned long mode, + void *cmd) { asm volatile( /* @@ -117,6 +119,8 @@ static void at91sam9g45_restart(enum reboot_mode mode, const char *cmd) "r" (AT91_DDRSDRC_LPCB_POWER_DOWN), "r" (AT91_RSTC_KEY | AT91_RSTC_PERRST | AT91_RSTC_PROCRST) : "r0"); + + return NOTIFY_DONE; } static void __init at91_reset_status(struct platform_device *pdev) @@ -161,6 +165,10 @@ static struct of_device_id at91_reset_of_match[] = { { /* sentinel */ } }; +static struct notifier_block at91_restart_nb = { + .priority = 192, +}; + static int at91_reset_of_probe(struct platform_device *pdev) { const struct of_device_id *match; @@ -183,9 +191,8 @@ static int at91_reset_of_probe(struct platform_device *pdev) } match = of_match_node(at91_reset_of_match, pdev->dev.of_node); - arm_pm_restart = match->data; - - return 0; + at91_restart_nb.notifier_call = match->data; + return register_restart_handler(&at91_restart_nb); } static int at91_reset_platform_probe(struct platform_device *pdev) @@ -212,10 +219,11 @@ static int at91_reset_platform_probe(struct platform_device *pdev) } match = platform_get_device_id(pdev); - arm_pm_restart = (void (*)(enum reboot_mode, const char*)) - match->driver_data; + at91_restart_nb.notifier_call = + (int (*)(struct notifier_block *, + unsigned long, void *)) match->driver_data; - return 0; + return register_restart_handler(&at91_restart_nb); } static int at91_reset_probe(struct platform_device *pdev) diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index 100606f9b3dc..884b53c483c0 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -11,6 +11,7 @@ * GNU General Public License for more details. */ +#include <linux/bitops.h> #include <linux/device.h> #include <linux/errno.h> #include <linux/init.h> @@ -34,13 +35,20 @@ static struct regmap *regmap; static u32 rst_src_en; static u32 sw_mstr_rst; +struct reset_reg_mask { + u32 rst_src_en_mask; + u32 sw_mstr_rst_mask; +}; + +static const struct reset_reg_mask *reset_masks; + static int brcmstb_restart_handler(struct notifier_block *this, unsigned long mode, void *cmd) { int rc; u32 tmp; - rc = regmap_write(regmap, rst_src_en, 1); + rc = regmap_write(regmap, rst_src_en, reset_masks->rst_src_en_mask); if (rc) { pr_err("failed to write rst_src_en (%d)\n", rc); return NOTIFY_DONE; @@ -52,7 +60,7 @@ static int brcmstb_restart_handler(struct notifier_block *this, return NOTIFY_DONE; } - rc = regmap_write(regmap, sw_mstr_rst, 1); + rc = regmap_write(regmap, sw_mstr_rst, reset_masks->sw_mstr_rst_mask); if (rc) { pr_err("failed to write sw_mstr_rst (%d)\n", rc); return NOTIFY_DONE; @@ -75,10 +83,34 @@ static struct notifier_block brcmstb_restart_nb = { .priority = 128, }; +static const struct reset_reg_mask reset_bits_40nm = { + .rst_src_en_mask = BIT(0), + .sw_mstr_rst_mask = BIT(0), +}; + +static const struct reset_reg_mask reset_bits_65nm = { + .rst_src_en_mask = BIT(3), + .sw_mstr_rst_mask = BIT(31), +}; + +static const struct of_device_id of_match[] = { + { .compatible = "brcm,brcmstb-reboot", .data = &reset_bits_40nm }, + { .compatible = "brcm,bcm7038-reboot", .data = &reset_bits_65nm }, + {}, +}; + static int brcmstb_reboot_probe(struct platform_device *pdev) { int rc; struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + + of_id = of_match_node(of_match, np); + if (!of_id) { + pr_err("failed to look up compatible string\n"); + return -EINVAL; + } + reset_masks = of_id->data; regmap = syscon_regmap_lookup_by_phandle(np, "syscon"); if (IS_ERR(regmap)) { @@ -108,11 +140,6 @@ static int brcmstb_reboot_probe(struct platform_device *pdev) return rc; } -static const struct of_device_id of_match[] = { - { .compatible = "brcm,brcmstb-reboot", }, - {}, -}; - static struct platform_driver brcmstb_reboot_driver = { .probe = brcmstb_reboot_probe, .driver = { diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c index 34f38a3dc3ff..7ef193b6f7fe 100644 --- a/drivers/power/reset/ltc2952-poweroff.c +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -32,7 +32,9 @@ * - trigger (input) * A level change indicates the shut-down trigger. If it's state reverts * within the time-out defined by trigger_delay, the shut down is not - * executed. + * executed. If no pin is assigned to this input, the driver will start the + * watchdog toggle immediately. The chip will only power off the system if + * it is requested to do so through the kill line. * * - watchdog (output) * Once a shut down is triggered, the driver will toggle this signal, @@ -63,7 +65,7 @@ #include <linux/gpio/consumer.h> #include <linux/reboot.h> -struct ltc2952_poweroff_data { +struct ltc2952_poweroff { struct hrtimer timer_trigger; struct hrtimer timer_wde; @@ -72,22 +74,21 @@ struct ltc2952_poweroff_data { struct device *dev; - unsigned int virq; + struct gpio_desc *gpio_trigger; + struct gpio_desc *gpio_watchdog; + struct gpio_desc *gpio_kill; - /** - * 0: trigger - * 1: watchdog - * 2: kill - */ - struct gpio_desc *gpio[3]; + bool kernel_panic; + struct notifier_block panic_notifier; }; -static int ltc2952_poweroff_panic; -static struct ltc2952_poweroff_data *ltc2952_data; +#define to_ltc2952(p, m) container_of(p, struct ltc2952_poweroff, m) -#define POWERPATH_IO_TRIGGER 0 -#define POWERPATH_IO_WATCHDOG 1 -#define POWERPATH_IO_KILL 2 +/* + * This global variable is only needed for pm_power_off. We should + * remove it entirely once we don't need the global state anymore. + */ +static struct ltc2952_poweroff *ltc2952_data; /** * ltc2952_poweroff_timer_wde - Timer callback @@ -103,29 +104,24 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer) ktime_t now; int state; unsigned long overruns; + struct ltc2952_poweroff *data = to_ltc2952(timer, timer_wde); - if (ltc2952_poweroff_panic) + if (data->kernel_panic) return HRTIMER_NORESTART; - state = gpiod_get_value(ltc2952_data->gpio[POWERPATH_IO_WATCHDOG]); - gpiod_set_value(ltc2952_data->gpio[POWERPATH_IO_WATCHDOG], !state); + state = gpiod_get_value(data->gpio_watchdog); + gpiod_set_value(data->gpio_watchdog, !state); now = hrtimer_cb_get_time(timer); - overruns = hrtimer_forward(timer, now, ltc2952_data->wde_interval); + overruns = hrtimer_forward(timer, now, data->wde_interval); return HRTIMER_RESTART; } -static enum hrtimer_restart ltc2952_poweroff_timer_trigger( - struct hrtimer *timer) +static void ltc2952_poweroff_start_wde(struct ltc2952_poweroff *data) { - int ret; - - ret = hrtimer_start(<c2952_data->timer_wde, - ltc2952_data->wde_interval, HRTIMER_MODE_REL); - - if (ret) { - dev_err(ltc2952_data->dev, "unable to start the timer\n"); + if (hrtimer_start(&data->timer_wde, data->wde_interval, + HRTIMER_MODE_REL)) { /* * The device will not toggle the watchdog reset, * thus shut down is only safe if the PowerPath controller @@ -134,10 +130,17 @@ static enum hrtimer_restart ltc2952_poweroff_timer_trigger( * * Only sending a warning as the system will power-off anyway */ + dev_err(data->dev, "unable to start the timer\n"); } +} - dev_info(ltc2952_data->dev, "executing shutdown\n"); +static enum hrtimer_restart +ltc2952_poweroff_timer_trigger(struct hrtimer *timer) +{ + struct ltc2952_poweroff *data = to_ltc2952(timer, timer_trigger); + ltc2952_poweroff_start_wde(data); + dev_info(data->dev, "executing shutdown\n"); orderly_poweroff(true); return HRTIMER_NORESTART; @@ -154,180 +157,161 @@ static enum hrtimer_restart ltc2952_poweroff_timer_trigger( */ static irqreturn_t ltc2952_poweroff_handler(int irq, void *dev_id) { - int ret; - struct ltc2952_poweroff_data *data = dev_id; + struct ltc2952_poweroff *data = dev_id; - if (ltc2952_poweroff_panic) - goto irq_ok; - - if (hrtimer_active(&data->timer_wde)) { + if (data->kernel_panic || hrtimer_active(&data->timer_wde)) { /* shutdown is already triggered, nothing to do any more */ - goto irq_ok; + return IRQ_HANDLED; } - if (!hrtimer_active(&data->timer_trigger)) { - ret = hrtimer_start(&data->timer_trigger, data->trigger_delay, - HRTIMER_MODE_REL); - - if (ret) + if (gpiod_get_value(data->gpio_trigger)) { + if (hrtimer_start(&data->timer_trigger, data->trigger_delay, + HRTIMER_MODE_REL)) dev_err(data->dev, "unable to start the wait timer\n"); } else { - ret = hrtimer_cancel(&data->timer_trigger); + hrtimer_cancel(&data->timer_trigger); /* omitting return value check, timer should have been valid */ } - -irq_ok: return IRQ_HANDLED; } static void ltc2952_poweroff_kill(void) { - gpiod_set_value(ltc2952_data->gpio[POWERPATH_IO_KILL], 1); -} - -static int ltc2952_poweroff_suspend(struct platform_device *pdev, - pm_message_t state) -{ - return -ENOSYS; -} - -static int ltc2952_poweroff_resume(struct platform_device *pdev) -{ - return -ENOSYS; + gpiod_set_value(ltc2952_data->gpio_kill, 1); } -static void ltc2952_poweroff_default(struct ltc2952_poweroff_data *data) +static void ltc2952_poweroff_default(struct ltc2952_poweroff *data) { - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(data->gpio); i++) - data->gpio[i] = NULL; - data->wde_interval = ktime_set(0, 300L*1E6L); data->trigger_delay = ktime_set(2, 500L*1E6L); hrtimer_init(&data->timer_trigger, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - data->timer_trigger.function = <c2952_poweroff_timer_trigger; + data->timer_trigger.function = ltc2952_poweroff_timer_trigger; hrtimer_init(&data->timer_wde, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - data->timer_wde.function = <c2952_poweroff_timer_wde; + data->timer_wde.function = ltc2952_poweroff_timer_wde; } static int ltc2952_poweroff_init(struct platform_device *pdev) { - int ret, virq; - unsigned int i; - struct ltc2952_poweroff_data *data; - - static char *name[] = { - "trigger", - "watchdog", - "kill", - NULL - }; - - data = ltc2952_data; - ltc2952_poweroff_default(ltc2952_data); - - for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) { - ltc2952_data->gpio[i] = gpiod_get(&pdev->dev, name[i]); + int ret; + struct ltc2952_poweroff *data = platform_get_drvdata(pdev); - if (IS_ERR(ltc2952_data->gpio[i])) { - ret = PTR_ERR(ltc2952_data->gpio[i]); - dev_err(&pdev->dev, - "unable to claim the following gpio: %s\n", - name[i]); - goto err_io; - } - } + ltc2952_poweroff_default(data); - ret = gpiod_direction_output( - ltc2952_data->gpio[POWERPATH_IO_WATCHDOG], 0); - if (ret) { - dev_err(&pdev->dev, "unable to use watchdog-gpio as output\n"); - goto err_io; + data->gpio_watchdog = devm_gpiod_get(&pdev->dev, "watchdog", + GPIOD_OUT_LOW); + if (IS_ERR(data->gpio_watchdog)) { + ret = PTR_ERR(data->gpio_watchdog); + dev_err(&pdev->dev, "unable to claim gpio \"watchdog\"\n"); + return ret; } - ret = gpiod_direction_output(ltc2952_data->gpio[POWERPATH_IO_KILL], 0); - if (ret) { - dev_err(&pdev->dev, "unable to use kill-gpio as output\n"); - goto err_io; + data->gpio_kill = devm_gpiod_get(&pdev->dev, "kill", GPIOD_OUT_LOW); + if (IS_ERR(data->gpio_kill)) { + ret = PTR_ERR(data->gpio_kill); + dev_err(&pdev->dev, "unable to claim gpio \"kill\"\n"); + return ret; } - virq = gpiod_to_irq(ltc2952_data->gpio[POWERPATH_IO_TRIGGER]); - if (virq < 0) { - dev_err(&pdev->dev, "cannot map GPIO as interrupt"); - goto err_io; + data->gpio_trigger = devm_gpiod_get(&pdev->dev, "trigger", GPIOD_IN); + if (IS_ERR(data->gpio_trigger)) { + /* + * It's not a problem if the trigger gpio isn't available, but + * it is worth a warning if its use was defined in the device + * tree. + */ + if (PTR_ERR(data->gpio_trigger) != -ENOENT) + dev_err(&pdev->dev, + "unable to claim gpio \"trigger\"\n"); + data->gpio_trigger = NULL; } - ltc2952_data->virq = virq; - ret = request_irq(virq, - ltc2952_poweroff_handler, - (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING), - "ltc2952-poweroff", - ltc2952_data - ); - - if (ret) { - dev_err(&pdev->dev, "cannot configure an interrupt handler\n"); - goto err_io; + if (devm_request_irq(&pdev->dev, gpiod_to_irq(data->gpio_trigger), + ltc2952_poweroff_handler, + (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING), + "ltc2952-poweroff", + data)) { + /* + * Some things may have happened: + * - No trigger input was defined + * - Claiming the GPIO failed + * - We could not map to an IRQ + * - We couldn't register an interrupt handler + * + * None of these really are problems, but all of them + * disqualify the push button from controlling the power. + * + * It is therefore important to note that if the ltc2952 + * detects a button press for long enough, it will still start + * its own powerdown window and cut the power on us if we don't + * start the watchdog trigger. + */ + if (data->gpio_trigger) { + dev_warn(&pdev->dev, + "unable to configure the trigger interrupt\n"); + devm_gpiod_put(&pdev->dev, data->gpio_trigger); + data->gpio_trigger = NULL; + } + dev_info(&pdev->dev, + "power down trigger input will not be used\n"); + ltc2952_poweroff_start_wde(data); } return 0; +} -err_io: - for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) - if (ltc2952_data->gpio[i]) - gpiod_put(ltc2952_data->gpio[i]); +static int ltc2952_poweroff_notify_panic(struct notifier_block *nb, + unsigned long code, void *unused) +{ + struct ltc2952_poweroff *data = to_ltc2952(nb, panic_notifier); - return ret; + data->kernel_panic = true; + return NOTIFY_DONE; } static int ltc2952_poweroff_probe(struct platform_device *pdev) { int ret; + struct ltc2952_poweroff *data; if (pm_power_off) { dev_err(&pdev->dev, "pm_power_off already registered"); return -EBUSY; } - ltc2952_data = kzalloc(sizeof(*ltc2952_data), GFP_KERNEL); - if (!ltc2952_data) + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) return -ENOMEM; - ltc2952_data->dev = &pdev->dev; + data->dev = &pdev->dev; + platform_set_drvdata(pdev, data); ret = ltc2952_poweroff_init(pdev); if (ret) - goto err; + return ret; - pm_power_off = <c2952_poweroff_kill; + /* TODO: remove ltc2952_data */ + ltc2952_data = data; + pm_power_off = ltc2952_poweroff_kill; + data->panic_notifier.notifier_call = ltc2952_poweroff_notify_panic; + atomic_notifier_chain_register(&panic_notifier_list, + &data->panic_notifier); dev_info(&pdev->dev, "probe successful\n"); return 0; - -err: - kfree(ltc2952_data); - return ret; } static int ltc2952_poweroff_remove(struct platform_device *pdev) { - unsigned int i; + struct ltc2952_poweroff *data = platform_get_drvdata(pdev); pm_power_off = NULL; - - if (ltc2952_data) { - free_irq(ltc2952_data->virq, ltc2952_data); - - for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) - gpiod_put(ltc2952_data->gpio[i]); - - kfree(ltc2952_data); - } - + hrtimer_cancel(&data->timer_trigger); + hrtimer_cancel(&data->timer_wde); + atomic_notifier_chain_unregister(&panic_notifier_list, + &data->panic_notifier); return 0; } @@ -344,41 +328,9 @@ static struct platform_driver ltc2952_poweroff_driver = { .name = "ltc2952-poweroff", .of_match_table = of_ltc2952_poweroff_match, }, - .suspend = ltc2952_poweroff_suspend, - .resume = ltc2952_poweroff_resume, -}; - -static int ltc2952_poweroff_notify_panic(struct notifier_block *nb, - unsigned long code, void *unused) -{ - ltc2952_poweroff_panic = 1; - return NOTIFY_DONE; -} - -static struct notifier_block ltc2952_poweroff_panic_nb = { - .notifier_call = ltc2952_poweroff_notify_panic, }; -static int __init ltc2952_poweroff_platform_init(void) -{ - ltc2952_poweroff_panic = 0; - - atomic_notifier_chain_register(&panic_notifier_list, - <c2952_poweroff_panic_nb); - - return platform_driver_register(<c2952_poweroff_driver); -} - -static void __exit ltc2952_poweroff_platform_exit(void) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - <c2952_poweroff_panic_nb); - - platform_driver_unregister(<c2952_poweroff_driver); -} - -module_init(ltc2952_poweroff_platform_init); -module_exit(ltc2952_poweroff_platform_exit); +module_platform_driver(ltc2952_poweroff_driver); MODULE_AUTHOR("René Moll <rene.moll@xsens.com>"); MODULE_DESCRIPTION("LTC PowerPath power-off driver"); diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c index f46f2c2e4648..41b22c4d5236 100644 --- a/drivers/power/reset/restart-poweroff.c +++ b/drivers/power/reset/restart-poweroff.c @@ -16,7 +16,6 @@ #include <linux/of_platform.h> #include <linux/module.h> #include <linux/reboot.h> -#include <asm/system_misc.h> static void restart_poweroff_do_poweroff(void) { diff --git a/drivers/power/reset/rmobile-reset.c b/drivers/power/reset/rmobile-reset.c new file mode 100644 index 000000000000..e6569df76941 --- /dev/null +++ b/drivers/power/reset/rmobile-reset.c @@ -0,0 +1,91 @@ +/* + * Renesas R-Mobile Reset Driver + * + * Copyright (C) 2014 Glider bvba + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/reboot.h> + +/* SYSC Register Bank 2 */ +#define RESCNT2 0x20 /* Reset Control Register 2 */ + +/* Reset Control Register 2 */ +#define RESCNT2_PRES 0x80000000 /* Soft power-on reset */ + +static void __iomem *sysc_base2; + +static int rmobile_reset_handler(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + pr_debug("%s %lu\n", __func__, mode); + + /* Let's assume we have acquired the HPB semaphore */ + writel(RESCNT2_PRES, sysc_base2 + RESCNT2); + + return NOTIFY_DONE; +} + +static struct notifier_block rmobile_reset_nb = { + .notifier_call = rmobile_reset_handler, + .priority = 192, +}; + +static int rmobile_reset_probe(struct platform_device *pdev) +{ + int error; + + sysc_base2 = of_iomap(pdev->dev.of_node, 1); + if (!sysc_base2) + return -ENODEV; + + error = register_restart_handler(&rmobile_reset_nb); + if (error) { + dev_err(&pdev->dev, + "cannot register restart handler (err=%d)\n", error); + goto fail_unmap; + } + + return 0; + +fail_unmap: + iounmap(sysc_base2); + return error; +} + +static int rmobile_reset_remove(struct platform_device *pdev) +{ + unregister_restart_handler(&rmobile_reset_nb); + iounmap(sysc_base2); + return 0; +} + +static const struct of_device_id rmobile_reset_of_match[] = { + { .compatible = "renesas,sysc-rmobile", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rmobile_reset_of_match); + +static struct platform_driver rmobile_reset_driver = { + .probe = rmobile_reset_probe, + .remove = rmobile_reset_remove, + .driver = { + .name = "rmobile_reset", + .of_match_table = rmobile_reset_of_match, + }, +}; + +module_platform_driver(rmobile_reset_driver); + +MODULE_DESCRIPTION("Renesas R-Mobile Reset Driver"); +MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/st-poweroff.c b/drivers/power/reset/st-poweroff.c index a0acf25ee2a2..27383de9caa8 100644 --- a/drivers/power/reset/st-poweroff.c +++ b/drivers/power/reset/st-poweroff.c @@ -15,10 +15,9 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/mfd/syscon.h> +#include <linux/reboot.h> #include <linux/regmap.h> -#include <asm/system_misc.h> - struct reset_syscfg { struct regmap *regmap; /* syscfg used for reset */ @@ -75,7 +74,8 @@ static struct reset_syscfg stid127_reset = { static struct reset_syscfg *st_restart_syscfg; -static void st_restart(enum reboot_mode reboot_mode, const char *cmd) +static int st_restart(struct notifier_block *this, unsigned long mode, + void *cmd) { /* reset syscfg updated */ regmap_update_bits(st_restart_syscfg->regmap, @@ -88,8 +88,15 @@ static void st_restart(enum reboot_mode reboot_mode, const char *cmd) st_restart_syscfg->offset_rst_msk, st_restart_syscfg->mask_rst_msk, 0); + + return NOTIFY_DONE; } +static struct notifier_block st_restart_nb = { + .notifier_call = st_restart, + .priority = 192, +}; + static struct of_device_id st_reset_of_match[] = { { .compatible = "st,stih415-restart", @@ -126,9 +133,7 @@ static int st_reset_probe(struct platform_device *pdev) return PTR_ERR(st_restart_syscfg->regmap); } - arm_pm_restart = st_restart; - - return 0; + return register_restart_handler(&st_restart_nb); } static struct platform_driver st_reset_driver = { diff --git a/drivers/power/reset/sun6i-reboot.c b/drivers/power/reset/sun6i-reboot.c deleted file mode 100644 index af2cd7ff2fe8..000000000000 --- a/drivers/power/reset/sun6i-reboot.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 9dfc9cee3232..be12d9b92957 100644 --- a/drivers/power/reset/vexpress-poweroff.c +++ b/drivers/power/reset/vexpress-poweroff.c @@ -111,23 +111,20 @@ static int _vexpress_register_restart_handler(struct device *dev) 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; int ret = 0; - if (match) - func = (enum vexpress_reset_func)match->data; - else - func = pdev->id_entry->driver_data; + if (!match) + return -EINVAL; regmap = devm_regmap_init_vexpress_config(&pdev->dev); if (IS_ERR(regmap)) return PTR_ERR(regmap); dev_set_drvdata(&pdev->dev, regmap); - switch (func) { + switch ((enum vexpress_reset_func)match->data) { case FUNC_SHUTDOWN: vexpress_power_off_device = &pdev->dev; pm_power_off = vexpress_power_off; @@ -144,20 +141,12 @@ static int vexpress_reset_probe(struct platform_device *pdev) return ret; } -static const struct platform_device_id vexpress_reset_id_table[] = { - { .name = "vexpress-reset", .driver_data = FUNC_RESET, }, - { .name = "vexpress-shutdown", .driver_data = FUNC_SHUTDOWN, }, - { .name = "vexpress-reboot", .driver_data = FUNC_REBOOT, }, - {} -}; - static struct platform_driver vexpress_reset_driver = { .probe = vexpress_reset_probe, .driver = { .name = "vexpress-reset", .of_match_table = vexpress_reset_of_match, }, - .id_table = vexpress_reset_id_table, }; static int __init vexpress_reset_init(void) diff --git a/drivers/power/rt5033_battery.c b/drivers/power/rt5033_battery.c new file mode 100644 index 000000000000..7b898f41c595 --- /dev/null +++ b/drivers/power/rt5033_battery.c @@ -0,0 +1,177 @@ +/* + * Fuel gauge driver for Richtek RT5033 + * + * Copyright (C) 2014 Samsung Electronics, Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.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 bythe Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/rt5033-private.h> +#include <linux/mfd/rt5033.h> + +static int rt5033_battery_get_capacity(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + u32 msb; + + regmap_read(battery->regmap, RT5033_FUEL_REG_SOC_H, &msb); + + return msb; +} + +static int rt5033_battery_get_present(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + u32 val; + + regmap_read(battery->regmap, RT5033_FUEL_REG_CONFIG_L, &val); + + return (val & RT5033_FUEL_BAT_PRESENT) ? true : false; +} + +static int rt5033_battery_get_watt_prop(struct i2c_client *client, + enum power_supply_property psp) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + unsigned int regh, regl; + int ret; + u32 msb, lsb; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + regh = RT5033_FUEL_REG_VBAT_H; + regl = RT5033_FUEL_REG_VBAT_L; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + regh = RT5033_FUEL_REG_AVG_VOLT_H; + regl = RT5033_FUEL_REG_AVG_VOLT_L; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + regh = RT5033_FUEL_REG_OCV_H; + regl = RT5033_FUEL_REG_OCV_L; + break; + default: + return -EINVAL; + } + + regmap_read(battery->regmap, regh, &msb); + regmap_read(battery->regmap, regl, &lsb); + + ret = ((msb << 4) + (lsb >> 4)) * 1250 / 1000; + + return ret; +} + +static int rt5033_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt5033_battery *battery = container_of(psy, + struct rt5033_battery, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = rt5033_battery_get_watt_prop(battery->client, + psp); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = rt5033_battery_get_present(battery->client); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = rt5033_battery_get_capacity(battery->client); + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property rt5033_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static struct regmap_config rt5033_battery_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RT5033_FUEL_REG_END, +}; + +static int rt5033_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct rt5033_battery *battery; + u32 ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + battery = devm_kzalloc(&client->dev, sizeof(*battery), GFP_KERNEL); + if (!battery) + return -EINVAL; + + battery->client = client; + battery->regmap = devm_regmap_init_i2c(client, + &rt5033_battery_regmap_config); + if (IS_ERR(battery->regmap)) { + dev_err(&client->dev, "Failed to initialize regmap\n"); + return -EINVAL; + } + + i2c_set_clientdata(client, battery); + + battery->psy.name = "rt5033-battery"; + battery->psy.type = POWER_SUPPLY_TYPE_BATTERY; + battery->psy.get_property = rt5033_battery_get_property; + battery->psy.properties = rt5033_battery_props; + battery->psy.num_properties = ARRAY_SIZE(rt5033_battery_props); + + ret = power_supply_register(&client->dev, &battery->psy); + if (ret) { + dev_err(&client->dev, "Failed to register power supply\n"); + return ret; + } + + return 0; +} + +static int rt5033_battery_remove(struct i2c_client *client) +{ + struct rt5033_battery *battery = i2c_get_clientdata(client); + + power_supply_unregister(&battery->psy); + + return 0; +} + +static const struct i2c_device_id rt5033_battery_id[] = { + { "rt5033-battery", }, + { } +}; +MODULE_DEVICE_TABLE(platform, rt5033_battery_id); + +static struct i2c_driver rt5033_battery_driver = { + .driver = { + .name = "rt5033-battery", + }, + .probe = rt5033_battery_probe, + .remove = rt5033_battery_remove, + .id_table = rt5033_battery_id, +}; +module_i2c_driver(rt5033_battery_driver); + +MODULE_DESCRIPTION("Richtek RT5033 fuel gauge driver"); +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c index 0152f35dca5c..f26b1fa00fe1 100644 --- a/drivers/power/test_power.c +++ b/drivers/power/test_power.c @@ -21,6 +21,13 @@ #include <linux/delay.h> #include <linux/vermagic.h> +enum test_power_id { + TEST_AC, + TEST_BATTERY, + TEST_USB, + TEST_POWER_NUM, +}; + static int ac_online = 1; static int usb_online = 1; static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING; @@ -147,7 +154,7 @@ static char *test_power_ac_supplied_to[] = { }; static struct power_supply test_power_supplies[] = { - { + [TEST_AC] = { .name = "test_ac", .type = POWER_SUPPLY_TYPE_MAINS, .supplied_to = test_power_ac_supplied_to, @@ -155,13 +162,15 @@ static struct power_supply test_power_supplies[] = { .properties = test_power_ac_props, .num_properties = ARRAY_SIZE(test_power_ac_props), .get_property = test_power_get_ac_property, - }, { + }, + [TEST_BATTERY] = { .name = "test_battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = test_power_battery_props, .num_properties = ARRAY_SIZE(test_power_battery_props), .get_property = test_power_get_battery_property, - }, { + }, + [TEST_USB] = { .name = "test_usb", .type = POWER_SUPPLY_TYPE_USB, .supplied_to = test_power_ac_supplied_to, @@ -178,6 +187,8 @@ static int __init test_power_init(void) int i; int ret; + BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_supplies)); + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) { ret = power_supply_register(NULL, &test_power_supplies[i]); if (ret) { @@ -309,7 +320,7 @@ static inline void signal_power_supply_changed(struct power_supply *psy) static int param_set_ac_online(const char *key, const struct kernel_param *kp) { ac_online = map_get_value(map_ac_online, key, ac_online); - signal_power_supply_changed(&test_power_supplies[0]); + signal_power_supply_changed(&test_power_supplies[TEST_AC]); return 0; } @@ -322,7 +333,7 @@ static int param_get_ac_online(char *buffer, const struct kernel_param *kp) static int param_set_usb_online(const char *key, const struct kernel_param *kp) { usb_online = map_get_value(map_ac_online, key, usb_online); - signal_power_supply_changed(&test_power_supplies[2]); + signal_power_supply_changed(&test_power_supplies[TEST_USB]); return 0; } @@ -336,7 +347,7 @@ static int param_set_battery_status(const char *key, const struct kernel_param *kp) { battery_status = map_get_value(map_status, key, battery_status); - signal_power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[TEST_BATTERY]); return 0; } @@ -350,7 +361,7 @@ static int param_set_battery_health(const char *key, const struct kernel_param *kp) { battery_health = map_get_value(map_health, key, battery_health); - signal_power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[TEST_BATTERY]); return 0; } @@ -364,7 +375,7 @@ static int param_set_battery_present(const char *key, const struct kernel_param *kp) { battery_present = map_get_value(map_present, key, battery_present); - signal_power_supply_changed(&test_power_supplies[0]); + signal_power_supply_changed(&test_power_supplies[TEST_AC]); return 0; } @@ -380,7 +391,7 @@ static int param_set_battery_technology(const char *key, { battery_technology = map_get_value(map_technology, key, battery_technology); - signal_power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[TEST_BATTERY]); return 0; } @@ -401,7 +412,7 @@ static int param_set_battery_capacity(const char *key, return -EINVAL; battery_capacity = tmp; - signal_power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[TEST_BATTERY]); return 0; } @@ -416,7 +427,7 @@ static int param_set_battery_voltage(const char *key, return -EINVAL; battery_voltage = tmp; - signal_power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[TEST_BATTERY]); return 0; } |