diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nouveau_volt.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_volt.c | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_volt.c b/drivers/gpu/drm/nouveau/nouveau_volt.c new file mode 100644 index 000000000000..6ce857688eb6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_volt.c | |||
@@ -0,0 +1,209 @@ | |||
1 | /* | ||
2 | * Copyright 2010 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 "drmP.h" | ||
26 | |||
27 | #include "nouveau_drv.h" | ||
28 | #include "nouveau_pm.h" | ||
29 | |||
30 | static const enum dcb_gpio_tag vidtag[] = { 0x04, 0x05, 0x06, 0x1a }; | ||
31 | static int nr_vidtag = sizeof(vidtag) / sizeof(vidtag[0]); | ||
32 | |||
33 | int | ||
34 | nouveau_voltage_gpio_get(struct drm_device *dev) | ||
35 | { | ||
36 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
37 | struct nouveau_gpio_engine *gpio = &dev_priv->engine.gpio; | ||
38 | struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; | ||
39 | u8 vid = 0; | ||
40 | int i; | ||
41 | |||
42 | for (i = 0; i < nr_vidtag; i++) { | ||
43 | if (!(volt->vid_mask & (1 << i))) | ||
44 | continue; | ||
45 | |||
46 | vid |= gpio->get(dev, vidtag[i]) << i; | ||
47 | } | ||
48 | |||
49 | return nouveau_volt_lvl_lookup(dev, vid); | ||
50 | } | ||
51 | |||
52 | int | ||
53 | nouveau_voltage_gpio_set(struct drm_device *dev, int voltage) | ||
54 | { | ||
55 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
56 | struct nouveau_gpio_engine *gpio = &dev_priv->engine.gpio; | ||
57 | struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; | ||
58 | int vid, i; | ||
59 | |||
60 | vid = nouveau_volt_vid_lookup(dev, voltage); | ||
61 | if (vid < 0) | ||
62 | return vid; | ||
63 | |||
64 | for (i = 0; i < nr_vidtag; i++) { | ||
65 | if (!(volt->vid_mask & (1 << i))) | ||
66 | continue; | ||
67 | |||
68 | gpio->set(dev, vidtag[i], !!(vid & (1 << i))); | ||
69 | } | ||
70 | |||
71 | return 0; | ||
72 | } | ||
73 | |||
74 | int | ||
75 | nouveau_volt_vid_lookup(struct drm_device *dev, int voltage) | ||
76 | { | ||
77 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
78 | struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; | ||
79 | int i; | ||
80 | |||
81 | for (i = 0; i < volt->nr_level; i++) { | ||
82 | if (volt->level[i].voltage == voltage) | ||
83 | return volt->level[i].vid; | ||
84 | } | ||
85 | |||
86 | return -ENOENT; | ||
87 | } | ||
88 | |||
89 | int | ||
90 | nouveau_volt_lvl_lookup(struct drm_device *dev, int vid) | ||
91 | { | ||
92 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
93 | struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; | ||
94 | int i; | ||
95 | |||
96 | for (i = 0; i < volt->nr_level; i++) { | ||
97 | if (volt->level[i].vid == vid) | ||
98 | return volt->level[i].voltage; | ||
99 | } | ||
100 | |||
101 | return -ENOENT; | ||
102 | } | ||
103 | |||
104 | void | ||
105 | nouveau_volt_init(struct drm_device *dev) | ||
106 | { | ||
107 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
108 | struct nouveau_pm_engine *pm = &dev_priv->engine.pm; | ||
109 | struct nouveau_pm_voltage *voltage = &pm->voltage; | ||
110 | struct nvbios *bios = &dev_priv->vbios; | ||
111 | struct bit_entry P; | ||
112 | u8 *volt = NULL, *entry; | ||
113 | int i, recordlen, entries, vidmask, vidshift; | ||
114 | |||
115 | if (bios->type == NVBIOS_BIT) { | ||
116 | if (bit_table(dev, 'P', &P)) | ||
117 | return; | ||
118 | |||
119 | if (P.version == 1) | ||
120 | volt = ROMPTR(bios, P.data[16]); | ||
121 | else | ||
122 | if (P.version == 2) | ||
123 | volt = ROMPTR(bios, P.data[12]); | ||
124 | else { | ||
125 | NV_WARN(dev, "unknown volt for BIT P %d\n", P.version); | ||
126 | } | ||
127 | } else { | ||
128 | if (bios->data[bios->offset + 6] < 0x27) { | ||
129 | NV_DEBUG(dev, "BMP version too old for voltage\n"); | ||
130 | return; | ||
131 | } | ||
132 | |||
133 | volt = ROMPTR(bios, bios->data[bios->offset + 0x98]); | ||
134 | } | ||
135 | |||
136 | if (!volt) { | ||
137 | NV_DEBUG(dev, "voltage table pointer invalid\n"); | ||
138 | return; | ||
139 | } | ||
140 | |||
141 | switch (volt[0]) { | ||
142 | case 0x10: | ||
143 | case 0x11: | ||
144 | case 0x12: | ||
145 | recordlen = 5; | ||
146 | entries = volt[2]; | ||
147 | vidshift = 0; | ||
148 | vidmask = volt[4]; | ||
149 | break; | ||
150 | case 0x20: | ||
151 | recordlen = volt[3]; | ||
152 | entries = volt[2]; | ||
153 | vidshift = 0; /* could be vidshift like 0x30? */ | ||
154 | vidmask = volt[5]; | ||
155 | break; | ||
156 | case 0x30: | ||
157 | recordlen = volt[2]; | ||
158 | entries = volt[3]; | ||
159 | vidshift = hweight8(volt[5]); | ||
160 | vidmask = volt[4]; | ||
161 | break; | ||
162 | default: | ||
163 | NV_WARN(dev, "voltage table 0x%02x unknown\n", volt[0]); | ||
164 | return; | ||
165 | } | ||
166 | |||
167 | /* validate vid mask */ | ||
168 | voltage->vid_mask = vidmask; | ||
169 | if (!voltage->vid_mask) | ||
170 | return; | ||
171 | |||
172 | i = 0; | ||
173 | while (vidmask) { | ||
174 | if (i > nr_vidtag) { | ||
175 | NV_DEBUG(dev, "vid bit %d unknown\n", i); | ||
176 | return; | ||
177 | } | ||
178 | |||
179 | if (!nouveau_bios_gpio_entry(dev, vidtag[i])) { | ||
180 | NV_DEBUG(dev, "vid bit %d has no gpio tag\n", i); | ||
181 | return; | ||
182 | } | ||
183 | |||
184 | vidmask >>= 1; | ||
185 | i++; | ||
186 | } | ||
187 | |||
188 | /* parse vbios entries into common format */ | ||
189 | voltage->level = kcalloc(entries, sizeof(*voltage->level), GFP_KERNEL); | ||
190 | if (!voltage->level) | ||
191 | return; | ||
192 | |||
193 | entry = volt + volt[1]; | ||
194 | for (i = 0; i < entries; i++, entry += recordlen) { | ||
195 | voltage->level[i].voltage = entry[0]; | ||
196 | voltage->level[i].vid = entry[1] >> vidshift; | ||
197 | } | ||
198 | voltage->nr_level = entries; | ||
199 | voltage->supported = true; | ||
200 | } | ||
201 | |||
202 | void | ||
203 | nouveau_volt_fini(struct drm_device *dev) | ||
204 | { | ||
205 | struct drm_nouveau_private *dev_priv = dev->dev_private; | ||
206 | struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; | ||
207 | |||
208 | kfree(volt->level); | ||
209 | } | ||