Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

[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
Loading
Loading
Loading
Loading
+16 −2
Original line number Diff line number Diff line
@@ -266,6 +266,8 @@ static int force_measure_pll = 0;
#ifdef CONFIG_MTRR
static int nomtrr = 0;
#endif
static int force_sleep;
static int ignore_devlist;

/*
 * prototypes
@@ -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
		 * 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
		radeonfb_pm_init(rinfo, default_dynclk);
		radeonfb_pm_init(rinfo, default_dynclk, ignore_devlist, force_sleep);

	pci_set_drvdata(pdev, info);

@@ -2477,6 +2479,12 @@ static int __init radeonfb_setup (char *options)
			force_measure_pll = 1;
		} else if (!strncmp(this_opt, "ignore_edid", 11)) {
			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
			mode_option = this_opt;
	}
@@ -2532,3 +2540,9 @@ module_param(panel_yres, int, 0);
MODULE_PARM_DESC(panel_yres, "int: set panel yres");
module_param(mode_option, charp, 0);
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
+125 −21
Original line number Diff line number Diff line
@@ -27,6 +27,99 @@

#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)
{
	u32 tmp;
@@ -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;
	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 */
		if (rinfo->family <= CHIP_FAMILY_RV280) {
			OUTREG(BUS_CNTL1, INREG(BUS_CNTL1) |  BUS_CNTL1__AGPCLK_VALID);

			OUTREG(BUS_CNTL1,
			       (INREG(BUS_CNTL1) & ~BUS_CNTL1__MOBILE_PLATFORM_SEL_MASK)
			       | (2<<BUS_CNTL1__MOBILE_PLATFORM_SEL__SHIFT));	// 440BX
@@ -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) & ~0x4000) | 0x8000);
		}
	}
#endif

	OUTREG(CRTC_OFFSET_CNTL, (INREG(CRTC_OFFSET_CNTL)
				  & ~CRTC_OFFSET_CNTL__CRTC_STEREO_SYNC_OUT_EN));
@@ -2713,7 +2814,7 @@ static void radeonfb_early_resume(void *data)

#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*/
	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)
	}

#if defined(CONFIG_PM)
#if defined(CONFIG_PPC_PMAC)
	/* 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
	 * "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
	 * 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 (rinfo->is_mobility && rinfo->pm_reg &&
		    rinfo->family <= CHIP_FAMILY_RV250)
@@ -2790,6 +2882,18 @@ void radeonfb_pm_init(struct radeonfb_info *rinfo, int dynclk)
	}
#endif /* defined(CONFIG_PPC_PMAC) */
#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)
+4 −2
Original line number Diff line number Diff line
@@ -273,6 +273,8 @@ enum radeon_pm_mode {
	radeon_pm_off	= 0x00000002,	/* Can resume from D3 cold */
};

typedef void (*reinit_function_ptr)(struct radeonfb_info *rinfo);

struct radeonfb_info {
	struct fb_info		*info;

@@ -338,7 +340,7 @@ struct radeonfb_info {
	int			dynclk;
	int			no_schedule;
	enum radeon_pm_mode	pm_mode;
	void			(*reinit_func)(struct radeonfb_info *rinfo);
	reinit_function_ptr     reinit_func;

	/* Lock on register access */
	spinlock_t		reg_lock;
@@ -600,7 +602,7 @@ extern int radeon_probe_i2c_connector(struct radeonfb_info *rinfo, int conn, u8
/* PM Functions */
extern int radeonfb_pci_suspend(struct pci_dev *pdev, pm_message_t state);
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);

/* Monitor probe functions */