/*
 * scale.c: A clock scaling implementation for the SA-1100 CPU
 *
 * Copyright (C) 2000, The Delft University of Technology
 *   portions Copyright 2000, Lernout & Hauspie Speech Products, N.V.
 *
 * Portions are Copyright (C) 2000 Lernout & Hauspie Speech Products, N.V.
 *    released under GNU Public Licence (GPL) version 2.
 *
 * Authors: 
 * - Johan Pouwelse (J.A.Pouwelse@its.tudelft.nl): initial version
 * - Erik Mouw (J.A.K.Mouw@its.tudelft.nl): major rewrite for linux-2.3.99
 *
 * Portions by:
 * - Eik Bunce (ebunce@lhsl.com): Added support for Intel Assabet and Bitsy
 * 
 * This software has been developed for the LART computing board
 * (http://www.lart.tudelft.nl/). The development has been sponsored by
 * the Mobile MultiMedia Communications (http://www.mmc.tudelft.nl/)
 * and Ubiquitous Communications (http://www.ubicom.tudelft.nl/)
 * projects.
 *
 * The authors can be reached at:
 *
 *  Erik Mouw
 *  Information and Communication Theory Group
 *  Faculty of Information Technology and Systems
 *  Delft University of Technology
 *  P.O. Box 5031
 *  2600 GA Delft
 *  The Netherlands
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 * Theory of operations
 * ====================
 * 
 * Clock scaling can be used to lower the power consumption of the CPU
 * core. This will give you a somewhat 

 * The SA-1100 has a single register to change the core clock speed:
 *
 *   PPCR      0x90020014    PLL config
 *
 * However, the DRAM timings are closely related to the core clock
 * speed, so we need to change these, too. The used registers are:
 *
 *   MDCNFG    0xA0000000    DRAM config
 *   MDCAS0    0xA0000004    Access waveform
 *   MDCAS1    0xA0000008    Access waveform
 *   MDCAS2    0xA000000C    Access waveform 
 * The SA-1110 also has the DRAM timing register
 *   MDREFR    0xA000001C    DRAM Refresh control register
 *
 * Care must be taken to change the DRAM parameters the correct way,
 * because otherwise the DRAM becomes unusable and the kernel will
 * crash. 
 *
 * The simple solution to avoid a kernel crash is to put the actual
 * clock change in ROM and jump to that code from the kernel. The main
 * disadvantage is that the ROM has to be modified, which is not
 * possible on all SA-1100 platforms. Another disadvantage is that
 * jumping to ROM makes clock switching unecessary complicated.
 *
 * The idea behind this driver is that the memory configuration can be
 * changed while running from DRAM (even with interrupts turned on!)
 * as long as all re-configuration steps yield a valid DRAM
 * configuration. The advantages are clear: it will run on all SA-1100
 * platforms, and the code is very simple.
 * 
 * If you really want to understand what is going on in
 * change_clock_speed(), you'll have to read sections 8.2, 9.5.7.3,
 * and 10.2 from the "Intel StrongARM SA-1100 Microprocessor
 * Developers Manual" or sections 8.2, 10.2, 10.4 from the "Intel
 * StrongARM SA-1110 Microprocessor Developers Manual" or
 * (both available for free from Intel) 
 *
 *
 * Notes
 * =====
 *
 * This driver works directly on the LART. If your system happens to
 * use 50ns EDO DRAM, it will work, too.
 *
 * This driver works directly on the Assabet. If your system happens to
 * use 125Mhz SDRAM in banks 0/1, it will work, too.
 *
 * The SA-1100 can be overclocked. CPUs specced at 190 MHz will safely
 * run at 250 MHz; CPUs specced at 220 MHz can do 280 MHz. If you want
 * more performance, you can increase the core voltage, but this is
 * not without risk.
 *
 * The SA-1110 can be overclocked. CPUs specced at 206 MHz will safely
 * run at up to 236 MHz.
 *
 * To Do
 * =====
 *
 * - Add proper voltage scaling for the LART
 * - Change the PCMCIA timings as well
 *
 *
 * Usage
 * =====
 *
 * insmod scale.o [max_speed=xx] [debug=1]
 * 
 * Where max_speed is the maximum allowed CPU speed (9 = 190 MHz, 11 =
 * 220HMz, 15 = 280MHz), and debug is to enable debug information.
 *
 * 2000-06-05	Erik Bunce	Make more flexible to support SA1110
 *				based boards, and add Assabet support.
 * 2000-08-21	Erik Bunce	Add initial Compaq Bitsy support.
 *				Default speed and limit per platform support.
 * 2000-08-23	Erik Bunce	Add timing loop recalibration.
 * 2001-04-17	John Dorsey	Added PCMCIA timing support.
 *
 */


