diff options
Diffstat (limited to 'drivers/thermal/msm_thermal-dev.c')
-rw-r--r-- | drivers/thermal/msm_thermal-dev.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/drivers/thermal/msm_thermal-dev.c b/drivers/thermal/msm_thermal-dev.c new file mode 100644 index 000000000000..e6af6b884e99 --- /dev/null +++ b/drivers/thermal/msm_thermal-dev.c @@ -0,0 +1,431 @@ +/* Copyright (c) 2013-2016, 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. + * + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/msm_thermal_ioctl.h> +#include <linux/msm_thermal.h> +#include <linux/uaccess.h> +#include <linux/cdev.h> +#include <linux/semaphore.h> +#include <linux/module.h> + +struct msm_thermal_ioctl_dev { + struct semaphore sem; + struct cdev char_dev; +}; + +static int msm_thermal_major; +static struct class *thermal_class; +static struct msm_thermal_ioctl_dev *msm_thermal_dev; +static unsigned int freq_table_len[NR_CPUS], freq_table_set[NR_CPUS]; +static unsigned int voltage_table_set[NR_CPUS]; +static unsigned int *freq_table_ptr[NR_CPUS]; +static uint32_t *voltage_table_ptr[NR_CPUS]; + +static int msm_thermal_ioctl_open(struct inode *node, struct file *filep) +{ + int ret = 0; + struct msm_thermal_ioctl_dev *dev; + + dev = container_of(node->i_cdev, struct msm_thermal_ioctl_dev, + char_dev); + filep->private_data = dev; + + return ret; +} + +static int msm_thermal_ioctl_release(struct inode *node, struct file *filep) +{ + pr_debug("%s: IOCTL: release\n", KBUILD_MODNAME); + return 0; +} + +static long validate_and_copy(unsigned int *cmd, unsigned long *arg, + struct msm_thermal_ioctl *query) +{ + long ret = 0, err_val = 0; + + if ((_IOC_TYPE(*cmd) != MSM_THERMAL_MAGIC_NUM) || + (_IOC_NR(*cmd) >= MSM_CMD_MAX_NR)) { + ret = -ENOTTY; + goto validate_exit; + } + + if (_IOC_DIR(*cmd) & _IOC_READ) { + err_val = !access_ok(VERIFY_WRITE, (void __user *)*arg, + _IOC_SIZE(*cmd)); + } else if (_IOC_DIR(*cmd) & _IOC_WRITE) { + err_val = !access_ok(VERIFY_READ, (void __user *)*arg, + _IOC_SIZE(*cmd)); + } + if (err_val) { + ret = -EFAULT; + goto validate_exit; + } + + if (copy_from_user(query, (void __user *)(*arg), + sizeof(struct msm_thermal_ioctl))) { + ret = -EACCES; + goto validate_exit; + } + + if (query->size != sizeof(struct msm_thermal_ioctl)) { + pr_err("%s: Invalid input argument size\n", __func__); + ret = -EINVAL; + goto validate_exit; + } + + switch (*cmd) { + case MSM_THERMAL_SET_CPU_MAX_FREQUENCY: + case MSM_THERMAL_SET_CPU_MIN_FREQUENCY: + if (query->cpu_freq.cpu_num >= num_possible_cpus()) { + pr_err("%s: Invalid CPU number: %u\n", __func__, + query->cpu_freq.cpu_num); + ret = -EINVAL; + goto validate_exit; + } + break; + default: + break; + } + +validate_exit: + return ret; +} + +static long msm_thermal_process_freq_table_req(struct msm_thermal_ioctl *query, + unsigned long *arg) +{ + long ret = 0; + uint32_t table_idx, idx = 0, cluster_id = query->clock_freq.cluster_num; + struct clock_plan_arg *clock_freq = &(query->clock_freq); + + if (cluster_id >= num_possible_cpus()) + return -EINVAL; + + if (!freq_table_len[cluster_id]) { + ret = msm_thermal_get_freq_plan_size(cluster_id, + &freq_table_len[cluster_id]); + if (ret) { + pr_err("%s: Cluster%d freq table length get err:%ld\n", + KBUILD_MODNAME, cluster_id, ret); + goto process_freq_exit; + } + if (!freq_table_len[cluster_id]) { + pr_err("%s: Cluster%d freq table empty\n", + KBUILD_MODNAME, cluster_id); + ret = -EAGAIN; + goto process_freq_exit; + } + + freq_table_set[cluster_id] = freq_table_len[cluster_id] + / MSM_IOCTL_FREQ_SIZE; + if (freq_table_len[cluster_id] % MSM_IOCTL_FREQ_SIZE) + freq_table_set[cluster_id]++; + + if (!freq_table_ptr[cluster_id]) { + freq_table_ptr[cluster_id] = kzalloc( + sizeof(unsigned int) * + freq_table_len[cluster_id], GFP_KERNEL); + if (!freq_table_ptr[cluster_id]) { + pr_err("%s: memory alloc failed\n", + KBUILD_MODNAME); + freq_table_len[cluster_id] = 0; + ret = -ENOMEM; + goto process_freq_exit; + } + } + ret = msm_thermal_get_cluster_freq_plan(cluster_id, + freq_table_ptr[cluster_id]); + if (ret) { + pr_err("%s: Error getting frequency table. err:%ld\n", + KBUILD_MODNAME, ret); + freq_table_len[cluster_id] = 0; + freq_table_set[cluster_id] = 0; + kfree(freq_table_ptr[cluster_id]); + freq_table_ptr[cluster_id] = NULL; + goto process_freq_exit; + } + } + + if (!clock_freq->freq_table_len) { + clock_freq->freq_table_len = freq_table_len[cluster_id]; + goto copy_and_return; + } + if (clock_freq->set_idx >= freq_table_set[cluster_id]) { + pr_err("%s: Invalid freq table set%d for cluster%d\n", + KBUILD_MODNAME, clock_freq->set_idx, + cluster_id); + ret = -EINVAL; + goto process_freq_exit; + } + + table_idx = MSM_IOCTL_FREQ_SIZE * clock_freq->set_idx; + for (; table_idx < freq_table_len[cluster_id] + && idx < MSM_IOCTL_FREQ_SIZE; idx++, table_idx++) { + clock_freq->freq_table[idx] = + freq_table_ptr[cluster_id][table_idx]; + } + clock_freq->freq_table_len = idx; + +copy_and_return: + ret = copy_to_user((void __user *)(*arg), query, + sizeof(struct msm_thermal_ioctl)); + if (ret) { + pr_err("%s: copy_to_user error:%ld.\n", KBUILD_MODNAME, ret); + goto process_freq_exit; + } + +process_freq_exit: + return ret; +} + +static long msm_thermal_process_voltage_table_req( + struct msm_thermal_ioctl *query, + unsigned long *arg) +{ + long ret = 0; + uint32_t table_idx = 0, idx = 0; + uint32_t cluster_id = query->voltage.cluster_num; + struct voltage_plan_arg *voltage = &(query->voltage); + + if (cluster_id >= num_possible_cpus()) + return -EINVAL; + + if (!voltage_table_ptr[cluster_id]) { + if (!freq_table_len[cluster_id]) { + ret = msm_thermal_get_freq_plan_size(cluster_id, + &freq_table_len[cluster_id]); + if (ret) { + pr_err( + "%s: Cluster%d freq table len err:%ld\n", + KBUILD_MODNAME, cluster_id, ret); + goto process_volt_exit; + } + if (!freq_table_len[cluster_id]) { + pr_err("%s: Cluster%d freq table empty\n", + KBUILD_MODNAME, cluster_id); + ret = -EAGAIN; + goto process_volt_exit; + } + } + voltage_table_ptr[cluster_id] = kzalloc( + sizeof(uint32_t) * + freq_table_len[cluster_id], GFP_KERNEL); + if (!voltage_table_ptr[cluster_id]) { + pr_err("%s: memory alloc failed\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto process_volt_exit; + } + ret = msm_thermal_get_cluster_voltage_plan(cluster_id, + voltage_table_ptr[cluster_id]); + if (ret) { + pr_err("%s: Error getting voltage table. err:%ld\n", + KBUILD_MODNAME, ret); + kfree(voltage_table_ptr[cluster_id]); + voltage_table_ptr[cluster_id] = NULL; + goto process_volt_exit; + } + } + + if (!voltage->voltage_table_len) { + voltage->voltage_table_len = freq_table_len[cluster_id]; + goto copy_and_return; + } + + voltage_table_set[cluster_id] = freq_table_len[cluster_id] + / MSM_IOCTL_FREQ_SIZE; + if (freq_table_len[cluster_id] % MSM_IOCTL_FREQ_SIZE) + voltage_table_set[cluster_id]++; + + if (voltage->set_idx >= voltage_table_set[cluster_id]) { + pr_err("%s: Invalid voltage table set%d for cluster%d\n", + KBUILD_MODNAME, voltage->set_idx, + cluster_id); + ret = -EINVAL; + goto process_volt_exit; + } + + table_idx = MSM_IOCTL_FREQ_SIZE * voltage->set_idx; + for (; table_idx < freq_table_len[cluster_id] + && idx < MSM_IOCTL_FREQ_SIZE; idx++, table_idx++) { + voltage->voltage_table[idx] = + voltage_table_ptr[cluster_id][table_idx]; + } + voltage->voltage_table_len = idx; + +copy_and_return: + ret = copy_to_user((void __user *)(*arg), query, + sizeof(struct msm_thermal_ioctl)); + if (ret) { + pr_err("%s: copy_to_user error:%ld.\n", KBUILD_MODNAME, ret); + goto process_volt_exit; + } + +process_volt_exit: + return ret; +} + +static long msm_thermal_ioctl_process(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + struct msm_thermal_ioctl query; + + pr_debug("%s: IOCTL: processing cmd:%u\n", KBUILD_MODNAME, cmd); + + ret = validate_and_copy(&cmd, &arg, &query); + if (ret) + goto process_exit; + + switch (cmd) { + case MSM_THERMAL_SET_CPU_MAX_FREQUENCY: + ret = msm_thermal_set_frequency(query.cpu_freq.cpu_num, + query.cpu_freq.freq_req, true); + break; + case MSM_THERMAL_SET_CPU_MIN_FREQUENCY: + ret = msm_thermal_set_frequency(query.cpu_freq.cpu_num, + query.cpu_freq.freq_req, false); + break; + case MSM_THERMAL_SET_CLUSTER_MAX_FREQUENCY: + ret = msm_thermal_set_cluster_freq(query.cpu_freq.cpu_num, + query.cpu_freq.freq_req, true); + break; + case MSM_THERMAL_SET_CLUSTER_MIN_FREQUENCY: + ret = msm_thermal_set_cluster_freq(query.cpu_freq.cpu_num, + query.cpu_freq.freq_req, false); + break; + case MSM_THERMAL_GET_CLUSTER_FREQUENCY_PLAN: + ret = msm_thermal_process_freq_table_req(&query, &arg); + break; + case MSM_THERMAL_GET_CLUSTER_VOLTAGE_PLAN: + ret = msm_thermal_process_voltage_table_req(&query, &arg); + break; + default: + ret = -ENOTTY; + goto process_exit; + } +process_exit: + return ret; +} + +#ifdef CONFIG_COMPAT +static long msm_thermal_compat_ioctl_process(struct file *filep, + unsigned int cmd, unsigned long arg) +{ + arg = (unsigned long)compat_ptr(arg); + return msm_thermal_ioctl_process(filep, cmd, arg); +} +#endif /* CONFIG_COMPAT */ + +static const struct file_operations msm_thermal_fops = { + .owner = THIS_MODULE, + .open = msm_thermal_ioctl_open, + .unlocked_ioctl = msm_thermal_ioctl_process, +#ifdef CONFIG_COMPAT + .compat_ioctl = msm_thermal_compat_ioctl_process, +#endif /* CONFIG_COMPAT */ + .release = msm_thermal_ioctl_release, +}; + +int msm_thermal_ioctl_init() +{ + int ret = 0; + dev_t thermal_dev; + struct device *therm_device; + + ret = alloc_chrdev_region(&thermal_dev, 0, 1, + MSM_THERMAL_IOCTL_NAME); + if (ret < 0) { + pr_err("%s: Error in allocating char device region. Err:%d\n", + KBUILD_MODNAME, ret); + goto ioctl_init_exit; + } + + msm_thermal_major = MAJOR(thermal_dev); + + thermal_class = class_create(THIS_MODULE, "msm_thermal"); + if (IS_ERR(thermal_class)) { + pr_err("%s: Error in creating class\n", + KBUILD_MODNAME); + ret = PTR_ERR(thermal_class); + goto ioctl_class_fail; + } + + therm_device = device_create(thermal_class, NULL, thermal_dev, NULL, + MSM_THERMAL_IOCTL_NAME); + if (IS_ERR(therm_device)) { + pr_err("%s: Error in creating character device\n", + KBUILD_MODNAME); + ret = PTR_ERR(therm_device); + goto ioctl_dev_fail; + } + msm_thermal_dev = kmalloc(sizeof(struct msm_thermal_ioctl_dev), + GFP_KERNEL); + if (!msm_thermal_dev) { + pr_err("%s: Error allocating memory\n", + KBUILD_MODNAME); + ret = -ENOMEM; + goto ioctl_clean_all; + } + + memset(msm_thermal_dev, 0, sizeof(struct msm_thermal_ioctl_dev)); + sema_init(&msm_thermal_dev->sem, 1); + cdev_init(&msm_thermal_dev->char_dev, &msm_thermal_fops); + ret = cdev_add(&msm_thermal_dev->char_dev, thermal_dev, 1); + if (ret < 0) { + pr_err("%s: Error in adding character device\n", + KBUILD_MODNAME); + goto ioctl_clean_all; + } + + return ret; + +ioctl_clean_all: + device_destroy(thermal_class, thermal_dev); +ioctl_dev_fail: + class_destroy(thermal_class); +ioctl_class_fail: + unregister_chrdev_region(thermal_dev, 1); +ioctl_init_exit: + return ret; +} + +void msm_thermal_ioctl_cleanup() +{ + uint32_t idx = 0; + dev_t thermal_dev = MKDEV(msm_thermal_major, 0); + + if (!msm_thermal_dev) { + pr_err("%s: Thermal IOCTL cleanup already done\n", + KBUILD_MODNAME); + return; + } + + for (; idx < num_possible_cpus(); idx++) { + kfree(freq_table_ptr[idx]); + kfree(voltage_table_ptr[idx]); + } + device_destroy(thermal_class, thermal_dev); + class_destroy(thermal_class); + cdev_del(&msm_thermal_dev->char_dev); + unregister_chrdev_region(thermal_dev, 1); + kfree(msm_thermal_dev); + msm_thermal_dev = NULL; + thermal_class = NULL; +} |