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 25#include <subdev/volt.h> 26 27#include <subdev/bios.h> 28#include <subdev/bios/vmap.h> 29#include <subdev/bios/volt.h> 30 31static int 32nouveau_volt_get(struct nouveau_volt *volt) 33{ 34 if (volt->vid_get) { 35 int ret = volt->vid_get(volt), i; 36 if (ret >= 0) { 37 for (i = 0; i < volt->vid_nr; i++) { 38 if (volt->vid[i].vid == ret) 39 return volt->vid[i].uv; 40 } 41 ret = -EINVAL; 42 } 43 return ret; 44 } 45 return -ENODEV; 46} 47 48static int 49nouveau_volt_set(struct nouveau_volt *volt, u32 uv) 50{ 51 if (volt->vid_set) { 52 int i, ret = -EINVAL; 53 for (i = 0; i < volt->vid_nr; i++) { 54 if (volt->vid[i].uv == uv) { 55 ret = volt->vid_set(volt, volt->vid[i].vid); 56 nv_debug(volt, "set %duv: %d\n", uv, ret); 57 break; 58 } 59 } 60 return ret; 61 } 62 return -ENODEV; 63} 64 65static int 66nouveau_volt_map(struct nouveau_volt *volt, u8 id) 67{ 68 struct nouveau_bios *bios = nouveau_bios(volt); 69 struct nvbios_vmap_entry info; 70 u8 ver, len; 71 u16 vmap; 72 73 vmap = nvbios_vmap_entry_parse(bios, id, &ver, &len, &info); 74 if (vmap) { 75 if (info.link != 0xff) { 76 int ret = nouveau_volt_map(volt, info.link); 77 if (ret < 0) 78 return ret; 79 info.min += ret; 80 } 81 return info.min; 82 } 83 84 return id ? id * 10000 : -ENODEV; 85} 86 87static int 88nouveau_volt_set_id(struct nouveau_volt *volt, u8 id, int condition) 89{ 90 int ret = nouveau_volt_map(volt, id); 91 if (ret >= 0) { 92 int prev = nouveau_volt_get(volt); 93 if (!condition || prev < 0 || 94 (condition < 0 && ret < prev) || 95 (condition > 0 && ret > prev)) { 96 ret = nouveau_volt_set(volt, ret); 97 } else { 98 ret = 0; 99 } 100 } 101 return ret; 102} 103 104int 105_nouveau_volt_init(struct nouveau_object *object) 106{ 107 struct nouveau_volt *volt = (void *)object; 108 int ret; 109 110 ret = nouveau_subdev_init(&volt->base); 111 if (ret) 112 return ret; 113 114 ret = volt->get(volt); 115 if (ret < 0) { 116 if (ret != -ENODEV) 117 nv_debug(volt, "current voltage unknown\n"); 118 return 0; 119 } 120 121 nv_info(volt, "GPU voltage: %duv\n", ret); 122 return 0; 123} 124 125void 126_nouveau_volt_dtor(struct nouveau_object *object) 127{ 128 struct nouveau_volt *volt = (void *)object; 129 nouveau_subdev_destroy(&volt->base); 130} 131 132int 133nouveau_volt_create_(struct nouveau_object *parent, 134 struct nouveau_object *engine, 135 struct nouveau_oclass *oclass, int length, void **pobject) 136{ 137 struct nouveau_bios *bios = nouveau_bios(parent); 138 struct nouveau_volt *volt; 139 struct nvbios_volt_entry ivid; 140 struct nvbios_volt info; 141 u8 ver, hdr, cnt, len; 142 u16 data; 143 int ret, i; 144 145 ret = nouveau_subdev_create_(parent, engine, oclass, 0, "VOLT", 146 "voltage", length, pobject); 147 volt = *pobject; 148 if (ret) 149 return ret; 150 151 volt->get = nouveau_volt_get; 152 volt->set = nouveau_volt_set; 153 volt->set_id = nouveau_volt_set_id; 154 155 data = nvbios_volt_parse(bios, &ver, &hdr, &cnt, &len, &info); 156 if (data && info.vidmask && info.base && info.step) { 157 for (i = 0; i < info.vidmask + 1; i++) { 158 if (info.base >= info.min && 159 info.base <= info.max) { 160 volt->vid[volt->vid_nr].uv = info.base; 161 volt->vid[volt->vid_nr].vid = i; 162 volt->vid_nr++; 163 } 164 info.base += info.step; 165 } 166 volt->vid_mask = info.vidmask; 167 } else 168 if (data && info.vidmask) { 169 for (i = 0; i < cnt; i++) { 170 data = nvbios_volt_entry_parse(bios, i, &ver, &hdr, 171 &ivid); 172 if (data) { 173 volt->vid[volt->vid_nr].uv = ivid.voltage; 174 volt->vid[volt->vid_nr].vid = ivid.vid; 175 volt->vid_nr++; 176 } 177 } 178 volt->vid_mask = info.vidmask; 179 } 180 181 if (volt->vid_nr) { 182 for (i = 0; i < volt->vid_nr; i++) { 183 nv_debug(volt, "VID %02x: %duv\n", 184 volt->vid[i].vid, volt->vid[i].uv); 185 } 186 187 /*XXX: this is an assumption.. there probably exists boards 188 * out there with i2c-connected voltage controllers too.. 189 */ 190 ret = nouveau_voltgpio_init(volt); 191 if (ret == 0) { 192 volt->vid_get = nouveau_voltgpio_get; 193 volt->vid_set = nouveau_voltgpio_set; 194 } 195 } 196 197 return ret; 198} 199