/*
 * (C) Copyright 2006
 * Andre Renaud, Bluewater Systems Ltd
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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 <common.h>
#include <command.h>

#if defined (CONFIG_PXA250) || defined (CONFIG_PXA27X)
#include <asm/arch/pxa-regs.h>
#ifdef CONFIG_PXA250
#define MAX_GPIO 95
#else
#define MAX_GPIO 120
#endif

static void pxa_gpio_mode(int gpio, int mode)
{
	unsigned int gafr;

	gafr = GAFR(gpio) & ~(0x3 << (gpio & 0xf) * 2);
	GAFR(gpio) = gafr | (mode << (gpio & 0xf) * 2);
}

static inline void drive_gpio (int gpio, int dir) {
	if (dir)
		GPDR(gpio) |= GPIO_bit (gpio);
	else
		GPDR(gpio) &= ~GPIO_bit (gpio);
}

static inline int get_gpio_dir (int gpio) {
	return GPDR (gpio) & GPIO_bit (gpio);
}

static inline int read_gpio (int gpio) {
    return (GPLR(gpio) & GPIO_bit (gpio)) ? 1 : 0;
}

static inline void set_gpio (int gpio, int value) {
	if (value)
		GPSR (gpio) = GPIO_bit (gpio);
	else
		GPCR (gpio) = GPIO_bit (gpio);
}

static void describe_gpio (int gpio, char *buffer) {
    int shift = ((gpio & 0xf) * 2);
    int m = (GAFR (gpio) & (0x3 << shift)) >> shift;
    if (m == 0)
	sprintf (buffer, "GPIO");
    else
	sprintf (buffer, "AF%d", m);
}
#elif defined CONFIG_EP93XX
#include <ep93xx.h>
#define MAX_GPIO 63

static unsigned int gpio_dr [8]  = {0x00, 0x04, 0x08, 0x0c, 0x20, 0x30, 0x38, 0x40};
static unsigned int gpio_ddr [8] = {0x10, 0x14, 0x18, 0x1c, 0x24, 0x34, 0x3c, 0x44};
#define GPIO_DR(gpio) *(volatile unsigned int *)(0x80840000 + gpio_dr[gpio / 8])
#define GPIO_DDR(gpio) *(volatile unsigned int *)(0x80840000 + gpio_ddr[gpio / 8])
#define GPIO_bit(gpio) (1 << (gpio % 8))
static inline void drive_gpio (int gpio, int dir) {
	if (dir)
		GPIO_DDR(gpio) |= GPIO_bit (gpio);
	else
		GPIO_DDR(gpio) &= ~GPIO_bit (gpio);
}

static inline int get_gpio_dir (int gpio) {
	return (GPIO_DDR (gpio) & GPIO_bit (gpio)) ? 1 : 0;
}

static inline int read_gpio (int gpio) {
	return (GPIO_DR(gpio) & GPIO_bit (gpio)) ? 1 : 0;
}

static inline void set_gpio (int gpio, int value) {
	if (value)
		GPIO_DR(gpio) |= GPIO_bit (gpio);
	else
		GPIO_DR(gpio) &= ~GPIO_bit (gpio);
}

static void describe_gpio (int gpio, char *buffer) {
	unsigned int devcfg = SysconDEVCFG;
	int bank = gpio / 8;
	int bit = gpio % 8;
	buffer += sprintf (buffer, "%c%d ", 'A' + bank, bit);
	buffer += sprintf (buffer, "devcfg=0x%8.8x ", devcfg);

	switch (bank)
	{
		case 0: /* A, B & F are always gpio */
		case 1:
		case 5:
			buffer += sprintf (buffer, "GPIO ");
			break;

		case 2: /* C can be Row for keypad */
		case 3: /* D can be Col for keypad */
			if (devcfg & (1 << 27)) /* GonK */
				buffer += sprintf (buffer, "GPIO ");
			else
				buffer += sprintf (buffer, "%s%d ", bank == 2 ? "row" : "col", bit);
			break;
		case 4: /* E can be IDE */
			if (bit == 0)
				buffer += sprintf (buffer, "GRLED ");
			else if (bit == 1)
				buffer += sprintf (buffer, "RDLED ");
			else if (devcfg & (1 << 8)) /* EonIDE */
				buffer += sprintf (buffer, "GPIO ");
			else
				switch (bit)
				{
					case 7:
					case 6:
					case 5:
						buffer += sprintf (buffer, "IDEDA%d ", bit - 5);
						break;
					case 4:
						buffer += sprintf (buffer, "IDECS1n ");
						break;
					case 3:
						buffer += sprintf (buffer, "IDECS0n ");
						break;
					case 2:
						buffer += sprintf (buffer, "DIORn ");
						break;
				}
			break;
		case 6: /* G Can Be IDE */
			if (bit == 0)
				buffer += sprintf (buffer, "EECLK ");
			else if (bit == 1)
				buffer += sprintf (buffer, "EEDAT ");
			else if (bit == 2 || bit == 3)
				buffer += sprintf (buffer, "SLA%d ", bit - 2);
			else if (devcfg & (1 << 10)) /* GonIDE */
				buffer += sprintf (buffer, "GPIO ");
			else
				buffer += sprintf (buffer, "DD%d ", bit + 8);
			break;

		case 7: /* H can be IDE */
			if (devcfg & (1 << 11)) /* HonIDE */
				buffer += sprintf (buffer, "GPIO ");
			else
				buffer += sprintf (buffer, "DD%d ", bit);
			break;
	}
}
#elif defined CONFIG_AT91
#include <asm/arch/hardware.h>
#include <asm/arch/pio.h>
#define MAX_GPIO 95
#define PIO_BASE(pio) (pio < 32 ? AT91C_BASE_PIOA : (pio < 64 ? AT91C_BASE_PIOB : AT91C_BASE_PIOC))
#define PIO_OFFSET(pio) ((pio) % 32)

