diff options
author | Dave Airlie <airlied@linux.ie> | 2010-02-01 00:38:10 -0500 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2010-03-01 01:20:37 -0500 |
commit | 6a9ee8af344e3bd7dbd61e67037096cdf7f83289 (patch) | |
tree | 07cdb493a790cf45bc473f2fc2ea1b9a166d5191 | |
parent | 9fd1de52945e06ed88a440c99ca92dab74b9b33c (diff) |
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 <airlied@redhat.com>
24 files changed, 1104 insertions, 70 deletions
diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index 2307f98349f7..42ca07f04a21 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c | |||
@@ -35,6 +35,7 @@ | |||
35 | #include "i915_drv.h" | 35 | #include "i915_drv.h" |
36 | #include "i915_trace.h" | 36 | #include "i915_trace.h" |
37 | #include <linux/vgaarb.h> | 37 | #include <linux/vgaarb.h> |
38 | #include <linux/vga_switcheroo.h> | ||
38 | 39 | ||
39 | /* Really want an OS-independent resettable timer. Would like to have | 40 | /* Really want an OS-independent resettable timer. Would like to have |
40 | * this loop run for (eg) 3 sec, but have the timer reset every time | 41 | * this loop run for (eg) 3 sec, but have the timer reset every time |
@@ -1199,6 +1200,32 @@ static unsigned int i915_vga_set_decode(void *cookie, bool state) | |||
1199 | return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; | 1200 | return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; |
1200 | } | 1201 | } |
1201 | 1202 | ||
1203 | static void i915_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state) | ||
1204 | { | ||
1205 | struct drm_device *dev = pci_get_drvdata(pdev); | ||
1206 | pm_message_t pmm = { .event = PM_EVENT_SUSPEND }; | ||
1207 | if (state == VGA_SWITCHEROO_ON) { | ||
1208 | printk(KERN_INFO "i915: switched off\n"); | ||
1209 | /* i915 resume handler doesn't set to D0 */ | ||
1210 | pci_set_power_state(dev->pdev, PCI_D0); | ||
1211 | i915_resume(dev); | ||
1212 | } else { | ||
1213 | printk(KERN_ERR "i915: switched off\n"); | ||
1214 | i915_suspend(dev, pmm); | ||
1215 | } | ||
1216 | } | ||
1217 | |||
1218 | static bool i915_switcheroo_can_switch(struct pci_dev *pdev) | ||
1219 | { | ||
1220 | struct drm_device *dev = pci_get_drvdata(pdev); | ||
1221 | bool can_switch; | ||
1222 | |||
1223 | spin_lock(&dev->count_lock); | ||
1224 | can_switch = (dev->open_count == 0); | ||
1225 | spin_unlock(&dev->count_lock); | ||
1226 | return can_switch; | ||
1227 | } | ||
1228 | |||
1202 | static int i915_load_modeset_init(struct drm_device *dev, | 1229 | static int i915_load_modeset_init(struct drm_device *dev, |
1203 | unsigned long prealloc_start, | 1230 | unsigned long prealloc_start, |
1204 | unsigned long prealloc_size, | 1231 | unsigned long prealloc_size, |
@@ -1260,6 +1287,12 @@ static int i915_load_modeset_init(struct drm_device *dev, | |||
1260 | if (ret) | 1287 | if (ret) |
1261 | goto destroy_ringbuffer; | 1288 | goto destroy_ringbuffer; |
1262 | 1289 | ||
1290 | ret = vga_switcheroo_register_client(dev->pdev, | ||
1291 | i915_switcheroo_set_state, | ||
1292 | i915_switcheroo_can_switch); | ||
1293 | if (ret) | ||
1294 | goto destroy_ringbuffer; | ||
1295 | |||
1263 | intel_modeset_init(dev); | 1296 | intel_modeset_init(dev); |
1264 | 1297 | ||
1265 | ret = drm_irq_install(dev); | 1298 | ret = drm_irq_install(dev); |
@@ -1544,6 +1577,7 @@ int i915_driver_unload(struct drm_device *dev) | |||
1544 | dev_priv->child_dev_num = 0; | 1577 | dev_priv->child_dev_num = 0; |
1545 | } | 1578 | } |
1546 | drm_irq_uninstall(dev); | 1579 | drm_irq_uninstall(dev); |
1580 | vga_switcheroo_unregister_client(dev->pdev); | ||
1547 | vga_client_register(dev->pdev, NULL, NULL, NULL); | 1581 | vga_client_register(dev->pdev, NULL, NULL, NULL); |
1548 | } | 1582 | } |
1549 | 1583 | ||
@@ -1611,6 +1645,7 @@ void i915_driver_lastclose(struct drm_device * dev) | |||
1611 | 1645 | ||
1612 | if (!dev_priv || drm_core_check_feature(dev, DRIVER_MODESET)) { | 1646 | if (!dev_priv || drm_core_check_feature(dev, DRIVER_MODESET)) { |
1613 | drm_fb_helper_restore(); | 1647 | drm_fb_helper_restore(); |
1648 | vga_switcheroo_process_delayed_switch(); | ||
1614 | return; | 1649 | return; |
1615 | } | 1650 | } |
1616 | 1651 | ||
diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index cf4cb3e9a0c2..fd739efe73ce 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c | |||
@@ -201,7 +201,7 @@ static int i915_drm_freeze(struct drm_device *dev) | |||
201 | return 0; | 201 | return 0; |
202 | } | 202 | } |
203 | 203 | ||
204 | static int i915_suspend(struct drm_device *dev, pm_message_t state) | 204 | int i915_suspend(struct drm_device *dev, pm_message_t state) |
205 | { | 205 | { |
206 | int error; | 206 | int error; |
207 | 207 | ||
@@ -255,7 +255,7 @@ static int i915_drm_thaw(struct drm_device *dev) | |||
255 | return error; | 255 | return error; |
256 | } | 256 | } |
257 | 257 | ||
258 | static int i915_resume(struct drm_device *dev) | 258 | int i915_resume(struct drm_device *dev) |
259 | { | 259 | { |
260 | if (pci_enable_device(dev->pdev)) | 260 | if (pci_enable_device(dev->pdev)) |
261 | return -EIO; | 261 | return -EIO; |
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index b99b6a841d95..d77e56651352 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h | |||
@@ -736,6 +736,8 @@ extern unsigned int i915_fbpercrtc; | |||
736 | extern unsigned int i915_powersave; | 736 | extern unsigned int i915_powersave; |
737 | extern unsigned int i915_lvds_downclock; | 737 | extern unsigned int i915_lvds_downclock; |
738 | 738 | ||
739 | extern int i915_suspend(struct drm_device *dev, pm_message_t state); | ||
740 | extern int i915_resume(struct drm_device *dev); | ||
739 | extern void i915_save_display(struct drm_device *dev); | 741 | extern void i915_save_display(struct drm_device *dev); |
740 | extern void i915_restore_display(struct drm_device *dev); | 742 | extern void i915_restore_display(struct drm_device *dev); |
741 | extern int i915_master_create(struct drm_device *dev, struct drm_master *master); | 743 | extern int i915_master_create(struct drm_device *dev, struct drm_master *master); |
diff --git a/drivers/gpu/drm/i915/intel_fb.c b/drivers/gpu/drm/i915/intel_fb.c index aaabbcbe5905..8cd791dc5b29 100644 --- a/drivers/gpu/drm/i915/intel_fb.c +++ b/drivers/gpu/drm/i915/intel_fb.c | |||
@@ -35,6 +35,7 @@ | |||
35 | #include <linux/delay.h> | 35 | #include <linux/delay.h> |
36 | #include <linux/fb.h> | 36 | #include <linux/fb.h> |
37 | #include <linux/init.h> | 37 | #include <linux/init.h> |
38 | #include <linux/vga_switcheroo.h> | ||
38 | 39 | ||
39 | #include "drmP.h" | 40 | #include "drmP.h" |
40 | #include "drm.h" | 41 | #include "drm.h" |
@@ -235,6 +236,7 @@ static int intelfb_create(struct drm_device *dev, uint32_t fb_width, | |||
235 | obj_priv->gtt_offset, fbo); | 236 | obj_priv->gtt_offset, fbo); |
236 | 237 | ||
237 | mutex_unlock(&dev->struct_mutex); | 238 | mutex_unlock(&dev->struct_mutex); |
239 | vga_switcheroo_client_fb_set(dev->pdev, info); | ||
238 | return 0; | 240 | return 0; |
239 | 241 | ||
240 | out_unpin: | 242 | out_unpin: |
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c index 48227e744753..0e0730a53137 100644 --- a/drivers/gpu/drm/nouveau/nouveau_acpi.c +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c | |||
@@ -11,6 +11,8 @@ | |||
11 | #include "nouveau_drm.h" | 11 | #include "nouveau_drm.h" |
12 | #include "nv50_display.h" | 12 | #include "nv50_display.h" |
13 | 13 | ||
14 | #include <linux/vga_switcheroo.h> | ||
15 | |||
14 | #define NOUVEAU_DSM_SUPPORTED 0x00 | 16 | #define NOUVEAU_DSM_SUPPORTED 0x00 |
15 | #define NOUVEAU_DSM_SUPPORTED_FUNCTIONS 0x00 | 17 | #define NOUVEAU_DSM_SUPPORTED_FUNCTIONS 0x00 |
16 | 18 | ||
@@ -28,31 +30,30 @@ | |||
28 | #define NOUVEAU_DSM_POWER_SPEED 0x01 | 30 | #define NOUVEAU_DSM_POWER_SPEED 0x01 |
29 | #define NOUVEAU_DSM_POWER_STAMINA 0x02 | 31 | #define NOUVEAU_DSM_POWER_STAMINA 0x02 |
30 | 32 | ||
31 | static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) | 33 | static struct nouveau_dsm_priv { |
32 | { | 34 | bool dsm_detected; |
33 | static char muid[] = { | 35 | acpi_handle dhandle; |
34 | 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, | 36 | acpi_handle dsm_handle; |
35 | 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4, | 37 | } nouveau_dsm_priv; |
36 | }; | 38 | |
39 | static const char nouveau_dsm_muid[] = { | ||
40 | 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, | ||
41 | 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4, | ||
42 | }; | ||
37 | 43 | ||
38 | struct pci_dev *pdev = dev->pdev; | 44 | static int nouveau_dsm(acpi_handle handle, int func, int arg, int *result) |
39 | struct acpi_handle *handle; | 45 | { |
40 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | 46 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
41 | struct acpi_object_list input; | 47 | struct acpi_object_list input; |
42 | union acpi_object params[4]; | 48 | union acpi_object params[4]; |
43 | union acpi_object *obj; | 49 | union acpi_object *obj; |
44 | int err; | 50 | int err; |
45 | 51 | ||
46 | handle = DEVICE_ACPI_HANDLE(&pdev->dev); | ||
47 | |||
48 | if (!handle) | ||
49 | return -ENODEV; | ||
50 | |||
51 | input.count = 4; | 52 | input.count = 4; |
52 | input.pointer = params; | 53 | input.pointer = params; |
53 | params[0].type = ACPI_TYPE_BUFFER; | 54 | params[0].type = ACPI_TYPE_BUFFER; |
54 | params[0].buffer.length = sizeof(muid); | 55 | params[0].buffer.length = sizeof(nouveau_dsm_muid); |
55 | params[0].buffer.pointer = (char *)muid; | 56 | params[0].buffer.pointer = (char *)nouveau_dsm_muid; |
56 | params[1].type = ACPI_TYPE_INTEGER; | 57 | params[1].type = ACPI_TYPE_INTEGER; |
57 | params[1].integer.value = 0x00000102; | 58 | params[1].integer.value = 0x00000102; |
58 | params[2].type = ACPI_TYPE_INTEGER; | 59 | params[2].type = ACPI_TYPE_INTEGER; |
@@ -62,7 +63,7 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) | |||
62 | 63 | ||
63 | err = acpi_evaluate_object(handle, "_DSM", &input, &output); | 64 | err = acpi_evaluate_object(handle, "_DSM", &input, &output); |
64 | if (err) { | 65 | if (err) { |
65 | NV_INFO(dev, "failed to evaluate _DSM: %d\n", err); | 66 | printk(KERN_INFO "failed to evaluate _DSM: %d\n", err); |
66 | return err; | 67 | return err; |
67 | } | 68 | } |
68 | 69 | ||
@@ -86,40 +87,119 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) | |||
86 | return 0; | 87 | return 0; |
87 | } | 88 | } |
88 | 89 | ||
89 | int nouveau_hybrid_setup(struct drm_device *dev) | 90 | static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id) |
90 | { | 91 | { |
91 | int result; | 92 | return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id, NULL); |
92 | 93 | } | |
93 | if (nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STATE, | 94 | |
94 | &result)) | 95 | static int nouveau_dsm_set_discrete_state(acpi_handle handle, enum vga_switcheroo_state state) |
95 | return -ENODEV; | 96 | { |
96 | 97 | int arg; | |
97 | NV_INFO(dev, "_DSM hardware status gave 0x%x\n", result); | 98 | if (state == VGA_SWITCHEROO_ON) |
98 | 99 | arg = NOUVEAU_DSM_POWER_SPEED; | |
99 | if (result) { /* Ensure that the external GPU is enabled */ | 100 | else |
100 | nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_SPEED, NULL); | 101 | arg = NOUVEAU_DSM_POWER_STAMINA; |
101 | nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_SPEED, | 102 | nouveau_dsm(handle, NOUVEAU_DSM_POWER, arg, NULL); |
102 | NULL); | 103 | return 0; |
103 | } else { /* Stamina mode - disable the external GPU */ | 104 | } |
104 | nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_STAMINA, | 105 | |
105 | NULL); | 106 | static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id) |
106 | nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STAMINA, | 107 | { |
107 | NULL); | 108 | if (id == VGA_SWITCHEROO_IGD) |
108 | } | 109 | return nouveau_dsm_switch_mux(nouveau_dsm_priv.dsm_handle, NOUVEAU_DSM_LED_STAMINA); |
110 | else | ||
111 | return nouveau_dsm_switch_mux(nouveau_dsm_priv.dsm_handle, NOUVEAU_DSM_LED_SPEED); | ||
112 | } | ||
109 | 113 | ||
114 | static int nouveau_dsm_power_state(enum vga_switcheroo_client_id id, | ||
115 | enum vga_switcheroo_state state) | ||
116 | { | ||
117 | if (id == VGA_SWITCHEROO_IGD) | ||
118 | return 0; | ||
119 | |||
120 | return nouveau_dsm_set_discrete_state(nouveau_dsm_priv.dsm_handle, state); | ||
121 | } | ||
122 | |||
123 | static int nouveau_dsm_init(void) | ||
124 | { | ||
110 | return 0; | 125 | return 0; |
111 | } | 126 | } |
112 | 127 | ||
113 | bool nouveau_dsm_probe(struct drm_device *dev) | 128 | static int nouveau_dsm_get_client_id(struct pci_dev *pdev) |
114 | { | 129 | { |
115 | int support = 0; | 130 | if (nouveau_dsm_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev)) |
131 | return VGA_SWITCHEROO_IGD; | ||
132 | else | ||
133 | return VGA_SWITCHEROO_DIS; | ||
134 | } | ||
135 | |||
136 | static struct vga_switcheroo_handler nouveau_dsm_handler = { | ||
137 | .switchto = nouveau_dsm_switchto, | ||
138 | .power_state = nouveau_dsm_power_state, | ||
139 | .init = nouveau_dsm_init, | ||
140 | .get_client_id = nouveau_dsm_get_client_id, | ||
141 | }; | ||
116 | 142 | ||
117 | if (nouveau_dsm(dev, NOUVEAU_DSM_SUPPORTED, | 143 | static bool nouveau_dsm_pci_probe(struct pci_dev *pdev) |
118 | NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &support)) | 144 | { |
145 | acpi_handle dhandle, nvidia_handle; | ||
146 | acpi_status status; | ||
147 | int ret; | ||
148 | uint32_t result; | ||
149 | |||
150 | dhandle = DEVICE_ACPI_HANDLE(&pdev->dev); | ||
151 | if (!dhandle) | ||
152 | return false; | ||
153 | status = acpi_get_handle(dhandle, "_DSM", &nvidia_handle); | ||
154 | if (ACPI_FAILURE(status)) { | ||
119 | return false; | 155 | return false; |
156 | } | ||
120 | 157 | ||
121 | if (!support) | 158 | ret= nouveau_dsm(nvidia_handle, NOUVEAU_DSM_SUPPORTED, |
159 | NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &result); | ||
160 | if (ret < 0) | ||
122 | return false; | 161 | return false; |
123 | 162 | ||
163 | nouveau_dsm_priv.dhandle = dhandle; | ||
164 | nouveau_dsm_priv.dsm_handle = nvidia_handle; | ||
124 | return true; | 165 | return true; |
125 | } | 166 | } |
167 | |||
168 | static bool nouveau_dsm_detect(void) | ||
169 | { | ||
170 | char acpi_method_name[255] = { 0 }; | ||
171 | struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; | ||
172 | struct pci_dev *pdev = NULL; | ||
173 | int has_dsm = 0; | ||
174 | int vga_count = 0; | ||
175 | while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { | ||
176 | vga_count++; | ||
177 | |||
178 | has_dsm |= (nouveau_dsm_pci_probe(pdev) == true); | ||
179 | } | ||
180 | |||
181 | if (vga_count == 2 && has_dsm) { | ||
182 | acpi_get_name(nouveau_dsm_priv.dsm_handle, ACPI_FULL_PATHNAME, &buffer); | ||
183 | printk(KERN_INFO "VGA switcheroo: detected DSM switching method %s handle\n", | ||
184 | acpi_method_name); | ||
185 | nouveau_dsm_priv.dsm_detected = true; | ||
186 | return true; | ||
187 | } | ||
188 | return false; | ||
189 | } | ||
190 | |||
191 | void nouveau_register_dsm_handler(void) | ||
192 | { | ||
193 | bool r; | ||
194 | |||
195 | r = nouveau_dsm_detect(); | ||
196 | if (!r) | ||
197 | return; | ||
198 | |||
199 | vga_switcheroo_register_handler(&nouveau_dsm_handler); | ||
200 | } | ||
201 | |||
202 | void nouveau_unregister_dsm_handler(void) | ||
203 | { | ||
204 | vga_switcheroo_unregister_handler(); | ||
205 | } | ||
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.c b/drivers/gpu/drm/nouveau/nouveau_drv.c index da3b93b84502..f83ec65addba 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.c +++ b/drivers/gpu/drm/nouveau/nouveau_drv.c | |||
@@ -135,7 +135,7 @@ nouveau_pci_remove(struct pci_dev *pdev) | |||
135 | drm_put_dev(dev); | 135 | drm_put_dev(dev); |
136 | } | 136 | } |
137 | 137 | ||
138 | static int | 138 | int |
139 | nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state) | 139 | nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state) |
140 | { | 140 | { |
141 | struct drm_device *dev = pci_get_drvdata(pdev); | 141 | struct drm_device *dev = pci_get_drvdata(pdev); |
@@ -233,7 +233,7 @@ out_abort: | |||
233 | return ret; | 233 | return ret; |
234 | } | 234 | } |
235 | 235 | ||
236 | static int | 236 | int |
237 | nouveau_pci_resume(struct pci_dev *pdev) | 237 | nouveau_pci_resume(struct pci_dev *pdev) |
238 | { | 238 | { |
239 | struct drm_device *dev = pci_get_drvdata(pdev); | 239 | struct drm_device *dev = pci_get_drvdata(pdev); |
@@ -402,8 +402,10 @@ static int __init nouveau_init(void) | |||
402 | nouveau_modeset = 1; | 402 | nouveau_modeset = 1; |
403 | } | 403 | } |
404 | 404 | ||
405 | if (nouveau_modeset == 1) | 405 | if (nouveau_modeset == 1) { |
406 | driver.driver_features |= DRIVER_MODESET; | 406 | driver.driver_features |= DRIVER_MODESET; |
407 | nouveau_register_dsm_handler(); | ||
408 | } | ||
407 | 409 | ||
408 | return drm_init(&driver); | 410 | return drm_init(&driver); |
409 | } | 411 | } |
@@ -411,6 +413,7 @@ static int __init nouveau_init(void) | |||
411 | static void __exit nouveau_exit(void) | 413 | static void __exit nouveau_exit(void) |
412 | { | 414 | { |
413 | drm_exit(&driver); | 415 | drm_exit(&driver); |
416 | nouveau_unregister_dsm_handler(); | ||
414 | } | 417 | } |
415 | 418 | ||
416 | module_init(nouveau_init); | 419 | module_init(nouveau_init); |
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index 1c15ef37b71c..85c05feab4f0 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h | |||
@@ -614,7 +614,6 @@ struct drm_nouveau_private { | |||
614 | } susres; | 614 | } susres; |
615 | 615 | ||
616 | struct backlight_device *backlight; | 616 | struct backlight_device *backlight; |
617 | bool acpi_dsm; | ||
618 | 617 | ||
619 | struct nouveau_channel *evo; | 618 | struct nouveau_channel *evo; |
620 | 619 | ||
@@ -682,6 +681,9 @@ extern int nouveau_ignorelid; | |||
682 | extern int nouveau_nofbaccel; | 681 | extern int nouveau_nofbaccel; |
683 | extern int nouveau_noaccel; | 682 | extern int nouveau_noaccel; |
684 | 683 | ||
684 | extern int nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state); | ||
685 | extern int nouveau_pci_resume(struct pci_dev *pdev); | ||
686 | |||
685 | /* nouveau_state.c */ | 687 | /* nouveau_state.c */ |
686 | extern void nouveau_preclose(struct drm_device *dev, struct drm_file *); | 688 | extern void nouveau_preclose(struct drm_device *dev, struct drm_file *); |
687 | extern int nouveau_load(struct drm_device *, unsigned long flags); | 689 | extern int nouveau_load(struct drm_device *, unsigned long flags); |
@@ -848,19 +850,8 @@ extern int nouveau_dma_init(struct nouveau_channel *); | |||
848 | extern int nouveau_dma_wait(struct nouveau_channel *, int size); | 850 | extern int nouveau_dma_wait(struct nouveau_channel *, int size); |
849 | 851 | ||
850 | /* nouveau_acpi.c */ | 852 | /* nouveau_acpi.c */ |
851 | #ifdef CONFIG_ACPI | 853 | void nouveau_register_dsm_handler(void); |
852 | extern int nouveau_hybrid_setup(struct drm_device *dev); | 854 | void nouveau_unregister_dsm_handler(void); |
853 | extern bool nouveau_dsm_probe(struct drm_device *dev); | ||
854 | #else | ||
855 | static inline int nouveau_hybrid_setup(struct drm_device *dev) | ||
856 | { | ||
857 | return 0; | ||
858 | } | ||
859 | static inline bool nouveau_dsm_probe(struct drm_device *dev) | ||
860 | { | ||
861 | return false; | ||
862 | } | ||
863 | #endif | ||
864 | 855 | ||
865 | /* nouveau_backlight.c */ | 856 | /* nouveau_backlight.c */ |
866 | #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT | 857 | #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT |
diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c index ea879a2efef3..1ebf22b664dd 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c +++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c | |||
@@ -36,6 +36,7 @@ | |||
36 | #include <linux/fb.h> | 36 | #include <linux/fb.h> |
37 | #include <linux/init.h> | 37 | #include <linux/init.h> |
38 | #include <linux/screen_info.h> | 38 | #include <linux/screen_info.h> |
39 | #include <linux/vga_switcheroo.h> | ||
39 | 40 | ||
40 | #include "drmP.h" | 41 | #include "drmP.h" |
41 | #include "drm.h" | 42 | #include "drm.h" |
@@ -370,6 +371,7 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width, | |||
370 | nvbo->bo.offset, nvbo); | 371 | nvbo->bo.offset, nvbo); |
371 | 372 | ||
372 | mutex_unlock(&dev->struct_mutex); | 373 | mutex_unlock(&dev->struct_mutex); |
374 | vga_switcheroo_client_fb_set(dev->pdev, info); | ||
373 | return 0; | 375 | return 0; |
374 | 376 | ||
375 | out_unref: | 377 | out_unref: |
diff --git a/drivers/gpu/drm/nouveau/nouveau_state.c b/drivers/gpu/drm/nouveau/nouveau_state.c index a4851af5b05e..85d65b91389c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_state.c +++ b/drivers/gpu/drm/nouveau/nouveau_state.c | |||
@@ -29,6 +29,7 @@ | |||
29 | #include "drm_sarea.h" | 29 | #include "drm_sarea.h" |
30 | #include "drm_crtc_helper.h" | 30 | #include "drm_crtc_helper.h" |
31 | #include <linux/vgaarb.h> | 31 | #include <linux/vgaarb.h> |
32 | #include <linux/vga_switcheroo.h> | ||
32 | 33 | ||
33 | #include "nouveau_drv.h" | 34 | #include "nouveau_drv.h" |
34 | #include "nouveau_drm.h" | 35 | #include "nouveau_drm.h" |
@@ -371,6 +372,30 @@ out_err: | |||
371 | return ret; | 372 | return ret; |
372 | } | 373 | } |
373 | 374 | ||
375 | static void nouveau_switcheroo_set_state(struct pci_dev *pdev, | ||
376 | enum vga_switcheroo_state state) | ||
377 | { | ||
378 | pm_message_t pmm = { .event = PM_EVENT_SUSPEND }; | ||
379 | if (state == VGA_SWITCHEROO_ON) { | ||
380 | printk(KERN_ERR "VGA switcheroo: switched nouveau on\n"); | ||
381 | nouveau_pci_resume(pdev); | ||
382 | } else { | ||
383 | printk(KERN_ERR "VGA switcheroo: switched nouveau off\n"); | ||
384 | nouveau_pci_suspend(pdev, pmm); | ||
385 | } | ||
386 | } | ||
387 | |||
388 | static bool nouveau_switcheroo_can_switch(struct pci_dev *pdev) | ||
389 | { | ||
390 | struct drm_device *dev = pci_get_drvdata(pdev); | ||
391 | bool can_switch; | ||
392 | |||
393 | spin_lock(&dev->count_lock); | ||
394 | can_switch = (dev->open_count == 0); | ||
395 | spin_unlock(&dev->count_lock); | ||
396 | return can_switch; | ||
397 | } | ||
398 | |||
374 | int | 399 | int |
375 | nouveau_card_init(struct drm_device *dev) | 400 | nouveau_card_init(struct drm_device *dev) |
376 | { | 401 | { |
@@ -384,6 +409,8 @@ nouveau_card_init(struct drm_device *dev) | |||
384 | return 0; | 409 | return 0; |
385 | 410 | ||
386 | vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); | 411 | vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); |
412 | vga_switcheroo_register_client(dev->pdev, nouveau_switcheroo_set_state, | ||
413 | nouveau_switcheroo_can_switch); | ||
387 | 414 | ||
388 | /* Initialise internal driver API hooks */ | 415 | /* Initialise internal driver API hooks */ |
389 | ret = nouveau_init_engine_ptrs(dev); | 416 | ret = nouveau_init_engine_ptrs(dev); |
@@ -617,11 +644,6 @@ int nouveau_load(struct drm_device *dev, unsigned long flags) | |||
617 | NV_DEBUG(dev, "vendor: 0x%X device: 0x%X class: 0x%X\n", | 644 | NV_DEBUG(dev, "vendor: 0x%X device: 0x%X class: 0x%X\n", |
618 | dev->pci_vendor, dev->pci_device, dev->pdev->class); | 645 | dev->pci_vendor, dev->pci_device, dev->pdev->class); |
619 | 646 | ||
620 | dev_priv->acpi_dsm = nouveau_dsm_probe(dev); | ||
621 | |||
622 | if (dev_priv->acpi_dsm) | ||
623 | nouveau_hybrid_setup(dev); | ||
624 | |||
625 | dev_priv->wq = create_workqueue("nouveau"); | 647 | dev_priv->wq = create_workqueue("nouveau"); |
626 | if (!dev_priv->wq) | 648 | if (!dev_priv->wq) |
627 | return -EINVAL; | 649 | return -EINVAL; |
diff --git a/drivers/gpu/drm/radeon/Makefile b/drivers/gpu/drm/radeon/Makefile index 1cc7b937b1ea..8e62fe13e31c 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 \ | |||
54 | radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \ | 54 | radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \ |
55 | rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \ | 55 | rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \ |
56 | r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \ | 56 | r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \ |
57 | r600_blit_kms.o radeon_pm.o atombios_dp.o r600_audio.o r600_hdmi.o | 57 | r600_blit_kms.o radeon_pm.o atombios_dp.o r600_audio.o r600_hdmi.o \ |
58 | radeon_atpx_handler.o | ||
58 | 59 | ||
59 | radeon-$(CONFIG_COMPAT) += radeon_ioc32.o | 60 | radeon-$(CONFIG_COMPAT) += radeon_ioc32.o |
60 | 61 | ||
diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index c0356bb193e5..a5dfb1557d3e 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h | |||
@@ -118,6 +118,10 @@ struct radeon_device; | |||
118 | /* | 118 | /* |
119 | * BIOS. | 119 | * BIOS. |
120 | */ | 120 | */ |
121 | #define ATRM_BIOS_PAGE 4096 | ||
122 | |||
123 | bool radeon_atrm_supported(struct pci_dev *pdev); | ||
124 | int radeon_atrm_get_bios_chunk(uint8_t *bios, int offset, int len); | ||
121 | bool radeon_get_bios(struct radeon_device *rdev); | 125 | bool radeon_get_bios(struct radeon_device *rdev); |
122 | 126 | ||
123 | 127 | ||
@@ -838,6 +842,8 @@ struct radeon_device { | |||
838 | int audio_bits_per_sample; | 842 | int audio_bits_per_sample; |
839 | uint8_t audio_status_bits; | 843 | uint8_t audio_status_bits; |
840 | uint8_t audio_category_code; | 844 | uint8_t audio_category_code; |
845 | |||
846 | bool powered_down; | ||
841 | }; | 847 | }; |
842 | 848 | ||
843 | int radeon_device_init(struct radeon_device *rdev, | 849 | 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 | |||
1042 | extern void radeon_atom_set_clock_gating(struct radeon_device *rdev, int enable); | 1048 | extern void radeon_atom_set_clock_gating(struct radeon_device *rdev, int enable); |
1043 | extern void radeon_ttm_placement_from_domain(struct radeon_bo *rbo, u32 domain); | 1049 | extern void radeon_ttm_placement_from_domain(struct radeon_bo *rbo, u32 domain); |
1044 | extern bool radeon_ttm_bo_is_radeon_bo(struct ttm_buffer_object *bo); | 1050 | extern bool radeon_ttm_bo_is_radeon_bo(struct ttm_buffer_object *bo); |
1051 | extern int radeon_resume_kms(struct drm_device *dev); | ||
1052 | extern int radeon_suspend_kms(struct drm_device *dev, pm_message_t state); | ||
1045 | 1053 | ||
1046 | /* r100,rv100,rs100,rv200,rs200,r200,rv250,rs300,rv280 */ | 1054 | /* r100,rv100,rs100,rv200,rs200,r200,rv250,rs300,rv280 */ |
1047 | struct r100_mc_save { | 1055 | 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 000000000000..0ae52f19071d --- /dev/null +++ b/drivers/gpu/drm/radeon/radeon_atpx_handler.c | |||
@@ -0,0 +1,258 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Red Hat Inc. | ||
3 | * Author : Dave Airlie <airlied@redhat.com> | ||
4 | * | ||
5 | * Licensed under GPLv2 | ||
6 | * | ||
7 | * ATPX support for both Intel/ATI | ||
8 | */ | ||
9 | |||
10 | #include <linux/vga_switcheroo.h> | ||
11 | #include <acpi/acpi.h> | ||
12 | #include <acpi/acpi_bus.h> | ||
13 | #include <linux/pci.h> | ||
14 | |||
15 | #define ATPX_VERSION 0 | ||
16 | #define ATPX_GPU_PWR 2 | ||
17 | #define ATPX_MUX_SELECT 3 | ||
18 | |||
19 | #define ATPX_INTEGRATED 0 | ||
20 | #define ATPX_DISCRETE 1 | ||
21 | |||
22 | #define ATPX_MUX_IGD 0 | ||
23 | #define ATPX_MUX_DISCRETE 1 | ||
24 | |||
25 | static struct radeon_atpx_priv { | ||
26 | bool atpx_detected; | ||
27 | /* handle for device - and atpx */ | ||
28 | acpi_handle dhandle; | ||
29 | acpi_handle atpx_handle; | ||
30 | acpi_handle atrm_handle; | ||
31 | } radeon_atpx_priv; | ||
32 | |||
33 | /* retrieve the ROM in 4k blocks */ | ||
34 | static int radeon_atrm_call(acpi_handle atrm_handle, uint8_t *bios, | ||
35 | int offset, int len) | ||
36 | { | ||
37 | acpi_status status; | ||
38 | union acpi_object atrm_arg_elements[2], *obj; | ||
39 | struct acpi_object_list atrm_arg; | ||
40 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL}; | ||
41 | |||
42 | atrm_arg.count = 2; | ||
43 | atrm_arg.pointer = &atrm_arg_elements[0]; | ||
44 | |||
45 | atrm_arg_elements[0].type = ACPI_TYPE_INTEGER; | ||
46 | atrm_arg_elements[0].integer.value = offset; | ||
47 | |||
48 | atrm_arg_elements[1].type = ACPI_TYPE_INTEGER; | ||
49 | atrm_arg_elements[1].integer.value = len; | ||
50 | |||
51 | status = acpi_evaluate_object(atrm_handle, NULL, &atrm_arg, &buffer); | ||
52 | if (ACPI_FAILURE(status)) { | ||
53 | printk("failed to evaluate ATRM got %s\n", acpi_format_exception(status)); | ||
54 | return -ENODEV; | ||
55 | } | ||
56 | |||
57 | obj = (union acpi_object *)buffer.pointer; | ||
58 | memcpy(bios+offset, obj->buffer.pointer, len); | ||
59 | kfree(buffer.pointer); | ||
60 | return len; | ||
61 | } | ||
62 | |||
63 | bool radeon_atrm_supported(struct pci_dev *pdev) | ||
64 | { | ||
65 | /* get the discrete ROM only via ATRM */ | ||
66 | if (!radeon_atpx_priv.atpx_detected) | ||
67 | return false; | ||
68 | |||
69 | if (radeon_atpx_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev)) | ||
70 | return false; | ||
71 | return true; | ||
72 | } | ||
73 | |||
74 | |||
75 | int radeon_atrm_get_bios_chunk(uint8_t *bios, int offset, int len) | ||
76 | { | ||
77 | return radeon_atrm_call(radeon_atpx_priv.atrm_handle, bios, offset, len); | ||
78 | } | ||
79 | |||
80 | static int radeon_atpx_get_version(acpi_handle handle) | ||
81 | { | ||
82 | acpi_status status; | ||
83 | union acpi_object atpx_arg_elements[2], *obj; | ||
84 | struct acpi_object_list atpx_arg; | ||
85 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
86 | |||
87 | atpx_arg.count = 2; | ||
88 | atpx_arg.pointer = &atpx_arg_elements[0]; | ||
89 | |||
90 | atpx_arg_elements[0].type = ACPI_TYPE_INTEGER; | ||
91 | atpx_arg_elements[0].integer.value = ATPX_VERSION; | ||
92 | |||
93 | atpx_arg_elements[1].type = ACPI_TYPE_INTEGER; | ||
94 | atpx_arg_elements[1].integer.value = ATPX_VERSION; | ||
95 | |||
96 | status = acpi_evaluate_object(handle, NULL, &atpx_arg, &buffer); | ||
97 | if (ACPI_FAILURE(status)) { | ||
98 | printk("%s: failed to call ATPX: %s\n", __func__, acpi_format_exception(status)); | ||
99 | return -ENOSYS; | ||
100 | } | ||
101 | obj = (union acpi_object *)buffer.pointer; | ||
102 | if (obj && (obj->type == ACPI_TYPE_BUFFER)) | ||
103 | printk(KERN_INFO "radeon atpx: version is %d\n", *((u8 *)(obj->buffer.pointer) + 2)); | ||
104 | kfree(buffer.pointer); | ||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static int radeon_atpx_execute(acpi_handle handle, int cmd_id, u16 value) | ||
109 | { | ||
110 | acpi_status status; | ||
111 | union acpi_object atpx_arg_elements[2]; | ||
112 | struct acpi_object_list atpx_arg; | ||
113 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
114 | uint8_t buf[4] = {0}; | ||
115 | |||
116 | if (!handle) | ||
117 | return -EINVAL; | ||
118 | |||
119 | atpx_arg.count = 2; | ||
120 | atpx_arg.pointer = &atpx_arg_elements[0]; | ||
121 | |||
122 | atpx_arg_elements[0].type = ACPI_TYPE_INTEGER; | ||
123 | atpx_arg_elements[0].integer.value = cmd_id; | ||
124 | |||
125 | buf[2] = value & 0xff; | ||
126 | buf[3] = (value >> 8) & 0xff; | ||
127 | |||
128 | atpx_arg_elements[1].type = ACPI_TYPE_BUFFER; | ||
129 | atpx_arg_elements[1].buffer.length = 4; | ||
130 | atpx_arg_elements[1].buffer.pointer = buf; | ||
131 | |||
132 | status = acpi_evaluate_object(handle, NULL, &atpx_arg, &buffer); | ||
133 | if (ACPI_FAILURE(status)) { | ||
134 | printk("%s: failed to call ATPX: %s\n", __func__, acpi_format_exception(status)); | ||
135 | return -ENOSYS; | ||
136 | } | ||
137 | kfree(buffer.pointer); | ||
138 | |||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | static int radeon_atpx_set_discrete_state(acpi_handle handle, int state) | ||
143 | { | ||
144 | return radeon_atpx_execute(handle, ATPX_GPU_PWR, state); | ||
145 | } | ||
146 | |||
147 | static int radeon_atpx_switch_mux(acpi_handle handle, int mux_id) | ||
148 | { | ||
149 | return radeon_atpx_execute(handle, ATPX_MUX_SELECT, mux_id); | ||
150 | } | ||
151 | |||
152 | |||
153 | static int radeon_atpx_switchto(enum vga_switcheroo_client_id id) | ||
154 | { | ||
155 | if (id == VGA_SWITCHEROO_IGD) | ||
156 | radeon_atpx_switch_mux(radeon_atpx_priv.atpx_handle, 0); | ||
157 | else | ||
158 | radeon_atpx_switch_mux(radeon_atpx_priv.atpx_handle, 1); | ||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | static int radeon_atpx_power_state(enum vga_switcheroo_client_id id, | ||
163 | enum vga_switcheroo_state state) | ||
164 | { | ||
165 | /* on w500 ACPI can't change intel gpu state */ | ||
166 | if (id == VGA_SWITCHEROO_IGD) | ||
167 | return 0; | ||
168 | |||
169 | radeon_atpx_set_discrete_state(radeon_atpx_priv.atpx_handle, state); | ||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev) | ||
174 | { | ||
175 | acpi_handle dhandle, atpx_handle, atrm_handle; | ||
176 | acpi_status status; | ||
177 | |||
178 | dhandle = DEVICE_ACPI_HANDLE(&pdev->dev); | ||
179 | if (!dhandle) | ||
180 | return false; | ||
181 | |||
182 | status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); | ||
183 | if (ACPI_FAILURE(status)) | ||
184 | return false; | ||
185 | |||
186 | status = acpi_get_handle(dhandle, "ATRM", &atrm_handle); | ||
187 | if (ACPI_FAILURE(status)) | ||
188 | return false; | ||
189 | |||
190 | radeon_atpx_priv.dhandle = dhandle; | ||
191 | radeon_atpx_priv.atpx_handle = atpx_handle; | ||
192 | radeon_atpx_priv.atrm_handle = atrm_handle; | ||
193 | return true; | ||
194 | } | ||
195 | |||
196 | static int radeon_atpx_init(void) | ||
197 | { | ||
198 | /* set up the ATPX handle */ | ||
199 | |||
200 | radeon_atpx_get_version(radeon_atpx_priv.atpx_handle); | ||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int radeon_atpx_get_client_id(struct pci_dev *pdev) | ||
205 | { | ||
206 | if (radeon_atpx_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev)) | ||
207 | return VGA_SWITCHEROO_IGD; | ||
208 | else | ||
209 | return VGA_SWITCHEROO_DIS; | ||
210 | } | ||
211 | |||
212 | static struct vga_switcheroo_handler radeon_atpx_handler = { | ||
213 | .switchto = radeon_atpx_switchto, | ||
214 | .power_state = radeon_atpx_power_state, | ||
215 | .init = radeon_atpx_init, | ||
216 | .get_client_id = radeon_atpx_get_client_id, | ||
217 | }; | ||
218 | |||
219 | static bool radeon_atpx_detect(void) | ||
220 | { | ||
221 | char acpi_method_name[255] = { 0 }; | ||
222 | struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; | ||
223 | struct pci_dev *pdev = NULL; | ||
224 | bool has_atpx = false; | ||
225 | int vga_count = 0; | ||
226 | |||
227 | while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { | ||
228 | vga_count++; | ||
229 | |||
230 | has_atpx |= (radeon_atpx_pci_probe_handle(pdev) == true); | ||
231 | } | ||
232 | |||
233 | if (has_atpx && vga_count == 2) { | ||
234 | acpi_get_name(radeon_atpx_priv.atpx_handle, ACPI_FULL_PATHNAME, &buffer); | ||
235 | printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n", | ||
236 | acpi_method_name); | ||
237 | radeon_atpx_priv.atpx_detected = true; | ||
238 | return true; | ||
239 | } | ||
240 | return false; | ||
241 | } | ||
242 | |||
243 | void radeon_register_atpx_handler(void) | ||
244 | { | ||
245 | bool r; | ||
246 | |||
247 | /* detect if we have any ATPX + 2 VGA in the system */ | ||
248 | r = radeon_atpx_detect(); | ||
249 | if (!r) | ||
250 | return; | ||
251 | |||
252 | vga_switcheroo_register_handler(&radeon_atpx_handler); | ||
253 | } | ||
254 | |||
255 | void radeon_unregister_atpx_handler(void) | ||
256 | { | ||
257 | vga_switcheroo_unregister_handler(); | ||
258 | } | ||
diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 906921740c60..a34b909485b8 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c | |||
@@ -30,6 +30,7 @@ | |||
30 | #include "radeon.h" | 30 | #include "radeon.h" |
31 | #include "atom.h" | 31 | #include "atom.h" |
32 | 32 | ||
33 | #include <linux/vga_switcheroo.h> | ||
33 | /* | 34 | /* |
34 | * BIOS. | 35 | * BIOS. |
35 | */ | 36 | */ |
@@ -62,7 +63,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) | |||
62 | iounmap(bios); | 63 | iounmap(bios); |
63 | return false; | 64 | return false; |
64 | } | 65 | } |
65 | memcpy(rdev->bios, bios, size); | 66 | memcpy_fromio(rdev->bios, bios, size); |
66 | iounmap(bios); | 67 | iounmap(bios); |
67 | return true; | 68 | return true; |
68 | } | 69 | } |
@@ -93,6 +94,38 @@ static bool radeon_read_bios(struct radeon_device *rdev) | |||
93 | return true; | 94 | return true; |
94 | } | 95 | } |
95 | 96 | ||
97 | /* ATRM is used to get the BIOS on the discrete cards in | ||
98 | * dual-gpu systems. | ||
99 | */ | ||
100 | static bool radeon_atrm_get_bios(struct radeon_device *rdev) | ||
101 | { | ||
102 | int ret; | ||
103 | int size = 64 * 1024; | ||
104 | int i; | ||
105 | |||
106 | if (!radeon_atrm_supported(rdev->pdev)) | ||
107 | return false; | ||
108 | |||
109 | rdev->bios = kmalloc(size, GFP_KERNEL); | ||
110 | if (!rdev->bios) { | ||
111 | DRM_ERROR("Unable to allocate bios\n"); | ||
112 | return false; | ||
113 | } | ||
114 | |||
115 | for (i = 0; i < size / ATRM_BIOS_PAGE; i++) { | ||
116 | ret = radeon_atrm_get_bios_chunk(rdev->bios, | ||
117 | (i * ATRM_BIOS_PAGE), | ||
118 | ATRM_BIOS_PAGE); | ||
119 | if (ret <= 0) | ||
120 | break; | ||
121 | } | ||
122 | |||
123 | if (i == 0 || rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { | ||
124 | kfree(rdev->bios); | ||
125 | return false; | ||
126 | } | ||
127 | return true; | ||
128 | } | ||
96 | static bool r700_read_disabled_bios(struct radeon_device *rdev) | 129 | static bool r700_read_disabled_bios(struct radeon_device *rdev) |
97 | { | 130 | { |
98 | uint32_t viph_control; | 131 | uint32_t viph_control; |
@@ -388,16 +421,16 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev) | |||
388 | return legacy_read_disabled_bios(rdev); | 421 | return legacy_read_disabled_bios(rdev); |
389 | } | 422 | } |
390 | 423 | ||
424 | |||
391 | bool radeon_get_bios(struct radeon_device *rdev) | 425 | bool radeon_get_bios(struct radeon_device *rdev) |
392 | { | 426 | { |
393 | bool r; | 427 | bool r; |
394 | uint16_t tmp; | 428 | uint16_t tmp; |
395 | 429 | ||
396 | if (rdev->flags & RADEON_IS_IGP) { | 430 | r = radeon_atrm_get_bios(rdev); |
431 | if (r == false) | ||
397 | r = igp_read_bios_from_vram(rdev); | 432 | r = igp_read_bios_from_vram(rdev); |
398 | if (r == false) | 433 | if (r == false) |
399 | r = radeon_read_bios(rdev); | ||
400 | } else | ||
401 | r = radeon_read_bios(rdev); | 434 | r = radeon_read_bios(rdev); |
402 | if (r == false) { | 435 | if (r == false) { |
403 | r = radeon_read_disabled_bios(rdev); | 436 | r = radeon_read_disabled_bios(rdev); |
@@ -408,6 +441,7 @@ bool radeon_get_bios(struct radeon_device *rdev) | |||
408 | return false; | 441 | return false; |
409 | } | 442 | } |
410 | if (rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { | 443 | if (rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { |
444 | printk("BIOS signature incorrect %x %x\n", rdev->bios[0], rdev->bios[1]); | ||
411 | goto free_bios; | 445 | goto free_bios; |
412 | } | 446 | } |
413 | 447 | ||
diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 768b1509fa03..cb8d9a1dd69c 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c | |||
@@ -30,6 +30,7 @@ | |||
30 | #include <drm/drm_crtc_helper.h> | 30 | #include <drm/drm_crtc_helper.h> |
31 | #include <drm/radeon_drm.h> | 31 | #include <drm/radeon_drm.h> |
32 | #include <linux/vgaarb.h> | 32 | #include <linux/vgaarb.h> |
33 | #include <linux/vga_switcheroo.h> | ||
33 | #include "radeon_reg.h" | 34 | #include "radeon_reg.h" |
34 | #include "radeon.h" | 35 | #include "radeon.h" |
35 | #include "radeon_asic.h" | 36 | #include "radeon_asic.h" |
@@ -613,6 +614,36 @@ void radeon_check_arguments(struct radeon_device *rdev) | |||
613 | } | 614 | } |
614 | } | 615 | } |
615 | 616 | ||
617 | static void radeon_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state) | ||
618 | { | ||
619 | struct drm_device *dev = pci_get_drvdata(pdev); | ||
620 | struct radeon_device *rdev = dev->dev_private; | ||
621 | pm_message_t pmm = { .event = PM_EVENT_SUSPEND }; | ||
622 | if (state == VGA_SWITCHEROO_ON) { | ||
623 | printk(KERN_INFO "radeon: switched on\n"); | ||
624 | /* don't suspend or resume card normally */ | ||
625 | rdev->powered_down = false; | ||
626 | radeon_resume_kms(dev); | ||
627 | } else { | ||
628 | printk(KERN_INFO "radeon: switched off\n"); | ||
629 | radeon_suspend_kms(dev, pmm); | ||
630 | /* don't suspend or resume card normally */ | ||
631 | rdev->powered_down = true; | ||
632 | } | ||
633 | } | ||
634 | |||
635 | static bool radeon_switcheroo_can_switch(struct pci_dev *pdev) | ||
636 | { | ||
637 | struct drm_device *dev = pci_get_drvdata(pdev); | ||
638 | bool can_switch; | ||
639 | |||
640 | spin_lock(&dev->count_lock); | ||
641 | can_switch = (dev->open_count == 0); | ||
642 | spin_unlock(&dev->count_lock); | ||
643 | return can_switch; | ||
644 | } | ||
645 | |||
646 | |||
616 | int radeon_device_init(struct radeon_device *rdev, | 647 | int radeon_device_init(struct radeon_device *rdev, |
617 | struct drm_device *ddev, | 648 | struct drm_device *ddev, |
618 | struct pci_dev *pdev, | 649 | struct pci_dev *pdev, |
@@ -692,6 +723,9 @@ int radeon_device_init(struct radeon_device *rdev, | |||
692 | /* this will fail for cards that aren't VGA class devices, just | 723 | /* this will fail for cards that aren't VGA class devices, just |
693 | * ignore it */ | 724 | * ignore it */ |
694 | vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode); | 725 | vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode); |
726 | vga_switcheroo_register_client(rdev->pdev, | ||
727 | radeon_switcheroo_set_state, | ||
728 | radeon_switcheroo_can_switch); | ||
695 | 729 | ||
696 | r = radeon_init(rdev); | 730 | r = radeon_init(rdev); |
697 | if (r) | 731 | if (r) |
@@ -723,6 +757,7 @@ void radeon_device_fini(struct radeon_device *rdev) | |||
723 | rdev->shutdown = true; | 757 | rdev->shutdown = true; |
724 | radeon_fini(rdev); | 758 | radeon_fini(rdev); |
725 | destroy_workqueue(rdev->wq); | 759 | destroy_workqueue(rdev->wq); |
760 | vga_switcheroo_unregister_client(rdev->pdev); | ||
726 | vga_client_register(rdev->pdev, NULL, NULL, NULL); | 761 | vga_client_register(rdev->pdev, NULL, NULL, NULL); |
727 | iounmap(rdev->rmmio); | 762 | iounmap(rdev->rmmio); |
728 | rdev->rmmio = NULL; | 763 | rdev->rmmio = NULL; |
@@ -746,6 +781,8 @@ int radeon_suspend_kms(struct drm_device *dev, pm_message_t state) | |||
746 | } | 781 | } |
747 | rdev = dev->dev_private; | 782 | rdev = dev->dev_private; |
748 | 783 | ||
784 | if (rdev->powered_down) | ||
785 | return 0; | ||
749 | /* unpin the front buffers */ | 786 | /* unpin the front buffers */ |
750 | list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { | 787 | list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { |
751 | struct radeon_framebuffer *rfb = to_radeon_framebuffer(crtc->fb); | 788 | struct radeon_framebuffer *rfb = to_radeon_framebuffer(crtc->fb); |
@@ -791,6 +828,9 @@ int radeon_resume_kms(struct drm_device *dev) | |||
791 | { | 828 | { |
792 | struct radeon_device *rdev = dev->dev_private; | 829 | struct radeon_device *rdev = dev->dev_private; |
793 | 830 | ||
831 | if (rdev->powered_down) | ||
832 | return 0; | ||
833 | |||
794 | acquire_console_sem(); | 834 | acquire_console_sem(); |
795 | pci_set_power_state(dev->pdev, PCI_D0); | 835 | pci_set_power_state(dev->pdev, PCI_D0); |
796 | pci_restore_state(dev->pdev); | 836 | pci_restore_state(dev->pdev); |
diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c index 8ba3de7994d4..4ab53aa163b2 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) | |||
339 | driver = &kms_driver; | 339 | driver = &kms_driver; |
340 | driver->driver_features |= DRIVER_MODESET; | 340 | driver->driver_features |= DRIVER_MODESET; |
341 | driver->num_ioctls = radeon_max_kms_ioctl; | 341 | driver->num_ioctls = radeon_max_kms_ioctl; |
342 | radeon_register_atpx_handler(); | ||
342 | } | 343 | } |
343 | /* if the vga console setting is enabled still | 344 | /* if the vga console setting is enabled still |
344 | * let modprobe override it */ | 345 | * let modprobe override it */ |
@@ -348,6 +349,7 @@ static int __init radeon_init(void) | |||
348 | static void __exit radeon_exit(void) | 349 | static void __exit radeon_exit(void) |
349 | { | 350 | { |
350 | drm_exit(driver); | 351 | drm_exit(driver); |
352 | radeon_unregister_atpx_handler(); | ||
351 | } | 353 | } |
352 | 354 | ||
353 | module_init(radeon_init); | 355 | module_init(radeon_init); |
diff --git a/drivers/gpu/drm/radeon/radeon_drv.h b/drivers/gpu/drm/radeon/radeon_drv.h index c57ad606504d..736237195143 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, | |||
455 | int sx, int sy, int dx, int dy, | 455 | int sx, int sy, int dx, int dy, |
456 | int w, int h, int src_pitch, int dst_pitch, int cpp); | 456 | int w, int h, int src_pitch, int dst_pitch, int cpp); |
457 | 457 | ||
458 | /* atpx handler */ | ||
459 | void radeon_register_atpx_handler(void); | ||
460 | void radeon_unregister_atpx_handler(void); | ||
458 | /* Flags for stats.boxes | 461 | /* Flags for stats.boxes |
459 | */ | 462 | */ |
460 | #define RADEON_BOX_DMA_IDLE 0x1 | 463 | #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 d71e346e9ab5..561719223988 100644 --- a/drivers/gpu/drm/radeon/radeon_fb.c +++ b/drivers/gpu/drm/radeon/radeon_fb.c | |||
@@ -39,6 +39,8 @@ | |||
39 | 39 | ||
40 | #include "drm_fb_helper.h" | 40 | #include "drm_fb_helper.h" |
41 | 41 | ||
42 | #include <linux/vga_switcheroo.h> | ||
43 | |||
42 | struct radeon_fb_device { | 44 | struct radeon_fb_device { |
43 | struct drm_fb_helper helper; | 45 | struct drm_fb_helper helper; |
44 | struct radeon_framebuffer *rfb; | 46 | struct radeon_framebuffer *rfb; |
@@ -291,6 +293,7 @@ int radeonfb_create(struct drm_device *dev, | |||
291 | rfbdev->rdev = rdev; | 293 | rfbdev->rdev = rdev; |
292 | 294 | ||
293 | mutex_unlock(&rdev->ddev->struct_mutex); | 295 | mutex_unlock(&rdev->ddev->struct_mutex); |
296 | vga_switcheroo_client_fb_set(rdev->ddev->pdev, info); | ||
294 | return 0; | 297 | return 0; |
295 | 298 | ||
296 | out_unref: | 299 | out_unref: |
diff --git a/drivers/gpu/drm/radeon/radeon_kms.c b/drivers/gpu/drm/radeon/radeon_kms.c index f23b05606eb5..5db7af6b91f4 100644 --- a/drivers/gpu/drm/radeon/radeon_kms.c +++ b/drivers/gpu/drm/radeon/radeon_kms.c | |||
@@ -30,6 +30,8 @@ | |||
30 | #include "radeon.h" | 30 | #include "radeon.h" |
31 | #include "radeon_drm.h" | 31 | #include "radeon_drm.h" |
32 | 32 | ||
33 | #include <linux/vga_switcheroo.h> | ||
34 | |||
33 | int radeon_driver_unload_kms(struct drm_device *dev) | 35 | int radeon_driver_unload_kms(struct drm_device *dev) |
34 | { | 36 | { |
35 | struct radeon_device *rdev = dev->dev_private; | 37 | struct radeon_device *rdev = dev->dev_private; |
@@ -136,6 +138,7 @@ int radeon_driver_firstopen_kms(struct drm_device *dev) | |||
136 | 138 | ||
137 | void radeon_driver_lastclose_kms(struct drm_device *dev) | 139 | void radeon_driver_lastclose_kms(struct drm_device *dev) |
138 | { | 140 | { |
141 | vga_switcheroo_process_delayed_switch(); | ||
139 | } | 142 | } |
140 | 143 | ||
141 | int radeon_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) | 144 | int radeon_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) |
diff --git a/drivers/gpu/vga/Kconfig b/drivers/gpu/vga/Kconfig index 790e675b13eb..6116a0196214 100644 --- a/drivers/gpu/vga/Kconfig +++ b/drivers/gpu/vga/Kconfig | |||
@@ -8,3 +8,16 @@ config VGA_ARB | |||
8 | are accessed at same time they need some kind of coordination. Please | 8 | are accessed at same time they need some kind of coordination. Please |
9 | see Documentation/vgaarbiter.txt for more details. Select this to | 9 | see Documentation/vgaarbiter.txt for more details. Select this to |
10 | enable VGA arbiter. | 10 | enable VGA arbiter. |
11 | |||
12 | config VGA_SWITCHEROO | ||
13 | bool "Laptop Hybrid Grapics - GPU switching support" | ||
14 | default y | ||
15 | depends on X86 | ||
16 | depends on ACPI | ||
17 | help | ||
18 | Many laptops released in 2008/9/10 have two gpus with a multiplxer | ||
19 | to switch between them. This adds support for dynamic switching when | ||
20 | X isn't running and delayed switching until the next logoff. This | ||
21 | features is called hybrid graphics, ATI PowerXpress, and Nvidia | ||
22 | HybridPower. | ||
23 | |||
diff --git a/drivers/gpu/vga/Makefile b/drivers/gpu/vga/Makefile index 7cc8c1ed645b..14ca30b75d0a 100644 --- a/drivers/gpu/vga/Makefile +++ b/drivers/gpu/vga/Makefile | |||
@@ -1 +1,2 @@ | |||
1 | obj-$(CONFIG_VGA_ARB) += vgaarb.o | 1 | obj-$(CONFIG_VGA_ARB) += vgaarb.o |
2 | obj-$(CONFIG_VGA_SWITCHEROO) += vga_switcheroo.o | ||
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c new file mode 100644 index 000000000000..a3f587a0aba9 --- /dev/null +++ b/drivers/gpu/vga/vga_switcheroo.c | |||
@@ -0,0 +1,453 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Red Hat Inc. | ||
3 | * Author : Dave Airlie <airlied@redhat.com> | ||
4 | * | ||
5 | * | ||
6 | * Licensed under GPLv2 | ||
7 | * | ||
8 | * vga_switcheroo.c - Support for laptop with dual GPU using one set of outputs | ||
9 | |||
10 | Switcher interface - methods require for ATPX and DCM | ||
11 | - switchto - this throws the output MUX switch | ||
12 | - discrete_set_power - sets the power state for the discrete card | ||
13 | |||
14 | GPU driver interface | ||
15 | - set_gpu_state - this should do the equiv of s/r for the card | ||
16 | - this should *not* set the discrete power state | ||
17 | - switch_check - check if the device is in a position to switch now | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/dmi.h> | ||
22 | #include <linux/seq_file.h> | ||
23 | #include <linux/uaccess.h> | ||
24 | #include <linux/fs.h> | ||
25 | #include <linux/debugfs.h> | ||
26 | #include <linux/fb.h> | ||
27 | |||
28 | #include <acpi/acpi.h> | ||
29 | #include <acpi/acpi_bus.h> | ||
30 | |||
31 | #include <linux/pci.h> | ||
32 | #include <linux/vga_switcheroo.h> | ||
33 | |||
34 | struct vga_switcheroo_client { | ||
35 | struct pci_dev *pdev; | ||
36 | struct fb_info *fb_info; | ||
37 | int pwr_state; | ||
38 | void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state); | ||
39 | bool (*can_switch)(struct pci_dev *pdev); | ||
40 | int id; | ||
41 | bool active; | ||
42 | }; | ||
43 | |||
44 | static DEFINE_MUTEX(vgasr_mutex); | ||
45 | |||
46 | struct vgasr_priv { | ||
47 | |||
48 | bool active; | ||
49 | bool delayed_switch_active; | ||
50 | enum vga_switcheroo_client_id delayed_client_id; | ||
51 | |||
52 | struct dentry *debugfs_root; | ||
53 | struct dentry *switch_file; | ||
54 | |||
55 | int registered_clients; | ||
56 | struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS]; | ||
57 | |||
58 | struct vga_switcheroo_handler *handler; | ||
59 | }; | ||
60 | |||
61 | static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); | ||
62 | static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); | ||
63 | |||
64 | /* only one switcheroo per system */ | ||
65 | static struct vgasr_priv vgasr_priv; | ||
66 | |||
67 | int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) | ||
68 | { | ||
69 | mutex_lock(&vgasr_mutex); | ||
70 | if (vgasr_priv.handler) { | ||
71 | mutex_unlock(&vgasr_mutex); | ||
72 | return -EINVAL; | ||
73 | } | ||
74 | |||
75 | vgasr_priv.handler = handler; | ||
76 | mutex_unlock(&vgasr_mutex); | ||
77 | return 0; | ||
78 | } | ||
79 | EXPORT_SYMBOL(vga_switcheroo_register_handler); | ||
80 | |||
81 | void vga_switcheroo_unregister_handler(void) | ||
82 | { | ||
83 | mutex_lock(&vgasr_mutex); | ||
84 | vgasr_priv.handler = NULL; | ||
85 | mutex_unlock(&vgasr_mutex); | ||
86 | } | ||
87 | EXPORT_SYMBOL(vga_switcheroo_unregister_handler); | ||
88 | |||
89 | static void vga_switcheroo_enable(void) | ||
90 | { | ||
91 | int i; | ||
92 | int ret; | ||
93 | /* call the handler to init */ | ||
94 | vgasr_priv.handler->init(); | ||
95 | |||
96 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
97 | ret = vgasr_priv.handler->get_client_id(vgasr_priv.clients[i].pdev); | ||
98 | if (ret < 0) | ||
99 | return; | ||
100 | |||
101 | vgasr_priv.clients[i].id = ret; | ||
102 | } | ||
103 | vga_switcheroo_debugfs_init(&vgasr_priv); | ||
104 | vgasr_priv.active = true; | ||
105 | } | ||
106 | |||
107 | int vga_switcheroo_register_client(struct pci_dev *pdev, | ||
108 | void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state), | ||
109 | bool (*can_switch)(struct pci_dev *pdev)) | ||
110 | { | ||
111 | int index; | ||
112 | |||
113 | mutex_lock(&vgasr_mutex); | ||
114 | /* don't do IGD vs DIS here */ | ||
115 | if (vgasr_priv.registered_clients & 1) | ||
116 | index = 1; | ||
117 | else | ||
118 | index = 0; | ||
119 | |||
120 | vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON; | ||
121 | vgasr_priv.clients[index].pdev = pdev; | ||
122 | vgasr_priv.clients[index].set_gpu_state = set_gpu_state; | ||
123 | vgasr_priv.clients[index].can_switch = can_switch; | ||
124 | vgasr_priv.clients[index].id = -1; | ||
125 | if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) | ||
126 | vgasr_priv.clients[index].active = true; | ||
127 | |||
128 | vgasr_priv.registered_clients |= (1 << index); | ||
129 | |||
130 | /* if we get two clients + handler */ | ||
131 | if (vgasr_priv.registered_clients == 0x3 && vgasr_priv.handler) { | ||
132 | printk(KERN_INFO "vga_switcheroo: enabled\n"); | ||
133 | vga_switcheroo_enable(); | ||
134 | } | ||
135 | mutex_unlock(&vgasr_mutex); | ||
136 | return 0; | ||
137 | } | ||
138 | EXPORT_SYMBOL(vga_switcheroo_register_client); | ||
139 | |||
140 | void vga_switcheroo_unregister_client(struct pci_dev *pdev) | ||
141 | { | ||
142 | int i; | ||
143 | |||
144 | mutex_lock(&vgasr_mutex); | ||
145 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
146 | if (vgasr_priv.clients[i].pdev == pdev) { | ||
147 | vgasr_priv.registered_clients &= ~(1 << i); | ||
148 | break; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | printk(KERN_INFO "vga_switcheroo: disabled\n"); | ||
153 | vga_switcheroo_debugfs_fini(&vgasr_priv); | ||
154 | vgasr_priv.active = false; | ||
155 | mutex_unlock(&vgasr_mutex); | ||
156 | } | ||
157 | EXPORT_SYMBOL(vga_switcheroo_unregister_client); | ||
158 | |||
159 | void vga_switcheroo_client_fb_set(struct pci_dev *pdev, | ||
160 | struct fb_info *info) | ||
161 | { | ||
162 | int i; | ||
163 | |||
164 | mutex_lock(&vgasr_mutex); | ||
165 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
166 | if (vgasr_priv.clients[i].pdev == pdev) { | ||
167 | vgasr_priv.clients[i].fb_info = info; | ||
168 | break; | ||
169 | } | ||
170 | } | ||
171 | mutex_unlock(&vgasr_mutex); | ||
172 | } | ||
173 | EXPORT_SYMBOL(vga_switcheroo_client_fb_set); | ||
174 | |||
175 | static int vga_switcheroo_show(struct seq_file *m, void *v) | ||
176 | { | ||
177 | int i; | ||
178 | mutex_lock(&vgasr_mutex); | ||
179 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
180 | seq_printf(m, "%d:%c:%s:%s\n", i, | ||
181 | vgasr_priv.clients[i].active ? '+' : ' ', | ||
182 | vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off", | ||
183 | pci_name(vgasr_priv.clients[i].pdev)); | ||
184 | } | ||
185 | mutex_unlock(&vgasr_mutex); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) | ||
190 | { | ||
191 | return single_open(file, vga_switcheroo_show, NULL); | ||
192 | } | ||
193 | |||
194 | static int vga_switchon(struct vga_switcheroo_client *client) | ||
195 | { | ||
196 | int ret; | ||
197 | |||
198 | ret = vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); | ||
199 | /* call the driver callback to turn on device */ | ||
200 | client->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); | ||
201 | client->pwr_state = VGA_SWITCHEROO_ON; | ||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static int vga_switchoff(struct vga_switcheroo_client *client) | ||
206 | { | ||
207 | /* call the driver callback to turn off device */ | ||
208 | client->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); | ||
209 | vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); | ||
210 | client->pwr_state = VGA_SWITCHEROO_OFF; | ||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static int vga_switchto(struct vga_switcheroo_client *new_client) | ||
215 | { | ||
216 | int ret; | ||
217 | int i; | ||
218 | struct vga_switcheroo_client *active = NULL; | ||
219 | |||
220 | if (new_client->active == true) | ||
221 | return 0; | ||
222 | |||
223 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
224 | if (vgasr_priv.clients[i].active == true) { | ||
225 | active = &vgasr_priv.clients[i]; | ||
226 | break; | ||
227 | } | ||
228 | } | ||
229 | if (!active) | ||
230 | return 0; | ||
231 | |||
232 | /* power up the first device */ | ||
233 | ret = pci_enable_device(new_client->pdev); | ||
234 | if (ret) | ||
235 | return ret; | ||
236 | |||
237 | if (new_client->pwr_state == VGA_SWITCHEROO_OFF) | ||
238 | vga_switchon(new_client); | ||
239 | |||
240 | /* swap shadow resource to denote boot VGA device has changed so X starts on new device */ | ||
241 | active->active = false; | ||
242 | |||
243 | active->pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW; | ||
244 | new_client->pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW; | ||
245 | |||
246 | if (new_client->fb_info) { | ||
247 | struct fb_event event; | ||
248 | event.info = new_client->fb_info; | ||
249 | fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); | ||
250 | } | ||
251 | |||
252 | ret = vgasr_priv.handler->switchto(new_client->id); | ||
253 | if (ret) | ||
254 | return ret; | ||
255 | |||
256 | if (active->pwr_state == VGA_SWITCHEROO_ON) | ||
257 | vga_switchoff(active); | ||
258 | |||
259 | new_client->active = true; | ||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | static ssize_t | ||
264 | vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, | ||
265 | size_t cnt, loff_t *ppos) | ||
266 | { | ||
267 | char usercmd[64]; | ||
268 | const char *pdev_name; | ||
269 | int i, ret; | ||
270 | bool delay = false, can_switch; | ||
271 | int client_id = -1; | ||
272 | struct vga_switcheroo_client *client = NULL; | ||
273 | |||
274 | if (cnt > 63) | ||
275 | cnt = 63; | ||
276 | |||
277 | if (copy_from_user(usercmd, ubuf, cnt)) | ||
278 | return -EFAULT; | ||
279 | |||
280 | mutex_lock(&vgasr_mutex); | ||
281 | |||
282 | if (!vgasr_priv.active) | ||
283 | return -EINVAL; | ||
284 | |||
285 | /* pwr off the device not in use */ | ||
286 | if (strncmp(usercmd, "OFF", 3) == 0) { | ||
287 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
288 | if (vgasr_priv.clients[i].active) | ||
289 | continue; | ||
290 | if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON) | ||
291 | vga_switchoff(&vgasr_priv.clients[i]); | ||
292 | } | ||
293 | goto out; | ||
294 | } | ||
295 | /* pwr on the device not in use */ | ||
296 | if (strncmp(usercmd, "ON", 2) == 0) { | ||
297 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
298 | if (vgasr_priv.clients[i].active) | ||
299 | continue; | ||
300 | if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF) | ||
301 | vga_switchon(&vgasr_priv.clients[i]); | ||
302 | } | ||
303 | goto out; | ||
304 | } | ||
305 | |||
306 | /* request a delayed switch - test can we switch now */ | ||
307 | if (strncmp(usercmd, "DIGD", 4) == 0) { | ||
308 | client_id = VGA_SWITCHEROO_IGD; | ||
309 | delay = true; | ||
310 | } | ||
311 | |||
312 | if (strncmp(usercmd, "DDIS", 4) == 0) { | ||
313 | client_id = VGA_SWITCHEROO_DIS; | ||
314 | delay = true; | ||
315 | } | ||
316 | |||
317 | if (strncmp(usercmd, "IGD", 3) == 0) | ||
318 | client_id = VGA_SWITCHEROO_IGD; | ||
319 | |||
320 | if (strncmp(usercmd, "DIS", 3) == 0) | ||
321 | client_id = VGA_SWITCHEROO_DIS; | ||
322 | |||
323 | if (client_id == -1) | ||
324 | goto out; | ||
325 | |||
326 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
327 | if (vgasr_priv.clients[i].id == client_id) { | ||
328 | client = &vgasr_priv.clients[i]; | ||
329 | break; | ||
330 | } | ||
331 | } | ||
332 | |||
333 | vgasr_priv.delayed_switch_active = false; | ||
334 | /* okay we want a switch - test if devices are willing to switch */ | ||
335 | can_switch = true; | ||
336 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
337 | can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); | ||
338 | if (can_switch == false) { | ||
339 | printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); | ||
340 | break; | ||
341 | } | ||
342 | } | ||
343 | |||
344 | if (can_switch == false && delay == false) | ||
345 | goto out; | ||
346 | |||
347 | if (can_switch == true) { | ||
348 | pdev_name = pci_name(client->pdev); | ||
349 | ret = vga_switchto(client); | ||
350 | if (ret) | ||
351 | printk(KERN_ERR "vga_switcheroo: switching failed %d\n", ret); | ||
352 | } else { | ||
353 | printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id); | ||
354 | vgasr_priv.delayed_switch_active = true; | ||
355 | vgasr_priv.delayed_client_id = client_id; | ||
356 | |||
357 | /* we should at least power up the card to | ||
358 | make the switch faster */ | ||
359 | if (client->pwr_state == VGA_SWITCHEROO_OFF) | ||
360 | vga_switchon(client); | ||
361 | } | ||
362 | |||
363 | out: | ||
364 | mutex_unlock(&vgasr_mutex); | ||
365 | return cnt; | ||
366 | } | ||
367 | |||
368 | static const struct file_operations vga_switcheroo_debugfs_fops = { | ||
369 | .owner = THIS_MODULE, | ||
370 | .open = vga_switcheroo_debugfs_open, | ||
371 | .write = vga_switcheroo_debugfs_write, | ||
372 | .read = seq_read, | ||
373 | .llseek = seq_lseek, | ||
374 | .release = single_release, | ||
375 | }; | ||
376 | |||
377 | static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv) | ||
378 | { | ||
379 | if (priv->switch_file) { | ||
380 | debugfs_remove(priv->switch_file); | ||
381 | priv->switch_file = NULL; | ||
382 | } | ||
383 | if (priv->debugfs_root) { | ||
384 | debugfs_remove(priv->debugfs_root); | ||
385 | priv->debugfs_root = NULL; | ||
386 | } | ||
387 | } | ||
388 | |||
389 | static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv) | ||
390 | { | ||
391 | /* already initialised */ | ||
392 | if (priv->debugfs_root) | ||
393 | return 0; | ||
394 | priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL); | ||
395 | |||
396 | if (!priv->debugfs_root) { | ||
397 | printk(KERN_ERR "vga_switcheroo: Cannot create /sys/kernel/debug/vgaswitcheroo\n"); | ||
398 | goto fail; | ||
399 | } | ||
400 | |||
401 | priv->switch_file = debugfs_create_file("switch", 0644, | ||
402 | priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops); | ||
403 | if (!priv->switch_file) { | ||
404 | printk(KERN_ERR "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n"); | ||
405 | goto fail; | ||
406 | } | ||
407 | return 0; | ||
408 | fail: | ||
409 | vga_switcheroo_debugfs_fini(priv); | ||
410 | return -1; | ||
411 | } | ||
412 | |||
413 | int vga_switcheroo_process_delayed_switch(void) | ||
414 | { | ||
415 | struct vga_switcheroo_client *client = NULL; | ||
416 | const char *pdev_name; | ||
417 | bool can_switch = true; | ||
418 | int i; | ||
419 | int ret; | ||
420 | int err = -EINVAL; | ||
421 | |||
422 | mutex_lock(&vgasr_mutex); | ||
423 | if (!vgasr_priv.delayed_switch_active) | ||
424 | goto err; | ||
425 | |||
426 | printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id); | ||
427 | |||
428 | for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { | ||
429 | if (vgasr_priv.clients[i].id == vgasr_priv.delayed_client_id) | ||
430 | client = &vgasr_priv.clients[i]; | ||
431 | can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); | ||
432 | if (can_switch == false) { | ||
433 | printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); | ||
434 | break; | ||
435 | } | ||
436 | } | ||
437 | |||
438 | if (can_switch == false || client == NULL) | ||
439 | goto err; | ||
440 | |||
441 | pdev_name = pci_name(client->pdev); | ||
442 | ret = vga_switchto(client); | ||
443 | if (ret) | ||
444 | printk(KERN_ERR "vga_switcheroo: delayed switching failed %d\n", ret); | ||
445 | |||
446 | vgasr_priv.delayed_switch_active = false; | ||
447 | err = 0; | ||
448 | err: | ||
449 | mutex_unlock(&vgasr_mutex); | ||
450 | return err; | ||
451 | } | ||
452 | EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); | ||
453 | |||
diff --git a/drivers/video/console/fbcon.c b/drivers/video/console/fbcon.c index 3681c6a88212..b0a3fa00706d 100644 --- a/drivers/video/console/fbcon.c +++ b/drivers/video/console/fbcon.c | |||
@@ -3025,6 +3025,20 @@ static int fbcon_fb_unregistered(struct fb_info *info) | |||
3025 | return 0; | 3025 | return 0; |
3026 | } | 3026 | } |
3027 | 3027 | ||
3028 | static void fbcon_remap_all(int idx) | ||
3029 | { | ||
3030 | int i; | ||
3031 | for (i = first_fb_vc; i <= last_fb_vc; i++) | ||
3032 | set_con2fb_map(i, idx, 0); | ||
3033 | |||
3034 | if (con_is_bound(&fb_con)) { | ||
3035 | printk(KERN_INFO "fbcon: Remapping primary device, " | ||
3036 | "fb%i, to tty %i-%i\n", idx, | ||
3037 | first_fb_vc + 1, last_fb_vc + 1); | ||
3038 | info_idx = idx; | ||
3039 | } | ||
3040 | } | ||
3041 | |||
3028 | #ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY | 3042 | #ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY |
3029 | static void fbcon_select_primary(struct fb_info *info) | 3043 | static void fbcon_select_primary(struct fb_info *info) |
3030 | { | 3044 | { |
@@ -3225,6 +3239,10 @@ static int fbcon_event_notify(struct notifier_block *self, | |||
3225 | caps = event->data; | 3239 | caps = event->data; |
3226 | fbcon_get_requirement(info, caps); | 3240 | fbcon_get_requirement(info, caps); |
3227 | break; | 3241 | break; |
3242 | case FB_EVENT_REMAP_ALL_CONSOLE: | ||
3243 | idx = info->node; | ||
3244 | fbcon_remap_all(idx); | ||
3245 | break; | ||
3228 | } | 3246 | } |
3229 | done: | 3247 | done: |
3230 | return ret; | 3248 | return ret; |
diff --git a/include/linux/fb.h b/include/linux/fb.h index 369767bd873e..c10163b4c40e 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h | |||
@@ -543,6 +543,8 @@ struct fb_cursor_user { | |||
543 | #define FB_EVENT_GET_REQ 0x0D | 543 | #define FB_EVENT_GET_REQ 0x0D |
544 | /* Unbind from the console if possible */ | 544 | /* Unbind from the console if possible */ |
545 | #define FB_EVENT_FB_UNBIND 0x0E | 545 | #define FB_EVENT_FB_UNBIND 0x0E |
546 | /* CONSOLE-SPECIFIC: remap all consoles to new fb - for vga switcheroo */ | ||
547 | #define FB_EVENT_REMAP_ALL_CONSOLE 0x0F | ||
546 | 548 | ||
547 | struct fb_event { | 549 | struct fb_event { |
548 | struct fb_info *info; | 550 | struct fb_info *info; |
diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h new file mode 100644 index 000000000000..4b58ab1e8612 --- /dev/null +++ b/include/linux/vga_switcheroo.h | |||
@@ -0,0 +1,58 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Red Hat Inc. | ||
3 | * Author : Dave Airlie <airlied@redhat.com> | ||
4 | * | ||
5 | * Licensed under GPLv2 | ||
6 | * | ||
7 | * vga_switcheroo.h - Support for laptop with dual GPU using one set of outputs | ||
8 | */ | ||
9 | |||
10 | #include <acpi/acpi.h> | ||
11 | #include <linux/fb.h> | ||
12 | |||
13 | enum vga_switcheroo_state { | ||
14 | VGA_SWITCHEROO_OFF, | ||
15 | VGA_SWITCHEROO_ON, | ||
16 | }; | ||
17 | |||
18 | enum vga_switcheroo_client_id { | ||
19 | VGA_SWITCHEROO_IGD, | ||
20 | VGA_SWITCHEROO_DIS, | ||
21 | VGA_SWITCHEROO_MAX_CLIENTS, | ||
22 | }; | ||
23 | |||
24 | struct vga_switcheroo_handler { | ||
25 | int (*switchto)(enum vga_switcheroo_client_id id); | ||
26 | int (*power_state)(enum vga_switcheroo_client_id id, | ||
27 | enum vga_switcheroo_state state); | ||
28 | int (*init)(void); | ||
29 | int (*get_client_id)(struct pci_dev *pdev); | ||
30 | }; | ||
31 | |||
32 | |||
33 | #if defined(CONFIG_VGA_SWITCHEROO) | ||
34 | void vga_switcheroo_unregister_client(struct pci_dev *dev); | ||
35 | int vga_switcheroo_register_client(struct pci_dev *dev, | ||
36 | void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state), | ||
37 | bool (*can_switch)(struct pci_dev *dev)); | ||
38 | |||
39 | void vga_switcheroo_client_fb_set(struct pci_dev *dev, | ||
40 | struct fb_info *info); | ||
41 | |||
42 | int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler); | ||
43 | void vga_switcheroo_unregister_handler(void); | ||
44 | |||
45 | int vga_switcheroo_process_delayed_switch(void); | ||
46 | |||
47 | #else | ||
48 | |||
49 | static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} | ||
50 | static inline int vga_switcheroo_register_client(struct pci_dev *dev, | ||
51 | void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state), | ||
52 | bool (*can_switch)(struct pci_dev *dev)) { return 0; } | ||
53 | static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {} | ||
54 | static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; } | ||
55 | static inline void vga_switcheroo_unregister_handler(void) {} | ||
56 | static inline int vga_switcheroo_process_delayed_switch(void) { return 0; } | ||
57 | |||
58 | #endif | ||