#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>		/* because we are a module */
#include <linux/init.h>                 /* for the __init macros */
#include <linux/proc_fs.h>		/* all the /proc functions */
#include <linux/ioport.h>
#include <asm/uaccess.h>		/* to copy to/from userspace */
#include <asm/arch/hardware.h> 
#include <asm/arch/pcmcia.h>
#include <asm/delay.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif

#define MODULE_NAME "scale"

/* maximum speed setting in PPCR*/
#define SCALE_MAX_SPEED (21)
/* only the lower five bits are defined in the PPCR */
#define PPCR_MASK (0x0000001f)

/* Registers for SA1100 processor */
typedef struct sa1100_freq_regs {
	u32 mdcnfg;
	u32 mdcas0; 
	u32 mdcas1;
	u32 mdcas2;
} sa1100_freq_regs_t;

/* Registers for SA1110 processor w/ SDRAM in memory banks 0/1 */
typedef struct sa1110_freq_regs {
	u32 mdrefr;
	u32 mdcas0_2;
} sa1110_freq_regs_t;

/* Info about platform */
typedef struct scale_platform_data {
	int cpu_is_sa1110;
	int default_speed;
	int speed_limit;
	int * speeds;
	union {
		const sa1100_freq_regs_t*	settings_1100;
		const sa1110_freq_regs_t*	settings_1110;
	} platform_settings;
} scale_platform_data_t;

/* This is for boards using a 3.6864 MHz Crystal */
static int sa11x0_3_6864_speeds[SCALE_MAX_SPEED + 1] = {
	59, /* 00 */
	74, /* 01 */
	89, /* 02 */
	103, /* 03 */
	118, /* 04 */
	133, /* 05 */
	148, /* 06 */
	162, /* 07 */
	177, /* 08 */
	192, /* 09 */
	206, /* 10 */
	221, /* 11 */
	236, /* 12 */
	251, /* 13 */
	265, /* 14 */
	280, /* 15 */
	295, /* 16 */
	310, /* 17 */
	324, /* 18 */
	339, /* 19 */
	354, /* 20 */
	369, /* 21 */
};

#if 0
/* This is for boards using a 3.5795 MHz Crystal */
static int sa11x0_3_5795_speeds[SCALE_MAX_SPEED + 1] = {
	57, /* 00 */
	72, /* 01 */
	86, /* 02 */
	100, /* 03 */
	115, /* 04 */
	129, /* 05 */
	143, /* 06 */
	158, /* 07 */ 
	172, /* 08 */
	186, /* 09 */
	201, /* 10 */
	215, /* 11 */
	229, /* 12 */
	243, /* 13 */
	258, /* 14 */
	272, /* 15 */
	286, /* 16 */
	301, /* 17 */
	315, /* 18 */
	329, /* 19 */
	344, /* 20 */
	358, /* 21 */
};
#endif

