/* linux/arch/arm/mach-wmt/gpio.c
 *
 * Copyright (c) 2013 WonderMedia Technologies, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 */
#include <linux/module.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/seq_file.h>
#include <linux/kobject.h>
#include <linux/debugfs.h>

#include <mach/hardware.h>
#include <mach/wmt_iomux.h>

#undef	WMT_PIN
#define WMT_PIN(__gp, __bit, __irq, __name)	\
	{ .label = #__name, .regoff = __gp, .shift = __bit, .irqnum = __irq },

const static struct wmt_gpio {
	const char *label;
	unsigned char regoff;
	unsigned char shift;
	int irqnum;
} wmt_gpios[] = {
	#include <mach/iomux.h>
};

#define to_wmt(__chip)	container_of(__chip, struct wmt_gpio_port, chip)

struct wmt_gpio_port {
	uint32_t base;
	uint32_t gpio_irq_no_base;
	int gpio_irq_nr;
	struct gpio_chip chip;
	spinlock_t lock;
};

static struct wmt_gpio_port wmt_gpio_port;

#define INVALUE_REGS		0x00
#define ENABLE_REGS		0x40
#define DIRECTION_REGS		0x80
#define OUTVALUE_REGS		0xc0

#define INTMASK_REGS		0x300
#define INTSTAT_REGS		0x360

#define PULLENABLE_REGS		0x480
#define PULLCONTROL_REGS	0x4c0

#define GPIO_INT_LOW_LEV	0x0
#define GPIO_INT_HIGH_LEV	0x1
#define GPIO_INT_FALL_EDGE	0x2
#define GPIO_INT_RISE_EDGE	0x3
#define GPIO_INT_BOTH_EDGE	0x4

#define CHIP_GPIO_BASE	0

static void _clear_gpio_irqstatus(u32 irqindex)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	u32 addr = port->base + INTSTAT_REGS + (irqindex >> 3);
	u8 index = irqindex & 0x7;

	__raw_writeb(1 << index, addr);
}

static int _gpio_irqstatus(u32 irqindex)
{
	u8 l;
	struct wmt_gpio_port *port = &wmt_gpio_port;
	u32 addr = port->base + INTSTAT_REGS + (irqindex >> 3);
	u8 index = irqindex & 0x7;
	l = __raw_readb(addr);
	return l & (1<<index);
}

int gpio_irqstatus(unsigned int gpio)
{
	int offset = gpio - CHIP_GPIO_BASE;
	int irqindex = wmt_gpios[offset].irqnum;	
	return _gpio_irqstatus(irqindex);
}
EXPORT_SYMBOL(gpio_irqstatus);

static void _set_gpio_irqenable(u32 irqindex, int enable)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	u32 addr = port->base + INTMASK_REGS + irqindex;
	u8 l;

	l = __raw_readb(addr);
	l = (l & 0x7f) | (!!enable << 7);
	__raw_writeb(l, addr);
}

int is_gpio_irqenable(unsigned int gpio)
{
	u8 l;
	int offset = gpio - CHIP_GPIO_BASE;
	int irqindex = wmt_gpios[offset].irqnum;
	struct wmt_gpio_port *port = &wmt_gpio_port;
	u32 addr = port->base + INTMASK_REGS + irqindex;
	
	l = __raw_readb(addr);
	return (l & 0x80);
}
EXPORT_SYMBOL(is_gpio_irqenable);

void wmt_gpio_ack_irq(unsigned int gpio)
{
	int offset = gpio - CHIP_GPIO_BASE;
	int irqindex = wmt_gpios[offset].irqnum;

	_clear_gpio_irqstatus(irqindex);
}
EXPORT_SYMBOL(wmt_gpio_ack_irq);

void wmt_gpio_mask_irq(unsigned int gpio)
{
	int offset = gpio - CHIP_GPIO_BASE;
	int irqindex = wmt_gpios[offset].irqnum;

	_set_gpio_irqenable(irqindex, 0);
}
EXPORT_SYMBOL(wmt_gpio_mask_irq);

