/*
 * BRIEF MODULE DESCRIPTION
 *	Driver for using and allocating GPIO on the
 *      AMD Au1000 MIPS processor.
 *
 * Copyright 2004 Cooper Street Innovations Inc.
 * Author: Charles Eidsness	<charles@cooper-street.com>
 *
 *  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  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.
 *
 * History:
 *
 * 2004-10-14 Charles Eidsness -- Original Version
 *
 */


#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <asm/mach-au1x00/au1000.h>
#include <asm/mach-au1x00/au1000_gpio.h>

MODULE_AUTHOR("Charles Eidsness <charles@cooper-street.com>");
MODULE_DESCRIPTION("Au1000 GPIO Driver");
MODULE_LICENSE("GPL");

#define GPIO_NAME	"gpio"
#define VERSION		"1.0"
#define GPIO_MAJOR	254

typedef struct _au1000_gpio_reg {
	u32 volatile triout;
	u32 volatile blank;
	u32 volatile output;
	u32 volatile outputclr;
	u32 volatile input;
} au1000_gpio_reg_t;

typedef struct _au1000_pinfunc_reg {
	u32 volatile pinfunc;
} au1000_pinfunc_reg_t;

typedef struct _au1000_gpio {
	short init;
	_gpio_get_t default_pinfunc;
	_gpio_get_t default_tristate;
	au1000_pinfunc_reg_t volatile *gpio_pinfunc;
	struct resource *gpio_pinfunc_res;
	au1000_gpio_reg_t volatile *gpio_ioport;
	struct resource *gpio_ioport_res;
	spinlock_t gpio_lock;
} au1000_gpio_t;

static au1000_gpio_t* au1000 = NULL;

static int gpio_pinfunc_get(_gpio_get_t * data)
{
	spin_lock(&au1000->gpio_lock);
	*data = au1000->gpio_pinfunc->pinfunc;
	spin_unlock(&au1000->gpio_lock);
	return 0;
}

static int gpio_pinfunc_set(_gpio_set_t data)
{
	_gpio_get_t prev;
	spin_lock(&au1000->gpio_lock);
	prev = au1000->gpio_pinfunc->pinfunc;
	au1000->gpio_pinfunc->pinfunc = (prev & ~data.set) | (data.val & data.set);
	prev = au1000->gpio_pinfunc->pinfunc;
	printk(KERN_NOTICE "GPIO: Pin function register set to: %08X.\n",prev);
	spin_unlock(&au1000->gpio_lock);
	return 0;
}

static int gpio_tristate_get(_gpio_get_t * data)
{
	spin_lock(&au1000->gpio_lock);
	*data = au1000->gpio_ioport->triout;
	spin_unlock(&au1000->gpio_lock);
	return 0;
}

static int gpio_tristate_set(_gpio_set_t data)
{
	_gpio_get_t prev;
	spin_lock(&au1000->gpio_lock);
	prev = au1000->gpio_ioport->triout;
	au1000->gpio_ioport->triout = ~((prev & ~data.set) | (~data.val & data.set));
	prev = au1000->gpio_ioport->triout;
	spin_unlock(&au1000->gpio_lock);
	return 0;
}

static int gpio_ioctl (struct inode *inode, struct file *filp, 
			unsigned int cmd, unsigned long arg)
{
	int err;
	_gpio_get_t input;
	_gpio_set_t output;
	
	if (_IOC_TYPE(cmd) != AU1000_GPIO_IOCTL_BASE) {
		printk(KERN_ERR "GPIO: not a GPIO command.\n");
		return -ENOTTY;
	}
	
	switch (cmd) {
		case AU1000_GPIO_PINFUNC_SET:
			if (copy_from_user(&output, (_gpio_set_t *)arg, sizeof(_gpio_set_t)))
				return -EFAULT;
			return gpio_pinfunc_set(output);
		case AU1000_GPIO_PINFUNC_GET :
			err = gpio_pinfunc_get(&input);
			if (err != 0) return err;
			if (copy_to_user((_gpio_get_t *)arg, &input, sizeof(_gpio_get_t)))
				return -EFAULT;
			return err;
		case AU1000_GPIO_TRISTATE_SET:
			if (copy_from_user(&output, (_gpio_set_t *)arg, sizeof(_gpio_set_t)))
				return -EFAULT;	
			return gpio_tristate_set(output);			
		case AU1000_GPIO_TRISTATE_GET :
			err = gpio_tristate_get(&input);
			if (err != 0) return err;
			if (copy_to_user((_gpio_get_t *)arg, &input, sizeof(_gpio_get_t)))
				return -EFAULT;
			return err;
		default:
			printk(KERN_ERR "GPIO: not a GPIO command.\n");
			return -ENOTTY;
	}
	return 0;
}

