/* Copyright (c) 2024 Amelia Zabardast Ziabari
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**
**  1. Redistributions of source code must retain the above copyright notice,
**     this list of conditions and the following disclaimer.
**  2. Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in the
**     documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define ERROR(...) (fprintf(stderr, __VA_ARGS__),exit(1))

static int states;
struct state {
    int mV;
    int msr;
    };
static struct state *table;


static void validate_value( int v )
    {
    if ( ( v - 700 ) % 16 != 0 )
	ERROR("specified voltage value (%d) cannot compute to MSR value\n",v);
    }


static void validate_range( int mi, int ma )
    {
    /* TODO: Pentium M always goes down to 700 mV, but unsure if any processors
    ** exceed 1500 mV.  someone should check this if they need it.
    ** (Core/Core 2 just have their minimum voltage locked?)
    */
    if ( mi < 700 || ma < 700 || mi > 1500 || ma > 1500 )
	ERROR("specified voltage invalid or out of sane range (700-1500)?\n");
    if ( mi > ma )
	ERROR("specified minimum voltage is larger than maximum voltage?\n");
    validate_value( mi );
    validate_value( ma );
    }


static float lerp( float mi, float ma, float omi, float oma )
    {
    return mi + ( ma - mi ) * ( omi / oma );
    }


static void gen_range( int mi, int ma )
    {
    int i;
    float mV;

    for ( i = 0; i < states; i++ )
	{
	mV = lerp( mi, ma, i, states - 1 );
	/* intel appears to round for default values, but some may prefer
	** rounding up for safety reasons.  alternatively, just raise max mV
	*/
	mV = roundf( ( mV - 700 ) / 16 );
	table[i].msr = (int)  mV;
	table[i].mV  = (int) (mV * 16) + 700;
	}
    }


static void print_acc( )
    {
    static int t = 0;

    if ( (t++) >= 5 )
	{
	t = 0;
	printf( "\n" );
	}
    }


static void print_range( int fmt )
    {
    int i;

    switch ( fmt )
	{
	case 2: /* vids only */
	for ( i = states - 1; i >= 0; i-- )
	    printf( "%d ", table[i].msr );
	break;

	case 1: /* netbsd sysctl */
	printf( "machdep.cpu.voltage.custom=" );

	for ( i = states - 1; i >= 0; i-- )
	    printf( "%d ", table[i].mV );
	break;

	default: /* human readable form */
	for ( i = 0; i < states; i++ )
	    {
	    printf( "%4d mV (%2d) ", table[i].mV, table[i].msr );
	    print_acc( );
	    }
	break;
	}

    printf( "\n" );
    }


static void print_omap( int imi, int ima )
    {
    /* get the full range of possible vids so the user can select them */
    int i, msrmi, msrma;

    msrmi = (imi - 700) >> 4;
    msrma = (ima - 700) >> 4;

    for ( i = msrmi; i < (msrma + 1); i++ )
	{
	printf( "%4d mV (%2d) ", (i << 4) + 700, i );
	print_acc( );
	}

    printf( "\n" );
    }


static void print_rmap( int imi, int ima, int fmt )
    {
    gen_range( imi, ima );
    print_range( fmt );
    }


static void command( char *cmd, int imi, int ima )
    {
    char c;

    validate_range( imi, ima );

    if ( strlen(cmd) != 1 )
	{
	invalid:
	ERROR("invalid command (must be d, a, A, v)\n");
	}
    c = cmd[0];

    switch (c)
	{
	case 'd': print_omap( imi, ima );    break;
	case 'a': print_rmap( imi, ima, 0 ); break;
	case 'A': print_rmap( imi, ima, 1 ); break;
	case 'v': print_rmap( imi, ima, 2 ); break;
	default : goto invalid;
	}
    }

int main( int argc, char **argv )
    {
    if (argc >= 2)
	{
	states = atoi(argv[1]);
	if ( states < 2 || states > 16 )
	    ERROR("specified amount of states outside sane range (2-16)\n");
	table = malloc( sizeof(struct state) * states );
	}
    switch ( argc )
	{
	case 5: command( argv[2], atoi( argv[3] ), atoi( argv[4] ) ); break;

	default:
	ERROR(
	    "insufficient params.\n"
	    "%s <S> <M> <A> <B>: show possible VIDs\n"
	    "where A-B is min voltage to max voltage in mV (e.g. 1020-1404)\n"
	    "and M is a single-char operation:\n"
	    "  d[umb] - print all possible MSR values in given range\n"
	    "  a[uto] - auto-calculate new VID table for given range\n"
	    "  A[uto] - ditto, but format as a NetBSD est.c sysctl\n"
	    "  v[ids] - ditto, but just print raw VID numbers\n"
	    "and S is the total number of original MSR states (e.g. 8)\n"
	    "example: %s 8 a 1020 1404\n",
	    argv[0], argv[0]);
	}
    return 0;
    }
