329 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved.4 *5 * Author:6 * Mikko Perttunen <mperttunen@nvidia.com>7 * Aapo Vienamo <avienamo@nvidia.com>8 */9 10#include <linux/err.h>11#include <linux/module.h>12#include <linux/platform_device.h>13#include <linux/thermal.h>14#include <linux/workqueue.h>15 16#include <soc/tegra/bpmp.h>17#include <soc/tegra/bpmp-abi.h>18 19struct tegra_bpmp_thermal_zone {20 struct tegra_bpmp_thermal *tegra;21 struct thermal_zone_device *tzd;22 struct work_struct tz_device_update_work;23 unsigned int idx;24};25 26struct tegra_bpmp_thermal {27 struct device *dev;28 struct tegra_bpmp *bpmp;29 unsigned int num_zones;30 struct tegra_bpmp_thermal_zone **zones;31};32 33static int __tegra_bpmp_thermal_get_temp(struct tegra_bpmp_thermal_zone *zone,34 int *out_temp)35{36 struct mrq_thermal_host_to_bpmp_request req;37 union mrq_thermal_bpmp_to_host_response reply;38 struct tegra_bpmp_message msg;39 int err;40 41 memset(&req, 0, sizeof(req));42 req.type = CMD_THERMAL_GET_TEMP;43 req.get_temp.zone = zone->idx;44 45 memset(&msg, 0, sizeof(msg));46 msg.mrq = MRQ_THERMAL;47 msg.tx.data = &req;48 msg.tx.size = sizeof(req);49 msg.rx.data = &reply;50 msg.rx.size = sizeof(reply);51 52 err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);53 if (err)54 return err;55 if (msg.rx.ret == -BPMP_EFAULT)56 return -EAGAIN;57 if (msg.rx.ret)58 return -EINVAL;59 60 *out_temp = reply.get_temp.temp;61 62 return 0;63}64 65static int tegra_bpmp_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)66{67 struct tegra_bpmp_thermal_zone *zone = thermal_zone_device_priv(tz);68 69 return __tegra_bpmp_thermal_get_temp(zone, out_temp);70}71 72static int tegra_bpmp_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)73{74 struct tegra_bpmp_thermal_zone *zone = thermal_zone_device_priv(tz);75 struct mrq_thermal_host_to_bpmp_request req;76 struct tegra_bpmp_message msg;77 int err;78 79 memset(&req, 0, sizeof(req));80 req.type = CMD_THERMAL_SET_TRIP;81 req.set_trip.zone = zone->idx;82 req.set_trip.enabled = true;83 req.set_trip.low = low;84 req.set_trip.high = high;85 86 memset(&msg, 0, sizeof(msg));87 msg.mrq = MRQ_THERMAL;88 msg.tx.data = &req;89 msg.tx.size = sizeof(req);90 91 err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);92 if (err)93 return err;94 if (msg.rx.ret)95 return -EINVAL;96 97 return 0;98}99 100static void tz_device_update_work_fn(struct work_struct *work)101{102 struct tegra_bpmp_thermal_zone *zone;103 104 zone = container_of(work, struct tegra_bpmp_thermal_zone,105 tz_device_update_work);106 107 thermal_zone_device_update(zone->tzd, THERMAL_TRIP_VIOLATED);108}109 110static void bpmp_mrq_thermal(unsigned int mrq, struct tegra_bpmp_channel *ch,111 void *data)112{113 struct mrq_thermal_bpmp_to_host_request req;114 struct tegra_bpmp_thermal *tegra = data;115 size_t offset;116 int i;117 118 offset = offsetof(struct tegra_bpmp_mb_data, data);119 iosys_map_memcpy_from(&req, &ch->ib, offset, sizeof(req));120 121 if (req.type != CMD_THERMAL_HOST_TRIP_REACHED) {122 dev_err(tegra->dev, "%s: invalid request type: %d\n", __func__, req.type);123 tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);124 return;125 }126 127 for (i = 0; i < tegra->num_zones; ++i) {128 if (tegra->zones[i]->idx != req.host_trip_reached.zone)129 continue;130 131 schedule_work(&tegra->zones[i]->tz_device_update_work);132 tegra_bpmp_mrq_return(ch, 0, NULL, 0);133 return;134 }135 136 dev_err(tegra->dev, "%s: invalid thermal zone: %d\n", __func__,137 req.host_trip_reached.zone);138 tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);139}140 141static int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp,142 int *num_zones)143{144 struct mrq_thermal_host_to_bpmp_request req;145 union mrq_thermal_bpmp_to_host_response reply;146 struct tegra_bpmp_message msg;147 int err;148 149 memset(&req, 0, sizeof(req));150 req.type = CMD_THERMAL_GET_NUM_ZONES;151 152 memset(&msg, 0, sizeof(msg));153 msg.mrq = MRQ_THERMAL;154 msg.tx.data = &req;155 msg.tx.size = sizeof(req);156 msg.rx.data = &reply;157 msg.rx.size = sizeof(reply);158 159 err = tegra_bpmp_transfer(bpmp, &msg);160 if (err)161 return err;162 if (msg.rx.ret)163 return -EINVAL;164 165 *num_zones = reply.get_num_zones.num;166 167 return 0;168}169 170static int tegra_bpmp_thermal_trips_supported(struct tegra_bpmp *bpmp, bool *supported)171{172 struct mrq_thermal_host_to_bpmp_request req;173 union mrq_thermal_bpmp_to_host_response reply;174 struct tegra_bpmp_message msg;175 int err;176 177 memset(&req, 0, sizeof(req));178 req.type = CMD_THERMAL_QUERY_ABI;179 req.query_abi.type = CMD_THERMAL_SET_TRIP;180 181 memset(&msg, 0, sizeof(msg));182 msg.mrq = MRQ_THERMAL;183 msg.tx.data = &req;184 msg.tx.size = sizeof(req);185 msg.rx.data = &reply;186 msg.rx.size = sizeof(reply);187 188 err = tegra_bpmp_transfer(bpmp, &msg);189 if (err)190 return err;191 192 if (msg.rx.ret == 0) {193 *supported = true;194 return 0;195 } else if (msg.rx.ret == -BPMP_ENODEV) {196 *supported = false;197 return 0;198 } else {199 return -EINVAL;200 }201}202 203static const struct thermal_zone_device_ops tegra_bpmp_of_thermal_ops = {204 .get_temp = tegra_bpmp_thermal_get_temp,205 .set_trips = tegra_bpmp_thermal_set_trips,206};207 208static const struct thermal_zone_device_ops tegra_bpmp_of_thermal_ops_notrips = {209 .get_temp = tegra_bpmp_thermal_get_temp,210};211 212static int tegra_bpmp_thermal_probe(struct platform_device *pdev)213{214 struct tegra_bpmp *bpmp = dev_get_drvdata(pdev->dev.parent);215 const struct thermal_zone_device_ops *thermal_ops;216 struct tegra_bpmp_thermal *tegra;217 struct thermal_zone_device *tzd;218 unsigned int i, max_num_zones;219 bool supported;220 int err;221 222 err = tegra_bpmp_thermal_trips_supported(bpmp, &supported);223 if (err) {224 dev_err(&pdev->dev, "failed to determine if trip points are supported\n");225 return err;226 }227 228 if (supported)229 thermal_ops = &tegra_bpmp_of_thermal_ops;230 else231 thermal_ops = &tegra_bpmp_of_thermal_ops_notrips;232 233 tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);234 if (!tegra)235 return -ENOMEM;236 237 tegra->dev = &pdev->dev;238 tegra->bpmp = bpmp;239 240 err = tegra_bpmp_thermal_get_num_zones(bpmp, &max_num_zones);241 if (err) {242 dev_err(&pdev->dev, "failed to get the number of zones: %d\n",243 err);244 return err;245 }246 247 tegra->zones = devm_kcalloc(&pdev->dev, max_num_zones,248 sizeof(*tegra->zones), GFP_KERNEL);249 if (!tegra->zones)250 return -ENOMEM;251 252 for (i = 0; i < max_num_zones; ++i) {253 struct tegra_bpmp_thermal_zone *zone;254 int temp;255 256 zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL);257 if (!zone)258 return -ENOMEM;259 260 zone->idx = i;261 zone->tegra = tegra;262 263 err = __tegra_bpmp_thermal_get_temp(zone, &temp);264 265 /*266 * Sensors in powergated domains may temporarily fail to be read267 * (-EAGAIN), but will become accessible when the domain is powered on.268 */269 if (err < 0 && err != -EAGAIN) {270 devm_kfree(&pdev->dev, zone);271 continue;272 }273 274 tzd = devm_thermal_of_zone_register(275 &pdev->dev, i, zone, thermal_ops);276 if (IS_ERR(tzd)) {277 if (PTR_ERR(tzd) == -EPROBE_DEFER)278 return -EPROBE_DEFER;279 devm_kfree(&pdev->dev, zone);280 continue;281 }282 283 zone->tzd = tzd;284 INIT_WORK(&zone->tz_device_update_work,285 tz_device_update_work_fn);286 287 tegra->zones[tegra->num_zones++] = zone;288 }289 290 err = tegra_bpmp_request_mrq(bpmp, MRQ_THERMAL, bpmp_mrq_thermal,291 tegra);292 if (err) {293 dev_err(&pdev->dev, "failed to register mrq handler: %d\n",294 err);295 return err;296 }297 298 platform_set_drvdata(pdev, tegra);299 300 return 0;301}302 303static void tegra_bpmp_thermal_remove(struct platform_device *pdev)304{305 struct tegra_bpmp_thermal *tegra = platform_get_drvdata(pdev);306 307 tegra_bpmp_free_mrq(tegra->bpmp, MRQ_THERMAL, tegra);308}309 310static const struct of_device_id tegra_bpmp_thermal_of_match[] = {311 { .compatible = "nvidia,tegra186-bpmp-thermal" },312 { },313};314MODULE_DEVICE_TABLE(of, tegra_bpmp_thermal_of_match);315 316static struct platform_driver tegra_bpmp_thermal_driver = {317 .probe = tegra_bpmp_thermal_probe,318 .remove_new = tegra_bpmp_thermal_remove,319 .driver = {320 .name = "tegra-bpmp-thermal",321 .of_match_table = tegra_bpmp_thermal_of_match,322 },323};324module_platform_driver(tegra_bpmp_thermal_driver);325 326MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");327MODULE_DESCRIPTION("NVIDIA Tegra BPMP thermal sensor driver");328MODULE_LICENSE("GPL v2");329