blob: 3bc94e19a6faa238495061f014122d6fd9e83b0b [file] [log] [blame]
/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/wakelock.h>
#define BATT_DRV_NAME "lge_battery"
#define pr_bm(reason, format, ...) \
do { \
if (debug_mask & (reason)) \
pr_info("%s: %s: " format, BATT_DRV_NAME, \
__func__, ##__VA_ARGS__); \
else \
pr_debug("%s: %s: " format, BATT_DRV_NAME, \
__func__, ##__VA_ARGS__); \
} while (0)
#define NORM_VOLT 4400000
#define LIM_VOLT 4100000
#define PARALLEL_VOLT 4450000
#define CHG_CURRENT_MAX 3550000
#define LCD_ON_CURRENT 1000000
#define WATCH_DELAY 30000
#define DEMO_MODE_MAX 35
#define DEMO_MODE_MIN 30
#define DEFAULT_CHARGE_STOP_LEVEL 100
#define DEFAULT_CHARGE_START_LEVEL 0
enum debug_mask_print {
ASSERT = BIT(0),
ERROR = BIT(1),
INTERRUPT = BIT(2),
REGISTER = BIT(3),
MISC = BIT(4),
VERBOSE = BIT(5),
};
enum bm_vote_reason {
BM_REASON_DEFAULT,
BM_REASON_LCD,
BM_REASON_STEP,
BM_REASON_THERM,
BM_REASON_DEMO,
BM_REASON_MAX,
};
enum bm_therm_states {
BM_HEALTH_COLD,
BM_HEALTH_COOL,
BM_HEALTH_GOOD,
BM_HEALTH_WARM,
BM_HEALTH_HOT,
BM_HEALTH_WARM_LIM,
BM_HEALTH_MAX,
};
enum bm_batt_maker {
BM_BATT_LGC,
BM_BATT_TOCAD,
BM_BATT_MAX,
};
struct battery_manager {
struct device *dev;
struct power_supply *batt_psy;
struct power_supply *bms_psy;
struct power_supply *usb_psy;
struct power_supply *pl_psy;
struct notifier_block ps_nb;
struct notifier_block fb_nb;
struct work_struct bm_batt_update;
struct work_struct bm_usb_update;
struct work_struct bm_fb_update;
struct delayed_work bm_watch;
struct wake_lock chg_wake_lock;
struct mutex work_lock;
enum bm_therm_states therm_stat;
int batt_id;
int chg_present;
int chg_status;
int batt_soc;
int fb_state;
int bm_vote_fcc_reason;
int bm_vote_fcc_value;
int demo_iusb;
int demo_ibat;
int demo_enable;
int sc_status;
bool bm_active;
};
struct bm_therm_table {
int min;
int max;
int cur;
};
static struct bm_therm_table therm_table[BM_HEALTH_MAX] = {
{ INT_MIN, 20, 0},
{ 0, 220, 710000},
{ 200, 450, -EINVAL},
{ 430, 550, 710000},
{ 530, INT_MAX, 0},
{ 430, 550, 0},
};
static int bm_vote_fcc_table[BM_REASON_MAX] = {
CHG_CURRENT_MAX,
-EINVAL,
-EINVAL,
-EINVAL,
-EINVAL,
};
struct bm_step_table {
int cur;
int volt;
};
static struct bm_step_table LGC_step_table[] = {
{ -EINVAL, 4200000},
{ 2400000, INT_MAX},
};
static struct bm_step_table TOCAD_step_table[] = {
{ -EINVAL, 4200000},
{ 3000000, 4350000},
{ 2400000, INT_MAX},
};
struct bm_batt_id_table {
int min;
int max;
int step_max;
struct bm_step_table *step_table;
};
static struct bm_batt_id_table valid_batt_id[BM_BATT_MAX] = {
{ 16000, 24000, 1, LGC_step_table},
{ 44800, 67200, 2, TOCAD_step_table},
};
static int debug_mask = ERROR | INTERRUPT | MISC | VERBOSE;
static int demo_mode;
static int charge_stop_level = DEFAULT_CHARGE_STOP_LEVEL;
static int charge_start_level = DEFAULT_CHARGE_START_LEVEL;
static int bm_get_property(struct power_supply *psy,
enum power_supply_property prop, int *value)
{
union power_supply_propval val = {0, };
int rc = 0;
if (!psy) {
pr_bm(ERROR, "Couldn't get psy\n");
return -EINVAL;
}
rc = power_supply_get_property(psy, prop, &val);
if (rc < 0) {
pr_bm(ERROR, "Couldn't get property %d, rc=%d\n", prop, rc);
return rc;
}
*value = val.intval;
return rc;
}
static int bm_set_property(struct power_supply *psy,
enum power_supply_property prop, int value)
{
union power_supply_propval val = {0, };
int rc = 0;
if (!psy) {
pr_bm(ERROR, "Couldn't get psy\n");
return -EINVAL;
}
val.intval = value;
rc = power_supply_set_property(psy, prop, &val);
if (rc < 0)
pr_bm(ERROR, "Couldn't set property %d, rc=%d\n", prop, rc);
return rc;
}
static int battery_power_supply_changed(void)
{
struct power_supply *batt_psy;
batt_psy = power_supply_get_by_name("battery");
if (!batt_psy) {
pr_bm(ERROR, "Couldn't get batt_psy\n");
return -ENODEV;
}
power_supply_changed(batt_psy);
return 0;
}
static int bm_vote_fcc_update(struct battery_manager *bm)
{
int fcc = INT_MAX;
int reason = -EINVAL;
int i, rc = 0;
for (i = 0; i < BM_REASON_MAX; i++) {
if (bm_vote_fcc_table[i] == -EINVAL)
continue;
if (fcc > bm_vote_fcc_table[i]) {
fcc = bm_vote_fcc_table[i];
reason = i;
}
}
if (reason != bm->bm_vote_fcc_reason || fcc != bm->bm_vote_fcc_value) {
if (fcc != bm->bm_vote_fcc_value) {
rc = bm_set_property(
bm->batt_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
fcc);
if (rc < 0) {
pr_bm(ERROR,
"Couldn't set current, rc=%d\n", rc);
return rc;
}
rc = bm_set_property(
bm->batt_psy,
POWER_SUPPLY_PROP_CHARGE_DISABLE,
(fcc == 0 ? 1 : 0));
if (rc < 0) {
pr_bm(ERROR,
"Couldn't charge disable, rc=%d\n", rc);
return rc;
}
}
bm->bm_vote_fcc_reason = reason;
bm->bm_vote_fcc_value = fcc;
pr_bm(MISC, "vote id[%d], set cur[%d]\n", reason, fcc);
}
return rc;
}
static int bm_vote_fcc(struct battery_manager *bm, int reason, int fcc)
{
int rc = 0;
bm_vote_fcc_table[reason] = fcc;
rc = bm_vote_fcc_update(bm);
if (rc < 0) {
pr_bm(ERROR, "Couldn't vote id[%d] set cur[%d], rc=%d\n",
reason, fcc, rc);
bm_vote_fcc_table[reason] = -EINVAL;
}
return rc;
}
static int bm_vote_fcc_get(struct battery_manager *bm)
{
if (bm->bm_vote_fcc_reason == -EINVAL)
return -EINVAL;
return bm_vote_fcc_table[bm->bm_vote_fcc_reason];
}
void bm_check_demo_mode(struct battery_manager *bm)
{
int rc = 0;
int before_demo_iusb = bm->demo_iusb;
int before_demo_ibat = bm->demo_ibat;
if (!demo_mode || !bm->bm_active) {
bm->demo_iusb = 1;
bm->demo_ibat = 1;
} else {
if (bm->batt_soc > charge_stop_level) {
bm->demo_iusb = 0;
bm->demo_ibat = 0;
} else if (bm->batt_soc >= charge_stop_level) {
bm->demo_iusb = 1;
bm->demo_ibat = 0;
} else if (bm->batt_soc <= charge_start_level) {
bm->demo_iusb = 1;
bm->demo_ibat = 1;
}
}
if (bm->demo_ibat != before_demo_ibat) {
if (!bm->demo_ibat)
rc = bm_vote_fcc(bm, BM_REASON_DEMO, 0);
else
rc = bm_vote_fcc(bm, BM_REASON_DEMO, -EINVAL);
if (rc < 0) {
bm->demo_iusb = before_demo_iusb;
bm->demo_ibat = before_demo_ibat;
pr_bm(ERROR, "Couldn't set ibat for demo rc=%d\n", rc);
return;
}
}
if (bm->demo_iusb != before_demo_iusb) {
if (!bm->demo_iusb)
rc = bm_set_property(bm->batt_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND,
1);
else
rc = bm_set_property(bm->batt_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND,
0);
if (rc < 0) {
bm->demo_iusb = before_demo_iusb;
pr_bm(ERROR, "Couldn't set iusb for demo rc=%d\n", rc);
return;
}
}
bm->demo_enable = demo_mode;
}
void bm_check_therm_charging(struct battery_manager *bm,
int batt_temp, int batt_volt)
{
enum bm_therm_states stat;
int i, rc = 0;
if (bm->therm_stat == BM_HEALTH_WARM_LIM)
stat = BM_HEALTH_WARM;
else
stat = bm->therm_stat;
for (i = 0; i < BM_HEALTH_MAX - 1; i++) {
if (batt_temp < therm_table[stat].min)
stat--;
else if (batt_temp >= therm_table[stat].max)
stat++;
else
break;
}
if (stat == BM_HEALTH_WARM && batt_volt >= LIM_VOLT) {
stat = BM_HEALTH_WARM_LIM;
}
if (bm->therm_stat != stat) {
pr_bm(MISC, "STATE[%d->%d] TEMP[%d] CUR[%d] VOL[%d]\n",
bm->therm_stat, stat, batt_temp,
therm_table[stat].cur, batt_volt);
rc = bm_vote_fcc(bm, BM_REASON_THERM, therm_table[stat].cur);
if (rc < 0) {
pr_bm(ERROR, "Couldn't set ibat current rc=%d\n", rc);
return;
}
bm->therm_stat = stat;
}
}
void bm_check_step_charging(struct battery_manager *bm, int volt)
{
int rc, stat;
if (!bm->bm_active && bm->sc_status) {
rc = bm_vote_fcc(bm, BM_REASON_STEP, -EINVAL);
if (rc < 0) {
pr_bm(ERROR,
"Couldn't set ibat curr rc=%d\n", rc);
return;
}
bm->sc_status = 0;
return;
}
for (stat = bm->sc_status;
stat < valid_batt_id[bm->batt_id].step_max; stat++) {
if (volt < valid_batt_id[bm->batt_id].step_table[stat].volt)
break;
}
if (bm->sc_status != stat) {
pr_bm(MISC, "STATE[%d->%d] CUR[%d] VOL[%d]\n",
bm->sc_status, stat,
valid_batt_id[bm->batt_id].step_table[stat].cur, volt);
rc = bm_vote_fcc(bm, BM_REASON_STEP,
valid_batt_id[bm->batt_id].step_table[stat].cur);
if (rc < 0) {
pr_bm(ERROR, "Couldn't set ibat curr rc=%d\n", rc);
return;
}
bm->sc_status = stat;
}
}
static void bm_check_status(struct battery_manager *bm)
{
int rc, vbus_out = 0;
int chg_active = bm->bm_active;
rc = bm_get_property(bm->usb_psy,
POWER_SUPPLY_PROP_USE_EXTERNAL_VBUS_OUTPUT,
&vbus_out);
if (rc < 0)
pr_bm(ERROR, "Couldn't get use_external_vbus_output=%d\n", rc);
if (!bm->chg_present || (bm->chg_present && vbus_out) ||
(bm->chg_present && bm->chg_status == POWER_SUPPLY_STATUS_FULL)) {
if (wake_lock_active(&bm->chg_wake_lock)) {
bm->bm_active = false;
wake_unlock(&bm->chg_wake_lock);
}
} else if (bm->chg_present &&
bm->chg_status != POWER_SUPPLY_STATUS_FULL) {
if (!vbus_out && !wake_lock_active(&bm->chg_wake_lock)) {
bm->bm_active = true;
wake_lock(&bm->chg_wake_lock);
}
}
if (bm->bm_active != chg_active)
pr_bm(MISC, "wake_%s: present[%d] chg_state[%d] vbus[%d]",
bm->bm_active ? "locked" : "unlocked", bm->chg_present,
bm->chg_status, vbus_out);
}
static void bm_watch_work(struct work_struct *work)
{
struct battery_manager *bm = container_of(work,
struct battery_manager,
bm_watch.work);
int rc, batt_volt, batt_temp, ibat_max = 0;
mutex_lock(&bm->work_lock);
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, &batt_volt);
if (rc < 0) {
pr_bm(ERROR, "Couldn't do bm_check_step_charging=%d\n", rc);
goto error;
}
if (bm->bm_active)
bm_check_step_charging(bm, batt_volt);
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_TEMP, &batt_temp);
if (rc < 0) {
pr_bm(ERROR, "Couldn't do bm_check_therm_charging=%d\n", rc);
goto error;
}
bm_check_therm_charging(bm, batt_temp, batt_volt);
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&ibat_max);
pr_bm(VERBOSE, "PRESENT:%d, CHG_STAT:%d, THM_STAT:%d, " \
"BAT_TEMP:%d, BAT_VOLT:%d, VOTE_CUR:%d, " \
"SET_CUR:%d \n",
bm->chg_present, bm->chg_status, bm->therm_stat,
batt_temp, batt_volt, bm_vote_fcc_get(bm),
ibat_max);
error:
mutex_unlock(&bm->work_lock);
if (bm->therm_stat >= BM_HEALTH_WARM)
schedule_delayed_work(&bm->bm_watch,
msecs_to_jiffies(WATCH_DELAY / 3));
else
schedule_delayed_work(&bm->bm_watch,
msecs_to_jiffies(WATCH_DELAY));
}
static void bm_batt_update_work(struct work_struct *work)
{
struct battery_manager *bm = container_of(work,
struct battery_manager,
bm_batt_update);
int rc, batt_soc = bm->batt_soc;
mutex_lock(&bm->work_lock);
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_STATUS, &bm->chg_status);
if (rc < 0)
goto error;
bm_check_status(bm);
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &bm->batt_soc);
if (rc < 0)
goto error;
if ((bm->demo_enable != demo_mode) ||
(demo_mode && (bm->batt_soc != batt_soc)))
bm_check_demo_mode(bm);
error:
mutex_unlock(&bm->work_lock);
}
static void bm_usb_update_work(struct work_struct *work)
{
struct battery_manager *bm = container_of(work,
struct battery_manager,
bm_usb_update);
int rc, chg_active = bm->bm_active;
mutex_lock(&bm->work_lock);
rc = bm_get_property(bm->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &bm->chg_present);
if (rc < 0)
goto error;
bm_check_status(bm);
if (bm->bm_active != chg_active) {
if (!bm->bm_active) {
bm_check_step_charging(bm, 0);
bm_check_therm_charging(bm, 250, 0);
}
if (bm->demo_enable)
bm_check_demo_mode(bm);
}
error:
mutex_unlock(&bm->work_lock);
}
static int bm_ps_notifier_call(struct notifier_block *nb,
unsigned long ev, void *v)
{
struct power_supply *psy = v;
struct battery_manager *bm = container_of(nb,
struct battery_manager,
ps_nb);
if (!strcmp(psy->desc->name, "battery")) {
if (!bm->batt_psy)
bm->batt_psy = psy;
if (ev == PSY_EVENT_PROP_CHANGED && bm->batt_psy)
schedule_work(&bm->bm_batt_update);
}
if (!strcmp(psy->desc->name, "usb")) {
if (!bm->usb_psy)
bm->usb_psy = psy;
if (ev == PSY_EVENT_PROP_CHANGED && bm->usb_psy)
schedule_work(&bm->bm_usb_update);
}
return NOTIFY_OK;
}
static int bm_ps_register_notifier(struct battery_manager *bm)
{
int rc = 0;
bm->ps_nb.notifier_call = bm_ps_notifier_call;
rc = power_supply_reg_notifier(&bm->ps_nb);
if (rc < 0)
pr_bm(ERROR, "Couldn't register bm notifier = %d", rc);
return rc;
}
static void bm_fb_update_work(struct work_struct *work)
{
struct battery_manager *bm = container_of(work,
struct battery_manager,
bm_fb_update);
mutex_lock(&bm->work_lock);
if (!(bm->fb_state & BL_CORE_FBBLANK))
bm_vote_fcc(bm, BM_REASON_LCD, LCD_ON_CURRENT);
else
bm_vote_fcc(bm, BM_REASON_LCD, -EINVAL);
mutex_unlock(&bm->work_lock);
}
static int bm_fb_notifier_call(struct notifier_block *nb,
unsigned long ev, void *v)
{
struct fb_event *evdata = v;
struct battery_manager *bm = container_of(nb,
struct battery_manager,
fb_nb);
int fb_blank = 0;
if (ev != FB_EVENT_BLANK)
return NOTIFY_OK;
if (evdata && evdata->data) {
fb_blank = *(int *)evdata->data;
switch (fb_blank) {
case FB_BLANK_UNBLANK:
bm->fb_state &= ~BL_CORE_FBBLANK;
break;
case FB_BLANK_NORMAL:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
bm->fb_state |= BL_CORE_FBBLANK;
break;
default:
pr_bm(ERROR, "not used evdata=%d\n", fb_blank);
break;
}
schedule_work(&bm->bm_fb_update);
}
return NOTIFY_OK;
}
static int bm_fb_register_notifier(struct battery_manager *bm)
{
int rc = 0;
bm->fb_nb.notifier_call = bm_fb_notifier_call;
rc = fb_register_client(&bm->fb_nb);
if (rc < 0)
pr_bm(ERROR, "Couldn't register bm notifier = %d\n", rc);
return rc;
}
static int bm_init(struct battery_manager *bm)
{
int i, rc, batt_temp, batt_volt, batt_id = 0;
bm->fb_state = 0;
bm->sc_status = 0;
bm->therm_stat = BM_HEALTH_GOOD;
bm->bm_vote_fcc_reason = -EINVAL;
bm->bm_vote_fcc_value = -EINVAL;
bm->batt_psy = power_supply_get_by_name("battery");
if (!bm->batt_psy) {
pr_bm(ERROR, "Couldn't get batt_psy\n");
return -ENODEV;
}
bm->usb_psy = power_supply_get_by_name("usb");
if (!bm->usb_psy) {
pr_bm(ERROR, "Couldn't get usb_psy\n");
return -ENODEV;
}
bm->pl_psy = power_supply_get_by_name("parallel");
if (!bm->pl_psy) {
pr_bm(ERROR, "Couldn't get pl_psy\n");
return -ENODEV;
}
bm->bms_psy = power_supply_get_by_name("bms");
if (!bm->bms_psy) {
pr_bm(ERROR, "Couldn't get bms_psy\n");
return -ENODEV;
}
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_STATUS, &bm->chg_status);
if (rc < 0)
bm->chg_status = 0;
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_TEMP, &batt_temp);
if (rc < 0)
batt_temp = 25;
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, &batt_volt);
if (rc < 0)
batt_volt = 4000000;
rc = bm_get_property(bm->batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &bm->batt_soc);
if (rc < 0)
bm->batt_soc = 50;
rc = bm_get_property(bm->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &bm->chg_present);
if (rc < 0)
bm->chg_present = 0;
rc = bm_get_property(bm->bms_psy,
POWER_SUPPLY_PROP_RESISTANCE_ID, &batt_id);
if (rc < 0) {
bm->batt_id = BM_BATT_TOCAD;
} else {
for (i = 0; i < BM_BATT_MAX; i++) {
if (valid_batt_id[i].min <= batt_id &&
valid_batt_id[i].max >= batt_id)
break;
}
if (i == BM_BATT_MAX) {
pr_bm(ERROR, "Couldn't get valid battery id\n");
return -EINVAL;
}
bm->batt_id = i;
}
if (bm->chg_present) {
bm->demo_iusb = 1;
bm->demo_ibat = 1;
} else {
bm->demo_iusb = 0;
bm->demo_ibat = 0;
}
rc = bm_set_property(bm->pl_psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
PARALLEL_VOLT);
if (rc < 0)
pr_bm(ERROR, "Couldn't set pl float voltage, rc=%d", rc);
INIT_WORK(&bm->bm_fb_update, bm_fb_update_work);
INIT_WORK(&bm->bm_batt_update, bm_batt_update_work);
INIT_WORK(&bm->bm_usb_update, bm_usb_update_work);
INIT_DELAYED_WORK(&bm->bm_watch, bm_watch_work);
mutex_init(&bm->work_lock);
wake_lock_init(&bm->chg_wake_lock,
WAKE_LOCK_SUSPEND, "bm_wake_lock");
if (bm->chg_present)
bm_check_status(bm);
if (bm->bm_active)
bm_check_therm_charging(bm, batt_temp, batt_volt);
schedule_delayed_work(&bm->bm_watch,
msecs_to_jiffies(WATCH_DELAY));
return 0;
}
static int lge_battery_probe(struct platform_device *pdev)
{
struct battery_manager *bm;
int rc = 0;
bm = devm_kzalloc(&pdev->dev, sizeof(struct battery_manager),
GFP_KERNEL);
if (!bm) {
pr_bm(ERROR, "no memory\n");
return -ENOMEM;
}
bm->dev = &pdev->dev;
rc = bm_init(bm);
if (rc < 0) {
pr_bm(ERROR, "bm_init fail\n");
return rc;
}
platform_set_drvdata(pdev, bm);
rc = bm_ps_register_notifier(bm);
if (rc < 0) {
pr_bm(ERROR, "bm_power_register_notifier fail\n");
goto error;
}
rc = bm_fb_register_notifier(bm);
if (rc < 0) {
pr_bm(ERROR, "bm_fb_register_notifier fail!\n");
goto error;
}
pr_bm(VERBOSE, "Battery manager driver probe success!\n");
return 0;
error:
mutex_destroy(&bm->work_lock);
platform_set_drvdata(pdev, NULL);
return rc;
}
static int lge_battery_suspend(struct device *dev)
{
struct battery_manager *bm = dev_get_drvdata(dev);
if (!bm) {
pr_bm(ERROR, "There is no battery manager\n");
return -ENODEV;
}
cancel_delayed_work_sync(&bm->bm_watch);
return 0;
}
static int lge_battery_resume(struct device *dev)
{
struct battery_manager *bm = dev_get_drvdata(dev);
if (!bm) {
pr_bm(ERROR, "There is no battery manager\n");
return -ENODEV;
}
schedule_delayed_work(&bm->bm_watch, 0);
return 0;
}
static int lge_battery_remove(struct platform_device *pdev)
{
struct battery_manager *bm = dev_get_drvdata(&pdev->dev);
mutex_destroy(&bm->work_lock);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct dev_pm_ops lge_battery_pm_ops = {
.suspend = lge_battery_suspend,
.resume = lge_battery_resume,
};
static struct platform_device lge_battery_pdev = {
.name = BATT_DRV_NAME,
.id = -1,
};
static struct platform_driver lge_battery_driver = {
.probe = lge_battery_probe,
.remove = lge_battery_remove,
.driver = {
.name = BATT_DRV_NAME,
.owner = THIS_MODULE,
.pm = &lge_battery_pm_ops,
},
};
static int __init lge_battery_init(void)
{
int ret;
ret = platform_device_register(&lge_battery_pdev);
if (ret < 0) {
pr_bm(ERROR, "device register fail\n");
return ret;
}
ret = platform_driver_register(&lge_battery_driver);
if (ret < 0) {
pr_bm(ERROR, "driver register fail\n");
platform_device_unregister(&lge_battery_pdev);
return ret;
}
return 0;
}
static void __exit lge_battery_exit(void)
{
platform_device_unregister(&lge_battery_pdev);
platform_driver_unregister(&lge_battery_driver);
}
static int set_demo_mode(const char *val, const struct kernel_param *kp)
{
int rc = 0;
int old_val = demo_mode;
rc = param_set_int(val, kp);
if (rc) {
pr_bm(ERROR, "Unable to set demo mode = %d\n", rc);
return rc;
}
if (demo_mode == old_val)
return 0;
if (demo_mode) {
charge_stop_level = DEMO_MODE_MAX;
charge_start_level = DEMO_MODE_MIN;
} else {
charge_stop_level = DEFAULT_CHARGE_STOP_LEVEL;
charge_start_level = DEFAULT_CHARGE_START_LEVEL;
}
battery_power_supply_changed();
return 0;
}
static struct kernel_param_ops demo_mode_ops = {
.set = set_demo_mode,
.get = param_get_int,
};
module_param_cb(demo_mode, &demo_mode_ops, &demo_mode, 0644);
MODULE_PARM_DESC(demo_mode, "VZW Demo mode <on|off>");
static int set_charge_stop_level(const char *val,
const struct kernel_param *kp)
{
int rc;
int old_val = charge_stop_level;
rc = param_set_int(val, kp);
if (rc) {
pr_bm(ERROR, "Unable to set charge_stop_level: %d\n", rc);
return rc;
}
if (charge_stop_level == old_val)
return 0;
if (charge_stop_level <= charge_start_level) {
charge_stop_level = old_val;
return 0;
}
if ((charge_stop_level == DEFAULT_CHARGE_STOP_LEVEL) &&
(charge_start_level == DEFAULT_CHARGE_START_LEVEL))
demo_mode = 0;
else
demo_mode = 1;
battery_power_supply_changed();
return 0;
}
static struct kernel_param_ops charge_stop_ops = {
.set = set_charge_stop_level,
.get = param_get_int,
};
module_param_cb(charge_stop_level, &charge_stop_ops,
&charge_stop_level, 0644);
static int set_charge_start_level(const char *val,
const struct kernel_param *kp)
{
int rc;
int old_val = charge_start_level;
rc = param_set_int(val, kp);
if (rc) {
pr_bm(ERROR, "Unable to set charge_start_level: %d\n", rc);
return rc;
}
if (charge_start_level == old_val)
return 0;
if (charge_stop_level <= charge_start_level) {
charge_start_level = old_val;
return 0;
}
if ((charge_stop_level == DEFAULT_CHARGE_STOP_LEVEL) &&
(charge_start_level == DEFAULT_CHARGE_START_LEVEL))
demo_mode = 0;
else
demo_mode = 1;
battery_power_supply_changed();
return 0;
}
static struct kernel_param_ops charge_start_ops = {
.set = set_charge_start_level,
.get = param_get_int,
};
module_param_cb(charge_start_level, &charge_start_ops,
&charge_start_level, 0644);
module_init(lge_battery_init);
module_exit(lge_battery_exit);
MODULE_LICENSE("GPL");