--- arch/avr32/mach-at32ap/Kconfig | 8 arch/avr32/mach-at32ap/pio.c | 499 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 506 insertions(+), 1 deletion(-) Index: linux-2.6.18-avr32/arch/avr32/mach-at32ap/pio.c =================================================================== --- linux-2.6.18-avr32.orig/arch/avr32/mach-at32ap/pio.c 2006-11-29 16:22:14.000000000 +0100 +++ linux-2.6.18-avr32/arch/avr32/mach-at32ap/pio.c 2006-11-29 16:29:20.000000000 +0100 @@ -22,7 +22,7 @@ struct pio_device { void __iomem *regs; - const struct platform_device *pdev; + struct platform_device *pdev; struct clk *clk; u32 pinmux_mask; u32 gpio_mask; @@ -119,6 +119,34 @@ fail: dump_stack(); } +static unsigned int pio_id(struct pio_device *pio) +{ + return pio - pio_dev; +} + +static void __enable_gpio(struct pio_device *pio, u32 mask) +{ + pio_writel(pio, PUER, mask); + pio_writel(pio, ODR, mask); + pio_writel(pio, PER, mask); +} + +static void __disable_gpio(struct pio_device *pio, u32 mask) +{ + pio_writel(pio, PUER, mask); + pio_writel(pio, ODR, mask); +} + +static void pio_dealloc_mask(struct pio_device *pio, u32 mask) +{ + u32 old, new; + + do { + old = pio->pinmux_mask; + new = old & ~mask; + } while (cmpxchg(&pio->pinmux_mask, old, new) != old); +} + /* GPIO API */ int gpio_request(unsigned int gpio, const char *label) @@ -210,6 +238,475 @@ void gpio_set_value(unsigned int gpio, i } EXPORT_SYMBOL(gpio_set_value); +#ifdef CONFIG_PIO_DEV +#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; + + /* Too bad we don't have committable items... */ + int enabled; + + struct pio_device *pio; + u32 pin_mask; + + 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 int gpio_dev_open(struct inode *inode, struct file *file) +{ + struct gpio_item *gpio = container_of(inode->i_cdev, + struct gpio_item, + char_dev); + + config_item_get(&gpio->item); + file->private_data = gpio; + return 0; +} + +static int gpio_dev_release(struct inode *inode, struct file *file) +{ + struct gpio_item *gpio = file->private_data; + + config_item_put(&gpio->item); + return 0; +} + +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; + + value = pio_readl(gpio->pio, PDSR) & gpio->pin_mask; + + 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; + + pio_writel(gpio->pio, CODR, ~value & mask); + pio_writel(gpio->pio, SODR, 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, + .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) +{ + if (gpio->pio) + return sprintf(page, "%u\n", pio_id(gpio->pio)); + else + return sprintf(page, "-1\n"); +} + +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 ((id < MAX_NR_PIO_DEVICES) && pio_dev[id].regs) { + gpio->pio = &pio_dev[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) +{ + struct pio_device *pio; + u32 old_mask, new_mask; + u32 old, new; + char *p = (char *)page; + ssize_t ret = -EINVAL; + + new_mask = simple_strtoul(p, &p, 0); + if (!p || (*p && (*p != '\n'))) + return -EINVAL; + + /* + * Must have a PIO before we can start allocating pins, but we + * must not be live. + */ + spin_lock(&gpio->lock); + pio = gpio->pio; + if (!pio || gpio->enabled) + goto out; + + ret = -EBUSY; + old_mask = gpio->pin_mask; + do { + old = pio->pinmux_mask; + if ((old & ~old_mask) & new_mask) + goto out; + + new = (old & ~old_mask) | new_mask; + } while (cmpxchg(&pio->pinmux_mask, old, new) != old); + + gpio->pin_mask = new_mask; + __disable_gpio(pio, old_mask); + __enable_gpio(pio, new_mask); + ret = count; + +out: + spin_unlock(&gpio->lock); + return ret; +} + +static ssize_t gpio_show_oe_mask(struct gpio_item *gpio, char *page) +{ + u32 mask = 0; + + spin_lock(&gpio->lock); + if (gpio->pio) { + mask = pio_readl(gpio->pio, OSR); + mask &= gpio->pin_mask; + } + spin_unlock(&gpio->lock); + + return sprintf(page, "0x%08x\n", 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->pio) { + mask &= gpio->pin_mask; + pio_writel(gpio->pio, ODR, mask ^ gpio->pin_mask); + pio_writel(gpio->pio, OER, 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) + /* Already enabled; do nothing. */ + return count; + + BUG_ON(gpio->id >= GPIO_DEV_MAX); + + if (!enabled) { + class_device_unregister(gpio->gpio_dev); + cdev_del(&gpio->char_dev); + } + + /* Disallow any updates to gpio_id or pin_mask */ + spin_lock(&gpio->lock); + gpio->enabled = enabled; + spin_unlock(&gpio->lock); + + if (!enabled) + return count; + + 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), + &gpio->pio->pdev->dev, + "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 (pio%d/0x%08x) as (%d:%d)\n", + gpio->id, pio_id(gpio->pio), 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: + 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); + + return &gpio->item; +} + +static void gpio_drop_item(struct config_group *group, + struct config_item *item) +{ + struct gpio_item *gpio = to_gpio_item(item); + struct pio_device *pio; + + spin_lock(&gpio->lock); + if (gpio->enabled) { + class_device_unregister(gpio->gpio_dev); + cdev_del(&gpio->char_dev); + } + + pio = gpio->pio; + if (pio) { + __disable_gpio(pio, gpio->pin_mask); + pio_dealloc_mask(pio, gpio->pin_mask); + gpio->pio = NULL; + } + 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 pio_init_dev(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(pio_init_dev); +#endif /* CONFIG_PIO_DEV */ + static int __init pio_probe(struct platform_device *pdev) { struct pio_device *pio = NULL; Index: linux-2.6.18-avr32/arch/avr32/mach-at32ap/Kconfig =================================================================== --- linux-2.6.18-avr32.orig/arch/avr32/mach-at32ap/Kconfig 2006-11-29 16:23:31.000000000 +0100 +++ linux-2.6.18-avr32/arch/avr32/mach-at32ap/Kconfig 2006-11-29 16:24:35.000000000 +0100 @@ -2,6 +2,14 @@ if PLATFORM_AT32AP menu "Atmel AVR32 AP options" +config PIO_DEV + bool "PIO /dev interface" + select CONFIGFS_FS + default y + help + Say `Y' to enable a /dev interface to the Parallel I/O + Controller. + endmenu endif