#if defined(CONFIG_SA1100_LART)
static const sa1100_freq_regs_t lart_freq_regs[SCALE_MAX_SPEED + 1] =
{
	/* { mdcnfg, mdcas0, mdcas1, mdcas2 } */ /* clock frequency */
	{ 0x00dc88a3, 0xcccccccf, 0xfffffffc, 0xffffffff }, /*  59.0 MHz */
	{ 0x011490a3, 0xcccccccf, 0xfffffffc, 0xffffffff }, /*  73.7 MHz */
	{ 0x014e90a3, 0xcccccccf, 0xfffffffc, 0xffffffff }, /*  88.5 MHz */
	{ 0x01889923, 0xcccccccf, 0xfffffffc, 0xffffffff }, /* 103.2 MHz */
	{ 0x01c29923, 0x9999998f, 0xfffffff9, 0xffffffff }, /* 118.0 MHz */
	{ 0x01fb2123, 0x9999998f, 0xfffffff9, 0xffffffff }, /* 132.7 MHz */
	{ 0x02352123, 0x3333330f, 0xfffffff3, 0xffffffff }, /* 147.5 MHz */
	{ 0x026b29a3, 0x38e38e1f, 0xfff8e38e, 0xffffffff }, /* 162.2 MHz */
	{ 0x02a329a3, 0x71c71c1f, 0xfff1c71c, 0xffffffff }, /* 176.9 MHz */
	{ 0x02dd31a3, 0xe38e383f, 0xffe38e38, 0xffffffff }, /* 191.7 MHz */
	{ 0x03153223, 0xc71c703f, 0xffc71c71, 0xffffffff }, /* 206.4 MHz */
	{ 0x034fba23, 0xc71c703f, 0xffc71c71, 0xffffffff }, /* 221.2 MHz */
	{ 0x03853a23, 0xe1e1e07f, 0xe1e1e1e1, 0xffffffe1 }, /* 235.9 MHz */
	{ 0x03bf3aa3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3 }, /* 250.7 MHz */
	{ 0x03f7c2a3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3 }, /* 265.4 MHz */
	{ 0x0431c2a3, 0x878780ff, 0x87878787, 0xffffff87 }, /* 280.2 MHz */
	{ 0x0469caa3, 0x1e0f00ff, 0xe0f0783c, 0xffff83c1 }, /* 294.9 MHz */
	{ 0x049f4b23, 0xf07c01ff, 0x7c1f07c1, 0xff07c1f0 }, /* 309.7 MHz */
	{ 0x04d9d323, 0xf07c01ff, 0x7c1f07c1, 0xff07c1f0 }, /* 324.4 MHz */
	{ 0x0511d323, 0xc0f803ff, 0x81f03e07, 0x03e07c0f }, /* 339.1 MHz */
	{ 0x05475be3, 0xe38e383f, 0xffe38e38, 0xffffffff }, /* 353.9 MHz */
	{ 0x0581dbe3, 0xe38e383f, 0xffe38e38, 0xffffffff }, /* 368.6 MHz */
};

static const scale_platform_data_t lart_platform_data =
{
	0,
	11, /* Default speed: 221.2 MHz */
	11, /* Speed limit: 221.2 MHz */
	sa11x0_3_6864_speeds,
	{ lart_freq_regs }
};
#endif

#if defined(CONFIG_SA1100_ASSABET) 
static const sa1110_freq_regs_t assabet_freq_regs[SCALE_MAX_SPEED + 1] =
{
	/* mdrefr, mdcas0_2 */ /* clock frequency */
	{ 0x00000001, 0xaaaaaa7f }, /*  59.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  73.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  88.5 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 103.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 118.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 132.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 147.5 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 162.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 176.9 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 191.7 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 206.4 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 221.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 235.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 250.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 265.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 280.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 294.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 309.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 324.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 339.1 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 353.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 368.6 MHz */
};

static const scale_platform_data_t assabet_platform_data =
{
	1,
	10, /* Default speed: 206.4 MHz */
	12, /* Speed limit: 235.9 MHz - determined empirically */
	sa11x0_3_6864_speeds,
	{  assabet_freq_regs }
};
#endif

