552 lines
13 KiB
C
552 lines
13 KiB
C
/*
|
|
* Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* Author:
|
|
* Cyril Raju <craju@nvidia.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/version.h>
|
|
|
|
#include <linux/backlight.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <dt-bindings/thermal/tegra-skin-thermal.h>
|
|
|
|
struct backlight_feature {
|
|
struct backlight_device *back_dev;
|
|
int val_rc;
|
|
};
|
|
|
|
struct iio_feature {
|
|
struct iio_channel *iio_c;
|
|
int val_rc;
|
|
};
|
|
|
|
struct power_feature {
|
|
int type;
|
|
int rc_k;
|
|
int resistance;
|
|
/*
|
|
* data pointer specific to corresponding feature type
|
|
*/
|
|
void *feature_data;
|
|
struct list_head node;
|
|
};
|
|
|
|
struct tegra_skin_thermal_hotspot {
|
|
int hotspot_rc;
|
|
int offset;
|
|
struct thermal_zone_device *ref_tzd;
|
|
int rc_k;
|
|
struct list_head node;
|
|
struct list_head power_feature_list;
|
|
};
|
|
|
|
struct tegra_skin_thermal_sensor {
|
|
struct thermal_zone_device *tzd;
|
|
struct list_head node;
|
|
struct list_head hotspot_list;
|
|
};
|
|
|
|
struct tegra_skin_thermal {
|
|
struct device *dev;
|
|
struct list_head sensor_list;
|
|
};
|
|
|
|
static int tegra_skin_hotspot_get_temp(
|
|
struct tegra_skin_thermal_hotspot *htspt,
|
|
int *out_temp)
|
|
{
|
|
struct power_feature *cur; /* iterator */
|
|
int delta_temp = 0, err = 0;
|
|
int ref_temp = THERMAL_TEMP_INVALID;
|
|
int temp = THERMAL_TEMP_INVALID;
|
|
|
|
if (htspt == NULL) {
|
|
temp = THERMAL_TEMP_INVALID;
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (!htspt->ref_tzd || !htspt->ref_tzd->ops->get_temp ||
|
|
htspt->ref_tzd->ops->get_temp(htspt->ref_tzd, &ref_temp)) {
|
|
pr_warn("tegra-skin: Reference sensor missing.\n");
|
|
temp = THERMAL_TEMP_INVALID;
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
list_for_each_entry(cur, &htspt->power_feature_list, node) {
|
|
if (cur->type == BACKLIGHT_BRIGHTNESS) {
|
|
struct backlight_feature *back_f = cur->feature_data;
|
|
int val;
|
|
|
|
val = back_f->back_dev->props.brightness;
|
|
back_f->val_rc += (val - back_f->val_rc);
|
|
back_f->val_rc *= cur->rc_k;
|
|
delta_temp += cur->resistance * back_f->val_rc;
|
|
} else if (cur->type == IIO_SUBSYSTEM) {
|
|
struct iio_feature *iio_f = cur->feature_data;
|
|
int val;
|
|
|
|
err = iio_read_channel_processed(iio_f->iio_c, &val);
|
|
if (err < 0) {
|
|
pr_warn("tegra-skin: iio read failed\n");
|
|
temp = THERMAL_TEMP_INVALID;
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
iio_f->val_rc += (val - iio_f->val_rc);
|
|
iio_f->val_rc *= cur->rc_k;
|
|
delta_temp += cur->resistance * iio_f->val_rc;
|
|
}
|
|
}
|
|
|
|
delta_temp += htspt->offset;
|
|
htspt->hotspot_rc += ref_temp - delta_temp - htspt->hotspot_rc;
|
|
htspt->hotspot_rc *= htspt->rc_k;
|
|
temp = htspt->hotspot_rc;
|
|
|
|
out:
|
|
*out_temp = temp;
|
|
return err;
|
|
}
|
|
|
|
static int tegra_skin_sensor_get_temp(void *data, int *out_temp)
|
|
{
|
|
struct tegra_skin_thermal_sensor *sensor = data;
|
|
struct tegra_skin_thermal_hotspot *cur;
|
|
int err, temp;
|
|
int max_temp = THERMAL_TEMP_INVALID; /* By default returning max */
|
|
|
|
list_for_each_entry(cur, &sensor->hotspot_list, node) {
|
|
err = tegra_skin_hotspot_get_temp(cur, &temp);
|
|
if (err < 0)
|
|
goto out;
|
|
else
|
|
err = 0;
|
|
|
|
if (temp > max_temp)
|
|
max_temp = temp;
|
|
}
|
|
|
|
*out_temp = max_temp;
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int tegra_skin_sensor_get_trend(void *data, int trip,
|
|
enum thermal_trend *trend)
|
|
{
|
|
struct tegra_skin_thermal_sensor *sensor = data;
|
|
int ret;
|
|
int trip_temp, temp, last_temp;
|
|
|
|
if (!sensor->tzd)
|
|
return -ENODEV;
|
|
|
|
ret = sensor->tzd->ops->get_trip_temp(sensor->tzd, trip, &trip_temp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&sensor->tzd->lock);
|
|
temp = sensor->tzd->temperature;
|
|
last_temp = sensor->tzd->last_temperature;
|
|
mutex_unlock(&sensor->tzd->lock);
|
|
|
|
if (temp > trip_temp) {
|
|
if (temp >= last_temp)
|
|
*trend = THERMAL_TREND_RAISING;
|
|
else
|
|
*trend = THERMAL_TREND_STABLE;
|
|
} else if (temp < trip_temp)
|
|
*trend = THERMAL_TREND_DROPPING;
|
|
else
|
|
*trend = THERMAL_TREND_STABLE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = {
|
|
.get_temp = tegra_skin_sensor_get_temp,
|
|
.get_trend = tegra_skin_sensor_get_trend,
|
|
};
|
|
|
|
static int of_tegra_skin_power_feature_parse(
|
|
struct platform_device *pdev,
|
|
struct tegra_skin_thermal_hotspot *hotspot,
|
|
struct device_node *child)
|
|
{
|
|
struct of_phandle_args out_args;
|
|
int i, of_err, num;
|
|
struct tegra_skin_thermal *tskin = platform_get_drvdata(pdev);
|
|
|
|
num = of_property_count_u32_elems(child, "power-features-list");
|
|
if (num <= 0) {
|
|
pr_err("tegra-skin: power-features-list incorrect/missing/empty\n");
|
|
of_err = num;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < num; i++) {
|
|
struct power_feature *pf;
|
|
struct backlight_feature *back_f;
|
|
struct device_node *node;
|
|
struct iio_feature *iio_f;
|
|
const char *str = NULL;
|
|
|
|
of_err = of_parse_phandle_with_args(child,
|
|
"power-features-list",
|
|
"#power-feature-cells", i,
|
|
&out_args);
|
|
if (of_err == -ENOENT) {
|
|
of_err = 0;
|
|
break;
|
|
} else if (of_err != 0) {
|
|
pr_err("tegra-skin: power-features-list parse fail\n");
|
|
goto out;
|
|
}
|
|
|
|
pf = devm_kzalloc(tskin->dev, sizeof(*pf), GFP_KERNEL);
|
|
if (!pf) {
|
|
of_err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
of_err = of_property_read_u32(out_args.np, "type", &pf->type);
|
|
if (of_err || pf->type < 0 || pf->type >= FEATURE_TYPE_COUNT) {
|
|
pr_err("tegra-skin: 'type' prop missing/incorrect\n");
|
|
goto out;
|
|
}
|
|
|
|
of_err = of_property_read_u32(out_args.np, "resistance",
|
|
&pf->resistance);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: 'resistance' prop missing\n");
|
|
goto out;
|
|
}
|
|
|
|
of_err = of_property_read_u32(out_args.np, "rc_k", &pf->rc_k);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: 'rc_k' feature prop missing\n");
|
|
goto out;
|
|
}
|
|
|
|
switch (pf->type) {
|
|
case BACKLIGHT_BRIGHTNESS:
|
|
back_f = devm_kzalloc(tskin->dev, sizeof(*back_f),
|
|
GFP_KERNEL);
|
|
if (!back_f) {
|
|
of_err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
node = of_find_node_by_phandle(out_args.args[0]);
|
|
if (node) {
|
|
back_f->back_dev =
|
|
of_find_backlight_by_node(node);
|
|
if (!back_f->back_dev) {
|
|
of_err = -EPROBE_DEFER;
|
|
pr_err("tegra-skin: of_find_backlight_by_node failed\n");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
pf->feature_data = (void *)back_f;
|
|
break;
|
|
case IIO_SUBSYSTEM:
|
|
iio_f = devm_kzalloc(tskin->dev, sizeof(*iio_f),
|
|
GFP_KERNEL);
|
|
if (!iio_f) {
|
|
of_err = -ENOMEM;
|
|
pr_err("tegra-skin: No memory\n");
|
|
goto out;
|
|
}
|
|
|
|
of_err = of_property_read_string_index(
|
|
pdev->dev.of_node,
|
|
"io-channel-names",
|
|
out_args.args[0], &str);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: 'io-channel-names' parse failed\n");
|
|
goto out;
|
|
}
|
|
|
|
iio_f->iio_c = iio_channel_get(&pdev->dev, str);
|
|
if (!iio_f->iio_c || IS_ERR(iio_f->iio_c)) {
|
|
of_err = -EINVAL;
|
|
pr_err("tegra-skin: iio_channel_get failed\n");
|
|
goto out;
|
|
}
|
|
|
|
pf->feature_data = (void *) iio_f;
|
|
break;
|
|
default:
|
|
of_err = -EINVAL;
|
|
pr_err("tegra-skin: Invalid type\n");
|
|
goto out;
|
|
}
|
|
list_add(&pf->node, &hotspot->power_feature_list);
|
|
}
|
|
out:
|
|
return of_err;
|
|
}
|
|
|
|
static int of_tegra_skin_hotspot_parse(
|
|
struct platform_device *pdev,
|
|
struct tegra_skin_thermal_hotspot *hotspot,
|
|
struct device_node *dev_node)
|
|
{
|
|
int of_err;
|
|
struct device_node *tz_np;
|
|
|
|
of_err = of_property_read_u32(dev_node, "offset",
|
|
&hotspot->offset);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: 'offset' prop missing\n");
|
|
goto out;
|
|
}
|
|
|
|
of_err = of_property_read_u32(dev_node, "rc_k", &hotspot->rc_k);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: 'rc_k' prop missing\n");
|
|
goto out;
|
|
}
|
|
|
|
tz_np = of_parse_phandle(dev_node, "reference-sensor", 0);
|
|
|
|
if (tz_np) {
|
|
hotspot->ref_tzd = thermal_zone_get_zone_by_node(tz_np);
|
|
if (IS_ERR(hotspot->ref_tzd)) {
|
|
pr_err("tegra-skin: 'reference-sensor' missing. Defer Probe!\n");
|
|
of_err = -EPROBE_DEFER;
|
|
goto out;
|
|
}
|
|
}
|
|
of_err = of_tegra_skin_power_feature_parse(pdev, hotspot, dev_node);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: Power feature of parse failed\n");
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return of_err;
|
|
}
|
|
|
|
static int of_tegra_skin_hotspots_parse(
|
|
struct platform_device *pdev,
|
|
struct tegra_skin_thermal_sensor *sensor,
|
|
struct device_node *child)
|
|
{
|
|
int num, of_err = 0, i;
|
|
struct device_node *htspt_node;
|
|
struct tegra_skin_thermal *tskin = platform_get_drvdata(pdev);
|
|
|
|
num = of_property_count_u32_elems(child, "hotspot-list");
|
|
if (num <= 0) {
|
|
pr_err("tegra-skin: hotspot-list incorrect/missing/empty\n");
|
|
of_err = num;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < num; i++) {
|
|
struct tegra_skin_thermal_hotspot *hotspot;
|
|
|
|
htspt_node = of_parse_phandle(child, "hotspot-list", i);
|
|
if (IS_ERR_OR_NULL(htspt_node)) {
|
|
pr_err("tegra-skin: Property 'hotspot-list' failed\n");
|
|
of_err = PTR_ERR(htspt_node);
|
|
goto out;
|
|
}
|
|
|
|
hotspot = devm_kzalloc(tskin->dev, sizeof(*hotspot),
|
|
GFP_KERNEL);
|
|
if (!hotspot) {
|
|
of_err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&hotspot->power_feature_list);
|
|
|
|
of_err = of_tegra_skin_hotspot_parse(pdev, hotspot, htspt_node);
|
|
if (of_err) {
|
|
pr_err("tegra-skin: Failed to parse hotspot\n");
|
|
goto out;
|
|
}
|
|
|
|
list_add(&hotspot->node, &sensor->hotspot_list);
|
|
}
|
|
|
|
out:
|
|
return of_err;
|
|
}
|
|
|
|
static void free_sensor(struct platform_device *pdev,
|
|
struct tegra_skin_thermal_sensor *sensor)
|
|
{
|
|
struct tegra_skin_thermal_hotspot *h;
|
|
struct power_feature *pf;
|
|
|
|
list_for_each_entry(h, &sensor->hotspot_list, node) {
|
|
list_for_each_entry(pf, &h->power_feature_list, node) {
|
|
devm_kfree(&pdev->dev, pf->feature_data);
|
|
devm_kfree(&pdev->dev, pf);
|
|
}
|
|
devm_kfree(&pdev->dev, h);
|
|
}
|
|
devm_kfree(&pdev->dev, sensor);
|
|
}
|
|
|
|
static int of_tegra_skin_parse(struct platform_device *pdev)
|
|
{
|
|
int of_err = 0;
|
|
struct device_node *dn = pdev->dev.of_node;
|
|
struct device_node *child = NULL;
|
|
struct thermal_zone_device *tzd;
|
|
int idx = 0;
|
|
struct tegra_skin_thermal *tskin = platform_get_drvdata(pdev);
|
|
|
|
INIT_LIST_HEAD(&tskin->sensor_list);
|
|
|
|
for_each_child_of_node(dn, child) {
|
|
struct tegra_skin_thermal_sensor *sensor;
|
|
|
|
if (!of_property_read_bool(child, "thermal-sensor"))
|
|
continue;
|
|
|
|
pr_info("tegra-skin: Parsing sensor %s\n", child->full_name);
|
|
sensor = devm_kzalloc(tskin->dev, sizeof(*sensor),
|
|
GFP_KERNEL);
|
|
if (!sensor) {
|
|
of_err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&sensor->hotspot_list);
|
|
|
|
of_err = of_property_read_u32(child, "thermal-sensor", &idx);
|
|
if (of_err || idx < 0) {
|
|
pr_err("tegra-skin: 'thermal-sensor' prop incorrect\n");
|
|
of_err = -EINVAL;
|
|
goto free_sensor;
|
|
}
|
|
|
|
of_err = of_tegra_skin_hotspots_parse(pdev, sensor, child);
|
|
if (of_err) {
|
|
if (of_err == -EPROBE_DEFER)
|
|
goto out;
|
|
pr_err("tegra-skin: Hotspots parse failed\n");
|
|
goto free_sensor;
|
|
}
|
|
|
|
tzd = thermal_zone_of_sensor_register(tskin->dev, idx, sensor,
|
|
&tegra_of_thermal_ops);
|
|
if (IS_ERR_OR_NULL(tzd)) {
|
|
pr_err("tegra-skin: Sensor registration failed\n");
|
|
of_err = PTR_ERR(tzd);
|
|
goto free_sensor;
|
|
}
|
|
|
|
sensor->tzd = tzd;
|
|
list_add(&sensor->node, &tskin->sensor_list);
|
|
|
|
continue;
|
|
free_sensor:
|
|
pr_warn("tegra-skin: Freeing sensor %s (%d)\n",
|
|
child->full_name, of_err);
|
|
of_err = 0;
|
|
free_sensor(pdev, sensor);
|
|
}
|
|
|
|
out:
|
|
return of_err;
|
|
}
|
|
static void free_resources(struct platform_device *pdev)
|
|
{
|
|
struct tegra_skin_thermal *tskin = platform_get_drvdata(pdev);
|
|
struct tegra_skin_thermal_sensor *cur;
|
|
|
|
list_for_each_entry(cur, &tskin->sensor_list, node)
|
|
thermal_zone_of_sensor_unregister(&pdev->dev, cur->tzd);
|
|
|
|
}
|
|
static int tegra_skin_thermal_probe(struct platform_device *pdev)
|
|
{
|
|
int err = 0;
|
|
struct tegra_skin_thermal *tskin = NULL;
|
|
|
|
tskin = devm_kzalloc(&pdev->dev, sizeof(*tskin), GFP_KERNEL);
|
|
if (!tskin) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
tskin->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, tskin);
|
|
|
|
err = of_tegra_skin_parse(pdev);
|
|
if (err) {
|
|
pr_err("tegra-skin: tegra_skin_thermal_probe failed\n");
|
|
goto free_queue;
|
|
}
|
|
|
|
pr_info("tegra-skin: tegra_skin_thermal registered!\n");
|
|
|
|
return err;
|
|
|
|
free_queue:
|
|
free_resources(pdev);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int tegra_skin_thermal_remove(struct platform_device *pdev)
|
|
{
|
|
free_resources(pdev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id tegra_skin_thermal_of_match[] = {
|
|
{
|
|
.compatible = "nvidia,tegra-skin-thermal",
|
|
},
|
|
{ },
|
|
};
|
|
|
|
static struct platform_driver tegra_skin_thermal_driver = {
|
|
.probe = tegra_skin_thermal_probe,
|
|
.remove = tegra_skin_thermal_remove,
|
|
.driver = {
|
|
.name = "tegra-skin-thermal",
|
|
.of_match_table = tegra_skin_thermal_of_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(tegra_skin_thermal_driver);
|
|
|
|
MODULE_AUTHOR("Cyril Raju <craju@nvidia.com>");
|
|
MODULE_DESCRIPTION("NVIDIA Tegra skin sensor driver");
|
|
MODULE_LICENSE("GPL");
|