/*
	FVCool version 1.04

	VCool for FreeBSD (and possibly Liunx):
	CPU cooling software for AMD's Athlon/Duron motherboard.

 */

/* Original is VCool of Marin Peters in the following */
/***************************************************************************
                          LVCool.cpp
 Sets the "enable Bus disconnect on STPGNT" bit on the northbridge
 and runs and idle loop that puts the CPU into STPGNT mode.

 Since I'm not a Linux guy just the minimum approach here *g*


					--------------------------------
	begin			: Fri Jul  6 10:13:24 CEST 2001
	copyright		: (C) 2001 by Martin Peters
	email			: mpet@bigfoot.de
	URL				: http://vcool.occludo.net/
 ***************************************************************************/

/* Data for Athlon/Duron Cooling information by Hidemi Oya */
/***************************************************************************
                          CoolON
	URL				: http://homepage2.nifty.com/coolon/ (Japanese)
 ***************************************************************************/

/*
	<<< BASIC DATASHEETS >>>
	disconnect CPU bus at HLT or STPGNT state: bit in Northbridge

	AMD 751/761
		 PCI conf. register 0x62  bit 1  and bit 2   i.e. |= 0x06
		(PCI conf. register 0x60  bit 17 and bit 18  i.e. |= 0x060000)

	AMD 762
		 PCI conf. register 0x62  bit 1  i.e. |= 0x02
		(PCI conf. register 0x60  bit 17 i.e. |= 0x020000)
		 PCI conf. register 0x6A  bit 1  i.e. |= 0x02
		(PCI conf. register 0x68  bit 17 i.e. |= 0x020000)

	VIA KT133/A/KM133/KL133/KN133, KX133, KLE133
		 PCI conf. register 0x52  bit 7   i.e. |= 0x80      < --- STPGNT
		(PCI conf. register 0x50  bit 23  i.e. |= 0x800000)
		 PCI conf. register 0x70  bit 3   i.e. |= 0x08
		(PCI conf. register 0x70  bit 3   i.e. |= 0x08)
	with additional ACPI operation: set STPGNT state in idle-loop.
	This is done by reading ACPI Processor Level 2 register.

	VIA KT266/A/KT333, KM266/KL266/KM333, KN266
		 PCI conf. register 0x92  bit 7   i.e. |= 0x80      < --- STPGNT
		(PCI conf. register 0x90  bit 23  i.e. |= 0x800000)
		 PCI conf. register 0x95  bit 1   i.e. |= 0x02      < --- HLT
		(PCI conf. register 0x94  bit 9   i.e. |= 0x0200)
		 PCI conf. register 0x70  bit 3   i.e. |= 0x08
		(PCI conf. register 0x70  bit 3   i.e. |= 0x08)

	VIA KT400/A/KT600, KM400
		 PCI conf. register 0xD2  bit 7   i.e. |= 0x80      < --- STPGNT
		(PCI conf. register 0xD0  bit 23  i.e. |= 0x800000)
		 PCI conf. register 0xD5  bit 1   i.e. |= 0x02      < --- HLT
		(PCI conf. register 0xD4  bit 9   i.e. |= 0x0200)
		 PCI conf. register 0x70  bit 3   i.e. |= 0x08
		(PCI conf. register 0x70  bit 3   i.e. |= 0x08)

	VIA KT880
		 PCI conf. register 0x82  bit 7   i.e. |= 0x80      < --- STPGNT
		(PCI conf. register 0x80  bit 23  i.e. |= 0x800000)
		 PCI conf. register 0x85  bit 1   i.e. |= 0x02      < --- HLT
		(PCI conf. register 0x84  bit 9   i.e. |= 0x0200)

	Sis 730, 733
		 PCI conf. register 0x6B  bit 0   i.e. |= 0x01
		(PCI conf. register 0x68  bit 24  i.e. |= 0x01000000)

	Sis 735, 741, 740, 745
		 PCI conf. register 0x6A  bit 0&1 i.e. |= 0x03
		(PCI conf. register 0x68  bit 16&17  i.e. |= 0x030000)

	Sis 746, 748
		 PCI conf. register 0x6C  bit 0   i.e. |= 0x01
		(PCI conf. register 0x6C  bit 0   i.e. |= 0x00000001)

	NVidia nForce
		 PCI conf. register 0x6D  bit 7   i.e. |= 0x80
		(PCI conf. register 0x6C  bit 15  i.e. |= 0x8000)
		 PCI conf. register 0xE7  bit 1  and bit 2   i.e. |= 0x06
		(PCI conf. register 0xE4  bit 25 and bit 26  i.e. |= 0x06000000)

	NVidia nForce2
		 PCI conf. register 0x6F  bit 4   i.e. |= 0x10
		(PCI conf. register 0x6C  bit 28  i.e. |= 0x10000000)
		for some of C1 revisions, further need
		 PCI conf. register 0x6F  bit 7-5   be 0, i.e. &= 0x1F
		(PCI conf. register 0x6C  bit 31-29 be 0, i.e. &= 0x1FFFFFFF)

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#include "io_static.c"

#define DWORD unsigned int

#define PCI_CFIO	0x0CF8
#define PCI_CWRV	0x0CFC
#define PCI_BUSM	0x03
#define PCI_DEVM	0x32
#define PCI_FUNM	0x08

/* supported chipsets */

