/*
 *  File Name	    : linux/arch/arm/mach-mp200/pm_pmu.c
 *  Function	    : pm_pmu
 *  Release Version : Ver 1.00
 *  Release Date    : 2006/03/20
 *
 *  Copyright (C) NEC Electronics Corporation 2005-2006
 *
 *
 *  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 Free Softwere Foundation; either version 2
 *  of 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 warrnty 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.
 *
 */

/*
 * Copyright 2006,2007 Sony Corporation. 
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>

#include <asm/irq.h>
#include <asm/io.h>
#include <asm/errno.h>

#include <asm/arch/smu.h>
#include <asm/arch/pmu.h>

#include "uwire.h"
#include "time.h"
#include "pm.h"

static void (*change_flash_read_mode) (struct flash_read_param *);
static struct flash_read_param flash_read_param_fullspeed;
static struct flash_read_param flash_read_param_economy = {
	.smu_ab0_cs0readctrl = SMU_AB0_READMODE_SINGLE0,
	.smu_ab0_cs1readctrl = SMU_AB0_READMODE_SINGLE0,
	.smu_ab0_flashrcr = SMU_AB0_ASYNC_READ_MODE,
};

/* #define  PM_DEBUG_CLK */

#define PLL_KIND_NUM 5

static struct pll_data pll_tbl[PLL_KIND_NUM] =
{
  {
    .param = { 0x00000001, 0x00090000, 0x00040000 },
    .mode  = MP200_PLL_248MHZ,
    .lpj   = MP200_LPJ_248MHZ,
  },
  { 
    .param = { 0x00000005, 0x00050000, 0x00020000 },
    .mode  = MP200_PLL_164MHZ, 
    .lpj   = MP200_LPJ_164MHZ,
  },
  { 
    .param = { 0x00000001, 0x01040000, 0x00000000 },
    .mode  = MP200_PLL_124MHZ,
    .lpj   = MP200_LPJ_124MHZ,
  },
  {
    .param = { 0x00000001, 0x02020000, 0x00000000 },
    .mode  = MP200_PLL_83MHZ,
    .lpj   = MP200_LPJ_83MHZ,
  },
  { 
    .param = { 0x00000001, 0x04010000, 0x00000000 }, 
    .mode  = MP200_PLL_50MHZ, 
    .lpj   = MP200_LPJ_50MHZ,
  },
};

void (*mp200_change_pll_on_sram) (struct pll_param *);
void (*mp200_change_osc_on_sram) (void);

static inline int get_idx(int x) {
	int i;	
	for(i=0;i<PLL_KIND_NUM;i++) {
		if(pll_tbl[i].mode == x) {
#ifdef PM_DEBUG_CLK
			printk("get_idx. idx=%d mode=%d \n", i, pll_tbl[i].mode);
#endif
			return i;
		}
	}
	return 0;
}


void mp200_pm_set_uwire_clock(void)
{
	unsigned int hpll_p_val;
	unsigned int hpll_m_val;
	unsigned int hpll_n_val;
	unsigned int lpll_ctrl1;
	unsigned int pll1;

	mp200_pmu_close_clockgate(MP200_CLOCKGATE_MWI_SCLK);
	mp200_pmu_close_clockgate(MP200_CLOCKGATE_MWI_PCLK);

	switch (mp200_pm_get_pll()) {
	case MP200_PLL_12MHZ:
		outl((DIV_12M - 1), SMU_MWI_SCLK_DIV);
		break;
	case MP200_PLL_164MHZ:
		/* PLL is L */
		lpll_ctrl1 = inl(SMU_LPLL_CTRL1);
		pll1 = ((3750 + lpll_ctrl1*125) * 32768 )/1000;	/* 32.768kHz(RTC) */
		outl( (pll1 / 3000) - 1, SMU_MWI_SCLK_DIV );

#ifdef PM_DEBUG
		printk(KERN_INFO "%s(): sclk src=%u.%3u, divisor=%u\n",
		       __FUNCTION__, pll1 / 1000, pll1 % 1000, pll1 / 3000);
#endif
		break;
	case MP200_PLL_248MHZ:
	case MP200_PLL_124MHZ:
	case MP200_PLL_83MHZ:
	case MP200_PLL_50MHZ:
		/* PLL is H */
		hpll_n_val = (inl(SMU_HPLL_CTRL1) & MASK_HPLL_N) + 1;
		hpll_m_val = (inl(SMU_HPLL_CTRL2) & MASK_HPLL_M) + 1;
		hpll_p_val = (inl(SMU_HPLL_CTRL3) & MASK_HPLL_P);

		pll1 = ((12000000/1000) * hpll_n_val) / hpll_m_val;	/* OSC(6-12MHz ) */
		if( hpll_p_val != 0 ) {
			pll1 >>= 1;
		}
		outl( (pll1 / 3000) - 1, SMU_MWI_SCLK_DIV );
#ifdef PM_DEBUG
		printk(KERN_INFO "%s(): sclk src=%u.%3u, divisor=%u\n",
		       __FUNCTION__, pll1 / 1000, pll1 % 1000, pll1 / 3000);
#endif
		break;
	default:
		printk(KERN_INFO "%s(): Unknown SCLK Source\n", __FUNCTION__);
		outl((DIV_12M - 1), SMU_MWI_SCLK_DIV);
		break;
	}

	mp200_pmu_open_clockgate(MP200_CLOCKGATE_MWI_PCLK);
}

