/*
 *  File Name       : linux/arch/arm/mach-mp200/pwc.c
 *  Function        : pwc
 *  Release Version : Ver 1.01
 *  Release Date    : 2006/07/27
 *
 *  Copyright (C) NEC Electronics Corporation 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/interrupt.h>
#include <linux/module.h>
#ifdef CONFIG_MP201
#include <linux/delay.h>
#endif				/* CONFIG_MP201 */

#include <asm/mach/irq.h>
#include <asm/arch/pwc.h>
#include <asm/arch/uwire.h>
/* 2006.10.05 fuj */
#ifdef CONFIG_SONY_ICX1087
#include <asm/io.h>
#endif

#include "pwc.h"

#ifdef CONFIG_MP201
static int mp200_pwc_set_irq_type(unsigned int irq, unsigned int type);

static int pwc_init_flag = 1;
static unsigned int lsi_ver;
spinlock_t pwc_spinlock = SPIN_LOCK_UNLOCKED;
#else
static int pwc_init_flag = 1;
static unsigned int pwc_ofint_flag = 0;
spinlock_t pwc_spinlock = SPIN_LOCK_UNLOCKED;
spinlock_t pwc_ofint_spinlock = SPIN_LOCK_UNLOCKED;
#endif				/* CONFIG_MP201 */

static unsigned char pwc_data[PWC_MAX];

/* PWC access functions */
inline int pwc_reg_read(unsigned char addr, unsigned char *data)
{
	int i, ret = 0;

	for (i = 0; i < UWIRE_CHECK_COUNT; i++) {
		ret = uwire_read(UWIRE_CS_PWC, addr, data);
		if (ret == 0) {
			break;
		}
	}

	if (ret != 0) {
		printk(KERN_INFO "pwc_reg_read(): uwire_read error\n");
		return ret;
	}

	return 0;
}

inline int pwc_reg_write(unsigned char addr, unsigned char data)
{
	int i, ret = 0;

	for (i = 0; i < UWIRE_CHECK_COUNT; i++) {
		ret = uwire_write(UWIRE_CS_PWC, addr, data);
		if (ret == 0) {
			break;
		}
	}
	if (ret != 0) {
		printk(KERN_INFO "pwc_reg_write(): uwire_write error\n");
		return ret;
	}

	return 0;
}

int pwc_read(unsigned short offset, unsigned int *data)
{
	int ret = 0;

	/* pwc initialize error check */
	if (pwc_init_flag != 0) {
		printk(KERN_INFO "pwc_read(): pwc initialize error\n");
		return -EPERM;
	}
#ifdef CONFIG_MP201
	if ((offset >= PWC_MAX) || (data == NULL)) {
		return -EINVAL;
	}
#endif				/* CONFIG_MP201 */

	switch (offset) {
		/* read only register */
#ifdef CONFIG_MP201
	case PWC_IOIN1:
	case PWC_IOIN2:
	case PWC_FACTOR1:
	case PWC_FACTOR2:
	case PWC_FACTOR3:
	case PWC_ADDATA1:
	case PWC_ADDATA2:
	case PWC_ADDATA3:
	case PWC_ADDATA4:
	case PWC_ADDATA5:
	case PWC_ADDATA6:
	case PWC_ADDATA7:
	case PWC_ADDATA8:
	case PWC_CHGIRR:
	case PWC_CHGSTATE:
	case PWC_PSW_STATE:
	case PWC_WRSTFCT:
	case PWC_LSIVER:
	case PWC_SEC_RD:
	case PWC_MIN_RD:
	case PWC_HOUR_RD:
	case PWC_WEEK_RD:
	case PWC_DAY_RD:
	case PWC_MONTH_RD:
	case PWC_YEAR_RD:
	case PWC_TRIM_RD:
	case PWC_ALM_WM_RD:
	case PWC_ALM_WH_RD:
	case PWC_ALM_WW_RD:
	case PWC_ALM_DM_RD:
	case PWC_ALM_DH_RD:
	case PWC_RTC1_RD:
	case PWC_RTC2_RD:
#else
	case PWC_BKUP2_RD:
	case PWC_BKUP3_RD:
	case PWC_BKUP4_RD:
	case PWC_BKUP5_RD:
	case PWC_BKUP6_RD:
	case PWC_BKUP7_RD:
	case PWC_BKUP8_RD:
	case PWC_INTR3:
	case PWC_VFBMON:
	case PWC_PWR_C_H_R:
	case PWC_BKUP1_RD:
	case PWC_USBINTR:
	case PWC_USBINT:
	case PWC_RSTFACTOR:
	case PWC_RSTRD:
	case PWC_FACTOR2:
	case PWC_IOSTATE2:
	case PWC_SEC_RD:
	case PWC_MIN_RD:
	case PWC_HOUR_RD:
	case PWC_WEEK_RD:
	case PWC_DAY_RD:
	case PWC_MONTH_RD:
	case PWC_YEAR_RD:
	case PWC_TRIM_RD:
	case PWC_ALM_WM_RD:
	case PWC_ALM_WH_RD:
	case PWC_ALM_WW_RD:
	case PWC_ALM_DM_RD:
	case PWC_ALM_DH_RD:
	case PWC_RTC1_RD:
	case PWC_RTC2_RD:
	case PWC_LSIVER:
#endif				/* CONFIG_MP201 */
		*data = 0;
		ret = pwc_reg_read(offset, (unsigned char *)data);
		break;

	default:
		*data = (unsigned int)pwc_data[offset];
		break;
	}

	return ret;
}