static inline void drive_gpio (int gpio, int dir) {
	if (dir)
		AT91F_PIO_CfgOutput(PIO_BASE(gpio), (1 << PIO_OFFSET(gpio)));
	else
		AT91F_PIO_CfgInput(PIO_BASE(gpio), (1 << PIO_OFFSET(gpio)));
}

static inline int get_gpio_dir (int gpio) {
	return AT91F_PIO_GetOutputStatus(PIO_BASE(gpio)) & (1 << PIO_OFFSET(gpio));
}

static inline int read_gpio (int gpio) {
	return (AT91F_PIO_GetInput(PIO_BASE(gpio)) & (1 << PIO_OFFSET(gpio))) ? 1 : 0;
}

static inline void set_gpio (int gpio, int value) {
	if (value)
		AT91F_PIO_SetOutput(PIO_BASE(gpio), (1 << PIO_OFFSET(gpio)));
	else
		AT91F_PIO_ClearOutput(PIO_BASE(gpio), (1 << PIO_OFFSET(gpio)));
}

static void describe_gpio (int gpio, char *buffer) {
        int written = 0;
	AT91PS_PIO pPio = PIO_BASE(gpio);
        written += sprintf (&buffer[written], "%c%d ", gpio < 32 ? 'A' : gpio < 64 ? 'B' : 'C', PIO_OFFSET(gpio));
    if (pPio->PIO_PSR & (1 << PIO_OFFSET(gpio)))
    	written += sprintf (&buffer[written], "GPIO");
    else {
    	written += sprintf (&buffer[written], "PER%c", pPio->PIO_ABSR & (1 << PIO_OFFSET(gpio)) ? 'B' : 'A');
    }
}

#elif defined CONFIG_MPC8555
#include <asm/io.h>
#include <asm/immap_85xx.h>
#define	NUM_OF_PORTS		4
#define	NUM_OF_PINS		32
#define MAX_GPIO		((NUM_OF_PORTS * NUM_OF_PINS) - 1)
#define MPC85xx_E500_BASE_ADDR	0xff790000
#define MPC85xx_GPIO_OFFSET	0xD00
typedef struct par_io_8555 {
	u32	cpdir;
	u32	cppar;
	u32	cpsor;
	u32	cpodr;
	u32	cpdat;
	u32	res[3];
} par_io_t_8555;