void mp200_pm_update_lpj(void)
{
	unsigned int mode = mp200_pm_get_pll();
	switch (mode) {
	case MP200_PLL_248MHZ:
	case MP200_PLL_164MHZ:
	case MP200_PLL_124MHZ:
	case MP200_PLL_83MHZ:
	case MP200_PLL_50MHZ:
		loops_per_jiffy = pll_tbl[get_idx(mode)].lpj;
		timer_set_clock_parm(TIMER_FULLSPEED);
		break;
	case MP200_PLL_12MHZ:
		loops_per_jiffy = MP200_LPJ_ECONOMY;
		timer_set_clock_parm(TIMER_ECONOMY);
		break;
	default:
		loops_per_jiffy = MP200_LPJ_ECONOMY;
		timer_set_clock_parm(TIMER_ECONOMY);
		break;
	}
}

/*
 * economy  ->  fullspeed
 */
void mp200_pm_eco_to_full(unsigned int new)
{
	if (mp200_pm_get_pll() == new) {
#ifdef CONFIG_SONY_ICX1087
        /* In case of resume */
        /* 2006.08.17 ish : HPLL(248M)/49 = 5.06MHz */
        outl(0x00000130,  SMU_LCD_LCLK_DIV);
        outl(0x00000002,  SMU_LCD_LCLK_SEL);
#endif
		return;
	}

#ifdef PM_DEBUG_CLK
	printk("PM: %s(). change clock to [%d] from [%d]\n", 
		__FUNCTION__, new, mp200_pm_get_pll());
#endif
	mp200_change_pll_on_sram(&pll_tbl[get_idx(new)].param);

#ifdef CONFIG_SONY_ICX1087
    /* 2006.08.09 ish : HPLL(248M)/49 = 5.06MHz */
    outl(0x00000130,  SMU_LCD_LCLK_DIV);
    outl(0x00000002,  SMU_LCD_LCLK_SEL);
#endif    
    
	/* update loops_per_jiffy */
	mp200_pm_update_lpj();

/* 2006.06.02 ish */    
#ifndef CONFIG_SONY_ICX1087 
	/* change flash read mode */
	change_flash_read_mode(&flash_read_param_fullspeed);
#endif
    
	/* set uWire clock */
	mp200_pm_set_uwire_clock();
}

/*
 * fullspeed  ->  economy
 */