int pwc_write(unsigned short offset, unsigned int data, unsigned int mask)
{
	int ret = 0;
#ifdef CONFIG_MP201
	unsigned char tmp;
#endif				/* CONFIG_MP201 */
	unsigned long flags;

	/* pwc initialize error check */
	if (pwc_init_flag != 0) {
		printk(KERN_INFO "pwc_write(): pwc initialize error\n");
		return -EPERM;
	}
#ifdef CONFIG_MP201
	if (offset >= PWC_MAX) {
		return -EINVAL;
	}
#endif				/* CONFIG_MP201 */

#ifdef CONFIG_MP201
	/* write only register */
	switch (offset) {
	case PWC_REGA:
	case PWC_PSCNT1:
	case PWC_PSCNT2:
	case PWC_MODESET1:
	case PWC_VOUTSET1:
	case PWC_VOUTSET2:
	case PWC_VOUTSET3:
	case PWC_IOSEL1:
	case PWC_IOSEL2:
	case PWC_IOOUT1:
	case PWC_IOOUT2:
	case PWC_GPPUPD1:
	case PWC_GPPUPD2:
	case PWC_GPPUPD3:
	case PWC_GPPUPD4:
	case PWC_FMASK1:
	case PWC_FMASK2:
	case PWC_FMASK3:
	case PWC_STATE1:
	case PWC_STATE2:
	case PWC_STATE3:
	case PWC_DETMOD1L:
	case PWC_DETMOD1H:
	case PWC_DETMOD2L:
	case PWC_DETMOD2H:
	case PWC_DETMOD3L:
	case PWC_DETMOD3H:
	case PWC_BMASK1:
	case PWC_BMASK2:
	case PWC_BMASK3:
	case PWC_REGRSTMSK1:
	case PWC_REGRSTMSK2:
	case PWC_SYSRSTMSK1:
	case PWC_SYSRSTMSK2:
	case PWC_REGPWONMSK:
	case PWC_ADMODE:
	case PWC_VCCVBATSW:
	case PWC_VCCVBUSSW:
	case PWC_CHGCNTR1:
	case PWC_TIMCNTR:
	case PWC_CVSETR:
	case PWC_CHGCNTR2:
	case PWC_RDET_EN:
	case PWC_PWRSWCONT:
	case PWC_DETIOCONT:
	case PWC_RTC1:
	case PWC_RTC2:
	case PWC_USBCONT:
	case PWC_WRSTMSK:
		if (((offset == PWC_REGRSTMSK1) && (lsi_ver > PWC_LSIVER_ES1_1)) ||
		    ((offset == PWC_REGRSTMSK2) && (lsi_ver > PWC_LSIVER_ES1_1)) ||
		    ((offset == PWC_SYSRSTMSK1) && (lsi_ver > PWC_LSIVER_ES1_1)) ||
		    ((offset == PWC_SYSRSTMSK2) && (lsi_ver > PWC_LSIVER_ES1_1))) {
			break;
		}
		spin_lock_irqsave(&pwc_spinlock, flags);
		if( offset == PWC_TIMCNTR ) {
			pwc_data[PWC_TIMCNTR] |= PWC_TIMCNTR_INIT_VAL;
		}
		tmp = ((data & mask) | (pwc_data[offset] & ~mask));
		ret = pwc_reg_write(offset, tmp);
		if (ret == 0) {
			pwc_data[offset] = tmp;
		}
		spin_unlock_irqrestore(&pwc_spinlock, flags);
		break;

	case PWC_CLRFACT1:
	case PWC_CLRFACT2:
	case PWC_CLRFACT3:
	case PWC_CHGIRCL:
	case PWC_SFTRST:
	case PWC_WRSTCL:
	case PWC_SEC:
	case PWC_MIN:
	case PWC_HOUR:
	case PWC_WEEK:
	case PWC_DAY:
	case PWC_MONTH:
	case PWC_YEAR:
	case PWC_TRIM:
	case PWC_ALM_WM:
	case PWC_ALM_WH:
	case PWC_ALM_WW:
	case PWC_ALM_DM:
	case PWC_ALM_DH:
	case PWC_WAKECONT:
		if ((offset == PWC_WAKECONT) && (lsi_ver <= PWC_LSIVER_ES1_1)) {
			break;
		}
		spin_lock_irqsave(&pwc_spinlock, flags);
		tmp = (data & mask);
		ret = pwc_reg_write(offset, tmp);
		if (ret == 0) {
			pwc_data[offset] = tmp;
		}
		spin_unlock_irqrestore(&pwc_spinlock, flags);
		break;

	default:
		break;
	}
#else
	/* (read) / write register */
	if ((offset == PWC_CLRMODE)
	    || ((PWC_INTM3 <= offset) && (offset <= PWC_INTCLR3))
	    || ((PWC_MCARD <= offset) && (offset <= PWC_MCARDEN))
	    || ((PWC_RGBCNT1 <= offset) && (offset <= PWC_USBINTM))
	    || (offset == PWC_LED)
	    || ((PWC_PSCONT <= offset) && (offset <= PWC_RSTMASK))
	    || (offset == PWC_RDET_EN)
	    || ((PWC_MASK3 <= offset) && (offset <= PWC_IOOUTSEL))
	    || ((PWC_CHTTIM1H <= offset) && (offset <= PWC_PSCNTA))
	    || ((PWC_IRCNT1 <= offset) && (offset <= PWC_IROUT5A))
	    || ((PWC_IROUT6A <= offset) && (offset <= PWC_IROUT7B))
	    || ((PWC_ENCCTRL0 <= offset) && (offset <= PWC_MACROV18))) {
		spin_lock_irqsave(&pwc_spinlock, flags);
		pwc_data[offset] = (pwc_data[offset] & (~mask)) | (data & mask);
		ret = pwc_reg_write(offset, pwc_data[offset]);
		spin_unlock_irqrestore(&pwc_spinlock, flags);
	} else {
		switch (offset) {
			/* write only register */
		case PWC_INTCLR2:
		case PWC_INTCLR:
		case PWC_BKUP2:
		case PWC_BKUP3:
		case PWC_BKUP4:
		case PWC_BKUP5:
		case PWC_BKUP6:
		case PWC_BKUP7:
		case PWC_BKUP8:
		case PWC_PWR_C_H_C:
		case PWC_BKUP1:
		case PWC_STEP:
		case PWC_SFTRST:
		case PWC_RSTFACTCLR:
		case PWC_COUNTCLR:
		case PWC_SEC:
		case PWC_MIN:
		case PWC_HOUR:
		case PWC_WEEK:
		case PWC_DAY:
		case PWC_MONTH:
		case PWC_YEAR:
		case PWC_TRIM:
		case PWC_ALM_WM:
		case PWC_ALM_WH:
		case PWC_ALM_WW:
		case PWC_ALM_DM:
		case PWC_ALM_DH:
		case PWC_RTC1:
		case PWC_RTC2:
		case PWC_PDBRTC:
		case PWC_IRRQC:
		case PWC_IRSND:
		case PWC_IRRST:
			ret = pwc_reg_write(offset, (data & mask));
			break;

		default:
			break;
		}
	}
#endif				/* CONFIG_MP201 */

	return ret;
}

