18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2015-2017, NVIDIA CORPORATION.  All rights reserved.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Author:
68c2ecf20Sopenharmony_ci *	Mikko Perttunen <mperttunen@nvidia.com>
78c2ecf20Sopenharmony_ci *	Aapo Vienamo	<avienamo@nvidia.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/err.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/thermal.h>
148c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <soc/tegra/bpmp.h>
178c2ecf20Sopenharmony_ci#include <soc/tegra/bpmp-abi.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistruct tegra_bpmp_thermal_zone {
208c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal *tegra;
218c2ecf20Sopenharmony_ci	struct thermal_zone_device *tzd;
228c2ecf20Sopenharmony_ci	struct work_struct tz_device_update_work;
238c2ecf20Sopenharmony_ci	unsigned int idx;
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistruct tegra_bpmp_thermal {
278c2ecf20Sopenharmony_ci	struct device *dev;
288c2ecf20Sopenharmony_ci	struct tegra_bpmp *bpmp;
298c2ecf20Sopenharmony_ci	unsigned int num_zones;
308c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal_zone **zones;
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic int tegra_bpmp_thermal_get_temp(void *data, int *out_temp)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal_zone *zone = data;
368c2ecf20Sopenharmony_ci	struct mrq_thermal_host_to_bpmp_request req;
378c2ecf20Sopenharmony_ci	union mrq_thermal_bpmp_to_host_response reply;
388c2ecf20Sopenharmony_ci	struct tegra_bpmp_message msg;
398c2ecf20Sopenharmony_ci	int err;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	memset(&req, 0, sizeof(req));
428c2ecf20Sopenharmony_ci	req.type = CMD_THERMAL_GET_TEMP;
438c2ecf20Sopenharmony_ci	req.get_temp.zone = zone->idx;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
468c2ecf20Sopenharmony_ci	msg.mrq = MRQ_THERMAL;
478c2ecf20Sopenharmony_ci	msg.tx.data = &req;
488c2ecf20Sopenharmony_ci	msg.tx.size = sizeof(req);
498c2ecf20Sopenharmony_ci	msg.rx.data = &reply;
508c2ecf20Sopenharmony_ci	msg.rx.size = sizeof(reply);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
538c2ecf20Sopenharmony_ci	if (err)
548c2ecf20Sopenharmony_ci		return err;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	*out_temp = reply.get_temp.temp;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	return 0;
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic int tegra_bpmp_thermal_set_trips(void *data, int low, int high)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal_zone *zone = data;
648c2ecf20Sopenharmony_ci	struct mrq_thermal_host_to_bpmp_request req;
658c2ecf20Sopenharmony_ci	struct tegra_bpmp_message msg;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	memset(&req, 0, sizeof(req));
688c2ecf20Sopenharmony_ci	req.type = CMD_THERMAL_SET_TRIP;
698c2ecf20Sopenharmony_ci	req.set_trip.zone = zone->idx;
708c2ecf20Sopenharmony_ci	req.set_trip.enabled = true;
718c2ecf20Sopenharmony_ci	req.set_trip.low = low;
728c2ecf20Sopenharmony_ci	req.set_trip.high = high;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
758c2ecf20Sopenharmony_ci	msg.mrq = MRQ_THERMAL;
768c2ecf20Sopenharmony_ci	msg.tx.data = &req;
778c2ecf20Sopenharmony_ci	msg.tx.size = sizeof(req);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	return tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic void tz_device_update_work_fn(struct work_struct *work)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal_zone *zone;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	zone = container_of(work, struct tegra_bpmp_thermal_zone,
878c2ecf20Sopenharmony_ci			    tz_device_update_work);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	thermal_zone_device_update(zone->tzd, THERMAL_TRIP_VIOLATED);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic void bpmp_mrq_thermal(unsigned int mrq, struct tegra_bpmp_channel *ch,
938c2ecf20Sopenharmony_ci			     void *data)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct mrq_thermal_bpmp_to_host_request *req;
968c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal *tegra = data;
978c2ecf20Sopenharmony_ci	int i;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	req = (struct mrq_thermal_bpmp_to_host_request *)ch->ib->data;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	if (req->type != CMD_THERMAL_HOST_TRIP_REACHED) {
1028c2ecf20Sopenharmony_ci		dev_err(tegra->dev, "%s: invalid request type: %d\n",
1038c2ecf20Sopenharmony_ci			__func__, req->type);
1048c2ecf20Sopenharmony_ci		tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);
1058c2ecf20Sopenharmony_ci		return;
1068c2ecf20Sopenharmony_ci	}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	for (i = 0; i < tegra->num_zones; ++i) {
1098c2ecf20Sopenharmony_ci		if (tegra->zones[i]->idx != req->host_trip_reached.zone)
1108c2ecf20Sopenharmony_ci			continue;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci		schedule_work(&tegra->zones[i]->tz_device_update_work);
1138c2ecf20Sopenharmony_ci		tegra_bpmp_mrq_return(ch, 0, NULL, 0);
1148c2ecf20Sopenharmony_ci		return;
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	dev_err(tegra->dev, "%s: invalid thermal zone: %d\n", __func__,
1188c2ecf20Sopenharmony_ci		req->host_trip_reached.zone);
1198c2ecf20Sopenharmony_ci	tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp,
1238c2ecf20Sopenharmony_ci					    int *num_zones)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	struct mrq_thermal_host_to_bpmp_request req;
1268c2ecf20Sopenharmony_ci	union mrq_thermal_bpmp_to_host_response reply;
1278c2ecf20Sopenharmony_ci	struct tegra_bpmp_message msg;
1288c2ecf20Sopenharmony_ci	int err;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	memset(&req, 0, sizeof(req));
1318c2ecf20Sopenharmony_ci	req.type = CMD_THERMAL_GET_NUM_ZONES;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
1348c2ecf20Sopenharmony_ci	msg.mrq = MRQ_THERMAL;
1358c2ecf20Sopenharmony_ci	msg.tx.data = &req;
1368c2ecf20Sopenharmony_ci	msg.tx.size = sizeof(req);
1378c2ecf20Sopenharmony_ci	msg.rx.data = &reply;
1388c2ecf20Sopenharmony_ci	msg.rx.size = sizeof(reply);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	err = tegra_bpmp_transfer(bpmp, &msg);
1418c2ecf20Sopenharmony_ci	if (err)
1428c2ecf20Sopenharmony_ci		return err;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	*num_zones = reply.get_num_zones.num;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	return 0;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic const struct thermal_zone_of_device_ops tegra_bpmp_of_thermal_ops = {
1508c2ecf20Sopenharmony_ci	.get_temp = tegra_bpmp_thermal_get_temp,
1518c2ecf20Sopenharmony_ci	.set_trips = tegra_bpmp_thermal_set_trips,
1528c2ecf20Sopenharmony_ci};
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic int tegra_bpmp_thermal_probe(struct platform_device *pdev)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	struct tegra_bpmp *bpmp = dev_get_drvdata(pdev->dev.parent);
1578c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal *tegra;
1588c2ecf20Sopenharmony_ci	struct thermal_zone_device *tzd;
1598c2ecf20Sopenharmony_ci	unsigned int i, max_num_zones;
1608c2ecf20Sopenharmony_ci	int err;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
1638c2ecf20Sopenharmony_ci	if (!tegra)
1648c2ecf20Sopenharmony_ci		return -ENOMEM;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	tegra->dev = &pdev->dev;
1678c2ecf20Sopenharmony_ci	tegra->bpmp = bpmp;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	err = tegra_bpmp_thermal_get_num_zones(bpmp, &max_num_zones);
1708c2ecf20Sopenharmony_ci	if (err) {
1718c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to get the number of zones: %d\n",
1728c2ecf20Sopenharmony_ci			err);
1738c2ecf20Sopenharmony_ci		return err;
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	tegra->zones = devm_kcalloc(&pdev->dev, max_num_zones,
1778c2ecf20Sopenharmony_ci				    sizeof(*tegra->zones), GFP_KERNEL);
1788c2ecf20Sopenharmony_ci	if (!tegra->zones)
1798c2ecf20Sopenharmony_ci		return -ENOMEM;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	for (i = 0; i < max_num_zones; ++i) {
1828c2ecf20Sopenharmony_ci		struct tegra_bpmp_thermal_zone *zone;
1838c2ecf20Sopenharmony_ci		int temp;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci		zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL);
1868c2ecf20Sopenharmony_ci		if (!zone)
1878c2ecf20Sopenharmony_ci			return -ENOMEM;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci		zone->idx = i;
1908c2ecf20Sopenharmony_ci		zone->tegra = tegra;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		err = tegra_bpmp_thermal_get_temp(zone, &temp);
1938c2ecf20Sopenharmony_ci		if (err < 0) {
1948c2ecf20Sopenharmony_ci			devm_kfree(&pdev->dev, zone);
1958c2ecf20Sopenharmony_ci			continue;
1968c2ecf20Sopenharmony_ci		}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		tzd = devm_thermal_zone_of_sensor_register(
1998c2ecf20Sopenharmony_ci			&pdev->dev, i, zone, &tegra_bpmp_of_thermal_ops);
2008c2ecf20Sopenharmony_ci		if (IS_ERR(tzd)) {
2018c2ecf20Sopenharmony_ci			if (PTR_ERR(tzd) == -EPROBE_DEFER)
2028c2ecf20Sopenharmony_ci				return -EPROBE_DEFER;
2038c2ecf20Sopenharmony_ci			devm_kfree(&pdev->dev, zone);
2048c2ecf20Sopenharmony_ci			continue;
2058c2ecf20Sopenharmony_ci		}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci		zone->tzd = tzd;
2088c2ecf20Sopenharmony_ci		INIT_WORK(&zone->tz_device_update_work,
2098c2ecf20Sopenharmony_ci			  tz_device_update_work_fn);
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci		tegra->zones[tegra->num_zones++] = zone;
2128c2ecf20Sopenharmony_ci	}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	err = tegra_bpmp_request_mrq(bpmp, MRQ_THERMAL, bpmp_mrq_thermal,
2158c2ecf20Sopenharmony_ci				     tegra);
2168c2ecf20Sopenharmony_ci	if (err) {
2178c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to register mrq handler: %d\n",
2188c2ecf20Sopenharmony_ci			err);
2198c2ecf20Sopenharmony_ci		return err;
2208c2ecf20Sopenharmony_ci	}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, tegra);
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	return 0;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic int tegra_bpmp_thermal_remove(struct platform_device *pdev)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	struct tegra_bpmp_thermal *tegra = platform_get_drvdata(pdev);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	tegra_bpmp_free_mrq(tegra->bpmp, MRQ_THERMAL, tegra);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	return 0;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cistatic const struct of_device_id tegra_bpmp_thermal_of_match[] = {
2378c2ecf20Sopenharmony_ci	{ .compatible = "nvidia,tegra186-bpmp-thermal" },
2388c2ecf20Sopenharmony_ci	{ },
2398c2ecf20Sopenharmony_ci};
2408c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, tegra_bpmp_thermal_of_match);
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic struct platform_driver tegra_bpmp_thermal_driver = {
2438c2ecf20Sopenharmony_ci	.probe = tegra_bpmp_thermal_probe,
2448c2ecf20Sopenharmony_ci	.remove = tegra_bpmp_thermal_remove,
2458c2ecf20Sopenharmony_ci	.driver = {
2468c2ecf20Sopenharmony_ci		.name = "tegra-bpmp-thermal",
2478c2ecf20Sopenharmony_ci		.of_match_table = tegra_bpmp_thermal_of_match,
2488c2ecf20Sopenharmony_ci	},
2498c2ecf20Sopenharmony_ci};
2508c2ecf20Sopenharmony_cimodule_platform_driver(tegra_bpmp_thermal_driver);
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
2538c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("NVIDIA Tegra BPMP thermal sensor driver");
2548c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
255