void wmt_gpio_unmask_irq(unsigned int gpio)
{
	int offset = gpio - CHIP_GPIO_BASE;
	int irqindex = wmt_gpios[offset].irqnum;

	_set_gpio_irqenable(irqindex, 1);
}
EXPORT_SYMBOL(wmt_gpio_unmask_irq);

int wmt_gpio_set_irq_type(unsigned int gpio, u32 type)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	int offset = gpio - CHIP_GPIO_BASE;
	int irqindex = wmt_gpios[offset].irqnum;
	int edge;
	u32 reg, val;

	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		edge = GPIO_INT_RISE_EDGE;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		edge = GPIO_INT_FALL_EDGE;
		break;
	case IRQ_TYPE_EDGE_BOTH:
		edge = GPIO_INT_BOTH_EDGE;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		edge = GPIO_INT_LOW_LEV;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		edge = GPIO_INT_HIGH_LEV;
		break;
	default:
		return -EINVAL;
	}

	reg  = port->base + INTMASK_REGS + irqindex;
	val = __raw_readb(reg) & 0xf8;
	__raw_writeb(val | edge, reg);

	_clear_gpio_irqstatus(irqindex);

	return 0;
}
EXPORT_SYMBOL(wmt_gpio_set_irq_type);

#ifdef WMT_GPIO_IRQ
static void gpio_ack_irq(struct irq_data *d)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	int irqindex = d->irq - port->gpio_irq_no_base;

	_clear_gpio_irqstatus(irqindex);
}

static void gpio_mask_irq(struct irq_data *d)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	int irqindex = d->irq - port->gpio_irq_no_base;

	_set_gpio_irqenable(irqindex, 0);
}

static void gpio_unmask_irq(struct irq_data *d)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	int irqindex = d->irq - port->gpio_irq_no_base;

	_set_gpio_irqenable(irqindex, 1);
}

static int gpio_set_irq_type(struct irq_data *d, u32 type)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	int edge, irqindex;
	u32 reg, val;

	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		edge = GPIO_INT_RISE_EDGE;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		edge = GPIO_INT_FALL_EDGE;
		break;
	case IRQ_TYPE_EDGE_BOTH:
		edge = GPIO_INT_BOTH_EDGE;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		edge = GPIO_INT_LOW_LEV;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		edge = GPIO_INT_HIGH_LEV;
		break;
	default:
		return -EINVAL;
	}

	irqindex = d->irq - port->gpio_irq_no_base;
	reg  = port->base + INTMASK_REGS + irqindex;
	val = __raw_readb(reg) & 0xf8;
	__raw_writeb(val | edge, reg);

	_clear_gpio_irqstatus(irqindex);

	return 0;
}

/* WMT has one interrupt *for all* gpio ports */
static void wmt_gpio_irq_handler(u32 irq, struct irq_desc *desc)
{
	int i;
	u32 irq_msk, irq_stat;
	struct wmt_gpio_port *port = &wmt_gpio_port;

	/* walk through all interrupt status registers */
	for (i = 0; i < 4; i++) {
		irq_msk = __raw_readl(port->base + INTMASK_REGS + i*4);
		if (!irq_msk)
			continue;

		irq_stat = __raw_readl(port->base + INTSTAT_REGS + i*4) & irq_msk;
		while (irq_stat != 0) {
			int irqoffset = fls(irq_stat) - 1;

			generic_handle_irq(port->gpio_irq_no_base + i*32 + irqoffset);

			irq_stat &= ~(1 << irqoffset);
		}
	}
}

static struct irq_chip gpio_irq_chip = {
	.name = "GPIO",
	.irq_ack = gpio_ack_irq,
	.irq_mask = gpio_mask_irq,
	.irq_unmask = gpio_unmask_irq,
	.irq_set_type = gpio_set_irq_type,
};
#endif

static int wmt_gpio_request(struct gpio_chip *chip, unsigned offset)
{
	struct wmt_gpio_port *port = to_wmt(chip);
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;

	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + ENABLE_REGS + regoff);
	val |= (1 << shift);
	writeb(val, base + ENABLE_REGS + regoff);

	spin_unlock_irqrestore(&port->lock, flags);
	return 0;
}

