lkml.org 
[lkml]   [2008]   [Mar]   [26]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 1/1] [GPIO]: new arch-independent simple-gpio driver
    Date
    From: Mike Frysinger <vapier.adi@gmail.com>

    Signed-off-by: Mike Frysinger <vapier.adi@gmail.com>
    Signed-off-by: Bryan Wu <cooloney@kernel.org>
    ---
    drivers/char/Kconfig | 14 ++
    drivers/char/Makefile | 1 +
    drivers/char/simple-gpio.c | 308 ++++++++++++++++++++++++++++++++++++++++++++
    3 files changed, 323 insertions(+), 0 deletions(-)
    create mode 100644 drivers/char/simple-gpio.c

    diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
    index 47c6be8..dd17d06 100644
    --- a/drivers/char/Kconfig
    +++ b/drivers/char/Kconfig
    @@ -4,6 +4,20 @@

    menu "Character devices"

    +config SIMPLE_GPIO
    + tristate "Simple GPIO char interface"
    + depends on GENERIC_GPIO
    + default n
    + help
    + If you say Y here, you will get support for a character device
    + interface to the GPIO pins which will allow you to read and
    + write GPIO values from userspace.
    +
    + To compile this driver as a module, choose M here: the module
    + will be called simple-gpio.
    +
    + If unsure, it is safe to say Y.
    +
    config VT
    bool "Virtual terminal" if EMBEDDED
    depends on !S390
    diff --git a/drivers/char/Makefile b/drivers/char/Makefile
    index 5407b76..e087ec1 100644
    --- a/drivers/char/Makefile
    +++ b/drivers/char/Makefile
    @@ -97,6 +97,7 @@ obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o
    obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
    obj-$(CONFIG_GPIO_TB0219) += tb0219.o
    obj-$(CONFIG_TELCLOCK) += tlclk.o
    +obj-$(CONFIG_SIMPLE_GPIO) += simple-gpio.o

    obj-$(CONFIG_MWAVE) += mwave/
    obj-$(CONFIG_AGP) += agp/
    diff --git a/drivers/char/simple-gpio.c b/drivers/char/simple-gpio.c
    new file mode 100644
    index 0000000..36204ca
    --- /dev/null
    +++ b/drivers/char/simple-gpio.c
    @@ -0,0 +1,308 @@
    +/*
    + * Simple character interface to GPIOs. Allows you to read/write values and
    + * control the direction. Maybe add wakeup when gpio framework supports it ...
    + *
    + * To use, just declare in your board resources:
    + * static struct resource foo_resources[] = {
    + * .start = 0,
    + * .end = 5,
    + * .flags = IORESOURCE_IRQ,
    + * };
    + * static struct platform_device foo_dev = {
    + * .name = "simple-gpio",
    + * .num_resources = 1,
    + * .resource = &foo_resources
    + * };
    + * This will setup GPIO0 - GPIO5 (inclusive) for use by this driver.
    + *
    + * Copyright 2008 Analog Devices Inc.
    + *
    + * Licensed under the GPL-2 or later.
    + */
    +
    +#include <linux/cdev.h>
    +#include <linux/device.h>
    +#include <linux/errno.h>
    +#include <linux/fs.h>
    +#include <linux/kernel.h>
    +#include <linux/module.h>
    +#include <linux/platform_device.h>
    +#include <linux/types.h>
    +
    +#include <asm/atomic.h>
    +#include <asm/gpio.h>
    +#include <asm/uaccess.h>
    +
    +#define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args)
    +#define stampit() stamp("here i am")
    +#define pr_devinit(fmt, args...) ({ static const __devinitdata char __fmt[] = fmt; printk(__fmt, ## args); })
    +#define pr_init(fmt, args...) ({ static const __initdata char __fmt[] = fmt; printk(__fmt, ## args); })
    +
    +#define DRIVER_NAME "simple-gpio"
    +#define PFX DRIVER_NAME ": "
    +
    +struct gpio_data {
    + atomic_t open_count;
    +};
    +struct group_data {
    + dev_t dev_node;
    + struct cdev cdev;
    + struct resource *gpio_range;
    + struct gpio_data *gpios;
    +};
    +
    +/**
    + * simple_gpio_read - sample the value of the specified GPIO
    + */
    +static ssize_t simple_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
    +{
    + unsigned int gpio = iminor(file->f_path.dentry->d_inode);
    + ssize_t ret;
    +
    + stampit();
    +
    + for (ret = 0; ret < count; ++ret) {
    + char byte = '0' + gpio_get_value(gpio);
    + if (put_user(byte, buf + ret))
    + break;
    + }
    +
    + return ret;
    +}
    +
    +/**
    + * simple_gpio_write - modify the state of the specified GPIO
    + *
    + * We allow people to control the direction and value of the specified GPIO.
    + * You can only change these once per write though. We process the user buf
    + * and only do the last thing specified. We also ignore newlines. This way
    + * you can quickly test by doing from the shell:
    + * $ echo 'O1' > /dev/gpio8
    + * This will set GPIO8 to ouput mode and set the value to 1.
    + */
    +static ssize_t simple_gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
    +{
    + unsigned int gpio = iminor(file->f_path.dentry->d_inode);
    + ssize_t ret;
    + char dir = '?', uvalue = '?';
    + int value;
    +
    + stampit();
    +
    + ret = 0;
    + while (ret < count) {
    + char byte;
    + int user_ret = get_user(byte, buf + ret++);
    + if (user_ret)
    + return user_ret;
    +
    + switch (byte) {
    + case '\r':
    + case '\n': continue;
    + case 'I':
    + case 'O': dir = byte; break;
    + case 'T':
    + case '0':
    + case '1': uvalue = byte; break;
    + default: return -EINVAL;
    + }
    + stamp("processed byte '%c'", byte);
    + }
    +
    + switch (uvalue) {
    + case '?': value = gpio_get_value(gpio); break;
    + case 'T': value = !gpio_get_value(gpio); break;
    + default: value = uvalue - '0'; break;
    + }
    +
    + switch (dir) {
    + case 'I': gpio_direction_input(gpio); break;
    + case 'O': gpio_direction_output(gpio, value); break;
    + }
    +
    + if (uvalue != '?')
    + gpio_set_value(gpio, value);
    +
    + return ret;
    +}
    +
    +/**
    + * simple_gpio_open - claim the specified GPIO
    + *
    + * Grab the specified GPIO if it's available and keep track of how many times
    + * we've been opened (see close() below). We allow multiple people to open
    + * at the same time as there's no real limitation in the hardware for reading
    + * from different processes. Plus this way you can have one app do the write
    + * and management while quickly monitoring from another by doing:
    + * $ cat /dev/gpio8
    + */
    +static int simple_gpio_open(struct inode *ino, struct file *file)
    +{
    + struct group_data *group_data = container_of(ino->i_cdev, struct group_data, cdev);
    + unsigned int gpio = iminor(ino);
    + struct gpio_data *gpio_data = &group_data->gpios[gpio - group_data->gpio_range->start];
    + int ret;
    +
    + stampit();
    +
    + if (gpio < group_data->gpio_range->start || gpio > group_data->gpio_range->end)
    + return -ENXIO;
    +
    + ret = gpio_request(gpio, DRIVER_NAME);
    + if (ret)
    + return ret;
    +
    + atomic_inc(&gpio_data->open_count);
    +
    + return 0;
    +}
    +
    +/**
    + * simple_gpio_close - release the specified GPIO
    + *
    + * Do not actually free the specified GPIO until the last person has closed.
    + * We claim/release here rather than during probe() so that people can swap
    + * between drivers on the fly during runtime without having to load/unload
    + * kernel modules.
    + */
    +static int simple_gpio_release(struct inode *ino, struct file *file)
    +{
    + struct group_data *group_data = container_of(ino->i_cdev, struct group_data, cdev);
    + unsigned int gpio = iminor(ino);
    + struct gpio_data *gpio_data = &group_data->gpios[gpio - group_data->gpio_range->start];
    +
    + stampit();
    +
    + /* do not free until last consumer has closed */
    + if (atomic_dec_and_test(&gpio_data->open_count))
    + gpio_free(gpio);
    + else
    + stamp("gpio still in use -- not freeing");
    +
    + return 0;
    +}
    +
    +static struct class *simple_gpio_class;
    +
    +static struct file_operations simple_gpio_fops = {
    + .owner = THIS_MODULE,
    + .read = simple_gpio_read,
    + .write = simple_gpio_write,
    + .open = simple_gpio_open,
    + .release = simple_gpio_release,
    +};
    +
    +/**
    + * simple_gpio_probe - setup the range of GPIOs
    + *
    + * Create a character device for the range of GPIOs and have the minor be
    + * used to specify the GPIO.
    + */
    +static int __devinit simple_gpio_probe(struct platform_device *pdev)
    +{
    + int ret;
    + struct group_data *group_data;
    + struct resource *gpio_range = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    + int gpio, gpio_max = gpio_range->end - gpio_range->start + 1;
    +
    + stampit();
    +
    + group_data = kzalloc(sizeof(*group_data) + sizeof(struct gpio_data) * gpio_max, GFP_KERNEL);
    + if (!group_data)
    + return -ENOMEM;
    + group_data->gpio_range = gpio_range;
    + group_data->gpios = (void *)group_data + sizeof(*group_data);
    + platform_set_drvdata(pdev, group_data);
    +
    + ret = alloc_chrdev_region(&group_data->dev_node, gpio_range->start, gpio_max, "gpio");
    + if (ret) {
    + pr_devinit(KERN_ERR PFX "unable to get a char device\n");
    + kfree(group_data);
    + return ret;
    + }
    +
    + cdev_init(&group_data->cdev, &simple_gpio_fops);
    + group_data->cdev.owner = THIS_MODULE;
    +
    + ret = cdev_add(&group_data->cdev, group_data->dev_node, gpio_max);
    + if (ret) {
    + pr_devinit(KERN_ERR PFX "unable to register char device\n");
    + kfree(group_data);
    + unregister_chrdev_region(group_data->dev_node, gpio_max);
    + return ret;
    + }
    +
    + for (gpio = gpio_range->start; gpio < gpio_range->end; ++gpio)
    + device_create(simple_gpio_class, &pdev->dev, group_data->dev_node + gpio, "gpio%i", gpio);
    +
    + device_init_wakeup(&pdev->dev, 1);
    +
    + pr_devinit(KERN_INFO PFX "now handling %i GPIOs: %i - %i\n",
    + gpio_max, gpio_range->start, gpio_range->end);
    +
    + return 0;
    +}
    +
    +/**
    + * simple_gpio_remove - break down the range of GPIOs
    + *
    + * Release the character device and related pieces for this range of GPIOs.
    + */
    +static int __devexit simple_gpio_remove(struct platform_device *pdev)
    +{
    + struct group_data *group_data = platform_get_drvdata(pdev);
    + struct resource *gpio_range = group_data->gpio_range;
    + int gpio, gpio_max = gpio_range->end - gpio_range->start + 1;
    +
    + stampit();
    +
    + for (gpio = gpio_range->start; gpio < gpio_range->end; ++gpio)
    + device_destroy(simple_gpio_class, group_data->dev_node + gpio);
    +
    + cdev_del(&group_data->cdev);
    + unregister_chrdev_region(group_data->dev_node, gpio_max);
    +
    + kfree(group_data);
    +
    + return 0;
    +}
    +
    +struct platform_driver simple_gpio_device_driver = {
    + .probe = simple_gpio_probe,
    + .remove = __devexit_p(simple_gpio_remove),
    + .driver = {
    + .name = DRIVER_NAME,
    + }
    +};
    +
    +/**
    + * simple_gpio_init - setup our GPIO device driver
    + *
    + * Create one GPIO class for the entire driver
    + */
    +static int __init simple_gpio_init(void)
    +{
    + simple_gpio_class = class_create(THIS_MODULE, DRIVER_NAME);
    + if (IS_ERR(simple_gpio_class)) {
    + pr_init(KERN_ERR PFX "unable to create gpio class\n");
    + return PTR_ERR(simple_gpio_class);
    + }
    +
    + return platform_driver_register(&simple_gpio_device_driver);
    +}
    +module_init(simple_gpio_init);
    +
    +/**
    + * simple_gpio_exit - break down our GPIO device driver
    + */
    +static void __exit simple_gpio_exit(void)
    +{
    + class_destroy(simple_gpio_class);
    +
    + platform_driver_unregister(&simple_gpio_device_driver);
    +}
    +module_exit(simple_gpio_exit);
    +
    +MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
    +MODULE_DESCRIPTION("Simple GPIO character interface");
    +MODULE_LICENSE("GPL");
    --
    1.5.4.3

    \
     
     \ /
      Last update: 2008-03-26 11:07    [W:0.043 / U:0.604 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site