/*
 * drivers/misc/at91_pwm.c
 *
 *  Copyright (C) 2009 Bluewater System Ltd
 *
 * 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.
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/clk.h>

#include <asm/arch/board.h>
#include <asm/arch/gpio.h>
#include <asm/delay.h>

#include <asm/irq.h>

#define PERIPH_A			0
#define PERIPH_B			1

/* Timer counter chanel registers */
#define AT91_PWM_CCR			0x00
#define  CRR_CLKEN			(1 << 0)
#define  CRR_CLKDIS			(1 << 1)
#define  CRR_SWTRG			(1 << 2)
#define AT91_PWM_CMR			0x04
#define  CMR_WAVESEL_UP_NOAUTO		(0 << 13)
#define  CMR_WAVESEL_UP_AUTO		(2 << 13)
#define  CMR_WAVESEL_UPDOWN_NOAUTO	(1 << 13)
#define  CMR_WAVESEL_UPDOWN_AUTO	(3 << 13)
#define  CMR_WAVE			(1 << 15)
#define  CMR_ACPA_NONE			(0 << 16)
#define  CMR_ACPA_SET			(1 << 16)
#define  CMR_ACPA_CLEAR			(2 << 16)
#define  CMR_ACPA_TOGGLE		(3 << 16)
#define  CMR_ACPC_NONE			(0 << 18)
#define  CMR_ACPC_SET			(1 << 18)
#define  CMR_ACPC_CLEAR			(2 << 18)
#define  CMR_ACPC_TOGGLE		(3 << 18)
#define  CMR_BCPB_SET			(1 << 24)
#define  CMR_BCPB_CLEAR			(2 << 24)
#define  CMR_BCPC_SET			(1 << 26)
#define  CMR_EEVT_XC0			(1 << 10)
#define AT91_PWM_CV			0x10
#define AT91_PWM_RA			0x14
#define AT91_PWM_RB			0x18
#define AT91_PWM_RC			0x1C
#define AT91_PWM_SR			0x20
#define AT91_PWM_IER			0x24
#define  IER_NONE			0x00
#define AT91_PWM_IDR			0x28
#define  IDR_MASK			0xFF
#define AT91_PWM_IMR			0x2C

struct at91_tc_pin {
	int		gpio;
	int		periph;
};

struct at91_pwm {
	unsigned int	channel;
	void __iomem	*regs;
	struct clk	*clk;
	unsigned long	clock_rate;
	unsigned int	tioa_duty;	/* Percentage */
	unsigned int	tiob_duty;	/* Percentage */
	unsigned int	period;		/* In micro-seconds */
};

static struct at91_tc_pin at91_tioa_pin[] = {
	{AT91_PIN_PA26,	PERIPH_A},
	{AT91_PIN_PA27,	PERIPH_A},
	{AT91_PIN_PA28, PERIPH_A},
	{AT91_PIN_PB0,	PERIPH_B},
	{AT91_PIN_PB2,	PERIPH_B},
	{AT91_PIN_PB3,	PERIPH_B},
};

static struct at91_tc_pin at91_tiob_pin[] = {
	{AT91_PIN_PC9,  PERIPH_B},
	{AT91_PIN_PC7,  PERIPH_A},
	{AT91_PIN_PC6,  PERIPH_A},
	{AT91_PIN_PB1,  PERIPH_B},
	{AT91_PIN_PB18, PERIPH_B},
	{AT91_PIN_PB19, PERIPH_B},
};

static inline unsigned long micros_to_rc(struct at91_pwm *pwm, 
					 unsigned long micros)
{
	return (micros * (pwm->clock_rate / 1000) / 1000);	
}

static inline void at91_pwm_write(struct at91_pwm *pwm, int reg, int val)
{
	writel(val, pwm->regs + reg);
}

static inline int at91_pwm_read(struct at91_pwm *pwm, int reg)
{
	return readl(pwm->regs + reg);
}

static int at91_pwm_find(struct device *dev, void *data)
{	
	struct platform_device *pdev = to_platform_device(dev);
	unsigned int channel = (unsigned int)data;
	struct at91_pwm *pwm;
	
	if (strcmp(pdev->name, "at91_pwm") != 0)
		return 0;

	pwm = dev_get_drvdata(dev);
	if (!pwm)
		return 0;
	
	return (pwm->channel == channel);
}

static struct at91_pwm *at91_pwm_get(int channel)
{
	struct device *dev;

	dev = bus_find_device(&platform_bus_type, NULL, 
			      (void *)channel, at91_pwm_find);
	if (!dev)
		return NULL;
	
	return dev_get_drvdata(dev);
}