void mp200_pm_full_to_eco(void)
{
	if (mp200_pm_get_pll() == MP200_PLL_12MHZ) {
		return;
	}

    /* 2006.05.19 ish */
    printk(KERN_INFO "%s() 1. \n", __FUNCTION__);

/* 2006.05.19 ish */
#ifndef CONFIG_SONY_ICX1087  
	/* change flash read mode */
	change_flash_read_mode(&flash_read_param_economy);
#endif    

#ifdef PM_DEBUG_CLK
	printk("PM: %s(). change to osc from [%d] \n", 
		__FUNCTION__, mp200_pm_get_pll());
#endif
	mp200_change_osc_on_sram();

#ifdef CONFIG_SONY_ICX1087
    /* 2006.08.09 ish : OSC(11.2896)/3 = 3.7632MHz */
    outl(0x00000000,  SMU_LCD_LCLK_SEL);
    outl(0x00000102,  SMU_LCD_LCLK_DIV);
#endif    
    
    /* 2006.05.19 ish */
    printk(KERN_INFO "%s() 2. \n", __FUNCTION__);
    
	/* update loops_per_jiffy */
	mp200_pm_update_lpj();

	/* set uWire clock */
	mp200_pm_set_uwire_clock();
}

unsigned int mp200_pm_get_pll(void)
{
	if ((inl(SMU_PLLSEL) & MASK_SMU_PLLSEL) == SMU_PLLSEL_PLL) {
		if(inl(SMU_PLLSEL2) & SMU_NRM_CK_SEL) { /* low */
			return MP200_PLL_164MHZ; 
		}
		else { /* high */
			int i;
			unsigned int frq_change = inl(SMU_AUTO_FRQ_CHANGE);
			for (i = 0;i < PLL_KIND_NUM; i++) {
				if((frq_change & SMU_PLL_NRM_DIV_MASK) 
					== (pll_tbl[i].param.auto_frq_change 
					    & SMU_PLL_NRM_DIV_MASK)) {
					return pll_tbl[i].mode;
				}
			}
		}
	}

	return MP200_PLL_12MHZ;
}

