/* 2009-08-18: File added by Sony Corporation */
/*
 *  PWM-based Backlight Driver Support for PGPF
 *
 *  Copyright 2009 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;  version 2 of the  License.
 *
 *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
 *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
 *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  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.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/delay.h>

#include <asm/arch/smu.h>
#include <asm/arch/pwm.h>
#include <asm/arch/gpio.h>

#include <asm/arch/pgpf/lcdhw.h>
#include <asm/arch/pgpf/blhw.h>

#include "../../char/mp200_pwm.h"

#define PWM1_PINSEL_MASK (0x40000000)
#define PWM0_PINSEL_MASK (0x10000000)

#define DEV_NAME "pgpf_bl_pwm"

#define PGPF_BACKLIGHT_MAX (100)
#define PGPF_BACKLIGHT_DEFAULT PGPF_BACKLIGHT_MAX

#define _DEBUG_BL  0x00

DEFINE_SPINLOCK(pgpf_bl_pwm_lock);

#define printk_err(fmt, arg...) \
	do{                     \
		printk(KERN_ERR DEV_NAME ": %s: " fmt, __func__, ## arg); \
	}while(0)

#define printk_wrn(fmt, arg...) \
	do{                     \
		printk(KERN_WARNING DEV_NAME ": %s: " fmt, __func__, ## arg); \
	}while(0)

#define printk_info(fmt, arg...) \
	do{                      \
		printk(KERN_INFO DEV_NAME ": " fmt, ## arg); \
	}while(0)

#if _DEBUG_BL
#define printk_dbg(level, fmt, arg...) \
	do{                            \
		if(level > 0) printk(KERN_DEBUG DEV_NAME ": %s: " fmt, __func__, ## arg); \
	}while(0)
#else
#define printk_dbg(level, fmt, arg...) \
	;
#endif

extern struct backlight_ops pgpf_bl_ops;

static struct pgpf_bl_pwm_param param;

static int pgpf_bl_pwm_start(void);
static int pgpf_bl_pwm_end(void);
static int pgpf_bl_pwm_suspend(void);
static int pgpf_bl_pwm_resume(void);
static int pgpf_bl_pwm_init(struct fb_info* info);
static void pgpf_bl_pwm_exit(struct fb_info* info);
static int pgpf_bl_pwm_get_brightness(struct backlight_device *bd);
static int pgpf_bl_pwm_update_status(struct backlight_device *bd);

static struct pgpf_bl_device pgpf_bl_pwm_dev = {
	.init = pgpf_bl_pwm_init,
	.prep = pgpf_bl_pwm_start,
	.suspend = pgpf_bl_pwm_suspend,
	.resume = pgpf_bl_pwm_resume,
	.post = pgpf_bl_pwm_end,
	.exit = pgpf_bl_pwm_exit,
	.get_brightness = pgpf_bl_pwm_get_brightness,
	.update_status = pgpf_bl_pwm_update_status,
};

static inline int pgpf_bl_check_pwm_active(int channel)
{
	if (channel == MP200_PWM_CH0) {
		return (readl(PWM_CH0_CTRL) & PWM_START_BIT_ON);
	} else {
		return (readl(PWM_CH1_CTRL) & PWM_START_BIT_ON);
	}
}

static int pgpf_bl_pwm_get_brightness(struct backlight_device *bd)
{
	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_get_brightness() called\n");

	return pgpf_bl_pwm_dev.bd->props.brightness;
}

static int pgpf_bl_pwm_update_status(struct backlight_device *bd)
{
	int ret = 0;
	unsigned long lock_flags;
	int brightness = bd->props.brightness;
	mp200_pwm_ch_config_t ch_config = {
		.use_cmp = PWM_BIT_CMP0,
		.inverse = param.dev_data.inverse,
		.interrupt_count = 0,
		.interrupt_loop = 0,
		.mode = PWM_CODE_MODE_OR,
	};
	mp200_pwm_cmpcnt_t src_cmpcnt = {
		.cmp = MP200_PWM_COMPARE0,
		.delay = 0,
		.lead_edge = 0,
		.trail_edge = brightness * (param.hw_data.clock / param.dev_data.period) / PGPF_BACKLIGHT_MAX,
		.total_cycle = param.hw_data.clock / param.dev_data.period,
		.loop_count = 0,
		.cb_info = NULL,
	};

	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_update_status() called\n");

	/* if FB is blanked, backlight will be off */
	if (bd->props.power != FB_BLANK_UNBLANK || 
		bd->props.fb_blank != FB_BLANK_UNBLANK) {
		brightness = 0;
	}

	if (brightness < 0 || brightness > PGPF_BACKLIGHT_MAX) {
		printk_err("invalid value\n");
		return -EINVAL;
	}

	spin_lock_irqsave(&pgpf_bl_pwm_lock, lock_flags);

	ret = mp200_pwm_stop(param.hw_data.channel);
	if (ret) {
		printk_err("error on mp200_pwm_stop (0x%08x)\n", ret);
		goto end;
	}
	
	udelay(1);

	if (brightness != 0) {
		ret = mp200_pwm_set_channel_config(param.hw_data.channel, &ch_config);
		if (ret) {
			printk_err("error on mp200_pwm_set_channel_config (0x%08x)\n", ret);
			goto end;
		}
		
		ret = mp200_pwm_set_compare_counter(param.hw_data.channel, &src_cmpcnt);
		if (ret) {
			printk_err("error on mp200_pwm_set_compare_counter (0x%08x)\n", ret);
			goto end;
		}
		ret = mp200_pwm_start(param.hw_data.channel);
		if (ret) {
			printk_err("error on mp200_pwm_start (0x%08x)\n", ret);
			goto end;
		}
	}
	
  end:
	spin_unlock_irqrestore(&pgpf_bl_pwm_lock, lock_flags);

	return ret;
}