static void wmt_gpio_free(struct gpio_chip *chip, unsigned offset)
{
	struct wmt_gpio_port *port = to_wmt(chip);
//	uint32_t base = port->base;
//	uint8_t regoff = wmt_gpios[offset].regoff;
//	uint8_t shift  = wmt_gpios[offset].shift;
//	uint8_t val;

	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

//	val = readb(base + ENABLE_REGS + regoff);
//	val &= ~(1 << shift);
//	writeb(val, base + ENABLE_REGS + regoff);

	spin_unlock_irqrestore(&port->lock, flags);
}

static void _set_gpio_direction(struct gpio_chip *chip, unsigned offset, int dir)
{
	struct wmt_gpio_port *port = to_wmt(chip);
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;
	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + DIRECTION_REGS + regoff);
	if (dir)
		val |= (1 << shift);
	else
		val &= ~(1 << shift);
	writeb(val, base + DIRECTION_REGS + regoff);

	spin_unlock_irqrestore(&port->lock, flags);
}

static int wmt_gpio_get_value(struct gpio_chip *chip, unsigned offset)
{
	struct wmt_gpio_port *port = to_wmt(chip);
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;
	int dir;

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + DIRECTION_REGS + regoff);
	dir = (val >> shift) & 1;

	if (dir)
		return (readb(base + OUTVALUE_REGS + regoff) >> shift) & 1;
	else
		return (readb(base + INVALUE_REGS + regoff) >> shift) & 1;
}

static void wmt_gpio_set_value(struct gpio_chip *chip,
			       unsigned offset, int value)
{
	struct wmt_gpio_port *port = to_wmt(chip);
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;
	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + OUTVALUE_REGS + regoff);

	if (value)
		val |= (1 << shift);
	else
		val &= ~(1 << shift);

	writeb(val, base + OUTVALUE_REGS + regoff);

	spin_unlock_irqrestore(&port->lock, flags);
}

static int wmt_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
	_set_gpio_direction(chip, offset, 0);
	return 0;
}

static int wmt_gpio_direction_output(struct gpio_chip *chip,
				     unsigned offset, int value)
{
	wmt_gpio_set_value(chip, offset, value);
	_set_gpio_direction(chip, offset, 1);
	return 0;
}

static void _set_gpio_pullenable(unsigned offset, int enable)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;
	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + PULLENABLE_REGS + regoff);
	if (enable)
		val |= (1 << shift);
	else
		val &= ~(1 << shift);
	writeb(val, base + PULLENABLE_REGS + regoff);

	spin_unlock_irqrestore(&port->lock, flags);
}

static void _set_gpio_pullup(unsigned offset, int up)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;
	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + PULLCONTROL_REGS + regoff);
	if (up)
		val |= (1 << shift);
	else
		val &= ~(1 << shift);
	writeb(val, base + PULLCONTROL_REGS + regoff);

	spin_unlock_irqrestore(&port->lock, flags);
}

int wmt_gpio_setpull(unsigned int gpio, enum wmt_gpio_pulltype pull)
{
	int offset = gpio - CHIP_GPIO_BASE;

	switch (pull) {
	case WMT_GPIO_PULL_NONE:
		_set_gpio_pullenable(offset, 0);
		break;
	case WMT_GPIO_PULL_UP:
		_set_gpio_pullenable(offset, 1);
		_set_gpio_pullup(offset, 1);
		break;
	case WMT_GPIO_PULL_DOWN:
		_set_gpio_pullenable(offset, 1);
		_set_gpio_pullup(offset, 0);
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int _get_gpio_pull(unsigned offset)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;
	uint32_t base = port->base;
	uint8_t regoff = wmt_gpios[offset].regoff;
	uint8_t shift  = wmt_gpios[offset].shift;
	uint8_t val;
	int dir;

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	val = readb(base + DIRECTION_REGS + regoff);
	dir = (val >> shift) & 1;

	if (dir)
		return (readb(base + OUTVALUE_REGS + regoff) >> shift) & 1;
	else
		return (readb(base + INVALUE_REGS + regoff) >> shift) & 1;
}


int wmt_gpio_getpull(unsigned int gpio)
{
	int offset = gpio - CHIP_GPIO_BASE;

	return _get_gpio_pull(offset);
}

EXPORT_SYMBOL(wmt_gpio_setpull);
EXPORT_SYMBOL(wmt_gpio_getpull);

#ifdef WMT_GPIO_IRQ
static int wmt_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
	struct wmt_gpio_port *port = &wmt_gpio_port;

	WARN_ON(offset >= ARRAY_SIZE(wmt_gpios));

	if (wmt_gpios[offset].irqnum < 0)
		return -EINVAL;

	return port->gpio_irq_no_base + wmt_gpios[offset].irqnum;
}
#endif