#if defined(CONFIG_SA1100_BITSY)
/* These values haven't been specifically tuned to bitsy yet,
 * but it seems similar enough to assabet to mostly work,
 * although w/ the 100MHz rated SDRAM, speeds above 206.4 MHz
 * will probably need tweeking
*/
static const sa1110_freq_regs_t bitsy_freq_regs[SCALE_MAX_SPEED + 1] =
{
	/* mdrefr, mdcas0_2 */ /* clock frequency */
	{ 0x00000001, 0xaaaaaa7f }, /*  59.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  73.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  88.5 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 103.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 118.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 132.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 147.5 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 162.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 176.9 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 191.7 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 206.4 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 221.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 235.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 250.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 265.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 280.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 294.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 309.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 324.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 339.1 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 353.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 368.6 MHz */
};

static const scale_platform_data_t bitsy_platform_data =
{
	1,
	10,  /* Default speed: 206.4 MHz */
	12, /* Speed limit: 235.9 MHz - heard rumor YMMV */
	sa11x0_3_6864_speeds,
	{  bitsy_freq_regs }
};
#endif

#if defined(CONFIG_SA1100_FREEBIRD) 
static const sa1110_freq_regs_t freebird_freq_regs[SCALE_MAX_SPEED + 1] =
{
	/* mdrefr, mdcas0_2 */ /* clock frequency */
	{ 0x00000001, 0xaaaaaa7f }, /*  59.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  73.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  88.5 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 103.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 118.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 132.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 147.5 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 162.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 176.9 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 191.7 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 206.4 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 221.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 235.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 250.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 265.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 280.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 294.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 309.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 324.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 339.1 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 353.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 368.6 MHz */
};

static const scale_platform_data_t freebird_platform_data =
{
	1,
	10, /* Default speed: 206.4 MHz */
	12, /* Speed limit: 235.9 MHz - determined empirically */
	sa11x0_3_6864_speeds,
	{  freebird_freq_regs }
};
#endif

#if defined(CONFIG_SA1100_PFS168)
/* Assuming PFS168 timing is identical to Assabet!
 */
static const sa1110_freq_regs_t pfs168_freq_regs[SCALE_MAX_SPEED + 1] =
{
	/* mdrefr, mdcas0_2 */ /* clock frequency */
	{ 0x00000001, 0xaaaaaa7f }, /*  59.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  73.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /*  88.5 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 103.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 118.0 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 132.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 147.5 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 162.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 176.9 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 191.7 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 206.4 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 221.2 MHz */
	{ 0x00000000, 0xaaaaaa9f }, /* 235.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 250.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 265.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 280.2 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 294.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 309.7 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 324.4 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 339.1 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 353.9 MHz */
	{ 0x00000001, 0xaaaaaa7f }, /* 368.6 MHz */
};

static const scale_platform_data_t pfs168_platform_data =
{
	1,
	10, /* Default speed: 206.4 MHz */
	10, /*  Be conservative, assume speed limit: 206.4 MHz */
	sa11x0_3_6864_speeds,
	{  pfs168_freq_regs }
};
#endif

/* Declare variables */
static const scale_platform_data_t *current_platform;
static int cpu_is_sa1110 = 0;
static int debug = 0;
static u32 current_speed;
#if	!defined(CONFIG_SA1100_PFS168)
static int max_speed = 11; /* 11 = 220MHz, safe for all CPUs */
static int speed_limit = 11;
#else	!defined(CONFIG_SA1100_PFS168)
static int max_speed = 10; /* Be conservative, assume  max_speed 10 (206MHz) for PFS168 */
static int speed_limit = 10;
#endif	/* !defined(CONFIG_SA1100_PFS168) */
extern unsigned long loops_per_jiffy;

/* Forward decl function(s) */
static void recalibrate_delay(void);

