/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      i386 CPU detection routines, by Phil Frisbie.
 *
 *      Theuzifan improved the support for Cyrix chips.
 *
 *      Calin Andrian added 3DNow! detection code.
 *
 *      See readme.txt for copyright information.
 */


#include "allegro.h"
#include "allegro/internal/aintern.h"



/* helpers from icpus.s */
int _i_is_486(void);
int _i_is_fpu(void);
int _i_is_cyrix(void);
void _i_cx_w(int index, int value);
char _i_cx_r(int index);
int _i_is_cpuid_supported(void);
void _i_get_cpuid_info(uint32_t cpuid_levels, uint32_t *reg);



/* cyrix_type:
 *  Detects which type of Cyrix CPU is in use.
 */
static void cyrix_type(void)
{
   char orgc2, newc2, orgc3, newc3;
   int cr2_rw = FALSE, cr3_rw = FALSE, type;

   type = 0xFF;

   orgc2 = _i_cx_r(0xC2);              /* get current c2 value */
   newc2 = orgc2 ^ 4;                  /* toggle test bit */
   _i_cx_w(0xC2, newc2);               /* write test value to c2 */
   _i_cx_r(0xC0);                      /* dummy read to change bus */
   if (_i_cx_r(0xC2) != orgc2)         /* did test bit toggle */
      cr2_rw = TRUE;                   /* yes bit changed */
   _i_cx_w(0xC2, orgc2);               /* return c2 to original value */

   orgc3 = _i_cx_r(0xC3);              /* get current c3 value */
   newc3 = orgc3 ^ 0x80;               /* toggle test bit */
   _i_cx_w(0xC3, newc3);               /* write test value to c3 */
   _i_cx_r(0xC0);                      /* dummy read to change bus */
   if (_i_cx_r(0xC3) != orgc3)         /* did test bit change */
      cr3_rw = TRUE;                   /* yes it did */
   _i_cx_w(0xC3, orgc3);               /* return c3 to original value */

   if (((cr2_rw) && (cr3_rw)) || ((!cr2_rw) && (cr3_rw))) {
      type = _i_cx_r(0xFE);            /* DEV ID register ok */
   }
   else if ((cr2_rw) && (!cr3_rw)) {
      type = 0xFE;                     /* Cx486S A step */
   }
   else if ((!cr2_rw) && (!cr3_rw)) {
      type = 0xFD;                     /* Pre ID Regs. Cx486SLC or DLC */
   }

   if ((type < 0x30) || (type > 0xFC)) {
      cpu_family = 4;                  /* 486 class-including 5x86 */
      cpu_model = 14;                  /* Cyrix */
   }
   else if (type < 0x50) {
      cpu_family = 5;                  /* Pentium class-6x86 and Media GX */
      cpu_model = 14;                  /* Cyrix */
   }
   else {
      cpu_family = 6;                  /* Pentium || class- 6x86MX */
      cpu_model = 14;                  /* Cyrix */
      cpu_capabilities |= CPU_MMX;
   }
}



/* check_cpu:
 *  This is the function to call to set the globals
 */
void check_cpu() 
{
   uint32_t cpuid_levels;
   uint32_t vendor_temp[4];
   uint32_t reg[4];

   cpu_capabilities = 0;

   if (_i_is_cpuid_supported()) {
      cpu_capabilities |= CPU_ID;
      _i_get_cpuid_info(0x00000000, reg);
      cpuid_levels = reg[0];
      vendor_temp[0] = reg[1];
      vendor_temp[1] = reg[3];
      vendor_temp[2] = reg[2];
      vendor_temp[3] = 0;
      do_uconvert((char *)vendor_temp, U_ASCII, cpu_vendor, U_CURRENT,
		  _AL_CPU_VENDOR_SIZE);

      if (cpuid_levels > 0) {
	 reg[0] = reg[1] = reg[2] = reg[3] = 0;
	 _i_get_cpuid_info(1, reg);

	 cpu_family = (reg[0] & 0xF00) >> 8;
	 cpu_model = (reg[0] & 0xF0) >> 4;
	 /* Note: cpu_family = 15 can mean a Pentium IV, Xeon, AMD64, Opteron... */

	 cpu_capabilities |= (reg[3] & 1 ? CPU_FPU : 0);
	 cpu_capabilities |= (reg[3] & 0x800000 ? CPU_MMX : 0);

	 /* SSE has MMX+ included */
	 cpu_capabilities |= (reg[3] & 0x02000000 ? CPU_SSE | CPU_MMXPLUS : 0);
	 cpu_capabilities |= (reg[3] & 0x04000000 ? CPU_SSE2 : 0);
         cpu_capabilities |= (reg[2] & 0x00000001 ? CPU_SSE3 : 0);
	 cpu_capabilities |= (reg[3] & 0x00008000 ? CPU_CMOV : 0);
         cpu_capabilities |= (reg[3] & 0x40000000 ? CPU_IA64 : 0);
      }

      _i_get_cpuid_info(0x80000000, reg);
      if (reg[0] > 0x80000000) {
	 _i_get_cpuid_info(0x80000001, reg);

	 cpu_capabilities |= (reg[3] & 0x80000000 ? CPU_3DNOW : 0);
	 cpu_capabilities |= (reg[3] & 0x20000000 ? CPU_AMD64 : 0);
	  
	 /* Enhanced 3DNow! has MMX+ included */	 
	 cpu_capabilities |= (reg[3] & 0x40000000 ? CPU_ENH3DNOW | CPU_MMXPLUS : 0);
      }

      if (_i_is_cyrix())
	 cpu_model = 14;
   }
   else {
      cpu_capabilities |= (_i_is_fpu() ? CPU_FPU : 0);
      if (!_i_is_486()) {
	 cpu_family = 3;
      }
      else {
	 if (_i_is_cyrix()) {
	    do_uconvert("CyrixInstead", U_ASCII, cpu_vendor, U_CURRENT,
			_AL_CPU_VENDOR_SIZE);
	    cyrix_type();
	 }
	 else {
	    cpu_family = 4;
	    cpu_model = 15;
	 }
      }
   }
}

