/*
        Name (_PSS, Package (0x04)
        {
            Package (0x06)
            {
                0x00000898, 
                0x0001ADB0, 
                0x00000064, 
                0x00000009, 
                0xE0202A0E, 
                0x0000020E
            }, 

            Package (0x06)
            {
                0x000007D0, 
                0x00019C80, 
                0x00000064, 
                0x00000009, 
                0xE0202A8C, 
                0x0000028C
            }, 

            Package (0x06)
            {
                0x00000708, 
                0x00015C0C, 
                0x00000064, 
                0x00000009, 
                0xE0202B0A, 
                0x0000030A
            }, 

            Package (0x06)
            {
                0x000003E8, 
                0x0000BF68, 
                0x00000064, 
                0x00000009, 
                0xE0202C82, 
                0x00000482
            }
        })
        Name (_PPC, 0x00)
    }
*/

#include <stdint.h>

unsigned int vid_from_reg(uint8_t val)
{
	/* fixme 6 bits */
	val &= 0x1f;
	return (val == 0x1f ? 0 : 1550 - val * 25);
}

uint8_t vid_to_reg(uint32_t vid)
{
	return (1550 - vid) / 25;
}

//uint8_t fidcodes[32] = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10,
//      0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24, 0x26,
//      0x28, 0x2a
//};


//from kernel
/* Return a frequency in MHz, given an input fid */
static uint32_t fid_to_freq(uint32_t fid)
{
	return 800 + (fid * 100);
}

uint8_t freq_to_fid(uint32_t freq)
{
//      /* fix the odd fids */
//      return fidcodes[(freq / 200) - 4];
	return (freq - 800) / 100;
}

//from kernel 

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

static inline unsigned int cpuid_eax(unsigned int op)
{
	unsigned int eax;

      __asm__("cpuid":"=a"(eax)
      :	"0"(op)
      :	"bx", "cx", "dx");
	return eax;
}
static inline unsigned int cpuid_ebx(unsigned int op)
{
	unsigned int eax, ebx;

      __asm__("cpuid":"=a"(eax), "=b"(ebx)
      :	"0"(op)
      :	"cx", "dx");
	return ebx;
}

static inline unsigned int cpuid_edx(unsigned int op)
{
	unsigned int eax, edx;

      __asm__("cpuid":"=a"(eax), "=d"(edx)
      :	"0"(op)
      :	"bx", "cx");
	return edx;
}

/*
 *  $Id: rdmsr.c,v 1.16 2003/06/09 22:15:20 davej Exp $
 *  This file is part of x86info.
 *  (C) 2001 Dave Jones.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 *  Contributions by Arjan van de Ven & Philipp Rumpf.
 */

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

int read_msr(int cpu, unsigned int idx, unsigned long long *val)
{
	char cpuname[16];
	unsigned char buffer[8];
	unsigned long lo, hi;
	int fh;
	static int nodriver = 0;

	if (nodriver == 1)
		return 0;

	sprintf(cpuname, "/dev/cpu/%d/msr", cpu);

	fh = open(cpuname, O_RDONLY);
	if (fh == -1) {
//              if (!silent)
		perror(cpuname);
		nodriver = 1;
		return (0);
	}

	lseek(fh, idx, SEEK_CUR);

	if (fh != -1) {

		if (read(fh, &buffer[0], 8) != 8) {
			close(fh);
			return (0);
		}

		lo = (*(unsigned long *) buffer);
		hi = (*(unsigned long *) (buffer + 4));
		*val = hi;
		*val = (*val << 32) | lo;
	}
	close(fh);
	return (1);
}

#include <stdio.h>

#define MAXP 8

struct pstate {
	uint32_t freqMhz;
	uint32_t voltage;
	uint32_t tdp;
};

struct cpuentry {
	uint32_t modelnr;
	uint8_t brandID;
	uint32_t cpuid;
	uint8_t maxFID;
	uint8_t startFID;
	uint8_t maxVID;
	uint8_t startVID;
	struct pstate pstates[MAXP];
};

/* generated entry for revF */

struct cpuentry entr[] = {
	{165, 0x2c, 0x20f32, 0xa, 0xa, 0x8, 0xa,
	 {{1800, 1300, 1100}, {1000, 1100, 516}}},
	{165, 0x2c, 0x20f32, 0xa, 0xa, 0x6, 0x8,
	 {{1800, 1350, 1100}, {1000, 1100, 516}}},
	{170, 0x2c, 0x20f32, 0xc, 0xc, 0x8, 0xa,
	 {{2000, 1300, 1100}, {1800, 1300, 1056}, {1000, 1100, 514}}},
	{170, 0x2c, 0x20f32, 0xc, 0xc, 0x6, 0x8,
	 {{2000, 1350, 1100}, {1800, 1300, 1056}, {1000, 1100, 514}}},
	{175, 0x2c, 0x20f32, 0xe, 0xe, 0x8, 0xa,
	 {{2200, 1300, 1100}, {2000, 1300, 1056}, {1800, 1250, 891},
	  {1000, 1100, 490}}},
	{175, 0x2c, 0x20f32, 0xe, 0xe, 0x6, 0x8,
	 {{2200, 1350, 1100}, {2000, 1300, 1056}, {1800, 1250, 891},
	  {1000, 1100, 490}}},
	{180, 0x2c, 0x20f32, 0x10, 0x10, 0x8, 0xa,
	 {{2400, 1300, 1100}, {2200, 1300, 1056}, {2000, 1250, 891},
	  {1800, 1200, 748}, {1000, 1100, 466}}},
	{180, 0x2c, 0x20f32, 0x10, 0x10, 0x6, 0x8,
	 {{2400, 1350, 1100}, {2200, 1300, 1056}, {2000, 1250, 891},
	  {1800, 1200, 748}, {1000, 1100, 466}}},
};

