1 /* 2 * Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved. 3 * 4 * Author: 5 * Mikko Perttunen <mperttunen@nvidia.com> 6 * Aapo Vienamo <avienamo@nvidia.com> 7 * 8 * This software is licensed under the terms of the GNU General Public 9 * License version 2, as published by the Free Software Foundation, and 10 * may be copied, distributed, and modified under those terms. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 */ 18 19 #include <linux/err.h> 20 #include <linux/module.h> 21 #include <linux/platform_device.h> 22 #include <linux/thermal.h> 23 #include <linux/workqueue.h> 24 25 #include <soc/tegra/bpmp.h> 26 #include <soc/tegra/bpmp-abi.h> 27 28 struct tegra_bpmp_thermal_zone { 29 struct tegra_bpmp_thermal *tegra; 30 struct thermal_zone_device *tzd; 31 struct work_struct tz_device_update_work; 32 unsigned int idx; 33 }; 34 35 struct tegra_bpmp_thermal { 36 struct device *dev; 37 struct tegra_bpmp *bpmp; 38 unsigned int num_zones; 39 struct tegra_bpmp_thermal_zone **zones; 40 }; 41 42 static int tegra_bpmp_thermal_get_temp(void *data, int *out_temp) 43 { 44 struct tegra_bpmp_thermal_zone *zone = data; 45 struct mrq_thermal_host_to_bpmp_request req; 46 union mrq_thermal_bpmp_to_host_response reply; 47 struct tegra_bpmp_message msg; 48 int err; 49 50 memset(&req, 0, sizeof(req)); 51 req.type = CMD_THERMAL_GET_TEMP; 52 req.get_temp.zone = zone->idx; 53 54 memset(&msg, 0, sizeof(msg)); 55 msg.mrq = MRQ_THERMAL; 56 msg.tx.data = &req; 57 msg.tx.size = sizeof(req); 58 msg.rx.data = &reply; 59 msg.rx.size = sizeof(reply); 60 61 err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg); 62 if (err) 63 return err; 64 65 *out_temp = reply.get_temp.temp; 66 67 return 0; 68 } 69 70 static int tegra_bpmp_thermal_set_trips(void *data, int low, int high) 71 { 72 struct tegra_bpmp_thermal_zone *zone = data; 73 struct mrq_thermal_host_to_bpmp_request req; 74 struct tegra_bpmp_message msg; 75 76 memset(&req, 0, sizeof(req)); 77 req.type = CMD_THERMAL_SET_TRIP; 78 req.set_trip.zone = zone->idx; 79 req.set_trip.enabled = true; 80 req.set_trip.low = low; 81 req.set_trip.high = high; 82 83 memset(&msg, 0, sizeof(msg)); 84 msg.mrq = MRQ_THERMAL; 85 msg.tx.data = &req; 86 msg.tx.size = sizeof(req); 87 88 return tegra_bpmp_transfer(zone->tegra->bpmp, &msg); 89 } 90 91 static void tz_device_update_work_fn(struct work_struct *work) 92 { 93 struct tegra_bpmp_thermal_zone *zone; 94 95 zone = container_of(work, struct tegra_bpmp_thermal_zone, 96 tz_device_update_work); 97 98 thermal_zone_device_update(zone->tzd, THERMAL_TRIP_VIOLATED); 99 } 100 101 static void bpmp_mrq_thermal(unsigned int mrq, struct tegra_bpmp_channel *ch, 102 void *data) 103 { 104 struct mrq_thermal_bpmp_to_host_request *req; 105 struct tegra_bpmp_thermal *tegra = data; 106 int i; 107 108 req = (struct mrq_thermal_bpmp_to_host_request *)ch->ib->data; 109 110 if (req->type != CMD_THERMAL_HOST_TRIP_REACHED) { 111 dev_err(tegra->dev, "%s: invalid request type: %d\n", 112 __func__, req->type); 113 tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0); 114 return; 115 } 116 117 for (i = 0; i < tegra->num_zones; ++i) { 118 if (tegra->zones[i]->idx != req->host_trip_reached.zone) 119 continue; 120 121 schedule_work(&tegra->zones[i]->tz_device_update_work); 122 tegra_bpmp_mrq_return(ch, 0, NULL, 0); 123 return; 124 } 125 126 dev_err(tegra->dev, "%s: invalid thermal zone: %d\n", __func__, 127 req->host_trip_reached.zone); 128 tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0); 129 } 130 131 static int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp, 132 int *num_zones) 133 { 134 struct mrq_thermal_host_to_bpmp_request req; 135 union mrq_thermal_bpmp_to_host_response reply; 136 struct tegra_bpmp_message msg; 137 int err; 138 139 memset(&req, 0, sizeof(req)); 140 req.type = CMD_THERMAL_GET_NUM_ZONES; 141 142 memset(&msg, 0, sizeof(msg)); 143 msg.mrq = MRQ_THERMAL; 144 msg.tx.data = &req; 145 msg.tx.size = sizeof(req); 146 msg.rx.data = &reply; 147 msg.rx.size = sizeof(reply); 148 149 err = tegra_bpmp_transfer(bpmp, &msg); 150 if (err) 151 return err; 152 153 *num_zones = reply.get_num_zones.num; 154 155 return 0; 156 } 157 158 static const struct thermal_zone_of_device_ops tegra_bpmp_of_thermal_ops = { 159 .get_temp = tegra_bpmp_thermal_get_temp, 160 .set_trips = tegra_bpmp_thermal_set_trips, 161 }; 162 163 static int tegra_bpmp_thermal_probe(struct platform_device *pdev) 164 { 165 struct tegra_bpmp *bpmp = dev_get_drvdata(pdev->dev.parent); 166 struct tegra_bpmp_thermal *tegra; 167 struct thermal_zone_device *tzd; 168 unsigned int i, max_num_zones; 169 int err; 170 171 tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); 172 if (!tegra) 173 return -ENOMEM; 174 175 tegra->dev = &pdev->dev; 176 tegra->bpmp = bpmp; 177 178 err = tegra_bpmp_thermal_get_num_zones(bpmp, &max_num_zones); 179 if (err) { 180 dev_err(&pdev->dev, "failed to get the number of zones: %d\n", 181 err); 182 return err; 183 } 184 185 tegra->zones = devm_kcalloc(&pdev->dev, max_num_zones, 186 sizeof(*tegra->zones), GFP_KERNEL); 187 if (!tegra->zones) 188 return -ENOMEM; 189 190 for (i = 0; i < max_num_zones; ++i) { 191 struct tegra_bpmp_thermal_zone *zone; 192 int temp; 193 194 zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); 195 if (!zone) 196 return -ENOMEM; 197 198 zone->idx = i; 199 zone->tegra = tegra; 200 201 err = tegra_bpmp_thermal_get_temp(zone, &temp); 202 if (err < 0) { 203 devm_kfree(&pdev->dev, zone); 204 continue; 205 } 206 207 tzd = devm_thermal_zone_of_sensor_register( 208 &pdev->dev, i, zone, &tegra_bpmp_of_thermal_ops); 209 if (IS_ERR(tzd)) { 210 if (PTR_ERR(tzd) == -EPROBE_DEFER) 211 return -EPROBE_DEFER; 212 devm_kfree(&pdev->dev, zone); 213 continue; 214 } 215 216 zone->tzd = tzd; 217 INIT_WORK(&zone->tz_device_update_work, 218 tz_device_update_work_fn); 219 220 tegra->zones[tegra->num_zones++] = zone; 221 } 222 223 err = tegra_bpmp_request_mrq(bpmp, MRQ_THERMAL, bpmp_mrq_thermal, 224 tegra); 225 if (err) { 226 dev_err(&pdev->dev, "failed to register mrq handler: %d\n", 227 err); 228 return err; 229 } 230 231 platform_set_drvdata(pdev, tegra); 232 233 return 0; 234 } 235 236 static int tegra_bpmp_thermal_remove(struct platform_device *pdev) 237 { 238 struct tegra_bpmp_thermal *tegra = platform_get_drvdata(pdev); 239 240 tegra_bpmp_free_mrq(tegra->bpmp, MRQ_THERMAL, tegra); 241 242 return 0; 243 } 244 245 static const struct of_device_id tegra_bpmp_thermal_of_match[] = { 246 { .compatible = "nvidia,tegra186-bpmp-thermal" }, 247 { }, 248 }; 249 MODULE_DEVICE_TABLE(of, tegra_bpmp_thermal_of_match); 250 251 static struct platform_driver tegra_bpmp_thermal_driver = { 252 .probe = tegra_bpmp_thermal_probe, 253 .remove = tegra_bpmp_thermal_remove, 254 .driver = { 255 .name = "tegra-bpmp-thermal", 256 .of_match_table = tegra_bpmp_thermal_of_match, 257 }, 258 }; 259 module_platform_driver(tegra_bpmp_thermal_driver); 260 261 MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>"); 262 MODULE_DESCRIPTION("NVIDIA Tegra BPMP thermal sensor driver"); 263 MODULE_LICENSE("GPL v2"); 264