| /* 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); \ |
| _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_PARAM_MAX_ATTR + 1), GFP_KERNEL); |
| 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); |