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