#define ID_AMD751	0x70061022
#define ID_AMD751S	0x70041022
#define ID_AMD761	0x700E1022
#define ID_AMD762	0x700C1022
#define AMD_751		0x01
#define AMD_762		0x02

#define ID_VIA8363	0x03051106
#define ID_VIA8371	0x03911106
#define ID_VIA8371A	0x06911106
#define ID_VIA8361	0x31121106
#define ID_VIA8366	0x30991106
#define ID_VIA8375	0x31161106
#define ID_VIA8372	0x31561106
#define ID_VIA8377	0x31891106
#define ID_VIA8378	0x32051106
#define ID_VIAKT880	0x22691106
#define ID_VIA686	0x30571106
#define VIA_KT133	0x11
#define VIA_KT266	0x12
#define VIA_KT400	0x13
#define VIA_KT880	0x14

#define ID_SIS730	0x07301039
#define ID_SIS733	0x07331039
#define ID_SIS735	0x07351039
#define ID_SIS740	0x07401039
#define ID_SIS741	0x07411039
#define ID_SIS745	0x07451039
#define ID_SIS746	0x07461039
#define ID_SIS748	0x07481039
#define SIS_730		0x21
#define SIS_735		0x22
#define SIS_746		0x23

#define ID_NFORCE	0x01A410DE
#define ID_NFORCE2	0x01E010DE
#define NFORCE		0x31
#define NFORCE2		0x32

/* global variables */
int Reg_PL2;
int North_Chip = 0;
int idle_flag = 0, debug_flag = 0, enable_flag = 0;

int iofl;
int iopl_counter;

DWORD PCIRead(int reg, int fun, int dev, int bus)
{
	DWORD r = 0x80000000, ret, org;
	
	r |= (( bus & 0xff) <<16);
	r |= (( dev & 0x1f) <<11);
	r |= (( fun & 0x07) << 8);
	r |= (( reg & 0xfc) );
	
	org = INl(PCI_CFIO); WAIT;
	OUTl(PCI_CFIO, r); WAIT;
	ret = INl(PCI_CWRV); WAIT;
	org &= 0x7FFFFFFF;
	OUTl(PCI_CFIO, org); WAIT;
	
	return ret;
}

void PCIWrite(DWORD val, int reg, int fun, int dev, int bus)
{
	DWORD r = 0x80000000, org;
	
	r |= (( bus & 0xff) <<16);
	r |= (( dev & 0x1f) <<11);
	r |= (( fun & 0x07) << 8);
	r |= (( reg & 0xfc) );

	org = INl(PCI_CFIO); WAIT;
	OUTl(PCI_CFIO, r); WAIT;
	OUTl(PCI_CWRV, val); WAIT;
	org &= 0x7FFFFFFF;
	OUTl(PCI_CFIO, org); WAIT;
}

/* scan PCI bus for north and south (VIA82C686 only) bridge */