static int pgpf_bl_pwm_start(void)
{
	int ret = 0;
	unsigned long lock_flags;

	mp200_pwm_ch_config_t ch_config = {
		.use_cmp = PWM_BIT_CMP0,
		.inverse = param.dev_data.inverse,
		.interrupt_count = 0,
		.interrupt_loop = 0,
		.mode = PWM_CODE_MODE_OR,
	};
	mp200_pwm_cmpcnt_t src_cmpcnt = {
		.cmp = MP200_PWM_COMPARE0,
		.delay = 0,
		.lead_edge = 0,
		.trail_edge = param.hw_data.clock / param.dev_data.period,
		.total_cycle = param.hw_data.clock / param.dev_data.period,
		.loop_count = 1, /* just for initialization */
		.cb_info = NULL,
	};

	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_start() called\n");

	spin_lock_irqsave(&pgpf_bl_pwm_lock, lock_flags);
	if (!pgpf_bl_check_pwm_active(param.hw_data.channel)) {
		/* set to pwm */
		if (param.hw_data.channel == MP200_PWM_CH0) {
			outl(inl(CHG_PINSEL_G80) | PWM0_PINSEL_MASK, CHG_PINSEL_G80);
		} else {
			outl(inl(CHG_PINSEL_G80) | PWM1_PINSEL_MASK, CHG_PINSEL_G80);
		}
		
		ret = mp200_pwm_stop(param.hw_data.channel);
		if (ret) {
			printk_err("error on mp200_pwm_stop (0x%08x)\n", ret);
			goto end;
		}

		udelay(1);

		ret = mp200_pwm_set_channel_config(param.hw_data.channel, &ch_config);
		if (ret) {
			printk_err("error on mp200_pwm_set_channel_config (0x%08x)\n", ret);
			goto end;
		}
		ret = mp200_pwm_set_compare_counter(param.hw_data.channel, &src_cmpcnt);
		if (ret) {
			printk_err("error on mp200_pwm_set_compare_counter (0x%08x)\n", ret);
			goto end;
		}
		ret = mp200_pwm_start(param.hw_data.channel);
		if (ret) {
			printk_err("error on mp200_pwm_start (0x%08x)\n", ret);
			goto end;
		}
	}
	pgpf_bl_pwm_dev.suspend_flag = 0;

  end:
	spin_unlock_irqrestore(&pgpf_bl_pwm_lock, lock_flags);

	return ret;
}