static int change_clock_speed(u32 new_speed)
{
	/* sanity check */
	if((new_speed < 0) || (new_speed > speed_limit)) {
		if(debug)
			printk(KERN_ERR MODULE_NAME 
			       ": clock speed out of range\n");

		return(-EINVAL);
	}

	/* don't do anything if the speed is the same */
	if(new_speed == current_speed) {
		if(debug)
			printk(KERN_INFO MODULE_NAME
			       ": keeping current clock speed\n");

		return(0);
	}

	/* Is this an SA1110, it has to be handled differently... */
	if (cpu_is_sa1110) {
		const sa1110_freq_regs_t *new_settings;
		u32 newMDREFR;

		new_settings =
			&current_platform->platform_settings.settings_1110[new_speed];

		if (new_settings->mdrefr)
			newMDREFR = MDREFR | MDREFR_K1DB2;
		else
			newMDREFR = MDREFR & ~MDREFR_K1DB2;

		/* No risk, no fun: run with interrupts on! */
		if(new_speed > current_speed) {
			/* We're going FASTER, so first relax the memory
                	 * timings before changing the core frequency 
			 */
			MDREFR = newMDREFR;

			/* The order of these statements IS important
		 	*/
			MDCAS2 = new_settings->mdcas0_2;
			MDCAS0 = new_settings->mdcas0_2;

			/* change the clock frequency */
			PPCR = new_speed & PPCR_MASK;
		} else {
			/* We're going SLOWER: first decrease the core
		 	* frequency and then tighten the memory settings.
		 	*/

			/* change the clock frequency */
			PPCR = new_speed & PPCR_MASK;
		
			/* Set the memory access clock */
			MDREFR = newMDREFR;

			/* The order of these statements IS important
		 	*/
			MDCAS0 = new_settings->mdcas0_2;
			MDCAS2 = new_settings->mdcas0_2;
		}
	}
	else {
		const sa1100_freq_regs_t *new_settings;
		new_settings =
			&(current_platform->platform_settings.settings_1100[new_speed]);
		
		/* No risk, no fun: run with interrupts on! */
		if(new_speed > current_speed) {
			/* We're going FASTER, so first relax the memory
                	 * timings before changing the core frequency 
			 */
		
			/* Half the memory access clock */
			MDCNFG |= MDCNFG_CDB2;

			/* The order of these statements IS important, keep 8
                 	* pulses!!
		 	*/
			MDCAS2 = new_settings->mdcas2;
			MDCAS1 = new_settings->mdcas1;
			MDCAS0 = new_settings->mdcas0;
			MDCNFG = new_settings->mdcnfg;

			/* change the clock frequency */
			PPCR = new_speed & PPCR_MASK;
		} else {
			/* We're going SLOWER: first decrease the core
		 	* frequency and then tighten the memory settings.
		 	*/

			/* change the clock frequency */
			PPCR = new_speed & PPCR_MASK;
		
			/* Half the memory access clock */
			MDCNFG |= MDCNFG_CDB2;

			/* The order of these statements IS important, keep 8
                 	* pulses!!
		 	*/
			MDCAS0 = new_settings->mdcas0;
			MDCAS1 = new_settings->mdcas1;
			MDCAS2 = new_settings->mdcas2;
			MDCNFG = new_settings->mdcnfg;
		}
	}

#ifdef CONFIG_PCMCIA_SA1100
	if(sa1100_pcmcia_adjust_timing() < 0)
	  return -1;
#endif

	printk(KERN_INFO MODULE_NAME
	       ": old speed %d (%d MHz), new speed %d (%d MHz),"
	       " default speed %d (%d MHz)\n",
	       current_speed,
	       current_platform->speeds[current_speed],
	       new_speed,
	       current_platform->speeds[new_speed],
	       current_platform->default_speed,
	       current_platform->speeds[current_platform->default_speed]);
	
	/* update the current speed */
	current_speed = new_speed;

	/* recalibrate delay info */
	recalibrate_delay();
	
	return(0);
}


/* The recalibrate_delay() code is a brazenly hacked version of
 * init/main.c's calibrate_delay() code.
 *
 * NOTE: Do NOT touch global loops_per_jiffies except for storing result.
 *       Otherwise could seriously mess up other users of the global.
 */

/* This is the number of bits of precision for the loops_per_jiffy.  Each
   bit takes on average 1.5/HZ seconds.  This (like the original) is a little
   better than 1% */
#define LPS_PREC 8

