832 lines
21 KiB
C
832 lines
21 KiB
C
/*
|
|
* battery-charger-gauge-comm.c --- Communication between battery charger and
|
|
* battery gauge driver.
|
|
*
|
|
* Copyright (c) 2013-2018, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* Author: Laxman Dewangan <ldewangan@nvidia.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation version 2.
|
|
*
|
|
* This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
|
|
* whether express or implied; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
* 02111-1307, USA
|
|
*/
|
|
|
|
#include <linux/alarmtimer.h>
|
|
#include <linux/module.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/list.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/time.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/power/battery-charger-gauge-comm.h>
|
|
#include <linux/power/reset/system-pmic.h>
|
|
#include <linux/device.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/iio/types.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/types.h>
|
|
|
|
#define JETI_TEMP_COLD 0
|
|
#define JETI_TEMP_COOL 10
|
|
#define JETI_TEMP_WARM 45
|
|
#define JETI_TEMP_HOT 60
|
|
|
|
#define MAX_STR_PRINT 50
|
|
|
|
static DEFINE_MUTEX(charger_gauge_list_mutex);
|
|
static LIST_HEAD(charger_list);
|
|
static LIST_HEAD(gauge_list);
|
|
|
|
struct battery_charger_dev {
|
|
int cell_id;
|
|
char *tz_name;
|
|
struct device *parent_dev;
|
|
struct battery_charging_ops *ops;
|
|
struct list_head list;
|
|
void *drv_data;
|
|
struct delayed_work restart_charging_wq;
|
|
struct delayed_work poll_temp_monitor_wq;
|
|
int polling_time_sec;
|
|
struct thermal_zone_device *battery_tz;
|
|
bool start_monitoring;
|
|
struct wakeup_source charger_wake_lock;
|
|
bool locked;
|
|
struct rtc_device *rtc;
|
|
bool enable_thermal_monitor;
|
|
};
|
|
|
|
struct battery_gauge_dev {
|
|
int cell_id;
|
|
char *tz_name;
|
|
struct device *parent_dev;
|
|
struct battery_gauge_ops *ops;
|
|
struct list_head list;
|
|
void *drv_data;
|
|
struct thermal_zone_device *battery_tz;
|
|
int battery_voltage;
|
|
int battery_capacity;
|
|
const char *bat_curr_channel_name;
|
|
struct iio_channel *bat_current_iio_channel;
|
|
const char *input_power_channel_name;
|
|
struct iio_channel *input_power_iio_channel;
|
|
};
|
|
|
|
struct battery_gauge_dev *bg_temp;
|
|
|
|
static void battery_charger_restart_charging_wq(struct work_struct *work)
|
|
{
|
|
struct battery_charger_dev *bc_dev;
|
|
|
|
bc_dev = container_of(work, struct battery_charger_dev,
|
|
restart_charging_wq.work);
|
|
if (!bc_dev->ops->restart_charging) {
|
|
dev_err(bc_dev->parent_dev,
|
|
"No callback for restart charging\n");
|
|
return;
|
|
}
|
|
bc_dev->ops->restart_charging(bc_dev);
|
|
}
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
static void battery_charger_thermal_monitor_wq(struct work_struct *work)
|
|
{
|
|
struct battery_charger_dev *bc_dev;
|
|
struct device *dev;
|
|
int temperature;
|
|
bool charger_enable_state;
|
|
bool charger_current_half;
|
|
int battery_thersold_voltage;
|
|
int ret;
|
|
|
|
bc_dev = container_of(work, struct battery_charger_dev,
|
|
poll_temp_monitor_wq.work);
|
|
if (!bc_dev->tz_name)
|
|
return;
|
|
|
|
dev = bc_dev->parent_dev;
|
|
if (!bc_dev->battery_tz) {
|
|
bc_dev->battery_tz = thermal_zone_get_zone_by_name(
|
|
bc_dev->tz_name);
|
|
|
|
if (IS_ERR(bc_dev->battery_tz)) {
|
|
dev_info(dev,
|
|
"Battery thermal zone %s is not registered yet\n",
|
|
bc_dev->tz_name);
|
|
schedule_delayed_work(&bc_dev->poll_temp_monitor_wq,
|
|
msecs_to_jiffies(bc_dev->polling_time_sec * 1000));
|
|
bc_dev->battery_tz = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ret = thermal_zone_get_temp(bc_dev->battery_tz, &temperature);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Temperature read failed: %d\n ", ret);
|
|
goto exit;
|
|
}
|
|
temperature = temperature / 1000;
|
|
|
|
charger_enable_state = true;
|
|
charger_current_half = false;
|
|
battery_thersold_voltage = 4250;
|
|
|
|
if (temperature <= JETI_TEMP_COLD || temperature >= JETI_TEMP_HOT) {
|
|
charger_enable_state = false;
|
|
} else if (temperature <= JETI_TEMP_COOL ||
|
|
temperature >= JETI_TEMP_WARM) {
|
|
charger_current_half = true;
|
|
battery_thersold_voltage = 4100;
|
|
}
|
|
|
|
if (bc_dev->ops->thermal_configure)
|
|
bc_dev->ops->thermal_configure(bc_dev, temperature,
|
|
charger_enable_state, charger_current_half,
|
|
battery_thersold_voltage);
|
|
|
|
exit:
|
|
if (bc_dev->start_monitoring)
|
|
schedule_delayed_work(&bc_dev->poll_temp_monitor_wq,
|
|
msecs_to_jiffies(bc_dev->polling_time_sec * 1000));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
int battery_charger_set_current_broadcast(struct battery_charger_dev *bc_dev)
|
|
{
|
|
struct battery_gauge_dev *bg_dev;
|
|
int ret = 0;
|
|
|
|
if (!bc_dev) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(bg_dev, &gauge_list, list) {
|
|
if (bg_dev->cell_id != bc_dev->cell_id)
|
|
continue;
|
|
if (bg_dev->ops && bg_dev->ops->set_current_broadcast)
|
|
ret = bg_dev->ops->set_current_broadcast(bg_dev);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_set_current_broadcast);
|
|
|
|
int battery_charger_thermal_start_monitoring(
|
|
struct battery_charger_dev *bc_dev)
|
|
{
|
|
if (!bc_dev || !bc_dev->polling_time_sec || !bc_dev->tz_name)
|
|
return -EINVAL;
|
|
|
|
bc_dev->start_monitoring = true;
|
|
schedule_delayed_work(&bc_dev->poll_temp_monitor_wq,
|
|
msecs_to_jiffies(bc_dev->polling_time_sec * 1000));
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_thermal_start_monitoring);
|
|
|
|
int battery_charger_thermal_stop_monitoring(
|
|
struct battery_charger_dev *bc_dev)
|
|
{
|
|
if (!bc_dev || !bc_dev->polling_time_sec || !bc_dev->tz_name)
|
|
return -EINVAL;
|
|
|
|
bc_dev->start_monitoring = false;
|
|
cancel_delayed_work(&bc_dev->poll_temp_monitor_wq);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_thermal_stop_monitoring);
|
|
|
|
int battery_charger_acquire_wake_lock(struct battery_charger_dev *bc_dev)
|
|
{
|
|
if (!bc_dev->locked) {
|
|
__pm_stay_awake(&bc_dev->charger_wake_lock);
|
|
bc_dev->locked = true;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_acquire_wake_lock);
|
|
|
|
int battery_charger_release_wake_lock(struct battery_charger_dev *bc_dev)
|
|
{
|
|
if (bc_dev->locked) {
|
|
__pm_relax(&bc_dev->charger_wake_lock);
|
|
bc_dev->locked = false;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_release_wake_lock);
|
|
|
|
int battery_charging_restart(struct battery_charger_dev *bc_dev, int after_sec)
|
|
{
|
|
if (!bc_dev->ops->restart_charging) {
|
|
dev_err(bc_dev->parent_dev,
|
|
"No callback for restart charging\n");
|
|
return -EINVAL;
|
|
}
|
|
if (!after_sec)
|
|
return 0;
|
|
|
|
schedule_delayed_work(&bc_dev->restart_charging_wq,
|
|
msecs_to_jiffies(after_sec * HZ));
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charging_restart);
|
|
|
|
static ssize_t battery_show_max_capacity(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct iio_channel *channel;
|
|
int val, ret;
|
|
|
|
channel = iio_channel_get(dev, "batt_id");
|
|
if (IS_ERR(channel)) {
|
|
dev_err(dev,
|
|
"%s: Failed to get channel batt_id, %ld\n",
|
|
__func__, PTR_ERR(channel));
|
|
return 0;
|
|
}
|
|
|
|
ret = iio_read_channel_raw(channel, &val);
|
|
if (ret < 0) {
|
|
dev_err(dev,
|
|
"%s: Failed to read channel, %d\n",
|
|
__func__, ret);
|
|
return 0;
|
|
}
|
|
|
|
return snprintf(buf, MAX_STR_PRINT, "%d\n", val);
|
|
}
|
|
|
|
static DEVICE_ATTR(battery_max_capacity, S_IRUGO,
|
|
battery_show_max_capacity, NULL);
|
|
|
|
static struct attribute *battery_sysfs_attributes[] = {
|
|
&dev_attr_battery_max_capacity.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group battery_sysfs_attr_group = {
|
|
.attrs = battery_sysfs_attributes,
|
|
};
|
|
|
|
int battery_gauge_get_charging_current(struct battery_charger_dev *bc_dev)
|
|
{
|
|
struct battery_gauge_dev *bg_dev;
|
|
int ret = 0;
|
|
|
|
if (!bc_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(bg_dev, &gauge_list, list) {
|
|
if (bg_dev->cell_id != bc_dev->cell_id)
|
|
continue;
|
|
if (bg_dev->ops && bg_dev->ops->get_battery_current)
|
|
ret = bg_dev->ops->get_battery_current(bg_dev);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_charging_current);
|
|
|
|
int battery_gauge_get_battery_voltage(struct battery_charger_dev *bc_dev)
|
|
{
|
|
struct battery_gauge_dev *bg_dev;
|
|
int ret = 0;
|
|
|
|
if (!bc_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(bg_dev, &gauge_list, list) {
|
|
if (bg_dev->cell_id != bc_dev->cell_id)
|
|
continue;
|
|
if (bg_dev->ops && bg_dev->ops->get_battery_voltage)
|
|
ret = bg_dev->ops->get_battery_voltage(bg_dev);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_battery_voltage);
|
|
|
|
int battery_gauge_get_battery_soc(struct battery_charger_dev *bc_dev)
|
|
{
|
|
struct battery_gauge_dev *bg_dev;
|
|
int ret = 0;
|
|
|
|
if (!bc_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(bg_dev, &gauge_list, list) {
|
|
if (bg_dev->cell_id != bc_dev->cell_id)
|
|
continue;
|
|
if (bg_dev->ops && bg_dev->ops->get_battery_soc)
|
|
ret = bg_dev->ops->get_battery_soc(bg_dev);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_battery_soc);
|
|
|
|
int battery_gauge_report_battery_soc(struct battery_gauge_dev *bg_dev,
|
|
int battery_soc)
|
|
{
|
|
struct battery_charger_dev *node;
|
|
int ret = 0;
|
|
|
|
if (!bg_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(node, &charger_list, list) {
|
|
if (node->cell_id != bg_dev->cell_id)
|
|
continue;
|
|
if (node->ops && node->ops->input_voltage_configure)
|
|
ret = node->ops->input_voltage_configure(node,
|
|
battery_soc);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_report_battery_soc);
|
|
|
|
int battery_gauge_get_charging_status(struct battery_charger_dev *bc_dev)
|
|
{
|
|
struct battery_gauge_dev *bg_dev;
|
|
int ret = 0;
|
|
|
|
if (!bc_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(bg_dev, &gauge_list, list) {
|
|
if (bg_dev->cell_id != bc_dev->cell_id)
|
|
continue;
|
|
if (bg_dev->ops && bg_dev->ops->get_charging_status)
|
|
ret = bg_dev->ops->get_charging_status(bg_dev);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_charging_status);
|
|
|
|
int battery_gauge_get_scaled_soc(struct battery_gauge_dev *bg_dev,
|
|
int actual_soc_semi, int thresod_soc)
|
|
{
|
|
int thresod_soc_semi = thresod_soc * 100;
|
|
|
|
if (actual_soc_semi >= 10000)
|
|
return 100;
|
|
|
|
if (actual_soc_semi <= thresod_soc_semi)
|
|
return 0;
|
|
|
|
return (actual_soc_semi - thresod_soc_semi) / (100 - thresod_soc);
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_scaled_soc);
|
|
|
|
int battery_gauge_get_adjusted_soc(struct battery_gauge_dev *bg_dev,
|
|
int min_soc, int max_soc, int actual_soc_semi)
|
|
{
|
|
int min_soc_semi = min_soc * 100;
|
|
int max_soc_semi = max_soc * 100;
|
|
|
|
if (actual_soc_semi >= max_soc_semi)
|
|
return 100;
|
|
|
|
if (actual_soc_semi <= min_soc_semi)
|
|
return 0;
|
|
|
|
return (actual_soc_semi - min_soc_semi + 50) / (max_soc - min_soc);
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_adjusted_soc);
|
|
|
|
void battery_charging_restart_cancel(struct battery_charger_dev *bc_dev)
|
|
{
|
|
if (!bc_dev->ops->restart_charging) {
|
|
dev_err(bc_dev->parent_dev,
|
|
"No callback for restart charging\n");
|
|
return;
|
|
}
|
|
cancel_delayed_work(&bc_dev->restart_charging_wq);
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charging_restart_cancel);
|
|
|
|
int battery_charging_wakeup(struct battery_charger_dev *bc_dev, int after_sec)
|
|
{
|
|
int ret;
|
|
unsigned long now;
|
|
struct rtc_wkalrm alm;
|
|
int alarm_time = after_sec;
|
|
|
|
if (!alarm_time)
|
|
return 0;
|
|
|
|
bc_dev->rtc = alarmtimer_get_rtcdev();
|
|
if (!bc_dev->rtc) {
|
|
dev_err(bc_dev->parent_dev, "No RTC device found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
alm.enabled = true;
|
|
ret = rtc_read_time(bc_dev->rtc, &alm.time);
|
|
if (ret < 0) {
|
|
dev_err(bc_dev->parent_dev, "RTC read time failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
rtc_tm_to_time(&alm.time, &now);
|
|
|
|
rtc_time_to_tm(now + alarm_time, &alm.time);
|
|
ret = rtc_set_alarm(bc_dev->rtc, &alm);
|
|
if (ret < 0) {
|
|
dev_err(bc_dev->parent_dev, "RTC set alarm failed %d\n", ret);
|
|
alm.enabled = false;
|
|
return ret;
|
|
}
|
|
alm.enabled = false;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charging_wakeup);
|
|
|
|
int battery_charging_system_reset_after(struct battery_charger_dev *bc_dev,
|
|
int after_sec)
|
|
{
|
|
struct system_pmic_rtc_data rtc_data;
|
|
int ret;
|
|
|
|
if (!after_sec)
|
|
return 0;
|
|
|
|
dev_info(bc_dev->parent_dev, "Setting system on after %d sec\n",
|
|
after_sec);
|
|
battery_charging_wakeup(bc_dev, after_sec);
|
|
rtc_data.power_on_after_sec = after_sec;
|
|
|
|
ret = system_pmic_set_power_on_event(SYSTEM_PMIC_RTC_ALARM, &rtc_data);
|
|
if (ret < 0)
|
|
dev_err(bc_dev->parent_dev,
|
|
"Setting power on event failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charging_system_reset_after);
|
|
|
|
int battery_charging_system_power_on_usb_event(
|
|
struct battery_charger_dev *bc_dev)
|
|
{
|
|
int ret;
|
|
|
|
dev_info(bc_dev->parent_dev,
|
|
"Setting system on with USB connect/disconnect\n");
|
|
|
|
ret = system_pmic_set_power_on_event(SYSTEM_PMIC_USB_VBUS_INSERTION,
|
|
NULL);
|
|
if (ret < 0)
|
|
dev_err(bc_dev->parent_dev,
|
|
"Setting power on event failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charging_system_power_on_usb_event);
|
|
|
|
struct battery_charger_dev *battery_charger_register(struct device *dev,
|
|
struct battery_charger_info *bci, void *drv_data)
|
|
{
|
|
struct battery_charger_dev *bc_dev;
|
|
|
|
dev_info(dev, "Registering battery charger driver\n");
|
|
|
|
if (!dev || !bci)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
bc_dev = kzalloc(sizeof(*bc_dev), GFP_KERNEL);
|
|
if (!bc_dev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
INIT_LIST_HEAD(&bc_dev->list);
|
|
bc_dev->cell_id = bci->cell_id;
|
|
bc_dev->ops = bci->bc_ops;
|
|
bc_dev->parent_dev = dev;
|
|
bc_dev->drv_data = drv_data;
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
/* Thermal monitoring */
|
|
if (bci->tz_name) {
|
|
bc_dev->tz_name = kstrdup(bci->tz_name, GFP_KERNEL);
|
|
bc_dev->polling_time_sec = bci->polling_time_sec;
|
|
bc_dev->enable_thermal_monitor = true;
|
|
INIT_DELAYED_WORK(&bc_dev->poll_temp_monitor_wq,
|
|
battery_charger_thermal_monitor_wq);
|
|
}
|
|
#endif
|
|
|
|
INIT_DELAYED_WORK(&bc_dev->restart_charging_wq,
|
|
battery_charger_restart_charging_wq);
|
|
|
|
wakeup_source_init(&bc_dev->charger_wake_lock, "charger-suspend-lock");
|
|
|
|
list_add(&bc_dev->list, &charger_list);
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return bc_dev;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_register);
|
|
|
|
void battery_charger_unregister(struct battery_charger_dev *bc_dev)
|
|
{
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
list_del(&bc_dev->list);
|
|
if (bc_dev->polling_time_sec)
|
|
cancel_delayed_work(&bc_dev->poll_temp_monitor_wq);
|
|
cancel_delayed_work(&bc_dev->restart_charging_wq);
|
|
wakeup_source_trash(&bc_dev->charger_wake_lock);
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
kfree(bc_dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_unregister);
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
int battery_gauge_get_battery_temperature(struct battery_gauge_dev *bg_dev,
|
|
int *temp)
|
|
{
|
|
int ret;
|
|
int temperature;
|
|
|
|
if (!bg_dev || !bg_dev->tz_name)
|
|
return -EINVAL;
|
|
|
|
if (!bg_dev->battery_tz)
|
|
bg_dev->battery_tz =
|
|
thermal_zone_get_zone_by_name(bg_dev->tz_name);
|
|
|
|
if (IS_ERR(bg_dev->battery_tz)) {
|
|
dev_info(bg_dev->parent_dev,
|
|
"Battery thermal zone %s is not registered yet\n",
|
|
bg_dev->tz_name);
|
|
bg_dev->battery_tz = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = thermal_zone_get_temp(bg_dev->battery_tz, &temperature);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*temp = temperature / 1000;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_battery_temperature);
|
|
#endif
|
|
|
|
int battery_gauge_get_battery_current(struct battery_gauge_dev *bg_dev,
|
|
int *current_ma)
|
|
{
|
|
int ret;
|
|
|
|
if (!bg_dev || !bg_dev->bat_curr_channel_name)
|
|
return -EINVAL;
|
|
|
|
if (!bg_dev->bat_current_iio_channel)
|
|
bg_dev->bat_current_iio_channel =
|
|
iio_channel_get(bg_dev->parent_dev,
|
|
bg_dev->bat_curr_channel_name);
|
|
if (!bg_dev->bat_current_iio_channel || IS_ERR(bg_dev->bat_current_iio_channel)) {
|
|
dev_info(bg_dev->parent_dev,
|
|
"Battery IIO current channel %s not registered yet\n",
|
|
bg_dev->bat_curr_channel_name);
|
|
bg_dev->bat_current_iio_channel = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = iio_read_channel_processed(bg_dev->bat_current_iio_channel,
|
|
current_ma);
|
|
if (ret < 0) {
|
|
dev_err(bg_dev->parent_dev, " The channel read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_battery_current);
|
|
|
|
int battery_gauge_get_input_power(struct battery_gauge_dev *bg_dev,
|
|
int *power_mw)
|
|
{
|
|
int ret;
|
|
|
|
if (!bg_dev || !bg_dev->input_power_channel_name)
|
|
return -EINVAL;
|
|
|
|
if (!bg_dev->input_power_iio_channel)
|
|
bg_dev->input_power_iio_channel =
|
|
iio_channel_get(bg_dev->parent_dev,
|
|
bg_dev->input_power_channel_name);
|
|
if (!bg_dev->input_power_iio_channel ||
|
|
IS_ERR(bg_dev->input_power_iio_channel)) {
|
|
dev_info(bg_dev->parent_dev,
|
|
"Battery IIO power channel %s not registered yet\n",
|
|
bg_dev->input_power_channel_name);
|
|
bg_dev->input_power_iio_channel = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = iio_read_channel_processed(bg_dev->input_power_iio_channel,
|
|
power_mw);
|
|
if (ret < 0) {
|
|
dev_err(bg_dev->parent_dev,
|
|
"power channel read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_input_power);
|
|
|
|
int battery_gauge_get_input_current_limit(struct battery_gauge_dev *bg_dev)
|
|
{
|
|
struct battery_charger_dev *node;
|
|
int ret = 0;
|
|
|
|
if (!bg_dev)
|
|
return -EINVAL;
|
|
|
|
list_for_each_entry(node, &charger_list, list) {
|
|
if (node->cell_id != bg_dev->cell_id)
|
|
continue;
|
|
if (node->ops && node->ops->get_input_current_limit)
|
|
ret = node->ops->get_input_current_limit(node);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_input_current_limit);
|
|
|
|
int battery_gauge_fc_state(struct battery_gauge_dev *bg_dev,
|
|
int fullcharge_state)
|
|
{
|
|
struct battery_charger_dev *node;
|
|
int ret = 0;
|
|
|
|
if (!bg_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(node, &charger_list, list) {
|
|
if (node->cell_id != bg_dev->cell_id)
|
|
continue;
|
|
if (node->ops && node->ops->charge_term_configure)
|
|
ret = node->ops->charge_term_configure(node,
|
|
fullcharge_state);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_fc_state);
|
|
|
|
struct battery_gauge_dev *battery_gauge_register(struct device *dev,
|
|
struct battery_gauge_info *bgi, void *drv_data)
|
|
{
|
|
struct battery_gauge_dev *bg_dev;
|
|
int ret;
|
|
|
|
dev_info(dev, "Registering battery gauge driver\n");
|
|
|
|
if (!dev || !bgi)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
bg_dev = kzalloc(sizeof(*bg_dev), GFP_KERNEL);
|
|
if (!bg_dev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ret = sysfs_create_group(&dev->kobj, &battery_sysfs_attr_group);
|
|
if (ret < 0)
|
|
dev_info(dev, "Could not create battery sysfs group\n");
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
INIT_LIST_HEAD(&bg_dev->list);
|
|
bg_dev->cell_id = bgi->cell_id;
|
|
bg_dev->ops = bgi->bg_ops;
|
|
bg_dev->parent_dev = dev;
|
|
bg_dev->drv_data = drv_data;
|
|
bg_dev->tz_name = NULL;
|
|
|
|
if (bgi->current_channel_name)
|
|
bg_dev->bat_curr_channel_name = bgi->current_channel_name;
|
|
if (bgi->input_power_channel_name)
|
|
bg_dev->input_power_channel_name =
|
|
bgi->input_power_channel_name;
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
if (bgi->tz_name) {
|
|
bg_dev->tz_name = kstrdup(bgi->tz_name, GFP_KERNEL);
|
|
bg_dev->battery_tz = thermal_zone_get_zone_by_name(
|
|
bg_dev->tz_name);
|
|
if (IS_ERR(bg_dev->battery_tz))
|
|
dev_info(dev,
|
|
"Battery thermal zone %s is not registered yet\n",
|
|
bg_dev->tz_name);
|
|
bg_dev->battery_tz = NULL;
|
|
}
|
|
#endif
|
|
|
|
list_add(&bg_dev->list, &gauge_list);
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
bg_temp = bg_dev;
|
|
|
|
return bg_dev;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_register);
|
|
|
|
void battery_gauge_unregister(struct battery_gauge_dev *bg_dev)
|
|
{
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
list_del(&bg_dev->list);
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
kfree(bg_dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_unregister);
|
|
|
|
int battery_charging_status_update(struct battery_charger_dev *bc_dev,
|
|
enum battery_charger_status status)
|
|
{
|
|
struct battery_gauge_dev *node;
|
|
int ret = -EINVAL;
|
|
|
|
if (!bc_dev)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&charger_gauge_list_mutex);
|
|
|
|
list_for_each_entry(node, &gauge_list, list) {
|
|
if (node->cell_id != bc_dev->cell_id)
|
|
continue;
|
|
if (node->ops && node->ops->update_battery_status)
|
|
ret = node->ops->update_battery_status(node, status);
|
|
}
|
|
|
|
mutex_unlock(&charger_gauge_list_mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charging_status_update);
|
|
|
|
void *battery_charger_get_drvdata(struct battery_charger_dev *bc_dev)
|
|
{
|
|
if (bc_dev)
|
|
return bc_dev->drv_data;
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_get_drvdata);
|
|
|
|
void battery_charger_set_drvdata(struct battery_charger_dev *bc_dev, void *data)
|
|
{
|
|
if (bc_dev)
|
|
bc_dev->drv_data = data;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_charger_set_drvdata);
|
|
|
|
void *battery_gauge_get_drvdata(struct battery_gauge_dev *bg_dev)
|
|
{
|
|
if (bg_dev)
|
|
return bg_dev->drv_data;
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_get_drvdata);
|
|
|
|
void battery_gauge_set_drvdata(struct battery_gauge_dev *bg_dev, void *data)
|
|
{
|
|
if (bg_dev)
|
|
bg_dev->drv_data = data;
|
|
}
|
|
EXPORT_SYMBOL_GPL(battery_gauge_set_drvdata);
|