/*
 * arch/arm/mach-pnx4008/gpio.c
 *
 * PNX4008 GPIO driver
 *
 * Author: Dmitry Chigirev <source@mvista.com>
 *
 * Based on reference code by Iwo Mergler and Z.Tabaaloute from Philips:
 * Copyright (c) 2005 Koninklijke Philips Electronics N.V.
 *
 * 2005 (c) MontaVista Software, Inc. This file is licensed under
 * the terms of the GNU General Public License version 2. This program
 * is licensed "as is" without any warranty of any kind, whether express
 * or implied.
 */

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/semaphore.h>
#include <asm/io.h>
#include <asm/arch/platform.h>
#include <asm/arch/gpio.h>

/* register definitions */
#define PIO_VA_BASE	IO_ADDRESS(PNX4008_PIO_BASE)

#define PIO_INP_STATE	(0x00U)
#define PIO_OUTP_SET	(0x04U)
#define PIO_OUTP_CLR	(0x08U)
#define PIO_OUTP_STATE	(0x0CU)
#define PIO_DRV_SET	(0x10U)
#define PIO_DRV_CLR	(0x14U)
#define PIO_DRV_STATE	(0x18U)
#define PIO_SDINP_STATE	(0x1CU)
#define PIO_SDOUTP_SET	(0x20U)
#define PIO_SDOUTP_CLR	(0x24U)
#define PIO_MUX_SET	(0x28U)
#define PIO_MUX_CLR	(0x2CU)
#define PIO_MUX_STATE	(0x30U)

static inline void gpio_lock(void)
{
	local_irq_disable();
}

static inline void gpio_unlock(void)
{
	local_irq_enable();
}

/* Inline functions */
static inline int gpio_read_bit(u32 reg, int gpio)
{
	u32 bit, val;
	int ret = -EFAULT;

	if (gpio < 0)
		goto out;

	bit = GPIO_BIT(gpio);
	if (bit) {
		val = __raw_readl(PIO_VA_BASE + reg);
		ret = (val & bit) ? 1 : 0;
	}
out:
	return ret;
}

static inline int gpio_set_bit(u32 reg, int gpio)
{
	u32 bit, val;
	int ret = -EFAULT;

	if (gpio < 0)
		goto out;

	bit = GPIO_BIT(gpio);
	if (bit) {
		val = __raw_readl(PIO_VA_BASE + reg);
		val |= bit;
		__raw_writel(val, PIO_VA_BASE + reg);
		ret = 0;
	}
out:
	return ret;
}

/* Very simple access control, bitmap for allocated/free */
static unsigned long access_map[4];
#define INP_INDEX	0
#define OUTP_INDEX	1
#define GPIO_INDEX	2
#define MUX_INDEX	3

/*GPIO to Input Mapping */
static short gpio_to_inp_map[32] = {
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, 10, 11, 12, 13, 14, 24, -1
};

/*GPIO to Mux Mapping */
static short gpio_to_mux_map[32] = {
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, 0, 1, 4, 5, -1
};

/*Output to Mux Mapping */
static short outp_to_mux_map[32] = {
	-1, -1, -1, 6, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, 2, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1
};

int pnx4008_gpio_register_pin(unsigned short pin)
{
	unsigned long bit = GPIO_BIT(pin);
	int ret = -EBUSY;	/* Already in use */

	gpio_lock();

	if (GPIO_ISBID(pin)) {
		if (access_map[GPIO_INDEX] & bit)
			goto out;
		access_map[GPIO_INDEX] |= bit;

	} else if (GPIO_ISRAM(pin)) {
		if (access_map[GPIO_INDEX] & bit)
			goto out;
		access_map[GPIO_INDEX] |= bit;

	} else if (GPIO_ISMUX(pin)) {
		if (access_map[MUX_INDEX] & bit)
			goto out;
		access_map[MUX_INDEX] |= bit;

	} else if (GPIO_ISOUT(pin)) {
		if (access_map[OUTP_INDEX] & bit)
			goto out;
		access_map[OUTP_INDEX] |= bit;

	} else if (GPIO_ISIN(pin)) {
		if (access_map[INP_INDEX] & bit)
			goto out;
		access_map[INP_INDEX] |= bit;
	} else
		goto out;
	ret = 0;

out:
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_register_pin);

int pnx4008_gpio_unregister_pin(unsigned short pin)
{
	unsigned long bit = GPIO_BIT(pin);
	int ret = -EFAULT;	/* Not registered */

	gpio_lock();

	if (GPIO_ISBID(pin)) {
		if (~access_map[GPIO_INDEX] & bit)
			goto out;
		access_map[GPIO_INDEX] &= ~bit;
	} else if (GPIO_ISRAM(pin)) {
		if (~access_map[GPIO_INDEX] & bit)
			goto out;
		access_map[GPIO_INDEX] &= ~bit;
	} else if (GPIO_ISMUX(pin)) {
		if (~access_map[MUX_INDEX] & bit)
			goto out;
		access_map[MUX_INDEX] &= ~bit;
	} else if (GPIO_ISOUT(pin)) {
		if (~access_map[OUTP_INDEX] & bit)
			goto out;
		access_map[OUTP_INDEX] &= ~bit;
	} else if (GPIO_ISIN(pin)) {
		if (~access_map[INP_INDEX] & bit)
			goto out;
		access_map[INP_INDEX] &= ~bit;
	} else
		goto out;
	ret = 0;

out:
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_unregister_pin);

