/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

/****************************************************************************/
/*						Definitions											*/
/****************************************************************************/

#define	VCO_MIN		275				// Lowest PLL frequency [MHz]
#define	VCO_MAX		550				// Highest PLL frequency [MHz]
#define	FLASH_T		50				// FLASH access speed [ns]

/*
*	This structure is used for the CPU clock calculations
*/

typedef struct {

	int		pll_m;					// PLL M value
	int		pll_n;					// PLL N value
	int		cpu_d;					// CPU clock divider
	int		usb_d;					// USB clock divider
	double	cpu_e;					// CPU frequency error
	double	usb_e;					// USB frequency error
	double	vco_f;					// VCO frequency

} FREQ;

/*
*	This structure is used for the UART calculations
*/

typedef struct {

	int		brate;					// Requested baud rate
	double	final;					// Final baudrate
	double	error;					// Error (percentage)
	int		i_val;					// Integer divisor
	int		a_val;					// Fractional divider A value
	int		m_val;					// Fractional divider M value

} BDIV;

/****************************************************************************/
/*						Prototypes											*/
/****************************************************************************/

static BDIV		*CommandLine( int argc, char **argv );
static int		CalculatePll( FREQ *this );
static int		CalculateSio( double pclk, BDIV *bdiv );
static int		CalculateGcd( int a, int b );
static int		BrateCompare( const void *a, const void *b );
static void		DumpResults( char *prg, FREQ *pll, BDIV *sio );

/****************************************************************************/
/*						Data												*/
/****************************************************************************/

/*
*	Command line options
*/

static	double	xtal	= 0;		// Reference clock			[MHz]
static	double	cclk	= 0;		// Required CPU clock		[MHz]
static	double	cmax	= 60;		// Maximum CPU freq			[MHz]
static	double	cmin	= 0;		// Minimum CPU freq			[MHz]
static	double	usbe	= 0;		// USB error 				[ppm]
static	int		pdiv	= 4;		// P clock divider value	[-]
static	int		form	= 0;		// Use alternate format		[-]

/*
*	The identifier used for the #ifdef around the results.
*/

static	char	*name	= (char *) "_LPC_CLOCK_TOOL";

/*
*	These are the low frequency PLL multipliers, as per NXP
*/

static const int lowf_m[ 65 ] = {

	 4272,  4395,  4578,  4725,  4807,
	 5127,  5188,  5400,  5493,  5859,
	 6042,  6075,  6104,  6409,  6592,
	 6750,  6836,  6866,  6958,  7050,
	 7324,  7425,  7690,  7813,  7935,
	 8057,  8100,  8545,  8789,  9155,
	 9613, 10254, 10376, 10986, 11719,
	12085, 12207, 12817, 13184, 13672,
	13733, 13916, 14099, 14420, 14648,
	15381, 15564, 15625, 15869, 16113,
	16479, 17578, 18127, 18311, 19226,
	19775, 20508, 20599, 20874, 21149,
	21973, 23071, 23438, 23804, 24170
};

/*
*	The help message
*/

