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