/* 2006.07.21 has */
#ifdef CONFIG_SONY_ICX1087

static spinlock_t pwc_regulator_spinlock=SPIN_LOCK_UNLOCKED;
static int        pwc_regulator_count[PWC_MAX_REGULATOR]={0};

int pwc_regulator_on(unsigned long regulator)
{
	unsigned long flags;
	unsigned short addr;
	unsigned short mask;
	int regu;
	int rv=0;

	addr=(unsigned short)(regulator&0xFF);
	mask=(unsigned short)((regulator>>8)&0xFF);
	regu=(int)((regulator>>16)&0xFF);

	if(addr!=PWC_PSCNT1 && addr!=PWC_PSCNT2)
		return(-EINVAL);

	if(regu<0 || regu>=PWC_MAX_REGULATOR)
		return(-EINVAL);

	spin_lock_irqsave(&pwc_regulator_spinlock,flags);

	/* if(pwc_regulator_count[regu]==0){ */
	if(pwc_regulator_count[regu]>=0){
		/* regulator on */
		rv=pwc_write(addr,0xFF,mask);
	}

	if(rv>=0)
		pwc_regulator_count[regu]++;

	spin_unlock_irqrestore(&pwc_regulator_spinlock,flags);

	/* printk("*** reg %d on: %d\n",regu,pwc_regulator_count[regu]); */

	return(rv);
}

int pwc_regulator_off(unsigned long regulator)
{
	unsigned long flags;
	unsigned short addr;
	unsigned short mask;
	int regu;
	int rv=0;

	addr=(unsigned short)(regulator&0xFF);
	mask=(unsigned short)((regulator>>8)&0xFF);
	regu=(int)((regulator>>16)&0xFF);

	if(addr!=PWC_PSCNT1 && addr!=PWC_PSCNT2)
		return(-EINVAL);

	if(regu<0 || regu>=PWC_MAX_REGULATOR)
		return(-EINVAL);

	spin_lock_irqsave(&pwc_regulator_spinlock,flags);

	/* if(pwc_regulator_count[regu]==1){ */
	if(pwc_regulator_count[regu]<=1){
		/* regulator off */
		rv=pwc_write(addr,0x00,mask);
	}

	if(rv>=0 && pwc_regulator_count[regu]>0)
		pwc_regulator_count[regu]--;

	spin_unlock_irqrestore(&pwc_regulator_spinlock,flags);

	/* printk("*** reg %d off: %d\n",regu,pwc_regulator_count[regu]); */

	return(rv);
}

#endif

