Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1750900AbVKDUQM (ORCPT ); Fri, 4 Nov 2005 15:16:12 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1750901AbVKDUQL (ORCPT ); Fri, 4 Nov 2005 15:16:11 -0500 Received: from bender.bawue.de ([193.7.176.20]:50121 "EHLO bender.bawue.de") by vger.kernel.org with ESMTP id S1750900AbVKDUQK (ORCPT ); Fri, 4 Nov 2005 15:16:10 -0500 Date: Fri, 4 Nov 2005 21:16:03 +0100 From: Joerg Sommrey To: Linux kernel mailing list Subject: [PATCH 2/2] amd76x_pm: C2 powersaving for AMD K7 Message-ID: <20051104201603.GB24914@sommrey.de> Mail-Followup-To: Joerg Sommrey , Linux kernel mailing list References: <20051104201213.GA24914@sommrey.de> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20051104201213.GA24914@sommrey.de> User-Agent: Mutt/1.5.10i Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 12236 Lines: 522 This patch adds some experimental features to amd76x_pm, namely C3, NTH and POS support. These features are not enabled by default and are intended for kernel hackers only. This patch can also be found at http://www.sommrey.de/amd76x_pm/amd76x_pm-2.6.14-1-extra.patch Signed-off-by: Joerg Sommrey --- linux-2.6.14-jo/drivers/acpi/amd76x_pm.c.orig 2005-11-04 20:36:19.000000000 +0100 +++ linux-2.6.14-jo/drivers/acpi/amd76x_pm.c 2005-11-04 20:41:47.000000000 +0100 @@ -59,12 +59,17 @@ #include -#define VERSION "20050906" +#define VERSION "20050906-extra" +// #define AMD76X_NTH 1 +// #define AMD76X_POS 1 +// #define AMD76X_C3 1 // #define AMD76X_LOG_C1 1 extern void default_idle(void); +#ifndef AMD76X_NTH static void amd76x_smp_idle(void); +#endif static int amd76x_pm_main(void); static unsigned long lazy_idle = 0; @@ -72,8 +77,10 @@ static unsigned long watch_int = 0; static unsigned long min_C1 = AMD76X_MIN_C1; +#ifndef AMD76X_NTH static int show_watch_irqs(char *, struct kernel_param *); static int set_watch_irqs(const char *, struct kernel_param *); +#endif module_param(lazy_idle, long, S_IRUGO | S_IWUSR); @@ -84,6 +91,7 @@ MODULE_PARM_DESC(spin_idle, "\tnumber of spin cycles to wait for other CPUs to become idle"); +#ifndef AMD76X_NTH module_param(watch_int, long, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(watch_int, "\twatch interval (in milliseconds) for interrupts"); @@ -94,6 +102,7 @@ "\tlist of irqs (and optional their limit per second) that " "cause fallback to C1 mode. " "Syntax: irq0[:limit0],irq1[:limit1],..."); +#endif module_param(min_C1, long, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(min_C1, @@ -110,6 +119,12 @@ struct PM_cfg { unsigned int status_reg; unsigned int C2_reg; +#ifdef AMD76X_C3 + unsigned int C3_reg; +#endif +#ifdef AMD76X_NTH + unsigned int NTH_reg; +#endif unsigned int slp_reg; unsigned int resume_reg; void (*orig_idle) (void); @@ -118,21 +133,34 @@ static struct PM_cfg amd76x_pm_cfg __cacheline_aligned_in_smp; +#ifndef AMD76X_NTH struct idle_stat { atomic_t num_idle; +#ifdef AMD76X_C3 + atomic_t num_C2; +#endif }; static struct idle_stat amd76x_stat __cacheline_aligned_in_smp = { .num_idle = ATOMIC_INIT(0), +#ifdef AMD76X_C3 + .num_C2 = ATOMIC_INIT(0) +#endif }; struct cpu_stat { int idle_count; int C2_cnt; +#ifdef AMD76X_C3 + int C3_cnt; + int C2_active; +#else int _fill[2]; +#endif }; static struct cpu_stat prs[NR_CPUS] __cacheline_aligned_in_smp; +#endif struct watch_item { int irq; @@ -216,7 +244,13 @@ regdword &= 0xff80; amd76x_pm_cfg.status_reg = (regdword + 0x00); amd76x_pm_cfg.slp_reg = (regdword + 0x04); +#ifdef AMD76X_NTH + amd76x_pm_cfg.NTH_reg = (regdword + 0x10); +#endif amd76x_pm_cfg.C2_reg = (regdword + 0x14); +#ifdef AMD76X_C3 + amd76x_pm_cfg.C3_reg = (regdword + 0x15); +#endif amd76x_pm_cfg.resume_reg = (regdword + 0x16); /* N/A for 768 */ } @@ -261,6 +295,52 @@ regdword &= ~((STPCLK_EN | CPUSLP_EN) << C2_REGS); pci_write_config_dword(pdev_sb, 0x50, regdword); } +#ifdef AMD76X_C3 +/* + * Untested C3 idle support for AMD-766. + */ +static void +config_amd766_C3(int enable) +{ + unsigned int regdword; + + /* Set C3 options in C3A50, page 63 in AMD-766 doc */ + pci_read_config_dword(pdev_sb, 0x50, ®dword); + if(enable) { + regdword &= ~((DCSTOP_EN | PCISTP_EN | SUSPND_EN | CPURST_EN) + << C3_REGS); + regdword |= (STPCLK_EN /* ~ 20 Watt savings max */ + | CPUSLP_EN /* Additional ~ 70 Watts max! */ + | CPUSTP_EN) /* yet more savings! */ + << C3_REGS; + } + else + regdword &= ~((STPCLK_EN | CPUSLP_EN | CPUSTP_EN) << C3_REGS); + pci_write_config_dword(pdev_sb, 0x50, regdword); +} +#endif + + +#ifdef AMD76X_POS +static void +config_amd766_POS(int enable) +{ + unsigned int regdword; + + /* Set C3 options in C3A50, page 63 in AMD-766 doc */ + pci_read_config_dword(pdev_sb, 0x50, ®dword); + if(enable) { + regdword &= ~((ZZ_CACHE_EN | CPURST_EN) << POS_REGS); + regdword |= ((DCSTOP_EN | STPCLK_EN | CPUSTP_EN | PCISTP_EN | + CPUSLP_EN | SUSPND_EN) << POS_REGS); + } + else + regdword ^= (0xff << POS_REGS); + pci_write_config_dword(pdev_sb, 0x50, regdword); +} +#endif + + /* * Configures the 765 & 766 southbridges. @@ -271,6 +351,13 @@ amd76x_get_PM(); config_PMIO_amd76x(1, 1); config_amd766_C2(enable); +#ifdef AMD76X_C3 + config_amd766_C3(enable); +#endif + +#ifdef AMD76X_POS + config_amd766_POS(enable); +#endif return 0; } @@ -293,6 +380,69 @@ pci_write_config_byte(pdev_sb, 0x4F, regbyte); } +#ifdef AMD76X_C3 +/* + * C3 idle support for AMD-768. The idle loop would need some extra + * handling for C3, but it would make more sense for ACPI to handle CX level + * transitions like it is supposed to. Unfortunately ACPI doesn't do CX + * levels on SMP systems yet. + */ +static void +config_amd768_C3(int enable) +{ + unsigned char regbyte; + + /* Set C3 options in DevB:3x4F, page 100 in AMD-768 doc */ + pci_read_config_byte(pdev_sb, 0x4F, ®byte); + if(enable) + regbyte |= (C3EN /* | ZZ_C3EN | CSLP_C3EN | CSTP_C3EN */); + else + regbyte ^= C3EN; + pci_write_config_byte(pdev_sb, 0x4F, regbyte); +} +#endif + +#ifdef AMD76X_POS +/* + * Untested Power On Suspend support for AMD-768. This should also be handled + * by ACPI. + */ +static void +config_amd768_POS(int enable) +{ + unsigned int regdword; + + /* Set POS options in DevB:3x50, page 101 in AMD-768 doc */ + pci_read_config_dword(pdev_sb, 0x50, ®dword); + if(enable) + regdword |= (POSEN | CSTP | PSTP | ASTP | DCSTP | CSLP | SUSP); + else + regdword ^= POSEN; + pci_write_config_dword(pdev_sb, 0x50, regdword); +} +#endif + + +#ifdef AMD76X_NTH +/* + * Normal Throttling support for AMD-768. There are several settings + * that can be set depending on how long you want some of the delays to be. + * I'm not sure if this is even neccessary at all as the 766 doesn't need this. + */ +static void +config_amd768_NTH(int enable, int ntper, int thminen) +{ + unsigned char regbyte; + + /* DevB:3x40, pg 93 of 768 doc */ + pci_read_config_byte(pdev_sb, 0x40, ®byte); + /* Is it neccessary to use THMINEN at ANY time? */ + regbyte |= (NTPER(ntper) | THMINEN(thminen)); + pci_write_config_byte(pdev_sb, 0x40, regbyte); +} +#endif + + /* * Configures the 768 southbridge to support idle calls, and gets * the processor idle call register location. @@ -305,10 +455,64 @@ config_PMIO_amd76x(0, 1); config_amd768_C2(enable); +#ifdef AMD76X_C3 + config_amd768_C3(enable); +#endif +#ifdef AMD76X_POS + config_amd768_POS(enable); +#endif +#ifdef AMD76X_NTH + config_amd768_NTH(enable, 1, 2); +#endif return 0; } +#ifdef AMD76X_NTH +/* + * Activate normal throttling via its ACPI register (P_CNT). + */ +static void +activate_amd76x_NTH(int enable, int ratio) +{ + unsigned int regdword; + + /* PM10, pg 110 of 768 doc, pg 70 of 766 doc */ + regdword=inl(amd76x_pm_cfg.NTH_reg); + if(enable) + regdword |= (NTH_EN | NTH_RATIO(ratio)); + else + regdword ^= NTH_EN; + outl(regdword, amd76x_pm_cfg.NTH_reg); +} +#endif + +#ifdef AMD76X_POS +/* + * Activate sleep state via its ACPI register (PM1_CNT). + */ +static void +activate_amd76x_SLP(int type) +{ + unsigned short regshort; + + /* PM04, pg 109 of 768 doc, pg 69 of 766 doc */ + regshort=inw(amd76x_pm_cfg.slp_reg); + regshort |= (SLP_EN | SLP_TYP(type)) ; + outw(regshort, amd76x_pm_cfg.slp_reg); +} +#endif /* AMD76X_POS */ +#ifdef AMD76X_POS +/* + * Wrapper function to activate POS sleep state. + */ +static void +activate_amd76x_POS(void) +{ + activate_amd76x_SLP(1); +} +#endif + /* * Idle loop for single processor systems */ @@ -321,6 +525,7 @@ amd76x_pm_cfg.orig_idle(); } +#ifndef AMD76X_NTH /* * Idle loop for SMP systems * @@ -367,9 +572,30 @@ smp_mb(); if (unlikely(atomic_read(&amd76x_stat.num_idle) == num_online)) { + #ifdef AMD76X_C3 + /* Enter C3 if all CPUs are ready for C3. + * Enter C2 otherwise. + */ + if (unlikely(atomic_read(&amd76x_stat.num_C2) == + num_online)) { + /* Invoke C3 */ + prs[cpu].C3_cnt++; + inb(amd76x_pm_cfg.C3_reg); + } else { + /* Invoke C2 */ + prs[cpu].C2_cnt++; + inb(amd76x_pm_cfg.C2_reg); + /* Now we are ready for C3 */ + if (!prs[cpu].C2_active) { + prs[cpu].C2_active = 1; + atomic_inc(&amd76x_stat.num_C2); + } + } + #else /* Invoke C2 */ prs[cpu].C2_cnt++; inb(amd76x_pm_cfg.C2_reg); + #endif smp_mb(); atomic_dec(&amd76x_stat.num_idle); prs[cpu].idle_count = 0; @@ -380,13 +606,24 @@ smp_mb(); atomic_dec(&amd76x_stat.num_idle); +#ifdef AMD76X_C3 + /* We haven't been in C2/C3 if we are here. We are no longer + * ready for C3. + */ + if (prs[cpu].C2_active) { + prs[cpu].C2_active = 0; + atomic_dec(&amd76x_stat.num_C2); + } +#endif prs[cpu].idle_count = 0; local_irq_enable(); } +#endif /* * sysfs support, RW */ +#ifndef AMD76X_NTH static int show_watch_irqs(char *buf, struct kernel_param *kp) { @@ -460,8 +697,26 @@ return sprintf(buf,"%u\n", C2_cnt); } +#ifdef AMD76X_C3 +static ssize_t +show_C3_cnt (struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned int C3_cnt = 0; + int i; + for_each_online_cpu(i) + C3_cnt += prs[i].C3_cnt; + return sprintf(buf,"%u\n", C3_cnt); +} +#endif + static DEVICE_ATTR(C2_cnt, S_IRUGO, show_C2_cnt, NULL); +#ifdef AMD76X_C3 +static DEVICE_ATTR(C3_cnt, S_IRUGO, + show_C3_cnt, NULL); +#endif +#endif static void setup_watch(void) @@ -519,7 +774,9 @@ static int amd76x_pm_main(void) { +#ifndef AMD76X_NTH int i; +#endif amd76x_pm_cfg.orig_idle = 0; if(lazy_idle == 0) @@ -572,13 +829,16 @@ switch (pdev_nb->device) { case PCI_DEVICE_ID_AMD_FE_GATE_700C: /* AMD-762 */ config_amd762(1); +#ifndef AMD76X_NTH amd76x_pm_cfg.curr_idle = amd76x_smp_idle; +#endif break; default: printk(KERN_ERR "amd76x_pm: No northbridge to initialize\n"); break; } +#ifndef AMD76X_NTH if(num_online_cpus() == 1) { amd76x_pm_cfg.curr_idle = amd76x_up_idle; printk(KERN_ERR "amd76x_pm: UP machine detected. ACPI is your friend.\n"); @@ -591,6 +851,10 @@ for (i = 0; i < NR_CPUS; i++) { prs[i].idle_count = 0; prs[i].C2_cnt = 0; + #ifdef AMD76X_C3 + prs[i].C3_cnt = 0; + prs[i].C2_active = 0; + #endif } amd76x_pm_cfg.orig_idle = pm_idle; @@ -600,7 +864,20 @@ /* sysfs */ device_create_file(&pdev_nb->dev, &dev_attr_C2_cnt); +#ifdef AMD76X_C3 + device_create_file(&pdev_nb->dev, &dev_attr_C3_cnt); +#endif +#endif +#ifdef AMD76X_NTH + /* Turn NTH on with maxium throttling for testing. */ + activate_amd76x_NTH(1, 1); +#endif + +#ifdef AMD76X_POS + /* Testing here only. */ + activate_amd76x_POS(); +#endif setup_watch(); return 0; @@ -618,9 +895,15 @@ static void __exit amd76x_pm_cleanup(void) { +#ifndef AMD76X_NTH int i; unsigned int C2_cnt = 0; +#ifdef AMD76X_C3 + unsigned int C3_cnt = 0; +#endif +#endif +#ifndef AMD76X_NTH pm_idle = amd76x_pm_cfg.orig_idle; cpu_idle_wait(); @@ -629,11 +912,28 @@ /* This isn't really needed. */ for_each_online_cpu(i) { C2_cnt += prs[i].C2_cnt; +#ifdef AMD76X_C3 + C3_cnt += prs[i].C3_cnt; +#endif } +#ifdef AMD76X_C3 + printk(KERN_INFO "amd76x_pm: %u C2 calls, %u C3 calls\n", + C2_cnt, C3_cnt); +#else printk(KERN_INFO "amd76x_pm: %u C2 calls\n", C2_cnt); +#endif /* remove sysfs */ device_remove_file(&pdev_nb->dev, &dev_attr_C2_cnt); +#ifdef AMD76X_C3 + device_remove_file(&pdev_nb->dev, &dev_attr_C3_cnt); +#endif +#endif + +#ifdef AMD76X_NTH + /* Turn NTH off*/ + activate_amd76x_NTH(0, 0); +#endif irqwatch.schedule = 0; flush_scheduled_work(); - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/