/*
 * Atmel PIO2 Port Multiplexer support
 *
 * Copyright (C) 2004-2006 Atmel Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>

#include <mach/portmux.h>

#include "pio.h"

#define MAX_NR_PIO_DEVICES		8

struct pio_device {
	struct gpio_chip chip;
	void __iomem *regs;
	const struct platform_device *pdev;
	struct clk *clk;
	u32 pinmux_mask;
	char name[8];
};

static struct pio_device pio_dev[MAX_NR_PIO_DEVICES];

static struct pio_device *gpio_to_pio(unsigned int gpio)
{
	struct pio_device *pio;
	unsigned int index;

	index = gpio >> 5;
	if (index >= MAX_NR_PIO_DEVICES)
		return NULL;
	pio = &pio_dev[index];
	if (!pio->regs)
		return NULL;

	return pio;
}

/* Pin multiplexing API */
static DEFINE_SPINLOCK(pio_lock);

void __init at32_select_periph(unsigned int port, u32 pin_mask,
			       unsigned int periph, unsigned long flags)
{
	struct pio_device *pio;

	/* assign and verify pio */
	pio = gpio_to_pio(port);
	if (unlikely(!pio)) {
		printk(KERN_WARNING "pio: invalid port %u\n", port);
		goto fail;
	}

	/* Test if any of the requested pins is already muxed */
	spin_lock(&pio_lock);
	if (unlikely(pio->pinmux_mask & pin_mask)) {
		printk(KERN_WARNING "%s: pin(s) busy (requested 0x%x, busy 0x%x)\n",
		       pio->name, pin_mask, pio->pinmux_mask & pin_mask);
		spin_unlock(&pio_lock);
		goto fail;
	}

	pio->pinmux_mask |= pin_mask;

	/* enable pull ups */
	pio_writel(pio, PUER, pin_mask);

	/* select either peripheral A or B */
	if (periph)
		pio_writel(pio, BSR, pin_mask);
	else
		pio_writel(pio, ASR, pin_mask);

	/* enable peripheral control */
	pio_writel(pio, PDR, pin_mask);

	/* Disable pull ups if not requested. */
	if (!(flags & AT32_GPIOF_PULLUP))
		pio_writel(pio, PUDR, pin_mask);

	spin_unlock(&pio_lock);

	return;

fail:
	dump_stack();
}

void __init at32_select_gpio(unsigned int pin, unsigned long flags)
{
	struct pio_device *pio;
	unsigned int pin_index = pin & 0x1f;
	u32 mask = 1 << pin_index;

	pio = gpio_to_pio(pin);
	if (unlikely(!pio)) {
		printk("pio: invalid pin %u\n", pin);
		goto fail;
	}

	if (unlikely(test_and_set_bit(pin_index, &pio->pinmux_mask))) {
		printk("%s: pin %u is busy\n", pio->name, pin_index);
		goto fail;
	}

	if (flags & AT32_GPIOF_OUTPUT) {
		if (flags & AT32_GPIOF_HIGH)
			pio_writel(pio, SODR, mask);
		else
			pio_writel(pio, CODR, mask);
		if (flags & AT32_GPIOF_MULTIDRV)
			pio_writel(pio, MDER, mask);
		else
			pio_writel(pio, MDDR, mask);
		pio_writel(pio, PUDR, mask);
		pio_writel(pio, OER, mask);
	} else {
		if (flags & AT32_GPIOF_PULLUP)
			pio_writel(pio, PUER, mask);
		else
			pio_writel(pio, PUDR, mask);
		if (flags & AT32_GPIOF_DEGLITCH)
			pio_writel(pio, IFER, mask);
		else
			pio_writel(pio, IFDR, mask);
		pio_writel(pio, ODR, mask);
	}

	pio_writel(pio, PER, mask);

	return;

fail:
	dump_stack();
}

/*
 * Undo a previous pin reservation. Will not affect the hardware
 * configuration.
 */
void at32_deselect_pin(unsigned int pin)
{
	struct pio_device *pio;
	unsigned int pin_index = pin & 0x1f;

	pio = gpio_to_pio(pin);
	if (unlikely(!pio)) {
