blob: 6f541e6a3e1b8badcb548a769321ff4d1214547c [file] [log] [blame]
/*
* intel_fg_usr_iface.c - Intel FG algo interface
*
* Copyright (C) 2014 Intel Corporation
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 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 of the License.
*
* 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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/power/intel_fuel_gauge.h>
#include <linux/kdev_t.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#define DRIVER_NAME "intel_fg_iface"
struct fg_iface_info {
struct platform_device *pdev;
struct mutex lock;
int vbatt_boot;
int ibat_boot;
int vbatt;
int vavg;
int vocv;
int ibatt;
int iavg;
int bat_temp;
int delta_q;
int soc;
int nac;
int fcc;
int cycle_count;
int calib_cc;
wait_queue_head_t wait;
bool uevent_ack;
bool suspended;
struct miscdevice intel_fg_misc_device;
};
static struct fg_iface_info *info_ptr;
static ssize_t fg_iface_get_volt_now(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->vbatt);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_volt_ocv(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->vocv);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_volt_boot(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->vbatt_boot);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_ibat_boot(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->ibat_boot);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_cur_now(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->ibatt);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_cur_avg(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->iavg);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_batt_temp(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->bat_temp);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_delta_q(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->delta_q);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_get_capacity(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->soc);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_set_capacity(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&info_ptr->lock);
info_ptr->soc = val;
mutex_unlock(&info_ptr->lock);
return count;
}
static ssize_t fg_iface_get_nac(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->nac);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_set_nac(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&info_ptr->lock);
info_ptr->nac = val;
mutex_unlock(&info_ptr->lock);
return count;
}
static ssize_t fg_iface_get_fcc(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->fcc);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_set_fcc(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&info_ptr->lock);
info_ptr->fcc = val;
mutex_unlock(&info_ptr->lock);
return count;
}
static ssize_t fg_iface_get_cyc_cnt(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->cycle_count);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_set_cyc_cnt(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&info_ptr->lock);
info_ptr->cycle_count = val;
mutex_unlock(&info_ptr->lock);
return count;
}
static ssize_t fg_iface_get_cc_calib(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
mutex_lock(&info_ptr->lock);
ret = sprintf(buf, "%d\n", info_ptr->calib_cc);
mutex_unlock(&info_ptr->lock);
return ret;
}
static ssize_t fg_iface_set_cc_calib(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
mutex_lock(&info_ptr->lock);
info_ptr->calib_cc = val;
info_ptr->uevent_ack = true;
wake_up(&info_ptr->wait);
mutex_unlock(&info_ptr->lock);
return count;
}
static DEVICE_ATTR(volt_now, S_IRUGO,
fg_iface_get_volt_now, NULL);
static DEVICE_ATTR(volt_ocv, S_IRUGO,
fg_iface_get_volt_ocv, NULL);
static DEVICE_ATTR(volt_boot, S_IRUGO,
fg_iface_get_volt_boot, NULL);
static DEVICE_ATTR(ibat_boot, S_IRUGO,
fg_iface_get_ibat_boot, NULL);
static DEVICE_ATTR(cur_now, S_IRUGO,
fg_iface_get_cur_now, NULL);
static DEVICE_ATTR(cur_avg, S_IRUGO,
fg_iface_get_cur_avg, NULL);
static DEVICE_ATTR(batt_temp, S_IRUGO,
fg_iface_get_batt_temp, NULL);
static DEVICE_ATTR(delta_q, S_IRUGO,
fg_iface_get_delta_q, NULL);
static DEVICE_ATTR(capacity, S_IWUSR | S_IRUGO,
fg_iface_get_capacity, fg_iface_set_capacity);
static DEVICE_ATTR(nac, S_IWUSR | S_IRUGO,
fg_iface_get_nac, fg_iface_set_nac);
static DEVICE_ATTR(fcc, S_IWUSR | S_IRUGO,
fg_iface_get_fcc, fg_iface_set_fcc);
static DEVICE_ATTR(cyc_cnt, S_IWUSR | S_IRUGO,
fg_iface_get_cyc_cnt, fg_iface_set_cyc_cnt);
static DEVICE_ATTR(cc_calib, S_IWUSR | S_IRUGO,
fg_iface_get_cc_calib, fg_iface_set_cc_calib);
static struct attribute *fg_iface_sysfs_attributes[] = {
&dev_attr_volt_now.attr,
&dev_attr_volt_ocv.attr,
&dev_attr_volt_boot.attr,
&dev_attr_ibat_boot.attr,
&dev_attr_cur_now.attr,
&dev_attr_cur_avg.attr,
&dev_attr_batt_temp.attr,
&dev_attr_delta_q.attr,
&dev_attr_capacity.attr,
&dev_attr_nac.attr,
&dev_attr_fcc.attr,
&dev_attr_cyc_cnt.attr,
&dev_attr_cc_calib.attr,
NULL,
};
static const struct attribute_group fg_iface_sysfs_attr_group = {
.attrs = fg_iface_sysfs_attributes,
};
static int fg_iface_sysfs_init(struct fg_iface_info *info)
{
int ret;
info->intel_fg_misc_device.minor = MISC_DYNAMIC_MINOR;
info->intel_fg_misc_device.name = DRIVER_NAME;
info->intel_fg_misc_device.mode = (S_IWUSR | S_IRUGO);
ret = misc_register(&info->intel_fg_misc_device);
if (ret) {
dev_err(&info->pdev->dev,
"\nErr %d in registering misc class", ret);
return ret;
}
ret = sysfs_create_group(&info->intel_fg_misc_device.this_device->kobj,
&fg_iface_sysfs_attr_group);
if (ret) {
dev_err(&info->pdev->dev,
"\nError %d in creating sysfs group", ret);
misc_deregister(&info->intel_fg_misc_device);
}
return ret;
}
static void fg_iface_sysfs_exit(struct fg_iface_info *info)
{
sysfs_remove_group(&info->pdev->dev.kobj,
&fg_iface_sysfs_attr_group);
if (info->intel_fg_misc_device.this_device)
misc_deregister(&info->intel_fg_misc_device);
}
static int intel_fg_iface_algo_process(struct fg_algo_ip_params *ip,
struct fg_algo_op_params *op)
{
int ret;
mutex_lock(&info_ptr->lock);
info_ptr->vbatt = ip->vbatt;
info_ptr->vavg = ip->vavg;
info_ptr->vocv = ip->vocv;
info_ptr->ibatt = ip->ibatt;
info_ptr->iavg = ip->iavg;
info_ptr->bat_temp = ip->bat_temp;
info_ptr->delta_q = ip->delta_q;
/* TODO: Add user space event generation mechanism */
dev_dbg(&info_ptr->pdev->dev, "Sending uevent from intel_fg_uiface\n");
if (!IS_ERR_OR_NULL(info_ptr->intel_fg_misc_device.this_device))
sysfs_notify(&info_ptr->intel_fg_misc_device.this_device->kobj,
NULL, "uevent");
/*Wait for user space to write back*/
info_ptr->uevent_ack = false;
mutex_unlock(&info_ptr->lock);
if (info_ptr->suspended) {
dev_err(&info_ptr->pdev->dev, "Error SUSPENDED\n");
return -ESHUTDOWN;
}
/*
* Since we need to wait for user space event and since the user space
* scheduling depends on the system load and other high priority tasks,
* hence, the safe margin to wait for timeout would be 9secs
*/
ret = wait_event_timeout(info_ptr->wait,
info_ptr->uevent_ack == true, 9 * HZ);
if (0 == ret) {
dev_err(&info_ptr->pdev->dev,
"\n Error TIMEOUT waiting for user space write back");
return -ETIMEDOUT;
}
mutex_lock(&info_ptr->lock);
op->soc = info_ptr->soc;
op->nac = info_ptr->nac;
op->fcc = info_ptr->fcc;
op->cycle_count = info_ptr->cycle_count;
op->calib_cc = info_ptr->calib_cc;
mutex_unlock(&info_ptr->lock);
return 0;
}
static int intel_fg_iface_algo_init(struct fg_batt_params *bat_params)
{
mutex_lock(&info_ptr->lock);
info_ptr->vbatt_boot = bat_params->v_ocv_bootup;
info_ptr->ibat_boot = bat_params->i_bat_bootup;
info_ptr->vbatt = bat_params->vbatt_now;
info_ptr->vocv = bat_params->v_ocv_now;
info_ptr->ibatt = bat_params->i_batt_now;
info_ptr->iavg = bat_params->i_batt_avg;
info_ptr->bat_temp = bat_params->batt_temp_now;
mutex_unlock(&info_ptr->lock);
fg_iface_sysfs_init(info_ptr);
return 0;
}
struct intel_fg_algo algo = {
.type = INTEL_FG_ALGO_PRIMARY,
.fg_algo_init = intel_fg_iface_algo_init,
.fg_algo_process = intel_fg_iface_algo_process,
};
static int intel_fg_iface_probe(struct platform_device *pdev)
{
int ret;
struct fg_iface_info *info;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
dev_err(&pdev->dev, "mem alloc failed\n");
return -ENOMEM;
}
info->pdev = pdev;
mutex_init(&info->lock);
info_ptr = info;
info_ptr->suspended = false;
init_waitqueue_head(&info->wait);
ret = intel_fg_register_algo(&algo);
if (ret < 0) {
dev_err(&pdev->dev, "FG algo registration error\n");
mutex_destroy(&info->lock);
}
return ret;
}
static int intel_fg_iface_remove(struct platform_device *pdev)
{
intel_fg_unregister_algo(&algo);
fg_iface_sysfs_exit(info_ptr);
return 0;
}
#ifdef CONFIG_PM
static int intel_fg_iface_suspend(struct device *dev)
{
/* Force ACK uevent to avoid blocking as user space freezing */
info_ptr->suspended = true;
mutex_lock(&info_ptr->lock);
info_ptr->uevent_ack = true;
wake_up(&info_ptr->wait);
mutex_unlock(&info_ptr->lock);
return 0;
}
static int intel_fg_iface_resume(struct device *dev)
{
info_ptr->suspended = false;
return 0;
}
#else
#define intel_fg_iface_suspend NULL
#define intel_fg_iface_resume NULL
#endif
static const struct dev_pm_ops intel_fg_iface_driver_pm_ops = {
.suspend = intel_fg_iface_suspend,
.resume = intel_fg_iface_resume,
};
static struct platform_driver intel_fg_iface_driver = {
.probe = intel_fg_iface_probe,
.remove = intel_fg_iface_remove,
.driver = {
.name = DRIVER_NAME,
.pm = &intel_fg_iface_driver_pm_ops,
},
};
static int __init intel_fg_iface_init(void)
{
return platform_driver_register(&intel_fg_iface_driver);
}
late_initcall(intel_fg_iface_init);
static void __exit intel_fg_iface_exit(void)
{
platform_driver_unregister(&intel_fg_iface_driver);
}
module_exit(intel_fg_iface_exit);
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
MODULE_DESCRIPTION("Intel Fuel Gauge interface driver");
MODULE_LICENSE("GPL");