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