1c39f472eSBen Skeggs /*
2c39f472eSBen Skeggs * Copyright 2011 Red Hat Inc.
3c39f472eSBen Skeggs *
4c39f472eSBen Skeggs * Permission is hereby granted, free of charge, to any person obtaining a
5c39f472eSBen Skeggs * copy of this software and associated documentation files (the "Software"),
6c39f472eSBen Skeggs * to deal in the Software without restriction, including without limitation
7c39f472eSBen Skeggs * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8c39f472eSBen Skeggs * and/or sell copies of the Software, and to permit persons to whom the
9c39f472eSBen Skeggs * Software is furnished to do so, subject to the following conditions:
10c39f472eSBen Skeggs *
11c39f472eSBen Skeggs * The above copyright notice and this permission notice shall be included in
12c39f472eSBen Skeggs * all copies or substantial portions of the Software.
13c39f472eSBen Skeggs *
14c39f472eSBen Skeggs * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15c39f472eSBen Skeggs * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16c39f472eSBen Skeggs * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17c39f472eSBen Skeggs * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18c39f472eSBen Skeggs * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19c39f472eSBen Skeggs * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20c39f472eSBen Skeggs * OTHER DEALINGS IN THE SOFTWARE.
21c39f472eSBen Skeggs *
22c39f472eSBen Skeggs * Authors: Ben Skeggs
23c39f472eSBen Skeggs */
24c39f472eSBen Skeggs #include "mxms.h"
25c39f472eSBen Skeggs
26be83cd4eSBen Skeggs #include <core/option.h>
27be83cd4eSBen Skeggs #include <subdev/bios.h>
28be83cd4eSBen Skeggs #include <subdev/bios/mxm.h>
29be83cd4eSBen Skeggs #include <subdev/i2c.h>
30be83cd4eSBen Skeggs
31c39f472eSBen Skeggs static bool
mxm_shadow_rom_fetch(struct nvkm_i2c_bus * bus,u8 addr,u8 offset,u8 size,u8 * data)322aa5eac5SBen Skeggs mxm_shadow_rom_fetch(struct nvkm_i2c_bus *bus, u8 addr,
33c39f472eSBen Skeggs u8 offset, u8 size, u8 *data)
34c39f472eSBen Skeggs {
35c39f472eSBen Skeggs struct i2c_msg msgs[] = {
36c39f472eSBen Skeggs { .addr = addr, .flags = 0, .len = 1, .buf = &offset },
37c39f472eSBen Skeggs { .addr = addr, .flags = I2C_M_RD, .len = size, .buf = data, },
38c39f472eSBen Skeggs };
39c39f472eSBen Skeggs
402aa5eac5SBen Skeggs return i2c_transfer(&bus->i2c, msgs, 2) == 2;
41c39f472eSBen Skeggs }
42c39f472eSBen Skeggs
43c39f472eSBen Skeggs static bool
mxm_shadow_rom(struct nvkm_mxm * mxm,u8 version)44be83cd4eSBen Skeggs mxm_shadow_rom(struct nvkm_mxm *mxm, u8 version)
45c39f472eSBen Skeggs {
4646484438SBen Skeggs struct nvkm_device *device = mxm->subdev.device;
4746484438SBen Skeggs struct nvkm_bios *bios = device->bios;
4846484438SBen Skeggs struct nvkm_i2c *i2c = device->i2c;
492aa5eac5SBen Skeggs struct nvkm_i2c_bus *bus = NULL;
50c39f472eSBen Skeggs u8 i2cidx, mxms[6], addr, size;
51c39f472eSBen Skeggs
52c39f472eSBen Skeggs i2cidx = mxm_ddc_map(bios, 1 /* LVDS_DDC */) & 0x0f;
53c39f472eSBen Skeggs if (i2cidx < 0x0f)
542aa5eac5SBen Skeggs bus = nvkm_i2c_bus_find(i2c, i2cidx);
552aa5eac5SBen Skeggs if (!bus)
56c39f472eSBen Skeggs return false;
57c39f472eSBen Skeggs
58c39f472eSBen Skeggs addr = 0x54;
592aa5eac5SBen Skeggs if (!mxm_shadow_rom_fetch(bus, addr, 0, 6, mxms)) {
60c39f472eSBen Skeggs addr = 0x56;
612aa5eac5SBen Skeggs if (!mxm_shadow_rom_fetch(bus, addr, 0, 6, mxms))
62c39f472eSBen Skeggs return false;
63c39f472eSBen Skeggs }
64c39f472eSBen Skeggs
65c39f472eSBen Skeggs mxm->mxms = mxms;
66c39f472eSBen Skeggs size = mxms_headerlen(mxm) + mxms_structlen(mxm);
67c39f472eSBen Skeggs mxm->mxms = kmalloc(size, GFP_KERNEL);
68c39f472eSBen Skeggs
69c39f472eSBen Skeggs if (mxm->mxms &&
702aa5eac5SBen Skeggs mxm_shadow_rom_fetch(bus, addr, 0, size, mxm->mxms))
71c39f472eSBen Skeggs return true;
72c39f472eSBen Skeggs
73c39f472eSBen Skeggs kfree(mxm->mxms);
74c39f472eSBen Skeggs mxm->mxms = NULL;
75c39f472eSBen Skeggs return false;
76c39f472eSBen Skeggs }
77c39f472eSBen Skeggs
78c39f472eSBen Skeggs #if defined(CONFIG_ACPI)
79c39f472eSBen Skeggs static bool
mxm_shadow_dsm(struct nvkm_mxm * mxm,u8 version)80be83cd4eSBen Skeggs mxm_shadow_dsm(struct nvkm_mxm *mxm, u8 version)
81c39f472eSBen Skeggs {
8227cc60a1SBen Skeggs struct nvkm_subdev *subdev = &mxm->subdev;
8327cc60a1SBen Skeggs struct nvkm_device *device = subdev->device;
8494116f81SAndy Shevchenko static guid_t muid =
8594116f81SAndy Shevchenko GUID_INIT(0x4004A400, 0x917D, 0x4CF2,
8694116f81SAndy Shevchenko 0xB8, 0x9C, 0x79, 0xB6, 0x2F, 0xD5, 0x56, 0x65);
87c39f472eSBen Skeggs u32 mxms_args[] = { 0x00000000 };
88c39f472eSBen Skeggs union acpi_object argv4 = {
89c39f472eSBen Skeggs .buffer.type = ACPI_TYPE_BUFFER,
90c39f472eSBen Skeggs .buffer.length = sizeof(mxms_args),
91c39f472eSBen Skeggs .buffer.pointer = (char *)mxms_args,
92c39f472eSBen Skeggs };
93c39f472eSBen Skeggs union acpi_object *obj;
94c39f472eSBen Skeggs acpi_handle handle;
95c39f472eSBen Skeggs int rev;
96c39f472eSBen Skeggs
9726c9e8efSBen Skeggs handle = ACPI_HANDLE(device->dev);
98c39f472eSBen Skeggs if (!handle)
99c39f472eSBen Skeggs return false;
100c39f472eSBen Skeggs
101c39f472eSBen Skeggs /*
102c39f472eSBen Skeggs * spec says this can be zero to mean "highest revision", but
103c39f472eSBen Skeggs * of course there's at least one bios out there which fails
104c39f472eSBen Skeggs * unless you pass in exactly the version it supports..
105c39f472eSBen Skeggs */
106c39f472eSBen Skeggs rev = (version & 0xf0) << 4 | (version & 0x0f);
10794116f81SAndy Shevchenko obj = acpi_evaluate_dsm(handle, &muid, rev, 0x00000010, &argv4);
108c39f472eSBen Skeggs if (!obj) {
10927cc60a1SBen Skeggs nvkm_debug(subdev, "DSM MXMS failed\n");
110c39f472eSBen Skeggs return false;
111c39f472eSBen Skeggs }
112c39f472eSBen Skeggs
113c39f472eSBen Skeggs if (obj->type == ACPI_TYPE_BUFFER) {
114c39f472eSBen Skeggs mxm->mxms = kmemdup(obj->buffer.pointer,
115c39f472eSBen Skeggs obj->buffer.length, GFP_KERNEL);
116c39f472eSBen Skeggs } else if (obj->type == ACPI_TYPE_INTEGER) {
11727cc60a1SBen Skeggs nvkm_debug(subdev, "DSM MXMS returned 0x%llx\n",
11827cc60a1SBen Skeggs obj->integer.value);
119c39f472eSBen Skeggs }
120c39f472eSBen Skeggs
121c39f472eSBen Skeggs ACPI_FREE(obj);
122c39f472eSBen Skeggs return mxm->mxms != NULL;
123c39f472eSBen Skeggs }
124c39f472eSBen Skeggs #endif
125c39f472eSBen Skeggs
126c39f472eSBen Skeggs #if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
127c39f472eSBen Skeggs
128c39f472eSBen Skeggs #define WMI_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0"
129c39f472eSBen Skeggs
130c39f472eSBen Skeggs static u8
wmi_wmmx_mxmi(struct nvkm_mxm * mxm,u8 version)131be83cd4eSBen Skeggs wmi_wmmx_mxmi(struct nvkm_mxm *mxm, u8 version)
132c39f472eSBen Skeggs {
13327cc60a1SBen Skeggs struct nvkm_subdev *subdev = &mxm->subdev;
134c39f472eSBen Skeggs u32 mxmi_args[] = { 0x494D584D /* MXMI */, version, 0 };
135c39f472eSBen Skeggs struct acpi_buffer args = { sizeof(mxmi_args), mxmi_args };
136c39f472eSBen Skeggs struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL };
137c39f472eSBen Skeggs union acpi_object *obj;
138c39f472eSBen Skeggs acpi_status status;
139c39f472eSBen Skeggs
140c39f472eSBen Skeggs status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn);
141c39f472eSBen Skeggs if (ACPI_FAILURE(status)) {
14227cc60a1SBen Skeggs nvkm_debug(subdev, "WMMX MXMI returned %d\n", status);
143c39f472eSBen Skeggs return 0x00;
144c39f472eSBen Skeggs }
145c39f472eSBen Skeggs
146c39f472eSBen Skeggs obj = retn.pointer;
147c39f472eSBen Skeggs if (obj->type == ACPI_TYPE_INTEGER) {
148c39f472eSBen Skeggs version = obj->integer.value;
14927cc60a1SBen Skeggs nvkm_debug(subdev, "WMMX MXMI version %d.%d\n",
150c39f472eSBen Skeggs (version >> 4), version & 0x0f);
151c39f472eSBen Skeggs } else {
152c39f472eSBen Skeggs version = 0;
15327cc60a1SBen Skeggs nvkm_debug(subdev, "WMMX MXMI returned non-integer\n");
154c39f472eSBen Skeggs }
155c39f472eSBen Skeggs
156c39f472eSBen Skeggs kfree(obj);
157c39f472eSBen Skeggs return version;
158c39f472eSBen Skeggs }
159c39f472eSBen Skeggs
160c39f472eSBen Skeggs static bool
mxm_shadow_wmi(struct nvkm_mxm * mxm,u8 version)161be83cd4eSBen Skeggs mxm_shadow_wmi(struct nvkm_mxm *mxm, u8 version)
162c39f472eSBen Skeggs {
16327cc60a1SBen Skeggs struct nvkm_subdev *subdev = &mxm->subdev;
164c39f472eSBen Skeggs u32 mxms_args[] = { 0x534D584D /* MXMS */, version, 0 };
165c39f472eSBen Skeggs struct acpi_buffer args = { sizeof(mxms_args), mxms_args };
166c39f472eSBen Skeggs struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL };
167c39f472eSBen Skeggs union acpi_object *obj;
168c39f472eSBen Skeggs acpi_status status;
169c39f472eSBen Skeggs
170c39f472eSBen Skeggs if (!wmi_has_guid(WMI_WMMX_GUID)) {
17127cc60a1SBen Skeggs nvkm_debug(subdev, "WMMX GUID not found\n");
172c39f472eSBen Skeggs return false;
173c39f472eSBen Skeggs }
174c39f472eSBen Skeggs
175c39f472eSBen Skeggs mxms_args[1] = wmi_wmmx_mxmi(mxm, 0x00);
176c39f472eSBen Skeggs if (!mxms_args[1])
177c39f472eSBen Skeggs mxms_args[1] = wmi_wmmx_mxmi(mxm, version);
178c39f472eSBen Skeggs if (!mxms_args[1])
179c39f472eSBen Skeggs return false;
180c39f472eSBen Skeggs
181c39f472eSBen Skeggs status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn);
182c39f472eSBen Skeggs if (ACPI_FAILURE(status)) {
18327cc60a1SBen Skeggs nvkm_debug(subdev, "WMMX MXMS returned %d\n", status);
184c39f472eSBen Skeggs return false;
185c39f472eSBen Skeggs }
186c39f472eSBen Skeggs
187c39f472eSBen Skeggs obj = retn.pointer;
188c39f472eSBen Skeggs if (obj->type == ACPI_TYPE_BUFFER) {
189c39f472eSBen Skeggs mxm->mxms = kmemdup(obj->buffer.pointer,
190c39f472eSBen Skeggs obj->buffer.length, GFP_KERNEL);
191c39f472eSBen Skeggs }
192c39f472eSBen Skeggs
193c39f472eSBen Skeggs kfree(obj);
194c39f472eSBen Skeggs return mxm->mxms != NULL;
195c39f472eSBen Skeggs }
196c39f472eSBen Skeggs #endif
197c39f472eSBen Skeggs
198c39f472eSBen Skeggs static struct mxm_shadow_h {
199c39f472eSBen Skeggs const char *name;
200be83cd4eSBen Skeggs bool (*exec)(struct nvkm_mxm *, u8 version);
201c39f472eSBen Skeggs } _mxm_shadow[] = {
202c39f472eSBen Skeggs { "ROM", mxm_shadow_rom },
203c39f472eSBen Skeggs #if defined(CONFIG_ACPI)
204c39f472eSBen Skeggs { "DSM", mxm_shadow_dsm },
205c39f472eSBen Skeggs #endif
206c39f472eSBen Skeggs #if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
207c39f472eSBen Skeggs { "WMI", mxm_shadow_wmi },
208c39f472eSBen Skeggs #endif
209c39f472eSBen Skeggs {}
210c39f472eSBen Skeggs };
211c39f472eSBen Skeggs
212c39f472eSBen Skeggs static int
mxm_shadow(struct nvkm_mxm * mxm,u8 version)213be83cd4eSBen Skeggs mxm_shadow(struct nvkm_mxm *mxm, u8 version)
214c39f472eSBen Skeggs {
215c39f472eSBen Skeggs struct mxm_shadow_h *shadow = _mxm_shadow;
216c39f472eSBen Skeggs do {
21727cc60a1SBen Skeggs nvkm_debug(&mxm->subdev, "checking %s\n", shadow->name);
218c39f472eSBen Skeggs if (shadow->exec(mxm, version)) {
219c39f472eSBen Skeggs if (mxms_valid(mxm))
220c39f472eSBen Skeggs return 0;
221c39f472eSBen Skeggs kfree(mxm->mxms);
222c39f472eSBen Skeggs mxm->mxms = NULL;
223c39f472eSBen Skeggs }
224c39f472eSBen Skeggs } while ((++shadow)->name);
225c39f472eSBen Skeggs return -ENOENT;
226c39f472eSBen Skeggs }
227c39f472eSBen Skeggs
228a4f7bd36SBen Skeggs static const struct nvkm_subdev_func
229a4f7bd36SBen Skeggs nvkm_mxm = {
230a4f7bd36SBen Skeggs };
231a4f7bd36SBen Skeggs
232c39f472eSBen Skeggs int
nvkm_mxm_new_(struct nvkm_device * device,enum nvkm_subdev_type type,int inst,struct nvkm_mxm ** pmxm)233*0a7bff10SBen Skeggs nvkm_mxm_new_(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
234*0a7bff10SBen Skeggs struct nvkm_mxm **pmxm)
235c39f472eSBen Skeggs {
23646484438SBen Skeggs struct nvkm_bios *bios = device->bios;
237be83cd4eSBen Skeggs struct nvkm_mxm *mxm;
238c39f472eSBen Skeggs u8 ver, len;
239c39f472eSBen Skeggs u16 data;
240c39f472eSBen Skeggs
241a4f7bd36SBen Skeggs if (!(mxm = *pmxm = kzalloc(sizeof(*mxm), GFP_KERNEL)))
242a4f7bd36SBen Skeggs return -ENOMEM;
243a4f7bd36SBen Skeggs
244*0a7bff10SBen Skeggs nvkm_subdev_ctor(&nvkm_mxm, device, type, inst, &mxm->subdev);
245c39f472eSBen Skeggs
246c39f472eSBen Skeggs data = mxm_table(bios, &ver, &len);
2477f5f518fSBen Skeggs if (!data || !(ver = nvbios_rd08(bios, data))) {
24827cc60a1SBen Skeggs nvkm_debug(&mxm->subdev, "no VBIOS data, nothing to do\n");
249c39f472eSBen Skeggs return 0;
250c39f472eSBen Skeggs }
251c39f472eSBen Skeggs
25227cc60a1SBen Skeggs nvkm_info(&mxm->subdev, "BIOS version %d.%d\n", ver >> 4, ver & 0x0f);
2534d987730SBen Skeggs nvkm_debug(&mxm->subdev, "module flags: %02x\n",
2544d987730SBen Skeggs nvbios_rd08(bios, data + 0x01));
2554d987730SBen Skeggs nvkm_debug(&mxm->subdev, "config flags: %02x\n",
2564d987730SBen Skeggs nvbios_rd08(bios, data + 0x02));
257c39f472eSBen Skeggs
258c39f472eSBen Skeggs if (mxm_shadow(mxm, ver)) {
25927cc60a1SBen Skeggs nvkm_warn(&mxm->subdev, "failed to locate valid SIS\n");
260c39f472eSBen Skeggs #if 0
261c39f472eSBen Skeggs /* we should, perhaps, fall back to some kind of limited
262c39f472eSBen Skeggs * mode here if the x86 vbios hasn't already done the
263c39f472eSBen Skeggs * work for us (so we prevent loading with completely
264c39f472eSBen Skeggs * whacked vbios tables).
265c39f472eSBen Skeggs */
266c39f472eSBen Skeggs return -EINVAL;
267c39f472eSBen Skeggs #else
268c39f472eSBen Skeggs return 0;
269c39f472eSBen Skeggs #endif
270c39f472eSBen Skeggs }
271c39f472eSBen Skeggs
27227cc60a1SBen Skeggs nvkm_debug(&mxm->subdev, "MXMS Version %d.%d\n",
273c39f472eSBen Skeggs mxms_version(mxm) >> 8, mxms_version(mxm) & 0xff);
274c39f472eSBen Skeggs mxms_foreach(mxm, 0, NULL, NULL);
275c39f472eSBen Skeggs
276be83cd4eSBen Skeggs if (nvkm_boolopt(device->cfgopt, "NvMXMDCB", true))
277c39f472eSBen Skeggs mxm->action |= MXM_SANITISE_DCB;
278c39f472eSBen Skeggs return 0;
279c39f472eSBen Skeggs }
280