summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormkarthik <mkarthik@nvidia.com>2020-06-17 08:01:11 -0400
committermobile promotions <svcmobile_promotions@nvidia.com>2020-09-15 11:39:29 -0400
commitf9d1658833bbd2dfd5b24bb8cfff4da88db658ce (patch)
treec6ec514c8f0e7f8d1473e895790f3ab70c6d6607
parent8d0f7dcdc17f692894c11684c8cfb672d6881492 (diff)
therm-fan-est: Add support for Tmargin to drive fan
Why? Tmargin method of fan control allows multiple groups of therma-fan-est devices temperatures to be considered for the fan control algorithm/fan curve. The limitation of the existing max temp algorithm in therm-fan-est driver is that devices with different fan curves cannot be accomodated in the driver. In case, there are multple fan curves, the device which has the steepest curve wins the algoritm. Hence, taking the temperatures as a reference from their respective critical temperatures and using that value to drive the fan will help in accomodating both the devices' fan curves. How? * Calculate the effective crit temp of all the thermal zones during probe. * In the polling cycles, calculate the effective temperatures of the individual groups and use the tmargin formula to calculate the current temperature. * Since the Tmargin temp trip values are in reverse order, we need to use the reverse order in the pwm-fan dt profile. * The hysterysis is subtracted from the temp in cooling scenario to avoid frequent switching at trip temps. Since the tmargin trip values are taken in the decending order, hysterysis temps in dt should be given as negative values. Bug 200627962 Change-Id: Ideba4bfdb3d3306d1b4aff15093bcfac13d7bb86 Signed-off-by: Mantravadi Karthik <mkarthik@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2362354 Reviewed-by: automaticguardword <automaticguardword@nvidia.com> Reviewed-by: Rajkumar Kasirajan <rkasirajan@nvidia.com> Reviewed-by: Bibek Basu <bbasu@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> Tested-by: Rajkumar Kasirajan <rkasirajan@nvidia.com> Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> GVS: Gerrit_Virtual_Submit
-rw-r--r--Documentation/devicetree/bindings/misc/therm_est.txt90
-rw-r--r--drivers/misc/therm_fan_est.c152
-rw-r--r--include/linux/therm_est.h2
3 files changed, 225 insertions, 19 deletions
diff --git a/Documentation/devicetree/bindings/misc/therm_est.txt b/Documentation/devicetree/bindings/misc/therm_est.txt
index 0dbcb147a..0538981a7 100644
--- a/Documentation/devicetree/bindings/misc/therm_est.txt
+++ b/Documentation/devicetree/bindings/misc/therm_est.txt
@@ -6,6 +6,8 @@ Properties :
6 - tc1 : Coefficient 1 for thermal trend calculation. 6 - tc1 : Coefficient 1 for thermal trend calculation.
7 - tc2 : Coefficient 2 for thermal trend calculation. 7 - tc2 : Coefficient 2 for thermal trend calculation.
8 - node for subdev : Node for subdevice information. Required. 8 - node for subdev : Node for subdevice information. Required.
9 - use_tmargin : if tmargin algorithm should be used for calculating the
10 effective temp. Refer Tmargin section for more info.
9 11
10Properties in subdev node : Required. 12Properties in subdev node : Required.
11 - subdev_names : list of strings. It contains list of the name of the therm 13 - subdev_names : list of strings. It contains list of the name of the therm
@@ -45,3 +47,91 @@ Example:
45 }; 47 };
46 }; 48 };
47 }; 49 };
50
51Tmargin Algorithm:
52The native max temp algorithm lacks support for accomodating multiple thermal
53fan curves. Tmargin algorithm solves this by calculating the effective
54temperature as a difference from critical temperature of the therm fan est
55group. This allows us to take a union of two fan curves and satisfy the needs
56of both the therm-fan-est groups.
57The algorithm use the effective temperature(crit_temp - sensor_reading) to
58drive the cooling device.
59
60Properties rules for therm-fan-est dt entry:
61 - use_tmargin : To use the tmargin feature.
62 - profiles : New profile tmargin should be defined and chosen as default.
63 * The trip temperatures need to be given only in ascending order for the
64 thermal framework to register therm-fan-est zone. Hence, the dt values
65 for active trip temps should be given in ascending values of tmargin
66 values.
67 * Hysterysis is used only in cooling scenario. Since tmargin is
68 considered as a diff wrt crit_Temp, hysterysis values should be taken
69 as -ve values.
70
71Properties rules for fan dt entry:
72 - profiles : New profile for tmargin should be defined and chosen as default.
73 * The trip temps are considered in a ascending order of tmargin temps.
74 Hence, the pwm values should be considered in a descending order of
75 pwms.
76 * active_steps - The tmargin trip values are considered in the ascending
77 order(hence actual trip temps will be in descending order), so the
78 pwm mapping should be in the reverse order starting from 255 for
79 index 0.
80
81Example :
82
83 thermal-fan-est {
84 compatible = "thermal-fan-est";
85 name = "thermal-fan-est";
86 status = "okay";
87 num_resources = <0>;
88 shared_data = <&thermal_fan_est_shared_data>;
89 trip_length = <10>;
90 use_tmargin;
91
92 profiles {
93 default = "tmargin";
94 quiet {
95 active_trip_temps = <0 50000 63000 72000 81000
96 140000 150000 160000 170000 180000>;
97 active_hysteresis = <0 18000 8000 8000 8000
98 0 0 0 0 0>;
99 };
100 cool {
101 active_trip_temps = <0 35000 53000 62000 73000
102 140000 150000 160000 170000 180000>;
103 active_hysteresis = <0 9000 8000 8000 9000
104 0 0 0 0 0>;
105 };
106 tmargin {
107 active_trip_temps = <0 10000 15000 25000 35000
108 45000 55000 65000 75000 105000>;
109 active_hysteresis = <0 0 0 0 (-3000)
110 (-4000) (-4000) 0 0 0>;
111 };
112 };
113 };
114
115 pwm-fan {
116 compatible = "pwm-fan";
117 status = "okay";
118 #pwm-cells = <1>;
119 pwms = <&tegra_pwm4 0 45334>;
120 shared_data = <&pwm_fan_shared_data>;
121 profiles {
122 default = "tmargin";
123 quiet {
124 state_cap = <8>;
125 active_pwm = <0 60 90 120 150 180 210 240 255 255>;
126 };
127 cool {
128 state_cap = <4>;
129 active_pwm = <0 77 120 160 255 255 255 255 255 255>;
130 };
131 tmargin {
132 state_cap = <0>;
133 active_pwm = <255 255 240 210 180 150 120 90 60 0>;
134 };
135 };
136 };
137
diff --git a/drivers/misc/therm_fan_est.c b/drivers/misc/therm_fan_est.c
index f07a31009..e37c1130d 100644
--- a/drivers/misc/therm_fan_est.c
+++ b/drivers/misc/therm_fan_est.c
@@ -52,7 +52,8 @@ static void therm_fan_est_work_func(struct work_struct *work)
52{ 52{
53 int i, j, group, index, trip_index = 0; 53 int i, j, group, index, trip_index = 0;
54 int sum[MAX_SUBDEVICE_GROUP] = {0, }; 54 int sum[MAX_SUBDEVICE_GROUP] = {0, };
55 int sum_max = 0; 55 int sum_max = INT_MIN;
56 int sum_min = INT_MAX;
56 int temp = 0; 57 int temp = 0;
57 struct delayed_work *dwork = container_of(work, 58 struct delayed_work *dwork = container_of(work,
58 struct delayed_work, work); 59 struct delayed_work, work);
@@ -77,11 +78,21 @@ static void therm_fan_est_work_func(struct work_struct *work)
77 } 78 }
78 } 79 }
79 80
81 if (est->use_tmargin) {
82 for (i = 0; i < MAX_SUBDEVICE_GROUP; i++)
83 sum[i] = (est->crit_temp[i] * 100) - sum[i];
84 }
85
80#if !DEBUG 86#if !DEBUG
81 for (i = 0; i < MAX_SUBDEVICE_GROUP; i++) 87 for (i = 0; i < MAX_SUBDEVICE_GROUP; i++) {
88 sum_min = min(sum_min, sum[i]);
82 sum_max = max(sum_max, sum[i]); 89 sum_max = max(sum_max, sum[i]);
90 }
83 91
84 est->cur_temp = sum_max / 100 + est->toffset; 92 if (est->use_tmargin)
93 est->cur_temp = sum_min / 100 + est->toffset;
94 else
95 est->cur_temp = sum_max / 100 + est->toffset;
85#else 96#else
86 est->cur_temp = est->cur_temp_debug; 97 est->cur_temp = est->cur_temp_debug;
87#endif 98#endif
@@ -95,7 +106,9 @@ static void therm_fan_est_work_func(struct work_struct *work)
95 } 106 }
96 107
97 if (est->cur_temp != est->pre_temp) { 108 if (est->cur_temp != est->pre_temp) {
98 if (est->cur_temp > est->pre_temp) { 109 if (est->use_tmargin ?
110 (est->cur_temp < est->pre_temp) :
111 (est->cur_temp > est->pre_temp)) {
99 /* temperature is rising */ 112 /* temperature is rising */
100 read_lock(&est->state_lock); 113 read_lock(&est->state_lock);
101 for (trip_index = 0; 114 for (trip_index = 0;
@@ -105,10 +118,16 @@ static void therm_fan_est_work_func(struct work_struct *work)
105 } 118 }
106 read_unlock(&est->state_lock); 119 read_unlock(&est->state_lock);
107 120
108 if (est->current_trip_level < trip_index 121 if (est->use_tmargin) {
109 && est->current_trip_level != (trip_index - 1)) 122 if (est->current_trip_level >= trip_index
110 update_flag = true; 123 && est->current_trip_level != (trip_index - 1))
111 } else if (est->cur_temp < est->pre_temp) { 124 update_flag = true;
125 } else {
126 if (est->current_trip_level < trip_index
127 && est->current_trip_level != (trip_index - 1))
128 update_flag = true;
129 }
130 } else {
112 /* temperature is cooling */ 131 /* temperature is cooling */
113 read_lock(&est->state_lock); 132 read_lock(&est->state_lock);
114 for (trip_index = 1; 133 for (trip_index = 1;
@@ -119,10 +138,16 @@ static void therm_fan_est_work_func(struct work_struct *work)
119 } 138 }
120 read_unlock(&est->state_lock); 139 read_unlock(&est->state_lock);
121 140
122 if (est->current_trip_level >= trip_index 141 if (est->use_tmargin) {
123 && est->current_trip_level != (trip_index - 1) 142 if (est->current_trip_level < trip_index
124 && trip_index != (MAX_ACTIVE_STATES + 1)) 143 && est->current_trip_level != (trip_index - 1))
125 update_flag = true; 144 update_flag = true;
145 } else {
146 if (est->current_trip_level >= trip_index
147 && est->current_trip_level != (trip_index - 1)
148 && trip_index != (MAX_ACTIVE_STATES + 1))
149 update_flag = true;
150 }
126 } 151 }
127 152
128 if (update_flag) { 153 if (update_flag) {
@@ -317,6 +342,51 @@ static struct thermal_zone_device_ops therm_fan_est_ops = {
317 .set_trip_hyst = therm_fan_est_set_trip_hyst, 342 .set_trip_hyst = therm_fan_est_set_trip_hyst,
318}; 343};
319 344
345static int fan_est_match(struct thermal_zone_device *thz, void *data)
346{
347 return (strcmp((char *)data, thz->type) == 0);
348}
349
350static int fan_est_get_crit_temp(struct therm_fan_estimator *est)
351{
352 struct thermal_zone_device *thz;
353 int crit_temp;
354 int i;
355
356 for (i = 0; i < est->ndevs; i++) {
357 thz = thermal_zone_device_find((void *)est->devs[i].dev_data,
358 fan_est_match);
359
360 if (thz && thz->ops && thz->ops->get_crit_temp) {
361 thz->ops->get_crit_temp(thz, &crit_temp);
362 if (crit_temp <= 0) {
363 pr_err("THERMAL EST: Failed to get crit temp of %s\n",
364 est->devs[i].dev_data);
365 return -EINVAL;
366 }
367 } else {
368 pr_err("THERMAL EST: Invalid ops for %s\n",
369 est->devs[i].dev_data);
370 return -EINVAL;
371 }
372
373 if (crit_temp > est->crit_temp[est->devs[i].group])
374 est->crit_temp[est->devs[i].group] = crit_temp;
375 }
376
377 for (i = 0; i < MAX_SUBDEVICE_GROUP; i++) {
378 if (est->crit_temp[i] == 0) {
379 pr_err("THERMAL EST: Group %d has no crit temp\n", i);
380 return -EINVAL;
381 } else {
382 pr_info("THERMAL EST: Group %d crit temp - %ld", i,
383 est->crit_temp[i]);
384 }
385 }
386
387 return 0;
388}
389
320static ssize_t show_coeff(struct device *dev, 390static ssize_t show_coeff(struct device *dev,
321 struct device_attribute *da, 391 struct device_attribute *da,
322 char *buf) 392 char *buf)
@@ -506,6 +576,44 @@ static ssize_t set_sleep_mode(struct device *dev,
506 return count; 576 return count;
507} 577}
508 578
579static ssize_t show_crit_temps(struct device *dev,
580 struct device_attribute *da,
581 char *buf)
582{
583 struct therm_fan_estimator *est = dev_get_drvdata(dev);
584 ssize_t len, total_len = 0;
585 int i;
586
587 for (i = 0; i < MAX_SUBDEVICE_GROUP; i++) {
588 len = snprintf(buf + total_len, PAGE_SIZE,
589 "group[%d] - %ld", i, est->crit_temp[i]);
590 total_len += len;
591 len = snprintf(buf + total_len, PAGE_SIZE, "\n");
592 total_len += len;
593 }
594 return strlen(buf);
595}
596
597static ssize_t show_zone_map(struct device *dev,
598 struct device_attribute *da,
599 char *buf)
600{
601 struct therm_fan_estimator *est = dev_get_drvdata(dev);
602 ssize_t len, total_len = 0;
603 int i;
604
605 for (i = 0; i < est->ndevs; i++) {
606 len = snprintf(buf + total_len, PAGE_SIZE,
607 "%s - ndev[%d], group[%d]",
608 est->devs[i].dev_data, i, est->devs[i].group);
609 total_len += len;
610 len = snprintf(buf + total_len, PAGE_SIZE, "\n");
611 total_len += len;
612 }
613 return strlen(buf);
614}
615
616
509static ssize_t show_temps(struct device *dev, 617static ssize_t show_temps(struct device *dev,
510 struct device_attribute *da, 618 struct device_attribute *da,
511 char *buf) 619 char *buf)
@@ -554,6 +662,8 @@ static struct sensor_device_attribute therm_fan_est_nodes[] = {
554 show_fan_profile, set_fan_profile, 0), 662 show_fan_profile, set_fan_profile, 0),
555 SENSOR_ATTR(sleep_mode, S_IRUGO | S_IWUSR, 663 SENSOR_ATTR(sleep_mode, S_IRUGO | S_IWUSR,
556 show_sleep_mode, set_sleep_mode, 0), 664 show_sleep_mode, set_sleep_mode, 0),
665 SENSOR_ATTR(zone_map, S_IRUGO, show_zone_map, 0, 0),
666 SENSOR_ATTR(crit_temps, S_IRUGO, show_crit_temps, 0, 0),
557#if DEBUG 667#if DEBUG
558 SENSOR_ATTR(temps, S_IRUGO | S_IWUSR, show_temps, set_temps, 0), 668 SENSOR_ATTR(temps, S_IRUGO | S_IWUSR, show_temps, set_temps, 0),
559#else 669#else
@@ -562,11 +672,6 @@ static struct sensor_device_attribute therm_fan_est_nodes[] = {
562}; 672};
563 673
564 674
565static int fan_est_match(struct thermal_zone_device *thz, void *data)
566{
567 return (strcmp((char *)data, thz->type) == 0);
568}
569
570static int fan_est_get_temp_func(const char *data, int *temp) 675static int fan_est_get_temp_func(const char *data, int *temp)
571{ 676{
572 struct thermal_zone_device *thz; 677 struct thermal_zone_device *thz;
@@ -579,7 +684,6 @@ static int fan_est_get_temp_func(const char *data, int *temp)
579 return 0; 684 return 0;
580} 685}
581 686
582
583static int therm_fan_est_probe(struct platform_device *pdev) 687static int therm_fan_est_probe(struct platform_device *pdev)
584{ 688{
585 int i, j; 689 int i, j;
@@ -652,6 +756,8 @@ static int therm_fan_est_probe(struct platform_device *pdev)
652 est_data->num_resources = value; 756 est_data->num_resources = value;
653 pr_info("THERMAL EST num_resources: %d\n", est_data->num_resources); 757 pr_info("THERMAL EST num_resources: %d\n", est_data->num_resources);
654 758
759 est_data->use_tmargin = of_property_read_bool(node, "use_tmargin");
760
655 of_err |= of_property_read_u32(node, "trip_length", &value); 761 of_err |= of_property_read_u32(node, "trip_length", &value);
656 if (of_err) { 762 if (of_err) {
657 pr_err("THERMAL EST: missing trip length\n"); 763 pr_err("THERMAL EST: missing trip length\n");
@@ -855,6 +961,14 @@ static int therm_fan_est_probe(struct platform_device *pdev)
855 i, temp); 961 i, temp);
856 } 962 }
857 963
964 if (est_data->use_tmargin) {
965 err = fan_est_get_crit_temp(est_data);
966 if (err) {
967 err = -EINVAL;
968 goto free_subdevs;
969 }
970 }
971
858 of_err |= of_property_read_string(data_node, "cdev_type", 972 of_err |= of_property_read_string(data_node, "cdev_type",
859 &est_data->cdev_type); 973 &est_data->cdev_type);
860 if (of_err) { 974 if (of_err) {
@@ -919,7 +1033,7 @@ static int therm_fan_est_probe(struct platform_device *pdev)
919 1033
920 /* workqueue related */ 1034 /* workqueue related */
921 est_data->workqueue = alloc_workqueue(dev_name(&pdev->dev), 1035 est_data->workqueue = alloc_workqueue(dev_name(&pdev->dev),
922 WQ_HIGHPRI | WQ_UNBOUND, 1); 1036 WQ_HIGHPRI | WQ_UNBOUND, 1);
923 if (!est_data->workqueue) { 1037 if (!est_data->workqueue) {
924 err = -ENOMEM; 1038 err = -ENOMEM;
925 goto free_tzp; 1039 goto free_tzp;
diff --git a/include/linux/therm_est.h b/include/linux/therm_est.h
index fa1ea7ffc..8b1786ea8 100644
--- a/include/linux/therm_est.h
+++ b/include/linux/therm_est.h
@@ -235,6 +235,8 @@ struct therm_fan_estimator {
235 /* allow cooling device to turn off at higher temperature if sleep */ 235 /* allow cooling device to turn off at higher temperature if sleep */
236 bool sleep_mode; 236 bool sleep_mode;
237 int nonsleep_hyst; 237 int nonsleep_hyst;
238 bool use_tmargin;
239 long crit_temp[MAX_SUBDEVICE_GROUP];
238 240
239 bool is_continuous_gov; 241 bool is_continuous_gov;
240}; 242};