int search_PCI(int *chip, int *nb, int *nd, int *nf, int *sb, int *sd, int *sf)
{
	int bus, dev, fun, res, ret = 0;

	*chip = 0;
	for (bus = 0; bus < PCI_BUSM; bus++) {
		for (dev = 0; dev < PCI_DEVM; dev++) {
			for (fun = 0; fun < PCI_FUNM; fun++) {
				res = PCIRead(0, fun, dev, bus);
				if (res == 0xffffffff)
					continue;
				switch (res) {
				case ID_AMD751:
				case ID_AMD751S:
				case ID_AMD761:
					*chip = AMD_751;
					break;
				case ID_AMD762:
					*chip = AMD_762;
					break;
				case ID_VIA8363:
				case ID_VIA8371:
				case ID_VIA8371A:
				case ID_VIA8361:
					*chip = VIA_KT133;
					break;
				case ID_VIA8366:
				case ID_VIA8375:
				case ID_VIA8372:
					*chip = VIA_KT266;
					break;
				case ID_VIA8377:
				case ID_VIA8378:
					*chip = VIA_KT400;
					break;
				case ID_VIAKT880:
					*chip = VIA_KT880;
					break;

				case ID_SIS730:
				case ID_SIS733:
					*chip = SIS_730;
					break;
				case ID_SIS735:
				case ID_SIS740:
				case ID_SIS741:
				case ID_SIS745:
					*chip = SIS_735;
					break;

				case ID_SIS746:
				case ID_SIS748:
					*chip = SIS_746;
					break;

				case ID_NFORCE:
					*chip = NFORCE;
					break;
				case ID_NFORCE2:
					*chip = NFORCE2;
					break;

				case ID_VIA686:
					*sb = bus;
					*sd = dev;
					*sf = fun;
					ret |= 0x02;
					break;
				}
				if (!(ret & 0x01) && *chip) {
					*nb = bus;
					*nd = dev;
					*nf = fun;
					ret |= 0x01;
				}
				if (ret >= 3) break;
			}
			if (ret >= 3) break;
		}
		if (ret >= 3) break;
	}
	return ret;
}

int maskbits(int value, int mask)
{
	int ret;

	if (enable_flag > 0)
		ret = value | mask;
	else if (enable_flag < 0)
		ret = value & ~mask;
	else
		ret = value;

	return ret;
}

void chip_info(int value, char *comment, int reg, int mask)
{
	int val1, val2, shiftbits;

	shiftbits = (reg % 4) * 8;
	val1 = (0xFF & (value >> shiftbits));
	val2 = maskbits(val1, mask);
	fprintf(stderr, "%s", comment);
	fprintf(stderr, " Change Reg0x%02X 0x%02X --> 0x%02X\n", reg, val1, val2);
}

