• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Red Hat Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20  * OTHER DEALINGS IN THE SOFTWARE.
21  *
22  * Authors: Ben Skeggs
23  */
24 #include "priv.h"
25 
26 #include <subdev/bios.h>
27 #include <subdev/bios/vmap.h>
28 #include <subdev/bios/volt.h>
29 #include <subdev/therm.h>
30 
31 int
nvkm_volt_get(struct nvkm_volt * volt)32 nvkm_volt_get(struct nvkm_volt *volt)
33 {
34 	int ret, i;
35 
36 	if (volt->func->volt_get)
37 		return volt->func->volt_get(volt);
38 
39 	ret = volt->func->vid_get(volt);
40 	if (ret >= 0) {
41 		for (i = 0; i < volt->vid_nr; i++) {
42 			if (volt->vid[i].vid == ret)
43 				return volt->vid[i].uv;
44 		}
45 		ret = -EINVAL;
46 	}
47 	return ret;
48 }
49 
50 static int
nvkm_volt_set(struct nvkm_volt * volt,u32 uv)51 nvkm_volt_set(struct nvkm_volt *volt, u32 uv)
52 {
53 	struct nvkm_subdev *subdev = &volt->subdev;
54 	int i, ret = -EINVAL, best_err = volt->max_uv, best = -1;
55 
56 	if (volt->func->volt_set)
57 		return volt->func->volt_set(volt, uv);
58 
59 	for (i = 0; i < volt->vid_nr; i++) {
60 		int err = volt->vid[i].uv - uv;
61 		if (err < 0 || err > best_err)
62 			continue;
63 
64 		best_err = err;
65 		best = i;
66 		if (best_err == 0)
67 			break;
68 	}
69 
70 	if (best == -1) {
71 		nvkm_error(subdev, "couldn't set %iuv\n", uv);
72 		return ret;
73 	}
74 
75 	ret = volt->func->vid_set(volt, volt->vid[best].vid);
76 	nvkm_debug(subdev, "set req %duv to %duv: %d\n", uv,
77 		   volt->vid[best].uv, ret);
78 	return ret;
79 }
80 
81 int
nvkm_volt_map_min(struct nvkm_volt * volt,u8 id)82 nvkm_volt_map_min(struct nvkm_volt *volt, u8 id)
83 {
84 	struct nvkm_bios *bios = volt->subdev.device->bios;
85 	struct nvbios_vmap_entry info;
86 	u8  ver, len;
87 	u32 vmap;
88 
89 	vmap = nvbios_vmap_entry_parse(bios, id, &ver, &len, &info);
90 	if (vmap) {
91 		if (info.link != 0xff) {
92 			int ret = nvkm_volt_map_min(volt, info.link);
93 			if (ret < 0)
94 				return ret;
95 			info.min += ret;
96 		}
97 		return info.min;
98 	}
99 
100 	return id ? id * 10000 : -ENODEV;
101 }
102 
103 int
nvkm_volt_map(struct nvkm_volt * volt,u8 id,u8 temp)104 nvkm_volt_map(struct nvkm_volt *volt, u8 id, u8 temp)
105 {
106 	struct nvkm_bios *bios = volt->subdev.device->bios;
107 	struct nvbios_vmap_entry info;
108 	u8  ver, len;
109 	u32 vmap;
110 
111 	vmap = nvbios_vmap_entry_parse(bios, id, &ver, &len, &info);
112 	if (vmap) {
113 		s64 result;
114 
115 		if (volt->speedo < 0)
116 			return volt->speedo;
117 
118 		if (ver == 0x10 || (ver == 0x20 && info.mode == 0)) {
119 			result  = div64_s64((s64)info.arg[0], 10);
120 			result += div64_s64((s64)info.arg[1] * volt->speedo, 10);
121 			result += div64_s64((s64)info.arg[2] * volt->speedo * volt->speedo, 100000);
122 		} else if (ver == 0x20) {
123 			switch (info.mode) {
124 			/* 0x0 handled above! */
125 			case 0x1:
126 				result =  ((s64)info.arg[0] * 15625) >> 18;
127 				result += ((s64)info.arg[1] * volt->speedo * 15625) >> 18;
128 				result += ((s64)info.arg[2] * temp * 15625) >> 10;
129 				result += ((s64)info.arg[3] * volt->speedo * temp * 15625) >> 18;
130 				result += ((s64)info.arg[4] * volt->speedo * volt->speedo * 15625) >> 30;
131 				result += ((s64)info.arg[5] * temp * temp * 15625) >> 18;
132 				break;
133 			case 0x3:
134 				result = (info.min + info.max) / 2;
135 				break;
136 			case 0x2:
137 			default:
138 				result = info.min;
139 				break;
140 			}
141 		} else {
142 			return -ENODEV;
143 		}
144 
145 		result = min(max(result, (s64)info.min), (s64)info.max);
146 
147 		if (info.link != 0xff) {
148 			int ret = nvkm_volt_map(volt, info.link, temp);
149 			if (ret < 0)
150 				return ret;
151 			result += ret;
152 		}
153 		return result;
154 	}
155 
156 	return id ? id * 10000 : -ENODEV;
157 }
158 
159 int
nvkm_volt_set_id(struct nvkm_volt * volt,u8 id,u8 min_id,u8 temp,int condition)160 nvkm_volt_set_id(struct nvkm_volt *volt, u8 id, u8 min_id, u8 temp,
161 		 int condition)
162 {
163 	int ret;
164 
165 	if (volt->func->set_id)
166 		return volt->func->set_id(volt, id, condition);
167 
168 	ret = nvkm_volt_map(volt, id, temp);
169 	if (ret >= 0) {
170 		int prev = nvkm_volt_get(volt);
171 		if (!condition || prev < 0 ||
172 		    (condition < 0 && ret < prev) ||
173 		    (condition > 0 && ret > prev)) {
174 			int min = nvkm_volt_map(volt, min_id, temp);
175 			if (min >= 0)
176 				ret = max(min, ret);
177 			ret = nvkm_volt_set(volt, ret);
178 		} else {
179 			ret = 0;
180 		}
181 	}
182 	return ret;
183 }
184 
185 static void
nvkm_volt_parse_bios(struct nvkm_bios * bios,struct nvkm_volt * volt)186 nvkm_volt_parse_bios(struct nvkm_bios *bios, struct nvkm_volt *volt)
187 {
188 	struct nvkm_subdev *subdev = &bios->subdev;
189 	struct nvbios_volt_entry ivid;
190 	struct nvbios_volt info;
191 	u8  ver, hdr, cnt, len;
192 	u32 data;
193 	int i;
194 
195 	data = nvbios_volt_parse(bios, &ver, &hdr, &cnt, &len, &info);
196 	if (data && info.vidmask && info.base && info.step && info.ranged) {
197 		nvkm_debug(subdev, "found ranged based VIDs\n");
198 		volt->min_uv = info.min;
199 		volt->max_uv = info.max;
200 		for (i = 0; i < info.vidmask + 1; i++) {
201 			if (info.base >= info.min &&
202 				info.base <= info.max) {
203 				volt->vid[volt->vid_nr].uv = info.base;
204 				volt->vid[volt->vid_nr].vid = i;
205 				volt->vid_nr++;
206 			}
207 			info.base += info.step;
208 		}
209 		volt->vid_mask = info.vidmask;
210 	} else if (data && info.vidmask && !info.ranged) {
211 		nvkm_debug(subdev, "found entry based VIDs\n");
212 		volt->min_uv = 0xffffffff;
213 		volt->max_uv = 0;
214 		for (i = 0; i < cnt; i++) {
215 			data = nvbios_volt_entry_parse(bios, i, &ver, &hdr,
216 						       &ivid);
217 			if (data) {
218 				volt->vid[volt->vid_nr].uv = ivid.voltage;
219 				volt->vid[volt->vid_nr].vid = ivid.vid;
220 				volt->vid_nr++;
221 				volt->min_uv = min(volt->min_uv, ivid.voltage);
222 				volt->max_uv = max(volt->max_uv, ivid.voltage);
223 			}
224 		}
225 		volt->vid_mask = info.vidmask;
226 	} else if (data && info.type == NVBIOS_VOLT_PWM) {
227 		volt->min_uv = info.base;
228 		volt->max_uv = info.base + info.pwm_range;
229 	}
230 }
231 
232 static int
nvkm_volt_speedo_read(struct nvkm_volt * volt)233 nvkm_volt_speedo_read(struct nvkm_volt *volt)
234 {
235 	if (volt->func->speedo_read)
236 		return volt->func->speedo_read(volt);
237 	return -EINVAL;
238 }
239 
240 static int
nvkm_volt_init(struct nvkm_subdev * subdev)241 nvkm_volt_init(struct nvkm_subdev *subdev)
242 {
243 	struct nvkm_volt *volt = nvkm_volt(subdev);
244 	int ret = nvkm_volt_get(volt);
245 	if (ret < 0) {
246 		if (ret != -ENODEV)
247 			nvkm_debug(subdev, "current voltage unknown\n");
248 		return 0;
249 	}
250 	nvkm_debug(subdev, "current voltage: %duv\n", ret);
251 	return 0;
252 }
253 
254 static int
nvkm_volt_oneinit(struct nvkm_subdev * subdev)255 nvkm_volt_oneinit(struct nvkm_subdev *subdev)
256 {
257 	struct nvkm_volt *volt = nvkm_volt(subdev);
258 
259 	volt->speedo = nvkm_volt_speedo_read(volt);
260 	if (volt->speedo > 0)
261 		nvkm_debug(&volt->subdev, "speedo %x\n", volt->speedo);
262 
263 	if (volt->func->oneinit)
264 		return volt->func->oneinit(volt);
265 
266 	return 0;
267 }
268 
269 static void *
nvkm_volt_dtor(struct nvkm_subdev * subdev)270 nvkm_volt_dtor(struct nvkm_subdev *subdev)
271 {
272 	return nvkm_volt(subdev);
273 }
274 
275 static const struct nvkm_subdev_func
276 nvkm_volt = {
277 	.dtor = nvkm_volt_dtor,
278 	.init = nvkm_volt_init,
279 	.oneinit = nvkm_volt_oneinit,
280 };
281 
282 void
nvkm_volt_ctor(const struct nvkm_volt_func * func,struct nvkm_device * device,int index,struct nvkm_volt * volt)283 nvkm_volt_ctor(const struct nvkm_volt_func *func, struct nvkm_device *device,
284 	       int index, struct nvkm_volt *volt)
285 {
286 	struct nvkm_bios *bios = device->bios;
287 	int i;
288 
289 	nvkm_subdev_ctor(&nvkm_volt, device, index, &volt->subdev);
290 	volt->func = func;
291 
292 	/* Assuming the non-bios device should build the voltage table later */
293 	if (bios) {
294 		u8 ver, hdr, cnt, len;
295 		struct nvbios_vmap vmap;
296 
297 		nvkm_volt_parse_bios(bios, volt);
298 		nvkm_debug(&volt->subdev, "min: %iuv max: %iuv\n",
299 			   volt->min_uv, volt->max_uv);
300 
301 		if (nvbios_vmap_parse(bios, &ver, &hdr, &cnt, &len, &vmap)) {
302 			volt->max0_id = vmap.max0;
303 			volt->max1_id = vmap.max1;
304 			volt->max2_id = vmap.max2;
305 		} else {
306 			volt->max0_id = 0xff;
307 			volt->max1_id = 0xff;
308 			volt->max2_id = 0xff;
309 		}
310 	}
311 
312 	if (volt->vid_nr) {
313 		for (i = 0; i < volt->vid_nr; i++) {
314 			nvkm_debug(&volt->subdev, "VID %02x: %duv\n",
315 				   volt->vid[i].vid, volt->vid[i].uv);
316 		}
317 	}
318 }
319 
320 int
nvkm_volt_new_(const struct nvkm_volt_func * func,struct nvkm_device * device,int index,struct nvkm_volt ** pvolt)321 nvkm_volt_new_(const struct nvkm_volt_func *func, struct nvkm_device *device,
322 	       int index, struct nvkm_volt **pvolt)
323 {
324 	if (!(*pvolt = kzalloc(sizeof(**pvolt), GFP_KERNEL)))
325 		return -ENOMEM;
326 	nvkm_volt_ctor(func, device, index, *pvolt);
327 	return 0;
328 }
329