text: #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/cpufreq.h>
  #include <linux/thermal.h>
  #include <linux/platform_device.h>
  #include <linux/of_platform.h>
  #include <linux/cpufreq.h>
  #include <linux/err.h>
  #include <linux/slab.h>
  #include <linux/cpu.h>
  #include <linux/cpumask.h>
  #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");
  
  