int InitPCI(debug_flag)
{
	DWORD res;
	int ret;
	int nb_b = 0, nb_d = 0, nb_f = 0;
	int sb_b = 0, sb_d = 7, sb_f = 4;
	
	ret = search_PCI(&North_Chip, &nb_b, &nb_d, &nb_f, &sb_b, &sb_d, &sb_f);
	if (!(ret & 0x01)) {
		perror("Supported Athlon/Duron chipset (north bridge) not found.\n");
		exit(1);
	}

/* now set enable bus Diconnect when HLT/STPGNT detected */

	switch (North_Chip) {
	case AMD_751:
		res = PCIRead(0x60, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x060000), 0x60, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "AMD751/761 found.\n", 0x62, 0x02);
		break;
	case AMD_762:
		res = PCIRead(0x60, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x020000), 0x60, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "AMD762 found.\n", 0x62, 0x02);
		res = PCIRead(0x68, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x020000), 0x68, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0x6A, 0x02);
		break;
	case VIA_KT133:
		res = PCIRead(0x50, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x800000), 0x50, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "VIA KT133/A/KX133/KM133 found.\n", 0x52, 0x80);
		res = PCIRead(0x70, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x08), 0x70, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0x70, 0x08);
		break;
	case VIA_KT266:
		res = PCIRead(0x90, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x800000), 0x90, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "VIA KT266/A/KT333 found.\n", 0x92, 0x80);
		res = PCIRead(0x94, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x0200), 0x94, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0x95, 0x02);
		res = PCIRead(0x70, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x08), 0x70, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0x70, 0x08);
		break;
	case VIA_KT400:
		res = PCIRead(0xD0, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x800000), 0xD0, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "VIA KT400/A/KT600 found.\n", 0xD2, 0x80);
		res = PCIRead(0xD4, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x0200), 0xD4, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0xD5, 0x02);
		res = PCIRead(0x70, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x08), 0x70, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0x70, 0x08);
		break;
	case VIA_KT880:
		res = PCIRead(0x80, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x800000), 0x80, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "VIA KT880 found.\n", 0x82, 0x80);
		res = PCIRead(0x84, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x0200), 0x84, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0x85, 0x02);
		break;
	case SIS_730:
		res = PCIRead(0x68, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x01000000), 0x68, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "SiS730/733 found.\n", 0x6B, 0x01);
		break;
	case SIS_735:
		res = PCIRead(0x68, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x030000), 0x68, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "SiS735/740/745 found.\n", 0x6A, 0x03);
		break;
	case SIS_746:
		res = PCIRead(0x6C, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x00000001), 0x6C, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "SiS746 found.\n", 0x6C, 0x01);
		break;
	case NFORCE:
		res = PCIRead(0x6C, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x8000), 0x6C, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "NVidia nForce found.\n", 0x6D, 0x80);
		res = PCIRead(0xE4, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x06000000), 0xE4, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "", 0xE7, 0x06);
		break;
	case NFORCE2:
		res = PCIRead(0x6C, nb_f, nb_d, nb_b);
		res &= 0x1FFFFFFF;	/* clear 0x6F bits 7-5 */
		PCIWrite(res, 0x6C, nb_f, nb_d, nb_b);
		PCIWrite(maskbits(res, 0x10000000), 0x6C, nb_f, nb_d, nb_b);
		if (debug_flag)
			chip_info(res, "NVidia nForce2 found.\n", 0x6F, 0x10);
		break;
	}

/* enable ACPI and find I/O-Space for VT82C686(KT133/A south) */

	if ((ret & 0x02) && (enable_flag > 0)) {
		res = PCIRead(0x41,sb_f,sb_d,sb_b);
		if ((res & 0x8000) == 0) {
			res |= 0x8000;
			PCIWrite(res, 0x41, sb_f, sb_d, sb_b);
		}

		res = PCIRead(0x48, sb_f, sb_d, sb_b);
		res &= 0xff80;

		Reg_PL2 = res + 0x14;

		if (debug_flag) {
			fprintf(stderr, "VT82C686/A/B(ACPI) found.\n");
			fprintf(stderr, " PLVL_2 Reg Address:0x%0X\n", Reg_PL2);
		}
	}
	return ret;
}

void Idleloop()
{
	if(nice(20) == -1) {
		perror("nice does not work!\n");
		exit(1);
	}
	while(1) {
		INb(Reg_PL2);
/*		usleep(30);	*/
	}
}

void usage() {
	fprintf(stderr, "Usage: [-v (verbose)] "
		"[ -e (enable) [ -i (idle loop)]] [ -d (disable)]\n");

	exit(1);
}

int main(int argc, char *argv[])
{
	int n, ch;
	int arguments_processed = 0;

	while ((ch = getopt(argc, argv, "vidhe")) != -1) {
		switch (ch) {
		case 'v':
			debug_flag = 1;
			break;
		case 'i':
			idle_flag = 1;
			break;
		case 'd':
			enable_flag = -1;
			break;
		case 'e':
			enable_flag = 1;
			break;
		case 'h':
		default:
			usage();
		}
		arguments_processed++;
	}

	if (arguments_processed == 0)
		usage();
	
	if (enable_flag <= 0)
		idle_flag = 0;

 	if (OpenIO() < 0) {
		perror("cannot open IO-port, must run as root!\n");
		exit(1);
	}

	if (idle_flag & !enable_flag) {
		fprintf(stderr, "cannot idle without enabling!\n");
		usage();
	}

	n = InitPCI(debug_flag);
	if (idle_flag && (n & 0x02)) {
		fprintf(stderr, "Now going into idle-loop!\n");
		Idleloop();
	}

	CloseIO();
	return 0;
}
