/*
 * ACPI style PM for SMP AMD-760MP(X) based systems.
 * Experimental stuff for normal throttling and power on suspend
 *
 * <Notes from jo>
 * This code was split off amd76x_pm.c by Joerg Sommrey <jo@sommrey.de>
 *
 * To use these features, you need to apply any appropriate
 * amd76x_pm-patch and replace drivers/acpi/amd76x_pm.c with this file.
 * Modify the #define's for NTH or POS and build the module.
 *
 * Warning: This is experimental code. Use it at your own risk.
 * I'm not even sure this module does anything useful after I separated
 * it into this file.
 * </Notes from jo>
 * 
 * Copyright (C) 2002	Johnathan Hicks <thetech@folkwolf.net>
 * 			Tony Lindgren <tony@atomide.com>
 *
 * This software is licensed under GNU General Public License Version 2
 * as specified in file COPYING in the Linux kernel source tree main
 * directory.
 */


#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/kernel.h>

#include <linux/amd76x_pm.h>

#define VERSION	"20060121-ts"

#define AMD76X_NTH 1
#define AMD76X_POS 1

extern void default_idle(void);
static int amd76x_pm_main(void);


MODULE_AUTHOR("Tony Lindgren, Johnathan Hicks");
MODULE_DESCRIPTION("ACPI style power management for SMP AMD-760MP(X) "
		"based systems, version " VERSION);


static struct pci_dev *pdev_nb;
static struct pci_dev *pdev_sb;

struct PM_cfg {
	unsigned int status_reg;
	unsigned int NTH_reg;
	unsigned int slp_reg;
	unsigned int resume_reg;
};

static struct PM_cfg amd76x_pm_cfg __read_mostly;

static struct pci_device_id  __devinitdata amd_nb_tbl[] = {
	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_FE_GATE_700C, PCI_ANY_ID,
		PCI_ANY_ID,},
	{0,}
};

static struct pci_device_id  __devinitdata amd_sb_tbl[] = {
	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7413, PCI_ANY_ID,
		PCI_ANY_ID,},
	{PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7443, PCI_ANY_ID,
		PCI_ANY_ID,},
	{0,}
};

/*
 * Configures the AMD-762 northbridge to support PM calls
 */
static int
config_amd762(int enable)
{
	unsigned int regdword;

	/* Enable STPGNT in BIU Status/Control for cpu0 */
	pci_read_config_dword(pdev_nb, 0x60, &regdword);
	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, &regdword);
	regdword |= (1 << 17);
	pci_write_config_dword(pdev_nb, 0x68, regdword);

	/* DRAM refresh enable */
	pci_read_config_dword(pdev_nb, 0x58, &regdword);
	regdword &= ~(1 << 19);
	pci_write_config_dword(pdev_nb, 0x58, regdword);

	/* Self refresh enable */
	pci_read_config_dword(pdev_nb, 0x70, &regdword);
	regdword |= (1 << 18);
	pci_write_config_dword(pdev_nb, 0x70, regdword);

	return 0;
}


/*
 * Get the base PMIO address and set the pm registers in amd76x_pm_cfg.
 */
static void
amd76x_get_PM(void)
{
	unsigned int regdword;

	/* Get the address for pm status, P_LVL2, etc */
	pci_read_config_dword(pdev_sb, 0x58, &regdword);
	regdword &= 0xff80;
	amd76x_pm_cfg.status_reg = (regdword + 0x00);
	amd76x_pm_cfg.slp_reg =    (regdword + 0x04);
	amd76x_pm_cfg.NTH_reg =    (regdword + 0x10);
	amd76x_pm_cfg.resume_reg = (regdword + 0x16); /* N/A for 768 */
}


/*
 * En/Disable PMIO and configure W4SG & STPGNT.
 */
