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