From 6a9ee8af344e3bd7dbd61e67037096cdf7f83289 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Mon, 1 Feb 2010 15:38:10 +1000 Subject: vga_switcheroo: initial implementation (v15) Many new laptops now come with 2 gpus, one to be used for low power modes and one for gaming/on-ac applications. These GPUs are typically wired to the laptop panel and VGA ports via a multiplexer unit which is controlled via ACPI methods. 4 combinations of systems typically exist - with 2 ACPI methods. Intel/ATI - Lenovo W500/T500 - use ATPX ACPI method ATI/ATI - some ASUS - use ATPX ACPI Method Intel/Nvidia - - use _DSM ACPI method Nvidia/Nvidia - - use _DSM ACPI method. TODO: This patch adds support for the ATPX method and initial bits for the _DSM methods that need to written by someone with access to the hardware. Add a proper non-debugfs interface - need to get some proper testing first. v2: add power up/down support for both devices on W500 puts i915/radeon into D3 and cuts power to radeon. v3: redo probing methods, no DMI list, drm devices call to register with switcheroo, it tries to find an ATPX method on any device and once there is two devices + ATPX it inits the switcher. v4: ATPX msg handling using buffers - should work on more machines v5: rearchitect after more mjg59 discussion - move ATPX handling to radeon driver. v6: add file headers + initial nouveau bits (to be filled out). v7: merge delayed switcher code. v8: avoid suspend/resume of gpu that is off v9: rearchitect - mjg59 is always right. - move all ATPX code to radeon, should allow simpler DSM also proper ATRM handling v10: add ATRM support for radeon BIOS, add mutex to lock vgasr_priv v11: fix bug in resuming Intel for 2nd time. v12: start fixing up nvidia code blindly. v13: blindly guess at finishing nvidia code v14: remove radeon audio hacks - fix up intel resume more like upstream v15: clean up printks + remove unnecessary igd/dis pointers mount debugfs /sys/kernel/debug/vgaswitcheroo/switch - should exist if ATPX detected + 2 cards. DIS - immediate change to discrete IGD - immediate change to IGD DDIS - delayed change to discrete DIGD - delayed change to IGD ON - turn on not in use OFF - turn off not in use Tested on W500 (Intel/ATI) and T500 (Intel/ATI) Signed-off-by: Dave Airlie --- drivers/gpu/drm/radeon/Makefile | 3 +- drivers/gpu/drm/radeon/radeon.h | 8 + drivers/gpu/drm/radeon/radeon_atpx_handler.c | 258 +++++++++++++++++++++++++++ drivers/gpu/drm/radeon/radeon_bios.c | 44 ++++- drivers/gpu/drm/radeon/radeon_device.c | 40 +++++ drivers/gpu/drm/radeon/radeon_drv.c | 2 + drivers/gpu/drm/radeon/radeon_drv.h | 3 + drivers/gpu/drm/radeon/radeon_fb.c | 3 + drivers/gpu/drm/radeon/radeon_kms.c | 3 + 9 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 drivers/gpu/drm/radeon/radeon_atpx_handler.c (limited to 'drivers/gpu/drm/radeon') diff --git a/drivers/gpu/drm/radeon/Makefile b/drivers/gpu/drm/radeon/Makefile index 1cc7b937b1e..8e62fe13e31 100644 --- a/drivers/gpu/drm/radeon/Makefile +++ b/drivers/gpu/drm/radeon/Makefile @@ -54,7 +54,8 @@ radeon-y += radeon_device.o radeon_kms.o \ radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \ rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \ r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \ - r600_blit_kms.o radeon_pm.o atombios_dp.o r600_audio.o r600_hdmi.o + r600_blit_kms.o radeon_pm.o atombios_dp.o r600_audio.o r600_hdmi.o \ + radeon_atpx_handler.o radeon-$(CONFIG_COMPAT) += radeon_ioc32.o diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index c0356bb193e..a5dfb1557d3 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h @@ -118,6 +118,10 @@ struct radeon_device; /* * BIOS. */ +#define ATRM_BIOS_PAGE 4096 + +bool radeon_atrm_supported(struct pci_dev *pdev); +int radeon_atrm_get_bios_chunk(uint8_t *bios, int offset, int len); bool radeon_get_bios(struct radeon_device *rdev); @@ -838,6 +842,8 @@ struct radeon_device { int audio_bits_per_sample; uint8_t audio_status_bits; uint8_t audio_category_code; + + bool powered_down; }; int radeon_device_init(struct radeon_device *rdev, @@ -1042,6 +1048,8 @@ extern void radeon_legacy_set_clock_gating(struct radeon_device *rdev, int enabl extern void radeon_atom_set_clock_gating(struct radeon_device *rdev, int enable); extern void radeon_ttm_placement_from_domain(struct radeon_bo *rbo, u32 domain); extern bool radeon_ttm_bo_is_radeon_bo(struct ttm_buffer_object *bo); +extern int radeon_resume_kms(struct drm_device *dev); +extern int radeon_suspend_kms(struct drm_device *dev, pm_message_t state); /* r100,rv100,rs100,rv200,rs200,r200,rv250,rs300,rv280 */ struct r100_mc_save { diff --git a/drivers/gpu/drm/radeon/radeon_atpx_handler.c b/drivers/gpu/drm/radeon/radeon_atpx_handler.c new file mode 100644 index 00000000000..0ae52f19071 --- /dev/null +++ b/drivers/gpu/drm/radeon/radeon_atpx_handler.c @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2010 Red Hat Inc. + * Author : Dave Airlie + * + * Licensed under GPLv2 + * + * ATPX support for both Intel/ATI + */ + +#include +#include +#include +#include + +#define ATPX_VERSION 0 +#define ATPX_GPU_PWR 2 +#define ATPX_MUX_SELECT 3 + +#define ATPX_INTEGRATED 0 +#define ATPX_DISCRETE 1 + +#define ATPX_MUX_IGD 0 +#define ATPX_MUX_DISCRETE 1 + +static struct radeon_atpx_priv { + bool atpx_detected; + /* handle for device - and atpx */ + acpi_handle dhandle; + acpi_handle atpx_handle; + acpi_handle atrm_handle; +} radeon_atpx_priv; + +/* retrieve the ROM in 4k blocks */ +static int radeon_atrm_call(acpi_handle atrm_handle, uint8_t *bios, + int offset, int len) +{ + acpi_status status; + union acpi_object atrm_arg_elements[2], *obj; + struct acpi_object_list atrm_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL}; + + atrm_arg.count = 2; + atrm_arg.pointer = &atrm_arg_elements[0]; + + atrm_arg_elements[0].type = ACPI_TYPE_INTEGER; + atrm_arg_elements[0].integer.value = offset; + + atrm_arg_elements[1].type = ACPI_TYPE_INTEGER; + atrm_arg_elements[1].integer.value = len; + + status = acpi_evaluate_object(atrm_handle, NULL, &atrm_arg, &buffer); + if (ACPI_FAILURE(status)) { + printk("failed to evaluate ATRM got %s\n", acpi_format_exception(status)); + return -ENODEV; + } + + obj = (union acpi_object *)buffer.pointer; + memcpy(bios+offset, obj->buffer.pointer, len); + kfree(buffer.pointer); + return len; +} + +bool radeon_atrm_supported(struct pci_dev *pdev) +{ + /* get the discrete ROM only via ATRM */ + if (!radeon_atpx_priv.atpx_detected) + return false; + + if (radeon_atpx_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev)) + return false; + return true; +} + + +int radeon_atrm_get_bios_chunk(uint8_t *bios, int offset, int len) +{ + return radeon_atrm_call(radeon_atpx_priv.atrm_handle, bios, offset, len); +} + +static int radeon_atpx_get_version(acpi_handle handle) +{ + acpi_status status; + union acpi_object atpx_arg_elements[2], *obj; + struct acpi_object_list atpx_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + atpx_arg.count = 2; + atpx_arg.pointer = &atpx_arg_elements[0]; + + atpx_arg_elements[0].type = ACPI_TYPE_INTEGER; + atpx_arg_elements[0].integer.value = ATPX_VERSION; + + atpx_arg_elements[1].type = ACPI_TYPE_INTEGER; + atpx_arg_elements[1].integer.value = ATPX_VERSION; + + status = acpi_evaluate_object(handle, NULL, &atpx_arg, &buffer); + if (ACPI_FAILURE(status)) { + printk("%s: failed to call ATPX: %s\n", __func__, acpi_format_exception(status)); + return -ENOSYS; + } + obj = (union acpi_object *)buffer.pointer; + if (obj && (obj->type == ACPI_TYPE_BUFFER)) + printk(KERN_INFO "radeon atpx: version is %d\n", *((u8 *)(obj->buffer.pointer) + 2)); + kfree(buffer.pointer); + return 0; +} + +static int radeon_atpx_execute(acpi_handle handle, int cmd_id, u16 value) +{ + acpi_status status; + union acpi_object atpx_arg_elements[2]; + struct acpi_object_list atpx_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + uint8_t buf[4] = {0}; + + if (!handle) + return -EINVAL; + + atpx_arg.count = 2; + atpx_arg.pointer = &atpx_arg_elements[0]; + + atpx_arg_elements[0].type = ACPI_TYPE_INTEGER; + atpx_arg_elements[0].integer.value = cmd_id; + + buf[2] = value & 0xff; + buf[3] = (value >> 8) & 0xff; + + atpx_arg_elements[1].type = ACPI_TYPE_BUFFER; + atpx_arg_elements[1].buffer.length = 4; + atpx_arg_elements[1].buffer.pointer = buf; + + status = acpi_evaluate_object(handle, NULL, &atpx_arg, &buffer); + if (ACPI_FAILURE(status)) { + printk("%s: failed to call ATPX: %s\n", __func__, acpi_format_exception(status)); + return -ENOSYS; + } + kfree(buffer.pointer); + + return 0; +} + +static int radeon_atpx_set_discrete_state(acpi_handle handle, int state) +{ + return radeon_atpx_execute(handle, ATPX_GPU_PWR, state); +} + +static int radeon_atpx_switch_mux(acpi_handle handle, int mux_id) +{ + return radeon_atpx_execute(handle, ATPX_MUX_SELECT, mux_id); +} + + +static int radeon_atpx_switchto(enum vga_switcheroo_client_id id) +{ + if (id == VGA_SWITCHEROO_IGD) + radeon_atpx_switch_mux(radeon_atpx_priv.atpx_handle, 0); + else + radeon_atpx_switch_mux(radeon_atpx_priv.atpx_handle, 1); + return 0; +} + +static int radeon_atpx_power_state(enum vga_switcheroo_client_id id, + enum vga_switcheroo_state state) +{ + /* on w500 ACPI can't change intel gpu state */ + if (id == VGA_SWITCHEROO_IGD) + return 0; + + radeon_atpx_set_discrete_state(radeon_atpx_priv.atpx_handle, state); + return 0; +} + +static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev) +{ + acpi_handle dhandle, atpx_handle, atrm_handle; + acpi_status status; + + dhandle = DEVICE_ACPI_HANDLE(&pdev->dev); + if (!dhandle) + return false; + + status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); + if (ACPI_FAILURE(status)) + return false; + + status = acpi_get_handle(dhandle, "ATRM", &atrm_handle); + if (ACPI_FAILURE(status)) + return false; + + radeon_atpx_priv.dhandle = dhandle; + radeon_atpx_priv.atpx_handle = atpx_handle; + radeon_atpx_priv.atrm_handle = atrm_handle; + return true; +} + +static int radeon_atpx_init(void) +{ + /* set up the ATPX handle */ + + radeon_atpx_get_version(radeon_atpx_priv.atpx_handle); + return 0; +} + +static int radeon_atpx_get_client_id(struct pci_dev *pdev) +{ + if (radeon_atpx_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev)) + return VGA_SWITCHEROO_IGD; + else + return VGA_SWITCHEROO_DIS; +} + +static struct vga_switcheroo_handler radeon_atpx_handler = { + .switchto = radeon_atpx_switchto, + .power_state = radeon_atpx_power_state, + .init = radeon_atpx_init, + .get_client_id = radeon_atpx_get_client_id, +}; + +static bool radeon_atpx_detect(void) +{ + char acpi_method_name[255] = { 0 }; + struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; + struct pci_dev *pdev = NULL; + bool has_atpx = false; + int vga_count = 0; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { + vga_count++; + + has_atpx |= (radeon_atpx_pci_probe_handle(pdev) == true); + } + + if (has_atpx && vga_count == 2) { + acpi_get_name(radeon_atpx_priv.atpx_handle, ACPI_FULL_PATHNAME, &buffer); + printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n", + acpi_method_name); + radeon_atpx_priv.atpx_detected = true; + return true; + } + return false; +} + +void radeon_register_atpx_handler(void) +{ + bool r; + + /* detect if we have any ATPX + 2 VGA in the system */ + r = radeon_atpx_detect(); + if (!r) + return; + + vga_switcheroo_register_handler(&radeon_atpx_handler); +} + +void radeon_unregister_atpx_handler(void) +{ + vga_switcheroo_unregister_handler(); +} diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 906921740c6..a34b909485b 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -30,6 +30,7 @@ #include "radeon.h" #include "atom.h" +#include /* * BIOS. */ @@ -62,7 +63,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) iounmap(bios); return false; } - memcpy(rdev->bios, bios, size); + memcpy_fromio(rdev->bios, bios, size); iounmap(bios); return true; } @@ -93,6 +94,38 @@ static bool radeon_read_bios(struct radeon_device *rdev) return true; } +/* ATRM is used to get the BIOS on the discrete cards in + * dual-gpu systems. + */ +static bool radeon_atrm_get_bios(struct radeon_device *rdev) +{ + int ret; + int size = 64 * 1024; + int i; + + if (!radeon_atrm_supported(rdev->pdev)) + return false; + + rdev->bios = kmalloc(size, GFP_KERNEL); + if (!rdev->bios) { + DRM_ERROR("Unable to allocate bios\n"); + return false; + } + + for (i = 0; i < size / ATRM_BIOS_PAGE; i++) { + ret = radeon_atrm_get_bios_chunk(rdev->bios, + (i * ATRM_BIOS_PAGE), + ATRM_BIOS_PAGE); + if (ret <= 0) + break; + } + + if (i == 0 || rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { + kfree(rdev->bios); + return false; + } + return true; +} static bool r700_read_disabled_bios(struct radeon_device *rdev) { uint32_t viph_control; @@ -388,16 +421,16 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev) return legacy_read_disabled_bios(rdev); } + bool radeon_get_bios(struct radeon_device *rdev) { bool r; uint16_t tmp; - if (rdev->flags & RADEON_IS_IGP) { + r = radeon_atrm_get_bios(rdev); + if (r == false) r = igp_read_bios_from_vram(rdev); - if (r == false) - r = radeon_read_bios(rdev); - } else + if (r == false) r = radeon_read_bios(rdev); if (r == false) { r = radeon_read_disabled_bios(rdev); @@ -408,6 +441,7 @@ bool radeon_get_bios(struct radeon_device *rdev) return false; } if (rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { + printk("BIOS signature incorrect %x %x\n", rdev->bios[0], rdev->bios[1]); goto free_bios; } diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 768b1509fa0..cb8d9a1dd69 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "radeon_reg.h" #include "radeon.h" #include "radeon_asic.h" @@ -613,6 +614,36 @@ void radeon_check_arguments(struct radeon_device *rdev) } } +static void radeon_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + struct radeon_device *rdev = dev->dev_private; + pm_message_t pmm = { .event = PM_EVENT_SUSPEND }; + if (state == VGA_SWITCHEROO_ON) { + printk(KERN_INFO "radeon: switched on\n"); + /* don't suspend or resume card normally */ + rdev->powered_down = false; + radeon_resume_kms(dev); + } else { + printk(KERN_INFO "radeon: switched off\n"); + radeon_suspend_kms(dev, pmm); + /* don't suspend or resume card normally */ + rdev->powered_down = true; + } +} + +static bool radeon_switcheroo_can_switch(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + bool can_switch; + + spin_lock(&dev->count_lock); + can_switch = (dev->open_count == 0); + spin_unlock(&dev->count_lock); + return can_switch; +} + + int radeon_device_init(struct radeon_device *rdev, struct drm_device *ddev, struct pci_dev *pdev, @@ -692,6 +723,9 @@ int radeon_device_init(struct radeon_device *rdev, /* this will fail for cards that aren't VGA class devices, just * ignore it */ vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode); + vga_switcheroo_register_client(rdev->pdev, + radeon_switcheroo_set_state, + radeon_switcheroo_can_switch); r = radeon_init(rdev); if (r) @@ -723,6 +757,7 @@ void radeon_device_fini(struct radeon_device *rdev) rdev->shutdown = true; radeon_fini(rdev); destroy_workqueue(rdev->wq); + vga_switcheroo_unregister_client(rdev->pdev); vga_client_register(rdev->pdev, NULL, NULL, NULL); iounmap(rdev->rmmio); rdev->rmmio = NULL; @@ -746,6 +781,8 @@ int radeon_suspend_kms(struct drm_device *dev, pm_message_t state) } rdev = dev->dev_private; + if (rdev->powered_down) + return 0; /* unpin the front buffers */ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { struct radeon_framebuffer *rfb = to_radeon_framebuffer(crtc->fb); @@ -791,6 +828,9 @@ int radeon_resume_kms(struct drm_device *dev) { struct radeon_device *rdev = dev->dev_private; + if (rdev->powered_down) + return 0; + acquire_console_sem(); pci_set_power_state(dev->pdev, PCI_D0); pci_restore_state(dev->pdev); diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c index 8ba3de7994d..4ab53aa163b 100644 --- a/drivers/gpu/drm/radeon/radeon_drv.c +++ b/drivers/gpu/drm/radeon/radeon_drv.c @@ -339,6 +339,7 @@ static int __init radeon_init(void) driver = &kms_driver; driver->driver_features |= DRIVER_MODESET; driver->num_ioctls = radeon_max_kms_ioctl; + radeon_register_atpx_handler(); } /* if the vga console setting is enabled still * let modprobe override it */ @@ -348,6 +349,7 @@ static int __init radeon_init(void) static void __exit radeon_exit(void) { drm_exit(driver); + radeon_unregister_atpx_handler(); } module_init(radeon_init); diff --git a/drivers/gpu/drm/radeon/radeon_drv.h b/drivers/gpu/drm/radeon/radeon_drv.h index c57ad606504..73623719514 100644 --- a/drivers/gpu/drm/radeon/radeon_drv.h +++ b/drivers/gpu/drm/radeon/radeon_drv.h @@ -455,6 +455,9 @@ extern void r600_blit_swap(struct drm_device *dev, int sx, int sy, int dx, int dy, int w, int h, int src_pitch, int dst_pitch, int cpp); +/* atpx handler */ +void radeon_register_atpx_handler(void); +void radeon_unregister_atpx_handler(void); /* Flags for stats.boxes */ #define RADEON_BOX_DMA_IDLE 0x1 diff --git a/drivers/gpu/drm/radeon/radeon_fb.c b/drivers/gpu/drm/radeon/radeon_fb.c index d71e346e9ab..56171922398 100644 --- a/drivers/gpu/drm/radeon/radeon_fb.c +++ b/drivers/gpu/drm/radeon/radeon_fb.c @@ -39,6 +39,8 @@ #include "drm_fb_helper.h" +#include + struct radeon_fb_device { struct drm_fb_helper helper; struct radeon_framebuffer *rfb; @@ -291,6 +293,7 @@ int radeonfb_create(struct drm_device *dev, rfbdev->rdev = rdev; mutex_unlock(&rdev->ddev->struct_mutex); + vga_switcheroo_client_fb_set(rdev->ddev->pdev, info); return 0; out_unref: diff --git a/drivers/gpu/drm/radeon/radeon_kms.c b/drivers/gpu/drm/radeon/radeon_kms.c index f23b05606eb..5db7af6b91f 100644 --- a/drivers/gpu/drm/radeon/radeon_kms.c +++ b/drivers/gpu/drm/radeon/radeon_kms.c @@ -30,6 +30,8 @@ #include "radeon.h" #include "radeon_drm.h" +#include + int radeon_driver_unload_kms(struct drm_device *dev) { struct radeon_device *rdev = dev->dev_private; @@ -136,6 +138,7 @@ int radeon_driver_firstopen_kms(struct drm_device *dev) void radeon_driver_lastclose_kms(struct drm_device *dev) { + vga_switcheroo_process_delayed_switch(); } int radeon_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) -- cgit v1.2.3