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