319 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			319 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| --- a/arch/arm/Kconfig
 | |
| +++ b/arch/arm/Kconfig
 | |
| @@ -505,6 +505,8 @@ config PCI_HOST_VIA82C505
 | |
|  	depends on PCI && ARCH_SHARK
 | |
|  	default y
 | |
|  
 | |
| +source "drivers/gpio/Kconfig"
 | |
| +
 | |
|  source "drivers/pci/Kconfig"
 | |
|  
 | |
|  source "drivers/pcmcia/Kconfig"
 | |
| --- a/drivers/Makefile
 | |
| +++ b/drivers/Makefile
 | |
| @@ -81,3 +81,4 @@ obj-$(CONFIG_GENERIC_TIME)	+= clocksourc
 | |
|  obj-$(CONFIG_DMA_ENGINE)	+= dma/
 | |
|  obj-$(CONFIG_HID)		+= hid/
 | |
|  obj-$(CONFIG_PPC_PS3)		+= ps3/
 | |
| +obj-$(CONFIG_PROC_GPIO)		+= gpio/
 | |
| --- /dev/null
 | |
| +++ b/drivers/gpio/Kconfig
 | |
| @@ -0,0 +1,13 @@
 | |
| +config PROC_GPIO
 | |
| +	tristate "GPIO /proc interface"
 | |
| +	depends on PXA25x || PXA27x
 | |
| +	help
 | |
| +	  This enables an interface under /proc/gpio which allows reading or setting
 | |
| +	  of any GPIO, and also changing the GPIO alt function mode of any line.
 | |
| +
 | |
| +config PROC_GPIO_DEBUG
 | |
| +	boolean "Enable /proc/gpio debug logging"
 | |
| +	depends on PROC_GPIO
 | |
| +	help
 | |
| +	  This enables printk logging of activity done through /proc/gpio
 | |
| +
 | |
| --- /dev/null
 | |
| +++ b/drivers/gpio/Makefile
 | |
| @@ -0,0 +1,2 @@
 | |
| +# Expose GPIOs under /proc
 | |
| +obj-$(CONFIG_PROC_GPIO)		+= proc_gpio.o
 | |
| --- /dev/null
 | |
| +++ b/drivers/gpio/proc_gpio.c
 | |
| @@ -0,0 +1,276 @@
 | |
| +/*
 | |
| + *
 | |
| + *  PXA25x GPIOs exposed under /proc for reading and writing
 | |
| + *  They will show up under /proc/gpio/NN
 | |
| + *
 | |
| + *  Based on patch 1773/1 in the arm kernel patch repository at arm.linux.co.uk
 | |
| + *
 | |
| + */
 | |
| +
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/init.h>
 | |
| +#include <linux/proc_fs.h>
 | |
| +#include <linux/string.h>
 | |
| +#include <linux/ctype.h>
 | |
| +
 | |
| +#include <asm/hardware.h>
 | |
| +#include <asm/arch/pxa-regs.h>
 | |
| +#include <asm/uaccess.h>
 | |
| +
 | |
| +static struct proc_dir_entry *proc_gpio_parent;
 | |
| +static struct proc_dir_entry *proc_gpios[PXA_LAST_GPIO + 1];
 | |
| +
 | |
| +typedef struct
 | |
| +{
 | |
| +	int	gpio;
 | |
| +	char    name[32];
 | |
| +} gpio_summary_type;
 | |
| +
 | |
| +static gpio_summary_type gpio_summaries[PXA_LAST_GPIO + 1];
 | |
| +
 | |
| +static int proc_gpio_write(struct file *file, const char __user *buf,
 | |
| +                           unsigned long count, void *data)
 | |
