Commit 13c07190 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-v3.20' of git://git.infradead.org/battery-2.6

Pull power supply and reset changes from Sebastian Reichel:
 "New drivers:
   - charger driver for Maxim 77693
   - battery gauge driver for LTC 2941/2943
   - battery gauge driver for RT5033
   - reset driver for R-Mobile platforms

  Convert drivers to restart handler framework:
   - arm-versatile
   - at91
   - st-poweroff

  Misc:
   - remove deprecated sun6i reboot driver
   - use alarmtimer instead of rtc in charger-manager
   - misc fixes"

* tag 'for-v3.20' of git://git.infradead.org/battery-2.6: (48 commits)
  power_supply: 88pm860x: Fix leaked power supply on probe fail
  power/reset: restart-poweroff: Remove arm dependencies
  power/reset: st-poweroff: Fix misleading Kconfig description
  power/reset: st-poweroff: Register with kernel restart handler
  power/reset: Remove sun6i reboot driver
  power/reset: at91: Register with kernel restart handler
  power/reset: arm-versatile: Register with kernel restart handler
  power: test_power: Use enum as index for array of supplies
  Add devicetree binding documentation for the LTC2941/LTC2943 driver
  Add LTC2941/LTC2943 Battery Gauge Driver
  power/reset: brcmstb: Add support for old 65nm chips
  power/reset: brcmstb: Use the DT "compatible" string to indicate bit positions
  power/reset: brcmstb: Make the driver buildable on MIPS
  power: charger-manager: Use alarmtimer for battery monitoring in suspend.
  power/reset: at91-poweroff: Fix error handling and other compiler warnings
  bq27x00_battery: Call power_supply_changed only when capacity changed
  bq27x00_battery: fix register offset for bq27425
  power: max14577: Remove SYSFS dependency from Kconfig
  power: bq24190_charger: suppress build warning
  power: reset: Add reset driver for R-Mobile platforms
  ...
