/*
 * gpio.c
 *
 * (C) Copyright 2009 Andre McCurdy, NXP Semiconductors
 *
 * 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/hardirq.h>

#include <gpio.h>

#ifndef GPIO_REGS_BASE
#define GPIO_REGS_BASE		0xB7E0F000
#endif

#define REG_PIO_IN		0x000	/* State of the PIO pins */
#define REG_PIO_OUT		0x004	/* Value driven on the PIO pins */
#define REG_PIO_DIR		0x008	/* Direction of the PIO port */
#define REG_PIO_SEL		0x014	/* IO or alternate function */
#define REG_PIO_INT_EDGE	0x020	/* Enable edge interrupts */
#define REG_PIO_INT_HI		0x024	/* Enable high level interrupts */
#define REG_PIO_INT_LO		0x028	/* Enable low level interrupts */
#define REG_PIO_DEB		0x044	/* Debounce control */
#define REG_PIO_IN2		0x05C	/* State of the PIO pins */
#define REG_PIO_OUT2		0x060	/* Value driven on the PIO pins */
#define REG_PIO_DIR2		0x064	/* Direction of the PIO port */
#define REG_PIO_SEL2		0x068	/* IO or alternate function */
#define REG_PIO_INT_STATUS	0xFE0	/* GPIO interrupt status (raw) */
#define REG_PIO_INT_ENABLE	0xFE4	/* GPIO interrupt enable */
#define REG_PIO_INT_CLEAR	0xFE8	/* GPIO interrupt clear */
#define REG_PIO_INT_SET		0xFEC	/* GPIO interrupt set */
#define REG_PIO_RESET		0xFF4	/* GPIO reset */
#define REG_PIO_MODULE_ID	0xFFC	/* GPIO module ID */


static DEFINE_SPINLOCK(gpio_regs_lock);

static unsigned int gpio_reg_read (unsigned int reg_offset)
{
	return *((volatile unsigned int *) (GPIO_REGS_BASE + reg_offset));
}

static void gpio_reg_write (unsigned int reg_offset, unsigned int value)
{
	*((volatile unsigned int *) (GPIO_REGS_BASE + reg_offset)) = value;
}

static void gpio_reg_bitset (unsigned int reg_offset, unsigned int bit_offset)
{
	unsigned long flags;

	spin_lock_irqsave (&gpio_regs_lock, flags);
	gpio_reg_write (reg_offset, gpio_reg_read (reg_offset) | (1 << bit_offset));
	spin_unlock_irqrestore (&gpio_regs_lock, flags);
}

static void gpio_reg_bitclear (unsigned int reg_offset, unsigned int bit_offset)
{
	unsigned long flags;

	spin_lock_irqsave (&gpio_regs_lock, flags);
	gpio_reg_write (reg_offset, gpio_reg_read (reg_offset) & ~(1 << bit_offset));
	spin_unlock_irqrestore (&gpio_regs_lock, flags);
}

/*
   Select GPIO direction for a pin
*/
void gpio_select_input (unsigned int pin)
{
	unsigned int reg = REG_PIO_DIR;

	if (pin >= 32) {
		pin -= 32;
		reg = REG_PIO_DIR2;
	}

	gpio_reg_bitclear (reg, pin);
}

void gpio_select_output (unsigned int pin)
{
	unsigned int reg = REG_PIO_DIR;

	if (pin >= 32) {
		pin -= 32;
		reg = REG_PIO_DIR2;
	}

	gpio_reg_bitset (reg, pin);
}

/*
   Select GPIO or alternate function for a pin
*/
void gpio_select_function_io (unsigned int pin)
{
	unsigned int reg = REG_PIO_SEL;

	if (pin >= 32) {
		pin -= 32;
		reg = REG_PIO_SEL2;
	}

	gpio_reg_bitclear (reg, pin);
}

void gpio_select_function_alt (unsigned int pin)
{
	unsigned int reg = REG_PIO_SEL;

	if (pin >= 32) {
		pin -= 32;
		reg = REG_PIO_SEL2;
	}

	gpio_reg_bitset (reg, pin);
}

/*
   Read current state of a GPIO pin
*/
int gpio_read (unsigned int pin)
{
	unsigned int reg = REG_PIO_IN;

	if (pin >= 32) {
		pin -= 32;
		reg = REG_PIO_IN2;
	}

	return (gpio_reg_read (reg) >> pin) & 0x01;
}

/*
   Write value to a GPIO pin
*/
void gpio_write (unsigned int val, unsigned int pin)
{
	unsigned int reg = REG_PIO_OUT;

	if (pin >= 32) {
		pin -= 32;
		reg = REG_PIO_OUT2;
	}

	if (val)
		gpio_reg_bitset (reg, pin);
	else
		gpio_reg_bitclear (reg, pin);
}

/*
   Set interrupt related regs set to a known state (IO + function select
   regs not touched; they should already have been setup by bootloader).
   This shouldn't get called on MIPS2, but if it is, do nothing to ensure
   we don't overwite any IRQ setup which has already been performed by MIPS1
*/
void gpio_irq_init (void)
{
#if ! defined (CONFIG_PNX8335_AUDIO_MIPS)

	gpio_reg_write (REG_PIO_INT_EDGE, 0);
	gpio_reg_write (REG_PIO_INT_HI, 0);
	gpio_reg_write (REG_PIO_INT_LO, 0);

	/*
	   Clear any spurious pending GPIO interrupt requests
	*/
	gpio_reg_write (REG_PIO_INT_CLEAR, 0xFFFF);
	gpio_reg_write (REG_PIO_INT_CLEAR, 0x0000);
	gpio_reg_write (REG_PIO_INT_ENABLE, 0);

#endif
}

#if defined (CONFIG_PNX833X_GPIO_INTERRUPTS)

/*
   Configure GPIO interrupt
*/
void gpio_setup_irq (int when, unsigned int pin)
{
	switch (when)
	{
		case GPIO_INT_LEVEL_LOW:
			gpio_reg_bitclear (REG_PIO_INT_EDGE, pin);
			gpio_reg_bitclear (REG_PIO_INT_HI, pin);
			gpio_reg_bitset (REG_PIO_INT_LO, pin);
			break;

		case GPIO_INT_LEVEL_HIGH:
			gpio_reg_bitclear (REG_PIO_INT_EDGE, pin);
			gpio_reg_bitset (REG_PIO_INT_HI, pin);
			gpio_reg_bitclear (REG_PIO_INT_LO, pin);
			break;

		case GPIO_INT_EDGE_RISING:
			gpio_reg_bitset (REG_PIO_INT_EDGE, pin);
			gpio_reg_bitset (REG_PIO_INT_HI, pin);
			gpio_reg_bitclear (REG_PIO_INT_LO, pin);
			break;

		case GPIO_INT_EDGE_FALLING:
			gpio_reg_bitset (REG_PIO_INT_EDGE, pin);
			gpio_reg_bitclear (REG_PIO_INT_HI, pin);
			gpio_reg_bitset (REG_PIO_INT_LO, pin);
			break;

		case GPIO_INT_EDGE_BOTH:
			gpio_reg_bitset (REG_PIO_INT_EDGE, pin);
			gpio_reg_bitset (REG_PIO_INT_HI, pin);
			gpio_reg_bitset (REG_PIO_INT_LO, pin);
			break;

		default:
			gpio_reg_bitclear (REG_PIO_INT_EDGE, pin);
			gpio_reg_bitclear (REG_PIO_INT_HI, pin);
			gpio_reg_bitclear (REG_PIO_INT_LO, pin);
			break;
	}
}

/*
   Enable/disable GPIO interrupt
*/
void gpio_enable_irq (unsigned int pin)
{
	gpio_reg_bitset (REG_PIO_INT_ENABLE, pin);
}

void gpio_disable_irq (unsigned int pin)
{
	gpio_reg_bitclear (REG_PIO_INT_ENABLE, pin);
}

/*
   Clear GPIO interrupt request
*/
void gpio_clear_irq (unsigned int pin)
{
	unsigned long flags;
	unsigned int temp;

	spin_lock_irqsave (&gpio_regs_lock, flags);
	temp = gpio_reg_read (REG_PIO_INT_CLEAR);
	gpio_reg_write (REG_PIO_INT_CLEAR, (temp | (1 << pin)));
	gpio_reg_write (REG_PIO_INT_CLEAR, (temp & ~(1 << pin)));
	spin_unlock_irqrestore (&gpio_regs_lock, flags);
}

/*
   Return bitfield of active irqs
*/
unsigned int gpio_irq_status_active (void)
{
	return gpio_reg_read (REG_PIO_INT_STATUS) & 0xFFFF;
}

/*
   Return bitfield of active _and_ unmasked irqs
*/
unsigned int gpio_irq_status_active_and_unmasked (void)
{
	unsigned long flags;
	unsigned int status, enable;

	spin_lock_irqsave (&gpio_regs_lock, flags);
	status = gpio_reg_read (REG_PIO_INT_STATUS);
	enable = gpio_reg_read (REG_PIO_INT_ENABLE);
	spin_unlock_irqrestore (&gpio_regs_lock, flags);

	return (status & enable) & 0xFFFF;
}

#endif

/*
   Export other functions as required...
*/
EXPORT_SYMBOL(gpio_select_input);
EXPORT_SYMBOL(gpio_select_output);
EXPORT_SYMBOL(gpio_read);
EXPORT_SYMBOL(gpio_write);
EXPORT_SYMBOL(gpio_setup_irq);
