From: Alex Hornung Date: Sun, 4 Oct 2009 12:09:33 +0000 (+0100) Subject: gpio - gpio framework, initial commit X-Git-Tag: v2.7.1~542 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/a0e087c360c37449ce8304aeafa6cd59067202a8 gpio - gpio framework, initial commit * Initial commit of the new gpio framework,including a sample gpio consumer driver, gpio_led. While the framework is not directly based on OpenBSD's, some structures and other similarities have been taken over to ease porting of gpio drivers from OpenBSD. Partially-Obtained-from: OpenBSD --- diff --git a/sys/dev/misc/gpio/gpio.c b/sys/dev/misc/gpio/gpio.c new file mode 100644 index 0000000000..bb90915a82 --- /dev/null +++ b/sys/dev/misc/gpio/gpio.c @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2009 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Alex Hornung + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Copyright (c) 2008 Marc Balmer + * Copyright (c) 2004, 2006 Alexander Yurchenko + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* + * XXX: consumer_detach stuff. + * XXX: userland stuff. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_driver { + char *name; + struct devfs_bitmap unit_bitmap; + LIST_ENTRY(gpio_driver) link; +}; + +static LIST_HEAD(, gpio_consumer) gpio_conslist = LIST_HEAD_INITIALIZER(&gpio_conslist); +static LIST_HEAD(, gpio_driver) gpio_driverlist = LIST_HEAD_INITIALIZER(&gpio_driverlist); +DEVFS_DECLARE_CLONE_BITMAP(gpio); +static struct lock gpio_lock; + +void +gpio_consumer_register(struct gpio_consumer *gcp) +{ + lockmgr(&gpio_lock, LK_EXCLUSIVE); + LIST_INSERT_HEAD(&gpio_conslist, gcp, link); + lockmgr(&gpio_lock, LK_RELEASE); +} + +void +gpio_consumer_unregister(struct gpio_consumer *gcp) +{ + lockmgr(&gpio_lock, LK_EXCLUSIVE); + LIST_REMOVE(gcp, link); + lockmgr(&gpio_lock, LK_RELEASE); +} + +int +gpio_consumer_attach(const char *consumer, void *arg, struct gpio *gp, + int pin, u_int32_t mask) +{ + struct gpio_consumer *gcp; + int error = -1; + int locked = 0; + + /* Check if it is locked already. if not, we acquire the lock */ + if (!(lockstatus(&gpio_lock, curthread)) == LK_EXCLUSIVE) { + lockmgr(&gpio_lock, LK_EXCLUSIVE); + locked = 1; + } + + LIST_FOREACH(gcp, &gpio_conslist, link) { + if (strcmp(gcp->consumer_name, consumer) != 0) + continue; + + if (gcp->consumer_attach) + error = gcp->consumer_attach(gp, arg, pin, mask); + if (error) { + kprintf("gpio: Attach of consumer %s to gpio %s%d pin %d failed " + "(consumer error %d)\n", consumer, gp->driver_name, + gp->driver_unit, pin, error); + goto end; + } + + kprintf("gpio: Attached consumer %s to gpio %s%d pin %d\n", + consumer, gp->driver_name, gp->driver_unit, pin); + goto end; + } + + kprintf("gpio: Attach of consumer %s to gpio %s%d pin %d failed " + "(unknown consumer)\n", consumer, gp->driver_name, gp->driver_unit, pin); + +end: + /* If we acquired the lock, we also get rid of it */ + if (locked) + lockmgr(&gpio_lock, LK_RELEASE); + return error; +} + +int +gpio_consumer_detach(const char *consumer, struct gpio *gp, + int pin) +{ + struct gpio_consumer *gcp; + int error = -1; + int locked = 0; + + /* Check if it is locked already. if not, we acquire the lock */ + if (!(lockstatus(&gpio_lock, curthread)) == LK_EXCLUSIVE) { + lockmgr(&gpio_lock, LK_EXCLUSIVE); + locked = 1; + } + + LIST_FOREACH(gcp, &gpio_conslist, link) { + if (strcmp(gcp->consumer_name, consumer) != 0) + continue; + + if (gcp->consumer_detach) + error = gcp->consumer_detach(gp, NULL, pin); + if (error) { + kprintf("gpio: Detach of consumer %s from gpio %s%d pin %d failed " + "(consumer error %d)\n", consumer, gp->driver_name, + gp->driver_unit, pin, error); + goto end; + } + + kprintf("gpio: Detached consumer %s from gpio %s%d pin %d\n", + consumer, gp->driver_name, gp->driver_unit, pin); + goto end; + } + + kprintf("gpio: Detach of consumer %s from gpio %s%d pin %d failed " + "(unknown consumer)\n", consumer, gp->driver_name, gp->driver_unit, pin); + +end: + /* If we acquired the lock, we also get rid of it */ + if (locked) + lockmgr(&gpio_lock, LK_RELEASE); + return error; +} + +struct gpio_mapping * +gpio_map(struct gpio *gp, int *map, int offset, u_int32_t mask) +{ + struct gpio_mapping *gmp; + int npins, pin, i; + int locked = 0; + + npins = gpio_npins(mask); + if (npins > gp->npins) + return NULL; + if (npins == 0) + return NULL; + + /* Check if it is locked already. if not, we acquire the lock */ + if (!(lockstatus(&gpio_lock, curthread)) == LK_EXCLUSIVE) { + lockmgr(&gpio_lock, LK_EXCLUSIVE); + locked = 1; + } + + gmp = kmalloc(sizeof(struct gpio_mapping), M_TEMP, M_WAITOK); + gmp->gp = gp; + if (map) { + gmp->map = map; + gmp->map_alloced = 0; + } else { + gmp->map = kmalloc(sizeof(int) * npins, M_TEMP, M_WAITOK); + gmp->map_alloced = 1; + } + + for (npins = 0, i = 0; i < 32; i++) + if (mask & (1 << i)) { + pin = offset + i; + if (pin < 0 || pin >= gp->npins || + gp->pins[pin].pin_mapped || gp->pins[pin].pin_opened) { + if (map == NULL) + kfree(gmp->map, M_TEMP); + kfree(gmp, M_TEMP); + /* If we acquired the lock, we also get rid of it */ + if (locked) + lockmgr(&gpio_lock, LK_RELEASE); + return NULL; + } + gp->pins[pin].pin_mapped = 1; + gmp->map[npins++] = pin; + } + gmp->size = npins; + + /* If we acquired the lock, we also get rid of it */ + if (locked) + lockmgr(&gpio_lock, LK_RELEASE); + + return gmp; +} + +void +gpio_unmap(struct gpio_mapping *gmp) +{ + int pin, i; + int locked = 0; + + /* Check if it is locked already. if not, we acquire the lock */ + if (!(lockstatus(&gpio_lock, curthread)) == LK_EXCLUSIVE) { + lockmgr(&gpio_lock, LK_EXCLUSIVE); + locked = 1; + } + + for (i = 0; i < gmp->size; i++) { + pin = gmp->map[i]; + gmp->gp->pins[pin].pin_mapped = 0; + } + + if (gmp->map_alloced) + kfree(gmp->map, M_TEMP); + kfree(gmp, M_TEMP); + + /* If we acquired the lock, we also get rid of it */ + if (locked) + lockmgr(&gpio_lock, LK_RELEASE); +} + +int +gpio_npins(u_int32_t mask) +{ + int npins, i; + + for (npins = 0, i = 0; i < 32; i++) + if (mask & (1 << i)) + npins++; + + return (npins); +} + +int +gpio_pin_read(struct gpio *gp, struct gpio_mapping *map, int pin) +{ + return gp->pin_read(gp->arg, map->map[pin]); +} + +void +gpio_pin_write(struct gpio *gp, struct gpio_mapping *map, int pin, int data) +{ + return gp->pin_write(gp->arg, map->map[pin], data); +} + +void +gpio_pin_ctl(struct gpio *gp, struct gpio_mapping *map, int pin, int flags) +{ + return gp->pin_ctl(gp->arg, map->map[pin], flags); +} + +int +gpio_pin_caps(struct gpio *gp, struct gpio_mapping *map, int pin) +{ + return (gp->pins[map->map[pin]].pin_caps); +} + +static int +gpio_open(struct dev_open_args *ap) +{ + struct gpio *gp; + gpio_pin_t *pin; + cdev_t dev; + + dev = ap->a_head.a_dev; + gp = dev->si_drv1; + pin = dev->si_drv2; + + if (pin->pin_opened || pin->pin_mapped) + return EBUSY; + + pin->pin_opened = 1; + + return 0; +} + +static int +gpio_close(struct dev_close_args *ap) +{ + struct gpio *gp; + gpio_pin_t *pin; + cdev_t dev; + + dev = ap->a_head.a_dev; + gp = dev->si_drv1; + pin = dev->si_drv2; + + if (pin->pin_opened) + pin->pin_opened = 0; + + return 0; +} + +static int +gpio_write(struct dev_write_args *ap) +{ + struct gpio *gp; + gpio_pin_t *pin; + cdev_t dev; + int error; + int data = 0; + + dev = ap->a_head.a_dev; + gp = dev->si_drv1; + pin = dev->si_drv2; + + if (ap->a_uio->uio_resid > sizeof(int)) + return EINVAL; + + error = uiomove((void *)&data, ap->a_uio->uio_resid, ap->a_uio); + if (error) + return error; + + if (data != GPIO_PIN_LOW && data != GPIO_PIN_HIGH) + return EINVAL; + + gp->pin_write(gp->arg, pin->pin_num, data); + pin->pin_state = data; + + return 0; +} + +static int +gpio_read(struct dev_read_args *ap) +{ + struct gpio *gp; + gpio_pin_t *pin; + cdev_t dev; + int error; + int data = 0; + + dev = ap->a_head.a_dev; + gp = dev->si_drv1; + pin = dev->si_drv2; + + if (ap->a_uio->uio_resid < sizeof(char)) + return EINVAL; + + data = gp->pin_read(gp->arg, pin->pin_num); + + error = uiomove((void *)&data, + (ap->a_uio->uio_resid > sizeof(int))?(sizeof(int)):(ap->a_uio->uio_resid), + ap->a_uio); + + return error; +} + +static int +gpio_ioctl(struct dev_ioctl_args *ap) +{ + struct gpio_pin_set_args *gpsa; + struct gpio *gp; + gpio_pin_t *pin; + cdev_t dev; + int error = 0; + + dev = ap->a_head.a_dev; + gp = dev->si_drv1; + pin = dev->si_drv2; + + switch(ap->a_cmd) { + case GPIOPINSET: + gpsa = (struct gpio_pin_set_args *)ap->a_data; + if (pin->pin_opened || pin->pin_mapped) + return EBUSY; + + gpsa->caps = pin->pin_caps; + gpsa->flags = pin->pin_flags; + + if ((gpsa->flags & pin->pin_caps) != gpsa->flags) + return ENODEV; + + if (gpsa->flags > 0) { + gp->pin_ctl(gp->arg, pin->pin_num, gpsa->flags); + pin->pin_flags = gpsa->flags | GPIO_PIN_SET; + } + break; + + case GPIOPINUNSET: + gpsa = (struct gpio_pin_set_args *)ap->a_data; + error = EINVAL; + break; + + default: + return EINVAL; + } + return 0; +} + +static int +gpio_master_ioctl(struct dev_ioctl_args *ap) +{ + struct gpio_pin_set_args *gpsa; + struct gpio_info *gpi; + struct gpio_attach_args *gpaa; + struct gpio *gp; + cdev_t dev; + gpio_pin_t *pin; + int error = 0; + + dev = ap->a_head.a_dev; + gp = dev->si_drv1; + + switch(ap->a_cmd) { + case GPIOINFO: + gpi = (struct gpio_info *)ap->a_data; + gpi->npins = gp->npins; + if (gpi->pins != NULL) { + error = copyout(gp->pins, gpi->pins, + sizeof(struct gpio_pin)*gp->npins); + } + break; + + case GPIOATTACH: + gpaa = (struct gpio_attach_args *)ap->a_data; + error = gpio_consumer_attach(gpaa->consumer_name, + (gpaa->arg_type == GPIO_TYPE_INT)? + ((void *)gpaa->consumer_arg.lint): + (gpaa->consumer_arg.string), + gp, gpaa->pin_offset, gpaa->pin_mask); + break; + + case GPIODETACH: + gpaa = (struct gpio_attach_args *)ap->a_data; + error = gpio_consumer_detach(gpaa->consumer_name, gp, + gpaa->pin_offset); + break; + + case GPIOPINSET: + gpsa = (struct gpio_pin_set_args *)ap->a_data; + if (gpsa->pin < 0 || gpsa->pin >= gp->npins) + return EINVAL; + + pin = &gp->pins[gpsa->pin]; + + if (pin->pin_opened || pin->pin_mapped) + return EBUSY; + + gpsa->caps = pin->pin_caps; + gpsa->flags = pin->pin_flags; + + if ((gpsa->flags & pin->pin_caps) != gpsa->flags) + return ENODEV; + + if (gpsa->flags > 0) { + gp->pin_ctl(gp->arg, gpsa->pin, gpsa->flags); + pin->pin_flags = gpsa->flags | GPIO_PIN_SET; + } + break; + + case GPIOPINUNSET: + gpsa = (struct gpio_pin_set_args *)ap->a_data; + error = EINVAL; + break; + + default: + return EINVAL; + } + + return error; +} + +static struct dev_ops gpio_ops = { + { "gpio", 0, 0 }, + .d_open = gpio_open, + .d_close = gpio_close, + .d_write = gpio_write, + .d_read = gpio_read, + .d_ioctl = gpio_ioctl, +}; + +static struct dev_ops gpio_master_ops = { + { "gpio", 0, 0 }, + .d_ioctl = gpio_master_ioctl, +}; + +void +gpio_register(struct gpio *gp) +{ + struct gpio_driver *gpd; + int i, unit, master_unit = -1; + + KKASSERT(gp->npins > 0); + KKASSERT(gp->pins); + + lockmgr(&gpio_lock, LK_EXCLUSIVE); + LIST_FOREACH(gpd, &gpio_driverlist, link) { + if (strcmp(gpd->name, gp->driver_name) != 0) + continue; + + master_unit = devfs_clone_bitmap_get(&gpd->unit_bitmap, 0); + break; + } + if (master_unit == -1) { + gpd = kmalloc(sizeof(struct gpio_driver), + M_TEMP, M_WAITOK | M_ZERO); + gpd->name = kstrdup(gp->driver_name, M_TEMP); + devfs_clone_bitmap_init(&gpd->unit_bitmap); + master_unit = devfs_clone_bitmap_get(&gpd->unit_bitmap, 0); + LIST_INSERT_HEAD(&gpio_driverlist, gpd, link); + } + lockmgr(&gpio_lock, LK_RELEASE); + + gp->driver_unit = master_unit; + kprintf("gpio: GPIO driver %s%d registered, npins = %d\n", + gp->driver_name, master_unit, gp->npins); + + unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(gpio), 0); + gp->master_dev = make_dev(&gpio_master_ops, unit, UID_ROOT, GID_WHEEL, 0600, + "gpio/%s%d/master", gp->driver_name, master_unit); + gp->master_dev->si_drv1 = gp; + + for (i = 0; i < gp->npins; i++) { + unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(gpio), 0); + gp->pins[i].dev = make_dev(&gpio_ops, unit, UID_ROOT, GID_WHEEL, 0600, + "gpio/%s%d/%d", gp->driver_name, master_unit, gp->pins[i].pin_num); + gp->pins[i].dev->si_drv1 = gp; + gp->pins[i].dev->si_drv2 = &gp->pins[i]; + } +} + +void +gpio_unregister(struct gpio *gp) +{ + struct gpio_driver *gpd; + int i; + + KKASSERT(gp->npins > 0); + KKASSERT(gp->pins); + + for (i = 0; i < gp->npins; i++) { + devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(gpio), + minor(gp->pins[i].dev)); + destroy_dev(gp->pins[i].dev); + } + + destroy_dev(gp->master_dev); + + lockmgr(&gpio_lock, LK_EXCLUSIVE); + LIST_FOREACH(gpd, &gpio_driverlist, link) { + if (strcmp(gpd->name, gp->driver_name) != 0) + continue; + + devfs_clone_bitmap_put(&gpd->unit_bitmap, gp->driver_unit); + LIST_REMOVE(gpd, link); + break; + } + lockmgr(&gpio_lock, LK_RELEASE); + + kprintf("gpio: GPIO driver %s%d unregistered\n", + gp->driver_name, gp->driver_unit); +} + +static void +gpio_drvinit(void *unused) +{ + lockinit(&gpio_lock, "gpio_lock", 0, 0); + devfs_clone_bitmap_init(&DEVFS_CLONE_BITMAP(gpio)); +} + +SYSINIT(gpio, SI_SUB_PRE_DRIVERS, SI_ORDER_FIRST, gpio_drvinit, NULL); diff --git a/sys/dev/misc/gpio/gpio.h b/sys/dev/misc/gpio/gpio.h new file mode 100644 index 0000000000..b211a8f119 --- /dev/null +++ b/sys/dev/misc/gpio/gpio.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2009 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Alex Hornung + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * + * Copyright (c) 2008 Marc Balmer + * Copyright (c) 2004, 2006 Alexander Yurchenko + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* GPIO pin states */ +#define GPIO_PIN_LOW 0x00 /* low level (logical 0) */ +#define GPIO_PIN_HIGH 0x01 /* high level (logical 1) */ + +/* Max name length of a pin */ +#define GPIOPINMAXNAME 64 + +/* GPIO pin configuration flags */ +#define GPIO_PIN_INPUT 0x0001 /* input direction */ +#define GPIO_PIN_OUTPUT 0x0002 /* output direction */ +#define GPIO_PIN_INOUT 0x0004 /* bi-directional */ +#define GPIO_PIN_OPENDRAIN 0x0008 /* open-drain output */ +#define GPIO_PIN_PUSHPULL 0x0010 /* push-pull output */ +#define GPIO_PIN_TRISTATE 0x0020 /* output disabled */ +#define GPIO_PIN_PULLUP 0x0040 /* internal pull-up enabled */ +#define GPIO_PIN_PULLDOWN 0x0080 /* internal pull-down enabled */ +#define GPIO_PIN_INVIN 0x0100 /* invert input */ +#define GPIO_PIN_INVOUT 0x0200 /* invert output */ +#define GPIO_PIN_USER 0x0400 /* user != 0 can access */ +#define GPIO_PIN_SET 0x8000 /* set for securelevel access */ + +/* Consumer attach arg types */ +#define GPIO_TYPE_INT 0x01 + +typedef struct gpio_pin { + int pin_num; /* number */ + int pin_caps; /* capabilities */ + int pin_flags; /* current configuration */ + int pin_state; /* current state */ + int pin_mapped; /* is mapped */ + int pin_opened; /* is opened */ + cdev_t dev; +} gpio_pin_t; + +struct gpio { + const char *driver_name; + void *arg; + int (*pin_read)(void *, int); + void (*pin_write)(void *, int, int); + void (*pin_ctl)(void *, int, int); + gpio_pin_t *pins; + int npins; + + /* Private members */ + int driver_unit; + cdev_t master_dev; +}; + +struct gpio_consumer { + const char *consumer_name; + int (*consumer_attach)(struct gpio *, void *, int, u_int32_t); + int (*consumer_detach)(struct gpio *, void *, int); + LIST_ENTRY(gpio_consumer) link; +}; + +struct gpio_info { + int npins; + gpio_pin_t *pins; +}; + +struct gpio_attach_args { + char consumer_name[16]; + int pin_offset; + u_int32_t pin_mask; + int arg_type; + union { + char string[32]; + long lint; + } consumer_arg; +}; + +struct gpio_pin_set_args { + int pin; + int caps; + int flags; +}; + +struct gpio_mapping { + struct gpio *gp; + int *map; + int size; + + int map_alloced; +}; + +void gpio_consumer_register(struct gpio_consumer *gcp); +void gpio_consumer_unregister(struct gpio_consumer *gcp); +int gpio_consumer_attach(const char *consumer, void *arg, struct gpio *gp, + int pin, u_int32_t mask); +int gpio_consumer_detach(const char *consumer, struct gpio *gp, int pin); +struct gpio_mapping *gpio_map(struct gpio *gp, int *map, int offset, u_int32_t mask); +void gpio_unmap(struct gpio_mapping *gmp); + +int gpio_npins(u_int32_t mask); +int gpio_pin_read(struct gpio *gp, struct gpio_mapping *map, int pin); +void gpio_pin_write(struct gpio *gp, struct gpio_mapping *map, int pin, int data); +void gpio_pin_ctl(struct gpio *gp, struct gpio_mapping *map, int pin, int flags); +int gpio_pin_caps(struct gpio *gp, struct gpio_mapping *map, int pin); +void gpio_register(struct gpio *gp); +void gpio_unregister(struct gpio *gp); + +void led_switch(const char *name, int on_off); + +#define GPIOINFO _IOWR('G', 0, struct gpio_info) +#define GPIOPINSET _IOWR('G', 4, struct gpio_pin_set_args) +#define GPIOPINUNSET _IOWR('G', 5, struct gpio_pin_set_args) +#define GPIOATTACH _IOWR('G', 6, struct gpio_attach_args) +#define GPIODETACH _IOWR('G', 7, struct gpio_attach_args) diff --git a/sys/dev/misc/gpio/gpio_led.c b/sys/dev/misc/gpio/gpio_led.c new file mode 100644 index 0000000000..ad0bbc9369 --- /dev/null +++ b/sys/dev/misc/gpio/gpio_led.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2009 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Alex Hornung + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ledsc { + LIST_ENTRY(ledsc) list; + struct gpio *gp; + int pin; + cdev_t dev; + int unit; + int opened; + char *name; + struct gpio_mapping *gp_map; +}; + +DEVFS_DECLARE_CLONE_BITMAP(nled); +static struct lock led_lock; +static LIST_HEAD(, ledsc) led_list = LIST_HEAD_INITIALIZER(&led_list); +static MALLOC_DEFINE(M_LED, "LED", "LED driver"); + + +static int +led_open(struct dev_open_args *ap) +{ + struct ledsc *sc; + cdev_t dev; + + dev = ap->a_head.a_dev; + sc = dev->si_drv1; + + if (sc->opened) + return EBUSY; + + sc->opened = 1; + + return 0; +} + +static int +led_close(struct dev_close_args *ap) +{ + struct ledsc *sc; + cdev_t dev; + + dev = ap->a_head.a_dev; + sc = dev->si_drv1; + + if (sc->opened) + sc->opened = 0; + + return 0; +} + +static int +led_write(struct dev_write_args *ap) +{ + struct ledsc *sc; + cdev_t dev; + int error; + int data = 0; + int len; + + dev = ap->a_head.a_dev; + sc = dev->si_drv1; + + if (ap->a_uio->uio_resid > sizeof(int)) + return EINVAL; + + len = ap->a_uio->uio_resid; + + error = uiomove((void *)&data, ap->a_uio->uio_resid, ap->a_uio); + if (error) + return error; + + if (len > 1) + data = ((char *)&data)[0]; + + if (data >= '0') + data -= '0'; + + gpio_pin_write(sc->gp, sc->gp_map, 0, data); + + return 0; +} + +static int +led_read(struct dev_read_args *ap) +{ + struct ledsc *sc; + cdev_t dev; + int error; + int data = 0; + + dev = ap->a_head.a_dev; + sc = dev->si_drv1; + + if (ap->a_uio->uio_resid < sizeof(int)) + return EINVAL; + + data = gpio_pin_read(sc->gp, sc->gp_map, 0); + + error = uiomove((void *)&data, + (ap->a_uio->uio_resid > sizeof(int))?(sizeof(int)):(ap->a_uio->uio_resid), + ap->a_uio); + + return error; +} + +static int +led_ioctl(struct dev_ioctl_args *ap) +{ + /* XXX: set a name */ + return 0; +} + +static struct dev_ops nled_ops = { + { "gpio", 0, 0 }, + .d_open = led_open, + .d_close = led_close, + .d_write = led_write, + .d_read = led_read, + .d_ioctl = led_ioctl, +}; + + +static int +led_attach(struct gpio *gp, void *arg, int pin, u_int32_t mask) +{ + struct ledsc *sc; + + if (arg == NULL) + return 1; + + lockmgr(&led_lock, LK_EXCLUSIVE); + sc = kmalloc(sizeof(struct ledsc), M_LED, M_WAITOK); + + /* XXX: check for name collisions */ + sc->name = kstrdup((char *)arg, M_LED); + sc->pin = pin; + sc->gp = gp; + + sc->gp_map = gpio_map(gp, NULL, pin, 1); + if (sc->gp_map == NULL) { + return 2; + } + + sc->unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(nled), 0); + + LIST_INSERT_HEAD(&led_list, sc, list); + sc->dev = make_dev(&nled_ops, sc->unit, + UID_ROOT, GID_WHEEL, 0600, "led/%s", sc->name); + sc->dev->si_drv1 = sc; + lockmgr(&led_lock, LK_RELEASE); + + kprintf("gpio_led: Attached led '%s' to gpio %s, pin %d\n", + sc->name, sc->gp->driver_name, pin); + + return 0; +} + +static void +led_detach(struct gpio *gp, void *arg, int pin) +{ + /* XXX: implement */ +} + +void +led_switch(const char *name, int on_off) +{ + struct ledsc *sc; + + if (name == NULL) + return; + + lockmgr(&led_lock, LK_EXCLUSIVE); + LIST_FOREACH(sc, &led_list, list) { + if (strcmp(name, sc->name) != 0) + continue; + + gpio_pin_write(sc->gp, sc->gp_map, 0, on_off); + break; + } + + lockmgr(&led_lock, LK_RELEASE); +} + +struct gpio_consumer led_gpio_cons = { + .consumer_name = "led", + .consumer_attach = led_attach, + .consumer_detach = led_detach, +}; + +static void +led_drvinit(void *unused) +{ + lockinit(&led_lock, "led_lock", 0, 0); + devfs_clone_bitmap_init(&DEVFS_CLONE_BITMAP(nled)); + gpio_consumer_register(&led_gpio_cons); +} + +SYSINIT(leddev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, led_drvinit, NULL);