drivers: misc: therm_est: Add DT support
Jinyoung Park [Fri, 12 Apr 2013 14:25:42 +0000 (23:25 +0900)]
Add DT support and documentation for thermal estimator.

Bug 1173854
Bug 1240803

Change-Id: I4631fc499cb042d7649681fe097a9087aa5c5098
Signed-off-by: Jinyoung Park <jinyoungp@nvidia.com>
Reviewed-on: http://git-master/r/211125
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Bitan Biswas <bbiswas@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com>

Documentation/devicetree/bindings/misc/therm_est.txt [new file with mode: 0644]
drivers/misc/therm_est.c

diff --git a/Documentation/devicetree/bindings/misc/therm_est.txt b/Documentation/devicetree/bindings/misc/therm_est.txt
new file mode 100644 (file)
index 0000000..1a0adb7
--- /dev/null
@@ -0,0 +1,108 @@
+Thermal estimator driver.
+
+Properties :
+ - compatible : Should contain "nvidia,therm-est".
+ - toffset : Temperature offset for thermal estimation, in milli-celsius.
+ - polling-period : Polling wait times for thermal estimation, in milliseconds.
+ - passive-delay : Polling wait times for passive cooling, in milliseconds.
+ - tc1 : Coefficient 1 for thermal trend calculation.
+ - tc2 : Coefficient 2 for thermal trend calculation.
+ - node for trip : Node for trip point information. Required.
+     This node can be numerous.
+ - node for subdev : Node for subdevice information. Required.
+     This node can be numerous.
+ - node for tzp : Node for thermal zone platform parameters. Optional.
+ - node for timer trip : Node for timer trip point information. Optional.
+     This node can be numerous.
+
+Properties in trips node : Required. Can be numerous.
+ - compatible : Should contain "nvidia,therm-est-trip".
+ - cdev-type : Thermal cooling device type for binding with the thermal
+     estimator thermal zone.
+ - trip-type : Type of this trip point.
+     This should be one of types in active, passive, hot, and critical.
+ - trip-temp : Temperature to fire this trip point, in milli-celsius.
+ - hysteresis : Hysteresis temperature for this trip point, in milli-celsius.
+ - upper : Upper limit of the cooling state for this trip point. Optional.
+     "-1" = THERMAL_NO_LIMIT: no upper limit.
+         If no upper property, THERMAL_NO_LIMIT will be used.
+ - lower : Lower limit of the cooling state for this trip point. Optional.
+     "-1" = THERMAL_NO_LIMIT: no lower limit.
+         If no lower property, THERMAL_NO_LIMIT will be used.
+
+Properties in subdevice node : Required. Can be numerous.
+ - compatible : Should contain "nvidia,therm-est-subdev".
+ - dev_data : Thermal zone device type for thermal estimation.
+ - coeffs : An array of coefficients, the number of entries should be twenty.
+
+Properties in tzp node : Optional
+ - compatible : Should contain "nvidia,therm-est-tzp".
+ - governor : Thermal throttling governor name.
+     The available governors could be different by Kernel build options.
+
+Properties in timer trip : Optional. Can be numerous.
+ - compatible : Should contain "nvidia,therm-est-timer-trip".
+ - trip : Trip point to apply timer trip in the thermal estimator thermal zone.
+ - node for timer : Nodes for timer trip point information. Required.
+     This node can be numerous.
+
+Properties in timer : Required if the timer trip is exist. Can be numerous.
+ - compatible : Should contain "nvidia,therm-est-timer-trip".
+ - time-after : Expiration time of the timer, in milliseconds.
+ - trip-temp : Trip temperature will be used for this trip point after timer
+     expires, in milli-celsius.
+ - hysteresis : Hysteresis temperature will be used for this trip point after
+     timer expires, in milli-celsius.
+
+Example:
+
+       therm_est {
+               compatible = "nvidia,therm-est";
+               toffset = <0>;
+               polling-period = <1100>;
+               passive-delay = <15000>;
+               tc1 = <10>;
+               tc2 = <1>;
+               trips@0 {
+                       compatible = "nvidia,therm-est-trip";
+                       cdev-type = "skin-balanced";
+                       trip-type = "active";
+                       trip-temp = <40000>;
+                       hysteresis = <0>;
+                       upper = "1"; // fix cooling state to 1
+                       lower = "1";
+               };
+               trips@1 {
+                       compatible = "nvidia,therm-est-trip";
+                       cdev-type = "skin-balanced";
+                       trip-type = "passive";
+                       trip-temp = <45000>;
+                       hysteresis = <5000>;
+                       upper = "-1"; // THERMAL_NO_LIMIT
+                       lower = "-1"; // THERMAL_NO_LIMIT
+               };
+               subdevs@0 {
+                       compatible = "nvidia,therm-est-subdev";
+                       dev-data = "nct_ext";
+                       coeffs = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+               };
+               subdevs@1 {
+                       compatible = "nvidia,therm-est-subdev";
+                       dev-data = "nct_int";
+                       coeffs = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+               };
+               tzp {
+                       compatible = "nvidia,therm-est-tzp";
+                       governor = "pid_thermal_gov";
+               };
+               timer-trips@0 {
+                       compatible = "nvidia,therm-est-timer-trip";
+                       trip = <1>; // this timer trip will be applied to trip1
+                       timers@0 {
+                               compatible = "nvidia,therm-est-timer";
+                               time-after = <600000>;
+                               trip-temp = <43000>;
+                               hysteresis = <5000>;
+                       };
+               };
+       };
index 17471bf..699c8ce 100644 (file)
@@ -32,6 +32,8 @@
 #include <linux/module.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/suspend.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
 
 struct therm_estimator {
        struct thermal_zone_device *thz;
@@ -694,6 +696,262 @@ static int therm_est_pm_notify(struct notifier_block *nb,
 }
 #endif
 
+#ifdef CONFIG_OF
+static int __parse_dt_trip(struct device_node *np,
+                          struct thermal_trip_info *trips)
+{
+       const char *str;
+       u32 val;
+       int ret;
+
+       ret = of_property_read_string(np, "cdev-type", &str);
+       if (ret < 0)
+               return ret;
+       trips->cdev_type = (char *)str;
+
+       ret = of_property_read_string(np, "trip-type", &str);
+       if (ret < 0)
+               return ret;
+
+       if (!strcasecmp("active", str))
+               trips->trip_type = THERMAL_TRIP_ACTIVE;
+       else if (!strcasecmp("passive", str))
+               trips->trip_type = THERMAL_TRIP_PASSIVE;
+       else if (!strcasecmp("hot", str))
+               trips->trip_type = THERMAL_TRIP_HOT;
+       else if (!strcasecmp("critical", str))
+               trips->trip_type = THERMAL_TRIP_CRITICAL;
+       else
+               return -EINVAL;
+
+       ret = of_property_read_u32(np, "trip-temp", &val);
+       if (ret < 0)
+               return ret;
+       trips->trip_temp = val;
+
+       trips->hysteresis = 0;
+       if (of_property_read_u32(np, "hysteresis", &val) == 0)
+               trips->hysteresis = val;
+
+       trips->upper = THERMAL_NO_LIMIT;
+       if (of_property_read_string(np, "upper", &str) == 0) {
+               if (kstrtou32(str, 10, &val) == 0)
+                       trips->upper = val;
+       }
+
+       trips->lower = THERMAL_NO_LIMIT;
+       if (of_property_read_string(np, "lower", &str) == 0) {
+               if (kstrtou32(str, 10, &val) == 0)
+                       trips->lower = val;
+       }
+
+       return 0;
+}
+
+static int __parse_dt_subdev(struct device_node *np,
+                            struct therm_est_subdevice *subdev)
+{
+       const char *str;
+       char *sbegin;
+       int i = 0;
+       int ret;
+
+       subdev->dev_data = (void *)of_get_property(np, "dev-data", NULL);
+       if (!subdev->dev_data)
+               return -ENODATA;
+
+       ret = of_property_read_string(np, "coeffs", &str);
+       if (ret < 0)
+               return ret;
+
+       while (str && (i < HIST_LEN)) {
+               str = skip_spaces(str);
+               sbegin = strsep((char **)&str, " ");
+               if (!sbegin || (kstrtol((const char *)sbegin, 10,
+                               &subdev->coeffs[i++]) < 0))
+                       break;
+       }
+
+       if (i != HIST_LEN)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int __parse_dt_tzp(struct device_node *np,
+                         struct thermal_zone_params *tzp)
+{
+       const char *str;
+
+       if (of_property_read_string(np, "governor", &str) == 0)
+               strncpy(tzp->governor_name, str, THERMAL_NAME_LENGTH);
+
+       return 0;
+}
+
+static int __parse_dt_timer_trip(struct device_node *np,
+                                struct therm_est_timer_trip_info *timer_info)
+{
+       struct device_node *ch;
+       u32 val;
+       int n_timers;
+       int ret;
+
+       ret = of_property_read_u32(np, "trip", &val);
+       if (ret < 0)
+               return ret;
+       timer_info->trip = val;
+
+       n_timers = 0;
+       for_each_child_of_node(np, ch) {
+               if (!of_device_is_compatible(ch, "nvidia,therm-est-timer"))
+                       continue;
+
+               ret = of_property_read_u32(ch, "time-after", &val);
+               if (ret < 0)
+                       return ret;
+               timer_info->timers[n_timers].time_after = val;
+
+               ret = of_property_read_u32(ch, "trip-temp", &val);
+               if (ret < 0)
+                       return ret;
+               timer_info->timers[n_timers].trip_temp = val;
+
+               timer_info->timers[n_timers].hysteresis = 0;
+               if (of_property_read_u32(ch, "hysteresis", &val) == 0)
+                       timer_info->timers[n_timers].hysteresis = val;
+
+               n_timers++;
+       }
+
+       timer_info->num_timers = n_timers;
+
+       return 0;
+}
+
+static struct therm_est_data *therm_est_get_pdata(struct device *dev)
+{
+       struct therm_est_data *data;
+       struct device_node *np;
+       struct device_node *ch;
+       u32 val;
+       int i, j, k, l;
+       int ret;
+
+       np = of_find_compatible_node(NULL, NULL, "nvidia,therm-est");
+       if (!np)
+               return dev->platform_data;
+
+       data = devm_kzalloc(dev, sizeof(struct therm_est_data), GFP_KERNEL);
+       if (!data)
+               return ERR_PTR(-ENOMEM);
+
+       ret = of_property_read_u32(np, "toffset", &val);
+       if (ret < 0)
+               return ERR_PTR(ret);
+       data->toffset = val;
+
+       ret = of_property_read_u32(np, "polling-period", &val);
+       if (ret < 0)
+               return ERR_PTR(ret);
+       data->polling_period = val;
+
+       ret = of_property_read_u32(np, "passive-delay", &val);
+       if (ret < 0)
+               return ERR_PTR(ret);
+       data->passive_delay = val;
+
+       ret = of_property_read_u32(np, "tc1", &val);
+       if (ret < 0)
+               return ERR_PTR(ret);
+       data->tc1 = val;
+
+       ret = of_property_read_u32(np, "tc2", &val);
+       if (ret < 0)
+               return ERR_PTR(ret);
+       data->tc2 = val;
+
+       i = j = k = l = 0;
+       for_each_child_of_node(np, ch) {
+               if (of_device_is_compatible(ch, "nvidia,therm-est-trip"))
+                       i++;
+               else if (of_device_is_compatible(ch, "nvidia,therm-est-subdev"))
+                       j++;
+               else if (of_device_is_compatible(ch, "nvidia,therm-est-tzp"))
+                       k++;
+               else if (of_device_is_compatible(ch,
+                                                "nvidia,therm-est-timer-trip"))
+                       l++;
+       }
+
+       /* trip point information and subdevices are must required data. */
+       if ((i == 0) || (j == 0))
+               return ERR_PTR(-ENOENT);
+
+       data->trips = devm_kzalloc(dev, sizeof(struct thermal_trip_info) * i,
+                                  GFP_KERNEL);
+       if (!data->trips)
+               return ERR_PTR(-ENOMEM);
+
+       data->devs = devm_kzalloc(dev, sizeof(struct therm_est_subdevice) * j,
+                                 GFP_KERNEL);
+       if (!data->devs)
+               return ERR_PTR(-ENOMEM);
+
+       /* thermal zone params is optional data. */
+       if (k > 0) {
+               data->tzp = devm_kzalloc(dev,
+                       sizeof(struct thermal_zone_params) * k, GFP_KERNEL);
+               if (!data->tzp)
+                       return ERR_PTR(-ENOMEM);
+       }
+
+       /* timer trip point information is optional data. */
+       if (l > 0) {
+               data->timer_trips = devm_kzalloc(dev,
+                               sizeof(struct therm_est_timer_trip_info) * l,
+                               GFP_KERNEL);
+               if (!data->timer_trips)
+                       return ERR_PTR(-ENOMEM);
+       }
+
+       i = j = l = 0;
+       for_each_child_of_node(np, ch) {
+               if (of_device_is_compatible(ch, "nvidia,therm-est-trip")) {
+                       ret = __parse_dt_trip(ch, &data->trips[i++]);
+                       if (ret < 0)
+                               return ERR_PTR(ret);
+               } else if (of_device_is_compatible(ch,
+                                                  "nvidia,therm-est-subdev")) {
+                       ret = __parse_dt_subdev(ch, &data->devs[j++]);
+                       if (ret < 0)
+                               return ERR_PTR(ret);
+               } else if (of_device_is_compatible(ch,
+                                                  "nvidia,therm-est-tzp")) {
+                       ret = __parse_dt_tzp(ch, data->tzp);
+                       if (ret < 0)
+                               return ERR_PTR(ret);
+               } else if (of_device_is_compatible(ch,
+                                       "nvidia,therm-est-timer-trip")) {
+                       ret = __parse_dt_timer_trip(ch, &data->timer_trips[l]);
+                       if (!ret)
+                               l++;
+               }
+       }
+
+       data->num_trips = i;
+       data->ndevs = j;
+       data->num_timer_trips = l;
+
+       return data;
+}
+#else
+static struct therm_est_data *therm_est_get_pdata(struct device *dev)
+{
+       return dev->platform_data;
+}
+#endif /* CONFIG_OF */
+
 static int __devinit therm_est_probe(struct platform_device *pdev)
 {
        int i;
@@ -706,7 +964,7 @@ static int __devinit therm_est_probe(struct platform_device *pdev)
 
        platform_set_drvdata(pdev, est);
 
-       data = pdev->dev.platform_data;
+       data = therm_est_get_pdata(&pdev->dev);
 
        est->devs = data->devs;
        est->ndevs = data->ndevs;