| +{
 | |
| +	char *cur, lbuf[count + 1];
 | |
| +	gpio_summary_type *summary = data;
 | |
| +	u32 altfn, direction, setclear, gafr;
 | |
| +
 | |
| +	if (!capable(CAP_SYS_ADMIN))
 | |
| +		return -EACCES;
 | |
| +
 | |
| +	memset(lbuf, 0, count + 1);
 | |
| +
 | |
| +	if (copy_from_user(lbuf, buf, count))
 | |
| +		return -EFAULT;
 | |
| +
 | |
| +	cur = lbuf;
 | |
| +
 | |
| +	// Initialize to current state
 | |
| +	altfn = ((GAFR(summary->gpio) >> ((summary->gpio & 0x0f) << 0x01)) & 0x03);
 | |
| +	direction = GPDR(summary->gpio) & GPIO_bit(summary->gpio);
 | |
| +	setclear = GPLR(summary->gpio) & GPIO_bit(summary->gpio);
 | |
| +	while(1)
 | |
| +	{
 | |
| +		// We accept options: {GPIO|AF1|AF2|AF3}, {set|clear}, {in|out}
 | |
| +		// Anything else is an error
 | |
| +		while(cur[0] && (isspace(cur[0]) || ispunct(cur[0]))) cur = &(cur[1]);
 | |
| +
 | |
| +		if('\0' == cur[0]) break;
 | |
| +
 | |
| +		// Ok, so now we're pointing at the start of something
 | |
| +		switch(cur[0])
 | |
| +		{
 | |
| +			case 'G':
 | |
| +				// Check that next is "PIO" -- '\0' will cause safe short-circuit if end of buf
 | |
| +				if(!(cur[1] == 'P' && cur[2] == 'I' && cur[3] == 'O')) goto parse_error;
 | |
| +				// Ok, so set this GPIO to GPIO (non-ALT) function
 | |
| +				altfn = 0;
 | |
| +				cur = &(cur[4]);
 | |
| +				break;
 | |
| +			case 'A':
 | |
| +				if(!(cur[1] == 'F' && cur[2] >= '1' && cur[2] <= '3')) goto parse_error;
 | |
| +				altfn = cur[2] - '0';
 | |
| +				cur = &(cur[3]);
 | |
| +				break;
 | |
| +			case 's':
 | |
| +				if(!(cur[1] == 'e' && cur[2] == 't')) goto parse_error;
 | |
| +				setclear = 1;
 | |
| +				cur = &(cur[3]);
 | |
| +				break;
 | |
| +			case 'c':
 | |
| +				if(!(cur[1] == 'l' && cur[2] == 'e' && cur[3] == 'a' && cur[4] == 'r')) goto parse_error;
 | |
| +				setclear = 0;
 | |
| +				cur = &(cur[5]);
 | |
| +				break;
 | |
| +			case 'i':
 | |
| +				if(!(cur[1] == 'n')) goto parse_error;
 | |
| +				direction = 0;
 | |
| +				cur = &(cur[2]);
 | |
| +				break;
 | |
| +			case 'o':
 | |
| +				if(!(cur[1] == 'u' && cur[2] == 't')) goto parse_error;
 | |
| +				direction = 1;
 | |
| +				cur = &(cur[3]);
 | |
| +				break;
 | |
| +			default: goto parse_error;
 | |
| +		}
 | |
| +	}
 | |
| +	// Ok, now set gpio mode and value
 | |
| +	if(direction)
 | |
| +		GPDR(summary->gpio) |= GPIO_bit(summary->gpio);
 | |
| +	else
 | |
| +		GPDR(summary->gpio) &= ~GPIO_bit(summary->gpio);
 | |
| +
 | |
| +	gafr = GAFR(summary->gpio) & ~(0x3 << (((summary->gpio) & 0xf)*2));
 | |
| +	GAFR(summary->gpio) = gafr |  (altfn  << (((summary->gpio) & 0xf)*2));
 | |
| +
 | |
| +	if(direction && !altfn)
 | |
| +	{
 | |
| +		if(setclear) GPSR(summary->gpio) = GPIO_bit(summary->gpio);
 | |
| +		else GPCR(summary->gpio) = GPIO_bit(summary->gpio);
 | |
| +	}
 | |
| +
 | |
| +#ifdef CONFIG_PROC_GPIO_DEBUG
 | |
| +	printk(KERN_INFO "Set (%s,%s,%s) via /proc/gpio/%s\n",altfn ? (altfn == 1 ? "AF1" : (altfn == 2 ? "AF2" : "AF3")) : "GPIO",
 | |
| +				direction ? "out" : "in",
 | |
| +				setclear ? "set" : "clear",
 | |
| +				summary->name);
 | |
| +#endif
 | |
| +
 | |
| +	return count;
 | |
| +
 | |
| +parse_error:
 | |
| +	printk(KERN_CRIT "Parse error: Expect \"[GPIO|AF1|AF2|AF3]|[set|clear]|[in|out] ...\"\n");
 | |
| +	return -EINVAL;
 | |
| +}
 | |
| +
 | |
| +static int proc_gpio_read(char *page, char **start, off_t off,
 | |
| +			int count, int *eof, void *data)
 | |
| +{
 | |
| +	char *p = page;
 | |
| +	gpio_summary_type *summary = data;
 | |
| +	int len, i, af;
 | |
| +	i = summary->gpio;
 | |
| +
 | |
| +	p += sprintf(p, "%d\t%s\t%s\t%s\n", i,
 | |
| +			(af = ((GAFR(i) >> ((i & 0x0f) << 0x01)) & 0x03)) ? (af == 1 ? "AF1" : (af == 2 ? "AF2" : "AF3")) : "GPIO",
 | |
| +			(GPDR(i) & GPIO_bit(i)) ? "out" : "in",
 | |
| +			(GPLR(i) & GPIO_bit(i)) ? "set" : "clear");
 | |
| +
 | |
| +	len = (p - page) - off;
 | |
| +
 | |
| +	if(len < 0)
 | |
| +	{
 | |
| +		len = 0;
 | |
| +	}
 | |
| +
 | |
| +	*eof = (len <= count) ? 1 : 0;
 | |
| +	*start = page + off;
 | |
| +
 | |
| +	return len;
 | |
| +}
 | |
| +
 | |
| +
 | |
| +#ifdef CONFIG_PXA25x
 | |
| +static const char const *GAFR_DESC[] = { "GAFR0_L", "GAFR0_U", "GAFR1_L", "GAFR1_U", "GAFR2_L", "GAFR2_U" };
 | |