static int __init mp200_pwc_init_data(void)
{
	int ret = 0;
	unsigned int offset;
	/* 2006.10.05 fuj */
#ifdef CONFIG_SONY_ICX1087
	unsigned int bootpowerinfo = 0;
	bootpowerinfo = inl(0xEA03FFF4);
#endif

#ifdef CONFIG_MP201
	for (offset = 0; offset < PWC_MAX; offset++) {
		pwc_data[offset] = 0;
	}

	/* set default value (not 0) */
	pwc_data[PWC_REGA] = PWC_REGA_INIT_VAL;
	pwc_data[PWC_VOUTSET1] = PWC_VOUTSET1_INIT_VAL;
/* 2006.10.5 fuj */
#ifdef CONFIG_SONY_ICX1087
	if (lsi_ver <= PWC_LSIVER_ES1_1) {
		pwc_data[PWC_VOUTSET2] = PWC_VOUTSET2_INIT_VAL_OLD;
		pwc_data[PWC_VOUTSET3] = PWC_VOUTSET3_INIT_VAL_OLD;
	} else if (lsi_ver <= PWC_LSIVER_ES2_0) {
		pwc_data[PWC_VOUTSET2] = PWC_VOUTSET2_INIT_VAL;
		pwc_data[PWC_VOUTSET3] = PWC_VOUTSET3_INIT_VAL;
	} else {
		pwc_data[PWC_VOUTSET2] = PWC_VOUTSET2_INIT_VAL;
		pwc_data[PWC_VOUTSET3] = PWC_VOUTSET3_INIT_VAL_ES2_2;
	}
#else
	if (lsi_ver <= PWC_LSIVER_ES1_1) {
		pwc_data[PWC_VOUTSET2] = PWC_VOUTSET2_INIT_VAL_OLD;
	} else {
		pwc_data[PWC_VOUTSET2] = PWC_VOUTSET2_INIT_VAL;
	}
#endif
	pwc_data[PWC_IOSEL1] = PWC_IOSEL1_INIT_VAL;
	pwc_data[PWC_GPPUPD3] = PWC_GPPUPD3_INIT_VAL;
	pwc_data[PWC_GPPUPD4] = PWC_GPPUPD4_INIT_VAL;
	pwc_data[PWC_CHGCNTR1] = PWC_CHGCNTR1_INIT_VAL;
	pwc_data[PWC_TIMCNTR] = PWC_TIMCNTR_INIT_VAL;
	pwc_data[PWC_USBCONT] = PWC_USBCONT_INIT_VAL;

/* 2006.06.7 ish */
#ifdef CONFIG_SONY_ICX1087
/* 2006.10.19 fuj */
/* 	pwc_data[PWC_PSCNT1] = PWC_PSCNT1_INIT_VAL; */
	if (lsi_ver <= PWC_LSIVER_ES2_0) {
		pwc_data[PWC_PSCNT1] = PWC_PSCNT1_INIT_VAL_OLD;
	}
	else {
		pwc_data[PWC_PSCNT1] = PWC_PSCNT1_INIT_VAL;
	}
/* 2006.11.30 fuj */
/*	pwc_data[PWC_PSCNT2] = PWC_PSCNT2_INIT_VAL; */
	/* 2006.09.22 aiz: set bootloader value */
	/* 2006.10.05 fuj : VBUS and VeryLowBatt */
	if (bootpowerinfo & 0x1) {
		/* boot in power save mode */
		pwc_data[PWC_PSCNT2] = PWC_PSCNT2_INIT_VAL_PWS;

		pwc_data[PWC_IOSEL2] = PWC_IOSEL2_INIT_VAL_PWS;
		if (lsi_ver <= PWC_LSIVER_ES1_1) {
			pwc_data[PWC_IOOUT2] = PWC_IOOUT2_INIT_VAL_OLD_PWS;
		} else {
			pwc_data[PWC_IOOUT2] = PWC_IOOUT2_INIT_VAL_PWS;
		}
	}
	else {
		pwc_data[PWC_PSCNT2] = PWC_PSCNT2_INIT_VAL;

		pwc_data[PWC_IOSEL2] = PWC_IOSEL2_INIT_VAL;
		if (lsi_ver <= PWC_LSIVER_ES1_1) {
			pwc_data[PWC_IOOUT2] = PWC_IOOUT2_INIT_VAL_OLD;
		} else {
			pwc_data[PWC_IOOUT2] = PWC_IOOUT2_INIT_VAL;
		}
	}

	/* 2006.10.05 fuj : set bootloader value */
	pwc_data[PWC_DETMOD3L] = PWC_DETMOD3L_INIT_VAL;
#endif    

#else
	unsigned long flags;

	for (offset = 0; offset < PWC_MAX; offset++) {
		/* (read) / write register */
		if ((offset == PWC_CLRMODE)
		    || ((PWC_INTM3 <= offset) && (offset <= PWC_INTCLR3))
		    || ((PWC_MCARD <= offset) && (offset <= PWC_MCARDEN))
		    || ((PWC_RGBCNT1 <= offset) && (offset <= PWC_USBINTM))
		    || (offset == PWC_LED)
		    || ((PWC_PSCONT <= offset) && (offset <= PWC_RSTMASK))
		    || (offset == PWC_RDET_EN)
		    || ((PWC_MASK3 <= offset) && (offset <= PWC_IOOUTSEL))
		    || ((PWC_CHTTIM1H <= offset) && (offset <= PWC_PSCNTA))
		    || ((PWC_IRCNT1 <= offset) && (offset <= PWC_IROUT5A))
		    || ((PWC_IROUT6A <= offset) && (offset <= PWC_IROUT7B))
		    || ((PWC_ENCCTRL0 <= offset) && (offset <= PWC_MACROV18))) {
			spin_lock_irqsave(&pwc_spinlock, flags);
			ret =
			    pwc_reg_write(PWC_RDBAK_ADR, (unsigned char)offset);
			if (ret != 0) {
				return ret;
			}
			ret = pwc_reg_read(PWC_RDBAK_DATA, &pwc_data[offset]);
			if (ret != 0) {
				return ret;
			}
			spin_unlock_irqrestore(&pwc_spinlock, flags);
		} else {
			pwc_data[offset] = 0;
		}
	}
#endif				/* CONFIG_MP201 */
	pwc_init_flag = 0;
	return ret;
}

