/** * @note Copyright (C) 2016 Philippe Gerum * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include struct rtdm_gpio_chan { int requested : 1, has_direction : 1, is_output : 1, is_interrupt : 1, want_timestamp : 1; }; static LIST_HEAD(rtdm_gpio_chips); static DEFINE_MUTEX(chip_lock); static int gpio_pin_interrupt(rtdm_irq_t *irqh) { struct rtdm_gpio_pin *pin; pin = rtdm_irq_get_arg(irqh, struct rtdm_gpio_pin); if (pin->monotonic_timestamp) pin->timestamp = rtdm_clock_read_monotonic(); else pin->timestamp = rtdm_clock_read(); rtdm_event_signal(&pin->event); return RTDM_IRQ_HANDLED; } static int request_gpio_irq(unsigned int gpio, struct rtdm_gpio_pin *pin, struct rtdm_gpio_chan *chan, int trigger) { int ret, irq_trigger, irq; if (trigger & ~GPIO_TRIGGER_MASK) return -EINVAL; if (!chan->requested) { ret = gpio_request(gpio, pin->name); if (ret) { if (ret != -EPROBE_DEFER) printk(XENO_ERR "can not request GPIO%d\n", gpio); return ret; } chan->requested = true; } ret = gpio_direction_input(gpio); if (ret) { printk(XENO_ERR "cannot set GPIO%d as input\n", gpio); goto fail; } chan->has_direction = true; gpio_export(gpio, true); rtdm_event_clear(&pin->event); /* * Attempt to hook the interrupt associated to that pin. We * might fail getting a valid IRQ number, in case the GPIO * chip did not define any mapping handler (->to_irq). If so, * just assume that either we have no IRQ indeed, or interrupt * handling may be open coded elsewhere. */ irq = gpio_to_irq(gpio); if (irq < 0) goto done; irq_trigger = 0; if (trigger & GPIO_TRIGGER_EDGE_RISING) irq_trigger |= IRQ_TYPE_EDGE_RISING; if (trigger & GPIO_TRIGGER_EDGE_FALLING) irq_trigger |= IRQ_TYPE_EDGE_FALLING; if (trigger & GPIO_TRIGGER_LEVEL_HIGH) irq_trigger |= IRQ_TYPE_LEVEL_HIGH; if (trigger & GPIO_TRIGGER_LEVEL_LOW) irq_trigger |= IRQ_TYPE_LEVEL_LOW; if (irq_trigger) irq_set_irq_type(irq, irq_trigger); ret = rtdm_irq_request(&pin->irqh, irq, gpio_pin_interrupt, 0, pin->name, pin); if (ret) { printk(XENO_ERR "cannot request GPIO%d interrupt\n", gpio); goto fail; } rtdm_irq_enable(&pin->irqh); done: chan->is_interrupt = true; return 0; fail: gpio_free(gpio); chan->requested = false; return ret; } static void release_gpio_irq(unsigned int gpio, struct rtdm_gpio_pin *pin, struct rtdm_gpio_chan *chan) { if (chan->is_interrupt) { rtdm_irq_free(&pin->irqh); chan->is_interrupt = false; } gpio_free(gpio); chan->requested = false; } static int gpio_pin_ioctl_nrt(struct rtdm_fd *fd, unsigned int request, void *arg) { struct rtdm_gpio_chan *chan = rtdm_fd_to_private(fd); struct rtdm_device *dev = rtdm_fd_device(fd); unsigned int gpio = rtdm_fd_minor(fd); int ret = 0, val, trigger; struct rtdm_gpio_pin *pin; pin = container_of(dev, struct rtdm_gpio_pin, dev); switch (request) { case GPIO_RTIOC_DIR_OUT: ret = rtdm_safe_copy_from_user(fd, &val, arg, sizeof(val)); if (ret) return ret; ret = gpio_direction_output(gpio, val); if (ret == 0) { chan->has_direction = true; chan->is_output = true; } break; case GPIO_RTIOC_DIR_IN: ret = gpio_direction_input(gpio); if (ret == 0) chan->has_direction = true; break; case GPIO_RTIOC_IRQEN: if (chan->is_interrupt) { return -EBUSY; } ret = rtdm_safe_copy_from_user(fd, &trigger, arg, sizeof(trigger)); if (ret) return ret; ret = request_gpio_irq(gpio, pin, chan, trigger); break; case GPIO_RTIOC_IRQDIS: if (chan->is_interrupt) { release_gpio_irq(gpio, pin, chan); chan->requested = false; chan->is_interrupt = false; } break; case GPIO_RTIOC_REQS: ret = gpio_request(gpio, pin->name); if (ret) return ret; else chan->requested = true; break; case GPIO_RTIOC_RELS: gpio_free(gpio); chan->requested = false; break; case GPIO_RTIOC_TS_MONO: case GPIO_RTIOC_TS_REAL: ret = rtdm_safe_copy_from_user(fd, &val, arg, sizeof(val)); if (ret) return ret; chan->want_timestamp = !!val; pin->monotonic_timestamp = request == GPIO_RTIOC_TS_MONO; break; default: return -EINVAL; } return ret; } static ssize_t gpio_pin_read_rt(struct rtdm_fd *fd, void __user *buf, size_t len) { struct rtdm_gpio_chan *chan = rtdm_fd_to_private(fd); struct rtdm_device *dev = rtdm_fd_device(fd); struct rtdm_gpio_readout rdo; struct rtdm_gpio_pin *pin; int ret; if (!chan->has_direction) return -EAGAIN; if (chan->is_output) return -EINVAL; pin = container_of(dev, struct rtdm_gpio_pin, dev); if (chan->want_timestamp) { if (len < sizeof(rdo)) return -EINVAL; if (!(fd->oflags & O_NONBLOCK)) { ret = rtdm_event_wait(&pin->event); if (ret) return ret; rdo.timestamp = pin->timestamp; } else if (pin->monotonic_timestamp) { rdo.timestamp = rtdm_clock_read_monotonic(); } else { rdo.timestamp = rtdm_clock_read(); } len = sizeof(rdo); rdo.value = gpiod_get_raw_value(pin->desc); ret = rtdm_safe_copy_to_user(fd, buf, &rdo, len); } else { if (len < sizeof(rdo.value)) return -EINVAL; if (!(fd->oflags & O_NONBLOCK)) { ret = rtdm_event_wait(&pin->event); if (ret) return ret; } len = sizeof(rdo.value); rdo.value = gpiod_get_raw_value(pin->desc); ret = rtdm_safe_copy_to_user(fd, buf, &rdo.value, len); } return ret ?: len; } static ssize_t gpio_pin_write_rt(struct rtdm_fd *fd, const void __user *buf, size_t len) { struct rtdm_gpio_chan *chan = rtdm_fd_to_private(fd); struct rtdm_device *dev = rtdm_fd_device(fd); struct rtdm_gpio_pin *pin; int value, ret; if (len < sizeof(value)) return -EINVAL; if (!chan->has_direction) return -EAGAIN; if (!chan->is_output) return -EINVAL; ret = rtdm_safe_copy_from_user(fd, &value, buf, sizeof(value)); if (ret) return ret; pin = container_of(dev, struct rtdm_gpio_pin, dev); gpiod_set_raw_value(pin->desc, value); return sizeof(value); } static int gpio_pin_select(struct rtdm_fd *fd, struct xnselector *selector, unsigned int type, unsigned int index) { struct rtdm_gpio_chan *chan = rtdm_fd_to_private(fd); struct rtdm_device *dev = rtdm_fd_device(fd); struct rtdm_gpio_pin *pin; if (!chan->has_direction) return -EAGAIN; if (chan->is_output) return -EINVAL; pin = container_of(dev, struct rtdm_gpio_pin, dev); return rtdm_event_select(&pin->event, selector, type, index); } int gpio_pin_open(struct rtdm_fd *fd, int oflags) { struct rtdm_gpio_chan *chan = rtdm_fd_to_private(fd); struct rtdm_device *dev = rtdm_fd_device(fd); unsigned int gpio = rtdm_fd_minor(fd); int ret = 0; struct rtdm_gpio_pin *pin; pin = container_of(dev, struct rtdm_gpio_pin, dev); ret = gpio_request(gpio, pin->name); if (ret) { printk(XENO_ERR "failed to request pin %d : %d\n", gpio, ret); return ret; } else { chan->requested = true; } return 0; } static void gpio_pin_close(struct rtdm_fd *fd) { struct rtdm_gpio_chan *chan = rtdm_fd_to_private(fd); struct rtdm_device *dev = rtdm_fd_device(fd); unsigned int gpio = rtdm_fd_minor(fd); struct rtdm_gpio_pin *pin; if (chan->requested) { pin = container_of(dev, struct rtdm_gpio_pin, dev); release_gpio_irq(gpio, pin, chan); } } static void delete_pin_devices(struct rtdm_gpio_chip *rgc) { struct rtdm_gpio_pin *pin; struct rtdm_device *dev; int offset; for (offset = 0; offset < rgc->gc->ngpio; offset++) { pin = rgc->pins + offset; dev = &pin->dev; rtdm_dev_unregister(dev); rtdm_event_destroy(&pin->event); kfree(dev->label); kfree(pin->name); } } static int create_pin_devices(struct rtdm_gpio_chip *rgc) { struct gpio_chip *gc = rgc->gc; struct rtdm_gpio_pin *pin; struct rtdm_device *dev; int offset, ret, gpio; for (offset = 0; offset < gc->ngpio; offset++) { ret = -ENOMEM; gpio = gc->base + offset; pin = rgc->pins + offset; pin->name = kasprintf(GFP_KERNEL, "gpio%d", gpio); if (pin->name == NULL) goto fail_name; pin->desc = gpio_to_desc(gpio); if (pin->desc == NULL) { ret = -ENODEV; goto fail_desc; } dev = &pin->dev; dev->driver = &rgc->driver; dev->label = kasprintf(GFP_KERNEL, "%s/gpio%%d", gc->label); if (dev->label == NULL) goto fail_label; dev->minor = gpio; dev->device_data = rgc; ret = rtdm_dev_register(dev); if (ret) goto fail_register; rtdm_event_init(&pin->event, 0); } return 0; fail_register: kfree(dev->label); fail_desc: fail_label: kfree(pin->name); fail_name: delete_pin_devices(rgc); return ret; } static char *gpio_pin_devnode(struct device *dev, umode_t *mode) { return kasprintf(GFP_KERNEL, "rtdm/%s/%s", dev->class->name, dev_name(dev)); } int rtdm_gpiochip_add(struct rtdm_gpio_chip *rgc, struct gpio_chip *gc, int gpio_subclass) { int ret; rgc->devclass = class_create(gc->owner, gc->label); if (IS_ERR(rgc->devclass)) { printk(XENO_ERR "cannot create sysfs class\n"); return PTR_ERR(rgc->devclass); } rgc->devclass->devnode = gpio_pin_devnode; rgc->driver.profile_info = (struct rtdm_profile_info) RTDM_PROFILE_INFO(rtdm_gpio_chip, RTDM_CLASS_GPIO, gpio_subclass, 0); rgc->driver.device_flags = RTDM_NAMED_DEVICE|RTDM_FIXED_MINOR; rgc->driver.base_minor = gc->base; rgc->driver.device_count = gc->ngpio; rgc->driver.context_size = sizeof(struct rtdm_gpio_chan); rgc->driver.ops = (struct rtdm_fd_ops){ .open = gpio_pin_open, .close = gpio_pin_close, .ioctl_nrt = gpio_pin_ioctl_nrt, .read_rt = gpio_pin_read_rt, .write_rt = gpio_pin_write_rt, .select = gpio_pin_select, }; rtdm_drv_set_sysclass(&rgc->driver, rgc->devclass); rgc->gc = gc; rtdm_lock_init(&rgc->lock); ret = create_pin_devices(rgc); if (ret) class_destroy(rgc->devclass); return ret; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_add); struct rtdm_gpio_chip * rtdm_gpiochip_alloc(struct gpio_chip *gc, int gpio_subclass) { struct rtdm_gpio_chip *rgc; size_t asize; int ret; if (gc->ngpio == 0) return ERR_PTR(-EINVAL); asize = sizeof(*rgc) + gc->ngpio * sizeof(struct rtdm_gpio_pin); rgc = kzalloc(asize, GFP_KERNEL); if (rgc == NULL) return ERR_PTR(-ENOMEM); ret = rtdm_gpiochip_add(rgc, gc, gpio_subclass); if (ret) { kfree(rgc); return ERR_PTR(ret); } mutex_lock(&chip_lock); list_add(&rgc->next, &rtdm_gpio_chips); mutex_unlock(&chip_lock); return rgc; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_alloc); void rtdm_gpiochip_remove(struct rtdm_gpio_chip *rgc) { mutex_lock(&chip_lock); list_del(&rgc->next); mutex_unlock(&chip_lock); delete_pin_devices(rgc); class_destroy(rgc->devclass); } EXPORT_SYMBOL_GPL(rtdm_gpiochip_remove); int rtdm_gpiochip_post_event(struct rtdm_gpio_chip *rgc, unsigned int offset) { struct rtdm_gpio_pin *pin; if (offset >= rgc->gc->ngpio) return -EINVAL; pin = rgc->pins + offset; if (pin->monotonic_timestamp) pin->timestamp = rtdm_clock_read_monotonic(); else pin->timestamp = rtdm_clock_read(); rtdm_event_signal(&pin->event); return 0; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_post_event); static int gpiochip_match_name(struct gpio_chip *chip, void *data) { const char *name = data; return !strcmp(chip->label, name); } static struct gpio_chip *find_chip_by_name(const char *name) { return gpiochip_find((void *)name, gpiochip_match_name); } int rtdm_gpiochip_add_by_name(struct rtdm_gpio_chip *rgc, const char *label, int gpio_subclass) { struct gpio_chip *gc = find_chip_by_name(label); if (gc == NULL) return -EPROBE_DEFER; return rtdm_gpiochip_add(rgc, gc, gpio_subclass); } EXPORT_SYMBOL_GPL(rtdm_gpiochip_add_by_name); int rtdm_gpiochip_find(struct device_node *from, const char *label, int type) { struct rtdm_gpio_chip *rgc; struct gpio_chip *chip; int ret = -ENODEV; if (!rtdm_available()) return -ENOSYS; chip = find_chip_by_name(label); if (chip == NULL) return ret; ret = 0; rgc = rtdm_gpiochip_alloc(chip, type); if (IS_ERR(rgc)) ret = PTR_ERR(rgc); return ret; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_find); int rtdm_gpiochip_array_find(struct device_node *from, const char *label[], int nentries, int type) { int ret = -ENODEV, _ret, n; for (n = 0; n < nentries; n++) { _ret = rtdm_gpiochip_find(from, label[n], type); if (_ret) { if (_ret != -ENODEV) return _ret; } else ret = 0; } return ret; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_array_find); #ifdef CONFIG_OF #include struct gpiochip_holder { struct gpio_chip *chip; struct list_head next; }; struct gpiochip_match_data { struct device *parent; struct list_head list; }; static int match_gpio_chip(struct gpio_chip *gc, void *data) { struct gpiochip_match_data *d = data; struct gpiochip_holder *h; if (cobalt_gpiochip_dev(gc) == d->parent) { h = kmalloc(sizeof(*h), GFP_KERNEL); if (h) { h->chip = gc; list_add(&h->next, &d->list); } } /* * Iterate over all existing GPIO chips, we may have several * hosted by the same pin controller mapping different ranges. */ return 0; } int rtdm_gpiochip_scan_of(struct device_node *from, const char *compat, int type) { struct gpiochip_match_data match; struct gpiochip_holder *h, *n; struct device_node *np = from; struct platform_device *pdev; struct rtdm_gpio_chip *rgc; int ret = -ENODEV, _ret; if (!rtdm_available()) return -ENOSYS; for (;;) { np = of_find_compatible_node(np, NULL, compat); if (np == NULL) break; pdev = of_find_device_by_node(np); of_node_put(np); if (pdev == NULL) break; match.parent = &pdev->dev; INIT_LIST_HEAD(&match.list); gpiochip_find(&match, match_gpio_chip); if (!list_empty(&match.list)) { ret = 0; list_for_each_entry_safe(h, n, &match.list, next) { list_del(&h->next); _ret = 0; rgc = rtdm_gpiochip_alloc(h->chip, type); if (IS_ERR(rgc)) _ret = PTR_ERR(rgc); kfree(h); if (_ret && !ret) ret = _ret; } if (ret) break; } } return ret; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_scan_of); int rtdm_gpiochip_scan_array_of(struct device_node *from, const char *compat[], int nentries, int type) { int ret = -ENODEV, _ret, n; for (n = 0; n < nentries; n++) { _ret = rtdm_gpiochip_scan_of(from, compat[n], type); if (_ret) { if (_ret != -ENODEV) return _ret; } else ret = 0; } return ret; } EXPORT_SYMBOL_GPL(rtdm_gpiochip_scan_array_of); #endif /* CONFIG_OF */ void rtdm_gpiochip_remove_by_type(int type) { struct rtdm_gpio_chip *rgc, *n; mutex_lock(&chip_lock); list_for_each_entry_safe(rgc, n, &rtdm_gpio_chips, next) { if (rgc->driver.profile_info.subclass_id == type) { mutex_unlock(&chip_lock); rtdm_gpiochip_remove(rgc); kfree(rgc); mutex_lock(&chip_lock); } } mutex_unlock(&chip_lock); } EXPORT_SYMBOL_GPL(rtdm_gpiochip_remove_by_type);