static const char *usage =
"\n"
"This program calculates the PLL and UART clock values for LPC2xxx chips.\n"
"It writes the result to the standard output in a format that is a valid\n"
"C header file. All calculated values are #define-s in the file.\n"
"Calculation details are also written as comments.\n"
"\n"
"The following symbols are defined:\n"
"\tCLOCK_XTAL    is the reference clock in Hz\n"
"\tCLOCK_CCLK    is the achieved CPU clock in Hz\n"
"\tCLOCK_PLL_M   is the PLL's 'M' parameter\n"
"\tCLOCK_PLL_N   is the PLL's 'N' parameter\n"
"\tCLOCK_CPU_DIV is the CPU clock divider value\n"
"\tCLOCK_USB_DIV is the USB clock divider value (if requested)\n"
"\tCLOCK_MAM_TIM is the value to use in the MAMTIM register\n"
"\tUART_CDIV     is the CCLK prescale factor for the uart\n"
"\n"
"The exit code is 0 in case of success and 1 if some error was detected,\n"
"in which case an error message is also sent to stderr.\n"
"\n"
"Usage:\n\n%1$s options [baud_rates]\n\nwhere the options are the following:\n\n"
"-x xtal\n"
"    The reference clock frequency (float)\n"
"    If the number is less than 100, it is taken as MHz, otherwise as Hz.\n"
"    Values between 32.768kHz and 40MHz are accepted.\n\n"
"    This option is mandatory.\n"
"-c cclk\n"
"    The required CPU clock (float).\n"
"    If the number is less than 100, it is taken as MHz, otherwise as Hz.\n"
"    The minimum is 1.075MHz, the maximum is 72MHz.\n\n"
"    This option is mandatory.\n"
"-h max_clock\n"
"    Maximum CPU clock (float).\n"
"    If the number is less than 100, it is taken as MHz, otherwise as Hz.\n"
"    The default is 60MHz or the specified clock (whichever is larger).\n"
"    If you specify a value that is less than the requested clock frequency\n"
"    the the requested clock frequency will be used instead.\n"
"    This value is only used if the required CPU clock can not be exactly\n"
"    achieved. A value close to the requested frequency will be accepted if\n"
"    it is not higher than this value.\n\n"
"-l min_clock\n"
"    Minimum CPU clock (float).\n"
"    If the number is less than 100, it is taken as MHz, otherwise as Hz.\n"
"    The default is 0. If you specify a value that is more than the requested\n"
"    clock frequency then the requested clock frequency will be used instead.\n"
"    This value is only used if the required CPU clock can not be exactly\n"
"    achieved. A value close to the requested frequency will be accepted if\n"
"    it is not lower than this value.\n\n"
"-u max_error\n"
"	 Maximum USB error (float).\n"
"    Indicates that USB is needed. In that case the VCO frequency must be\n"
"    set to an exact multiple of 96MHz. The parameter indicates the maximum\n"
"	 frequency error, in ppm. Specifying 0 or a negative value disables the\n"
"    USB calculations. The default is 0.\n\n"
"-p n\n"
"    The peripheral clock divider for the UART's clock (1, 2, 4 or 8)\n"
"    The default value is 4.\n\n"
"-i string\n"
"    The identifier to be used for the #ifndef directive protecting the\n"
"    header file from multiple inclusions. It must be a valid C identifier.\n"
"    The program does *not* check the validity of the string, it uses it\n"
"    verbatim. The default is \"_LPC_CLOCK_TOOL\".\n\n"
"-a\n"
"    Selects the alternate format for UART calculations.\n"
"    The default is to create three definitions for each baudrate:\n"
"\tUART_I_XXXX  is the UART's integer divider for baud rate XXXX\n"
"\tUART_M_XXXX  is the UART's fractional divider's 'M' value\n"
"\tUART_A_XXXX  is the UART's fractional divider's 'A' value\n"
"    The alternative format is that an initialiser is defined for an\n"
"    array of structures. The structure has to have 4 members, the first\n"
"    one is the baud rate, the next one is the intefer divider value,\n"
"    then the fractional divider's M value and finally the fractional\n"
"    divider's A value. The suggested member sizes are int, short\n"
"    char and char, all unsigned. The initialiser is called\n"
"    UART_CLOCK_TABLE_INIT.\n\n"
"[baud_rates]\n"
"    The required baud rates, in baud (integers) separated by space\n"
"    If no values are given, then no UART calculations are performed.\n"

"\n"
"For example,\n\n%1$s -x 10 -c 72 -u 500 -p 2 115200 38400\n\n"
"will calculate the PLL settings for a 10MHz external crystal and a 72MHz\n"
"CPU clock, with USB clock also being generated. In addition, the UART\n"
"clock divider values for for 115200 and 38400 baud operation when the\n"
"UART is running on half the CPU clock frequency are also calculated.\n\n"
"%1$s -x 32768 -c 48 -h 50 -l 45 9600\n\n"
"calculates the settings for a chip that is clocked from a 32.768kHz\n"
"real-time clock crystal. The desired CPU clock is 48MHz, but if it can not\n"
"be achieved, any frequency between 45 and 50MHz is accepted. No USB clock \n"
"is needed. The peripheral clock division ratio is 4 (the default) and the\n"
"UART should run at 9600 baud.\n";

