/* 2012-09-16: File added and changed by Sony Corporation */
/*
 * icx_pwm.c
 *
 * Copyright 2011 Sony Corporation.
 *
 * 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
 *
 */

#include <common.h>

#if defined(CONFIG_ICX) || defined(CONFIG_ICD)

#include <asm/errno.h>

#include "reg.h"

#include "icx_io.h"
#include "icx_smu.h"
#include "icx_pwm.h"


/************/
/*@ Defines */
/************/

/* PWM setting */
#define PWM_MODE_INIT	0x00000001		/* enable CMP0		*/
#define PWM_DELAY_INIT	5          		/* Delay 5 cycles	*/
#define PWM_LEDGE_INIT	0
#define PWM_TEDGE_INIT	(PWM_LEDGE_INIT + 1)

#define PWM_START		0x00000001
#define PWM_STOP		0x00000000


/**************/
/*@ structure */
/**************/

/* PWM register info */
typedef struct {
	volatile u_int ctrl;		/* CONTROL			(0x00)		*/ 
	volatile u_int mode;		/* MODE				(0x04)		*/ 
			 u_int reserve[2];	/* Reserve			(0x08-0x0C)	*/ 
	volatile u_int delay0;		/* DELAY0			(0x10)		*/ 
	volatile u_int ledge0;		/* LEDGE0			(0x14)		*/ 
	volatile u_int tedge0;		/* TEDGE0			(0x18)		*/ 
	volatile u_int total0;		/* TOTAL0			(0x1C)		*/ 
	volatile u_int loop0;		/* LOOP0			(0x20)		*/ 
} pwm_regs_t;

/* SMU/CHG info */
typedef struct {
	u_int pinsel;				/* pinsel						*/
	u_int pull_mask;			/* pull mask bit				*/
	u_int pull_val;				/* pull							*/
	u_int pwclk;                /* pwclk                        */
	u_int div_mask;				/* divisor mask bit				*/
	u_int div_shift;			/* divisor bit shift			*/
} pwm_smu_t;

/* PWM info */
typedef struct {
	pwm_regs_t		*regs;		/* PWM register info			*/
	pwm_smu_t		*smu;		/* SMU info						*/
} pwm_info_t;


/***************/
/*@ Prototypes */
/***************/

static void pwm_start(pwm_info_t *info);
static void pwm_stop(pwm_info_t *info);


/********************/
/*@ Local variables */
/********************/

/* SMU/CHG info(PWM0) */
static pwm_smu_t pwm_smu_0 = {
	.pinsel		= 0xFEFFFFFF,
	.pull_mask	= 0xFFFFFFF0,
	.pull_val	= 0x00000000,
	.pwclk		= SMU_CLK_PWM_PWCLK0,
	.div_mask	= 0xFFFF0000,
	.div_shift	= 0,
};

/* SMU/CHG info(PWM1) */
static pwm_smu_t pwm_smu_1 = {
	.pinsel		= 0xFDFFFFFF,
	.pull_mask	= 0xFFFFFF0F,
	.pull_val	= 0x00000000,
	.pwclk		= SMU_CLK_PWM_PWCLK1,
	.div_mask	= 0x0000FFFF,
	.div_shift	= 16,
};

/* PWM info */
static pwm_info_t pwm_info[] = {
	{	/* PWM0 */
		.regs	= (pwm_regs_t *)PWM_CH0_BASE,
		.smu	= &pwm_smu_0,
	},
	{	/* PWM1 */
		.regs	= (pwm_regs_t *)PWM_CH1_BASE,
		.smu	= &pwm_smu_1,
	}
};


/********************/
/*@ Global routines */
/********************/

/*-----------------------------------------------------------------------------
 * Function : pwm_init
 * Abstract : Initialize PWM
 * Input    : dev   : device no.(0/1)
 *          : pol	: pol
 *			: total : total pulse
 *			: div	: divisor
 * Return   : 0         : success
 *			: -ENODEV   : no device
 * Note     : 
 *----------------------------------------------------------------------------*/