void recalibrate_delay(void)
{
	unsigned long ticks, loopbit;
	int lps_precision = LPS_PREC;
	unsigned long tmp_loops_per_jiffy = (1<<12);

	tmp_loops_per_jiffy = (1<<12);

	printk("Recalibrating delay loop... ");
	while (tmp_loops_per_jiffy <<= 1) {
		/* wait for "start of" clock tick */
		ticks = jiffies;
		while (ticks == jiffies)
			/* nothing */;
		/* Go .. */
		ticks = jiffies;
		__delay(tmp_loops_per_jiffy);
		ticks = jiffies - ticks;
		if (ticks)
			break;
	}

/* Do a binary approximation to get tmp_loops_per_jiffy set to equal one clock
   (up to lps_precision bits) */
	tmp_loops_per_jiffy >>= 1;
	loopbit = tmp_loops_per_jiffy;
	while ( lps_precision-- && (loopbit >>= 1) ) {
		tmp_loops_per_jiffy |= loopbit;
		ticks = jiffies;
		while (ticks == jiffies);
		ticks = jiffies;
		__delay(tmp_loops_per_jiffy);
		if (jiffies != ticks)	/* longer than 1 tick */
			tmp_loops_per_jiffy &= ~loopbit;
	}

/* Update the global loops_per_jiffy */
	loops_per_jiffy = tmp_loops_per_jiffy;

/* Round the value and print it */	
	printk("%lu.%02lu BogoMIPS\n",
		loops_per_jiffy/(500000/HZ),
		(loops_per_jiffy/(5000/HZ)) % 100);
}


static int read_proc_scale(char *page, char **start, off_t off,
			       int count, int *eof, void *data)
{
	int len;

	MOD_INC_USE_COUNT;

	if(debug)
		len = sprintf(page, "%d (SA11%d0 at %d MHz)\n", 
			      current_speed,  cpu_is_sa1110,
			      current_platform->speeds[current_speed]);
	else
		len = sprintf(page, "%d\n", current_speed);

	MOD_DEC_USE_COUNT;

	return(len);
}




static int write_proc_scale(struct file *file, const char *buffer,
				unsigned long count, void *data)
{
	u32 new_speed;
	char *endp;
	unsigned long res;

	MOD_INC_USE_COUNT;

	/* get the value, automatically detect the base */
	new_speed = simple_strtoul(buffer, &endp, 0);

	/* set the new speed */
	res = change_clock_speed(new_speed);

	MOD_DEC_USE_COUNT;

	if(res < 0)
		return(res);
	else
		return(count + (endp - buffer));
}


#ifdef CONFIG_PM
static u32 resume_speed;
static struct pm_dev *clock_scaling_pm_dev;
static int clock_scaling_pm_callback(struct pm_dev *pm_dev, pm_request_t event, void *data)
{
#if 0  /* events below aren't defined */
        switch (event) {
        case PM_SLOW_CLOCK:
                if (resume_speed < current_speed)
                        resume_speed = current_speed;
                /* change to minimum speed */
                printk(__FUNCTION__ ": event=%d current_speed=%d resume_speed=%d\n", event, current_speed, resume_speed);
                change_clock_speed(current_platform->speeds[0]);
                break;
        case PM_UNSLOW_CLOCK:
                if (resume_speed != 0) {
                        printk(__FUNCTION__ ": event=%d current_speed=%d resume_speed=%d\n", event, current_speed, resume_speed);
                        change_clock_speed(resume_speed);
                        resume_speed = 0;
                }
                break;
        }
#endif
        return 0;
}
#endif