int at91_pwm_irq_enable(int channel, int enable)
{
	struct at91_pwm *pwm = at91_pwm_get(channel);

	if (!pwm)
		return -ENODEV;
	
	at91_pwm_write(pwm, AT91_PWM_IDR, ~0x0);
	at91_pwm_write(pwm, AT91_PWM_IER, enable ? (1 << 4) : 0x0);
	return 0;
}
EXPORT_SYMBOL(at91_pwm_irq_enable);

int at91_pwm_status(int channel, unsigned *status)
{
	struct at91_pwm *pwm = at91_pwm_get(channel);

	if (!pwm)
		return -ENODEV;

	at91_pwm_read(pwm, AT91_PWM_SR);
	return 0;
}
EXPORT_SYMBOL(at91_pwm_status);

static void at91_pwm_tioa_enable(struct at91_pwm *pwm, int enable)
{	
	unsigned int ctrl = at91_pwm_read(pwm, AT91_PWM_CMR);

	/*
	 * FIXME - This is dodgy since the at91 gpio functions are marked
	 * as __init_or_module, so this code will cause section mismatches if
	 * CONFIG_MODULES is not set
	 */
	if (enable) {
		if (at91_tioa_pin[pwm->channel].periph == PERIPH_A)
			at91_set_A_periph(at91_tioa_pin[pwm->channel].gpio, 0);
		else
			at91_set_B_periph(at91_tioa_pin[pwm->channel].gpio, 0);

	} else {
		at91_set_gpio_output(at91_tioa_pin[pwm->channel].gpio, 0);
	}
	
	at91_pwm_write(pwm, AT91_PWM_CMR, ctrl);
}

static void at91_pwm_tiob_enable(struct at91_pwm *pwm, int enable)
{	
	unsigned int ctrl = at91_pwm_read(pwm, AT91_PWM_CMR);

	/*
	 * FIXME - This is dodgy since the at91 gpio functions are marked
	 * as __init_or_module, so this code will cause section mismatches if
	 * CONFIG_MODULES is not set
	 */
	if (enable) {
		if (at91_tiob_pin[pwm->channel].periph == PERIPH_A)
			at91_set_A_periph(at91_tiob_pin[pwm->channel].gpio, 0);
		else
			at91_set_B_periph(at91_tiob_pin[pwm->channel].gpio, 0);

	} else {
		at91_set_gpio_output(at91_tiob_pin[pwm->channel].gpio, 0);
	}
	
	at91_pwm_write(pwm, AT91_PWM_CMR, ctrl);
}

/* Sysfs files */
static ssize_t at91_pwm_sysfs_period_show(struct device *dev, 
					  struct device_attribute *attr,
					  char *buf)	
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);

	return sprintf(buf, "%d\n", pwm->period);
}

static ssize_t at91_pwm_sysfs_period_store(struct device *dev, 
					   struct device_attribute *attr,
					   const char *buf, size_t count)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);
	unsigned int ra, rb, rc;
	unsigned long period;
	
	period = simple_strtoul(buf, NULL, 0);
	rc = micros_to_rc(pwm, period);
	if (rc > 0xffff)
		return -EINVAL;

	pwm->period = period;
	ra = (rc * pwm->tioa_duty) / 100;
	rb = (rc * pwm->tiob_duty) / 100;

	at91_pwm_write(pwm, AT91_PWM_RA, ra);
	at91_pwm_write(pwm, AT91_PWM_RB, rb);
	at91_pwm_write(pwm, AT91_PWM_RC, rc);
	
	return count;
}

static ssize_t at91_pwm_sysfs_tioa_duty_show(struct device *dev, 
					     struct device_attribute *attr,
					     char *buf)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);
	
	return sprintf(buf, "%d\n", pwm->tioa_duty);
}

static ssize_t at91_pwm_sysfs_tioa_duty_store(struct device *dev, 
					      struct device_attribute *attr,
					      const char *buf, size_t count)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);
	unsigned int ra;
	int duty;
	
	duty = simple_strtol(buf, NULL, 0);
	if (duty < 0 || duty > 100)
		return -EINVAL;

	if (pwm->tioa_duty == 0 && duty > 0)
		at91_pwm_tioa_enable(pwm, 1);
	pwm->tioa_duty = duty;
	if (pwm->tioa_duty == 0) 
		at91_pwm_tioa_enable(pwm, 0);

	ra = (at91_pwm_read(pwm, AT91_PWM_RC) * pwm->tioa_duty) / 100;	
	at91_pwm_write(pwm, AT91_PWM_RA, ra);
	
	return count;
}

static ssize_t at91_pwm_sysfs_tiob_duty_show(struct device *dev, 
					     struct device_attribute *attr,
					     char *buf)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);
	
	return sprintf(buf, "%d\n", pwm->tiob_duty);
}