parents c5ce28df 24727b45
......@@ -32,3 +32,45 @@ Description:
Valid values:
- 5, 6 or 7 (hours),
- 0: disabled.
What: /sys/class/power_supply/max77693-charger/device/fast_charge_timer
Date: January 2015
KernelVersion: 3.19.0
Contact: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Description:
This entry shows and sets the maximum time the max77693
charger operates in fast-charge mode. When the timer expires
the device will terminate fast-charge mode (charging current
will drop to 0 A) and will trigger interrupt.
Valid values:
- 4 - 16 (hours), step by 2 (rounded down)
- 0: disabled.
What: /sys/class/power_supply/max77693-charger/device/top_off_threshold_current
Date: January 2015
KernelVersion: 3.19.0
Contact: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Description:
This entry shows and sets the charging current threshold for
entering top-off charging mode. When charging current in fast
charge mode drops below this value, the charger will trigger
interrupt and start top-off charging mode.
Valid values:
- 100000 - 200000 (microamps), step by 25000 (rounded down)
- 200000 - 350000 (microamps), step by 50000 (rounded down)
- 0: disabled.
What: /sys/class/power_supply/max77693-charger/device/top_off_timer
Date: January 2015
KernelVersion: 3.19.0
Contact: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Description:
This entry shows and sets the maximum time the max77693
charger operates in top-off charge mode. When the timer expires
the device will terminate top-off charge mode (charging current
will drop to 0 A) and will trigger interrupt.
Valid values:
- 0 - 70 (minutes), step by 10 (rounded down)
......@@ -79,7 +79,9 @@ reboot
Required properties
- compatible
The string property "brcm,brcmstb-reboot".
The string property "brcm,brcmstb-reboot" for 40nm/28nm chips with
the new SYS_CTRL interface, or "brcm,bcm7038-reboot" for 65nm
chips with the old SUN_TOP_CTRL interface.
- syscon
A phandle / integer array that points to the syscon node which describes
......
......@@ -41,6 +41,41 @@ Optional properties:
To get more informations, please refer to documentaion.
[*] refer Documentation/devicetree/bindings/pwm/pwm.txt
- charger : Node configuring the charger driver.
If present, required properties:
- compatible : Must be "maxim,max77693-charger".
Optional properties (if not set, defaults will be used):
- maxim,constant-microvolt : Battery constant voltage in uV. The charger
will operate in fast charge constant current mode till battery voltage
reaches this level. Then the charger will switch to fast charge constant
voltage mode. Also vsys (system voltage) will be set to this value when
DC power is supplied but charger is not enabled.
Valid values: 3650000 - 4400000, step by 25000 (rounded down)
Default: 4200000
- maxim,min-system-microvolt : Minimal system voltage in uV.
Valid values: 3000000 - 3700000, step by 100000 (rounded down)
Default: 3600000
- maxim,thermal-regulation-celsius : Temperature in Celsius for entering
high temperature charging mode. If die temperature exceeds this value
the charging current will be reduced by 105 mA/Celsius.
Valid values: 70, 85, 100, 115
Default: 100
- maxim,battery-overcurrent-microamp : Overcurrent protection threshold
in uA (current from battery to system).
Valid values: 2000000 - 3500000, step by 250000 (rounded down)
Default: 3500000
- maxim,charge-input-threshold-microvolt : Threshold voltage in uV for
triggering input voltage regulation loop. If input voltage decreases
below this value, the input current will be reduced to reach the
threshold voltage.
Valid values: 4300000, 4700000, 4800000, 4900000
Default: 4300000
Example:
max77693@66 {
compatible = "maxim,max77693";
......@@ -73,4 +108,14 @@ Example:
pwms = <&pwm 0 40000 0>;
pwm-names = "haptic";
};
charger {
compatible = "maxim,max77693-charger";
maxim,constant-microvolt = <4200000>;
maxim,min-system-microvolt = <3600000>;
maxim,thermal-regulation-celsius = <75>;
maxim,battery-overcurrent-microamp = <3000000>;
maxim,charge-input-threshold-microvolt = <4300000>;
};
};
binding for LTC2941 and LTC2943 battery gauges
Both the LTC2941 and LTC2943 measure battery capacity.
The LTC2943 is compatible with the LTC2941, it adds voltage and
temperature monitoring, and uses a slightly different conversion
formula for the charge counter.
Required properties:
- compatible: Should contain "ltc2941" or "ltc2943" which also indicates the
type of I2C chip attached.
- reg: The 7-bit I2C address.
- lltc,resistor-sense: The sense resistor value in milli-ohms. Can be a 32-bit
negative value when the battery has been connected to the wrong end of the
resistor.
- lltc,prescaler-exponent: The prescaler exponent as explained in the datasheet.
This determines the range and accuracy of the gauge. The value is programmed
into the chip only if it differs from the current setting. The setting is
lost when the battery is disconnected.
Example from the Topic Miami Florida board:
fuelgauge: ltc2943@64 {
compatible = "ltc2943";
reg = <0x64>;
lltc,resistor-sense = <15>;
lltc,prescaler-exponent = <5>; /* 2^(2*5) = 1024 */
};
Binding for the LTC2952 PowerPath controller
This chip is used to externally trigger a system shut down. Once the trigger has
been sent, the chips' watchdog has to be reset to gracefully shut down.
If the Linux systems decides to shut down it powers off the platform via the
kill signal.
been sent, the chip's watchdog has to be reset to gracefully shut down.
A full powerdown can be triggered via the kill signal.
Required properties:
- compatible: Must contain: "lltc,ltc2952"
- trigger-gpios: phandle + gpio-specifier for the GPIO connected to the
chip's trigger line
- watchdog-gpios: phandle + gpio-specifier for the GPIO connected to the
chip's watchdog line
- kill-gpios: phandle + gpio-specifier for the GPIO connected to the
chip's kill line
Optional properties:
- trigger-gpios: phandle + gpio-specifier for the GPIO connected to the
chip's trigger line. If this property is not set, the
trigger function is ignored and the chip is kept alive
until an explicit kill signal is received
Example:
ltc2952 {
......
......@@ -6165,6 +6165,13 @@ F: Documentation/devicetree/bindings/i2c/max6697.txt
F: drivers/hwmon/max6697.c
F: include/linux/platform_data/max6697.h
MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS
M: Krzysztof Kozlowski <k.kozlowski@samsung.com>
L: linux-pm@vger.kernel.org
S: Supported
F: drivers/power/max14577_charger.c
F: drivers/power/max77693_charger.c
MAXIRADIO FM RADIO RECEIVER DRIVER
M: Hans Verkuil <hverkuil@xs4all.nl>
L: linux-media@vger.kernel.org
......
......@@ -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:
......
......@@ -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
......
......@@ -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
......
......@@ -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 {
......
......@@ -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);
......
......@@ -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;
}
......
......@@ -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 = {
......
......@@ -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);
......
......@@ -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);
......
/*
* 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 = &reg_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(&ltc294x_lock);
idr_remove(&ltc294x_id, info->id);
mutex_unlock(&ltc294x_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(&ltc294x_lock);
ret = idr_alloc(&ltc294x_id, client, 0, 0, GFP_KERNEL);
mutex_unlock(&ltc294x_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(&ltc294x_lock);
idr_remove(&ltc294x_id, num);
mutex_unlock(&ltc294x_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 (&ltc294x_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");
......@@ -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,
......
/*
* 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");
......@@ -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
......@@ -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
......@@ -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,
......@@ -35,6 +41,10 @@ static struct regmap *syscon_regmap;
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;
}
......
......@@ -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);
......
......@@ -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)
......
......@@ -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 = {
......
......@@ -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(&ltc2952_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 = &ltc2952_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 = &ltc2952_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 = &ltc2952_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,
&ltc2952_poweroff_panic_nb);
return platform_driver_register(&ltc2952_poweroff_driver);
}
static void __exit ltc2952_poweroff_platform_exit(void)
{
atomic_notifier_chain_unregister(&panic_notifier_list,
&ltc2952_poweroff_panic_nb);
platform_driver_unregister(&ltc2952_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");
......
......@@ -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)
{
......
/*
* 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");
......@@ -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 = {
......
/*
* 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);
......@@ -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)
......
/*
* 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");
......@@ -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;
}
......
......@@ -461,7 +461,6 @@ struct ab8500_fg;
#ifdef CONFIG_AB8500_BM
extern struct abx500_bm_data ab8500_bm_data;
void ab8500_fg_reinit(void);
void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA);
struct ab8500_btemp *ab8500_btemp_get(void);
int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp);
......
......@@ -143,10 +143,118 @@ enum max77693_pmic_reg {
#define FLASH_INT_FLED1_SHORT BIT(3)
#define FLASH_INT_OVER_CURRENT BIT(4)
/* Fast charge timer in in hours */
#define DEFAULT_FAST_CHARGE_TIMER 4
/* microamps */
#define DEFAULT_TOP_OFF_THRESHOLD_CURRENT 150000
/* minutes */
#define DEFAULT_TOP_OFF_TIMER 30
/* microvolts */
#define DEFAULT_CONSTANT_VOLT 4200000
/* microvolts */
#define DEFAULT_MIN_SYSTEM_VOLT 3600000
/* celsius */
#define DEFAULT_THERMAL_REGULATION_TEMP 100
/* microamps */
#define DEFAULT_BATTERY_OVERCURRENT 3500000
/* microvolts */
#define DEFAULT_CHARGER_INPUT_THRESHOLD_VOLT 4300000
/* MAX77693_CHG_REG_CHG_INT_OK register */
#define CHG_INT_OK_BYP_SHIFT 0
#define CHG_INT_OK_BAT_SHIFT 3
#define CHG_INT_OK_CHG_SHIFT 4
#define CHG_INT_OK_CHGIN_SHIFT 6
#define CHG_INT_OK_DETBAT_SHIFT 7
#define CHG_INT_OK_BYP_MASK BIT(CHG_INT_OK_BYP_SHIFT)
#define CHG_INT_OK_BAT_MASK BIT(CHG_INT_OK_BAT_SHIFT)
#define CHG_INT_OK_CHG_MASK BIT(CHG_INT_OK_CHG_SHIFT)
#define CHG_INT_OK_CHGIN_MASK BIT(CHG_INT_OK_CHGIN_SHIFT)
#define CHG_INT_OK_DETBAT_MASK BIT(CHG_INT_OK_DETBAT_SHIFT)
/* MAX77693_CHG_REG_CHG_DETAILS_00 register */
#define CHG_DETAILS_00_CHGIN_SHIFT 5
#define CHG_DETAILS_00_CHGIN_MASK (0x3 << CHG_DETAILS_00_CHGIN_SHIFT)
/* MAX77693_CHG_REG_CHG_DETAILS_01 register */
#define CHG_DETAILS_01_CHG_SHIFT 0
#define CHG_DETAILS_01_BAT_SHIFT 4
#define CHG_DETAILS_01_TREG_SHIFT 7
#define CHG_DETAILS_01_CHG_MASK (0xf << CHG_DETAILS_01_CHG_SHIFT)
#define CHG_DETAILS_01_BAT_MASK (0x7 << CHG_DETAILS_01_BAT_SHIFT)
#define CHG_DETAILS_01_TREG_MASK BIT(7)
/* MAX77693_CHG_REG_CHG_DETAILS_01/CHG field */
enum max77693_charger_charging_state {
MAX77693_CHARGING_PREQUALIFICATION = 0x0,
MAX77693_CHARGING_FAST_CONST_CURRENT,
MAX77693_CHARGING_FAST_CONST_VOLTAGE,
MAX77693_CHARGING_TOP_OFF,
MAX77693_CHARGING_DONE,
MAX77693_CHARGING_HIGH_TEMP,
MAX77693_CHARGING_TIMER_EXPIRED,
MAX77693_CHARGING_THERMISTOR_SUSPEND,
MAX77693_CHARGING_OFF,
MAX77693_CHARGING_RESERVED,
MAX77693_CHARGING_OVER_TEMP,
MAX77693_CHARGING_WATCHDOG_EXPIRED,
};
/* MAX77693_CHG_REG_CHG_DETAILS_01/BAT field */
enum max77693_charger_battery_state {
MAX77693_BATTERY_NOBAT = 0x0,
/* Dead-battery or low-battery prequalification */
MAX77693_BATTERY_PREQUALIFICATION,
MAX77693_BATTERY_TIMER_EXPIRED,
MAX77693_BATTERY_GOOD,
MAX77693_BATTERY_LOWVOLTAGE,
MAX77693_BATTERY_OVERVOLTAGE,
MAX77693_BATTERY_OVERCURRENT,
MAX77693_BATTERY_RESERVED,
};
/* MAX77693_CHG_REG_CHG_DETAILS_02 register */
#define CHG_DETAILS_02_BYP_SHIFT 0
#define CHG_DETAILS_02_BYP_MASK (0xf << CHG_DETAILS_02_BYP_SHIFT)
/* MAX77693 CHG_CNFG_00 register */
#define CHG_CNFG_00_CHG_MASK 0x1
#define CHG_CNFG_00_BUCK_MASK 0x4
/* MAX77693_CHG_REG_CHG_CNFG_01 register */
#define CHG_CNFG_01_FCHGTIME_SHIFT 0
#define CHG_CNFG_01_CHGRSTRT_SHIFT 4
#define CHG_CNFG_01_PQEN_SHIFT 7
#define CHG_CNFG_01_FCHGTIME_MASK (0x7 << CHG_CNFG_01_FCHGTIME_SHIFT)
#define CHG_CNFG_01_CHGRSTRT_MASK (0x3 << CHG_CNFG_01_CHGRSTRT_SHIFT)
#define CHG_CNFG_01_PQEN_MAKS BIT(CHG_CNFG_01_PQEN_SHIFT)
/* MAX77693_CHG_REG_CHG_CNFG_03 register */
#define CHG_CNFG_03_TOITH_SHIFT 0
#define CHG_CNFG_03_TOTIME_SHIFT 3
#define CHG_CNFG_03_TOITH_MASK (0x7 << CHG_CNFG_03_TOITH_SHIFT)
#define CHG_CNFG_03_TOTIME_MASK (0x7 << CHG_CNFG_03_TOTIME_SHIFT)
/* MAX77693_CHG_REG_CHG_CNFG_04 register */
#define CHG_CNFG_04_CHGCVPRM_SHIFT 0
#define CHG_CNFG_04_MINVSYS_SHIFT 5
#define CHG_CNFG_04_CHGCVPRM_MASK (0x1f << CHG_CNFG_04_CHGCVPRM_SHIFT)
#define CHG_CNFG_04_MINVSYS_MASK (0x7 << CHG_CNFG_04_MINVSYS_SHIFT)
/* MAX77693_CHG_REG_CHG_CNFG_06 register */
#define CHG_CNFG_06_CHGPROT_SHIFT 2
#define CHG_CNFG_06_CHGPROT_MASK (0x3 << CHG_CNFG_06_CHGPROT_SHIFT)
/* MAX77693_CHG_REG_CHG_CNFG_07 register */
#define CHG_CNFG_07_REGTEMP_SHIFT 5
#define CHG_CNFG_07_REGTEMP_MASK (0x3 << CHG_CNFG_07_REGTEMP_SHIFT)
/* MAX77693_CHG_REG_CHG_CNFG_12 register */
#define CHG_CNFG_12_B2SOVRC_SHIFT 0
#define CHG_CNFG_12_VCHGINREG_SHIFT 3
#define CHG_CNFG_12_B2SOVRC_MASK (0x7 << CHG_CNFG_12_B2SOVRC_SHIFT)
#define CHG_CNFG_12_VCHGINREG_MASK (0x3 << CHG_CNFG_12_VCHGINREG_SHIFT)
/* MAX77693 CHG_CNFG_09 Register */
#define CHG_CNFG_09_CHGIN_ILIM_MASK 0x7F
......
......@@ -17,6 +17,7 @@
#include <linux/power_supply.h>
#include <linux/extcon.h>
#include <linux/alarmtimer.h>
enum data_source {
CM_BATTERY_PRESENT,
......@@ -44,29 +45,6 @@ enum cm_event_types {
CM_EVENT_OTHERS,
};
/**
* struct charger_global_desc
* @rtc_name: the name of RTC used to wake up the system from suspend.
* @rtc_only_wakeup:
* If the system is woken up by waekup-sources other than the RTC or
* callbacks, Charger Manager should recognize with
* rtc_only_wakeup() returning false.
* If the RTC given to CM is the only wakeup reason,
* rtc_only_wakeup should return true.
* @assume_timer_stops_in_suspend:
* Assume that the jiffy timer stops in suspend-to-RAM.
* When enabled, CM does not rely on jiffies value in
* suspend_again and assumes that jiffies value does not
* change during suspend.
*/
struct charger_global_desc {
char *rtc_name;
bool (*rtc_only_wakeup)(void);
bool assume_timer_stops_in_suspend;
};
/**
* struct charger_cable
* @extcon_name: the name of extcon device.
......@@ -266,22 +244,14 @@ struct charger_manager {
char psy_name_buf[PSY_NAME_MAX + 1];
struct power_supply charger_psy;
bool status_save_ext_pwr_inserted;
bool status_save_batt;
u64 charging_start_time;
u64 charging_end_time;
};
#ifdef CONFIG_CHARGER_MANAGER
extern int setup_charger_manager(struct charger_global_desc *gd);
extern bool cm_suspend_again(void);
extern void cm_notify_event(struct power_supply *psy,
enum cm_event_types type, char *msg);
#else
static inline int setup_charger_manager(struct charger_global_desc *gd)
{ return 0; }
static inline bool cm_suspend_again(void) { return false; }
static inline void cm_notify_event(struct power_supply *psy,
enum cm_event_types type, char *msg) { }
#endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment