From 0af992ee2e52a5d92d380745b2c74e7acfa2e545 Mon Sep 17 00:00:00 2001 From: Rocky Hao Date: Thu, 1 Feb 2018 18:04:50 +0800 Subject: [PATCH] hwmon: gpio-fan: add thermal control add devfreq and devfreq_cooling feature for gpio-fan and then it can be used as thermal cooling device to support IPA thermal policy. Change-Id: I376faa485625ac41276df9bbac8188ea8d664b36 Signed-off-by: Rocky Hao --- drivers/hwmon/gpio-fan.c | 165 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 154 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 685568b1236d..4622cbc99b58 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -53,6 +55,8 @@ struct gpio_fan_data { bool pwm_enable; struct gpio_fan_alarm *alarm; struct work_struct alarm_work; + struct devfreq *devfreq; + struct thermal_cooling_device *devfreq_cooling; }; /* @@ -537,11 +541,128 @@ static const struct of_device_id of_gpio_fan_match[] = { MODULE_DEVICE_TABLE(of, of_gpio_fan_match); #endif /* CONFIG_OF_GPIO */ +static inline void reset_last_status(struct devfreq *devfreq) +{ + devfreq->last_status.total_time = 1; + devfreq->last_status.busy_time = 1; +} + +static int rockchip_fanfreq_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + struct dev_pm_opp *opp; + struct devfreq_dev_profile *devp; + int i = 0; + int speed_index = 0; + + if (!fan_data || IS_ERR_OR_NULL(fan_data->devfreq)) + return 0; + + devp = fan_data->devfreq->profile; + + rcu_read_lock(); + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) { + rcu_read_unlock(); + return PTR_ERR(opp); + } + *freq = dev_pm_opp_get_freq(opp); + rcu_read_unlock(); + + for (i = 0; i < devp->max_state; i++) { + if (*freq < devp->freq_table[i]) + break; + } + + speed_index = devp->max_state - i; + + set_fan_speed(fan_data, speed_index); + + fan_data->devfreq->last_status.current_frequency = *freq; + + return 0; +} + +static int rockchip_fanfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + stat->busy_time = 1; + stat->total_time = 1; + return 0; +} + +static int rockchip_fanfreq_get_cur_freq(struct device *dev, + unsigned long *freq) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + if (!fan_data || IS_ERR_OR_NULL(fan_data->devfreq)) + return 0; + + *freq = fan_data->devfreq->last_status.current_frequency; + + return 0; +} + +static struct devfreq_dev_profile rockchip_devfreq_fan_profile = { + .polling_ms = 2000, + .target = rockchip_fanfreq_target, + .get_dev_status = rockchip_fanfreq_get_dev_status, + .get_cur_freq = rockchip_fanfreq_get_cur_freq, +}; + +static struct devfreq_cooling_power fan_cooling_power_data = { + .dyn_power_coeff = 120, +}; + +static int rockchip_fanfreq_init_freq_table(struct device *dev, + struct devfreq_dev_profile *devp) +{ + int count; + int i = 0; + unsigned long freq = 0; + struct dev_pm_opp *opp; + + rcu_read_lock(); + count = dev_pm_opp_get_opp_count(dev); + if (count < 0) { + rcu_read_unlock(); + return count; + } + rcu_read_unlock(); + + devp->freq_table = + devm_kmalloc_array(dev, count, sizeof(devp->freq_table[0]), + GFP_KERNEL); + if (!devp->freq_table) + return -ENOMEM; + + rcu_read_lock(); + for (i = 0; i < count; i++, freq++) { + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + if (IS_ERR(opp)) + break; + + devp->freq_table[i] = freq; + } + rcu_read_unlock(); + + if (count != i) + dev_warn(dev, "Unable to enumerate all OPPs (%d!=%d)\n", + count, i); + + devp->max_state = i; + return 0; +} + static int gpio_fan_probe(struct platform_device *pdev) { int err; struct gpio_fan_data *fan_data; struct gpio_fan_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct devfreq_dev_profile *devp = &rockchip_devfreq_fan_profile; + struct device *dev = &pdev->dev; fan_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_fan_data), GFP_KERNEL); @@ -592,17 +713,39 @@ static int gpio_fan_probe(struct platform_device *pdev) gpio_fan_groups); if (IS_ERR(fan_data->hwmon_dev)) return PTR_ERR(fan_data->hwmon_dev); -#ifdef CONFIG_OF_GPIO - /* Optional cooling device register for Device tree platforms */ - fan_data->cdev = thermal_of_cooling_device_register(pdev->dev.of_node, - "gpio-fan", - fan_data, - &gpio_fan_cool_ops); -#else /* CONFIG_OF_GPIO */ - /* Optional cooling device register for non Device tree platforms */ - fan_data->cdev = thermal_cooling_device_register("gpio-fan", fan_data, - &gpio_fan_cool_ops); -#endif /* CONFIG_OF_GPIO */ + + if (dev_pm_opp_of_add_table(dev)) { + dev_err(dev, "Invalid operating-points\n"); + return -EINVAL; + } + + if (rockchip_fanfreq_init_freq_table(dev, devp)) + return -EFAULT; + + fan_data->devfreq = devm_devfreq_add_device(dev, devp, + "performance", NULL); + if (IS_ERR(fan_data->devfreq)) + return PTR_ERR(fan_data->devfreq); + + devm_devfreq_register_opp_notifier(dev, fan_data->devfreq); + + fan_data->devfreq->min_freq = devp->freq_table[0]; + fan_data->devfreq->max_freq = + devp->freq_table[devp->max_state ? devp->max_state - 1 : 0]; + fan_data->devfreq->last_status.current_frequency = + fan_data->devfreq->max_freq; + devp->initial_freq = fan_data->devfreq->max_freq; + + reset_last_status(fan_data->devfreq); + + fan_data->devfreq_cooling = + of_devfreq_cooling_register_power(dev->of_node, + fan_data->devfreq, + &fan_cooling_power_data); + if (IS_ERR_OR_NULL(fan_data->devfreq_cooling)) { + err = PTR_ERR(fan_data->devfreq_cooling); + dev_err(dev, "Failed to register cooling device (%d)\n", err); + } dev_info(&pdev->dev, "GPIO fan initialized\n");