/* PWC interrupt functions */
static void mp200_pwc_mask_irq(unsigned int irq)
{
	unsigned int port = 0;
#ifdef CONFIG_MP201

	if ((irq < INT_PWC_MASK1_BASE) || (INT_PWC_LAST < irq)) {
		return;
	}
	if (irq < INT_PWC_MASK2_BASE) {
		port = irq - INT_PWC_MASK1_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_mask_irq(): MASK1 port = %d\n",
		       port);
#endif
		/* mask FMASK1/BMASK1 */
		pwc_write(PWC_BMASK1, 0, (1 << port));
		pwc_write(PWC_FMASK1, 0, (1 << port));
	} else if (irq < INT_PWC_MASK3_BASE) {
		port = irq - INT_PWC_MASK2_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_mask_irq(): MASK2 port = %d\n",
		       port);
#endif
		/* mask FMASK2/BMASK2 */
		pwc_write(PWC_BMASK2, 0, (1 << port));
		pwc_write(PWC_FMASK2, 0, (1 << port));
	} else {
		port = irq - INT_PWC_MASK3_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_mask_irq(): MASK3 port = %d\n",
		       port);
#endif
		/* mask FMASK3/BMASK3 */
		pwc_write(PWC_BMASK3, 0, (1 << port));
		pwc_write(PWC_FMASK3, 0, (1 << port));
	}
#else
	unsigned long flags = 0;

	if ((irq < INT_PWC_MASK2_BASE) || (INT_PWC_LAST < irq)) {
		return;
	}

	if ((INT_PWC_MASK2_BASE <= irq) && (irq <= INT_PWC_MASK2_LAST)) {
		port = irq - INT_PWC_MASK2_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_mask_irq(): MASK2 port = %d\n",
		       port);
#endif
		/* mask FACTOR */
		pwc_write(PWC_MASK2, 0, (1 << port));
	} else if ((INT_PWC_MASK3_BASE <= irq) && (irq <= INT_PWC_MASK3_LAST)) {
		port = irq - INT_PWC_MASK3_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_mask_irq(): MASK3 port = %d\n",
		       port);
#endif
		/* mask USBINT or TVINT or IRINT or DDINT */
		pwc_write(PWC_MASK3, 0, (1 << port));

		/* mask OFINT */
		spin_lock_irqsave(&pwc_ofint_spinlock, flags);
		pwc_ofint_flag &= ~(1U << port);
		if (pwc_ofint_flag == 0) {
			pwc_write(PWC_MASK2, 0, (1 << PWC_INT_OFINT));
		}
		spin_unlock_irqrestore(&pwc_ofint_spinlock, flags);
	}
#endif				/* CONFIG_MP201 */
}

static void mp200_pwc_unmask_irq(unsigned int irq)
{
#ifdef CONFIG_MP201
	unsigned int port = 0;

	if ((irq < INT_PWC_MASK1_BASE) || (INT_PWC_LAST < irq)) {
		return;
	}

	if (irq < INT_PWC_MASK2_BASE) {
		port = irq - INT_PWC_MASK1_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_unmask_irq(): MASK1 port = %d\n",
		       port);
#endif
		/* unmask FMASK1/BMASK1 */
		pwc_write(PWC_FMASK1, (1 << port), (1 << port));
		pwc_write(PWC_BMASK1, (1 << port), (1 << port));
	} else if (irq < INT_PWC_MASK3_BASE) {
		port = irq - INT_PWC_MASK2_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_unmask_irq(): MASK2 port = %d\n",
		       port);
#endif
		/* unmask FMASK2/BMASK2 */
		pwc_write(PWC_FMASK2, (1 << port), (1 << port));
		pwc_write(PWC_BMASK2, (1 << port), (1 << port));
	} else {
		port = irq - INT_PWC_MASK3_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_unmask_irq(): MASK3 port = %d\n",
		       port);
#endif
		/* unmask FMASK3/BMASK3 */
		pwc_write(PWC_FMASK3, (1 << port), (1 << port));
		pwc_write(PWC_BMASK3, (1 << port), (1 << port));
	}
#else
	unsigned long flags;
	unsigned int port = 0;

	if ((irq < INT_PWC_MASK2_BASE) || (INT_PWC_LAST < irq)) {
		return;
	}

	if ((INT_PWC_MASK2_BASE <= irq) && (irq <= INT_PWC_MASK2_LAST)) {
		port = irq - INT_PWC_MASK2_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_unmask_irq(): MASK2 port = %d\n",
		       port);
#endif
		/* unmask MASK2 */
		pwc_write(PWC_MASK2, (1 << port), (1 << port));
	} else if ((INT_PWC_MASK3_BASE <= irq) && (irq <= INT_PWC_MASK3_LAST)) {
		port = irq - INT_PWC_MASK3_BASE;
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO "mp200_pwc_unmask_irq(): MASK3 port = %d\n",
		       port);
#endif
		/* unmask MASK3 */
		pwc_write(PWC_MASK3, (1 << port), (1 << port));

		/* unmask OFINT */
		spin_lock_irqsave(&pwc_ofint_spinlock, flags);
		if (pwc_ofint_flag == 0) {
			pwc_write(PWC_MASK2, (1 << PWC_INT_OFINT),
				  (1 << PWC_INT_OFINT));
		}
		pwc_ofint_flag |= (1U << port);
		spin_unlock_irqrestore(&pwc_ofint_spinlock, flags);
	}