static void get_io_config(unsigned char port, unsigned char pin, int *dir, int *open_drain, int *assign, int *val)
{
	volatile par_io_t_8555 *par_io = (volatile par_io_t_8555 *)((unsigned long)MPC85xx_E500_BASE_ADDR + MPC85xx_GPIO_OFFSET);
	unsigned long	pin_bit_mask = (unsigned long)(1 << (NUM_OF_PINS - (pin+1)));

	/* Get the direction */
	if (in_be32(&par_io[port].cpdir) & pin_bit_mask)
		*dir = 1;
	else 
		*dir = 0;

	/* Get the open drain settings */
	if (in_be32(&par_io[port].cpodr) & pin_bit_mask)
		*open_drain = 1;
	else 
		*open_drain = 0;

	/* Get the assignment */
	if (in_be32(&par_io[port].cppar) & pin_bit_mask)
		if (in_be32(&par_io[port].cpsor) & pin_bit_mask)
			*assign = 2;
		else
			*assign = 1;
	else 
		*assign = 0;

	/* Get the value */
	if (in_be32(&par_io[port].cpdat) & pin_bit_mask)
		*val = 1;
	else 
		*val = 0;
}

static void set_io_config(unsigned char port, unsigned char pin, int dir, int open_drain, int assign, int val)
{
	volatile par_io_t_8555 *par_io = (volatile par_io_t_8555 *)((unsigned long)MPC85xx_E500_BASE_ADDR + MPC85xx_GPIO_OFFSET);
	unsigned long			pin_bit_mask = (unsigned long)(1 << (NUM_OF_PINS - (pin+1)));
	unsigned long			tmp_val;

	/* Setup the direction */
	tmp_val = in_be32(&par_io[port].cpdir);
	tmp_val &= ~pin_bit_mask;
	tmp_val |= (unsigned long)(dir << (NUM_OF_PINS - (pin+1)));
	out_be32(&par_io[port].cpdir, tmp_val);

	/* Setup the open drain */
	tmp_val = in_be32(&par_io[port].cpodr);
	tmp_val &= ~pin_bit_mask;
	tmp_val |= (unsigned long)(open_drain << (NUM_OF_PINS - (pin+1)));
	out_be32(&par_io[port].cpodr, tmp_val);

	/* Setup the assignment */
	tmp_val = in_be32(&par_io[port].cppar);
	if (assign != 0) {
		tmp_val |= pin_bit_mask;
		out_be32(&par_io[port].cppar, tmp_val);
		tmp_val = in_be32(&par_io[port].cpsor);
		if (assign == 1)
			tmp_val &= ~pin_bit_mask;
		else
			tmp_val |= pin_bit_mask;
		out_be32(&par_io[port].cpsor, tmp_val);
	} else {
		tmp_val &= ~pin_bit_mask;
		out_be32(&par_io[port].cppar, tmp_val);
	}

	/* Set the value */
	tmp_val = in_be32(&par_io[port].cpdat);
	tmp_val &= ~pin_bit_mask;
	tmp_val |= (unsigned long)(val << (NUM_OF_PINS - (pin+1)));
	out_be32(&par_io[port].cpdat, tmp_val);
}