/****************************************************************************/
/*						Public functons										*/
/****************************************************************************/

/*
*	Program entry point
*	~~~~~~~~~~~~~~~~~~~
*/

int		main( int argc, char *argv[] )
{
BDIV	*btab;
FREQ	freq;

	// We have to initialise freq, because gcc complains that it might
	// be used uninitialised.

	memset( &freq, 0, sizeof( FREQ ) );
	if ( ! ( btab = CommandLine( argc, argv ) ) ) return( 2 );
	if ( ! CalculatePll( &freq ) ) return( 1 );
	if ( ! CalculateSio( freq.vco_f / freq.cpu_d / pdiv, btab ) ) return( 1 );
	DumpResults( argv[ 0 ], &freq, btab );
	return( 0 );
}

/****************************************************************************/
/*						Local functons										*/
/****************************************************************************/

/*
*	Processes the command
*	~~~~~~~~~~~~~~~~~~~~~
*/

static BDIV		*CommandLine( int argc, char **argv )
{
int		next, bcnt, temp;
BDIV	*btab;

	if ( argc < 3 ) {

		printf( usage, argv[ 0 ] );
		return( 0 );
	}

	for ( next = 1 ; next < argc ; next++ ) {

		// Switches start with a '-'

		if ( argv[ next ][ 0 ] != '-' ) break;

		// All switches are a single character

		if ( argv[ next ][ 2 ] ) {

			fprintf( stderr, "Unknown switch \"%s\"\n", argv[ next ] );
			return( 0 );
		}

		// The -a switch has no parameter

		if ( argv[ next ][ 1 ] == 'a' ) {

			form = 1;
			continue;
		}

		// All other switches has an additional argument

		if ( next == argc - 1 ) {

			fprintf( stderr, "Option %s needs an argument\n", argv[ next ] );
			return( 0 );
		}

		// Process the switches

		switch ( argv[ next ][ 1 ] ) {

			case 'u':

				temp = sscanf( argv[ ++next ], "%lf", &usbe );
				break;

			case 'x':

				temp = sscanf( argv[ ++next ], "%lf", &xtal );
				break;

			case 'c':

				temp = sscanf( argv[ ++next ], "%lf", &cclk );
				break;

			case 'p':

				temp = sscanf( argv[ ++next ], "%i", &pdiv );
				break;

			case 'l':

				temp = sscanf( argv[ ++next ], "%lf", &cmin );
				break;

			case 'h':

				temp = sscanf( argv[ ++next ], "%lf", &cmax );
				break;

			case 'i':

				temp = 1;
				name = argv[ ++next ];
				break;

			default:

				fprintf( stderr, "Unknown switch \"%s\"\n", argv[ next ] );
				return( 0 );
		}

		if ( temp != 1 ) {

			fprintf( stderr, "Invalid option argument \"%s\"\n", argv[ next ] );
			return( 0 );
		}
	}

	// Sanitise the values

	if ( xtal > 100 ) xtal /= 1E6;
	if ( cclk > 100 ) cclk /= 1E6;
	if ( cmin > 100 ) cmin /= 1E6;
	if ( cmax > 100 ) cmax /= 1E6;

	if ( usbe < 0    ) usbe = 0;
	if ( cmax < cclk ) cmax = cclk;
	if ( cmin > cclk ) cmin = cclk;

	// Test for limits and missing parameters

	if ( ! xtal || ! cclk ) {

		fprintf( stderr, "The -x and -c options are mandatory.\n" );
		return( 0 );
	}

	if ( xtal < 0.032768 || xtal > 40 ) {

		fprintf( stderr, "Invalid reference clock value\n" );
		return( 0 );
	}

	if ( cclk <= VCO_MIN / 256.0 || cclk > 72 ) {

		fprintf( stderr, "Invalid clock value\n" );
		return( 0 );
	}

	if ( pdiv != 1 && pdiv != 2 && pdiv != 4 && pdiv != 8 ) {

		fprintf( stderr, "Invalid peripheral clock divider value\n" );
		return( 0 );
	}

	// Work out the legth of the baud rate table and try to allocate it

	bcnt = sizeof( BDIV ) * ( argc - next + 1 );

	if ( ! ( btab = malloc( bcnt ) ) ) {

		fprintf( stderr, "Allocating %d bytes failed\n",  bcnt );
		return( 0 );
	}

	// Read in the requested baud rates

	for ( bcnt = 0 ; next < argc ; next++, bcnt++ ) {

		if ( sscanf( argv[ next ], "%i", &temp ) != 1 ||
				 temp < 1 || temp > 9999999 ) {

			fprintf( stderr, "%s is not a valid baud rate\n", argv[ next ] );
			free( btab );
			return( 0 );
		}

		btab[ bcnt ].brate = temp;
	}

	// Just to be a good citizen we check the table for duplicate
	// values and we also sort it.

	qsort( btab, bcnt, sizeof( BDIV ), BrateCompare );

	for ( next = 0 ; next < bcnt - 1 ; next++ ) {

		if ( btab[ next ].brate == btab[ next + 1 ].brate ) {

			for ( temp = next + 1 ; temp < bcnt - 1 ; temp++ )

				btab[ temp ].brate = btab[ temp + 1 ].brate;

			bcnt--;
			next--;
		}
	}

	btab[ bcnt ].brate = 0;
	return( btab );
}