#endif				/* CONFIG_MP201 */
}

#ifdef CONFIG_MP201
static void mp200_pwc_ack_irq(unsigned int irq)
{
	unsigned int port = 0;

	/* PWC clear before interrupt handling */
	if (irq < INT_PWC_MASK2_BASE) {
		port = irq - INT_PWC_MASK1_BASE;
		pwc_write(PWC_CLRFACT1, (1 << port), (1 << port));
	} else if (irq < INT_PWC_MASK3_BASE) {
		port = irq - INT_PWC_MASK2_BASE;
		pwc_write(PWC_CLRFACT2, (1 << port), (1 << port));
	} else {
		port = irq - INT_PWC_MASK3_BASE;
		pwc_write(PWC_CLRFACT3, (1 << port), (1 << port));
	}
}

static void mp200_pwc_ack_unmask_irq(unsigned int irq)
{
	mp200_pwc_ack_irq(irq);
	mp200_pwc_unmask_irq(irq);
}
#endif				/* CONFIG_MP201 */

static void mp200_pwc_irq_handler(unsigned int irq, struct irqdesc *desc,
				  struct pt_regs *regs)
{
#ifdef CONFIG_MP201
	int i;
	unsigned int pwc_factor1_stat = 0;
	unsigned int pwc_factor2_stat = 0;
	unsigned int pwc_factor3_stat = 0;
	unsigned int pwc_irq;
	struct irqdesc *d;

	if (irq != INT_GPIO_INTOUT) {
		return;
	}

	desc->chip->ack(irq);

	pwc_read(PWC_FACTOR1, &pwc_factor1_stat);
	pwc_read(PWC_FACTOR2, &pwc_factor2_stat);
	pwc_read(PWC_FACTOR3, &pwc_factor3_stat);

	if (pwc_factor1_stat != 0) {
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO
		       "mp200_pwc_irq_handler(): PWC_FACTOR1 = 0x%02x\n",
		       pwc_factor1_stat);
#endif
		/* IO interrupt */
		for (i = PWC_INT_B0; i <= PWC_INT_MAX; i++) {
			if ((pwc_factor1_stat & (1 << i)) != 0) {
				pwc_irq = INT_PWC_MASK1_BASE + i;
				d = irq_desc + pwc_irq;
				d->handle(pwc_irq, d, regs);
			}
		}
	}
	if (pwc_factor2_stat != 0) {
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO
		       "mp200_pwc_irq_handler(): PWC_FACTOR2 = 0x%02x\n",
		       pwc_factor2_stat);
#endif
		/* IO interrupt */
		for (i = PWC_INT_B0; i <= PWC_INT_MAX; i++) {
			if ((pwc_factor2_stat & (1 << i)) != 0) {
				pwc_irq = INT_PWC_MASK2_BASE + i;
				d = irq_desc + pwc_irq;
				d->handle(pwc_irq, d, regs);
			}
		}
	}
	if (pwc_factor3_stat != 0) {
#ifdef GPIO_IRQ_DEBUG
		printk(KERN_INFO
		       "mp200_pwc_irq_handler(): PWC_FACTOR3 = 0x%02x\n",
		       pwc_factor3_stat);
#endif
		/* IO interrupt */
		for (i = PWC_INT_B0; i <= PWC_INT_MAX; i++) {
			if ((pwc_factor3_stat & (1 << i)) != 0) {
				pwc_irq = INT_PWC_MASK3_BASE + i;
				d = irq_desc + pwc_irq;
				d->handle(pwc_irq, d, regs);
			}
		}
	}

	desc->chip->unmask(irq);
#else
	int i;
	unsigned int pwc_factor2_stat = 0;
	unsigned int pwc_usbintr_stat = 0;
	unsigned int pwc_intr3_stat = 0;
	unsigned int pwc_irq = 0;
	struct irqdesc *d;

	if (irq != INT_GPIO_ELTANIN_INT) {
		return;
	}

	desc->chip->ack(irq);

	pwc_read(PWC_FACTOR2, &pwc_factor2_stat);

#ifdef GPIO_IRQ_DEBUG
	printk(KERN_INFO
	       "mp200_pwc_irq_handler(): PWC_FACTOR2_STAT = 0x%02x\n",
	       pwc_factor2_stat);