#elif defined CONFIG_MPC85xx
#include <asm/io.h>
#include <asm/immap_85xx.h>
#define	NUM_OF_PORTS	6
#define	NUM_OF_PINS	32
#define MAX_GPIO	((NUM_OF_PORTS * NUM_OF_PINS) - 1)
static void get_io_config(u8 port, u8 pin, int *dir, int *open_drain, int *assign, int *val)
{
	u32			pin_2bit_mask;
	u32			pin_1bit_mask;
	volatile ccsr_gur_t	*gur = (void *)(CONFIG_SYS_MPC85xx_GUTS_ADDR);
	volatile par_io_t	*par_io = (volatile par_io_t *)
						&(gur->qe_par_io);

	/* Caculate pin location and masks */
	pin_2bit_mask = (u32)(3 << (NUM_OF_PINS-(pin%(NUM_OF_PINS/2)+1)*2));
	pin_1bit_mask = (u32)(1 << (NUM_OF_PINS - (pin+1)));

	/* Get the direction */
	if (pin > (NUM_OF_PINS/2) -1)
		*dir = (in_be32(&par_io[port].cpdir2) & pin_2bit_mask) >>
			(NUM_OF_PINS-(pin%(NUM_OF_PINS/2)+1)*2);
	else
		*dir = (in_be32(&par_io[port].cpdir1) & pin_2bit_mask) >>
			(NUM_OF_PINS-(pin%(NUM_OF_PINS/2)+1)*2);

	/* Get the open drain settings */
	if (in_be32(&par_io[port].cpodr) & pin_1bit_mask)
		*open_drain = 1;
	else 
		*open_drain = 0;

	/* Get the assignment */
	if (pin > (NUM_OF_PINS/2) -1)
		*assign = (in_be32(&par_io[port].cppar2) & pin_2bit_mask) >> 
			(NUM_OF_PINS - (pin%(NUM_OF_PINS/2)+1)*2);
	else
		*assign = (in_be32(&par_io[port].cppar1) & pin_2bit_mask) >> 
			(NUM_OF_PINS - (pin%(NUM_OF_PINS/2)+1)*2);

	/* Get the value */
	if (in_be32(&par_io[port].cpdat) & pin_1bit_mask)
		*val = 1;
	else 
		*val = 0;
}

static void set_io_config(u8 port, u8 pin, int dir, int open_drain, int assign, int val)
{
	u32			pin_2bit_mask = (u32)(3 << (NUM_OF_PINS-(pin%(NUM_OF_PINS/2)+1)*2));
	u32			pin_1bit_mask = (u32)(1 << (NUM_OF_PINS - (pin+1)));
	volatile ccsr_gur_t	*gur = (void *)(CONFIG_SYS_MPC85xx_GUTS_ADDR);
	volatile par_io_t	*par_io = (volatile par_io_t *)&(gur->qe_par_io);
	u32			tmp_val;

	/* Setup the direction */
	tmp_val = (pin > (NUM_OF_PINS/2) - 1) ? \
		in_be32(&par_io[port].cpdir2) :
		in_be32(&par_io[port].cpdir1);
	tmp_val &= ~pin_2bit_mask;
	tmp_val |= (u32)(dir << (NUM_OF_PINS - (pin%(NUM_OF_PINS/2)+1)*2));
	if (pin > (NUM_OF_PINS/2) -1)
		out_be32(&par_io[port].cpdir2, tmp_val);
	else
		out_be32(&par_io[port].cpdir1, tmp_val);

	/* Setup the open drain */
	tmp_val = in_be32(&par_io[port].cpodr);
	tmp_val &= ~pin_1bit_mask;
	tmp_val |= (u32)(open_drain << (NUM_OF_PINS - (pin+1)));
	out_be32(&par_io[port].cpodr, tmp_val);

	/* Setup the assignment */
	tmp_val = (pin > (NUM_OF_PINS/2) - 1) ?
		in_be32(&par_io[port].cppar2):
		in_be32(&par_io[port].cppar1);
	tmp_val &= ~pin_2bit_mask;
	tmp_val |= (u32)(assign << (NUM_OF_PINS - (pin%(NUM_OF_PINS/2)+1)*2));
	if (pin > (NUM_OF_PINS/2) - 1) {
		out_be32(&par_io[port].cppar2, tmp_val);
	} else {
		out_be32(&par_io[port].cppar1, tmp_val);
	}

	/* Set the value */
	tmp_val = in_be32(&par_io[port].cpdat);
	tmp_val &= ~pin_1bit_mask;
	tmp_val |= (u32)(val << (NUM_OF_PINS - (pin+1)));
	out_be32(&par_io[port].cpdat, tmp_val);
}
#else
#error "No GPIO manipulation functions defined for this architecture"
#endif