/*
*	Compare function for quicksort on baud rates
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		BrateCompare( const void *a, const void *b )
{
	if ( ((BDIV *) a)->brate > ((BDIV *) b)->brate ) return( +1 );
	if ( ((BDIV *) a)->brate < ((BDIV *) b)->brate ) return( -1 );
	return( 0 );
}

/*
*	Calculate the PLL values
*	~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns !0 on success, 0 if the requirements can not be satisfied.
*/

static	int		CalculatePll( FREQ *this )
{
double	vco_f, c_err, u_err, c_min;
int		pll_n, pll_m, div_f, usb_d;
int		usb_e, c_sol, multi, gcd_v;

	c_min = 1e20;
	div_f = 0;
	usb_d = 0;
	this->vco_f = 0;

	// We do a brute-force search for the best solution (if it exists).
	// We examine all valid PLL M,N combinations and select the solution
	// with the smallest error and lowest PLL frequency.

	for ( c_sol = 0, pll_n = 1 ; pll_n <= 32 ; pll_n++ ) {

		for ( multi = 0 ; multi <= 512-6+65 ; multi++ ) {

			usb_e = 0;
			pll_m = multi < 65 ? lowf_m[ multi ] : multi - 6;
			vco_f = ( xtal * 2 * pll_m ) / pll_n;

			if ( vco_f < VCO_MIN || vco_f > VCO_MAX ) continue;

			// If USB is required, we only accept the result if
			// it can be divided down to 96MHz with an error less
			// than the allowed error.

			if ( usbe ) {

				for ( div_f = 2 ; div_f <= 256 ; div_f += 2 ) {

					if ( vco_f / div_f > 48 * ( 1 - 1e-6 * usbe ) &&
						 vco_f / div_f < 48 * ( 1 + 1e-6 * usbe ) ) {

						u_err = ( vco_f / div_f ) / 48 - 1;
						usb_d = div_f;
						break;
					}
				}

				if ( div_f > 256 ) usb_e = 1;
			}

			// We should use the lowest N value. Thus, if M and N
			// has a common divisor, we should simplify the ratio.

			if ( pll_m <= 512 )

				gcd_v = CalculateGcd( pll_m, pll_n );
			else
				gcd_v = 1;

			// Run through the CPU dividers and find the division
			// that is closest to the required value

			for ( div_f = 2 ; div_f <= 256 ; div_f += 2 ) {

				if ( vco_f / div_f > cmax ) continue;
				if ( vco_f / div_f < cmin ) continue;

				c_err = ( vco_f / div_f - cclk ) / cclk;

				if ( fabs( c_err ) > c_min ) {

					// This is worse than what we've found before

					continue;
				}

				// This is a prospective contender. If the error is the same
				// as before, we replace it but only if the VCO frequency is
				// lower than before.

				if ( c_min > fabs( c_err ) || vco_f < this->vco_f ) {

					// This solution is better than any we had before.

					if ( ! usb_e ) {

						c_min = fabs( c_err );
						this->vco_f = vco_f;
						this->pll_m = pll_m / gcd_v;
						this->pll_n = pll_n / gcd_v;
						this->usb_d = usb_d;
						this->cpu_d = div_f;
						this->cpu_e = c_err;
						this->usb_e = u_err;
					}

					// Make note that we had a CPU solution

					c_sol = 1;
				}
			}
		}
	}

	if ( ! this->vco_f ) {

		if ( c_sol )

			fprintf( stderr, "Can not synthetise the USB clock.\n" );
		else
			fprintf( stderr, "Can not synthetise the CPU clock.\n" );

		return( 0 );
	}

	return( 1 );
}