static int gpio_open(struct inode *inode, struct file *file)
{
	if (!au1000->init) {
		/* require once per H/W reset to activate input FFs*/
		spin_lock(&au1000->gpio_lock);
		au1000->gpio_ioport->input = 0;
		spin_unlock(&au1000->gpio_lock);
		/* default set when booting, before driver is loaded*/
		gpio_pinfunc_get(&au1000->default_pinfunc);
		gpio_tristate_get(&au1000->default_tristate);
		au1000->init++;
	}

	return 0;
}

_gpio_get_t au1000_gpio_read(void)
{
	_gpio_get_t data;
	spin_lock(&au1000->gpio_lock);
	data = au1000->gpio_ioport->input;
	spin_unlock(&au1000->gpio_lock);
	return data;
}
EXPORT_SYMBOL(au1000_gpio_read);

static ssize_t gpio_read(struct file *file, char *buf, size_t count,
	loff_t *offset)
{
	_gpio_get_t data;
	if (count < sizeof(_gpio_get_t)) return 0;
	data = au1000_gpio_read();
	if (copy_to_user(buf, &data, sizeof(_gpio_get_t))) return -EFAULT;
	return sizeof(_gpio_set_t);
}

void au1000_gpio_write(_gpio_set_t data)
{
	spin_lock(&au1000->gpio_lock);
	au1000->gpio_ioport->outputclr = data.set & ~data.val;
	au1000->gpio_ioport->output = data.set & data.val;
	spin_unlock(&au1000->gpio_lock);
}
EXPORT_SYMBOL(au1000_gpio_write);

static ssize_t gpio_write(struct file *file, const char *buf, size_t count,
	loff_t *offset)
{
	_gpio_set_t data;
	if (count < sizeof(_gpio_set_t)) return 0;
	if (copy_from_user(&data, buf,sizeof(_gpio_set_t))) return -EFAULT;
	au1000_gpio_write(data);
	return sizeof(_gpio_set_t);		
}

static struct file_operations gpio_fops =
{
	owner:	THIS_MODULE,
	ioctl:	gpio_ioctl,
	open:	gpio_open,
	read:	gpio_read,
	write:	gpio_write,
};

static void gpio_free(au1000_gpio_t* r)
{
	if (r->gpio_pinfunc_res != NULL) {
		release_region(SYS_PINFUNC,sizeof(au1000_pinfunc_reg_t));
		r->gpio_pinfunc_res = NULL;
		r->gpio_pinfunc = NULL;
	}

	if (r->gpio_ioport_res != NULL) {
		release_region(SYS_TRIOUTRD,sizeof(au1000_gpio_reg_t));
		r->gpio_ioport_res = NULL;
		r->gpio_ioport = NULL;
	}
	kfree(r);
	r = NULL;
}

static au1000_gpio_t* gpio_new(void)
{
	au1000_gpio_t * r;
	r = kmalloc(sizeof(au1000_gpio_t), GFP_KERNEL);
	if (r == NULL)
		return NULL;

	memset(r, 0, sizeof(au1000_gpio_t));

	if ((r->gpio_pinfunc_res = request_region(SYS_PINFUNC,
	       		sizeof(au1000_pinfunc_reg_t), "Au1x00 PF")) == NULL) {
		printk(KERN_ERR "GPIO: can't grap pin function port.\n");
		gpio_free(r);
		return NULL;
	}
	r->gpio_pinfunc = (au1000_pinfunc_reg_t*)r->gpio_pinfunc_res->start;
	
	if ((r->gpio_ioport_res = request_region(SYS_TRIOUTRD,
	       		sizeof(au1000_gpio_reg_t), "Au1x00 GPIO")) == NULL) {
		printk(KERN_ERR "GPIO: can't grap IO port.\n");
		gpio_free(r);
		return NULL;
	}
	r->gpio_ioport = (au1000_gpio_reg_t*)r->gpio_ioport_res->start;

	spin_lock_init(&r->gpio_lock);
	return r;
}