#endif

	if (pwc_factor2_stat != 0) {
		/* PWC clear before interrupt handling */
		pwc_write(PWC_INTCLR, pwc_factor2_stat, pwc_factor2_stat);

		/* OFINT interrupt */
		if ((pwc_factor2_stat & (1 << PWC_INT_OFINT)) != 0) {
			pwc_read(PWC_USBINTR, &pwc_usbintr_stat);
			pwc_read(PWC_INTR3, &pwc_intr3_stat);
#ifdef GPIO_IRQ_DEBUG
			printk(KERN_INFO
			       "mp200_pwc_irq_handler(): pwc_usbintr_stat = 0x%02x\n",
			       pwc_usbintr_stat);
			printk(KERN_INFO
			       "mp200_pwc_irq_handler(): pwc_intr3_stat = 0x%02x\n",
			       pwc_intr3_stat);
#endif

			/* USB interrupt
			 * clear interrupt factors after do_IRQ because USB driver check it. */
			if ((pwc_usbintr_stat & PWC_USBINT_MASK) != 0) {
				d = irq_desc + INT_PWC_USBINT;
				d->handle(INT_PWC_USBINT, d, regs);
				pwc_write(PWC_INTCLR2,
					  (pwc_usbintr_stat & PWC_USBINT_MASK),
					  (pwc_usbintr_stat & PWC_USBINT_MASK));
			}
			/* TV interrupt */
			if ((pwc_usbintr_stat & PWC_TVINT_MASK) != 0) {
				pwc_write(PWC_INTCLR2, PWC_TVINT_MASK,
					  PWC_TVINT_MASK);
				d = irq_desc + INT_PWC_TVINT;
				d->handle(INT_PWC_TVINT, d, regs);
			}
			/* IRINT interrupt */
			if ((pwc_intr3_stat & PWC_IRINT_MASK) != 0) {
				pwc_write(PWC_IRRQC, PWC_IRINT_MASK,
					  PWC_IRINT_MASK);
				d = irq_desc + INT_PWC_IRINT;
				d->handle(INT_PWC_IRINT, d, regs);
			}
			/* DDINT interrupt */
			if ((pwc_intr3_stat & PWC_DDINT_MASK) != 0) {
				pwc_write(PWC_INTCLR3, PWC_DDINT_MASK,
					  PWC_DDINT_MASK);
				d = irq_desc + INT_PWC_DDINT;
				d->handle(INT_PWC_DDINT, d, regs);
			}
		}

		/* IO interrupt */
		for (i = PWC_INT_B1; i <= PWC_INT_MAX; i++) {
			if ((pwc_factor2_stat & (1 << i)) != 0) {
				pwc_irq = INT_PWC_MASK2_BASE + i;
				d = irq_desc + pwc_irq;
				d->handle(pwc_irq, d, regs);
			}
		}
	}

	desc->chip->unmask(irq);
#endif				/* CONFIG_MP201 */
}

#ifdef CONFIG_MP201
static struct irqchip mp200_pwc_chip_ack = {
	.ack = mp200_pwc_ack_irq,
	.mask = mp200_pwc_mask_irq,
	.unmask = mp200_pwc_unmask_irq,
	.type = mp200_pwc_set_irq_type,
};
static struct irqchip mp200_pwc_chip = {
	.ack = mp200_pwc_mask_irq,
	.mask = mp200_pwc_mask_irq,
	.unmask = mp200_pwc_ack_unmask_irq,
	.type = mp200_pwc_set_irq_type,
};
#else
static struct irqchip mp200_pwc_chip = {
	.ack = mp200_pwc_mask_irq,
	.mask = mp200_pwc_mask_irq,
	.unmask = mp200_pwc_unmask_irq,
};
#endif				/* CONFIG_MP201 */

