/* * Processor idle mode module for AMD SMP systems * * Copyright (C) 2002 Tony Lindgren * * Using this module saves about 70 - 90W of energy in the idle mode compared * to the default idle mode. Waking up from the idle mode is fast to keep the * system response time good. Currently no CPU load calculation is done, the * system exits the idle mode if the idle function runs twice on the same * processor in a row. This only works on SMP systems, but maybe the idle mode * enabling can be integrated to ACPI to provide C2 mode at some point. * * NOTE: Currently there's a bug somewhere where the reading the * P_LVL2 for the first time causes the system to sleep instead of * idling. This means that you need to hit the power button once to * wake the system after loading the module for the first time after * reboot. After that the system idles as supposed. * * * Influenced by Vcool, and LVCool. Rewrote everything from scratch to * use the PCI features in Linux, and to support SMP systems. * * Currently only tested on TYAN S2460 (760MP) system. Adding support * for other Athlon SMP or single processor systems should be easy if * desired. * * This software is licensed under GNU General Public License Version 2 * as specified in file COPYING in the Linux kernel source tree main * directory. Compile command: gcc -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes \ -Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common \ -pipe -mpreferred-stack-boundary=2 -march=athlon -DMODULE -DMODVERSIONS \ -include /usr/src/linux/include/linux/modversions.h -c amd-idle.c */ #include #include #include #include #include extern void default_idle(void); static void amd_smp_idle(void); static int amd_idle_main(void); static int __devinit amd_nb_init(struct pci_dev *pdev, const struct pci_device_id *ent); static void amd_nb_remove(struct pci_dev *pdev); static int __devinit amd_sb_init(struct pci_dev *pdev, const struct pci_device_id *ent); static void amd_sb_remove(struct pci_dev *pdev); #define DEBUG 1 #define VERSION "20020702" #define AMD762 0x700c #define AMD765_766 0x7413 #define CUR_PR smp_processor_id() struct pci_dev *pdev_nb; struct pci_dev *pdev_sb; struct idle_cfg { unsigned int status_reg; unsigned int idle_reg; unsigned int slp_reg; unsigned int resume_reg; void (*orig_idle) (void); void (*curr_idle) (void); int last_pr; }; static struct idle_cfg amd_idle_cfg; struct cpu_idle_state { int idle; int count; }; static struct cpu_idle_state prs[2]; static struct pci_device_id amd_nb_tbl[] __devinitdata = { {PCI_VENDOR_ID_AMD, AMD762, PCI_ANY_ID, PCI_ANY_ID,}, {0,} }; static struct pci_device_id amd_sb_tbl[] __devinitdata = { {PCI_VENDOR_ID_AMD, AMD765_766, PCI_ANY_ID, PCI_ANY_ID,}, {0,} }; static struct pci_driver amd_nb_driver = { name:"amd-smp-idle-nb", id_table:amd_nb_tbl, probe:amd_nb_init, remove:__devexit_p(amd_nb_remove), }; static struct pci_driver amd_sb_driver = { name:"amd-smp-idle-sb", id_table:amd_sb_tbl, probe:amd_sb_init, remove:__devexit_p(amd_sb_remove), }; static int __devinit amd_nb_init(struct pci_dev *pdev, const struct pci_device_id *ent) { pdev_nb = pdev; printk(KERN_INFO "amd-smp-idle: Initializing northbridge %s\n", pdev_nb->name); return 0; } static void __devexit amd_nb_remove(struct pci_dev *pdev) { } static int __devinit amd_sb_init(struct pci_dev *pdev, const struct pci_device_id *ent) { pdev_sb = pdev; printk(KERN_INFO "amd-smp-idle: Initializing southbridge %s\n", pdev_sb->name); return 0; } static void __devexit amd_sb_remove(struct pci_dev *pdev) { } /* * Configures the southbridge to support idle calls, and gets * the processor idle call register location. */ static int sb_idle_amd_766(int enable) { unsigned int regdword; unsigned short regshort; unsigned char regbyte; #define DCSTOP_EN (1 << 1) #define STPCLK_EN (1 << 2) #define CPUSTP_EN (1 << 3) #define PCISTP_EN (1 << 4) #define CPUSLP_EN (1 << 5) #define SUSPND_EN (1 << 6) #define C2_REGS 0 #define C3_REGS 8 #define POS_REGS 16 /* Get the address for pm status, P_LVL2, etc */ pci_read_config_dword(pdev_sb, 0x58, ®dword); regdword &= 0xff80; amd_idle_cfg.status_reg = regdword + 0x00; amd_idle_cfg.slp_reg = regdword + 0x04; amd_idle_cfg.idle_reg = regdword + 0x14; amd_idle_cfg.resume_reg = regdword + 0x16; /* Set C2 options in C3A50, page 63 in AMD-766 doc */ pci_read_config_dword(pdev_sb, 0x50, ®dword); regdword &= ~((DCSTOP_EN | CPUSTP_EN | PCISTP_EN | SUSPND_EN) << C2_REGS); regdword |= (STPCLK_EN << C2_REGS); /* ~ 20 Watt savings max */ regdword |= (CPUSLP_EN << C2_REGS); /* Additional ~ 70 Watts max! */ pci_write_config_dword(pdev_sb, 0x50, regdword); /* Clear W4SG, set STPGNT and PMIOEN at C3A41 */ pci_read_config_byte(pdev_sb, 0x41, ®byte); regbyte &= ~(1 << 0); regbyte |= ((1 << 1) | (1 << 7)); pci_write_config_byte(pdev_sb, 0x41, regbyte); return 0; } /* * Configures the northbridge to support idle calls */ static int nb_idle_amd_762(int enable) { unsigned int regdword; /* Enable STPGNT in BIU Status/Control for cpu0 */ pci_read_config_dword(pdev_nb, 0x60, ®dword); regdword |= (1 << 17); pci_write_config_dword(pdev_nb, 0x60, regdword); /* Enable STPGNT in BIU Status/Control for cpu1 */ pci_read_config_dword(pdev_nb, 0x68, ®dword); regdword |= (1 << 17); pci_write_config_dword(pdev_nb, 0x68, regdword); /* DRAM refresh enable */ pci_read_config_dword(pdev_nb, 0x58, ®dword); regdword &= ~(1 << 19); pci_write_config_dword(pdev_nb, 0x58, regdword); /* Self refresh enable */ pci_read_config_dword(pdev_nb, 0x70, ®dword); regdword |= (1 << 18); pci_write_config_dword(pdev_nb, 0x70, regdword); return 0; } /* * Idle loop for single processor systems */ void amd_idle(void) { // FIXME: Optionally add non-smp idle loop here } /* * Idle loop for SMP systems, supports currently only 2 processors. */ static void amd_smp_idle(void) { #define LAZY_IDLE_DELAY 800 /* 0: Best savings, 3000: More responsive */ /* * Exit idle mode immediately if the CPU does not change. * Usually that means that we have some load on another CPU. */ if (prs[0].idle && prs[1].idle && amd_idle_cfg.last_pr == CUR_PR) { prs[0].idle = 0; prs[1].idle = 0; amd_idle_cfg.last_pr = CUR_PR; return; } prs[CUR_PR].count++; /* Don't start the idle mode immediately */ if (prs[CUR_PR].count >= LAZY_IDLE_DELAY) { /* Put the current processor into idle mode */ prs[CUR_PR].idle = 1; /* Only idle if both processors are idle */ if (prs[0].idle && prs[1].idle) inb(amd_idle_cfg.idle_reg); prs[CUR_PR].count = 0; } amd_idle_cfg.last_pr = CUR_PR; } /* * Finds and initializes the bridges, and then sets the idle function */ static int amd_idle_main(void) { int found; /* Find northbridge */ found = pci_module_init(&amd_nb_driver); if (found < 0) { printk(KERN_ERR "amd-smp-idle: Could not find northbridge\n"); return 1; } /* Find southbridge */ found = pci_module_init(&amd_sb_driver); if (found < 0) { printk(KERN_ERR "amd-smp-idle: Could not find southbridge\n"); pci_unregister_driver(&amd_nb_driver); return 1; } /* Init southbridge */ switch (pdev_sb->device) { case AMD765_766: /* AMD-765 or 766 */ sb_idle_amd_766(1); break; default: printk(KERN_ERR "amd-smp-idle: No southbridge to initialize\n"); break; } /* Init northbridge and queue the new idle function */ switch (pdev_nb->device) { case AMD762: nb_idle_amd_762(1); amd_idle_cfg.curr_idle = amd_smp_idle; break; default: printk(KERN_ERR "amd-smp-idle: No northbridge to initialize\n"); break; } if (!amd_idle_cfg.curr_idle) { printk(KERN_ERR "amd-smp-idle: Idle function not changed\n"); return 1; } amd_idle_cfg.orig_idle = pm_idle; pm_idle = amd_idle_cfg.curr_idle; return 0; } static int __init amd_idle_init(void) { printk(KERN_INFO "amd-smp-idle: AMD processor idle module version %s\n", VERSION); return amd_idle_main(); } static void __exit amd_idle_cleanup(void) { pm_idle = amd_idle_cfg.orig_idle; /* * FIXME: We want to wait until all CPUs have set the new * idle function, otherwise we will oops. This may not be * the right way to do it, but seems to work. */ schedule(); mdelay(1000); pci_unregister_driver(&amd_nb_driver); pci_unregister_driver(&amd_sb_driver); } MODULE_LICENSE("GPL"); module_init(amd_idle_init); module_exit(amd_idle_cleanup);