| +#elif defined(CONFIG_PXA27x)
 | |
| +static const char const *GAFR_DESC[] = { "GAFR0_L", "GAFR0_U", "GAFR1_L", "GAFR1_U", "GAFR2_L", "GAFR2_U", "GAFR3_L", "GAFR3_U" };
 | |
| +#endif
 | |
| +
 | |
| +static int proc_gafr_read(char *page, char **start, off_t off,
 | |
| +			int count, int *eof, void *data)
 | |
| +{
 | |
| +	char *p = page;
 | |
| +	int i, len;
 | |
| +
 | |
| +	for(i=0; i<ARRAY_SIZE(GAFR_DESC); i++)
 | |
| +	{
 | |
| +		p += sprintf(p, "%s: %08x\n", GAFR_DESC[i], GAFR(i*16));
 | |
| +	}
 | |
| +
 | |
| +	len = (p - page) - off;
 | |
| +
 | |
| +	if(len < 0)
 | |
| +	{
 | |
| +		len = 0;
 | |
| +	}
 | |
| +
 | |
| +	*eof = (len <= count) ? 1 : 0;
 | |
| +	*start = page + off;
 | |
| +
 | |
| +	return len;
 | |
| +}
 | |
| +
 | |
| +static int proc_gpdr_read(char *page, char **start, off_t off,
 | |
| +			int count, int *eof, void *data)
 | |
| +{
 | |
| +	char *p = page;
 | |
| +	int i, len;
 | |
| +
 | |
| +	for(i=0; i<=2; i++)
 | |
| +	{
 | |
| +		p += sprintf(p, "GPDR%d: %08x\n", i, GPDR(i * 32));
 | |
| +	}
 | |
| +
 | |
| +	len = (p - page) - off;
 | |
| +
 | |
| +	if(len < 0)
 | |
| +	{
 | |
| +		len = 0;
 | |
| +	}
 | |
| +
 | |
| +	*eof = (len <= count) ? 1 : 0;
 | |
| +	*start = page + off;
 | |
| +
 | |
| +	return len;
 | |
| +}
 | |
| +
 | |
| +static int proc_gplr_read(char *page, char **start, off_t off,
 | |
| +			int count, int *eof, void *data)
 | |
| +{
 | |
| +	char *p = page;
 | |
| +	int i, len;
 | |
| +
 | |
| +	for(i=0; i<=2; i++)
 | |
| +	{
 | |
| +		p += sprintf(p, "GPLR%d: %08x\n", i, GPLR(i * 32));
 | |
| +	}
 | |
| +
 | |
| +	len = (p - page) - off;
 | |
| +
 | |
| +	if(len < 0)
 | |
| +	{
 | |
| +		len = 0;
 | |
| +	}
 | |
| +
 | |
| +	*eof = (len <= count) ? 1 : 0;
 | |
| +	*start = page + off;
 | |
| +
 | |
| +	return len;
 | |
| +}
 | |
| +
 | |
| +static int __init gpio_init(void)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +	proc_gpio_parent = create_proc_entry("gpio", S_IFDIR | S_IRUGO | S_IXUGO, NULL);
 | |
| +	if(!proc_gpio_parent) return 0;
 | |
| +
 | |
| +	for(i=0; i < (PXA_LAST_GPIO+1); i++)
 | |
| +	{
 | |
| +		gpio_summaries[i].gpio = i;
 | |
| +		sprintf(gpio_summaries[i].name, "GPIO%d", i);
 | |
| +		proc_gpios[i] = create_proc_entry(gpio_summaries[i].name, 0644, proc_gpio_parent);
 | |
| +		if(proc_gpios[i])
 | |
| +		{
 | |
| +			proc_gpios[i]->data = &gpio_summaries[i];
 | |
| +			proc_gpios[i]->read_proc = proc_gpio_read;
 | |
| +			proc_gpios[i]->write_proc = proc_gpio_write;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	create_proc_read_entry("GAFR", 0444, proc_gpio_parent, proc_gafr_read, NULL);
 | |
| +	create_proc_read_entry("GPDR", 0444, proc_gpio_parent, proc_gpdr_read, NULL);
 | |
| +	create_proc_read_entry("GPLR", 0444, proc_gpio_parent, proc_gplr_read, NULL);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void gpio_exit(void)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +	remove_proc_entry("GAFR", proc_gpio_parent);
 | |
| +	remove_proc_entry("GPDR", proc_gpio_parent);
 | |
| +	remove_proc_entry("GPLR", proc_gpio_parent);
 | |
| +
 | |
| +	for(i=0; i < (PXA_LAST_GPIO+1); i++)
 | |
| +	{
 | |
| +		if(proc_gpios[i]) remove_proc_entry(gpio_summaries[i].name, proc_gpio_parent);
 | |
| +	}
 | |
| +	if(proc_gpio_parent) remove_proc_entry("gpio", NULL);
 | |
| +}
 | |
| +
 | |
| +module_init(gpio_init);
 | |
| +module_exit(gpio_exit);
 | |
| +MODULE_LICENSE("GPL");
 |