#ifdef CONFIG_MP201
/* called from set_irq_type() */
static int mp200_pwc_set_irq_type(unsigned int irq, unsigned int type)
{
	unsigned int port;
	unsigned int mode = 0;
	unsigned int flags;
	unsigned int pwc_bmask;
	unsigned int pwc_fmask;
	unsigned int shift;

	if ((irq < INT_PWC_MASK1_BASE) || (INT_PWC_LAST < irq)) {
		return -EINVAL;
	}

	if ((irq == INT_PWC_IO01) && (lsi_ver > PWC_LSIVER_ES1_1)) {
		switch (type) {
		case IRQT_HIGH:
			mode = 0x00;
			break;
		case IRQT_BOTHEDGE:
			mode = 0x01;
			break;
		default:
			return -EINVAL;
		}
	} else {
		switch (type) {
		case IRQT_HIGH:
			mode = 0x00;
			break;
		case IRQT_RISING:
			mode = 0x01;
			break;
		case IRQT_FALLING:
			mode = 0x02;
			break;
		default:
			return -EINVAL;
		}
	}

	if ((mode == 0x01) || (mode == 0x02)) {
		set_irq_chip(irq, &mp200_pwc_chip_ack);
		set_irq_handler(irq, do_edge_IRQ);
	} else {
		set_irq_chip(irq, &mp200_pwc_chip);
		set_irq_handler(irq, do_level_IRQ);
	}

	spin_lock_irqsave(&pwc_spinlock, flags);

	if (irq < INT_PWC_MASK2_BASE) {
		port = irq - INT_PWC_MASK1_BASE;
		shift = (port & 0x03) << 1;

		/* BMASK1/FMASK1 enable -> disable */
		pwc_read(PWC_BMASK1, &pwc_bmask);
		if ((pwc_bmask & (1 << port)) != 0) {
			pwc_write(PWC_BMASK1, 0, (1 << port));
		}
		pwc_read(PWC_FMASK1, &pwc_fmask);
		if ((pwc_fmask & (1 << port)) != 0) {
			pwc_write(PWC_FMASK1, 0, (1 << port));
		}

		/* set DETMOD1 */
		if (port < 4) {
			pwc_write(PWC_DETMOD1L, (mode << shift),
				  (0x03 << shift));
		} else {
			pwc_write(PWC_DETMOD1H, (mode << shift),
				  (0x03 << shift));
		}
		udelay(60);
		pwc_write(PWC_CLRFACT1, (1 << port), (1 << port));

		/* Restore if changed */
		if ((pwc_bmask & (1 << port)) != 0) {
			pwc_write(PWC_BMASK1, (1 << port), (1 << port));
		}
		if ((pwc_fmask & (1 << port)) != 0) {
			pwc_write(PWC_FMASK1, (1 << port), (1 << port));
		}
	} else if (irq < INT_PWC_MASK3_BASE) {
		port = irq - INT_PWC_MASK2_BASE;
		shift = (port & 0x03) << 1;

		/* BMASK2/FMASK2 enable -> disable */
		pwc_read(PWC_BMASK2, &pwc_bmask);
		if ((pwc_bmask & (1 << port)) != 0) {
			pwc_write(PWC_BMASK2, 0, (1 << port));
		}
		pwc_read(PWC_FMASK2, &pwc_fmask);
		if ((pwc_fmask & (1 << port)) != 0) {
			pwc_write(PWC_FMASK2, 0, (1 << port));
		}

		/* set DETMOD2 */
		if (port < 4) {
			pwc_write(PWC_DETMOD2L, (mode << shift),
				  (0x03 << shift));
		} else {
			pwc_write(PWC_DETMOD2H, (mode << shift),
				  (0x03 << shift));
		}
		udelay(60);
		pwc_write(PWC_CLRFACT2, (1 << port), (1 << port));

		/* Restore if changed */
		if ((pwc_bmask & (1 << port)) != 0) {
			pwc_write(PWC_BMASK2, (1 << port), (1 << port));
		}
		if ((pwc_fmask & (1 << port)) != 0) {
			pwc_write(PWC_FMASK2, (1 << port), (1 << port));
		}
	} else {
		port = irq - INT_PWC_MASK3_BASE;
		shift = (port & 0x03) << 1;

		/* BMASK3/FMASK3 enable -> disable */
		pwc_read(PWC_BMASK3, &pwc_bmask);
		if ((pwc_bmask & (1 << port)) != 0) {
			pwc_write(PWC_BMASK3, 0, (1 << port));
		}
		pwc_read(PWC_FMASK3, &pwc_fmask);
		if ((pwc_fmask & (1 << port)) != 0) {
			pwc_write(PWC_FMASK3, 0, (1 << port));
		}

		/* set DETMOD3 */
		if (port < 4) {
			pwc_write(PWC_DETMOD3L, (mode << shift),
				  (0x03 << shift));
		} else {
			pwc_write(PWC_DETMOD3H, (mode << shift),
				  (0x03 << shift));
		}
		udelay(60);
		pwc_write(PWC_CLRFACT3, (1 << port), (1 << port));

		/* Restore if changed */
		if ((pwc_bmask & (1 << port)) != 0) {
			pwc_write(PWC_BMASK3, (1 << port), (1 << port));
		}
		if ((pwc_fmask & (1 << port)) != 0) {
			pwc_write(PWC_FMASK3, (1 << port), (1 << port));
		}
	}

	spin_unlock_irqrestore(&pwc_spinlock, flags);

	return 0;
}
#endif				/* CONFIG_MP201 */

static int __init mp200_pwc_init(void)
{
	int i;

#ifdef CONFIG_MP201
	pwc_reg_read(PWC_LSIVER, (unsigned char *)(&lsi_ver));
#endif				/* CONFIG_MP201 */

	/* initialize pwc_data */
	mp200_pwc_init_data();

#ifdef CONFIG_MP201
	pwc_write(PWC_IOSEL1, PWC_GIO_OUTPORT1, MASK_PWC_IOSEL1);
	pwc_write(PWC_IOSEL2, PWC_GIO_OUTPORT2, MASK_PWC_IOSEL2);

	/* setup default PWC Interrupt modes */
	for (i = INT_PWC_MASK1_BASE; i <= INT_PWC_LAST; i++) {
		set_irq_chip(i, &mp200_pwc_chip);
		set_irq_handler(i, do_level_IRQ);
		set_irq_flags(i, IRQF_VALID);
	}

	/* Initialize PWC INT */
	set_irq_type(INT_GPIO_INTOUT, IRQT_HIGH);
	set_irq_chained_handler(INT_GPIO_INTOUT, mp200_pwc_irq_handler);
#else
	pwc_write(PWC_IOSEL2, PWC_GIO_INPORT, MASK_PWC_IOSEL2);

	/* mask USBINT */
	pwc_write(PWC_MASK3, 0, PWC_MASK3_USBINTG);

	/* PWC set INT CLRMODE */
	pwc_write(PWC_CLRMODE, PWC_CLRMODE_INTMODE, PWC_CLRMODE_INTMODE);

	/* setup default PWC Interrupt modes */
	for (i = INT_PWC_MASK2_BASE; i <= INT_PWC_LAST; i++) {
		set_irq_chip(i, &mp200_pwc_chip);
		set_irq_handler(i, do_level_IRQ);
		set_irq_flags(i, IRQF_VALID);
	}

	/* Initialize PWC INT */
	set_irq_type(INT_GPIO_ELTANIN_INT, IRQT_RISING | __IRQT_32KPLS);
	set_irq_chained_handler(INT_GPIO_ELTANIN_INT, mp200_pwc_irq_handler);
#endif				/* CONFIG_MP201 */

	return 0;
}

arch_initcall(mp200_pwc_init);

EXPORT_SYMBOL(pwc_read);
EXPORT_SYMBOL(pwc_write);
EXPORT_SYMBOL(pwc_regulator_on);
EXPORT_SYMBOL(pwc_regulator_off);