#if defined(CONFIG_MPC85xx)
static inline void drive_gpio (int gpio, int direction)
{
	int dir, open_drain, assign, val;

	/* Get */
	get_io_config(gpio/32, gpio%32, &dir, &open_drain, &assign, &val);
	/* Update */
	dir = direction;
	/* Set */
	set_io_config(gpio/32, gpio%32, dir, open_drain, assign, val);
}

static inline int get_gpio_dir (int gpio)
{
	int dir, open_drain, assign, val;

	/* Get */
	get_io_config(gpio/32, gpio%32, &dir, &open_drain, &assign, &val);

	/* Return value */
	return dir;
}

static inline int read_gpio (int gpio)
{
	int dir, open_drain, assign, val;

	/* Get */
	get_io_config(gpio/32, gpio%32, &dir, &open_drain, &assign, &val);

	/* Return value */
	return val;
}

static inline void set_gpio (int gpio, int value)
{
	int dir, open_drain, assign, val;

	/* Get */
	get_io_config(gpio/32, gpio%32, &dir, &open_drain, &assign, &val);
	/* Update */
	val = value;
	/* Set */
	set_io_config(gpio/32, gpio%32, dir, open_drain, assign, val);
}

static inline void assign_gpio (int gpio, int assignment)
{
	int dir, open_drain, assign, val;

	/* Get */
	get_io_config(gpio/32, gpio%32, &dir, &open_drain, &assign, &val);
	/* Update */
	assign = assignment;
	/* Set */
	set_io_config(gpio/32, gpio%32, dir, open_drain, assign, val);
}

static void describe_gpio (int gpio, char *buffer)
{
	int dir, open_drain, assign, val;
	int written = 0;

	/* Get GPIO settings */
	get_io_config(gpio/32, gpio%32, &dir, &open_drain, &assign, &val);

	/* Describe GPIO */
        written += sprintf (&buffer[written], "%c%d ", ('A' + gpio/32), gpio%32);
	if (assign == 0) {
		written += sprintf (&buffer[written], "GPIO");
	}
	else {
		written += sprintf (&buffer[written], "D%d", assign);
	}
}
#endif

/*
 * Subroutine:  do_gpio
 *
 * Description: Handler for 'gpio' command..
 *
 * Inputs:	argv[1] contains the subcommand
 *
 * Return:      None
 *
 */