/*
*	Finds the greatest common divisor of two positive integers
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It uses the divisionless Euclidean algorithm, simplicity at its best.
*	Returns the GCD of a and b.
*/

static	int		CalculateGcd( int a, int b )
{
	if ( ! a ) return( b );

	while ( b ) {

		if ( a > b )

			a -= b;
		else
			b -= a;
	}

	return( a );
}

/*
*	Calculates the baud rate divisors from the peripheral clock
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns !0 on success, 0 if the required baud rate can't be achieved
*	within 3% error (as per the requirements of the standard).
*/

static	int		CalculateSio( double pclk, BDIV *bdiv )
{
int		i, m, a, is, ms, as;
double	b, bs, e, es;

	// We get pclk in MHz, we need it in Hz

	pclk *= 1E6;

	// Do some unnecessary inits, gcc is not smart enough

	ms = 0;
	as = 0;
	is = 0;
	bs = 0;

	while ( bdiv->brate ) {

		es = 1e6;

		// We do a brute-force search for the best solution.
		// We prefer solutions with the larger integer divisor.

		for ( a = 0 ; a < 15 ; a++ ) {

			for ( m = a+1 ; m <= 15 ; m++ ) {

				i = ( pclk / 16.0 / bdiv->brate / ( a + m ) ) * m;

				if ( i >= 65535 ) continue;
				if ( i < 1 ) i = 1;

				b = ( pclk / 16.0 / i / ( a + m ) ) * m;
				e = ( b - bdiv->brate ) / bdiv->brate;

				if ( fabs( e ) < fabs( es ) ||
					 ( fabs( e ) == fabs( es ) && i > is ) ) {

					bs = b;
					es = e;
					is = i;
					as = a;
					ms = m;
				}

				i++;
				b = ( pclk / 16.0 / i / ( a + m ) ) * m;
				e = ( b - bdiv->brate ) / bdiv->brate;

				if ( fabs( e ) < fabs( es ) ||
					 ( fabs( e ) == fabs( es ) && i > is ) ) {

					bs = b;
					es = e;
					is = i;
					as = a;
					ms = m;
				}
			}
		}

		if ( fabs( es ) > 0.03 ) {

			fprintf( stderr, "Can't synthetise %d baud\n", bdiv->brate );
			return( 0 );
		}

		bdiv->final = bs;
		bdiv->error = 100 * es;
		bdiv->i_val = is;
		bdiv->a_val = as;
		bdiv->m_val = ms;

		bdiv++;
	}

	return( 1 );
}

