diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/thermal/sprd_cpu_cooling.c | |
Diffstat (limited to 'drivers/thermal/sprd_cpu_cooling.c')
| -rw-r--r-- | drivers/thermal/sprd_cpu_cooling.c | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/drivers/thermal/sprd_cpu_cooling.c b/drivers/thermal/sprd_cpu_cooling.c new file mode 100644 index 00000000..29e14bc3 --- /dev/null +++ b/drivers/thermal/sprd_cpu_cooling.c @@ -0,0 +1,604 @@ +/* + * linux/drivers/thermal/sprd_cpu_cooling.c + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/thermal.h> +#include <linux/sprd_cpu_cooling.h> +#include <linux/platform_device.h> +#include <linux/cpumask.h> +#ifdef CONFIG_OF +#include <linux/of_device.h> +#endif + +#define SPRDCPU_DEBUG__ +#ifdef SPRDCPU_DEBUG__ +#define SPRDCPU_DEBUG(format, arg...) pr_info("cpu-cooling: " format, ## arg) +#else +#define SPRDCPU_DEBUG(format, arg...) +#endif + +struct cpu_cooling_param_t { + int state; + int max_core; + int limit_freq; + int low_freq; + int low_vol; +}; + +struct thermal_cooling_info_t { + unsigned long cooling_state; + struct thermal_cooling_device *cdev; + struct sprd_cpu_cooling_platform_data *pdata; + int max_state; + int enable; + int cluster; + struct cpu_cooling_param_t param; +#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8) + int binning; +#endif +}; + +static ssize_t sprd_cpu_store_caliberate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t sprd_cpu_show_caliberate(struct device *dev, + struct device_attribute *attr, + char *buf); + +/* sys I/F for cooling device */ +#define to_cooling_device(_dev) \ + container_of(_dev, struct thermal_cooling_device, device) + +#define SPRD_CPU_CALIBERATE_ATTR(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IRUGO | S_IWUSR | S_IWGRP,}, \ + .show = sprd_cpu_show_caliberate, \ + .store = sprd_cpu_store_caliberate, \ +} +#define SPRD_CPU_CALIBERATE_ATTR_RO(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IRUGO, }, \ + .show = sprd_cpu_show_caliberate, \ +} +#define SPRD_CPU_CALIBERATE_ATTR_WO(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IWUSR | S_IWGRP, }, \ + .store = sprd_cpu_store_caliberate, \ +} + +static struct device_attribute sprd_cpu_caliberate[] = { + SPRD_CPU_CALIBERATE_ATTR(cur_ctrl_param), +}; + +static int sprd_cpu_creat_caliberate_attr(struct device *dev) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(sprd_cpu_caliberate); i++) { + rc = device_create_file(dev, &sprd_cpu_caliberate[i]); + if (rc) + goto sprd_attrs_failed; + } + goto sprd_attrs_succeed; + +sprd_attrs_failed: + while (i--) + device_remove_file(dev, &sprd_cpu_caliberate[i]); + +sprd_attrs_succeed: + return rc; +} + +static int sprd_cpu_remove_caliberate_attr(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sprd_cpu_caliberate); i++) { + device_remove_file(dev, &sprd_cpu_caliberate[i]); + } + return 0; +} + + +static ssize_t sprd_cpu_show_caliberate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i = 0; + int offset = 0; + struct thermal_cooling_device *cdev = to_cooling_device(dev); + struct thermal_cooling_info_t *info = cdev->devdata; + unsigned long *data = &info->param; + int len = sizeof(info->param) / sizeof(info->param.state); + + for (i = 0; i < len; i++) + offset += sprintf(buf + offset, "%ld,", data[i]); + buf[offset - 1] = '\n'; + + SPRDCPU_DEBUG("dev_attr_cur_ctrl_param: %s\n", buf); + return offset; +} + +#define BUF_LEN 64 +static ssize_t sprd_cpu_store_caliberate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int i = 0; + char *str = NULL; + char *pbuf; + char *after = NULL; + char buffer[BUF_LEN]; + struct thermal_cooling_device *cdev = to_cooling_device(dev); + struct thermal_cooling_info_t *info = cdev->devdata; + int *data = &info->param; + int len = sizeof(info->param) / sizeof(info->param.state); + + if (count > BUF_LEN){ + count = BUF_LEN; + } + strncpy(buffer, buf, count); + buffer[count] = '\0'; + printk("buf param form usespace:%s\n", buf); + printk("buffer parm:%s\n", buffer); + pbuf = buffer; + while ((pbuf!= NULL) && (i < len)) + { + str = strsep(&pbuf, ","); + data[i++] = simple_strtoul(str, &after, 0); + } + printk("sprd_cpu_cooling_param_t.status: %d\n", info->param.state); + printk("sprd_cpu_cooling_param_t.core_num: %d\n", info->param.max_core); + printk("sprd_cpu_cooling_param_t.freq: %d\n", info->param.limit_freq); + printk("sprd_cpu_cooling_param_t.low_freq: %d\n", info->param.low_freq); + printk("sprd_cpu_cooling_param_t.low_vol: %d\n", info->param.low_vol); + + cpufreq_thermal_limit(info->cluster, info->param.limit_freq); + cpu_core_thermal_limit(info->cluster, info->param.max_core); +#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8) + if (info->binning == 0){ +#endif + if (info->param.low_freq > 0 && info->param.low_vol > 0){ + cpufreq_table_thermal_update(info->param.low_freq, info->param.low_vol); + info->param.low_freq = 0; + info->param.low_vol = 0; + } +#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8) + } +#endif + + return count; +} + +#if !defined(CONFIG_SPRD_CPU_DYNAMIC_HOTPLUG) && !defined(CONFIG_CPU_FREQ_GOV_SPRDEMAND) +int cpu_core_thermal_limit(int cluster, int max_core) +{ +#ifdef CONFIG_HOTPLUG_CPU + int cpuid, cpus; + int first_cpu, last_cpu; + + if (cluster){ + first_cpu = NR_CPUS / 2; + last_cpu = NR_CPUS - 1; + }else{ + first_cpu = 0; +#ifdef CONFIG_SCHED_HMP + last_cpu = NR_CPUS / 2 -1; +#else + last_cpu = NR_CPUS - 1; +#endif + } + for (cpus = 0, cpuid = first_cpu; cpuid <= last_cpu; ++cpuid){ + if (cpu_online(cpuid)){ + cpus++; + } + } + if (max_core == cpus){ + return 0; + } + if (cpus < max_core){ + /* plug cpu */ + for (cpuid = first_cpu; cpuid <= last_cpu; ++cpuid){ + if (cpu_online(cpuid)){ + continue; + } + pr_info("cpu-cooling: we gonna plugin cpu%d !!\n", cpuid); + if (cpu_up(cpuid)){ + pr_info("plug cpu%d failed!\n", cpuid); + } + if (++cpus >= max_core){ + return 0; + } + } + }else{ + /* unplug cpu */ + for (cpuid = last_cpu; cpuid >= first_cpu; --cpuid){ + if (!cpu_online(cpuid)){ + continue; + } + pr_info("cpu-cooling: we gonna unplug cpu%d !!\n", cpuid); + if (cpu_down(cpuid)){ + pr_info("unplug cpu%d failed!\n", cpuid); + } + if (--cpus <= max_core){ + return 0; + } + } + } +#endif + + return 0; +} +#endif + +static int get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct thermal_cooling_info_t *info = cdev->devdata; + int ret = 0; + + *state = info->max_state; + + return ret; +} + +static int get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct thermal_cooling_info_t *info = cdev->devdata; + int ret = 0; + + *state = info->cooling_state; + + return ret; +} + +static int thermal_set_vddarm(struct thermal_cooling_info_t *c_info, + unsigned long state) +{ + int i, j; + struct vddarm_update *pvddarm = c_info->pdata->vddarm_update; + struct freq_vddarm *pfreq_vddarm; + + if (pvddarm == NULL){ + printk("%s vddarm_update isn't configed.\n", __func__); + return -1; + } + for (i = 0; pvddarm[i].freq_vddarm; ++i){ + if (state != pvddarm[i].state){ + continue; + } + pfreq_vddarm = pvddarm[i].freq_vddarm; + for (j = 0; pfreq_vddarm[j].freq; ++j){ + cpufreq_table_thermal_update(pfreq_vddarm[j].freq, + pfreq_vddarm[j].vddarm_mv); + } + } + + return 0; +} + +static int set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct thermal_cooling_info_t *c_info = cdev->devdata; + int max_core; + int limit_freq; + + if (c_info->max_state <= 0){ + return 0; + } + thermal_set_vddarm(c_info, state); + + if (c_info->enable && c_info->cooling_state == state){ + return 0; + }else{ + c_info->cooling_state = state; + } + + limit_freq = c_info->pdata->cpu_state[state].max_freq; + max_core = c_info->pdata->cpu_state[state].max_core; + c_info->enable = 1; + pr_info("%s %s: %d limit_freq: %d kHz max_core: %d\n", + cdev->type, __func__, state, limit_freq, max_core); + cpufreq_thermal_limit(c_info->cluster, limit_freq); + cpu_core_thermal_limit(c_info->cluster, max_core); + + return 0; +} + +static struct thermal_cooling_device_ops sprd_cpufreq_cooling_ops = { + .get_max_state = get_max_state, + .get_cur_state = get_cur_state, + .set_cur_state = set_cur_state, +}; + +#ifdef CONFIG_OF +static int get_vddarm_updata_dt_data(struct device_node *np, + struct sprd_cpu_cooling_platform_data *pdata) +{ + struct vddarm_update *pvddarm; + int vddarm_nr[MAX_CPU_STATE]; + char name[32]; + int data[32]; + int freq_nr; + int i, j, k, ret; + + ret = of_property_read_u32_array(np, "vddarm_nr", vddarm_nr, 1); + if(ret){ + printk(KERN_ERR "fail to get vddarm_nr\n"); + goto error; + } + + ret = of_property_read_u32_array(np, "vddarm_nr", vddarm_nr, vddarm_nr[0] + 1); + if (ret){ + printk(KERN_ERR "fail to get all vddarm_nr data.\n"); + goto error; + } + printk("%s vddarm_nr:<", __func__); + for (i = 0; i < vddarm_nr[0]; ++i){ + printk("%d ", vddarm_nr[i + 1]); + } + printk(">\n"); + + pvddarm = kzalloc(sizeof(*pvddarm) * (vddarm_nr[0] + 1), GFP_KERNEL); + if (pvddarm == NULL){ + goto error; + } + + for (i = 0; i < vddarm_nr[0]; ++i){ + sprintf(name, "vddarm_update%d", i); + ret = of_property_read_u32_array(np, name, data, vddarm_nr[i + 1]); + if(ret){ + printk(KERN_ERR "fail to get %s len:%d\n", name, vddarm_nr[i + 1]); + goto vddarm_err; + } + freq_nr = (vddarm_nr[i + 1] - 1) / 2; + pvddarm[i].freq_vddarm = kzalloc(sizeof(struct freq_vddarm) * (freq_nr + 1), GFP_KERNEL); + if (pvddarm[i].freq_vddarm == NULL){ + goto vddarm_err; + } + printk("%s state: <", __func__); + for (j = 0; j < vddarm_nr[i + 1]; ++j){ + printk("%d ", data[j]); + } + printk(">\n"); + pvddarm[i].state = data[0]; + for (j = 0, k = 1; j < freq_nr; ++j){ + pvddarm[i].freq_vddarm[j].freq = data[k++]; + pvddarm[i].freq_vddarm[j].vddarm_mv = data[k++]; + } + pvddarm[i].freq_vddarm[j].freq = 0; + } + pvddarm[i].freq_vddarm = NULL; + pdata->vddarm_update = pvddarm; + + return 0; +vddarm_err: + for (j = 0; j < i; ++j){ + if (pvddarm[j].freq_vddarm){ + kfree(pvddarm[j].freq_vddarm); + pvddarm[j].freq_vddarm = NULL; + } + } + kfree(pvddarm); +error: + pdata->vddarm_update = NULL; + return -1; +} + +static struct sprd_cpu_cooling_platform_data *get_cpu_cooling_dt_data( + struct device *dev, struct thermal_cooling_info_t *info) +{ + struct sprd_cpu_cooling_platform_data *pdata = NULL; + struct device_node *np = dev->of_node; + int max_freq[MAX_CPU_STATE]; + int max_core[MAX_CPU_STATE]; + int ret, i; + + if (!np) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "could not allocate memory for platform data\n"); + return -1; + } + ret = of_property_read_u32(np, "cluster", &info->cluster); + if(ret){ + info->cluster = 0; + } + dev_info(dev, "cluster: %s\n", info->cluster ? "big" : "little"); + ret = of_property_read_u32(np, "state_num", &pdata->state_num); + if(ret || pdata->state_num <= 0){ + dev_info(dev, "no state_num dts config, Don't use kernel thermal policy.\n"); + pdata->state_num = 0; + info->max_state = 0; + goto done; + } + dev_info(dev, "state_num=%d\n", pdata->state_num); + ret = of_property_read_u32_array(np, "max_freq", max_freq, pdata->state_num); + if(ret){ + dev_err(dev, "fail to get max_freq\n"); + goto error; + } + ret = of_property_read_u32_array(np, "max_core", max_core, pdata->state_num); + if(ret){ + dev_err(dev, "fail to get max_core\n"); + goto error; + } + for (i = 0; i < pdata->state_num; ++i){ + pdata->cpu_state[i].max_freq = max_freq[i]; + pdata->cpu_state[i].max_core = max_core[i]; + dev_info(dev, "state:%d, max_freq:%d, max_core:%d\n", i, max_freq[i], max_core[i]); + } + info->max_state = pdata->state_num - 1; +done: + get_vddarm_updata_dt_data(np, pdata); + + return pdata; + +error: + kfree(pdata); + pdata = NULL; + return pdata; +} +#endif + +#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8) +extern int sci_efuse_Dhryst_binning_get(int *val); +#endif +static int sprd_cpu_cooling_probe(struct platform_device *pdev) +{ + struct thermal_cooling_info_t *info = NULL; + struct sprd_cpu_cooling_platform_data *pdata = NULL; + char cdev_name[32]; + int ret = 0; +#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8) + int val = 0; +#endif + + if (NULL == pdev){ + return -1; + } + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "could not allocate memory for info data\n"); + return -1; + } +#ifdef CONFIG_OF + ret = of_property_read_u32(pdev->dev.of_node, "id", &pdev->id); + if(ret || pdev->id < 0){ + pdev->id = 0; + } + pdata = get_cpu_cooling_dt_data(&pdev->dev, info); + if (!pdata){ + ret = -1; + goto err; + } + pdev->dev.platform_data = pdata; +#else + pdata = dev_get_platdata(&pdev->dev); + if (NULL == pdata){ + dev_err(&pdev->dev, "%s platform data is NULL!\n", __func__); + ret = -1; + goto err; + } + info->max_state = pdata->state_num - 1; +#endif + + pdata->devdata = info; + info->pdata = pdata; + info->cooling_state = 0, + info->enable = 0, + + info->param.state = 0; + info->param.max_core = 0; + info->param.limit_freq = 0; + info->param.low_freq = 0; + info->param.low_vol = 0; +#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8) + sci_efuse_Dhryst_binning_get(&val); + dev_info(&pdev->dev, "sci_efuse_Dhryst_binning_get--val : %d\n", val); + if ((val < 28) && (val >= 22)){ + info->binning = 1; + }else{ + info->binning = 0; + } + dev_info(&pdev->dev, "binning : %d\n", info->binning); +#endif + + sprintf(cdev_name, "thermal-cpufreq-%d", pdev->id); + info->cdev = thermal_cooling_device_register(cdev_name, info, + &sprd_cpufreq_cooling_ops); + if (IS_ERR(info->cdev)){ + ret = PTR_ERR(info->cdev); + goto err; + } + + sprd_cpu_creat_caliberate_attr(&info->cdev->device); + + return ret; +err: + kfree(info); + return ret; +} + +static int sprd_cpu_cooling_remove(struct platform_device *pdev) +{ + struct sprd_cpu_cooling_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct thermal_cooling_info_t *info = pdata->devdata; + + sprd_cpu_remove_caliberate_attr(&info->cdev->device); + +#ifdef CONFIG_OF + int i; + + if (pdata->vddarm_update){ + for (i = 0; pdata->vddarm_update[i].freq_vddarm; ++i){ + kfree(pdata->vddarm_update[i].freq_vddarm); + pdata->vddarm_update[i].freq_vddarm = NULL; + } + kfree(pdata->vddarm_update); + } + kfree(pdata); + pdata = NULL; +#endif + thermal_cooling_device_unregister(info->cdev); + kfree(info); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id cpu_cooling_of_match[] = { + { .compatible = "sprd,sprd-cpu-cooling", }, + { } +}; +#endif + +static struct platform_driver cpu_cooling_driver = { + .probe = sprd_cpu_cooling_probe, + .remove = sprd_cpu_cooling_remove, + .driver = { + .owner = THIS_MODULE, + .name = "sprd-cpu-cooling", +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(cpu_cooling_of_match), +#endif + }, +}; + + +static int __init sprd_cpu_cooling_init(void) +{ + return platform_driver_register(&cpu_cooling_driver); +} + +static void __exit sprd_cpu_cooling_exit(void) +{ + platform_driver_unregister(&cpu_cooling_driver); +} + +late_initcall(sprd_cpu_cooling_init); +module_exit(sprd_cpu_cooling_exit); |