int mp200_pm_set_pll(unsigned int flag)
{
	unsigned long irq_flags;
	unsigned int mode;
	int ret = 0;

	local_irq_save(irq_flags);

	switch (flag) {
	case MP200_PLL_248MHZ:
	case MP200_PLL_164MHZ:
	case MP200_PLL_124MHZ:
	case MP200_PLL_83MHZ:
	case MP200_PLL_50MHZ:
		mode = mp200_pm_get_pll();
		if(mode != flag) {
			mp200_pm_eco_to_full(flag);
		}
		if(mode == MP200_PLL_12MHZ) {
			timer_set_clock(TIMER_FULLSPEED);
		}
		break;
	case MP200_PLL_12MHZ:
		mode = mp200_pm_get_pll();
		if(mode != MP200_PLL_12MHZ) {
			timer_set_clock(TIMER_ECONOMY);
			mp200_pm_full_to_eco();
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}

	local_irq_restore(irq_flags);

	return ret;
}

static void change_flash_read_mode_on_flash(struct flash_read_param *param)
{
	unsigned int addr;

	outl(param->smu_ab0_flashrcr, SMU_AB0_FLASHRCR);
	outl(param->smu_ab0_flashrcr, SMU_AB0_FLASHCOMADD0);
	outl(param->smu_ab0_flashrcr | SMU_AB0_VALIDBIT | SMU_AB0_RW,
	     SMU_AB0_FLASHCOMADD1);
	outl(FLASH_CMD_CONFIGURATION_1ST, SMU_AB0_FLASHCOMDATA0);
	outl(FLASH_CMD_CONFIGURATION_2ND, SMU_AB0_FLASHCOMDATA1);

	outl(param->smu_ab0_cs0readctrl, SMU_AB0_CS0READCTRL);
	outl(AB0_SET_MODE | AB0_CS0, AB0_FLASHCOMSET);
	outl(param->smu_ab0_cs1readctrl, SMU_AB0_CS1READCTRL);
	outl(AB0_SET_MODE | AB0_CS1, AB0_FLASHCOMSET);

	addr = (param->smu_ab0_flashrcr << 1) + param->flash0_addr;
	outw(FLASH_CMD_CONFIGURATION_1ST, addr);
	outw(FLASH_CMD_CONFIGURATION_2ND, addr);
	addr = (param->smu_ab0_flashrcr << 1) + param->flash1_addr;
	outw(FLASH_CMD_CONFIGURATION_1ST, addr);
	outw(FLASH_CMD_CONFIGURATION_2ND, addr);
}

static unsigned long pm_pmu_compute_lpj(unsigned long ref, u_int div, u_int mult)
{
	unsigned long jiffy_l, jiffy_h;
	
	jiffy_h = ref / div;
	jiffy_l = (ref % div) / 100;
	jiffy_h *= mult;
	jiffy_l = jiffy_l * mult / div;
	
	return jiffy_h + jiffy_l * 100;
}

/*
 * init
 */
static int __init mp200_pm_pmu_init(void)
{
	void *saddr;
	void *daddr;
	void *flash_addr;
	int i;
	saddr = (void *)change_flash_read_mode_on_flash;
	daddr = kmalloc(CHANGE_FLASH_READ_MODE_SIZE, GFP_KERNEL);
	if (daddr == NULL) {
		return -ENOMEM;
	}

	memcpy(daddr, saddr, CHANGE_FLASH_READ_MODE_SIZE);
	change_flash_read_mode = (void (*)(struct flash_read_param *))daddr;

	flash_addr = ioremap(FLASH0_BASE, FLASH_SIZE);
	if (flash_addr == NULL) {
		kfree(daddr);
		return -EIO;
	}
	flash_read_param_fullspeed.flash0_addr = (unsigned int)flash_addr;
	flash_read_param_economy.flash0_addr = (unsigned int)flash_addr;

	flash_addr = ioremap(FLASH1_BASE, FLASH_SIZE);
	if (flash_addr == NULL) {
		iounmap((void *)flash_read_param_fullspeed.flash0_addr);
		kfree(daddr);
		return -EIO;
	}
	flash_read_param_fullspeed.flash1_addr = (unsigned int)flash_addr;
	flash_read_param_economy.flash1_addr = (unsigned int)flash_addr;

	flash_read_param_fullspeed.smu_ab0_cs0readctrl =
	    inl(SMU_AB0_CS0READCTRL);
	flash_read_param_fullspeed.smu_ab0_cs1readctrl =
	    inl(SMU_AB0_CS1READCTRL);
	flash_read_param_fullspeed.smu_ab0_flashrcr = inl(SMU_AB0_FLASHRCR);

	for (i = 0;i < PLL_KIND_NUM; i++) {
		pll_tbl[i].lpj = pm_pmu_compute_lpj(loops_per_jiffy, 
						mp200_pm_get_pll(), 
						pll_tbl[i].mode);
#ifdef PM_DEBUG_CLK
		printk("PM: %s(). clock[%d] - lpj[0x%08x]\n", 
			__FUNCTION__, pll_tbl[i].mode, pll_tbl[i].lpj);
#endif
	}

#ifdef PM_DEBUG
	printk(KERN_INFO "[flash_read_param_fullspeed]\n");
	printk(KERN_INFO "  flash0_addr         = 0x%08x\n",
	       flash_read_param_fullspeed.flash0_addr);
	printk(KERN_INFO "  flash1_addr         = 0x%08x\n",
	       flash_read_param_fullspeed.flash1_addr);
	printk(KERN_INFO "  smu_ab0_cs0readctrl = 0x%08x\n",
	       flash_read_param_fullspeed.smu_ab0_cs0readctrl);
	printk(KERN_INFO "  smu_ab0_cs1readctrl = 0x%08x\n",
	       flash_read_param_fullspeed.smu_ab0_cs1readctrl);
	printk(KERN_INFO "  smu_ab0_flashrcr    = 0x%08x\n",
	       flash_read_param_fullspeed.smu_ab0_flashrcr);
	printk(KERN_INFO "[flash_read_param_economy]\n");
	printk(KERN_INFO "  flash0_addr         = 0x%08x\n",
	       flash_read_param_economy.flash0_addr);
	printk(KERN_INFO "  flash1_addr         = 0x%08x\n",
	       flash_read_param_economy.flash1_addr);
	printk(KERN_INFO "  smu_ab0_cs0readctrl = 0x%08x\n",
	       flash_read_param_economy.smu_ab0_cs0readctrl);
	printk(KERN_INFO "  smu_ab0_cs1readctrl = 0x%08x\n",
	       flash_read_param_economy.smu_ab0_cs1readctrl);
	printk(KERN_INFO "  smu_ab0_flashrcr    = 0x%08x\n",
	       flash_read_param_economy.smu_ab0_flashrcr);
#endif

	return 0;
}

__initcall(mp200_pm_pmu_init);