/*
*	Write the results to the stdout
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static void		DumpResults( char *prg, FREQ *pll, BDIV *sio )
{
double	pck;
int		res;

	printf( "/*\n* This file was generated by the %s tool.\n*/\n", prg );
	printf( "\n#ifndef %s\n", name );
	printf( "#define %s\n\n", name );

	printf( "/*\n* PLL calculations:\n*\n" );
	printf( "*\tUSB is needed:        %s\n", usbe ? "yes" : "no" );

	if ( xtal < 1 )

		printf( "*\tReference clock: %14.6f kHz\n", xtal * 1e3 );
	else
		printf( "*\tReference clock: %14.6f MHz\n", xtal );

	printf( "*\tRequired CPU clock: %11.6f MHz\n", cclk );
	printf( "*\tMax. CPU clock:     %11.6f MHz.\n", cmax );
	printf( "*\tMin. CPU clock:     %11.6f MHz.\n", cmin );
	if ( usbe ) printf( "*\tUSB error limit:    %11.6f %%\n", usbe * 1E-4 );
	printf( "*\tAchieved CPU clock: %11.6f MHz\n", pll->vco_f / pll->cpu_d );
	printf( "*\tVCO frequency:      %11.6f MHz\n", pll->vco_f );
	printf( "*\tCPU clock error:    %+11.6f %%\n", pll->cpu_e * 100 );
	if ( usbe ) printf( "*\tUSB clock error:    %+11.6f %%\n", pll->usb_e*100);
	printf( "*/\n\n" );

	printf( "#define CLOCK_XTAL   %9d\n", (int)( 1E6 * xtal + .5 ) );
	printf( "#define CLOCK_CCLK   %9d\n",
		    (int)(1E6 * pll->vco_f / pll->cpu_d + .5) );
	printf( "#define CLOCK_PLL_M    %7d\n", pll->pll_m );
	printf( "#define CLOCK_PLL_N    %7d\n", pll->pll_n );
	printf( "#define CLOCK_CPU_DIV  %7d\n", pll->cpu_d );

	if ( usbe )

		printf( "#define CLOCK_USB_DIV  %7d\n", pll->usb_d );

	printf( "#define CLOCK_MAM_TIM  %7d\n\n",
		   (int) ceil( FLASH_T * 1E-3 * (pll->vco_f / pll->cpu_d) ) );

	if ( ! sio->brate ) {

		printf( "#endif\n" );
		return;
	}

	pck = pll->vco_f / pll->cpu_d / pdiv * 1e6;

	printf( "/*\n" );
	printf( "* UART calculations for peripheral clock of %.6f MHz\n*\n",
		    pck / 1E6 );
	printf( "*\tNominal       Achieved    Error\n" );
	printf( "*\t-------------------------------\n" );

	for ( res = 0 ; sio[ res ].brate ; res++ ) {

		printf( "*\t%7d     ", sio[ res ].brate );
		printf( "%10.2f   ", sio[ res ].final );
		printf( "%+4.2f%%\n", sio[ res ].error );
	}

	printf( "*/\n\n" );

	printf( "#define UART_CDIV %d\n", pdiv );

	if ( ! form ) {

		for ( res = 0 ; sio[ res ].brate ; res++ ) {

			printf( "#define UART_I_%-7d %d\n",
				    sio[ res ].brate, sio[ res ].i_val );
			printf( "#define UART_M_%-7d %d\n",
				    sio[ res ].brate, sio[ res ].m_val );
			printf( "#define UART_A_%-7d %d\n",
				    sio[ res ].brate, sio[ res ].a_val );
			printf( "\n" );
		}
	}
	else {

		printf( "#define UART_CLOCK_TABLE_INIT \\\n" );

		for ( res = 0 ; sio[ res ].brate ; ) {

			printf( "\t{ %8d, %5d, %2d, %2d }",
				    sio[ res ].brate, sio[ res ].i_val,
				    sio[ res ].m_val, sio[ res ].a_val );

			if ( sio[ ++res ].brate ) printf( ", \\" );
			printf( "\n" );
		}
	}

	printf( "\n#endif\n" );
}