static int __init init_clock_scaling(void)
{
	struct proc_dir_entry *entry;

#if defined(CONFIG_SA1100_LART)
	if (machine_is_lart())
		current_platform = &lart_platform_data;
	else
#endif		
#if defined(CONFIG_SA1100_ASSABET)
	if (machine_is_assabet() || machine_is_freebird()) 
		current_platform = &assabet_platform_data;
	else
#endif
#if defined(CONFIG_SA1100_BITSY)
	if (machine_is_bitsy()) 
		current_platform = &bitsy_platform_data;
	else
#endif
#if defined(CONFIG_SA1100_FREEBIRD)
	if (machine_is_freebird())
		current_platform = &freebird_platform_data;
	else
#endif
#if defined(CONFIG_SA1100_PFS168)
	if (machine_is_pfs168()) 
		current_platform = &pfs168_platform_data;
	else
#endif
		current_platform = NULL;

	/* if platform isn't supported, then don't do any more */
	if (current_platform == NULL) {
		printk(KERN_WARNING MODULE_NAME
		       ": clock scaling not supported here!\n");
		return(-EINVAL);
	}
	
	/* Is the machine a 1110 based board? */
	cpu_is_sa1110 = current_platform->cpu_is_sa1110;
	
	/* sanity check on the max_speed variable */
	if((max_speed < 0) || (max_speed > SCALE_MAX_SPEED)) {
		printk(KERN_WARNING MODULE_NAME ": max_speed out of range\n");
		return(-EINVAL);
	}

	/* Default to platform specific speed limit */
	speed_limit = current_platform->speed_limit;

	/* If max speed is greater than platform speed limit, use it */
	if (max_speed > speed_limit)
		speed_limit = max_speed;
	
	/* get the current speed */
	current_speed = PPCR & PPCR_MASK;

	/* sanity check on current speed */
	if(current_speed > speed_limit) {
		printk(KERN_ERR MODULE_NAME 
		       ": current clock speed out of range...\n");
		/*return(-ENOENT);*/
	}
		
	/* create proc entry */
	entry = create_proc_entry(MODULE_NAME, 
				  S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH, 
				  &proc_root);
	if(entry) {
		entry->read_proc = read_proc_scale;
		entry->write_proc = write_proc_scale;
	} else {
		printk(KERN_ERR MODULE_NAME 
		       ": can't create /proc/" MODULE_NAME "\n");
		return(-ENOMEM);
	}

	/* reserve I/O regions */
	request_region((unsigned long)&PPCR,   0x04, MODULE_NAME);
	request_region((unsigned long)&MDCNFG, 0x10, MODULE_NAME);
	if (machine_is_assabet() || machine_is_pfs168())
		request_region((unsigned long)&MDREFR, 0x04, MODULE_NAME);
#ifdef CONFIG_SA1100_FREEBIRD
	if (machine_is_freebird())
		request_region((unsigned long)&MDREFR,0x04,MODULE_NAME);
#endif

#ifdef CONFIG_PM
        clock_scaling_pm_dev = pm_register(PM_SYS_DEV, 0, clock_scaling_pm_callback);
#endif        

	printk(KERN_INFO MODULE_NAME
	       ": SA-11%d0 clock scaling initialized, clock speed is %d MHz\n",
	       cpu_is_sa1110, current_platform->speeds[current_speed]);

	/* everything went OK */
	return(0);
}




static void __exit cleanup_clock_scaling(void)
{
	/* Module wasn't initialized */
	if (current_platform == NULL)
		return;
	
	/* release I/O regions */
	release_region((unsigned long)&MDCNFG, 0x10);
	release_region((unsigned long)&PPCR,   0x04);
	if (machine_is_assabet() || machine_is_pfs168())
	  release_region((unsigned long)&MDREFR, 0x04);
#ifdef CONFIG_SA1100_FREEBIRD
	if (machine_is_freebird())
		release_region((unsigned long)&MDREFR,0x04);
#endif

	/* remove proc entry */
	remove_proc_entry(MODULE_NAME, &proc_root);

#ifdef CONFIG_PM
        pm_unregister(clock_scaling_pm_dev);
#endif        

	printk(KERN_INFO MODULE_NAME
	       ": SA-11%d0 clock scaling released, clock speed is %d MHz\n",
	       cpu_is_sa1110, current_platform->speeds[current_speed]);
}




module_init(init_clock_scaling);
module_exit(cleanup_clock_scaling);

MODULE_AUTHOR("Johan Pouwelse and Erik Mouw");
MODULE_DESCRIPTION("SA-1100 clock scaling");
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Enable debug mode");
MODULE_PARM(max_speed, "i");
MODULE_PARM_DESC(max_speed, "Maximum allowed clock speed");

EXPORT_NO_SYMBOLS;
