linux/drivers/gpu/drm/nouveau/nvkm/subdev/volt/base.c
<<
>>
Prefs
   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
  31int
  32nvkm_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
  50static int
  51nvkm_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
  81int
  82nvkm_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
 103int
 104nvkm_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
 159int
 160nvkm_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
 185static void
 186nvkm_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
 232static int
 233nvkm_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
 240static int
 241nvkm_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
 254static int
 255nvkm_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
 269static void *
 270nvkm_volt_dtor(struct nvkm_subdev *subdev)
 271{
 272        return nvkm_volt(subdev);
 273}
 274
 275static const struct nvkm_subdev_func
 276nvkm_volt = {
 277        .dtor = nvkm_volt_dtor,
 278        .init = nvkm_volt_init,
 279        .oneinit = nvkm_volt_oneinit,
 280};
 281
 282void
 283nvkm_volt_ctor(const struct nvkm_volt_func *func, struct nvkm_device *device,
 284               enum nvkm_subdev_type type, int inst, struct nvkm_volt *volt)
 285{
 286        struct nvkm_bios *bios = device->bios;
 287        int i;
 288
 289        nvkm_subdev_ctor(&nvkm_volt, device, type, inst, &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
 320int
 321nvkm_volt_new_(const struct nvkm_volt_func *func, struct nvkm_device *device,
 322               enum nvkm_subdev_type type, int inst, struct nvkm_volt **pvolt)
 323{
 324        if (!(*pvolt = kzalloc(sizeof(**pvolt), GFP_KERNEL)))
 325                return -ENOMEM;
 326        nvkm_volt_ctor(func, device, type, inst, *pvolt);
 327        return 0;
 328}
 329