static struct wmt_gpio_port wmt_gpio_port = {
	.base = GPIO_BASE_ADDR,
#ifdef WMT_GPIO_IRQ
	.gpio_irq_no_base = WMT_GPIO_IRQ_START,
	.gpio_irq_nr = WMT_GPIO_IRQS,
#endif
	.chip = {
		.label			= "wmt-gpio",
		.direction_input	= wmt_gpio_direction_input,
		.direction_output	= wmt_gpio_direction_output,
		.request		= wmt_gpio_request,
		.free			= wmt_gpio_free,
		.get			= wmt_gpio_get_value,
		.set			= wmt_gpio_set_value,
#ifdef WMT_GPIO_IRQ
		.to_irq			= wmt_gpio_to_irq,
#endif
		.can_sleep		= 0,
		.base			= CHIP_GPIO_BASE,
		.ngpio			= ARRAY_SIZE(wmt_gpios),
	},
};

void __init wmt_gpio_init(void)
{
#ifdef WMT_GPIO_IRQ
	int irq;
#endif

	spin_lock_init(&wmt_gpio_port.lock);
	BUG_ON(gpiochip_add(&wmt_gpio_port.chip) < 0);

	/* XXX conflict with touchscreen irq */
#ifdef WMT_GPIO_IRQ
	for (irq = wmt_gpio_port.gpio_irq_no_base;
	     irq < wmt_gpio_port.gpio_irq_no_base + wmt_gpio_port.gpio_irq_nr;
	     irq++) {
		irq_set_chip_and_handler(irq, &gpio_irq_chip, handle_level_irq);
		set_irq_flags(irq, IRQF_VALID);
	}

	irq_set_chained_handler(IRQ_GPIO, wmt_gpio_irq_handler);
#endif
}

const char *wmt_gpio_name(int gpio)
{
	if (gpio >= ARRAY_SIZE(wmt_gpios)) {
		pr_err("wmt: can not find gpio-%d\n", gpio);
		return NULL;
	}

	return wmt_gpios[gpio].label;
}

#ifdef CONFIG_DEBUG_FS

static int wmt_gpio_show(struct seq_file *s, void *unused)
{
	unsigned i;

	seq_printf(s, "GPIONUM  , REGOFF , SHIFT , MACRO NAME\n");
	seq_printf(s, "---------+--------+-------+-----------\n");

	for (i = 0; i < wmt_gpio_port.chip.ngpio; i++) {
		seq_printf(s, "gpio-%-3d ,  0x%02x  ,    %d  , %s",
			   i, wmt_gpios[i].regoff, wmt_gpios[i].shift, wmt_gpios[i].label);
		seq_printf(s, "\n");
	}
	return 0;
}

static int wmt_gpio_open(struct inode *inode, struct file *file)
{
	return single_open(file, wmt_gpio_show, NULL);
}

static const struct file_operations wmt_gpio_operations = {
	.open		= wmt_gpio_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
};

static int __init wmt_gpio_debugfs_init(void)
{
	/* /sys/kernel/debug/wmt-gpio */
	(void) debugfs_create_file("wmt-gpio", S_IFREG | S_IRUGO,
				   NULL, NULL, &wmt_gpio_operations);
	return 0;
}
subsys_initcall(wmt_gpio_debugfs_init);

#endif	/* DEBUG_FS */