diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/core/subdev/gpio/base.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/gpio/base.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c new file mode 100644 index 000000000000..acf818c58bf0 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c @@ -0,0 +1,271 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/gpio.h> +#include <subdev/bios.h> +#include <subdev/bios/gpio.h> + +static int +nouveau_gpio_drive(struct nouveau_gpio *gpio, + int idx, int line, int dir, int out) +{ + return gpio->drive ? gpio->drive(gpio, line, dir, out) : -ENODEV; +} + +static int +nouveau_gpio_sense(struct nouveau_gpio *gpio, int idx, int line) +{ + return gpio->sense ? gpio->sense(gpio, line) : -ENODEV; +} + +static int +nouveau_gpio_find(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, + struct dcb_gpio_func *func) +{ + if (line == 0xff && tag == 0xff) + return -EINVAL; + + if (!dcb_gpio_parse(nouveau_bios(gpio), idx, tag, line, func)) + return 0; + + /* Apple iMac G4 NV18 */ + if (nv_device_match(nv_object(gpio), 0x0189, 0x10de, 0x0010)) { + if (tag == DCB_GPIO_TVDAC0) { + *func = (struct dcb_gpio_func) { + .func = DCB_GPIO_TVDAC0, + .line = 4, + .log[0] = 0, + .log[1] = 1, + }; + return 0; + } + } + + return -EINVAL; +} + +static int +nouveau_gpio_set(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, int state) +{ + struct dcb_gpio_func func; + int ret; + + ret = nouveau_gpio_find(gpio, idx, tag, line, &func); + if (ret == 0) { + int dir = !!(func.log[state] & 0x02); + int out = !!(func.log[state] & 0x01); + ret = nouveau_gpio_drive(gpio, idx, func.line, dir, out); + } + + return ret; +} + +static int +nouveau_gpio_get(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line) +{ + struct dcb_gpio_func func; + int ret; + + ret = nouveau_gpio_find(gpio, idx, tag, line, &func); + if (ret == 0) { + ret = nouveau_gpio_sense(gpio, idx, func.line); + if (ret >= 0) + ret = (ret == (func.log[1] & 1)); + } + + return ret; +} + +static int +nouveau_gpio_irq(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, bool on) +{ + struct dcb_gpio_func func; + int ret; + + ret = nouveau_gpio_find(gpio, idx, tag, line, &func); + if (ret == 0) { + if (idx == 0 && gpio->irq_enable) + gpio->irq_enable(gpio, func.line, on); + else + ret = -ENODEV; + } + + return ret; +} + +struct gpio_isr { + struct nouveau_gpio *gpio; + struct list_head head; + struct work_struct work; + int idx; + struct dcb_gpio_func func; + void (*handler)(void *, int); + void *data; + bool inhibit; +}; + +static void +nouveau_gpio_isr_bh(struct work_struct *work) +{ + struct gpio_isr *isr = container_of(work, struct gpio_isr, work); + struct nouveau_gpio *gpio = isr->gpio; + unsigned long flags; + int state; + + state = nouveau_gpio_get(gpio, isr->idx, isr->func.func, + isr->func.line); + if (state >= 0) + isr->handler(isr->data, state); + + spin_lock_irqsave(&gpio->lock, flags); + isr->inhibit = false; + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void +nouveau_gpio_isr_run(struct nouveau_gpio *gpio, int idx, u32 line_mask) +{ + struct gpio_isr *isr; + + if (idx != 0) + return; + + spin_lock(&gpio->lock); + list_for_each_entry(isr, &gpio->isr, head) { + if (line_mask & (1 << isr->func.line)) { + if (isr->inhibit) + continue; + isr->inhibit = true; + schedule_work(&isr->work); + } + } + spin_unlock(&gpio->lock); +} + +static int +nouveau_gpio_isr_add(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, + void (*handler)(void *, int), void *data) +{ + struct gpio_isr *isr; + unsigned long flags; + int ret; + + isr = kzalloc(sizeof(*isr), GFP_KERNEL); + if (!isr) + return -ENOMEM; + + ret = nouveau_gpio_find(gpio, idx, tag, line, &isr->func); + if (ret) { + kfree(isr); + return ret; + } + + INIT_WORK(&isr->work, nouveau_gpio_isr_bh); + isr->gpio = gpio; + isr->handler = handler; + isr->data = data; + isr->idx = idx; + + spin_lock_irqsave(&gpio->lock, flags); + list_add(&isr->head, &gpio->isr); + spin_unlock_irqrestore(&gpio->lock, flags); + return 0; +} + +static void +nouveau_gpio_isr_del(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, + void (*handler)(void *, int), void *data) +{ + struct gpio_isr *isr, *tmp; + struct dcb_gpio_func func; + unsigned long flags; + LIST_HEAD(tofree); + int ret; + + ret = nouveau_gpio_find(gpio, idx, tag, line, &func); + if (ret == 0) { + spin_lock_irqsave(&gpio->lock, flags); + list_for_each_entry_safe(isr, tmp, &gpio->isr, head) { + if (memcmp(&isr->func, &func, sizeof(func)) || + isr->idx != idx || + isr->handler != handler || isr->data != data) + continue; + list_move_tail(&isr->head, &tofree); + } + spin_unlock_irqrestore(&gpio->lock, flags); + + list_for_each_entry_safe(isr, tmp, &tofree, head) { + flush_work(&isr->work); + kfree(isr); + } + } +} + +int +nouveau_gpio_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, int length, void **pobject) +{ + struct nouveau_gpio *gpio; + int ret; + + ret = nouveau_subdev_create_(parent, engine, oclass, 0, "GPIO", "gpio", + length, pobject); + gpio = *pobject; + if (ret) + return ret; + + gpio->find = nouveau_gpio_find; + gpio->set = nouveau_gpio_set; + gpio->get = nouveau_gpio_get; + gpio->irq = nouveau_gpio_irq; + gpio->isr_run = nouveau_gpio_isr_run; + gpio->isr_add = nouveau_gpio_isr_add; + gpio->isr_del = nouveau_gpio_isr_del; + INIT_LIST_HEAD(&gpio->isr); + spin_lock_init(&gpio->lock); + return 0; +} + +static struct dmi_system_id gpio_reset_ids[] = { + { + .ident = "Apple Macbook 10,1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro10,1"), + } + }, + { } +}; + +int +nouveau_gpio_init(struct nouveau_gpio *gpio) +{ + int ret = nouveau_subdev_init(&gpio->base); + if (ret == 0 && gpio->reset) { + if (dmi_check_system(gpio_reset_ids)) + gpio->reset(gpio); + } + return ret; +} |