unsigned long pnx4008_gpio_read_pin(unsigned short pin)
{
	unsigned long ret = -EFAULT;
	int gpio = GPIO_BIT_MASK(pin);
	gpio_lock();
	if (GPIO_ISOUT(pin)) {
		ret = gpio_read_bit(PIO_OUTP_STATE, gpio);
	} else if (GPIO_ISRAM(pin)) {
		if (gpio_read_bit(PIO_DRV_STATE, gpio) == 0) {
			ret = gpio_read_bit(PIO_SDINP_STATE, gpio);
		}
	} else if (GPIO_ISBID(pin)) {
		ret = gpio_read_bit(PIO_DRV_STATE, gpio);
		if (ret > 0)
			ret = gpio_read_bit(PIO_OUTP_STATE, gpio);
		else if (ret == 0)
			ret =
			    gpio_read_bit(PIO_INP_STATE, gpio_to_inp_map[gpio]);
	} else if (GPIO_ISIN(pin)) {
		ret = gpio_read_bit(PIO_INP_STATE, gpio);
	}
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_read_pin);

/* Write Value to output */
int pnx4008_gpio_write_pin(unsigned short pin, int output)
{
	int gpio = GPIO_BIT_MASK(pin);
	int ret = -EFAULT;

	gpio_lock();
	if (GPIO_ISOUT(pin)) {
		printk( "writing '%x' to '%x'\n",
				gpio, output ? PIO_OUTP_SET : PIO_OUTP_CLR );
		ret = gpio_set_bit(output ? PIO_OUTP_SET : PIO_OUTP_CLR, gpio);
	} else if (GPIO_ISRAM(pin)) {
		if (gpio_read_bit(PIO_DRV_STATE, gpio) > 0)
			ret = gpio_set_bit(output ? PIO_SDOUTP_SET :
					   PIO_SDOUTP_CLR, gpio);
	} else if (GPIO_ISBID(pin)) {
		if (gpio_read_bit(PIO_DRV_STATE, gpio) > 0)
			ret = gpio_set_bit(output ? PIO_OUTP_SET :
					   PIO_OUTP_CLR, gpio);
	}
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_write_pin);

/* Value = 1 : Set GPIO pin as output */
/* Value = 0 : Set GPIO pin as input */
int pnx4008_gpio_set_pin_direction(unsigned short pin, int output)
{
	int gpio = GPIO_BIT_MASK(pin);
	int ret = -EFAULT;

	gpio_lock();
	if (GPIO_ISBID(pin) || GPIO_ISRAM(pin)) {
		ret = gpio_set_bit(output ? PIO_DRV_SET : PIO_DRV_CLR, gpio);
	}
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_set_pin_direction);

/* Read GPIO pin direction: 0= pin used as input, 1= pin used as output*/
int pnx4008_gpio_read_pin_direction(unsigned short pin)
{
	int gpio = GPIO_BIT_MASK(pin);
	int ret = -EFAULT;

	gpio_lock();
	if (GPIO_ISBID(pin) || GPIO_ISRAM(pin)) {
		ret = gpio_read_bit(PIO_DRV_STATE, gpio);
	}
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_read_pin_direction);

/* Value = 1 : Set pin to muxed function  */
/* Value = 0 : Set pin as GPIO */
int pnx4008_gpio_set_pin_mux(unsigned short pin, int output)
{
	int gpio = GPIO_BIT_MASK(pin);
	int ret = -EFAULT;

	gpio_lock();
	if (GPIO_ISBID(pin)) {
		ret =
		    gpio_set_bit(output ? PIO_MUX_SET : PIO_MUX_CLR,
				 gpio_to_mux_map[gpio]);
	} else if (GPIO_ISOUT(pin)) {
		ret =
		    gpio_set_bit(output ? PIO_MUX_SET : PIO_MUX_CLR,
				 outp_to_mux_map[gpio]);
	} else if (GPIO_ISMUX(pin)) {
		ret = gpio_set_bit(output ? PIO_MUX_SET : PIO_MUX_CLR, gpio);
	}
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_set_pin_mux);

/* Read pin mux function: 0= pin used as GPIO, 1= pin used for muxed function*/
int pnx4008_gpio_read_pin_mux(unsigned short pin)
{
	int gpio = GPIO_BIT_MASK(pin);
	int ret = -EFAULT;

	gpio_lock();
	if (GPIO_ISBID(pin)) {
		ret = gpio_read_bit(PIO_MUX_STATE, gpio_to_mux_map[gpio]);
	} else if (GPIO_ISOUT(pin)) {
		ret = gpio_read_bit(PIO_MUX_STATE, outp_to_mux_map[gpio]);
	} else if (GPIO_ISMUX(pin)) {
		ret = gpio_read_bit(PIO_MUX_STATE, gpio);
	}
	gpio_unlock();
	return ret;
}

EXPORT_SYMBOL(pnx4008_gpio_read_pin_mux);