From 0ba3c3570e62386eba5cb04b54735d3203b38c2e Mon Sep 17 00:00:00 2001 From: Haavard Skinnemoen Date: Tue, 12 Jun 2007 17:03:10 +0200 Subject: [PATCH] [AVR32] GPIO /dev interface This adds a simple /dev interface for GPIO, configured using configfs. To use: echo 1 > gpio_id echo 0xff00 > pin_mask echo 0xff00 > oe_mask echo 1 > enabled echo -ne '\x00\x00\xff\x00' > /dev/gpio0 and watch the LEDs. Signed-off-by: Haavard Skinnemoen --- arch/avr32/mach-at32ap/Kconfig | 7 + arch/avr32/mach-at32ap/Makefile | 1 + arch/avr32/mach-at32ap/gpio-dev.c | 570 +++++++++++++++++++++++++++++++ arch/avr32/mach-at32ap/pio.c | 76 ++++ include/asm-avr32/arch-at32ap/portmux.h | 12 + 5 files changed, 666 insertions(+), 0 deletions(-) create mode 100644 arch/avr32/mach-at32ap/gpio-dev.c diff --git a/arch/avr32/mach-at32ap/Kconfig b/arch/avr32/mach-at32ap/Kconfig index eb30783..43c5b9f 100644 --- a/arch/avr32/mach-at32ap/Kconfig +++ b/arch/avr32/mach-at32ap/Kconfig @@ -26,6 +26,13 @@ config AP7000_8_BIT_SMC endchoice +config GPIO_DEV + bool "GPIO /dev interface" + select CONFIGFS_FS + default n + help + Say `Y' to enable a /dev interface to the GPIO pins. + endmenu endif # PLATFORM_AT32AP diff --git a/arch/avr32/mach-at32ap/Makefile b/arch/avr32/mach-at32ap/Makefile index a8b4450..250372a 100644 --- a/arch/avr32/mach-at32ap/Makefile +++ b/arch/avr32/mach-at32ap/Makefile @@ -2,3 +2,4 @@ obj-y += at32ap.o clock.o intc.o extint.o pio.o hsmc.o obj-$(CONFIG_CPU_AT32AP7000) += at32ap7000.o obj-$(CONFIG_CPU_AT32AP7000) += time-tc.o obj-$(CONFIG_CPU_FREQ_AT32AP) += cpufreq.o +obj-$(CONFIG_GPIO_DEV) += gpio-dev.o diff --git a/arch/avr32/mach-at32ap/gpio-dev.c b/arch/avr32/mach-at32ap/gpio-dev.c new file mode 100644 index 0000000..3d4810d --- /dev/null +++ b/arch/avr32/mach-at32ap/gpio-dev.c @@ -0,0 +1,570 @@ +/* + * GPIO /dev and configfs interface + * + * Copyright (C) 2006-2007 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#define GPIO_DEV_MAX 8 + +static struct class *gpio_dev_class; +static dev_t gpio_devt; + +struct gpio_item { + spinlock_t lock; + + int enabled; + int initialized; + int port; + u32 pin_mask; + u32 oe_mask; + + /* Pin state last time we read it (for blocking reads) */ + u32 pin_state; + int changed; + + wait_queue_head_t change_wq; + struct fasync_struct *async_queue; + + int id; + struct class_device *gpio_dev; + struct cdev char_dev; + struct config_item item; +}; + +struct gpio_attribute { + struct configfs_attribute attr; + ssize_t (*show)(struct gpio_item *, char *); + ssize_t (*store)(struct gpio_item *, const char *, size_t); +}; + +static irqreturn_t gpio_dev_interrupt(int irq, void *dev_id) +{ + struct gpio_item *gpio = dev_id; + u32 old_state, new_state; + + old_state = gpio->pin_state; + new_state = at32_gpio_get_value_multiple(gpio->port, gpio->pin_mask); + gpio->pin_state = new_state; + + if (new_state != old_state) { + gpio->changed = 1; + wake_up_interruptible(&gpio->change_wq); + + if (gpio->async_queue) + kill_fasync(&gpio->async_queue, SIGIO, POLL_IN); + } + + return IRQ_HANDLED; +} + +static int gpio_dev_open(struct inode *inode, struct file *file) +{ + struct gpio_item *gpio = container_of(inode->i_cdev, + struct gpio_item, + char_dev); + unsigned int irq; + unsigned int i; + int ret; + + nonseekable_open(inode, file); + config_item_get(&gpio->item); + file->private_data = gpio; + + gpio->pin_state = at32_gpio_get_value_multiple(gpio->port, + gpio->pin_mask); + gpio->changed = 1; + + for (i = 0; i < 32; i++) { + if (gpio->pin_mask & (1 << i)) { + irq = gpio_to_irq(32 * gpio->port + i); + ret = request_irq(irq, gpio_dev_interrupt, 0, + "gpio-dev", gpio); + if (ret) + goto err_irq; + } + } + + return 0; + +err_irq: + while (i--) { + if (gpio->pin_mask & (1 << i)) { + irq = gpio_to_irq(32 * gpio->port + i); + free_irq(irq, gpio); + } + } + + config_item_put(&gpio->item); + + return ret; +} + +static int gpio_dev_fasync(int fd, struct file *file, int mode) +{ + struct gpio_item *gpio = file->private_data; + + return fasync_helper(fd, file, mode, &gpio->async_queue); +} + +static int gpio_dev_release(struct inode *inode, struct file *file) +{ + struct gpio_item *gpio = file->private_data; + unsigned int irq; + unsigned int i; + + gpio_dev_fasync(-1, file, 0); + + for (i = 0; i < 32; i++) { + if (gpio->pin_mask & (1 << i)) { + irq = gpio_to_irq(32 * gpio->port + i); + free_irq(irq, gpio); + } + } + + config_item_put(&gpio->item); + + return 0; +} + +static unsigned int gpio_dev_poll(struct file *file, poll_table *wait) +{ + struct gpio_item *gpio = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &gpio->change_wq, wait); + if (gpio->changed) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static ssize_t gpio_dev_read(struct file *file, char __user *buf, + size_t count, loff_t *offset) +{ + struct gpio_item *gpio = file->private_data; + u32 value; + + spin_lock_irq(&gpio->lock); + while (!gpio->changed) { + spin_unlock_irq(&gpio->lock); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(gpio->change_wq, gpio->changed)) + return -ERESTARTSYS; + + spin_lock_irq(&gpio->lock); + } + + gpio->changed = 0; + value = at32_gpio_get_value_multiple(gpio->port, gpio->pin_mask); + + spin_unlock_irq(&gpio->lock); + + count = min(count, (size_t)4); + if (copy_to_user(buf, &value, count)) + return -EFAULT; + + return count; +} + +static ssize_t gpio_dev_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct gpio_item *gpio = file->private_data; + u32 value = 0; + u32 mask = ~0UL; + + count = min(count, (size_t)4); + if (copy_from_user(&value, buf, count)) + return -EFAULT; + + /* Assuming big endian */ + mask <<= (4 - count) * 8; + mask &= gpio->pin_mask; + + at32_gpio_set_value_multiple(gpio->port, value, mask); + + return count; +} + +static struct file_operations gpio_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = gpio_dev_open, + .release = gpio_dev_release, + .fasync = gpio_dev_fasync, + .poll = gpio_dev_poll, + .read = gpio_dev_read, + .write = gpio_dev_write, +}; + +static struct gpio_item *to_gpio_item(struct config_item *item) +{ + return item ? container_of(item, struct gpio_item, item) : NULL; +} + +static ssize_t gpio_show_gpio_id(struct gpio_item *gpio, char *page) +{ + return sprintf(page, "%d\n", gpio->port); +} + +static ssize_t gpio_store_gpio_id(struct gpio_item *gpio, + const char *page, size_t count) +{ + unsigned long id; + char *p = (char *)page; + ssize_t ret = -EINVAL; + + id = simple_strtoul(p, &p, 0); + if (!p || (*p && (*p != '\n'))) + return -EINVAL; + + /* Switching PIO is not allowed when live... */ + spin_lock(&gpio->lock); + if (!gpio->enabled) { + ret = -ENXIO; + if (at32_gpio_port_is_valid(id)) { + gpio->port = id; + ret = count; + } + } + spin_unlock(&gpio->lock); + + return ret; +} + +static ssize_t gpio_show_pin_mask(struct gpio_item *gpio, char *page) +{ + return sprintf(page, "0x%08x\n", gpio->pin_mask); +} + +static ssize_t gpio_store_pin_mask(struct gpio_item *gpio, + const char *page, size_t count) +{ + u32 new_mask; + char *p = (char *)page; + ssize_t ret = -EINVAL; + + new_mask = simple_strtoul(p, &p, 0); + if (!p || (*p && (*p != '\n'))) + return -EINVAL; + + /* Can't update the pin mask while live. */ + spin_lock(&gpio->lock); + if (!gpio->enabled) { + gpio->oe_mask &= new_mask; + gpio->pin_mask = new_mask; + ret = count; + } + spin_unlock(&gpio->lock); + + return ret; +} + +static ssize_t gpio_show_oe_mask(struct gpio_item *gpio, char *page) +{ + return sprintf(page, "0x%08x\n", gpio->oe_mask); +} + +static ssize_t gpio_store_oe_mask(struct gpio_item *gpio, + const char *page, size_t count) +{ + u32 mask; + char *p = (char *)page; + ssize_t ret = -EINVAL; + + mask = simple_strtoul(p, &p, 0); + if (!p || (*p && (*p != '\n'))) + return -EINVAL; + + spin_lock(&gpio->lock); + if (!gpio->enabled) { + gpio->oe_mask = mask & gpio->pin_mask; + ret = count; + } + spin_unlock(&gpio->lock); + + return ret; +} + +static ssize_t gpio_show_enabled(struct gpio_item *gpio, char *page) +{ + return sprintf(page, "%d\n", gpio->enabled); +} + +static ssize_t gpio_store_enabled(struct gpio_item *gpio, + const char *page, size_t count) +{ + char *p = (char *)page; + int enabled; + int ret; + + enabled = simple_strtoul(p, &p, 0); + if (!p || (*p && (*p != '\n'))) + return -EINVAL; + + /* make it a boolean value */ + enabled = !!enabled; + + if (gpio->enabled == enabled) + /* No change; do nothing. */ + return count; + + BUG_ON(gpio->id >= GPIO_DEV_MAX); + + if (!enabled) { + class_device_unregister(gpio->gpio_dev); + cdev_del(&gpio->char_dev); + at32_deselect_pins(gpio->port, gpio->pin_mask); + gpio->initialized = 0; + } else { + if (gpio->port < 0 || !gpio->pin_mask) + return -ENODEV; + } + + /* Disallow any updates to gpio_id or pin_mask */ + spin_lock(&gpio->lock); + gpio->enabled = enabled; + spin_unlock(&gpio->lock); + + if (!enabled) + return count; + + /* Now, try to allocate the pins */ + ret = at32_select_gpio_pins(gpio->port, gpio->pin_mask, gpio->oe_mask); + if (ret) + goto err_alloc_pins; + + gpio->initialized = 1; + + cdev_init(&gpio->char_dev, &gpio_dev_fops); + gpio->char_dev.owner = THIS_MODULE; + ret = cdev_add(&gpio->char_dev, MKDEV(MAJOR(gpio_devt), gpio->id), 1); + if (ret < 0) + goto err_cdev_add; + gpio->gpio_dev = class_device_create(gpio_dev_class, NULL, + MKDEV(MAJOR(gpio_devt), gpio->id), + NULL, + "gpio%d", gpio->id); + if (IS_ERR(gpio->gpio_dev)) { + printk(KERN_ERR "failed to create gpio%d\n", gpio->id); + ret = PTR_ERR(gpio->gpio_dev); + goto err_class_dev; + } + + printk(KERN_INFO "created gpio%d (port%d/0x%08x) as (%d:%d)\n", + gpio->id, gpio->port, gpio->pin_mask, + MAJOR(gpio->gpio_dev->devt), MINOR(gpio->gpio_dev->devt)); + + return 0; + +err_class_dev: + cdev_del(&gpio->char_dev); +err_cdev_add: + at32_deselect_pins(gpio->port, gpio->pin_mask); + gpio->initialized = 0; +err_alloc_pins: + spin_lock(&gpio->lock); + gpio->enabled = 0; + spin_unlock(&gpio->lock); + + return ret; +} + +static struct gpio_attribute gpio_item_attr_gpio_id = { + .attr = { + .ca_owner = THIS_MODULE, + .ca_name = "gpio_id", + .ca_mode = S_IRUGO | S_IWUSR, + }, + .show = gpio_show_gpio_id, + .store = gpio_store_gpio_id, +}; +static struct gpio_attribute gpio_item_attr_pin_mask = { + .attr = { + .ca_owner = THIS_MODULE, + .ca_name = "pin_mask", + .ca_mode = S_IRUGO | S_IWUSR, + }, + .show = gpio_show_pin_mask, + .store = gpio_store_pin_mask, +}; +static struct gpio_attribute gpio_item_attr_oe_mask = { + .attr = { + .ca_owner = THIS_MODULE, + .ca_name = "oe_mask", + .ca_mode = S_IRUGO | S_IWUSR, + }, + .show = gpio_show_oe_mask, + .store = gpio_store_oe_mask, +}; +static struct gpio_attribute gpio_item_attr_enabled = { + .attr = { + .ca_owner = THIS_MODULE, + .ca_name = "enabled", + .ca_mode = S_IRUGO | S_IWUSR, + }, + .show = gpio_show_enabled, + .store = gpio_store_enabled, +}; + +static struct configfs_attribute *gpio_item_attrs[] = { + &gpio_item_attr_gpio_id.attr, + &gpio_item_attr_pin_mask.attr, + &gpio_item_attr_oe_mask.attr, + &gpio_item_attr_enabled.attr, + NULL, +}; + +static ssize_t gpio_show_attr(struct config_item *item, + struct configfs_attribute *attr, + char *page) +{ + struct gpio_item *gpio_item = to_gpio_item(item); + struct gpio_attribute *gpio_attr + = container_of(attr, struct gpio_attribute, attr); + ssize_t ret = 0; + + if (gpio_attr->show) + ret = gpio_attr->show(gpio_item, page); + return ret; +} + +static ssize_t gpio_store_attr(struct config_item *item, + struct configfs_attribute *attr, + const char *page, size_t count) +{ + struct gpio_item *gpio_item = to_gpio_item(item); + struct gpio_attribute *gpio_attr + = container_of(attr, struct gpio_attribute, attr); + ssize_t ret = -EINVAL; + + if (gpio_attr->store) + ret = gpio_attr->store(gpio_item, page, count); + return ret; +} + +static void gpio_release(struct config_item *item) +{ + kfree(to_gpio_item(item)); +} + +static struct configfs_item_operations gpio_item_ops = { + .release = gpio_release, + .show_attribute = gpio_show_attr, + .store_attribute = gpio_store_attr, +}; + +static struct config_item_type gpio_item_type = { + .ct_item_ops = &gpio_item_ops, + .ct_attrs = gpio_item_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *gpio_make_item(struct config_group *group, + const char *name) +{ + static int next_id; + struct gpio_item *gpio; + + if (next_id >= GPIO_DEV_MAX) + return NULL; + + gpio = kzalloc(sizeof(struct gpio_item), GFP_KERNEL); + if (!gpio) + return NULL; + + gpio->id = next_id++; + config_item_init_type_name(&gpio->item, name, &gpio_item_type); + spin_lock_init(&gpio->lock); + init_waitqueue_head(&gpio->change_wq); + + return &gpio->item; +} + +static void gpio_drop_item(struct config_group *group, + struct config_item *item) +{ + struct gpio_item *gpio = to_gpio_item(item); + + spin_lock(&gpio->lock); + if (gpio->enabled) { + class_device_unregister(gpio->gpio_dev); + cdev_del(&gpio->char_dev); + } + + if (gpio->initialized) { + at32_deselect_pins(gpio->port, gpio->pin_mask); + gpio->initialized = 0; + gpio->enabled = 0; + } + spin_unlock(&gpio->lock); +} + +static struct configfs_group_operations gpio_group_ops = { + .make_item = gpio_make_item, + .drop_item = gpio_drop_item, +}; + +static struct config_item_type gpio_group_type = { + .ct_group_ops = &gpio_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem gpio_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "gpio", + .ci_type = &gpio_group_type, + }, + }, +}; + +static int __init gpio_dev_init(void) +{ + int err; + + gpio_dev_class = class_create(THIS_MODULE, "gpio-dev"); + if (IS_ERR(gpio_dev_class)) { + err = PTR_ERR(gpio_dev_class); + goto err_class_create; + } + + err = alloc_chrdev_region(&gpio_devt, 0, GPIO_DEV_MAX, "gpio"); + if (err < 0) + goto err_alloc_chrdev; + + /* Configfs initialization */ + config_group_init(&gpio_subsys.su_group); + init_MUTEX(&gpio_subsys.su_sem); + err = configfs_register_subsystem(&gpio_subsys); + if (err) + goto err_register_subsys; + + return 0; + +err_register_subsys: + unregister_chrdev_region(gpio_devt, GPIO_DEV_MAX); +err_alloc_chrdev: + class_destroy(gpio_dev_class); +err_class_create: + printk(KERN_WARNING "Failed to initialize gpio /dev interface\n"); + return err; +} +late_initcall(gpio_dev_init); diff --git a/arch/avr32/mach-at32ap/pio.c b/arch/avr32/mach-at32ap/pio.c index 1eb99b8..153bb9a 100644 --- a/arch/avr32/mach-at32ap/pio.c +++ b/arch/avr32/mach-at32ap/pio.c @@ -158,6 +158,82 @@ fail: dump_stack(); } +#ifdef CONFIG_GPIO_DEV + +/* Gang allocators and accessors; used by the GPIO /dev driver */ +int at32_gpio_port_is_valid(unsigned int port) +{ + return port < MAX_NR_PIO_DEVICES && pio_dev[port].regs != NULL; +} + +int at32_select_gpio_pins(unsigned int port, u32 pins, u32 oe_mask) +{ + struct pio_device *pio; + u32 old, new; + + pio = &pio_dev[port]; + BUG_ON(port > ARRAY_SIZE(pio_dev) || !pio->regs || (oe_mask & ~pins)); + + /* Try to allocate the pins */ + do { + old = pio->pinmux_mask; + if (old & pins) + return -EBUSY; + + new = old | pins; + } while (cmpxchg(&pio->pinmux_mask, old, new) != old); + + /* That went well, now configure the port */ + pio_writel(pio, OER, oe_mask); + pio_writel(pio, PER, pins); + + return 0; +} + +void at32_deselect_pins(unsigned int port, u32 pins) +{ + struct pio_device *pio; + u32 old, new; + + pio = &pio_dev[port]; + BUG_ON(port > ARRAY_SIZE(pio_dev) || !pio->regs); + + /* Return to a "safe" mux configuration */ + pio_writel(pio, PUER, pins); + pio_writel(pio, ODR, pins); + + /* Deallocate the pins */ + do { + old = pio->pinmux_mask; + new = old & ~pins; + } while (cmpxchg(&pio->pinmux_mask, old, new) != old); +} + +u32 at32_gpio_get_value_multiple(unsigned int port, u32 pins) +{ + struct pio_device *pio; + + pio = &pio_dev[port]; + BUG_ON(port > ARRAY_SIZE(pio_dev) || !pio->regs); + + return pio_readl(pio, PDSR) & pins; +} + +void at32_gpio_set_value_multiple(unsigned int port, u32 value, u32 mask) +{ + struct pio_device *pio; + + pio = &pio_dev[port]; + BUG_ON(port > ARRAY_SIZE(pio_dev) || !pio->regs); + + /* No atomic updates for now... */ + pio_writel(pio, CODR, ~value & mask); + pio_writel(pio, SODR, value & mask); +} + +#endif /* CONFIG_GPIO_DEV */ + + /*--------------------------------------------------------------------------*/ /* GPIO API */ diff --git a/include/asm-avr32/arch-at32ap/portmux.h b/include/asm-avr32/arch-at32ap/portmux.h index 9930871..0f95567 100644 --- a/include/asm-avr32/arch-at32ap/portmux.h +++ b/include/asm-avr32/arch-at32ap/portmux.h @@ -25,4 +25,16 @@ void at32_select_periph(unsigned int pin, unsigned int periph, void at32_select_gpio(unsigned int pin, unsigned long flags); void at32_reserve_pin(unsigned int pin); +#ifdef CONFIG_GPIO_DEV + +/* Gang allocators and accessors; used by the GPIO /dev driver */ +int at32_gpio_port_is_valid(unsigned int port); +int at32_select_gpio_pins(unsigned int port, u32 pins, u32 oe_mask); +void at32_deselect_pins(unsigned int port, u32 pins); + +u32 at32_gpio_get_value_multiple(unsigned int port, u32 pins); +void at32_gpio_set_value_multiple(unsigned int port, u32 value, u32 mask); + +#endif /* CONFIG_GPIO_DEV */ + #endif /* __ASM_ARCH_PORTMUX_H__ */ -- 1.5.2.2