void genpower(struct cpuentry *entr)
{
	uint32_t control;
	int i = 0;

	/* for nonrevF CPUs */
	if (entr->modelnr != 0xf) {
		/* IRT 80us, PLL_LOCK_TIME 2us, MVS 25mv, VST 100us */
		control = (3 << 30) | (2 << 20) | (0 << 18) | (5 << 11);
	} else {
		/* IRT 80us, PLL_LOCK_TIME 2us, MVS 25mv, VST 40us */
		control = (3 << 30) | (2 << 20) | (0 << 18) | (2 << 11);
	}

	do {
		printf("P#%d freq %d [MHz] voltage %f [V] TDP %f [W]\n", i,
		       entr->pstates[i].freqMhz,
		       entr->pstates[i].voltage / 1000.0,
		       entr->pstates[i].tdp / 10.0);
		i++;
	} while ((i < MAXP) && (entr->pstates[i].freqMhz != 0));

	i = 0;
/*
            Package (0x06)
            {
                0x00000898, 
                0x0001ADB0, 
                0x00000064, 
                0x00000009, 
                0xE0202A0E, 
                0x0000020E
            }, 
*/

	printf( " Scope (\_PR.CPU0)\n"
 "{\n Name (_PCT, Package (0x02) {\n"
"            ResourceTemplate ()\n"
"            {\n"
"                Register (FFixedHW, \n"
"                    0x00,               // Bit Width\n"
"                    0x00,               // Bit Offset\n"
"                    0x0000000000000000, // Address\n"
"                    ,)\n"
"            }, \n"
"\n"
"            ResourceTemplate ()\n"
"            {\n"
"                Register (FFixedHW, \n"
"                    0x00,               // Bit Width\n"
"                    0x00,               // Bit Offset\n"
"                    0x0000000000000000, // Address\n"
"                    ,)\n"
"            }\n"
"        })\n");
	printf(" Name (_PSS, Package (0x04) {");
	do {
		uint32_t fidvid =
		       (vid_to_reg(entr->pstates[i].voltage) << 6) |
		       (freq_to_fid(entr->pstates[i].freqMhz) & 0x3f);

		/* bus latencies are hardcoded */
		printf
		    (" Package (0x06) { \n 0x%08x,\n0x%08x,\n0x00000064,\n0x00000007,\n",
		     entr->pstates[i].freqMhz, entr->pstates[i].tdp * 100);
		printf("0x%08x,\n", control | fidvid );
		printf("0x%08x,\n}\n", fidvid);
		i++;
	} while ((i < MAXP) && (entr->pstates[i].freqMhz != 0));

	printf(")}\n        Name (_PPC, 0x00) \n}\n");

}

struct cpu_data {
	uint8_t brandID;
	uint32_t cpuid;
	uint8_t maxFID;
	uint8_t startFID;
	uint8_t maxVID;
	uint8_t startVID;
	uint8_t pwrlmt;
	unsigned long long msr_fidvid;
};

void get_cpuDATA(struct cpu_data *cpu)
{

	uint32_t brand;
	unsigned long long val = 0;

	if (read_msr(0, 0xC0010042, &val) != 1) {
		return;
	}
	printf("FID_VID_STATUS %llx ", val);

	cpu->msr_fidvid = val;
	cpu->startFID = (val >> 8) & 0x3f;
	cpu->maxFID = (val >> 16) & 0x3f;
	cpu->startVID = (val >> 40) & 0x3f;
	cpu->maxVID = (val >> 48) & 0x3f;
	cpu->cpuid = cpuid_eax(0x80000001);
	brand = cpuid_ebx(0x80000001);
	cpu->brandID = (brand >> 6) & 0x3f;


	if ((cpuid_edx(0x80000007) & 0x6) == 0x6) {
		/* pwrlmt is brand[8:6,14] */
		cpu->pwrlmt = (((brand >> 6) & 0x7) << 1) | (brand >> 14);
	} else {
		cpu->pwrlmt = 0;
	}

	printf
	    ("BrandID %x cpuid %x startFID %x maxFID %x startVID %x maxVID %x\n",
	     cpu->brandID, cpu->cpuid, cpu->startFID, cpu->maxFID,
	     cpu->startVID, cpu->maxVID);
}

