diff options
Diffstat (limited to 'drivers/thermal/lmh_interface.c')
-rw-r--r-- | drivers/thermal/lmh_interface.c | 1211 |
1 files changed, 1211 insertions, 0 deletions
diff --git a/drivers/thermal/lmh_interface.c b/drivers/thermal/lmh_interface.c new file mode 100644 index 000000000000..e7a873b6d426 --- /dev/null +++ b/drivers/thermal/lmh_interface.c @@ -0,0 +1,1211 @@ +/* Copyright (c) 2014-2015, 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/device.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/sysfs.h> +#include <linux/rwsem.h> +#include <linux/debugfs.h> +#include <linux/thermal.h> +#include <linux/slab.h> +#include "lmh_interface.h" +#include <linux/string.h> +#include <linux/uaccess.h> + +#define LMH_MON_NAME "lmh_monitor" +#define LMH_ISR_POLL_DELAY "interrupt_poll_delay_msec" +#define LMH_TRACE_ENABLE "hw_trace_enable" +#define LMH_TRACE_INTERVAL "hw_trace_interval" +#define LMH_DBGFS_DIR "debug" +#define LMH_DBGFS_READ "data" +#define LMH_DBGFS_CONFIG_READ "config" +#define LMH_DBGFS_READ_TYPES "data_types" +#define LMH_DBGFS_CONFIG_TYPES "config_types" +#define LMH_TRACE_INTERVAL_XO_TICKS 250 + +struct lmh_mon_threshold { + long value; + bool active; +}; + +struct lmh_device_data { + char device_name[LMH_NAME_MAX]; + struct lmh_device_ops *device_ops; + uint32_t max_level; + int curr_level; + int *levels; + struct dentry *dev_parent; + struct dentry *max_lvl_fs; + struct dentry *curr_lvl_fs; + struct dentry *avail_lvl_fs; + struct list_head list_ptr; + struct rw_semaphore lock; + struct device dev; +}; + +struct lmh_mon_sensor_data { + struct list_head list_ptr; + char sensor_name[LMH_NAME_MAX]; + struct lmh_sensor_ops *sensor_ops; + struct rw_semaphore lock; + struct lmh_mon_threshold trip[LMH_TRIP_MAX]; + struct thermal_zone_device *tzdev; + enum thermal_device_mode mode; +}; + +struct lmh_mon_driver_data { + struct dentry *debugfs_parent; + struct dentry *poll_fs; + struct dentry *enable_hw_log; + struct dentry *hw_log_delay; + uint32_t hw_log_enable; + uint64_t hw_log_interval; + struct dentry *debug_dir; + struct dentry *debug_read; + struct dentry *debug_config; + struct dentry *debug_read_type; + struct dentry *debug_config_type; + struct lmh_debug_ops *debug_ops; +}; + +enum lmh_read_type { + LMH_DEBUG_READ_TYPE, + LMH_DEBUG_CONFIG_TYPE, + LMH_PROFILES, +}; + +static struct lmh_mon_driver_data *lmh_mon_data; +static struct class lmh_class_info = { + .name = "msm_limits", +}; +static DECLARE_RWSEM(lmh_mon_access_lock); +static LIST_HEAD(lmh_sensor_list); +static DECLARE_RWSEM(lmh_dev_access_lock); +static LIST_HEAD(lmh_device_list); + +#define LMH_CREATE_DEBUGFS_FILE(_node, _name, _mode, _parent, _data, _ops, \ + _ret) do { \ + _node = debugfs_create_file(_name, _mode, _parent, \ + _data, _ops); \ + if (IS_ERR(_node)) { \ + _ret = PTR_ERR(_node); \ + pr_err("Error creating debugfs file:%s. err:%d\n", \ + _name, _ret); \ + } \ + } while (0) + +#define LMH_CREATE_DEBUGFS_DIR(_node, _name, _parent, _ret) \ + do { \ + _node = debugfs_create_dir(_name, _parent); \ + if (IS_ERR(_node)) { \ + _ret = PTR_ERR(_node); \ + pr_err("Error creating debugfs dir:%s. err:%d\n", \ + _name, _ret); \ + } \ + } while (0) + +#define LMH_HW_LOG_FS(_name) \ +static int _name##_get(void *data, u64 *val) \ +{ \ + *val = lmh_mon_data->_name; \ + return 0; \ +} \ +static int _name##_set(void *data, u64 val) \ +{ \ + struct lmh_mon_sensor_data *lmh_sensor = data; \ + int ret = 0; \ + lmh_mon_data->_name = val; \ + if (lmh_mon_data->hw_log_enable) \ + ret = lmh_sensor->sensor_ops->enable_hw_log( \ + lmh_mon_data->hw_log_interval \ + , lmh_mon_data->hw_log_enable); \ + else \ + ret = lmh_sensor->sensor_ops->disable_hw_log(); \ + return ret; \ +} \ +DEFINE_SIMPLE_ATTRIBUTE(_name##_fops, _name##_get, _name##_set, \ + "%llu\n"); + +#define LMH_DEV_GET(_name) \ +static ssize_t _name##_get(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct lmh_device_data *lmh_dev = container_of(dev, \ + struct lmh_device_data, dev); \ + return snprintf(buf, LMH_NAME_MAX, "%d", lmh_dev->_name); \ +} \ + +LMH_HW_LOG_FS(hw_log_enable); +LMH_HW_LOG_FS(hw_log_interval); +LMH_DEV_GET(max_level); +LMH_DEV_GET(curr_level); + +static ssize_t curr_level_set(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct lmh_device_data *lmh_dev = container_of(dev, + struct lmh_device_data, dev); + int val = 0, ret = 0; + + ret = kstrtouint(buf, 0, &val); + if (ret < 0) { + pr_err("Invalid input [%s]. err:%d\n", buf, ret); + return ret; + } + return lmh_set_dev_level(lmh_dev->device_name, val); +} + +static ssize_t avail_level_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lmh_device_data *lmh_dev = container_of(dev, + struct lmh_device_data, dev); + uint32_t *type_list = NULL; + int ret = 0, count = 0, lvl_buf_count = 0, idx = 0; + char *lvl_buf = NULL; + + if (!lmh_dev || !lmh_dev->levels || !lmh_dev->max_level) { + pr_err("Invalid input\n"); + return -EINVAL; + } + type_list = lmh_dev->levels; + lvl_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!lvl_buf) { + pr_err("Error allocating memory\n"); + return -ENOMEM; + } + for (idx = 0; (idx < lmh_dev->max_level) && (lvl_buf_count < PAGE_SIZE) + ; idx++) { + count = snprintf(lvl_buf + lvl_buf_count, + PAGE_SIZE - lvl_buf_count, "%d ", + type_list[idx]); + if (count + lvl_buf_count >= PAGE_SIZE) { + pr_err("overflow.\n"); + break; + } + lvl_buf_count += count; + } + count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, + "\n"); + if (count + lvl_buf_count < PAGE_SIZE) + lvl_buf_count += count; + + count = snprintf(buf, lvl_buf_count + 1, lvl_buf); + if (count > PAGE_SIZE) { + pr_err("copy to user buf failed\n"); + ret = -EFAULT; + goto lvl_get_exit; + } + +lvl_get_exit: + kfree(lvl_buf); + return (ret) ? ret : count; +} + +static int lmh_create_dev_sysfs(struct lmh_device_data *lmh_dev) +{ + int ret = 0; + static DEVICE_ATTR(level, 0600, curr_level_get, curr_level_set); + static DEVICE_ATTR(available_levels, 0400, avail_level_get, NULL); + static DEVICE_ATTR(total_levels, 0400, max_level_get, NULL); + + lmh_dev->dev.class = &lmh_class_info; + dev_set_name(&lmh_dev->dev, "%s", lmh_dev->device_name); + ret = device_register(&lmh_dev->dev); + if (ret) { + pr_err("Error registering profile device. err:%d\n", ret); + return ret; + } + ret = device_create_file(&lmh_dev->dev, &dev_attr_level); + if (ret) { + pr_err("Error creating profile level sysfs node. err:%d\n", + ret); + goto dev_sysfs_exit; + } + ret = device_create_file(&lmh_dev->dev, &dev_attr_total_levels); + if (ret) { + pr_err("Error creating total level sysfs node. err:%d\n", + ret); + goto dev_sysfs_exit; + } + ret = device_create_file(&lmh_dev->dev, &dev_attr_available_levels); + if (ret) { + pr_err("Error creating available level sysfs node. err:%d\n", + ret); + goto dev_sysfs_exit; + } + +dev_sysfs_exit: + if (ret) + device_unregister(&lmh_dev->dev); + return ret; +} + +static int lmh_create_debugfs_nodes(struct lmh_mon_sensor_data *lmh_sensor) +{ + int ret = 0; + + lmh_mon_data->hw_log_enable = 0; + lmh_mon_data->hw_log_interval = LMH_TRACE_INTERVAL_XO_TICKS; + LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->enable_hw_log, LMH_TRACE_ENABLE, + 0600, lmh_mon_data->debugfs_parent, (void *)lmh_sensor, + &hw_log_enable_fops, ret); + if (ret) + goto create_debugfs_exit; + LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->hw_log_delay, LMH_TRACE_INTERVAL, + 0600, lmh_mon_data->debugfs_parent, (void *)lmh_sensor, + &hw_log_interval_fops, ret); + if (ret) + goto create_debugfs_exit; + +create_debugfs_exit: + if (ret) + debugfs_remove_recursive(lmh_mon_data->debugfs_parent); + return ret; +} + +static struct lmh_mon_sensor_data *lmh_match_sensor_ops( + struct lmh_sensor_ops *ops) +{ + struct lmh_mon_sensor_data *lmh_sensor = NULL; + + list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) { + if (lmh_sensor->sensor_ops == ops) + return lmh_sensor; + } + + return NULL; +} + +static struct lmh_mon_sensor_data *lmh_match_sensor_name(char *sensor_name) +{ + struct lmh_mon_sensor_data *lmh_sensor = NULL; + + list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) { + if (!strncasecmp(lmh_sensor->sensor_name, sensor_name, + LMH_NAME_MAX)) + return lmh_sensor; + } + + return NULL; +} + +static void lmh_evaluate_and_notify(struct lmh_mon_sensor_data *lmh_sensor, + long val) +{ + int idx = 0, trip = 0; + bool cond = false; + + for (idx = 0; idx < LMH_TRIP_MAX; idx++) { + if (!lmh_sensor->trip[idx].active) + continue; + if (idx == LMH_HIGH_TRIP) { + trip = THERMAL_TRIP_CONFIGURABLE_HI; + cond = (val >= lmh_sensor->trip[idx].value); + } else { + trip = THERMAL_TRIP_CONFIGURABLE_LOW; + cond = (val <= lmh_sensor->trip[idx].value); + } + if (cond) { + lmh_sensor->trip[idx].active = false; + thermal_sensor_trip(lmh_sensor->tzdev, trip, val); + } + } +} + +void lmh_update_reading(struct lmh_sensor_ops *ops, long trip_val) +{ + struct lmh_mon_sensor_data *lmh_sensor = NULL; + + if (!ops) { + pr_err("Invalid input\n"); + return; + } + + down_read(&lmh_mon_access_lock); + lmh_sensor = lmh_match_sensor_ops(ops); + if (!lmh_sensor) { + pr_err("Invalid ops\n"); + goto interrupt_exit; + } + down_write(&lmh_sensor->lock); + pr_debug("Sensor:[%s] intensity:%ld\n", lmh_sensor->sensor_name, + trip_val); + lmh_evaluate_and_notify(lmh_sensor, trip_val); +interrupt_exit: + if (lmh_sensor) + up_write(&lmh_sensor->lock); + up_read(&lmh_mon_access_lock); + return; +} + +static int lmh_sensor_read(struct thermal_zone_device *dev, int *val) +{ + int ret = 0; + struct lmh_mon_sensor_data *lmh_sensor; + + if (!val || !dev || !dev->devdata) { + pr_err("Invalid input\n"); + return -EINVAL; + } + lmh_sensor = dev->devdata; + down_read(&lmh_mon_access_lock); + down_read(&lmh_sensor->lock); + ret = lmh_sensor->sensor_ops->read(lmh_sensor->sensor_ops, (long *)val); + if (ret) { + pr_err("Error reading sensor:%s. err:%d\n", + lmh_sensor->sensor_name, ret); + goto unlock_and_exit; + } +unlock_and_exit: + up_read(&lmh_sensor->lock); + up_read(&lmh_mon_access_lock); + + return ret; +} + +static int lmh_get_mode(struct thermal_zone_device *dev, + enum thermal_device_mode *mode) +{ + struct lmh_mon_sensor_data *lmh_sensor; + + if (!dev || !dev->devdata || !mode) { + pr_err("Invalid input\n"); + return -EINVAL; + } + lmh_sensor = dev->devdata; + *mode = lmh_sensor->mode; + + return 0; +} + +static int lmh_get_trip_type(struct thermal_zone_device *dev, + int trip, enum thermal_trip_type *type) +{ + if (!type || !dev || !dev->devdata || trip < 0 + || trip >= LMH_TRIP_MAX) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + switch (trip) { + case LMH_HIGH_TRIP: + *type = THERMAL_TRIP_CONFIGURABLE_HI; + break; + case LMH_LOW_TRIP: + *type = THERMAL_TRIP_CONFIGURABLE_LOW; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int lmh_activate_trip(struct thermal_zone_device *dev, + int trip, enum thermal_trip_activation_mode mode) +{ + struct lmh_mon_sensor_data *lmh_sensor; + + if (!dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + lmh_sensor = dev->devdata; + down_read(&lmh_mon_access_lock); + down_write(&lmh_sensor->lock); + lmh_sensor->trip[trip].active = (mode == + THERMAL_TRIP_ACTIVATION_ENABLED); + up_write(&lmh_sensor->lock); + up_read(&lmh_mon_access_lock); + + return 0; +} + +static int lmh_get_trip_value(struct thermal_zone_device *dev, + int trip, int *value) +{ + struct lmh_mon_sensor_data *lmh_sensor; + + if (!dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX + || !value) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + lmh_sensor = dev->devdata; + down_read(&lmh_mon_access_lock); + down_read(&lmh_sensor->lock); + *value = lmh_sensor->trip[trip].value; + up_read(&lmh_sensor->lock); + up_read(&lmh_mon_access_lock); + + return 0; +} + +static int lmh_set_trip_value(struct thermal_zone_device *dev, + int trip, int value) +{ + struct lmh_mon_sensor_data *lmh_sensor; + + if (!dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + lmh_sensor = dev->devdata; + down_read(&lmh_mon_access_lock); + down_write(&lmh_sensor->lock); + lmh_sensor->trip[trip].value = value; + up_write(&lmh_sensor->lock); + up_read(&lmh_mon_access_lock); + + return 0; +} + +static struct thermal_zone_device_ops lmh_sens_ops = { + .get_temp = lmh_sensor_read, + .get_mode = lmh_get_mode, + .get_trip_type = lmh_get_trip_type, + .activate_trip_type = lmh_activate_trip, + .get_trip_temp = lmh_get_trip_value, + .set_trip_temp = lmh_set_trip_value, +}; + +static int lmh_register_sensor(struct lmh_mon_sensor_data *lmh_sensor) +{ + int ret = 0; + + lmh_sensor->tzdev = thermal_zone_device_register( + lmh_sensor->sensor_name, LMH_TRIP_MAX, + (1 << LMH_TRIP_MAX) - 1, lmh_sensor, &lmh_sens_ops, + NULL, 0 , 0); + if (IS_ERR_OR_NULL(lmh_sensor->tzdev)) { + ret = PTR_ERR(lmh_sensor->tzdev); + pr_err("Error registering sensor:[%s] with thermal. err:%d\n", + lmh_sensor->sensor_name, ret); + return ret; + } + + return ret; +} + +static int lmh_sensor_init(struct lmh_mon_sensor_data *lmh_sensor, + char *sensor_name, struct lmh_sensor_ops *ops) +{ + int idx = 0, ret = 0; + + strlcpy(lmh_sensor->sensor_name, sensor_name, LMH_NAME_MAX); + lmh_sensor->sensor_ops = ops; + ops->new_value_notify = lmh_update_reading; + for (idx = 0; idx < LMH_TRIP_MAX; idx++) { + lmh_sensor->trip[idx].value = 0; + lmh_sensor->trip[idx].active = false; + } + init_rwsem(&lmh_sensor->lock); + if (list_empty(&lmh_sensor_list) + && !lmh_mon_data->enable_hw_log) + lmh_create_debugfs_nodes(lmh_sensor); + list_add_tail(&lmh_sensor->list_ptr, &lmh_sensor_list); + + return ret; +} + +int lmh_sensor_register(char *sensor_name, struct lmh_sensor_ops *ops) +{ + int ret = 0; + struct lmh_mon_sensor_data *lmh_sensor = NULL; + + if (!sensor_name || !ops) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + if (!ops->read || !ops->enable_hw_log || !ops->disable_hw_log) { + pr_err("Invalid ops input for sensor:%s\n", sensor_name); + return -EINVAL; + } + down_write(&lmh_mon_access_lock); + if (lmh_match_sensor_name(sensor_name) + || lmh_match_sensor_ops(ops)) { + ret = -EEXIST; + pr_err("Sensor[%s] exists\n", sensor_name); + goto register_exit; + } + lmh_sensor = kzalloc(sizeof(struct lmh_mon_sensor_data), GFP_KERNEL); + if (!lmh_sensor) { + pr_err("kzalloc failed\n"); + ret = -ENOMEM; + goto register_exit; + } + ret = lmh_sensor_init(lmh_sensor, sensor_name, ops); + if (ret) { + pr_err("Error registering sensor:%s. err:%d\n", sensor_name, + ret); + kfree(lmh_sensor); + goto register_exit; + } + + pr_debug("Registered Sensor:[%s]\n", sensor_name); + +register_exit: + up_write(&lmh_mon_access_lock); + if (ret) + return ret; + ret = lmh_register_sensor(lmh_sensor); + if (ret) { + pr_err("Thermal Zone register failed for Sensor:[%s]\n" + , sensor_name); + return ret; + } + pr_debug("Registered Sensor:[%s]\n", sensor_name); + return ret; +} + +static void lmh_sensor_remove(struct lmh_sensor_ops *ops) +{ + struct lmh_mon_sensor_data *lmh_sensor = NULL; + + lmh_sensor = lmh_match_sensor_ops(ops); + if (!lmh_sensor) { + pr_err("No match for the sensor\n"); + goto deregister_exit; + } + down_write(&lmh_sensor->lock); + thermal_zone_device_unregister(lmh_sensor->tzdev); + list_del(&lmh_sensor->list_ptr); + up_write(&lmh_sensor->lock); + pr_debug("Deregistered sensor:[%s]\n", lmh_sensor->sensor_name); + kfree(lmh_sensor); + +deregister_exit: + return; +} + +void lmh_sensor_deregister(struct lmh_sensor_ops *ops) +{ + if (!ops) { + pr_err("Invalid input\n"); + return; + } + + down_write(&lmh_mon_access_lock); + lmh_sensor_remove(ops); + up_write(&lmh_mon_access_lock); + + return; +} + +static struct lmh_device_data *lmh_match_device_name(char *device_name) +{ + struct lmh_device_data *lmh_device = NULL; + + list_for_each_entry(lmh_device, &lmh_device_list, list_ptr) { + if (!strncasecmp(lmh_device->device_name, device_name, + LMH_NAME_MAX)) + return lmh_device; + } + + return NULL; +} + +static struct lmh_device_data *lmh_match_device_ops(struct lmh_device_ops *ops) +{ + struct lmh_device_data *lmh_device = NULL; + + list_for_each_entry(lmh_device, &lmh_device_list, list_ptr) { + if (lmh_device->device_ops == ops) + return lmh_device; + } + + return NULL; +} + +static int lmh_device_init(struct lmh_device_data *lmh_device, + char *device_name, struct lmh_device_ops *ops) +{ + int ret = 0; + + ret = ops->get_curr_level(ops, &lmh_device->curr_level); + if (ret) { + pr_err("Error getting curr level for Device:[%s]. err:%d\n", + device_name, ret); + goto dev_init_exit; + } + ret = ops->get_available_levels(ops, NULL); + if (ret <= 0) { + pr_err("Error getting max level for Device:[%s]. err:%d\n", + device_name, ret); + ret = (!ret) ? -EINVAL : ret; + goto dev_init_exit; + } + lmh_device->max_level = ret; + lmh_device->levels = kzalloc(lmh_device->max_level * sizeof(int), + GFP_KERNEL); + if (!lmh_device->levels) { + pr_err("No memory\n"); + ret = -ENOMEM; + goto dev_init_exit; + } + ret = ops->get_available_levels(ops, lmh_device->levels); + if (ret) { + pr_err("Error getting device:[%s] levels. err:%d\n", + device_name, ret); + goto dev_init_exit; + } + init_rwsem(&lmh_device->lock); + lmh_device->device_ops = ops; + strlcpy(lmh_device->device_name, device_name, LMH_NAME_MAX); + list_add_tail(&lmh_device->list_ptr, &lmh_device_list); + lmh_create_dev_sysfs(lmh_device); + +dev_init_exit: + if (ret) + kfree(lmh_device->levels); + return ret; +} + +int lmh_get_all_dev_levels(char *device_name, int *val) +{ + int ret = 0; + struct lmh_device_data *lmh_device = NULL; + + if (!device_name) { + pr_err("Invalid input\n"); + return -EINVAL; + } + down_read(&lmh_dev_access_lock); + lmh_device = lmh_match_device_name(device_name); + if (!lmh_device) { + pr_err("Invalid device:%s\n", device_name); + ret = -EINVAL; + goto get_all_lvl_exit; + } + down_read(&lmh_device->lock); + if (!val) { + ret = lmh_device->max_level; + goto get_all_lvl_exit; + } + memcpy(val, lmh_device->levels, + sizeof(int) * lmh_device->max_level); + +get_all_lvl_exit: + if (lmh_device) + up_read(&lmh_device->lock); + up_read(&lmh_dev_access_lock); + return ret; +} + +int lmh_set_dev_level(char *device_name, int curr_lvl) +{ + int ret = 0; + struct lmh_device_data *lmh_device = NULL; + + if (!device_name) { + pr_err("Invalid input\n"); + return -EINVAL; + } + down_read(&lmh_dev_access_lock); + lmh_device = lmh_match_device_name(device_name); + if (!lmh_device) { + pr_err("Invalid device:%s\n", device_name); + ret = -EINVAL; + goto set_dev_exit; + } + down_write(&lmh_device->lock); + curr_lvl = min(curr_lvl, lmh_device->levels[lmh_device->max_level - 1]); + curr_lvl = max(curr_lvl, lmh_device->levels[0]); + if (curr_lvl == lmh_device->curr_level) + goto set_dev_exit; + ret = lmh_device->device_ops->set_level(lmh_device->device_ops, + curr_lvl); + if (ret) { + pr_err("Error setting current level%d for device[%s]. err:%d\n", + curr_lvl, device_name, ret); + goto set_dev_exit; + } + pr_debug("Device:[%s] configured to level %d\n", device_name, curr_lvl); + lmh_device->curr_level = curr_lvl; + +set_dev_exit: + if (lmh_device) + up_write(&lmh_device->lock); + up_read(&lmh_dev_access_lock); + return ret; +} + +int lmh_get_curr_level(char *device_name, int *val) +{ + int ret = 0; + struct lmh_device_data *lmh_device = NULL; + + if (!device_name || !val) { + pr_err("Invalid input\n"); + return -EINVAL; + } + down_read(&lmh_dev_access_lock); + lmh_device = lmh_match_device_name(device_name); + if (!lmh_device) { + pr_err("Invalid device:%s\n", device_name); + ret = -EINVAL; + goto get_curr_level; + } + down_read(&lmh_device->lock); + ret = lmh_device->device_ops->get_curr_level(lmh_device->device_ops, + &lmh_device->curr_level); + if (ret) { + pr_err("Error getting device[%s] current level. err:%d\n", + device_name, ret); + goto get_curr_level; + } + *val = lmh_device->curr_level; + pr_debug("Device:%s current level:%d\n", device_name, *val); + +get_curr_level: + if (lmh_device) + up_read(&lmh_device->lock); + up_read(&lmh_dev_access_lock); + return ret; +} + +int lmh_device_register(char *device_name, struct lmh_device_ops *ops) +{ + int ret = 0; + struct lmh_device_data *lmh_device = NULL; + + if (!device_name || !ops) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + if (!ops->get_available_levels || !ops->get_curr_level + || !ops->set_level) { + pr_err("Invalid ops input for device:%s\n", device_name); + return -EINVAL; + } + + down_write(&lmh_dev_access_lock); + if (lmh_match_device_name(device_name) + || lmh_match_device_ops(ops)) { + ret = -EEXIST; + pr_err("Device[%s] allready exists\n", device_name); + goto register_exit; + } + lmh_device = kzalloc(sizeof(struct lmh_device_data), GFP_KERNEL); + if (!lmh_device) { + pr_err("kzalloc failed\n"); + ret = -ENOMEM; + goto register_exit; + } + ret = lmh_device_init(lmh_device, device_name, ops); + if (ret) { + pr_err("Error registering device:%s. err:%d\n", device_name, + ret); + kfree(lmh_device); + goto register_exit; + } + + pr_debug("Registered Device:[%s] with %d levels\n", device_name, + lmh_device->max_level); + +register_exit: + up_write(&lmh_dev_access_lock); + return ret; +} + +static void lmh_device_remove(struct lmh_device_ops *ops) +{ + struct lmh_device_data *lmh_device = NULL; + + lmh_device = lmh_match_device_ops(ops); + if (!lmh_device) { + pr_err("No match for the device\n"); + goto deregister_exit; + } + down_write(&lmh_device->lock); + list_del(&lmh_device->list_ptr); + pr_debug("Deregistered device:[%s]\n", lmh_device->device_name); + kfree(lmh_device->levels); + up_write(&lmh_device->lock); + kfree(lmh_device); + +deregister_exit: + return; +} + +void lmh_device_deregister(struct lmh_device_ops *ops) +{ + if (!ops) { + pr_err("Invalid input\n"); + return; + } + + down_write(&lmh_dev_access_lock); + lmh_device_remove(ops); + up_write(&lmh_dev_access_lock); + return; +} + +static int lmh_parse_and_extract(const char __user *user_buf, size_t count, + enum lmh_read_type type) +{ + char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; + char *next_line = NULL; + int ret = 0, data_ct = 0, i = 0, size = 0; + uint32_t *config_buf = NULL; + + /* Allocate two extra space to add ';' character and NULL terminate */ + local_buf = kzalloc(count + 2, GFP_KERNEL); + if (!local_buf) { + ret = -ENOMEM; + goto dfs_cfg_write_exit; + } + if (copy_from_user(local_buf, user_buf, count)) { + pr_err("user buf error\n"); + ret = -EFAULT; + goto dfs_cfg_write_exit; + } + size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); + local_buf[size - 2] = ';'; + local_buf[size - 1] = '\0'; + curr_ptr = next_line = local_buf; + while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) + != NULL) { + data_ct = 0; + *token1 = '\0'; + curr_ptr = next_line; + next_line = token1 + 1; + for (token = (char *)curr_ptr; token && + ((token = strnchr(token, next_line - token, ' ')) + != NULL); token++) + data_ct++; + if (data_ct < 2) { + pr_err("Invalid format string:[%s]\n", curr_ptr); + ret = -EINVAL; + goto dfs_cfg_write_exit; + } + config_buf = kzalloc((++data_ct) * sizeof(uint32_t), + GFP_KERNEL); + if (!config_buf) { + ret = -ENOMEM; + goto dfs_cfg_write_exit; + } + pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); + for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); + i++) { + token = strnchr(token, next_line - token, ' '); + if (token) + *token = '\0'; + ret = kstrtouint(curr_ptr, 0, &config_buf[i]); + if (ret < 0) { + pr_err("Data[%s] scan error. err:%d\n", + curr_ptr, ret); + kfree(config_buf); + goto dfs_cfg_write_exit; + } + if (token) + curr_ptr = ++token; + } + switch (type) { + case LMH_DEBUG_READ_TYPE: + ret = lmh_mon_data->debug_ops->debug_config_read( + lmh_mon_data->debug_ops, config_buf, data_ct); + break; + case LMH_DEBUG_CONFIG_TYPE: + ret = lmh_mon_data->debug_ops->debug_config_lmh( + lmh_mon_data->debug_ops, config_buf, data_ct); + break; + default: + ret = -EINVAL; + break; + } + kfree(config_buf); + if (ret) { + pr_err("Config error. type:%d err:%d\n", type, ret); + goto dfs_cfg_write_exit; + } + } + +dfs_cfg_write_exit: + kfree(local_buf); + return ret; +} + +static ssize_t lmh_dbgfs_config_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + lmh_parse_and_extract(user_buf, count, LMH_DEBUG_CONFIG_TYPE); + return count; +} + +static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) +{ + uint32_t *read_buf = NULL; + int ret = 0, i = 0; + + ret = lmh_mon_data->debug_ops->debug_read(lmh_mon_data->debug_ops, + &read_buf); + if (ret <= 0 || !read_buf) + goto dfs_read_exit; + + do { + seq_printf(seq_fp, "0x%x ", read_buf[i]); + i++; + if ((i % LMH_READ_LINE_LENGTH) == 0) + seq_puts(seq_fp, "\n"); + } while (i < (ret / sizeof(uint32_t))); + +dfs_read_exit: + return (ret < 0) ? ret : 0; +} + +static ssize_t lmh_dbgfs_data_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + lmh_parse_and_extract(user_buf, count, LMH_DEBUG_READ_TYPE); + return count; +} + +static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) +{ + return single_open(file, lmh_dbgfs_data_read, inode->i_private); +} + +static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) +{ + int ret = 0, idx = 0, size = 0; + uint32_t *type_list = NULL; + + switch (type) { + case LMH_DEBUG_READ_TYPE: + ret = lmh_mon_data->debug_ops->debug_get_types( + lmh_mon_data->debug_ops, true, &type_list); + break; + case LMH_DEBUG_CONFIG_TYPE: + ret = lmh_mon_data->debug_ops->debug_get_types( + lmh_mon_data->debug_ops, false, &type_list); + break; + default: + return -EINVAL; + } + if (ret <= 0 || !type_list) { + pr_err("No device information. err:%d\n", ret); + return -ENODEV; + } + size = ret; + for (idx = 0; idx < size; idx++) + seq_printf(seq_fp, "0x%x ", type_list[idx]); + seq_puts(seq_fp, "\n"); + + return 0; +} + +static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) +{ + return lmh_get_types(seq_fp, LMH_DEBUG_READ_TYPE); +} + +static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) +{ + return single_open(file, lmh_dbgfs_read_type, inode->i_private); +} + +static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) +{ + return lmh_get_types(seq_fp, LMH_DEBUG_CONFIG_TYPE); +} + +static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) +{ + return single_open(file, lmh_dbgfs_config_type, inode->i_private); +} + +static const struct file_operations lmh_dbgfs_config_fops = { + .write = lmh_dbgfs_config_write, +}; +static const struct file_operations lmh_dbgfs_read_fops = { + .open = lmh_dbgfs_data_open, + .read = seq_read, + .write = lmh_dbgfs_data_write, + .llseek = seq_lseek, + .release = single_release, +}; +static const struct file_operations lmh_dbgfs_read_type_fops = { + .open = lmh_dbgfs_read_type_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +static const struct file_operations lmh_dbgfs_config_type_fops = { + .open = lmh_dbgfs_config_type_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int lmh_debug_register(struct lmh_debug_ops *ops) +{ + int ret = 0; + + if (!ops || !ops->debug_read || !ops->debug_config_read + || !ops->debug_get_types) { + pr_err("Invalid input"); + ret = -EINVAL; + goto dbg_reg_exit; + } + + lmh_mon_data->debug_ops = ops; + LMH_CREATE_DEBUGFS_DIR(lmh_mon_data->debug_dir, LMH_DBGFS_DIR, + lmh_mon_data->debugfs_parent, ret); + if (ret) + goto dbg_reg_exit; + + LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read, LMH_DBGFS_READ, 0600, + lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_fops, ret); + if (!lmh_mon_data->debug_read) { + pr_err("Error creating" LMH_DBGFS_READ "entry.\n"); + ret = -ENODEV; + goto dbg_reg_exit; + } + LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config, + LMH_DBGFS_CONFIG_READ, 0200, lmh_mon_data->debug_dir, NULL, + &lmh_dbgfs_config_fops, ret); + if (!lmh_mon_data->debug_config) { + pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); + ret = -ENODEV; + goto dbg_reg_exit; + } + LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read_type, + LMH_DBGFS_READ_TYPES, 0400, lmh_mon_data->debug_dir, NULL, + &lmh_dbgfs_read_type_fops, ret); + if (!lmh_mon_data->debug_read_type) { + pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); + ret = -ENODEV; + goto dbg_reg_exit; + } + LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config_type, + LMH_DBGFS_CONFIG_TYPES, 0400, lmh_mon_data->debug_dir, NULL, + &lmh_dbgfs_config_type_fops, ret); + if (!lmh_mon_data->debug_config_type) { + pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); + ret = -ENODEV; + goto dbg_reg_exit; + } + +dbg_reg_exit: + if (ret) { + /*Clean up all the dbg nodes*/ + debugfs_remove_recursive(lmh_mon_data->debug_dir); + lmh_mon_data->debug_ops = NULL; + } + + return ret; +} + +static int lmh_mon_init_driver(void) +{ + int ret = 0; + + lmh_mon_data = kzalloc(sizeof(struct lmh_mon_driver_data), + GFP_KERNEL); + if (!lmh_mon_data) { + pr_err("No memory\n"); + return -ENOMEM; + } + + LMH_CREATE_DEBUGFS_DIR(lmh_mon_data->debugfs_parent, LMH_MON_NAME, + NULL, ret); + if (ret) + goto init_exit; + lmh_mon_data->poll_fs = debugfs_create_u32(LMH_ISR_POLL_DELAY, 0600, + lmh_mon_data->debugfs_parent, &lmh_poll_interval); + if (IS_ERR(lmh_mon_data->poll_fs)) + pr_err("Error creating debugfs:[%s]. err:%ld\n", + LMH_ISR_POLL_DELAY, PTR_ERR(lmh_mon_data->poll_fs)); + +init_exit: + if (ret == -ENODEV) + ret = 0; + return ret; +} + +static int __init lmh_mon_init_call(void) +{ + int ret = 0; + + ret = lmh_mon_init_driver(); + if (ret) { + pr_err("Error initializing the debugfs. err:%d\n", ret); + goto lmh_init_exit; + } + ret = class_register(&lmh_class_info); + if (ret) + goto lmh_init_exit; + +lmh_init_exit: + if (ret) + class_unregister(&lmh_class_info); + return ret; +} + +static void lmh_mon_cleanup(void) +{ + down_write(&lmh_mon_access_lock); + while (!list_empty(&lmh_sensor_list)) { + lmh_sensor_remove(list_first_entry(&lmh_sensor_list, + struct lmh_mon_sensor_data, list_ptr)->sensor_ops); + } + up_write(&lmh_mon_access_lock); + debugfs_remove_recursive(lmh_mon_data->debugfs_parent); + kfree(lmh_mon_data); +} + +static void lmh_device_cleanup(void) +{ + down_write(&lmh_dev_access_lock); + while (!list_empty(&lmh_device_list)) { + lmh_device_remove(list_first_entry(&lmh_device_list, + struct lmh_device_data, list_ptr)->device_ops); + } + up_write(&lmh_dev_access_lock); +} + +static void lmh_debug_cleanup(void) +{ + if (lmh_mon_data->debug_ops) { + debugfs_remove_recursive(lmh_mon_data->debug_dir); + lmh_mon_data->debug_ops = NULL; + } +} + +static void __exit lmh_mon_exit(void) +{ + lmh_mon_cleanup(); + lmh_device_cleanup(); + lmh_debug_cleanup(); + class_unregister(&lmh_class_info); +} + +module_init(lmh_mon_init_call); +module_exit(lmh_mon_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("LMH monitor driver"); +MODULE_ALIAS("platform:" LMH_MON_NAME); |