static ssize_t at91_pwm_sysfs_tiob_duty_store(struct device *dev, 
					      struct device_attribute *attr,
					      const char *buf, size_t count)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);
	unsigned int rb;
	int duty;
	
	duty = simple_strtol(buf, NULL, 0);
	if (duty < 0 || duty > 100)
		return -EINVAL;

	if (pwm->tiob_duty == 0 && duty > 0)
		at91_pwm_tiob_enable(pwm, 1);
	pwm->tiob_duty = duty;
	if (pwm->tiob_duty == 0) 
		at91_pwm_tiob_enable(pwm, 0);

	rb = (at91_pwm_read(pwm, AT91_PWM_RC) * pwm->tiob_duty) / 100;	
	at91_pwm_write(pwm, AT91_PWM_RB, rb);
	
	return count;
}

static ssize_t at91_pwm_sysfs_ra_show(struct device *dev,
				      struct device_attribute *attr,
				      char *buf)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);

	return sprintf(buf, "%d\n", at91_pwm_read(pwm, AT91_PWM_RA));
}

static ssize_t at91_pwm_sysfs_rb_show(struct device *dev,
				      struct device_attribute *attr,
				      char *buf)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);

	return sprintf(buf, "%d\n", at91_pwm_read(pwm, AT91_PWM_RB));
}
static ssize_t at91_pwm_sysfs_rc_show(struct device *dev,
				      struct device_attribute *attr,
				      char *buf)
{
	struct at91_pwm *pwm = dev_get_drvdata(dev);

	return sprintf(buf, "%d\n", at91_pwm_read(pwm, AT91_PWM_RC));
}

static DEVICE_ATTR(period, S_IWUSR | S_IRUGO,
		   at91_pwm_sysfs_period_show,
		   at91_pwm_sysfs_period_store);
static DEVICE_ATTR(tioa_duty, S_IWUSR | S_IRUGO, 
		   at91_pwm_sysfs_tioa_duty_show, 
		   at91_pwm_sysfs_tioa_duty_store);
static DEVICE_ATTR(tiob_duty, S_IWUSR | S_IRUGO, 
		   at91_pwm_sysfs_tiob_duty_show, 
		   at91_pwm_sysfs_tiob_duty_store);
static DEVICE_ATTR(ra, S_IRUGO, at91_pwm_sysfs_ra_show, NULL);
static DEVICE_ATTR(rb, S_IRUGO, at91_pwm_sysfs_rb_show, NULL);
static DEVICE_ATTR(rc, S_IRUGO, at91_pwm_sysfs_rc_show, NULL);

static struct attribute *at91_pwm_attributes[] = {
	&dev_attr_period.attr,
	&dev_attr_tioa_duty.attr,
	&dev_attr_tiob_duty.attr,
	&dev_attr_ra.attr,
	&dev_attr_rb.attr,
	&dev_attr_rc.attr,
	NULL,
};

static const struct attribute_group at91_pwm_sysfs_files = {
	.attrs = at91_pwm_attributes,
};

static unsigned long __init at91_pwm_get_clock_rate(struct at91_pwm *pwm,
						    struct device *dev,
						    int clock_div)
{
	int clk_divs[] = {2, 8, 32, 128};
	struct clk *mclk;
	unsigned long mclk_rate;

	mclk = clk_get(dev, "main");
	if (IS_ERR(mclk))
		return PTR_ERR(mclk);
	mclk_rate = clk_get_rate(mclk);
	clk_put(mclk);

	if (clock_div == AT91_TC_SLCK)
		return AT91_SLOW_CLOCK;
	else
		return mclk_rate / clk_divs[clock_div];
}

static int __init at91_pwm_setup(struct at91_pwm *pwm, 
				 struct at91_pwm_data *tc)
{
	unsigned int ra, rb, rc;
	unsigned int ctrl;

	rc = micros_to_rc(pwm, tc->period);
	if (rc > 0xffff)
		return -EINVAL;

	ra = (rc * tc->tioa_duty) / 100;
	rb = (rc * tc->tiob_duty) / 100;

	pwm->channel = tc->channel;
	pwm->period = tc->period;
	pwm->tioa_duty = tc->tioa_duty;
	pwm->tiob_duty = tc->tiob_duty;

	/* Configure the output gpios */
	ctrl = CMR_WAVE | CMR_WAVESEL_UP_AUTO | (tc->clock_div & 0x7);
	if (tc->tio & AT91_TIOA) {
		ctrl |= CMR_ACPA_CLEAR | CMR_ACPC_SET | CMR_EEVT_XC0;
		if (at91_tioa_pin[tc->channel].periph == PERIPH_A)
			at91_set_A_periph(at91_tioa_pin[tc->channel].gpio, 0);
		else
			at91_set_B_periph(at91_tioa_pin[tc->channel].gpio, 0);
	}
	
