android_kernel_lge_bullhead/drivers/power/msm_bcl.c

376 lines
8.7 KiB
C
Raw Permalink Normal View History

bcl: Add BCL framework driver Add a new BCL framework driver which provides interface to configure and interact with the battery current limit hardware. This framework driver also provides a generic interface for the hardware drivers to register with function pointers, thus abstracting the underlying hardware from the upper level kernel modules. The BCL peripheral driver exposes the below read-only sysfs nodes for user debugging. -/sys/class/msm_bcl/voltage/value - Latest battery voltage value (uV) -/sys/class/msm_bcl/voltage/high_trip - Active maximum battery voltage threshold (uV) -/sys/class/msm_bcl/voltage/low_trip - Active minimum battery voltage threshold (uV) -/sys/class/msm_bcl/current/value - Latest battery current value (uA) -/sys/class/msm_bcl/current/high_trip - Active maximum battery current threshold (uA) -/sys/class/msm_bcl/current/low_trip - Active minimum battery current threshold (uA) The bcl framework driver provides a data-structure to input the trip threshold, trip type, trip notification, and its associated data to be passed along the notification. The kernel drivers can use this data-structure to pass the high and low trip threshold information for battery voltage and current. struct bcl_threshold { int trip_value; enum bcl_trip_type type; void (*trip_notify)(enum bcl_trip_type, int, void *); void *trip_data; }; - int msm_bcl_enable(void); - int msm_bcl_disable(void); Kernel driver should use these interfaces to enable and disable the BCL monitoring feature respectively. The msm_bcl_enable(void) call will set the thresholds for BCL monitoring and will enable hardware BCL monitoring. - int msm_bcl_set_threshold(enum bcl_param, enum bcl_trip_type, struct bcl_threshold *); Kernel drivers should use this interface to program the high trip and low trip thresholds along with their notification callbacks for battery voltage and current. - int msm_bcl_read(enum bcl_param, int *); Kernel driver can use this interface to read the last hardware sampled voltage and current reading in micro-volts and micro-ampere respectively. - struct bcl_param_data *msm_bcl_register_param(enum bcl_param, struct bcl_driver_ops *, char *); - int msm_bcl_unregister_param(struct bcl_param_data *); BCL hardware drivers should use this interface to register with the BCL framework. The BCL hardware drivers should define the ops functions required by the framework driver and provide that as input using the struct bcl_driver_ops parameter. The register function returns a struct bcl_param_data pointer on success, which can be used as input for unregistering the same from BCL framework driver. struct bcl_driver_ops { int (*read) (int *); int (*set_high_trip) (int); int (*get_high_trip) (int *); int (*set_low_trip) (int); int (*get_low_trip) (int *); int (*disable) (void); int (*enable) (void); int (*notify) (struct bcl_param_data *, int, enum bcl_trip_type); }; This BCL frmaework requires the BCL hardware drivers to have interfaces implementing the above functionalities. - read This function should read the last hardware sampled value. - set_high_trip - get_high_trip - set_low_trip - get_low_trip These functions should set and get the high trip and low trip. - disable - enable These functions should enable or disable the voltage or current param monitoring. -notify BCL framework populates this function pointer with a generic threshold handler interface. On reaching high/low trip, the BCL hardware driver should call this notification handler with appropriate input parameters. Change-Id: I5ee544bdf0bce7114f8251799ea32b121bb4464c Signed-off-by: Ram Chandrasekar <rkumbako@codeaurora.org>
2014-07-16 22:14:53 +02:00
/* Copyright (c) 2014, 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.
*
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/sysfs.h>
#include <linux/mutex.h>
#include <linux/msm_bcl.h>
#include <linux/slab.h>
#define BCL_PARAM_MAX_ATTR 3
#define BCL_DEFINE_RO_PARAM(_attr, _name, _attr_gp, _index) \
sysfs_attr_init(&_attr.attr); \
bcl: Add BCL framework driver Add a new BCL framework driver which provides interface to configure and interact with the battery current limit hardware. This framework driver also provides a generic interface for the hardware drivers to register with function pointers, thus abstracting the underlying hardware from the upper level kernel modules. The BCL peripheral driver exposes the below read-only sysfs nodes for user debugging. -/sys/class/msm_bcl/voltage/value - Latest battery voltage value (uV) -/sys/class/msm_bcl/voltage/high_trip - Active maximum battery voltage threshold (uV) -/sys/class/msm_bcl/voltage/low_trip - Active minimum battery voltage threshold (uV) -/sys/class/msm_bcl/current/value - Latest battery current value (uA) -/sys/class/msm_bcl/current/high_trip - Active maximum battery current threshold (uA) -/sys/class/msm_bcl/current/low_trip - Active minimum battery current threshold (uA) The bcl framework driver provides a data-structure to input the trip threshold, trip type, trip notification, and its associated data to be passed along the notification. The kernel drivers can use this data-structure to pass the high and low trip threshold information for battery voltage and current. struct bcl_threshold { int trip_value; enum bcl_trip_type type; void (*trip_notify)(enum bcl_trip_type, int, void *); void *trip_data; }; - int msm_bcl_enable(void); - int msm_bcl_disable(void); Kernel driver should use these interfaces to enable and disable the BCL monitoring feature respectively. The msm_bcl_enable(void) call will set the thresholds for BCL monitoring and will enable hardware BCL monitoring. - int msm_bcl_set_threshold(enum bcl_param, enum bcl_trip_type, struct bcl_threshold *); Kernel drivers should use this interface to program the high trip and low trip thresholds along with their notification callbacks for battery voltage and current. - int msm_bcl_read(enum bcl_param, int *); Kernel driver can use this interface to read the last hardware sampled voltage and current reading in micro-volts and micro-ampere respectively. - struct bcl_param_data *msm_bcl_register_param(enum bcl_param, struct bcl_driver_ops *, char *); - int msm_bcl_unregister_param(struct bcl_param_data *); BCL hardware drivers should use this interface to register with the BCL framework. The BCL hardware drivers should define the ops functions required by the framework driver and provide that as input using the struct bcl_driver_ops parameter. The register function returns a struct bcl_param_data pointer on success, which can be used as input for unregistering the same from BCL framework driver. struct bcl_driver_ops { int (*read) (int *); int (*set_high_trip) (int); int (*get_high_trip) (int *); int (*set_low_trip) (int); int (*get_low_trip) (int *); int (*disable) (void); int (*enable) (void); int (*notify) (struct bcl_param_data *, int, enum bcl_trip_type); }; This BCL frmaework requires the BCL hardware drivers to have interfaces implementing the above functionalities. - read This function should read the last hardware sampled value. - set_high_trip - get_high_trip - set_low_trip - get_low_trip These functions should set and get the high trip and low trip. - disable - enable These functions should enable or disable the voltage or current param monitoring. -notify BCL framework populates this function pointer with a generic threshold handler interface. On reaching high/low trip, the BCL hardware driver should call this notification handler with appropriate input parameters. Change-Id: I5ee544bdf0bce7114f8251799ea32b121bb4464c Signed-off-by: Ram Chandrasekar <rkumbako@codeaurora.org>
2014-07-16 22:14:53 +02:00
_attr.attr.name = __stringify(_name); \
_attr.attr.mode = 0444; \
_attr.show = _name##_show; \
_attr_gp.attrs[_index] = &_attr.attr;
static struct bcl_param_data *bcl[BCL_PARAM_MAX];
static ssize_t high_trip_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int val = 0, ret = 0;
struct bcl_param_data *dev_param = container_of(attr,
struct bcl_param_data, high_trip_attr);
if (!dev_param->registered)
return -ENODEV;
ret = dev_param->ops->get_high_trip(&val);
if (ret) {
pr_err("High trip value read failed. err:%d\n", ret);
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t low_trip_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int val = 0, ret = 0;
struct bcl_param_data *dev_param = container_of(attr,
struct bcl_param_data, low_trip_attr);
if (!dev_param->registered)
return -ENODEV;
ret = dev_param->ops->get_low_trip(&val);
if (ret) {
pr_err("Low trip value read failed. err:%d\n", ret);
return ret;
}
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int32_t val = 0, ret = 0;
struct bcl_param_data *dev_param = container_of(attr,
struct bcl_param_data, val_attr);
if (!dev_param->registered)
return -ENODEV;
ret = dev_param->ops->read(&val);
if (ret) {
pr_err("Value read failed. err:%d\n", ret);
return ret;
}
dev_param->last_read_val = val;
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
int msm_bcl_set_threshold(enum bcl_param param_type,
enum bcl_trip_type trip_type, struct bcl_threshold *inp_thresh)
{
int ret = 0;
if (!bcl[param_type] || !bcl[param_type]->registered) {
pr_err("BCL not initialized\n");
return -EINVAL;
}
if ((!inp_thresh)
|| (inp_thresh->trip_value < 0)
|| (!inp_thresh->trip_notify)
|| (param_type >= BCL_PARAM_MAX)
|| (trip_type >= BCL_TRIP_MAX)) {
pr_err("Invalid Input\n");
return -EINVAL;
}
bcl[param_type]->thresh[trip_type] = inp_thresh;
if (trip_type == BCL_HIGH_TRIP) {
bcl[param_type]->high_trip = inp_thresh->trip_value;
ret = bcl[param_type]->ops->set_high_trip(
inp_thresh->trip_value);
} else {
bcl[param_type]->low_trip = inp_thresh->trip_value;
ret = bcl[param_type]->ops->set_low_trip(
inp_thresh->trip_value);
}
if (ret) {
pr_err("Error setting trip%d for param%d. err:%d\n", trip_type,
param_type, ret);
return ret;
}
return ret;
}
static int bcl_thresh_notify(struct bcl_param_data *param_data, int val,
enum bcl_trip_type trip_type)
{
if (!param_data || trip_type >= BCL_TRIP_MAX
|| !param_data->registered) {
pr_err("Invalid input\n");
return -EINVAL;
}
param_data->thresh[trip_type]->trip_notify(trip_type, val,
param_data->thresh[trip_type]->trip_data);
return 0;
}
static int bcl_add_sysfs_nodes(enum bcl_param param_type);
struct bcl_param_data *msm_bcl_register_param(enum bcl_param param_type,
struct bcl_driver_ops *param_ops, char *name)
{
int ret = 0;
if (!bcl[param_type]
|| param_type >= BCL_PARAM_MAX || !param_ops || !name
|| !param_ops->read || !param_ops->set_high_trip
|| !param_ops->get_high_trip || !param_ops->set_low_trip
|| !param_ops->get_low_trip || !param_ops->enable
|| !param_ops->disable) {
pr_err("Invalid input\n");
return NULL;
}
if (bcl[param_type]->registered) {
pr_err("param%d already initialized\n", param_type);
return NULL;
}
ret = bcl_add_sysfs_nodes(param_type);
if (ret) {
pr_err("Error creating sysfs nodes. err:%d\n", ret);
return NULL;
}
bcl[param_type]->ops = param_ops;
bcl[param_type]->registered = true;
strlcpy(bcl[param_type]->name, name, BCL_NAME_MAX_LEN);
param_ops->notify = bcl_thresh_notify;
return bcl[param_type];
}
int msm_bcl_unregister_param(struct bcl_param_data *param_data)
{
int i = 0, ret = -EINVAL;
if (!bcl[i] || !param_data) {
pr_err("Invalid input\n");
return ret;
}
for (; i < BCL_PARAM_MAX; i++) {
if (param_data != bcl[i])
continue;
bcl[i]->ops->disable();
bcl[i]->registered = false;
ret = 0;
break;
}
return ret;
}
int msm_bcl_disable(void)
{
int ret = 0, i = 0;
if (!bcl[i]) {
pr_err("BCL not initialized\n");
return -EINVAL;
}
for (; i < BCL_PARAM_MAX; i++) {
if (!bcl[i]->registered)
continue;
ret = bcl[i]->ops->disable();
if (ret) {
pr_err("Error in disabling interrupt. param:%d err%d\n",
i, ret);
return ret;
}
}
return ret;
}
int msm_bcl_enable(void)
{
int ret = 0, i = 0;
struct bcl_param_data *param_data = NULL;
if (!bcl[i] || !bcl[BCL_PARAM_VOLTAGE]->thresh
|| !bcl[BCL_PARAM_CURRENT]->thresh) {
pr_err("BCL not initialized\n");
return -EINVAL;
}
for (; i < BCL_PARAM_MAX; i++) {
if (!bcl[i]->registered)
continue;
param_data = bcl[i];
ret = param_data->ops->set_high_trip(param_data->high_trip);
if (ret) {
pr_err("Error setting high trip. param:%d. err:%d",
i, ret);
return ret;
}
ret = param_data->ops->set_low_trip(param_data->low_trip);
if (ret) {
pr_err("Error setting low trip. param:%d. err:%d",
i, ret);
return ret;
}
ret = param_data->ops->enable();
if (ret) {
pr_err("Error enabling interrupt. param:%d. err:%d",
i, ret);
return ret;
}
}
return ret;
}
int msm_bcl_read(enum bcl_param param_type, int *value)
{
int ret = 0;
if (!value || param_type >= BCL_PARAM_MAX) {
pr_err("Invalid input\n");
return -EINVAL;
}
if (!bcl[param_type] || !bcl[param_type]->registered) {
pr_err("BCL driver not initialized\n");
return -ENOSYS;
}
ret = bcl[param_type]->ops->read(value);
if (ret) {
pr_err("Error reading param%d. err:%d\n", param_type, ret);
return ret;
}
bcl[param_type]->last_read_val = *value;
return ret;
}
static struct class msm_bcl_class = {
.name = "msm_bcl",
};
static int bcl_add_sysfs_nodes(enum bcl_param param_type)
{
char *param_name[BCL_PARAM_MAX] = {"voltage", "current"};
int ret = 0;
bcl[param_type]->device.class = &msm_bcl_class;
dev_set_name(&bcl[param_type]->device, "%s", param_name[param_type]);
ret = device_register(&bcl[param_type]->device);
if (ret) {
pr_err("Error registering device %s. err:%d\n",
param_name[param_type], ret);
return ret;
}
bcl[param_type]->bcl_attr_gp.attrs = kzalloc(sizeof(struct attribute *)
bcl: fix allocation for BCL attribute The size of the BCL attribute is incorrect due to a precedence bug: This was observed while booting with Kernel Address Sanitizer(KASan) enabled. ============================================================================= BUG kmalloc-64 (Tainted: G B ): kasan: bad access detected ----------------------------------------------------------------------------- INFO: Slab 0xffffffbc0661c6e0 objects=64 used=64 fp=0x (null) flags=0x0080 INFO: Object 0xffffffc0a360bb00 @offset=2816 fp=0xffffffc0a3454728 Bytes b4 ffffffc0a360baf0: 3f 37 9c 1c 00 00 00 00 02 00 02 00 a9 4e ad de ?7...........N.. Object ffffffc0a360bb00: 28 47 45 a3 c0 ff ff ff 48 47 45 a3 c0 ff ff ff (GE.....HGE..... Object ffffffc0a360bb10: 68 47 45 a3 c0 ff ff ff 00 00 00 00 00 00 00 00 hGE............. Object ffffffc0a360bb20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Object ffffffc0a360bb30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ CPU: 0 PID: 1 Comm: swapper/0 Tainted: G B 3.10.49-g465b172-00133-gb931dc1 #134 Call trace: [<ffffffc00040a2a4>] dump_backtrace+0x0/0x1d4 [<ffffffc00040a488>] show_stack+0x10/0x1c [<ffffffc000f971a4>] dump_stack+0x1c/0x28 [<ffffffc00054aeb4>] print_trailer+0x144/0x158 [<ffffffc00054b210>] object_err+0x38/0x4c [<ffffffc00054fed8>] kasan_report_error+0x210/0x3b0 [<ffffffc000550188>] kasan_report+0x68/0x78 [<ffffffc00054f1b0>] __asan_load8+0x90/0x9c [<ffffffc0005dff78>] internal_create_group+0x1a0/0x2f4 [<ffffffc0005e00dc>] sysfs_create_group+0x10/0x1c [<ffffffc000c5eb9c>] msm_bcl_register_param+0x384/0x450 [<ffffffc000c61758>] bcl_probe+0x840/0xb84 [<ffffffc000a394b8>] spmi_drv_probe+0x2c/0x3c [<ffffffc000999150>] driver_probe_device+0x1f4/0x47c [<ffffffc0009994c4>] __driver_attach+0x88/0xc0 [<ffffffc000996434>] bus_for_each_dev+0xdc/0x11c [<ffffffc0009988ac>] driver_attach+0x2c/0x3c [<ffffffc0009981fc>] bus_add_driver+0x1bc/0x32c [<ffffffc000999d1c>] driver_register+0x10c/0x1d8 [<ffffffc000a39a30>] spmi_driver_register+0x98/0xa8 [<ffffffc00183a300>] bcl_perph_init+0x2c/0x38 [<ffffffc000400b00>] do_one_initcall+0xcc/0x188 [<ffffffc001800b54>] kernel_init_freeable+0x1c0/0x264 [<ffffffc000f89b84>] kernel_init+0x10/0xcc Memory state around the buggy address: ffffffc0a360ba00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ffffffc0a360ba80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ffffffc0a360bb00: 00 00 00 01 fc fc fc fc fc fc fc fc fc fc fc fc ^ ffffffc0a360bb80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffffffc0a360bc00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ================================================================== Fix this by adding parantheses to fix precedence. CRs-Fixed: 826589 Change-Id: Ia58b6e52c491b89b10a2b8fe45445372bfe9fa20 Signed-off-by: David Keitel <dkeitel@codeaurora.org>
2015-04-21 00:51:33 +02:00
* (BCL_PARAM_MAX_ATTR + 1), GFP_KERNEL);
bcl: Add BCL framework driver Add a new BCL framework driver which provides interface to configure and interact with the battery current limit hardware. This framework driver also provides a generic interface for the hardware drivers to register with function pointers, thus abstracting the underlying hardware from the upper level kernel modules. The BCL peripheral driver exposes the below read-only sysfs nodes for user debugging. -/sys/class/msm_bcl/voltage/value - Latest battery voltage value (uV) -/sys/class/msm_bcl/voltage/high_trip - Active maximum battery voltage threshold (uV) -/sys/class/msm_bcl/voltage/low_trip - Active minimum battery voltage threshold (uV) -/sys/class/msm_bcl/current/value - Latest battery current value (uA) -/sys/class/msm_bcl/current/high_trip - Active maximum battery current threshold (uA) -/sys/class/msm_bcl/current/low_trip - Active minimum battery current threshold (uA) The bcl framework driver provides a data-structure to input the trip threshold, trip type, trip notification, and its associated data to be passed along the notification. The kernel drivers can use this data-structure to pass the high and low trip threshold information for battery voltage and current. struct bcl_threshold { int trip_value; enum bcl_trip_type type; void (*trip_notify)(enum bcl_trip_type, int, void *); void *trip_data; }; - int msm_bcl_enable(void); - int msm_bcl_disable(void); Kernel driver should use these interfaces to enable and disable the BCL monitoring feature respectively. The msm_bcl_enable(void) call will set the thresholds for BCL monitoring and will enable hardware BCL monitoring. - int msm_bcl_set_threshold(enum bcl_param, enum bcl_trip_type, struct bcl_threshold *); Kernel drivers should use this interface to program the high trip and low trip thresholds along with their notification callbacks for battery voltage and current. - int msm_bcl_read(enum bcl_param, int *); Kernel driver can use this interface to read the last hardware sampled voltage and current reading in micro-volts and micro-ampere respectively. - struct bcl_param_data *msm_bcl_register_param(enum bcl_param, struct bcl_driver_ops *, char *); - int msm_bcl_unregister_param(struct bcl_param_data *); BCL hardware drivers should use this interface to register with the BCL framework. The BCL hardware drivers should define the ops functions required by the framework driver and provide that as input using the struct bcl_driver_ops parameter. The register function returns a struct bcl_param_data pointer on success, which can be used as input for unregistering the same from BCL framework driver. struct bcl_driver_ops { int (*read) (int *); int (*set_high_trip) (int); int (*get_high_trip) (int *); int (*set_low_trip) (int); int (*get_low_trip) (int *); int (*disable) (void); int (*enable) (void); int (*notify) (struct bcl_param_data *, int, enum bcl_trip_type); }; This BCL frmaework requires the BCL hardware drivers to have interfaces implementing the above functionalities. - read This function should read the last hardware sampled value. - set_high_trip - get_high_trip - set_low_trip - get_low_trip These functions should set and get the high trip and low trip. - disable - enable These functions should enable or disable the voltage or current param monitoring. -notify BCL framework populates this function pointer with a generic threshold handler interface. On reaching high/low trip, the BCL hardware driver should call this notification handler with appropriate input parameters. Change-Id: I5ee544bdf0bce7114f8251799ea32b121bb4464c Signed-off-by: Ram Chandrasekar <rkumbako@codeaurora.org>
2014-07-16 22:14:53 +02:00
if (!bcl[param_type]->bcl_attr_gp.attrs) {
pr_err("Sysfs attribute create failed.\n");
ret = -ENOMEM;
goto add_sysfs_exit;
}
BCL_DEFINE_RO_PARAM(bcl[param_type]->val_attr, value,
bcl[param_type]->bcl_attr_gp, 0);
BCL_DEFINE_RO_PARAM(bcl[param_type]->high_trip_attr, high_trip,
bcl[param_type]->bcl_attr_gp, 1);
BCL_DEFINE_RO_PARAM(bcl[param_type]->low_trip_attr, low_trip,
bcl[param_type]->bcl_attr_gp, 2);
bcl[param_type]->bcl_attr_gp.attrs[BCL_PARAM_MAX_ATTR] = NULL;
ret = sysfs_create_group(&bcl[param_type]->device.kobj,
&bcl[param_type]->bcl_attr_gp);
if (ret) {
pr_err("Failure to create sysfs nodes. err:%d", ret);
goto add_sysfs_exit;
}
add_sysfs_exit:
return ret;
}
static int msm_bcl_init(void)
{
int ret = 0, i = 0;
for (; i < BCL_PARAM_MAX; i++) {
bcl[i] = kzalloc(sizeof(struct bcl_param_data),
GFP_KERNEL);
if (!bcl[i]) {
pr_err("kzalloc failed\n");
while ((--i) >= 0)
kfree(bcl[i]);
return -ENOMEM;
}
}
return ret;
}
static int __init msm_bcl_init_driver(void)
{
int ret = 0;
ret = msm_bcl_init();
if (ret) {
pr_err("msm bcl init failed. err:%d\n", ret);
return ret;
}
return class_register(&msm_bcl_class);
}
static void __exit bcl_exit(void)
{
int i = 0;
for (; i < BCL_PARAM_MAX; i++) {
sysfs_remove_group(&bcl[i]->device.kobj,
&bcl[i]->bcl_attr_gp);
kfree(bcl[i]->bcl_attr_gp.attrs);
kfree(bcl[i]);
}
class_unregister(&msm_bcl_class);
}
fs_initcall(msm_bcl_init_driver);
module_exit(bcl_exit);