/*
 *  File Name	    : linux/arch/arm/mach-mp200/pmu.c
 *  Function	    : pmu
 *  Release Version : Ver 1.05
 *  Release Date    : 2006/07/11
 *
 *  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/module.h>
#include <linux/delay.h>

#include <asm/io.h>

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

#include "pmu.h"

static spinlock_t lock_clockgate = SPIN_LOCK_UNLOCKED;
static spinlock_t lock_reset = SPIN_LOCK_UNLOCKED;
static spinlock_t lock_clkctrl = SPIN_LOCK_UNLOCKED;

int mp200_pmu_open_clockgate(unsigned int clkbit)
{
	unsigned long cpu_flags;
#ifdef CONFIG_MP201
#else
	unsigned int regval;
#endif				/* CONFIG_MP201 */
	unsigned int mask;
	int bit;
	int ret = 0;

	bit = clkbit & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	spin_lock_irqsave(&lock_clockgate, cpu_flags);

	switch (clkbit & SMU_REG_GROUP_MASK) {
#ifdef CONFIG_MP201
	case SMU_GCLKCTRL0_GROUP:
		outl(mask, SMU_GCLKCTRL0_ENA);
		outl(mask, SMU_GCLKCTRL0);
		outl(0, SMU_GCLKCTRL0_ENA);
		break;
	case SMU_GCLKCTRL1_GROUP:
		outl(mask, SMU_GCLKCTRL1_ENA);
		outl(mask, SMU_GCLKCTRL1);
		outl(0, SMU_GCLKCTRL1_ENA);
		break;
	case SMU_GCLKCTRL2_GROUP:
		outl(mask, SMU_GCLKCTRL2_ENA);
		outl(mask, SMU_GCLKCTRL2);
		outl(0, SMU_GCLKCTRL2_ENA);
		break;
#else
	case SMU_GCLKCTRL0_GROUP:
		outl(mask, SMU_GCLKCTRL0ENA);
		outl(mask, SMU_GCLKCTRL0);
		outl(0, SMU_GCLKCTRL0ENA);
		break;
	case SMU_GCLKCTRL1_GROUP:
		outl(mask, SMU_GCLKCTRL1ENA);
		outl(mask, SMU_GCLKCTRL1);
		outl(0, SMU_GCLKCTRL1ENA);
		break;
	case SMU_GCLKCTRL2_GROUP:
		outl(mask, SMU_GCLKCTRL2ENA);
		outl(mask, SMU_GCLKCTRL2);
		outl(0, SMU_GCLKCTRL2ENA);
		break;
	case SMU_GCLKCTRL3_GROUP:
		outl(mask, SMU_GCLKCTRL3ENA);
		outl(mask, SMU_GCLKCTRL3);
		outl(0, SMU_GCLKCTRL3ENA);
		break;
	case SMU_SWTTRACECLK_CTRL_GROUP:
		regval = inl(SMU_SWTTRACECLK_CTRL);
		regval |= mask;
		outl(regval, SMU_SWTTRACECLK_CTRL);
		break;
#endif				/* CONFIG_MP201 */
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock_irqrestore(&lock_clockgate, cpu_flags);

	return ret;
}

