1/* $NetBSD: nouveau_subdev_mxm_base.c,v 1.1.1.1 2014/08/06 12:36:31 riastradh Exp $ */
2
3/*
4 * Copyright 2011 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_mxm_base.c,v 1.1.1.1 2014/08/06 12:36:31 riastradh Exp $");
29
30#include <core/option.h>
31
32#include <subdev/i2c.h>
33#include <subdev/mxm.h>
34#include <subdev/bios.h>
35#include <subdev/bios/mxm.h>
36
37#include "mxms.h"
38
39static bool
40mxm_shadow_rom_fetch(struct nouveau_i2c_port *i2c, u8 addr,
41 u8 offset, u8 size, u8 *data)
42{
43 struct i2c_msg msgs[] = {
44 { .addr = addr, .flags = 0, .len = 1, .buf = &offset },
45 { .addr = addr, .flags = I2C_M_RD, .len = size, .buf = data, },
46 };
47
48 return i2c_transfer(&i2c->adapter, msgs, 2) == 2;
49}
50
51static bool
52mxm_shadow_rom(struct nouveau_mxm *mxm, u8 version)
53{
54 struct nouveau_bios *bios = nouveau_bios(mxm);
55 struct nouveau_i2c *i2c = nouveau_i2c(mxm);
56 struct nouveau_i2c_port *port = NULL;
57 u8 i2cidx, mxms[6], addr, size;
58
59 i2cidx = mxm_ddc_map(bios, 1 /* LVDS_DDC */) & 0x0f;
60 if (i2cidx < 0x0f)
61 port = i2c->find(i2c, i2cidx);
62 if (!port)
63 return false;
64
65 addr = 0x54;
66 if (!mxm_shadow_rom_fetch(port, addr, 0, 6, mxms)) {
67 addr = 0x56;
68 if (!mxm_shadow_rom_fetch(port, addr, 0, 6, mxms))
69 return false;
70 }
71
72 mxm->mxms = mxms;
73 size = mxms_headerlen(mxm) + mxms_structlen(mxm);
74 mxm->mxms = kmalloc(size, GFP_KERNEL);
75
76 if (mxm->mxms &&
77 mxm_shadow_rom_fetch(port, addr, 0, size, mxm->mxms))
78 return true;
79
80 kfree(mxm->mxms);
81 mxm->mxms = NULL;
82 return false;
83}
84
85#if defined(CONFIG_ACPI)
86static bool
87mxm_shadow_dsm(struct nouveau_mxm *mxm, u8 version)
88{
89 struct nouveau_device *device = nv_device(mxm);
90 static char muid[] = {
91 0x00, 0xA4, 0x04, 0x40, 0x7D, 0x91, 0xF2, 0x4C,
92 0xB8, 0x9C, 0x79, 0xB6, 0x2F, 0xD5, 0x56, 0x65
93 };
94 u32 mxms_args[] = { 0x00000000 };
95 union acpi_object argv4 = {
96 .buffer.type = ACPI_TYPE_BUFFER,
97 .buffer.length = sizeof(mxms_args),
98 .buffer.pointer = (char *)mxms_args,
99 };
100 union acpi_object *obj;
101 acpi_handle handle;
102 int rev;
103
104 handle = ACPI_HANDLE(nv_device_base(device));
105 if (!handle)
106 return false;
107
108 /*
109 * spec says this can be zero to mean "highest revision", but
110 * of course there's at least one bios out there which fails
111 * unless you pass in exactly the version it supports..
112 */
113 rev = (version & 0xf0) << 4 | (version & 0x0f);
114 obj = acpi_evaluate_dsm(handle, muid, rev, 0x00000010, &argv4);
115 if (!obj) {
116 nv_debug(mxm, "DSM MXMS failed\n");
117 return false;
118 }
119
120 if (obj->type == ACPI_TYPE_BUFFER) {
121 mxm->mxms = kmemdup(obj->buffer.pointer,
122 obj->buffer.length, GFP_KERNEL);
123 } else if (obj->type == ACPI_TYPE_INTEGER) {
124 nv_debug(mxm, "DSM MXMS returned 0x%llx\n", obj->integer.value);
125 }
126
127 ACPI_FREE(obj);
128 return mxm->mxms != NULL;
129}
130#endif
131
132#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
133
134#define WMI_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0"
135
136static u8
137wmi_wmmx_mxmi(struct nouveau_mxm *mxm, u8 version)
138{
139 u32 mxmi_args[] = { 0x494D584D /* MXMI */, version, 0 };
140 struct acpi_buffer args = { sizeof(mxmi_args), mxmi_args };
141 struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL };
142 union acpi_object *obj;
143 acpi_status status;
144
145 status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn);
146 if (ACPI_FAILURE(status)) {
147 nv_debug(mxm, "WMMX MXMI returned %d\n", status);
148 return 0x00;
149 }
150
151 obj = retn.pointer;
152 if (obj->type == ACPI_TYPE_INTEGER) {
153 version = obj->integer.value;
154 nv_debug(mxm, "WMMX MXMI version %d.%d\n",
155 (version >> 4), version & 0x0f);
156 } else {
157 version = 0;
158 nv_debug(mxm, "WMMX MXMI returned non-integer\n");
159 }
160
161 kfree(obj);
162 return version;
163}
164
165static bool
166mxm_shadow_wmi(struct nouveau_mxm *mxm, u8 version)
167{
168 u32 mxms_args[] = { 0x534D584D /* MXMS */, version, 0 };
169 struct acpi_buffer args = { sizeof(mxms_args), mxms_args };
170 struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL };
171 union acpi_object *obj;
172 acpi_status status;
173
174 if (!wmi_has_guid(WMI_WMMX_GUID)) {
175 nv_debug(mxm, "WMMX GUID not found\n");
176 return false;
177 }
178
179 mxms_args[1] = wmi_wmmx_mxmi(mxm, 0x00);
180 if (!mxms_args[1])
181 mxms_args[1] = wmi_wmmx_mxmi(mxm, version);
182 if (!mxms_args[1])
183 return false;
184
185 status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn);
186 if (ACPI_FAILURE(status)) {
187 nv_debug(mxm, "WMMX MXMS returned %d\n", status);
188 return false;
189 }
190
191 obj = retn.pointer;
192 if (obj->type == ACPI_TYPE_BUFFER) {
193 mxm->mxms = kmemdup(obj->buffer.pointer,
194 obj->buffer.length, GFP_KERNEL);
195 }
196
197 kfree(obj);
198 return mxm->mxms != NULL;
199}
200#endif
201
202static struct mxm_shadow_h {
203 const char *name;
204 bool (*exec)(struct nouveau_mxm *, u8 version);
205} _mxm_shadow[] = {
206 { "ROM", mxm_shadow_rom },
207#if defined(CONFIG_ACPI)
208 { "DSM", mxm_shadow_dsm },
209#endif
210#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
211 { "WMI", mxm_shadow_wmi },
212#endif
213 {}
214};
215
216static int
217mxm_shadow(struct nouveau_mxm *mxm, u8 version)
218{
219 struct mxm_shadow_h *shadow = _mxm_shadow;
220 do {
221 nv_debug(mxm, "checking %s\n", shadow->name);
222 if (shadow->exec(mxm, version)) {
223 if (mxms_valid(mxm))
224 return 0;
225 kfree(mxm->mxms);
226 mxm->mxms = NULL;
227 }
228 } while ((++shadow)->name);
229 return -ENOENT;
230}
231
232int
233nouveau_mxm_create_(struct nouveau_object *parent,
234 struct nouveau_object *engine,
235 struct nouveau_oclass *oclass, int length, void **pobject)
236{
237 struct nouveau_device *device = nv_device(parent);
238 struct nouveau_bios *bios = nouveau_bios(device);
239 struct nouveau_mxm *mxm;
240 u8 ver, len;
241 u16 data;
242 int ret;
243
244 ret = nouveau_subdev_create_(parent, engine, oclass, 0, "MXM", "mxm",
245 length, pobject);
246 mxm = *pobject;
247 if (ret)
248 return ret;
249
250 data = mxm_table(bios, &ver, &len);
251 if (!data || !(ver = nv_ro08(bios, data))) {
252 nv_debug(mxm, "no VBIOS data, nothing to do\n");
253 return 0;
254 }
255
256 nv_info(mxm, "BIOS version %d.%d\n", ver >> 4, ver & 0x0f);
257
258 if (mxm_shadow(mxm, ver)) {
259 nv_info(mxm, "failed to locate valid SIS\n");
260#if 0
261 /* we should, perhaps, fall back to some kind of limited
262 * mode here if the x86 vbios hasn't already done the
263 * work for us (so we prevent loading with completely
264 * whacked vbios tables).
265 */
266 return -EINVAL;
267#else
268 return 0;
269#endif
270 }
271
272 nv_info(mxm, "MXMS Version %d.%d\n",
273 mxms_version(mxm) >> 8, mxms_version(mxm) & 0xff);
274 mxms_foreach(mxm, 0, NULL, NULL);
275
276 if (nouveau_boolopt(device->cfgopt, "NvMXMDCB", true))
277 mxm->action |= MXM_SANITISE_DCB;
278 return 0;
279}
280