int do_gpio(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	int gpio;

	if (argc < 1)
		goto usage;

	if (argc == 1)
	{
		for (gpio = 0; gpio < MAX_GPIO + 1; gpio++)
		{
			char buffer[128];
			describe_gpio (gpio, buffer);
			buffer[127] = '\0';

			printf ("GPIO %d - %s %s: %s\n", gpio,
					read_gpio (gpio) ? "set" : "clear",
#if defined (CONFIG_MPC85xx)
					get_gpio_dir (gpio) == 0 ? "disabled" :
					 get_gpio_dir (gpio) == 1 ? "out" : 
					   get_gpio_dir (gpio) == 2 ? "in" : "io",
#else
					get_gpio_dir (gpio) ? "out" : "in",
#endif
					buffer);
		}
		return 0;
	}
#ifdef NUM_OF_PORTS
	if (argv[1][0] >= 'A' && argv[1][0] < ('A' + NUM_OF_PORTS)) {
		gpio = (argv[1][0] - 'A') * ((MAX_GPIO+1)/NUM_OF_PORTS);
		gpio += simple_strtoul (&argv[1][1], NULL, 0);
	}
	else if (argv[1][0] >= 'a' && argv[1][0] < ('a' + NUM_OF_PORTS)) {
		gpio = (argv[1][0] - 'a') * ((MAX_GPIO+1)/NUM_OF_PORTS);
		gpio += simple_strtoul (&argv[1][1], NULL, 0);
	}
	else
#endif
	gpio = simple_strtoul (argv[1], NULL, 0);
	if (gpio < 0 || gpio > MAX_GPIO)
	{
		printf ("Invalid gpio: %d\n", gpio);
		goto usage;
	}
	if (argc == 2)
	{
		char buffer[128];
		describe_gpio (gpio, buffer);
		buffer[127] = '\0';

		printf ("GPIO %d - %s %s: %s\n", gpio,
				read_gpio (gpio) ? "set" : "clear",
#if defined (CONFIG_MPC85xx)
				get_gpio_dir (gpio) == 0 ? "disabled" :
				 get_gpio_dir (gpio) == 1 ? "out" : 
				  get_gpio_dir (gpio) == 2 ? "in" : "io",
#else
				get_gpio_dir (gpio) ? "out" : "in",
#endif
				buffer);
		return 0;
	}
	else
	{
		int i;
		for (i = 2; i < argc; i++)
		{
			char *command = argv[i];
#if defined (CONFIG_MPC85xx)
			if (strcmp (command, "OUT") == 0 || strcmp (command, "out") == 0)
				drive_gpio (gpio, 1);
			else if (strcmp (command, "IN") == 0 || strcmp (command, "in") == 0)
				drive_gpio (gpio, 2);
#else
			if (strcmp (command, "OUT") == 0 || strcmp (command, "out") == 0)
				drive_gpio (gpio, 1);
			else if (strcmp (command, "IN") == 0 || strcmp (command, "in") == 0)
				drive_gpio (gpio, 0);
#endif
			else if (strcmp (command, "SET") == 0 || strcmp (command, "set") == 0)
				set_gpio (gpio, 1);
			else if (strcmp (command, "CLEAR") == 0 || strcmp (command, "clear") == 0)
				set_gpio (gpio, 0);
#if defined (CONFIG_MPC85xx)
			else if (strcmp (command, "DISABLED") == 0 || strcmp (command, "disabled") == 0)
				drive_gpio (gpio, 0);
			else if (strcmp (command, "IO") == 0 || strcmp (command, "io") == 0)
				drive_gpio (gpio, 3);

			else if (strcmp(command, "GPIO") == 0 ||
				 strcmp(command, "gpio") == 0)
				assign_gpio(gpio, 0);
			else if ((strncmp(command, "D", 1) == 0 ||
				  strncmp(command, "d", 1) == 0) &&
				 command[1] >= '0' && command[1] <= '3')
				assign_gpio(gpio, command[1] - '0');
#endif
#if defined (CONFIG_PXA250) || defined (CONFIG_PXA27X)
			/* Allow setting alternate functions on pxa2xx */
			else if (strcmp(command, "GPIO") == 0 ||
				 strcmp(command, "gpio") == 0)
				pxa_gpio_mode(gpio, 0);

			else if ((strncmp(command, "AF", 2) == 0 ||
				  strncmp(command, "af", 2) == 0) &&
				 command[2] >= '1' && command[2] <= '3')
				pxa_gpio_mode(gpio, command[2] - '0');
#endif
			else
			{
				printf ("Invalid command: %s\n", command);
				goto usage;
			}
		}
		return 0;
	}

usage:
	printf ("Usage:\n%s\n", cmdtp->usage);
	return 1;
}

U_BOOT_CMD(
	gpio,	10,	1,	do_gpio,
	"gpio    - manipulate GPIO pins",
	"        - display all gpio details\n"
	"gpio <gpio>  - display gpio info\n"
#if defined (CONFIG_PXA250) || defined (CONFIG_PXA27X)
	"gpio <gpio> [(clear|set|in|out|gpio|af[1-3])...] set gpio state\n"
#elif defined (CONFIG_MPC85xx)
	"gpio <gpio> [(clear|set|in|out|disabled|io|gpio|d[0-3])...]\n"
#else
	"gpio <gpio> [(clear|set|in|out)... ]  - set gpio state\n"
#endif
);