static int pgpf_bl_pwm_end(void)
{
	unsigned long lock_flags;
	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_end() called\n");

	/* set to gpio */
	spin_lock_irqsave(&pgpf_bl_pwm_lock, lock_flags);
	if (param.hw_data.channel == MP200_PWM_CH0) {
		gpio_direction_output(GPIO_P94, 0x0);
		outl(inl(CHG_PINSEL_G80) & ~(PWM0_PINSEL_MASK), CHG_PINSEL_G80);
	} else {
		gpio_direction_output(GPIO_P95, 0x0);
		outl(inl(CHG_PINSEL_G80) & ~(PWM1_PINSEL_MASK), CHG_PINSEL_G80);
	}
	spin_unlock_irqrestore(&pgpf_bl_pwm_lock, lock_flags);
	return 0;
}

static int pgpf_bl_pwm_suspend(void)
{
	int ret;
	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_suspend() called\n");

	pgpf_bl_pwm_dev.bd->props.power = pgpf_bl_pwm_dev.bd->props.fb_blank = FB_BLANK_POWERDOWN;
	ret = pgpf_bl_update_status(pgpf_bl_pwm_dev.bd);
	pgpf_bl_pwm_dev.suspend_flag = 1;
	return ret;
}

static int pgpf_bl_pwm_resume(void)
{
	int ret;

	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_resume() called\n");
	pgpf_bl_pwm_dev.suspend_flag = 0;
	pgpf_bl_pwm_dev.bd->props.power = pgpf_bl_pwm_dev.bd->props.fb_blank = FB_BLANK_UNBLANK;
	ret = pgpf_bl_update_status(pgpf_bl_pwm_dev.bd);

	return ret;
}

static int pgpf_bl_pwm_init(struct fb_info* info)
{
	int ret = 0;
	int start_flag = 0;
	unsigned long tedge1 = 0;

	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_init() called\n");

	if (!param.dev_data.name) {
		printk_err("backlight parameters are not set!\n");
		return -1;
	}

	pgpf_bl_pwm_dev.bd = backlight_device_register(param.dev_data.name, info->dev, NULL, &pgpf_bl_ops);
	if (IS_ERR(pgpf_bl_pwm_dev.bd)) {
		ret = PTR_ERR(pgpf_bl_pwm_dev.bd);
		printk_err("error on backlight_device_register (0x%08x)\n", ret);
		goto end;
	}
	info->bl_dev = pgpf_bl_pwm_dev.bd;
	pgpf_bl_pwm_dev.bd->props.max_brightness = PGPF_BACKLIGHT_MAX;
	pgpf_bl_pwm_dev.bd->props.power = FB_BLANK_UNBLANK;

	start_flag = pgpf_bl_check_pwm_active(param.hw_data.channel);

	if (start_flag) {
		printk_info("pwm has already started -> keep previous setting\n");
		pgpf_bl_pwm_dev.suspend_flag = 0;
		if (param.hw_data.channel == MP200_PWM_CH0) {
			tedge1 = readl(PWM_CH0_TEDGE0);
		} else {
			tedge1 = readl(PWM_CH1_TEDGE0);
		}
		pgpf_bl_pwm_dev.bd->props.brightness = tedge1 * PGPF_BACKLIGHT_MAX / (param.hw_data.clock / param.dev_data.period);
		pgpf_bl_update_status(pgpf_bl_pwm_dev.bd);
	} else {
		printk_info("pwm has not started yet -> set maximum value\n");
		pgpf_bl_pwm_dev.bd->props.brightness = PGPF_BACKLIGHT_MAX;
		pgpf_bl_pwm_start();
	}

  end:
	return ret;
}

static void pgpf_bl_pwm_exit(struct fb_info* info)
{
	printk_dbg(_DEBUG_BL, "pgpf_bl_pwm_exit() called\n");
	backlight_device_unregister(pgpf_bl_pwm_dev.bd);
	info->bl_dev = NULL;
}

int pgpf_bl_register(struct pgpf_bl_device** bl_dev, struct platform_device *pf_dev)
{
	printk_dbg(_DEBUG_BL, "pgpf_bl_register() called\n");
	*bl_dev = &pgpf_bl_pwm_dev;
	param = *(struct pgpf_bl_pwm_param*)pf_dev->dev.platform_data;
	return 0;
}