	if (tc->tio & AT91_TIOB) {
		ctrl |= CMR_BCPB_CLEAR | CMR_BCPC_SET;
		if (at91_tiob_pin[tc->channel].periph == PERIPH_A)
			at91_set_A_periph(at91_tiob_pin[tc->channel].gpio, 0);
		else
			at91_set_B_periph(at91_tiob_pin[tc->channel].gpio, 0);
	}

	at91_pwm_write(pwm, AT91_PWM_CCR, 0x0);
	at91_pwm_write(pwm, AT91_PWM_CMR, ctrl);
	at91_pwm_write(pwm, AT91_PWM_RA, ra);
	at91_pwm_write(pwm, AT91_PWM_RB, rb);
	at91_pwm_write(pwm, AT91_PWM_RC, rc);
	at91_pwm_write(pwm, AT91_PWM_IDR, IDR_MASK);	
	at91_pwm_write(pwm, AT91_PWM_IER, 
		       tc->enable_irq ? (1 << 4) : IER_NONE);
	at91_pwm_write(pwm, AT91_PWM_CCR, CRR_CLKEN | CRR_SWTRG);

	return 0;
}

static int __devinit at91_pwm_probe(struct platform_device *pdev)
{
	struct at91_pwm_data *pdata;
	struct resource *res;
	struct at91_pwm *pwm;
	char clk_name[8];
	int err = -ENODEV;

	pdata = pdev->dev.platform_data;
	if (!pdata)
		goto out;

	pwm = kzalloc(sizeof(struct at91_pwm), GFP_KERNEL);
	if (!pwm) {
		err = -ENOMEM;
		goto out;
	}
	
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		err = -ENODEV;		
		goto out_free_pwm;
	}

	pwm->regs = ioremap_nocache(res->start, res->end - res->start);
	if (!pwm->regs)
		goto out_free_pwm;
	
	snprintf(clk_name, sizeof(clk_name), "tc%d_clk", pdata->channel);
	pwm->clk = clk_get(&pdev->dev, clk_name);
	if (IS_ERR(pwm->clk)) {
		err = PTR_ERR(pwm->clk);
		goto out_unmap_regs;
	}
	clk_enable(pwm->clk);

	platform_set_drvdata(pdev, pwm);
	pwm->clock_rate = at91_pwm_get_clock_rate(pwm, &pdev->dev, 
						  pdata->clock_div);
	err = at91_pwm_setup(pwm, pdata);
	if (err)
		goto out_put_clk;

	err = sysfs_create_group(&pdev->dev.kobj, &at91_pwm_sysfs_files);
	if (err) 
		goto out_put_clk;
	
	if (pdata->tio & AT91_TIOA)
		dev_info(&pdev->dev, "TIOA: period = %dmsec, duty = %d%%\n", 
			 pdata->period, pdata->tioa_duty);
	if (pdata->tio & AT91_TIOB)
		dev_info(&pdev->dev, "TIOB: period = %dmsec, duty = %d%%\n", 
			 pdata->period, pdata->tiob_duty);
	return 0;
	
out_put_clk:
	clk_put(pwm->clk);
out_unmap_regs:
	iounmap(pwm->regs);
out_free_pwm:
	kfree(pwm);
out:
	return err;
}

static int __devexit at91_pwm_remove(struct platform_device *pdev)
{
	struct at91_pwm *pwm = platform_get_drvdata(pdev);

	sysfs_remove_group(&pdev->dev.kobj, &at91_pwm_sysfs_files);
	clk_put(pwm->clk);
	iounmap(pwm->regs);
	kfree(pwm);

	return 0;
}

static struct platform_driver at91_pwm_driver = {
	.probe		= at91_pwm_probe,
	.remove		= __devexit_p(at91_pwm_remove),
	.driver 	= {
		.name	= "at91_pwm",
	},
};

static int __init at91_pwm_init(void)
{
	return platform_driver_register(&at91_pwm_driver);
}

static void __exit at91_pwm_exit(void)
{
	platform_driver_unregister(&at91_pwm_driver);
}

module_init (at91_pwm_init);
module_exit (at91_pwm_exit);

MODULE_AUTHOR ("Andrew Turner <aturner@bluewatersys.com>, "
	       "Ryan Mallon <ryan@bluewatersys.com>");
MODULE_DESCRIPTION ("AT91 PWM driver");
MODULE_LICENSE ("GPL");

