Commit 994aad25 authored by Volker Braun's avatar Volker Braun Committed by Linus Torvalds

[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: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 256154fb
...@@ -266,6 +266,8 @@ static int force_measure_pll = 0; ...@@ -266,6 +266,8 @@ static int force_measure_pll = 0;
#ifdef CONFIG_MTRR #ifdef CONFIG_MTRR
static int nomtrr = 0; static int nomtrr = 0;
#endif #endif
static int force_sleep;
static int ignore_devlist;
/* /*
* prototypes * prototypes
...@@ -2327,9 +2329,9 @@ static int __devinit radeonfb_pci_register (struct pci_dev *pdev, ...@@ -2327,9 +2329,9 @@ static int __devinit radeonfb_pci_register (struct pci_dev *pdev,
/* -2 is special: means ON on mobility chips and do not /* -2 is special: means ON on mobility chips and do not
* change on others * change on others
*/ */
radeonfb_pm_init(rinfo, rinfo->is_mobility ? 1 : -1); radeonfb_pm_init(rinfo, rinfo->is_mobility ? 1 : -1, ignore_devlist, force_sleep);
} else } else
radeonfb_pm_init(rinfo, default_dynclk); radeonfb_pm_init(rinfo, default_dynclk, ignore_devlist, force_sleep);
pci_set_drvdata(pdev, info); pci_set_drvdata(pdev, info);
...@@ -2477,6 +2479,12 @@ static int __init radeonfb_setup (char *options) ...@@ -2477,6 +2479,12 @@ static int __init radeonfb_setup (char *options)
force_measure_pll = 1; force_measure_pll = 1;
} else if (!strncmp(this_opt, "ignore_edid", 11)) { } else if (!strncmp(this_opt, "ignore_edid", 11)) {
ignore_edid = 1; ignore_edid = 1;
#if defined(CONFIG_PM) && defined(CONFIG_X86)
} else if (!strncmp(this_opt, "force_sleep", 11)) {
force_sleep = 1;
} else if (!strncmp(this_opt, "ignore_devlist", 14)) {
ignore_devlist = 1;
#endif
} else } else
mode_option = this_opt; mode_option = this_opt;
} }
...@@ -2532,3 +2540,9 @@ module_param(panel_yres, int, 0); ...@@ -2532,3 +2540,9 @@ module_param(panel_yres, int, 0);
MODULE_PARM_DESC(panel_yres, "int: set panel yres"); MODULE_PARM_DESC(panel_yres, "int: set panel yres");
module_param(mode_option, charp, 0); module_param(mode_option, charp, 0);
MODULE_PARM_DESC(mode_option, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); MODULE_PARM_DESC(mode_option, "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" ");
#if defined(CONFIG_PM) && defined(CONFIG_X86)
module_param(force_sleep, bool, 0);
MODULE_PARM_DESC(force_sleep, "bool: force D2 sleep mode on all hardware");
module_param(ignore_devlist, bool, 0);
MODULE_PARM_DESC(ignore_devlist, "bool: ignore workarounds for bugs in specific laptops");
#endif
...@@ -27,6 +27,99 @@ ...@@ -27,6 +27,99 @@
#include "ati_ids.h" #include "ati_ids.h"
static void radeon_reinitialize_M10(struct radeonfb_info *rinfo);
/*
* Workarounds for bugs in PC laptops:
* - enable D2 sleep in some IBM Thinkpads
* - special case for Samsung P35
*
* Whitelist by subsystem vendor/device because
* its the subsystem vendor's fault!
*/
#if defined(CONFIG_PM) && defined(CONFIG_X86)
struct radeon_device_id {
const char *ident; /* (arbitrary) Name */
const unsigned short subsystem_vendor; /* Subsystem Vendor ID */
const unsigned short subsystem_device; /* Subsystem Device ID */
const enum radeon_pm_mode pm_mode_modifier; /* modify pm_mode */
const reinit_function_ptr new_reinit_func; /* changed reinit_func */
};
#define BUGFIX(model, sv, sd, pm, fn) { \
.ident = model, \
.subsystem_vendor = sv, \
.subsystem_device = sd, \
.pm_mode_modifier = pm, \
.new_reinit_func = fn \
}
static struct radeon_device_id radeon_workaround_list[] = {
BUGFIX("IBM Thinkpad R32",
PCI_VENDOR_ID_IBM, 0x1905,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad R40",
PCI_VENDOR_ID_IBM, 0x0526,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad R40",
PCI_VENDOR_ID_IBM, 0x0527,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad R50/R51/T40/T41",
PCI_VENDOR_ID_IBM, 0x0531,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad R51/T40/T41/T42",
PCI_VENDOR_ID_IBM, 0x0530,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad T30",
PCI_VENDOR_ID_IBM, 0x0517,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad T40p",
PCI_VENDOR_ID_IBM, 0x054d,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad T42",
PCI_VENDOR_ID_IBM, 0x0550,
radeon_pm_d2, NULL),
BUGFIX("IBM Thinkpad X31/X32",
PCI_VENDOR_ID_IBM, 0x052f,
radeon_pm_d2, NULL),
BUGFIX("Samsung P35",
PCI_VENDOR_ID_SAMSUNG, 0xc00c,
radeon_pm_off, radeon_reinitialize_M10),
{ .ident = NULL }
};
static int radeon_apply_workarounds(struct radeonfb_info *rinfo)
{
struct radeon_device_id *id;
for (id = radeon_workaround_list; id->ident != NULL; id++ )
if ((id->subsystem_vendor == rinfo->pdev->subsystem_vendor ) &&
(id->subsystem_device == rinfo->pdev->subsystem_device )) {
/* we found a device that requires workaround */
printk(KERN_DEBUG "radeonfb: %s detected"
", enabling workaround\n", id->ident);
rinfo->pm_mode |= id->pm_mode_modifier;
if (id->new_reinit_func != NULL)
rinfo->reinit_func = id->new_reinit_func;
return 1;
}
return 0; /* not found */
}
#else /* defined(CONFIG_PM) && defined(CONFIG_X86) */
static inline int radeon_apply_workarounds(struct radeonfb_info *rinfo)
{
return 0;
}
#endif /* defined(CONFIG_PM) && defined(CONFIG_X86) */
static void radeon_pm_disable_dynamic_mode(struct radeonfb_info *rinfo) static void radeon_pm_disable_dynamic_mode(struct radeonfb_info *rinfo)
{ {
u32 tmp; u32 tmp;
...@@ -853,10 +946,16 @@ static void radeon_pm_setup_for_suspend(struct radeonfb_info *rinfo) ...@@ -853,10 +946,16 @@ static void radeon_pm_setup_for_suspend(struct radeonfb_info *rinfo)
tmp = INPLL( pllMCLK_MISC) | MCLK_MISC__EN_MCLK_TRISTATE_IN_SUSPEND; tmp = INPLL( pllMCLK_MISC) | MCLK_MISC__EN_MCLK_TRISTATE_IN_SUSPEND;
OUTPLL( pllMCLK_MISC, tmp); OUTPLL( pllMCLK_MISC, tmp);
/* BUS_CNTL1__MOBILE_PLATORM_SEL setting is northbridge chipset
* and radeon chip dependent. Thus we only enable it on Mac for
* now (until we get more info on how to compute the correct
* value for various X86 bridges).
*/
#ifdef CONFIG_PPC_PMAC
if (machine_is(powermac)) {
/* AGP PLL control */ /* AGP PLL control */
if (rinfo->family <= CHIP_FAMILY_RV280) { if (rinfo->family <= CHIP_FAMILY_RV280) {
OUTREG(BUS_CNTL1, INREG(BUS_CNTL1) | BUS_CNTL1__AGPCLK_VALID); OUTREG(BUS_CNTL1, INREG(BUS_CNTL1) | BUS_CNTL1__AGPCLK_VALID);
OUTREG(BUS_CNTL1, OUTREG(BUS_CNTL1,
(INREG(BUS_CNTL1) & ~BUS_CNTL1__MOBILE_PLATFORM_SEL_MASK) (INREG(BUS_CNTL1) & ~BUS_CNTL1__MOBILE_PLATFORM_SEL_MASK)
| (2<<BUS_CNTL1__MOBILE_PLATFORM_SEL__SHIFT)); // 440BX | (2<<BUS_CNTL1__MOBILE_PLATFORM_SEL__SHIFT)); // 440BX
...@@ -864,6 +963,8 @@ static void radeon_pm_setup_for_suspend(struct radeonfb_info *rinfo) ...@@ -864,6 +963,8 @@ static void radeon_pm_setup_for_suspend(struct radeonfb_info *rinfo)
OUTREG(BUS_CNTL1, INREG(BUS_CNTL1)); OUTREG(BUS_CNTL1, INREG(BUS_CNTL1));
OUTREG(BUS_CNTL1, (INREG(BUS_CNTL1) & ~0x4000) | 0x8000); OUTREG(BUS_CNTL1, (INREG(BUS_CNTL1) & ~0x4000) | 0x8000);
} }
}
#endif
OUTREG(CRTC_OFFSET_CNTL, (INREG(CRTC_OFFSET_CNTL) OUTREG(CRTC_OFFSET_CNTL, (INREG(CRTC_OFFSET_CNTL)
& ~CRTC_OFFSET_CNTL__CRTC_STEREO_SYNC_OUT_EN)); & ~CRTC_OFFSET_CNTL__CRTC_STEREO_SYNC_OUT_EN));
...@@ -2713,7 +2814,7 @@ static void radeonfb_early_resume(void *data) ...@@ -2713,7 +2814,7 @@ static void radeonfb_early_resume(void *data)
#endif /* CONFIG_PM */ #endif /* CONFIG_PM */
void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk) void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk, int ignore_devlist, int force_sleep)
{ {
/* Find PM registers in config space if any*/ /* Find PM registers in config space if any*/
rinfo->pm_reg = pci_find_capability(rinfo->pdev, PCI_CAP_ID_PM); rinfo->pm_reg = pci_find_capability(rinfo->pdev, PCI_CAP_ID_PM);
...@@ -2729,22 +2830,13 @@ void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk) ...@@ -2729,22 +2830,13 @@ void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk)
} }
#if defined(CONFIG_PM) #if defined(CONFIG_PM)
#if defined(CONFIG_PPC_PMAC)
/* Check if we can power manage on suspend/resume. We can do /* Check if we can power manage on suspend/resume. We can do
* D2 on M6, M7 and M9, and we can resume from D3 cold a few other * D2 on M6, M7 and M9, and we can resume from D3 cold a few other
* "Mac" cards, but that's all. We need more infos about what the * "Mac" cards, but that's all. We need more infos about what the
* BIOS does tho. Right now, all this PM stuff is pmac-only for that * BIOS does tho. Right now, all this PM stuff is pmac-only for that
* reason. --BenH * reason. --BenH
*/ */
/* Special case for Samsung P35 laptops
*/
if ((rinfo->pdev->vendor == PCI_VENDOR_ID_ATI) &&
(rinfo->pdev->device == PCI_CHIP_RV350_NP) &&
(rinfo->pdev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG) &&
(rinfo->pdev->subsystem_device == 0xc00c)) {
rinfo->reinit_func = radeon_reinitialize_M10;
rinfo->pm_mode |= radeon_pm_off;
}
#if defined(CONFIG_PPC_PMAC)
if (machine_is(powermac) && rinfo->of_node) { if (machine_is(powermac) && rinfo->of_node) {
if (rinfo->is_mobility && rinfo->pm_reg && if (rinfo->is_mobility && rinfo->pm_reg &&
rinfo->family <= CHIP_FAMILY_RV250) rinfo->family <= CHIP_FAMILY_RV250)
...@@ -2790,6 +2882,18 @@ void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk) ...@@ -2790,6 +2882,18 @@ void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk)
} }
#endif /* defined(CONFIG_PPC_PMAC) */ #endif /* defined(CONFIG_PPC_PMAC) */
#endif /* defined(CONFIG_PM) */ #endif /* defined(CONFIG_PM) */
if (ignore_devlist)
printk(KERN_DEBUG
"radeonfb: skipping test for device workarounds\n");
else
radeon_apply_workarounds(rinfo);
if (force_sleep) {
printk(KERN_DEBUG
"radeonfb: forcefully enabling D2 sleep mode\n");
rinfo->pm_mode |= radeon_pm_d2;
}
} }
void radeonfb_pm_exit(struct radeonfb_info *rinfo) void radeonfb_pm_exit(struct radeonfb_info *rinfo)
......
...@@ -273,6 +273,8 @@ enum radeon_pm_mode { ...@@ -273,6 +273,8 @@ enum radeon_pm_mode {
radeon_pm_off = 0x00000002, /* Can resume from D3 cold */ radeon_pm_off = 0x00000002, /* Can resume from D3 cold */
}; };
typedef void (*reinit_function_ptr)(struct radeonfb_info *rinfo);
struct radeonfb_info { struct radeonfb_info {
struct fb_info *info; struct fb_info *info;
...@@ -338,7 +340,7 @@ struct radeonfb_info { ...@@ -338,7 +340,7 @@ struct radeonfb_info {
int dynclk; int dynclk;
int no_schedule; int no_schedule;
enum radeon_pm_mode pm_mode; enum radeon_pm_mode pm_mode;
void (*reinit_func)(struct radeonfb_info *rinfo); reinit_function_ptr reinit_func;
/* Lock on register access */ /* Lock on register access */
spinlock_t reg_lock; spinlock_t reg_lock;
...@@ -600,7 +602,7 @@ extern int radeon_probe_i2c_connector(struct radeonfb_info *rinfo, int conn, u8 ...@@ -600,7 +602,7 @@ extern int radeon_probe_i2c_connector(struct radeonfb_info *rinfo, int conn, u8
/* PM Functions */ /* PM Functions */
extern int radeonfb_pci_suspend(struct pci_dev *pdev, pm_message_t state); extern int radeonfb_pci_suspend(struct pci_dev *pdev, pm_message_t state);
extern int radeonfb_pci_resume(struct pci_dev *pdev); extern int radeonfb_pci_resume(struct pci_dev *pdev);
extern void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk); extern void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk, int ignore_devlist, int force_sleep);
extern void radeonfb_pm_exit(struct radeonfb_info *rinfo); extern void radeonfb_pm_exit(struct radeonfb_info *rinfo);
/* Monitor probe functions */ /* Monitor probe functions */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment