/*
 * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/gpio.h>

#include <mach/pinctrl.h>

#include "regs-pinctrl.h"

#define PINCTRL_BASE_ADDR IO_ADDRESS(PINCTRL_PHYS_ADDR)

static int
mx28_gpio_direction(struct mxs_gpio_port *port, int pin, unsigned int input)
{
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	if (input)
		__raw_writel(1 << pin, base + HW_PINCTRL_DOE0_CLR);
	else
		__raw_writel(1 << pin, base + HW_PINCTRL_DOE0_SET);

	return 0;
}

static int mx28_gpio_get(struct mxs_gpio_port *port, int pin)
{
	unsigned int data;
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	data = __raw_readl(base + HW_PINCTRL_DIN0);
	return data & (1 << pin);
}

static void mx28_gpio_set(struct mxs_gpio_port *port, int pin, int data)
{
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	if (data)
		__raw_writel(1 << pin, base + HW_PINCTRL_DOUT0_SET);
	else
		__raw_writel(1 << pin, base + HW_PINCTRL_DOUT0_CLR);
}

static unsigned int mx28_gpio_irq_stat(struct mxs_gpio_port *port)
{
	unsigned int mask;
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	mask = __raw_readl(base + HW_PINCTRL_IRQSTAT0);
	mask &= __raw_readl(base + HW_PINCTRL_IRQEN0);
	return mask;
}

static int
mx28_gpio_set_irq_type(struct mxs_gpio_port *port, int pin, unsigned int type)
{
	unsigned int level, pol;
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		level = 0;
		pol = 1;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		level = 0;
		pol = 0;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		level = 1;
		pol = 1;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		level = 1;
		pol = 0;
		break;
	default:
		pr_debug("%s: Incorrect GPIO interrupt type 0x%x\n",
			 __func__, type);
		return -ENXIO;
	}

	if (level)
		__raw_writel(1 << pin, base + HW_PINCTRL_IRQLEVEL0_SET);
	else
		__raw_writel(1 << pin, base + HW_PINCTRL_IRQLEVEL0_CLR);

	if (pol)
		__raw_writel(1 << pin, base + HW_PINCTRL_IRQPOL0_SET);
	else
		__raw_writel(1 << pin, base + HW_PINCTRL_IRQPOL0_CLR);

	return 0;
}

static void mx28_gpio_unmask_irq(struct mxs_gpio_port *port, int pin)
{
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	__raw_writel(1 << pin, base + HW_PINCTRL_IRQEN0_SET);
}

static void mx28_gpio_mask_irq(struct mxs_gpio_port *port, int pin)
{
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	__raw_writel(1 << pin, base + HW_PINCTRL_IRQEN0_CLR);
}

static void mx28_gpio_ack_irq(struct mxs_gpio_port *port, int pin)
{
	unsigned int mask;
	void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * port->id;
	mask = 1 << pin;
	if (mask)
		__raw_writel(mask, base + HW_PINCTRL_IRQSTAT0_CLR);
}

static struct mxs_gpio_port mx28_gpios[] = {
	{
	 .irq = IRQ_GPIO0,
	 },
	{
	 .irq = IRQ_GPIO1,
	 },
	{
	 .irq = IRQ_GPIO2,
	 },
	{
	 .irq = IRQ_GPIO3,
	 },
	{
	 .irq = IRQ_GPIO4,
	 },
};

static struct mxs_gpio_chip mx28_gpio_chip = {
	.set_dir = mx28_gpio_direction,
	.get = mx28_gpio_get,
	.set = mx28_gpio_set,
	.get_irq_stat = mx28_gpio_irq_stat,
	.set_irq_type = mx28_gpio_set_irq_type,
	.unmask_irq = mx28_gpio_unmask_irq,
	.mask_irq = mx28_gpio_mask_irq,
	.ack_irq = mx28_gpio_ack_irq,
};

int __init mx28_gpio_init(void)
{
	int i;
	unsigned int reg;
	if (__raw_readl(PINCTRL_BASE_ADDR + HW_PINCTRL_CTRL_CLR) &
	    BM_PINCTRL_CTRL_SFTRST) {
		__raw_writel(BM_PINCTRL_CTRL_SFTRST,
			     PINCTRL_BASE_ADDR + HW_PINCTRL_CTRL_CLR);
		for (i = 0; i < 10000; i++) {
			if (!(__raw_readl(PINCTRL_BASE_ADDR + HW_PINCTRL_CTRL) &
			      BM_PINCTRL_CTRL_SFTRST))
				break;
			udelay(2);
		}
		if (i >= 10000)
			return -EFAULT;

		__raw_writel(BM_PINCTRL_CTRL_CLKGATE,
			     PINCTRL_BASE_ADDR + HW_PINCTRL_CTRL_CLR);
	}

	reg = __raw_readl(PINCTRL_BASE_ADDR + HW_PINCTRL_CTRL);
	for (i = 0; i < ARRAY_SIZE(mx28_gpios); i++) {
		void __iomem *base = PINCTRL_BASE_ADDR + 0x10 * i;
		if (!(reg & (BM_PINCTRL_CTRL_PRESENT0 << i)))
			continue;
		mxs_set_gpio_chip(&mx28_gpios[i], &mx28_gpio_chip);
		mx28_gpios[i].id = i;
		__raw_writel(0, base + HW_PINCTRL_IRQEN0);
		__raw_writel(0xFFFFFFFF, base + HW_PINCTRL_PIN2IRQ0);
		mx28_gpios[i].child_irq = MXS_GPIO_IRQ_START +
		    (i * PINS_PER_BANK);
		mxs_add_gpio_port(&mx28_gpios[i]);
	}
	return 0;
}