int mp200_pmu_close_clockgate(unsigned int clkbit)
{
	unsigned long cpu_flags;
#ifdef CONFIG_MP201
#else
	unsigned int regval;
#endif				/* CONFIG_MP201 */
	unsigned int mask;
	int bit;
	int ret = 0;

	bit = clkbit & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	spin_lock_irqsave(&lock_clockgate, cpu_flags);

	switch (clkbit & SMU_REG_GROUP_MASK) {
#ifdef CONFIG_MP201
	case SMU_GCLKCTRL0_GROUP:
		outl(mask, SMU_GCLKCTRL0_ENA);
		outl(0, SMU_GCLKCTRL0);
		outl(0, SMU_GCLKCTRL0_ENA);
		break;
	case SMU_GCLKCTRL1_GROUP:
		outl(mask, SMU_GCLKCTRL1_ENA);
		outl(0, SMU_GCLKCTRL1);
		outl(0, SMU_GCLKCTRL1_ENA);
		break;
	case SMU_GCLKCTRL2_GROUP:
		outl(mask, SMU_GCLKCTRL2_ENA);
		outl(0, SMU_GCLKCTRL2);
		outl(0, SMU_GCLKCTRL2_ENA);
		break;
#else
	case SMU_GCLKCTRL0_GROUP:
		outl(mask, SMU_GCLKCTRL0ENA);
		outl(0, SMU_GCLKCTRL0);
		outl(0, SMU_GCLKCTRL0ENA);
		break;
	case SMU_GCLKCTRL1_GROUP:
		outl(mask, SMU_GCLKCTRL1ENA);
		outl(0, SMU_GCLKCTRL1);
		outl(0, SMU_GCLKCTRL1ENA);
		break;
	case SMU_GCLKCTRL2_GROUP:
		outl(mask, SMU_GCLKCTRL2ENA);
		outl(0, SMU_GCLKCTRL2);
		outl(0, SMU_GCLKCTRL2ENA);
		break;
	case SMU_GCLKCTRL3_GROUP:
		outl(mask, SMU_GCLKCTRL3ENA);
		outl(0, SMU_GCLKCTRL3);
		outl(0, SMU_GCLKCTRL3ENA);
		break;
	case SMU_SWTTRACECLK_CTRL_GROUP:
		regval = inl(SMU_SWTTRACECLK_CTRL);
		regval &= ~mask;
		outl(regval, SMU_SWTTRACECLK_CTRL);
		break;
#endif
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock_irqrestore(&lock_clockgate, cpu_flags);

	return ret;
}

int mp200_pmu_get_clockgate(unsigned int clkbit)
{
	unsigned int regval;
	unsigned int mask;
	int bit;
	int ret;

	bit = clkbit & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	switch (clkbit & SMU_REG_GROUP_MASK) {
	case SMU_GCLKCTRL0_GROUP:
		regval = inl(SMU_GCLKCTRL0);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_GCLKCTRL1_GROUP:
		regval = inl(SMU_GCLKCTRL1);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_GCLKCTRL2_GROUP:
		regval = inl(SMU_GCLKCTRL2);
		ret = (regval & mask) ? 1 : 0;
		break;
#ifndef CONFIG_MP201
	case SMU_GCLKCTRL3_GROUP:
		regval = inl(SMU_GCLKCTRL3);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_SWTTRACECLK_CTRL_GROUP:
		regval = inl(SMU_SWTTRACECLK_CTRL);
		ret = (regval & mask) ? 1 : 0;
		break;
#endif				/* CONFIG_MP201 */
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static inline void pmu_reset_wait(unsigned int rstdev)
{
	switch (rstdev) {
#ifdef CONFIG_MP201
	case MP200_RESETDEVICE_IIC:
	case MP200_RESETDEVICE_U70:
	case MP200_RESETDEVICE_GIO:
	case MP200_RESETDEVICE_LCD:	/* 1us>wait ok */
	case MP200_RESETDEVICE_CAM:	/* 1us>wait ok */
	case MP200_RESETDEVICE_SPI1:
	case MP200_RESETDEVICE_SPI0:
	case MP200_RESETDEVICE_DTV:
	case MP200_RESETDEVICE_MWI:
	case MP200_RESETDEVICE_PM1:
	case MP200_RESETDEVICE_PM0:
	case MP200_RESETDEVICE_MSP:
	case MP200_RESETDEVICE_TW1:
	case MP200_RESETDEVICE_TW0:
	case MP200_RESETDEVICE_TI3:
	case MP200_RESETDEVICE_TI2:
	case MP200_RESETDEVICE_TI1:
	case MP200_RESETDEVICE_TI0:
#else
	case MP200_RESETDEVICE_IIC:
	case MP200_RESETDEVICE_U73:
	case MP200_RESETDEVICE_U72:
	case MP200_RESETDEVICE_U71:
	case MP200_RESETDEVICE_U70:
	case MP200_RESETDEVICE_GIO:
	case MP200_RESETDEVICE_MOD0:
	case MP200_RESETDEVICE_LCD:
	case MP200_RESETDEVICE_CAM:
	case MP200_RESETDEVICE_TG5:
	case MP200_RESETDEVICE_TG4:
	case MP200_RESETDEVICE_TG3:
	case MP200_RESETDEVICE_TG2:
	case MP200_RESETDEVICE_TG1:
	case MP200_RESETDEVICE_TG0:
	case MP200_RESETDEVICE_SP2:
	case MP200_RESETDEVICE_SP1:
	case MP200_RESETDEVICE_SP0:
	case MP200_RESETDEVICE_DTV:
	case MP200_RESETDEVICE_MWI:
	case MP200_RESETDEVICE_SI1:
	case MP200_RESETDEVICE_SI0:
	case MP200_RESETDEVICE_PM2:
	case MP200_RESETDEVICE_PM1:
	case MP200_RESETDEVICE_PM0:
	case MP200_RESETDEVICE_MOD1:
	case MP200_RESETDEVICE_RMT:
	case MP200_RESETDEVICE_TW3:
	case MP200_RESETDEVICE_TW2:
	case MP200_RESETDEVICE_TW1:
	case MP200_RESETDEVICE_TW0:
	case MP200_RESETDEVICE_TI3:
	case MP200_RESETDEVICE_TI2:
	case MP200_RESETDEVICE_TI1:
	case MP200_RESETDEVICE_TI0:
#endif				/* CONFIG_MP201 */
		udelay(PMU_RESET_WAIT);
		break;
	default:
		break;
	}
}

int mp200_pmu_reset_device(unsigned int rstdev)
{
	unsigned long cpu_flags;
	unsigned int mask;
	int bit;
	int ret = 0;

	bit = rstdev & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	spin_lock_irqsave(&lock_reset, cpu_flags);

	switch (rstdev & SMU_REG_GROUP_MASK) {
#ifdef CONFIG_MP201
	case SMU_RESETREQ0_GROUP:
		outl(mask, SMU_RESETREQ0_ENA);
		outl(0, SMU_RESETREQ0);
		outl(0, SMU_RESETREQ0_ENA);
		break;
	case SMU_RESETREQ1A_GROUP:
		outl(mask, SMU_RESETREQ1A_ENA);
		outl(0, SMU_RESETREQ1A);
		outl(0, SMU_RESETREQ1A_ENA);
		break;
	case SMU_RESETREQ1B_GROUP:
		outl(mask, SMU_RESETREQ1B_ENA);
		outl(0, SMU_RESETREQ1B);
		outl(0, SMU_RESETREQ1B_ENA);
		break;
#else
	case SMU_RESETREQ0_GROUP:
		outl(mask, SMU_RESETREQ0ENA);
		outl(0, SMU_RESETREQ0);
		outl(0, SMU_RESETREQ0ENA);
		break;
	case SMU_RESETREQ1_GROUP:
		outl(mask, SMU_RESETREQ1ENA);
		outl(0, SMU_RESETREQ1);
		outl(0, SMU_RESETREQ1ENA);
		break;
#endif
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock_irqrestore(&lock_reset, cpu_flags);
	pmu_reset_wait(rstdev);

	return ret;
}

int mp200_pmu_unreset_device(unsigned int rstdev)
{
	unsigned long cpu_flags;
	unsigned int mask;
	int bit;
	int ret = 0;

	bit = rstdev & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	spin_lock_irqsave(&lock_reset, cpu_flags);

	switch (rstdev & SMU_REG_GROUP_MASK) {
#ifdef CONFIG_MP201
	case SMU_RESETREQ0_GROUP:
		outl(mask, SMU_RESETREQ0_ENA);
		outl(mask, SMU_RESETREQ0);
		outl(0, SMU_RESETREQ0_ENA);
		break;
	case SMU_RESETREQ1A_GROUP:
		outl(mask, SMU_RESETREQ1A_ENA);
		outl(mask, SMU_RESETREQ1A);
		outl(0, SMU_RESETREQ1A_ENA);
		break;
	case SMU_RESETREQ1B_GROUP:
		outl(mask, SMU_RESETREQ1B_ENA);
		outl(mask, SMU_RESETREQ1B);
		outl(0, SMU_RESETREQ1B_ENA);
		break;
#else
	case SMU_RESETREQ0_GROUP:
		outl(mask, SMU_RESETREQ0ENA);
		outl(mask, SMU_RESETREQ0);
		outl(0, SMU_RESETREQ0ENA);
		break;
	case SMU_RESETREQ1_GROUP:
		outl(mask, SMU_RESETREQ1ENA);
		outl(mask, SMU_RESETREQ1);
		outl(0, SMU_RESETREQ1ENA);
		break;
#endif				/* CONFIG_MP201 */
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock_irqrestore(&lock_reset, cpu_flags);
	pmu_reset_wait(rstdev);

	return ret;
}

int mp200_pmu_get_reset_status(unsigned int rstdev)
{
	unsigned int regval;
	unsigned int mask;
	int bit;
	int ret;

	bit = rstdev & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	switch (rstdev & SMU_REG_GROUP_MASK) {
	case SMU_RESETREQ0_GROUP:
		regval = inl(SMU_RESETREQ0);
		ret = (regval & mask) ? 1 : 0;
		break;
#ifdef CONFIG_MP201
	case SMU_RESETREQ1A_GROUP:
		regval = inl(SMU_RESETREQ1A);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_RESETREQ1B_GROUP:
		regval = inl(SMU_RESETREQ1B);
		ret = (regval & mask) ? 1 : 0;
		break;
#else
	case SMU_RESETREQ1_GROUP:
		regval = inl(SMU_RESETREQ1);
		ret = (regval & mask) ? 1 : 0;
		break;
#endif
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

int mp200_pmu_clkctrl_on(unsigned int dev)
{
	unsigned long cpu_flags;
	unsigned int regval;
	unsigned int ahbclkctrl1;
	unsigned int mask;
	int bit;
	int ret = 0;

	bit = dev & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	spin_lock_irqsave(&lock_clkctrl, cpu_flags);

	switch (dev & SMU_REG_GROUP_MASK) {
	case SMU_AHBCLKCTRL0_GROUP:
		regval = inl(SMU_AHBCLKCTRL0);
		regval |= mask;
		outl(regval, SMU_AHBCLKCTRL0);
		break;
	case SMU_AHBCLKCTRL1_GROUP:
		regval = inl(SMU_AHBCLKCTRL1);
		regval |= mask;
		outl(regval, SMU_AHBCLKCTRL1);
		break;
	case SMU_APBCLKCTRL_GROUP:
		/* change is need PB1&PB0 clock. PB1&PB0 change to mode off. */
		ahbclkctrl1 = inl(SMU_AHBCLKCTRL1);
		outl(ahbclkctrl1 & ~(SMU_CLKCTRL_PB0LP | SMU_CLKCTRL_PB1LP),
		     SMU_AHBCLKCTRL1);
		regval = inl(SMU_APBCLKCTRL);
		regval |= mask;
		outl(regval, SMU_APBCLKCTRL);
		outl(ahbclkctrl1, SMU_AHBCLKCTRL1);
		break;
	case SMU_CLKCTRL_GROUP:
		regval = inl(SMU_CLKCTRL);
		regval |= mask;
		outl(regval, SMU_CLKCTRL);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock_irqrestore(&lock_clkctrl, cpu_flags);

	return ret;
}

int mp200_pmu_clkctrl_off(unsigned int dev)
{
	unsigned long cpu_flags;
	unsigned int regval;
	unsigned int ahbclkctrl1;
	unsigned int mask;
	int bit;
	int ret = 0;

	bit = dev & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	spin_lock_irqsave(&lock_clkctrl, cpu_flags);

	switch (dev & SMU_REG_GROUP_MASK) {
	case SMU_AHBCLKCTRL0_GROUP:
		regval = inl(SMU_AHBCLKCTRL0);
		regval &= ~mask;
		outl(regval, SMU_AHBCLKCTRL0);
		break;
	case SMU_AHBCLKCTRL1_GROUP:
		regval = inl(SMU_AHBCLKCTRL1);
		regval &= ~mask;
		outl(regval, SMU_AHBCLKCTRL1);
		break;
	case SMU_APBCLKCTRL_GROUP:
		ahbclkctrl1 = inl(SMU_AHBCLKCTRL1);
		outl(ahbclkctrl1 & ~(SMU_CLKCTRL_PB0LP | SMU_CLKCTRL_PB1LP),
		     SMU_AHBCLKCTRL1);
		regval = inl(SMU_APBCLKCTRL);
		regval &= ~mask;
		outl(regval, SMU_APBCLKCTRL);
		outl(ahbclkctrl1, SMU_AHBCLKCTRL1);
		break;
	case SMU_CLKCTRL_GROUP:
		regval = inl(SMU_CLKCTRL);
		regval &= ~mask;
		outl(regval, SMU_CLKCTRL);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	spin_unlock_irqrestore(&lock_clkctrl, cpu_flags);

	return ret;
}

int mp200_pmu_get_clkctrl_status(unsigned int dev)
{
	unsigned int regval;
	unsigned int mask;
	int bit;
	int ret;

	bit = dev & SMU_REG_BIT_MASK;
	if (bit >= SMU_REG_MAX_BIT_LEN) {
		return -EINVAL;
	}
	mask = 1 << bit;

	switch (dev & SMU_REG_GROUP_MASK) {
	case SMU_AHBCLKCTRL0_GROUP:
		regval = inl(SMU_AHBCLKCTRL0);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_AHBCLKCTRL1_GROUP:
		regval = inl(SMU_AHBCLKCTRL1);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_APBCLKCTRL_GROUP:
		regval = inl(SMU_APBCLKCTRL);
		ret = (regval & mask) ? 1 : 0;
		break;
	case SMU_CLKCTRL_GROUP:
		regval = inl(SMU_CLKCTRL);
		ret = (regval & mask) ? 1 : 0;
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

/* 2006.07.13 has */
#ifdef CONFIG_SONY_ICX1087

int mp200_pmu_write_reg(unsigned int addr, unsigned int data, unsigned int mask)
{
	unsigned long cpu_flags;
	unsigned int regval;

	spin_lock_irqsave(&lock_clkctrl, cpu_flags);

	regval=inl(addr);
	regval=(regval&~mask)|(data&mask);
	outl(regval,addr);

	spin_unlock_irqrestore(&lock_clkctrl, cpu_flags);

	return(0);
}

int mp200_pmu_read_reg(unsigned int addr, unsigned int *data)
{
	*data=inl(addr);

	return(0);
}

#endif

EXPORT_SYMBOL(mp200_pmu_open_clockgate);
EXPORT_SYMBOL(mp200_pmu_close_clockgate);
EXPORT_SYMBOL(mp200_pmu_get_clockgate);

EXPORT_SYMBOL(mp200_pmu_reset_device);
EXPORT_SYMBOL(mp200_pmu_unreset_device);
EXPORT_SYMBOL(mp200_pmu_get_reset_status);

EXPORT_SYMBOL(mp200_pmu_clkctrl_on);
EXPORT_SYMBOL(mp200_pmu_clkctrl_off);
EXPORT_SYMBOL(mp200_pmu_get_clkctrl_status);

/* 2006.07.13 has */
#ifdef CONFIG_SONY_ICX1087
EXPORT_SYMBOL(mp200_pmu_write_reg);
EXPORT_SYMBOL(mp200_pmu_read_reg);
#endif