/* units W * 10 */
unsigned int tdp(unsigned int pwrlmt, struct pstate *p,
		 uint32_t freqMhzMAX, uint32_t voltageMAX)
{
	unsigned long long pwr;
	/* fixme here need to follow Power = (PwrLmt * P[N] Frequency * (P[N] Voltage^2))/(P[0] Frequency* (P[0] Voltage^2)). */
	pwr = pwrlmt * p->freqMhz * (unsigned long long) (p->voltage *  p->voltage);
	pwr/= (voltageMAX * voltageMAX * freqMhzMAX);
	return pwr * 10;
}


void gen_revF(void)
{
	struct cpuentry entr_revF;
	struct cpu_data cpu;
	int i = 0, j;
	uint8_t pstep;
	uint32_t startVoltage;
	uint32_t lowFreqMhz;

	if ((cpuid_eax(0x80000001) & 0xf0000) < 0x40000) {
		/* no revF or later */
		return;
	}
	get_cpuDATA(&cpu);

	/* algo cannot be used */
	if (cpu.pwrlmt == 0)

		return;

	/* construct the entry */
	entr_revF.brandID = cpu.brandID;
	entr_revF.modelnr = 0xf;	/* for getpower */
	entr_revF.brandID = cpu.brandID;
	entr_revF.cpuid = cpu.cpuid;
	entr_revF.startFID = cpu.startFID;
	entr_revF.startVID = cpu.startVID;
	entr_revF.maxVID = cpu.maxVID;
	entr_revF.maxFID = cpu.maxFID;

	pstep = (cpu.msr_fidvid >> 56) & 1;
	startVoltage = vid_from_reg(cpu.startVID);

	/* first do the max */
	entr_revF.pstates[0].voltage = vid_from_reg(cpu.maxVID + 2);

	/* If MaxFID = 10_1010b and MaxVID != 00_0000b */
	if ((entr_revF.maxFID == 0x2a) && (entr_revF.maxVID != 0)) {
		entr_revF.pstates[0].freqMhz =
		    fid_to_freq(cpu.startFID + 0xa);
		lowFreqMhz = fid_to_freq(0x2);

	} else {
		entr_revF.pstates[0].freqMhz = fid_to_freq(cpu.maxFID);
		lowFreqMhz = fid_to_freq(cpu.startFID);
	}

	/* intermediate states */
	if ((cpu.msr_fidvid >> 61) & 0x1) {
		/* P1 first from max */

		if (cpu.maxFID & 1) {
			entr_revF.pstates[1].freqMhz =
			    fid_to_freq(cpu.maxFID - 1);
		} else {
			entr_revF.pstates[1].freqMhz =
			    fid_to_freq(cpu.maxFID - 2);
		}

		if (cpu.maxVID & 1) {
			entr_revF.pstates[1].voltage =
			    vid_from_reg(cpu.maxVID + 1);
		} else {
			entr_revF.pstates[1].voltage =
			    vid_from_reg(cpu.maxVID + (1 << pstep));
		}

		i = 1;

		do {
			i++;
			/* If P[N-1] VID + 2^PstateStep >= P[Min]:  */
			if ((entr_revF.pstates[i - 1].voltage -
			     (25 << pstep)) < startVoltage) {
				entr_revF.pstates[i].voltage =
				    entr_revF.pstates[i - 1].voltage;
			} else {
				entr_revF.pstates[i].voltage =
				    entr_revF.pstates[i - 1].voltage -
				    (25 << pstep);
			}
			entr_revF.pstates[i].freqMhz = entr_revF.pstates[i - 1].freqMhz - 200;	/* P[N-1] FID - 00_0010b */

		} while ((entr_revF.pstates[i].voltage > startVoltage)
			 && (entr_revF.pstates[i].freqMhz - 800 >
			     lowFreqMhz));
	}

	/* min P state */
	entr_revF.pstates[i].voltage = startVoltage;
	entr_revF.pstates[i].freqMhz = lowFreqMhz;

	/* terminate the list */
	entr_revF.pstates[i+1].voltage = 0;
	entr_revF.pstates[i+1].freqMhz = 0;

	/* calculate the TDP estimation */
	for (j = 0; j <= i; j++) {
		entr_revF.pstates[j].tdp =
		    tdp(cpu.pwrlmt, &entr_revF.pstates[j],
			fid_to_freq(entr_revF.maxFID),
			vid_from_reg(entr_revF.maxVID));
	}

}

void gen_to_revE(void)
{
	struct cpu_data cpu;
	int i;
	get_cpuDATA(&cpu);

	for (i = 0; i < ARRAY_SIZE(entr); i++) {
		if ((entr[i].cpuid == cpu.cpuid)
		    && (entr[i].startFID == cpu.startFID)
		    && (entr[i].maxFID == cpu.maxFID)
		    && (entr[i].startVID == cpu.startVID)
		    && (entr[i].maxVID == cpu.maxVID)) {
			printf("HIT %d\n", i);
			genpower(&entr[i]);
		}
	}

}

int main(void)
{
	gen_to_revE();
	gen_revF();
	return 0;
}