static int
config_PMIO_amd76x(int is_766, int enable)
{
	unsigned char regbyte;

	/* Clear W4SG, and set PMIOEN, if using a 765/766 set STPGNT as well.
	 * AMD-766: C3A41; page 59 in AMD-766 doc
	 * AMD-768: DevB:3x41C; page 94 in AMD-768 doc */
	pci_read_config_byte(pdev_sb, 0x41, &regbyte);
	if(enable) {
		regbyte |= ((is_766 << 1) | (1 << 7));
	}
	pci_write_config_byte(pdev_sb, 0x41, regbyte);
	return 0;
}


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, &regdword);
	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);
}

/*
 * Configures the 765 & 766 southbridges.
 */
static int
config_amd766(int enable)
{
	amd76x_get_PM();
	config_PMIO_amd76x(1, 1);
#ifdef AMD76X_POS
	config_amd766_POS(enable);
#endif

	return 0;
}

/*
 * 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, &regdword);
	if(enable) 
		regdword |= (POSEN | CSTP | PSTP | ASTP | DCSTP | CSLP | SUSP);
	else
		regdword ^= POSEN;
	pci_write_config_dword(pdev_sb, 0x50, regdword);
}

/*
 * 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, &regbyte);
	/* Is it neccessary to use THMINEN at ANY time? */
	regbyte |= (NTPER(ntper) | THMINEN(thminen));
	pci_write_config_byte(pdev_sb, 0x40, regbyte);
}

/*
 * Configures the 768 southbridge to support idle calls, and gets
 * the processor idle call register location.
 */
static int
config_amd768(int enable)
{
	amd76x_get_PM();

	config_PMIO_amd76x(0, 1);

#ifdef AMD76X_POS
	config_amd768_POS(enable);
#endif
#ifdef AMD76X_NTH
	config_amd768_NTH(enable, 1, 2);
#endif

	return 0;
}

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

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

/*
 * Wrapper function to activate POS sleep state.
 */
static void
activate_amd76x_POS(void)
{
	activate_amd76x_SLP(1);
}

/*
 * Finds and initializes the bridges, and then sets the idle function
 */
static int
amd76x_pm_main(void)
{
	/* Find southbridge */
	pdev_sb = NULL;
	while((pdev_sb = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev_sb)) != NULL) {
		if(pci_match_id(amd_sb_tbl, pdev_sb) != NULL)
			goto found_sb;
	}
	printk(KERN_ERR "amd76x_pm: Could not find southbridge\n");
	return -ENODEV;

found_sb:
	/* Find northbridge */
	pdev_nb = NULL;
	while((pdev_nb = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev_nb)) != NULL) {
		if(pci_match_id(amd_nb_tbl, pdev_nb) != NULL)
			goto found_nb;
	}
	printk(KERN_ERR "amd76x_pm: Could not find northbridge\n");
	return -ENODEV;

 found_nb:	
	
	/* Init southbridge */
	switch (pdev_sb->device) {
	case PCI_DEVICE_ID_AMD_VIPER_7413:	/* AMD-765 or 766 */
		config_amd766(1);
		break;
	case PCI_DEVICE_ID_AMD_VIPER_7443:	/* AMD-768 */
		config_amd768(1);
		break;
	default:
		printk(KERN_ERR "amd76x_pm: No southbridge to initialize\n");		
		break;
	}

	/* Init northbridge and queue the new idle function */
	if(!pdev_nb) {
		printk("amd76x_pm: No northbridge found.\n");
		return -ENODEV;
	}
	switch (pdev_nb->device) {
	case PCI_DEVICE_ID_AMD_FE_GATE_700C:	/* AMD-762 */
		config_amd762(1);
		break;
	default:
		printk(KERN_ERR "amd76x_pm: No northbridge to initialize\n");
		break;
	}


#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

	return 0;
}


static int __init
amd76x_pm_init(void)
{
	printk(KERN_INFO "amd76x_pm: Version %s loaded.\n", VERSION);
	return amd76x_pm_main();
}


static void __exit
amd76x_pm_cleanup(void)
{
	
#ifdef AMD76X_NTH
	/* Turn NTH off */
	activate_amd76x_NTH(0, 0);
#endif

}


MODULE_LICENSE("GPL");
module_init(amd76x_pm_init);
module_exit(amd76x_pm_cleanup);
