#include #include #include #include #include #include #include #include #include #include #include #include "sunxi-budget-cooling.h" #include "sunxi-budget-interface.h" #define SUNXI_BUDGET_COOLING_NAME "sunxi-budget" #define SUNXI_BUDGET_DRIVER_NAME "sunxi-budget-cooling" static struct sunxi_budget_cooling_device *budget_cdev; #define USERSPACE_ROOMAGE #ifdef USERSPACE_ROOMAGE static ssize_t sunxi_budget_roomage_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret=0,i; unsigned int roomage_data[8] = {0}; struct sunxi_budget_cooling_device *budget_cdev = dev_get_drvdata(dev); if(!budget_cdev) return ret; for(i = 0; i < budget_cdev->cluster_num; i++){ sunxi_cpufreq_get_roomage(budget_cdev, &roomage_data[2 * i], &roomage_data[2 * i + 4], i); sunxi_hotplug_get_roomage(budget_cdev, &roomage_data[2 * i + 1], &roomage_data[2 * i + 5], i); } ret += sprintf(buf, "roomage:%d,%d,%d,%d,%d,%d,%d,%d\n", roomage_data[0], roomage_data[1], roomage_data[2], roomage_data[3], roomage_data[4], roomage_data[5], roomage_data[6], roomage_data[7]); return ret; } static ssize_t sunxi_budget_roomage_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int i; unsigned int roomage_data[8] = {0}; unsigned int pre_freq_floor = 0, pre_freq_roof = 0, next_freq_floor = 0, next_freq_roof = 0; unsigned int pre_num_floor = 0, pre_num_roof = 0, next_num_floor = 0, next_num_roof = 0; struct sunxi_budget_cooling_device *budget_cdev = dev_get_drvdata(dev); if(!budget_cdev) return count; sscanf(buf,"%u %u %u %u %u %u %u %u\n", &roomage_data[0], &roomage_data[1], &roomage_data[2], &roomage_data[3], &roomage_data[4], &roomage_data[5], &roomage_data[6], &roomage_data[7]); for(i = 0; i < budget_cdev->cluster_num; i++){ sunxi_cpufreq_get_roomage(budget_cdev, &pre_freq_floor, &pre_freq_roof, i); sunxi_hotplug_get_roomage(budget_cdev, &pre_num_floor, &pre_num_roof, i); next_freq_floor = roomage_data[2 * i]; next_num_floor = roomage_data[2 * i + 1]; next_freq_roof = roomage_data[2 * i + 4]; next_num_roof = roomage_data[2 *i + 5]; if(next_freq_roof < pre_freq_roof && next_num_roof > pre_num_roof){ sunxi_cpufreq_set_roomage(budget_cdev, next_freq_floor, next_freq_roof, i); sunxi_hotplug_set_roomage(budget_cdev, next_num_floor, next_num_roof, i); }else{ sunxi_hotplug_set_roomage(budget_cdev, next_num_floor, next_num_roof, i); sunxi_cpufreq_set_roomage(budget_cdev, next_freq_floor, next_freq_roof, i); } } return count; } static DEVICE_ATTR(roomage, 0644,sunxi_budget_roomage_show, sunxi_budget_roomage_store); #endif static int cpu_budget_apply_cooling(struct sunxi_budget_cooling_device *cooling_device, unsigned long cooling_state) { int ret = 0,cluster; u32 pre_state; u32 pre_freq = 0, next_freq = 0, pre_num = 0, next_num = 0; /* Check if the old cooling action is same as new cooling action */ if (cooling_device->cooling_state == cooling_state) return 0; if(cooling_state >= cooling_device->state_num) return -EINVAL; pre_state = cooling_device->cooling_state; cooling_device->cooling_state = cooling_state; for(cluster = 0; cluster < cooling_device->cluster_num; cluster++){ if(cooling_device->cpufreq){ pre_freq = cooling_device->cpufreq->tbl[pre_state].cluster_freq[cluster]; next_freq = cooling_device->cpufreq->tbl[cooling_state].cluster_freq[cluster]; } if(cooling_device->hotplug){ pre_num = cooling_device->hotplug->tbl[pre_state].cluster_num[cluster]; next_num = cooling_device->hotplug->tbl[cooling_state].cluster_num[cluster]; } if((pre_freq < next_freq) && (pre_num > next_num)){ ret = sunxi_hotplug_update_state(cooling_device, cluster); if(ret) return ret; ret = sunxi_cpufreq_update_state(cooling_device, cluster); if(ret) return ret; }else{ ret = sunxi_cpufreq_update_state(cooling_device, cluster); if(ret) return ret; ret = sunxi_hotplug_update_state(cooling_device, cluster); if(ret) return ret; } } return 0; } /** * cpu_budget_get_max_state - callback function to get the max cooling state. * @cdev: thermal cooling device pointer. * @state: fill this variable with the max cooling state. */ static int cpu_budget_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { struct sunxi_budget_cooling_device *cooling_device = cdev->devdata; *state = cooling_device->state_num-1; return 0; } /** * cpu_budget_get_cur_state - callback function to get the current cooling state. * @cdev: thermal cooling device pointer. * @state: fill this variable with the current cooling state. */ static int cpu_budget_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { struct sunxi_budget_cooling_device *cooling_device = cdev->devdata; *state = cooling_device->cooling_state; return 0; } /** * cpu_budget_set_cur_state - callback function to set the current cooling state. * @cdev: thermal cooling device pointer. * @state: set this variable to the current cooling state. */ static int cpu_budget_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { struct sunxi_budget_cooling_device *cooling_device = cdev->devdata; return cpu_budget_apply_cooling(cooling_device, state); } /* Bind cpufreq callbacks to thermal cooling device ops */ static struct thermal_cooling_device_ops const sunxi_cpu_cooling_ops = { .get_max_state = cpu_budget_get_max_state, .get_cur_state = cpu_budget_get_cur_state, .set_cur_state = cpu_budget_set_cur_state, }; /** * sunxi_budget_cooling_get_cpumask - get the cpumask of system. */ static int sunxi_budget_cooling_get_cpumask(void) { int cluster,i; unsigned int min = 0, max = 0; struct cpufreq_policy policy; #if defined(CONFIG_SCHED_HMP) arch_get_fast_and_slow_cpus(&budget_cdev->cluster_cpus[1],&budget_cdev->cluster_cpus[0]); #elif defined(CONFIG_SCHED_SMP_DCMP) if (strlen(CONFIG_CLUSTER0_CPU_MASK) && strlen(CONFIG_CLUSTER1_CPU_MASK)) { if (cpulist_parse(CONFIG_CLUSTER0_CPU_MASK, &budget_cdev->cluster_cpus[0])) { pr_err("Failed to parse cluster0 cpu mask!\n"); return -EBUSY; } if (cpulist_parse(CONFIG_CLUSTER1_CPU_MASK, &budget_cdev->cluster_cpus[1])) { pr_err("Failed to parse cluster1 cpu mask!\n"); return -EBUSY; } } #else cpumask_copy(&budget_cdev->cluster_cpus[0], cpu_possible_mask); budget_cdev->cluster_num = 1; #endif for(cluster = 0; cluster < budget_cdev->cluster_num; min = 0, max = 0, cluster ++){ for_each_cpu(i, &budget_cdev->cluster_cpus[cluster]) { /*continue if cpufreq policy not found and not return error*/ if (!cpufreq_get_policy(&policy, i)) continue; if (min == 0 && max == 0) { min = policy.cpuinfo.min_freq; max = policy.cpuinfo.max_freq; } else { if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq){ pr_err("different freq, return.\n"); return -EBUSY; } } } } return 0; } static int sunxi_budget_cooling_parse(struct platform_device *pdev) { struct device_node *np = NULL; int ret = 0; np = pdev->dev.of_node; if (!of_device_is_available(np)) { pr_err("%s: budget cooling is disable", __func__); return -EPERM; } budget_cdev = kzalloc(sizeof(*budget_cdev), GFP_KERNEL); if (IS_ERR_OR_NULL(budget_cdev)) { pr_err("cooling_dev: not enough memory for budget cooling data\n"); return -ENOMEM; } if (of_property_read_u32(np, "cluster_num", &budget_cdev->cluster_num)) { pr_err("%s: get cluster_num failed", __func__); ret = -EBUSY; goto parse_fail; } if (of_property_read_u32(np, "state_cnt", &budget_cdev->state_num)) { pr_err("%s: get state_cnt failed", __func__); ret = -EBUSY; goto parse_fail; } ret = sunxi_budget_cooling_get_cpumask(); if(ret) goto parse_fail; return 0; parse_fail: kfree(budget_cdev); return ret; } static int sunxi_budget_cooling_probe(struct platform_device *pdev) { s32 err = 0; struct thermal_cooling_device *cool_dev; pr_info("sunxi budget cooling probe start !\n"); if (pdev->dev.of_node) { /* get dt and sysconfig */ err = sunxi_budget_cooling_parse(pdev); }else{ pr_err("sunxi budget cooling device tree err!\n"); return -EBUSY; } if(err){ pr_err("sunxi budget cooling device tree parse err!\n"); return -EBUSY; } budget_cdev->dev = &pdev->dev; budget_cdev->cpufreq = sunxi_cpufreq_cooling_register(budget_cdev); budget_cdev->hotplug = sunxi_hotplug_cooling_register(budget_cdev); cool_dev = thermal_of_cooling_device_register(pdev->dev.of_node, SUNXI_BUDGET_COOLING_NAME, budget_cdev, &sunxi_cpu_cooling_ops); if (!cool_dev) goto fail; budget_cdev->cool_dev = cool_dev; budget_cdev->cooling_state = 0; dev_set_drvdata(&pdev->dev, budget_cdev); #ifdef USERSPACE_ROOMAGE device_create_file(&pdev->dev, &dev_attr_roomage); #endif pr_info("CPU budget cooling register Success\n"); return 0; fail: sunxi_cpufreq_cooling_unregister(budget_cdev); sunxi_hotplug_cooling_unregister(budget_cdev); kfree(budget_cdev); return -EBUSY; } static int sunxi_budget_cooling_remove(struct platform_device *pdev) { thermal_cooling_device_unregister(budget_cdev->cool_dev); sunxi_cpufreq_cooling_unregister(budget_cdev); sunxi_hotplug_cooling_unregister(budget_cdev);; kfree(budget_cdev); pr_info("CPU budget cooling unregister Success\n"); return 0; } #ifdef CONFIG_OF /* Translate OpenFirmware node properties into platform_data */ static struct of_device_id sunxi_budget_cooling_of_match[] = { { .compatible = "allwinner,budget_cooling", }, { }, }; MODULE_DEVICE_TABLE(of, sunxi_budget_cooling_of_match); #else /* !CONFIG_OF */ #endif static struct platform_driver sunxi_budget_cooling_driver = { .probe = sunxi_budget_cooling_probe, .remove = sunxi_budget_cooling_remove, .driver = { .name = SUNXI_BUDGET_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(sunxi_budget_cooling_of_match), } }; static int __init sunxi_budget_cooling_driver_init(void) { return platform_driver_register(&sunxi_budget_cooling_driver); } static void __exit sunxi_budget_cooling_driver_exit(void) { platform_driver_unregister(&sunxi_budget_cooling_driver); } subsys_initcall_sync(sunxi_budget_cooling_driver_init); module_exit(sunxi_budget_cooling_driver_exit); MODULE_DESCRIPTION("SUNXI budget cooling driver"); MODULE_AUTHOR("QIn"); MODULE_LICENSE("GPL v2");