Commit 994aad251acab32a5d40d4a9501dc3e736562b6d
Committed by
Linus Torvalds
1 parent
256154fbc3
Exists in
master
and in
4 other branches
[PATCH] radeonfb sleep fixes
Many IBM Thinkpad T4* models and some R* and X* with radeon video cards draw
too much power when suspended to RAM, reducing drastically the battery
lifetime. The solution is to enable suspend-to-D2 on these machines. They
are whitelisted through their subsystem vendor/device ID. This fixes
http://bugzilla.kernel.org/show_bug.cgi?id=3022
The patch introduces a framework to alter the pm_mode and reinit_func fields
of the radeonfb_info structure based on a whitelist. This should facilitate
future hardware-dependent workarounds. The workaround for the Samsung P35
that is already in the radeonfb code has been rewritten using this framework.
The behavior can be overridden with module options:
i) video=radeonfb:force_sleep=1
enable suspend-to-D2 also on non-whitelisted machines (useful for
testing new notebook models),
ii) video=radeonfb:ignore_devlist=1
Disable checking the whitelist and do not apply any workarounds.
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: "Antonino A. Daplas" <adaplas@pol.net>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Showing 3 changed files with 145 additions and 25 deletions Side-by-side Diff
drivers/video/aty/radeon_base.c
| ... | ... | @@ -266,6 +266,8 @@ |
| 266 | 266 | #ifdef CONFIG_MTRR |
| 267 | 267 | static int nomtrr = 0; |
| 268 | 268 | #endif |
| 269 | +static int force_sleep; | |
| 270 | +static int ignore_devlist; | |
| 269 | 271 | |
| 270 | 272 | /* |
| 271 | 273 | * prototypes |
| 272 | 274 | |
| ... | ... | @@ -2327,9 +2329,9 @@ |
| 2327 | 2329 | /* -2 is special: means ON on mobility chips and do not |
| 2328 | 2330 | * change on others |
| 2329 | 2331 | */ |
| 2330 | - radeonfb_pm_init(rinfo, rinfo->is_mobility ? 1 : -1); | |
| 2332 | + radeonfb_pm_init(rinfo, rinfo->is_mobility ? 1 : -1, ignore_devlist, force_sleep); | |
| 2331 | 2333 | } else |
| 2332 | - radeonfb_pm_init(rinfo, default_dynclk); | |
| 2334 | + radeonfb_pm_init(rinfo, default_dynclk, ignore_devlist, force_sleep); | |
| 2333 | 2335 | |
| 2334 | 2336 | pci_set_drvdata(pdev, info); |
| 2335 | 2337 | |
| ... | ... | @@ -2477,6 +2479,12 @@ |
| 2477 | 2479 | force_measure_pll = 1; |
| 2478 | 2480 | } else if (!strncmp(this_opt, "ignore_edid", 11)) { |
| 2479 | 2481 | ignore_edid = 1; |
| 2482 | +#if defined(CONFIG_PM) && defined(CONFIG_X86) | |
| 2483 | + } else if (!strncmp(this_opt, "force_sleep", 11)) { | |
| 2484 | + force_sleep = 1; | |
| 2485 | + } else if (!strncmp(this_opt, "ignore_devlist", 14)) { | |
| 2486 | + ignore_devlist = 1; | |
| 2487 | +#endif | |
| 2480 | 2488 | } else |
| 2481 | 2489 | mode_option = this_opt; |
| 2482 | 2490 | } |
| ... | ... | @@ -2532,4 +2540,10 @@ |
| 2532 | 2540 | MODULE_PARM_DESC(panel_yres, "int: set panel yres"); |
| 2533 | 2541 | module_param(mode_option, charp, 0); |
| 2534 | 2542 | MODULE_PARM_DESC(mode_option, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); |
| 2543 | +#if defined(CONFIG_PM) && defined(CONFIG_X86) | |
| 2544 | +module_param(force_sleep, bool, 0); | |
| 2545 | +MODULE_PARM_DESC(force_sleep, "bool: force D2 sleep mode on all hardware"); | |
| 2546 | +module_param(ignore_devlist, bool, 0); | |
| 2547 | +MODULE_PARM_DESC(ignore_devlist, "bool: ignore workarounds for bugs in specific laptops"); | |
| 2548 | +#endif |
drivers/video/aty/radeon_pm.c
| ... | ... | @@ -27,6 +27,99 @@ |
| 27 | 27 | |
| 28 | 28 | #include "ati_ids.h" |
| 29 | 29 | |
| 30 | +static void radeon_reinitialize_M10(struct radeonfb_info *rinfo); | |
| 31 | + | |
| 32 | +/* | |
| 33 | + * Workarounds for bugs in PC laptops: | |
| 34 | + * - enable D2 sleep in some IBM Thinkpads | |
| 35 | + * - special case for Samsung P35 | |
| 36 | + * | |
| 37 | + * Whitelist by subsystem vendor/device because | |
| 38 | + * its the subsystem vendor's fault! | |
| 39 | + */ | |
| 40 | + | |
| 41 | +#if defined(CONFIG_PM) && defined(CONFIG_X86) | |
| 42 | +struct radeon_device_id { | |
| 43 | + const char *ident; /* (arbitrary) Name */ | |
| 44 | + const unsigned short subsystem_vendor; /* Subsystem Vendor ID */ | |
| 45 | + const unsigned short subsystem_device; /* Subsystem Device ID */ | |
| 46 | + const enum radeon_pm_mode pm_mode_modifier; /* modify pm_mode */ | |
| 47 | + const reinit_function_ptr new_reinit_func; /* changed reinit_func */ | |
| 48 | +}; | |
| 49 | + | |
| 50 | +#define BUGFIX(model, sv, sd, pm, fn) { \ | |
| 51 | + .ident = model, \ | |
| 52 | + .subsystem_vendor = sv, \ | |
| 53 | + .subsystem_device = sd, \ | |
| 54 | + .pm_mode_modifier = pm, \ | |
| 55 | + .new_reinit_func = fn \ | |
| 56 | +} | |
| 57 | + | |
| 58 | +static struct radeon_device_id radeon_workaround_list[] = { | |
| 59 | + BUGFIX("IBM Thinkpad R32", | |
| 60 | + PCI_VENDOR_ID_IBM, 0x1905, | |
| 61 | + radeon_pm_d2, NULL), | |
| 62 | + BUGFIX("IBM Thinkpad R40", | |
| 63 | + PCI_VENDOR_ID_IBM, 0x0526, | |
| 64 | + radeon_pm_d2, NULL), | |
| 65 | + BUGFIX("IBM Thinkpad R40", | |
| 66 | + PCI_VENDOR_ID_IBM, 0x0527, | |
| 67 | + radeon_pm_d2, NULL), | |
| 68 | + BUGFIX("IBM Thinkpad R50/R51/T40/T41", | |
| 69 | + PCI_VENDOR_ID_IBM, 0x0531, | |
| 70 | + radeon_pm_d2, NULL), | |
| 71 | + BUGFIX("IBM Thinkpad R51/T40/T41/T42", | |
| 72 | + PCI_VENDOR_ID_IBM, 0x0530, | |
| 73 | + radeon_pm_d2, NULL), | |
| 74 | + BUGFIX("IBM Thinkpad T30", | |
| 75 | + PCI_VENDOR_ID_IBM, 0x0517, | |
| 76 | + radeon_pm_d2, NULL), | |
| 77 | + BUGFIX("IBM Thinkpad T40p", | |
| 78 | + PCI_VENDOR_ID_IBM, 0x054d, | |
| 79 | + radeon_pm_d2, NULL), | |
| 80 | + BUGFIX("IBM Thinkpad T42", | |
| 81 | + PCI_VENDOR_ID_IBM, 0x0550, | |
| 82 | + radeon_pm_d2, NULL), | |
| 83 | + BUGFIX("IBM Thinkpad X31/X32", | |
| 84 | + PCI_VENDOR_ID_IBM, 0x052f, | |
| 85 | + radeon_pm_d2, NULL), | |
| 86 | + BUGFIX("Samsung P35", | |
| 87 | + PCI_VENDOR_ID_SAMSUNG, 0xc00c, | |
| 88 | + radeon_pm_off, radeon_reinitialize_M10), | |
| 89 | + { .ident = NULL } | |
| 90 | +}; | |
| 91 | + | |
| 92 | +static int radeon_apply_workarounds(struct radeonfb_info *rinfo) | |
| 93 | +{ | |
| 94 | + struct radeon_device_id *id; | |
| 95 | + | |
| 96 | + for (id = radeon_workaround_list; id->ident != NULL; id++ ) | |
| 97 | + if ((id->subsystem_vendor == rinfo->pdev->subsystem_vendor ) && | |
| 98 | + (id->subsystem_device == rinfo->pdev->subsystem_device )) { | |
| 99 | + | |
| 100 | + /* we found a device that requires workaround */ | |
| 101 | + printk(KERN_DEBUG "radeonfb: %s detected" | |
| 102 | + ", enabling workaround\n", id->ident); | |
| 103 | + | |
| 104 | + rinfo->pm_mode |= id->pm_mode_modifier; | |
| 105 | + | |
| 106 | + if (id->new_reinit_func != NULL) | |
| 107 | + rinfo->reinit_func = id->new_reinit_func; | |
| 108 | + | |
| 109 | + return 1; | |
| 110 | + } | |
| 111 | + return 0; /* not found */ | |
| 112 | +} | |
| 113 | + | |
| 114 | +#else /* defined(CONFIG_PM) && defined(CONFIG_X86) */ | |
| 115 | +static inline int radeon_apply_workarounds(struct radeonfb_info *rinfo) | |
| 116 | +{ | |
| 117 | + return 0; | |
| 118 | +} | |
| 119 | +#endif /* defined(CONFIG_PM) && defined(CONFIG_X86) */ | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 30 | 123 | static void radeon_pm_disable_dynamic_mode(struct radeonfb_info *rinfo) |
| 31 | 124 | { |
| 32 | 125 | u32 tmp; |
| 33 | 126 | |
| 34 | 127 | |
| ... | ... | @@ -852,18 +945,26 @@ |
| 852 | 945 | /* because both INPLL and OUTPLL take the same lock, that's why. */ |
| 853 | 946 | tmp = INPLL( pllMCLK_MISC) | MCLK_MISC__EN_MCLK_TRISTATE_IN_SUSPEND; |
| 854 | 947 | OUTPLL( pllMCLK_MISC, tmp); |
| 855 | - | |
| 856 | - /* AGP PLL control */ | |
| 857 | - if (rinfo->family <= CHIP_FAMILY_RV280) { | |
| 858 | - OUTREG(BUS_CNTL1, INREG(BUS_CNTL1) | BUS_CNTL1__AGPCLK_VALID); | |
| 859 | 948 | |
| 860 | - OUTREG(BUS_CNTL1, | |
| 861 | - (INREG(BUS_CNTL1) & ~BUS_CNTL1__MOBILE_PLATFORM_SEL_MASK) | |
| 862 | - | (2<<BUS_CNTL1__MOBILE_PLATFORM_SEL__SHIFT)); // 440BX | |
| 863 | - } else { | |
| 864 | - OUTREG(BUS_CNTL1, INREG(BUS_CNTL1)); | |
| 865 | - OUTREG(BUS_CNTL1, (INREG(BUS_CNTL1) & ~0x4000) | 0x8000); | |
| 949 | + /* BUS_CNTL1__MOBILE_PLATORM_SEL setting is northbridge chipset | |
| 950 | + * and radeon chip dependent. Thus we only enable it on Mac for | |
| 951 | + * now (until we get more info on how to compute the correct | |
| 952 | + * value for various X86 bridges). | |
| 953 | + */ | |
| 954 | +#ifdef CONFIG_PPC_PMAC | |
| 955 | + if (machine_is(powermac)) { | |
| 956 | + /* AGP PLL control */ | |
| 957 | + if (rinfo->family <= CHIP_FAMILY_RV280) { | |
| 958 | + OUTREG(BUS_CNTL1, INREG(BUS_CNTL1) | BUS_CNTL1__AGPCLK_VALID); | |
| 959 | + OUTREG(BUS_CNTL1, | |
| 960 | + (INREG(BUS_CNTL1) & ~BUS_CNTL1__MOBILE_PLATFORM_SEL_MASK) | |
| 961 | + | (2<<BUS_CNTL1__MOBILE_PLATFORM_SEL__SHIFT)); // 440BX | |
| 962 | + } else { | |
| 963 | + OUTREG(BUS_CNTL1, INREG(BUS_CNTL1)); | |
| 964 | + OUTREG(BUS_CNTL1, (INREG(BUS_CNTL1) & ~0x4000) | 0x8000); | |
| 965 | + } | |
| 866 | 966 | } |
| 967 | +#endif | |
| 867 | 968 | |
| 868 | 969 | OUTREG(CRTC_OFFSET_CNTL, (INREG(CRTC_OFFSET_CNTL) |
| 869 | 970 | & ~CRTC_OFFSET_CNTL__CRTC_STEREO_SYNC_OUT_EN)); |
| ... | ... | @@ -2713,7 +2814,7 @@ |
| 2713 | 2814 | |
| 2714 | 2815 | #endif /* CONFIG_PM */ |
| 2715 | 2816 | |
| 2716 | -void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk) | |
| 2817 | +void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk, int ignore_devlist, int force_sleep) | |
| 2717 | 2818 | { |
| 2718 | 2819 | /* Find PM registers in config space if any*/ |
| 2719 | 2820 | rinfo->pm_reg = pci_find_capability(rinfo->pdev, PCI_CAP_ID_PM); |
| 2720 | 2821 | |
| ... | ... | @@ -2729,22 +2830,13 @@ |
| 2729 | 2830 | } |
| 2730 | 2831 | |
| 2731 | 2832 | #if defined(CONFIG_PM) |
| 2833 | +#if defined(CONFIG_PPC_PMAC) | |
| 2732 | 2834 | /* Check if we can power manage on suspend/resume. We can do |
| 2733 | 2835 | * D2 on M6, M7 and M9, and we can resume from D3 cold a few other |
| 2734 | 2836 | * "Mac" cards, but that's all. We need more infos about what the |
| 2735 | 2837 | * BIOS does tho. Right now, all this PM stuff is pmac-only for that |
| 2736 | 2838 | * reason. --BenH |
| 2737 | 2839 | */ |
| 2738 | - /* Special case for Samsung P35 laptops | |
| 2739 | - */ | |
| 2740 | - if ((rinfo->pdev->vendor == PCI_VENDOR_ID_ATI) && | |
| 2741 | - (rinfo->pdev->device == PCI_CHIP_RV350_NP) && | |
| 2742 | - (rinfo->pdev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG) && | |
| 2743 | - (rinfo->pdev->subsystem_device == 0xc00c)) { | |
| 2744 | - rinfo->reinit_func = radeon_reinitialize_M10; | |
| 2745 | - rinfo->pm_mode |= radeon_pm_off; | |
| 2746 | - } | |
| 2747 | -#if defined(CONFIG_PPC_PMAC) | |
| 2748 | 2840 | if (machine_is(powermac) && rinfo->of_node) { |
| 2749 | 2841 | if (rinfo->is_mobility && rinfo->pm_reg && |
| 2750 | 2842 | rinfo->family <= CHIP_FAMILY_RV250) |
| ... | ... | @@ -2790,6 +2882,18 @@ |
| 2790 | 2882 | } |
| 2791 | 2883 | #endif /* defined(CONFIG_PPC_PMAC) */ |
| 2792 | 2884 | #endif /* defined(CONFIG_PM) */ |
| 2885 | + | |
| 2886 | + if (ignore_devlist) | |
| 2887 | + printk(KERN_DEBUG | |
| 2888 | + "radeonfb: skipping test for device workarounds\n"); | |
| 2889 | + else | |
| 2890 | + radeon_apply_workarounds(rinfo); | |
| 2891 | + | |
| 2892 | + if (force_sleep) { | |
| 2893 | + printk(KERN_DEBUG | |
| 2894 | + "radeonfb: forcefully enabling D2 sleep mode\n"); | |
| 2895 | + rinfo->pm_mode |= radeon_pm_d2; | |
| 2896 | + } | |
| 2793 | 2897 | } |
| 2794 | 2898 | |
| 2795 | 2899 | void radeonfb_pm_exit(struct radeonfb_info *rinfo) |
drivers/video/aty/radeonfb.h
| ... | ... | @@ -273,6 +273,8 @@ |
| 273 | 273 | radeon_pm_off = 0x00000002, /* Can resume from D3 cold */ |
| 274 | 274 | }; |
| 275 | 275 | |
| 276 | +typedef void (*reinit_function_ptr)(struct radeonfb_info *rinfo); | |
| 277 | + | |
| 276 | 278 | struct radeonfb_info { |
| 277 | 279 | struct fb_info *info; |
| 278 | 280 | |
| ... | ... | @@ -338,7 +340,7 @@ |
| 338 | 340 | int dynclk; |
| 339 | 341 | int no_schedule; |
| 340 | 342 | enum radeon_pm_mode pm_mode; |
| 341 | - void (*reinit_func)(struct radeonfb_info *rinfo); | |
| 343 | + reinit_function_ptr reinit_func; | |
| 342 | 344 | |
| 343 | 345 | /* Lock on register access */ |
| 344 | 346 | spinlock_t reg_lock; |
| ... | ... | @@ -600,7 +602,7 @@ |
| 600 | 602 | /* PM Functions */ |
| 601 | 603 | extern int radeonfb_pci_suspend(struct pci_dev *pdev, pm_message_t state); |
| 602 | 604 | extern int radeonfb_pci_resume(struct pci_dev *pdev); |
| 603 | -extern void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk); | |
| 605 | +extern void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk, int ignore_devlist, int force_sleep); | |
| 604 | 606 | extern void radeonfb_pm_exit(struct radeonfb_info *rinfo); |
| 605 | 607 | |
| 606 | 608 | /* Monitor probe functions */ |