static int gpio_read_proc(char *buf, char **start, off_t offset, int count,
			int *eof, void *dat)
{
	_gpio_get_t data;
	u32 pinfunc;
	u32 tristate;
	int i;
	spin_lock(&au1000->gpio_lock);
	data = au1000->gpio_ioport->input;
	spin_unlock(&au1000->gpio_lock);
	gpio_pinfunc_get(&pinfunc);
	gpio_tristate_get(&tristate);
	printk("---- Pin Functions --\n\n");
	printk((pinfunc & (1 << 0)) ? "GPIO[16:18]\n"	: "SSI Port 0\n");
	printk((pinfunc & (1 << 1)) ? "SSI Port 1\n"	: "AC97 Port\n");
	printk((pinfunc & (1 << 2)) ? "GPIO[19]\n"	: "Infared Port\n");
	printk((pinfunc & (1 << 3)) ? "GPIO[20]\n"	: "UART0\n");
	printk((pinfunc & (1 << 4)) ? "GPIO[28:24]\n"	: "Ethernet Port 1\n");
	printk((pinfunc & (1 << 5)) ? "GPIO[31:29]\n" 	: "I2S\n");
	printk((pinfunc & (1 << 6)) ? "GPIO[8]\n"	: "I2S\n");
	printk((pinfunc & (1 << 7)) ? "UART3\n" 	: "GPIO[14:9]\n");
	printk((pinfunc & (1 << 8)) ? "Infared Port\n"  : "GPIO[15]\n");
	if (pinfunc & (1 << 9)) printk((pinfunc & (1 << 16)) ? "32kHz OSC out\n" : "EXTCLK0\n"); 
	else printk("GPIO[2]\n");
	printk((pinfunc & (1 << 10)) ? "EXTCLK1\n"	: "GPIO[3]\n");
	printk((pinfunc & (1 << 11)) ? "Static ROM CKE\n" : "GPIO[6]\n");
	printk((pinfunc & (1 << 12)) ? "GPIO[21]\n"	: "UART1 TX\n");
	printk((pinfunc & (1 << 13)) ? "GPIO[22]\n"	: "UART2 TX\n");
	printk((pinfunc & (1 << 14)) ? "GPIO[23]\n"	: "UART3 TX\n");
	printk((pinfunc & (1 << 15)) ? "USB Host Port\n": "USB Device Port\n");
	
	printk("\n---- GPIO Value ----\n\n");
	for (i = 0; i < 32; i++) {
		printk("GPIO %02i: ",i);
		printk((tristate & (1 << i)) ? "OUT " : " IN ");
		printk((data & (1 << i)) ? "++ HIGH\n" : "-- LOW\n");
	}
	printk("\n--------------------\n\n");
	return 0;
}

static void __exit gpio_exit(void)
{
	gpio_free(au1000);
	unregister_chrdev(GPIO_MAJOR, GPIO_NAME);
	remove_proc_entry("gpio", NULL);
	printk(KERN_NOTICE "GPIO: Closed.\n");
}

static int __init gpio_init(void)
{
	int res;
	res = register_chrdev(GPIO_MAJOR, GPIO_NAME, &gpio_fops);
	if(res < 0) {
		printk(KERN_ERR "GPIO: Can't register device with kernel.\n");
		return res;
	} else {
		printk(KERN_NOTICE "GPIO: Driver Version %s Initialized\n",VERSION);
	}

	au1000 = gpio_new();
	if(au1000 == NULL) gpio_exit();
	create_proc_read_entry("gpio", 0, NULL, gpio_read_proc, NULL);
	return res;
}

module_init(gpio_init);
module_exit(gpio_exit);