int pwm_init(u_char dev, u_int pol, u_int total, u_int div)
{
	pwm_info_t *pwm_dev;

	if ((dev != PWM_DEV_0) && (dev != PWM_DEV_1))
		return -ENODEV;

	pwm_dev = &pwm_info[dev];

	/* pinsel */
	outl(((inl(CHG_PINSEL_G096) & pwm_dev->smu->pinsel) | 0x00000000), CHG_PINSEL_G096);
	outl(((inl(CHG_PULL15) & pwm_dev->smu->pull_mask) | pwm_dev->smu->pull_val), CHG_PULL15);

	/* stop clock */
	smu_clockgate_control(pwm_dev->smu->pwclk, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_PWM_PCLK,    SMU_GCLKCTRL_CLOSE);

	/* select clock source */
	outl(((inl(SMU_PWMPWCLKDIV) & pwm_dev->smu->div_mask) | (div << pwm_dev->smu->div_shift)), SMU_PWMPWCLKDIV);

	/* start clock */
	smu_clockgate_control(pwm_dev->smu->pwclk, SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_PWM_PCLK,    SMU_GCLKCTRL_OPEN);

	/* unreset PWM */
	smu_reset_control(SMU_RST_PWM, SMU_RESETREQ_UNRESET);

	/* setting PWM */
	outl(0, PWM_INTENSET);
	pwm_dev->regs->loop0  = 1;
	pwm_dev->regs->mode   = PWM_MODE_INIT | (pol << 2);

	pwm_dev->regs->delay0 = PWM_DELAY_INIT;
	pwm_dev->regs->total0 = total;
	pwm_dev->regs->ledge0 = PWM_LEDGE_INIT;
	pwm_dev->regs->tedge0 = PWM_TEDGE_INIT;

	/* output PWM once */
	pwm_start(pwm_dev);
	pwm_stop(pwm_dev);

	udelay(10000);

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function : pwm_exit
 * Abstract : Exit PWM
 * Input    : dev   : device no.(0/1)
 * Return   : none
 * Note     : 
 *----------------------------------------------------------------------------*/
void pwm_exit(u_char dev)
{
	pwm_info_t *pwm_dev;

	if ((dev != PWM_DEV_0) && (dev != PWM_DEV_1))
		return;

	pwm_dev = &pwm_info[dev];

	pwm_stop(pwm_dev);

	/* stop clock */
	smu_clockgate_control(pwm_dev->smu->pwclk, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_PWM_PCLK,    SMU_GCLKCTRL_CLOSE);

	/* reset PWM */
	smu_reset_control(SMU_RST_PWM, SMU_RESETREQ_RESET);
}

/*-----------------------------------------------------------------------------
 * Function : pwm_set_value
 * Abstruct : Set PWM value
 * Input    : dev   : device no.(0/1)
 *          : full : full value
 *          : deg : 0-100
 * Return   : 0         : success
 *			: -ENODEV   : no device
 *			: -EINVAL   : invalid parameters
 * Note     : 
 *----------------------------------------------------------------------------*/
int pwm_set_value(u_char dev, u_int full, int deg)
{
	u_int       tedge;
	pwm_info_t *pwm_dev;

	if ((dev != PWM_DEV_0) && (dev != PWM_DEV_1))
		return -ENODEV;

	if ((deg < 0) || (deg > 100))
		return -EINVAL;

	pwm_dev = &pwm_info[dev];

	tedge = (full * deg) / 100;

	pwm_stop(pwm_dev);
	if (tedge > 0) {
		pwm_dev->regs->tedge0 = tedge;
		pwm_start(pwm_dev);
	}

	return 0;
}


/*******************/
/*@ Local routines */
/*******************/

/*-----------------------------------------------------------------------------
 * Function : pwm_start
 * Abstract : Start PWM
 * Input    : info	: PWM info
 * Return   : none
 * Note     : PWM0/1(28.672MHz same as kernel)
 *----------------------------------------------------------------------------*/
static void pwm_start(pwm_info_t *info)
{
	if ((info->regs->ctrl & PWM_START) == 0)
		info->regs->ctrl = PWM_START;
}

/*-----------------------------------------------------------------------------
 * Function : pwm_stop
 * Abstract : Stop PWM
 * Input    : info	: PWM info
 * Return   : none
 * Note     : PWM0/1(28.672MHz same as kernel)
 *----------------------------------------------------------------------------*/
static void pwm_stop(pwm_info_t *info)
{
	info->regs->ctrl = PWM_STOP;
}

#endif /* CONFIG_ICX || CONFIG_ICD */
