]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/gpu/drm/nouveau/nv50_gpio.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / gpu / drm / nouveau / nv50_gpio.c
index b2fab2bf3d6116f881d4900e30da61a4fd72dc93..6b149c0cc06d38435deb267afc5eac5e7248a2de 100644 (file)
 #include "nouveau_drv.h"
 #include "nouveau_hw.h"
 
+#include "nv50_display.h"
+
+static void nv50_gpio_isr(struct drm_device *dev);
+static void nv50_gpio_isr_bh(struct work_struct *work);
+
+struct nv50_gpio_priv {
+       struct list_head handlers;
+       spinlock_t lock;
+};
+
+struct nv50_gpio_handler {
+       struct drm_device *dev;
+       struct list_head head;
+       struct work_struct work;
+       bool inhibit;
+
+       struct dcb_gpio_entry *gpio;
+
+       void (*handler)(void *data, int state);
+       void *data;
+};
+
 static int
 nv50_gpio_location(struct dcb_gpio_entry *gpio, uint32_t *reg, uint32_t *shift)
 {
@@ -75,29 +97,123 @@ nv50_gpio_set(struct drm_device *dev, enum dcb_gpio_tag tag, int state)
        return 0;
 }
 
+int
+nv50_gpio_irq_register(struct drm_device *dev, enum dcb_gpio_tag tag,
+                      void (*handler)(void *, int), void *data)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+       struct nv50_gpio_priv *priv = pgpio->priv;
+       struct nv50_gpio_handler *gpioh;
+       struct dcb_gpio_entry *gpio;
+       unsigned long flags;
+
+       gpio = nouveau_bios_gpio_entry(dev, tag);
+       if (!gpio)
+               return -ENOENT;
+
+       gpioh = kzalloc(sizeof(*gpioh), GFP_KERNEL);
+       if (!gpioh)
+               return -ENOMEM;
+
+       INIT_WORK(&gpioh->work, nv50_gpio_isr_bh);
+       gpioh->dev  = dev;
+       gpioh->gpio = gpio;
+       gpioh->handler = handler;
+       gpioh->data = data;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       list_add(&gpioh->head, &priv->handlers);
+       spin_unlock_irqrestore(&priv->lock, flags);
+       return 0;
+}
+
 void
-nv50_gpio_irq_enable(struct drm_device *dev, enum dcb_gpio_tag tag, bool on)
+nv50_gpio_irq_unregister(struct drm_device *dev, enum dcb_gpio_tag tag,
+                        void (*handler)(void *, int), void *data)
 {
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+       struct nv50_gpio_priv *priv = pgpio->priv;
+       struct nv50_gpio_handler *gpioh, *tmp;
        struct dcb_gpio_entry *gpio;
-       u32 reg, mask;
+       unsigned long flags;
 
        gpio = nouveau_bios_gpio_entry(dev, tag);
-       if (!gpio) {
-               NV_ERROR(dev, "gpio tag 0x%02x not found\n", tag);
+       if (!gpio)
                return;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       list_for_each_entry_safe(gpioh, tmp, &priv->handlers, head) {
+               if (gpioh->gpio != gpio ||
+                   gpioh->handler != handler ||
+                   gpioh->data != data)
+                       continue;
+               list_del(&gpioh->head);
+               kfree(gpioh);
        }
+       spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+bool
+nv50_gpio_irq_enable(struct drm_device *dev, enum dcb_gpio_tag tag, bool on)
+{
+       struct dcb_gpio_entry *gpio;
+       u32 reg, mask;
+
+       gpio = nouveau_bios_gpio_entry(dev, tag);
+       if (!gpio)
+               return false;
 
        reg  = gpio->line < 16 ? 0xe050 : 0xe070;
        mask = 0x00010001 << (gpio->line & 0xf);
 
        nv_wr32(dev, reg + 4, mask);
-       nv_mask(dev, reg + 0, mask, on ? mask : 0);
+       reg = nv_mask(dev, reg + 0, mask, on ? mask : 0);
+       return (reg & mask) == mask;
+}
+
+static int
+nv50_gpio_create(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+       struct nv50_gpio_priv *priv;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&priv->handlers);
+       spin_lock_init(&priv->lock);
+       pgpio->priv = priv;
+       return 0;
+}
+
+static void
+nv50_gpio_destroy(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+
+       kfree(pgpio->priv);
+       pgpio->priv = NULL;
 }
 
 int
 nv50_gpio_init(struct drm_device *dev)
 {
        struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+       struct nv50_gpio_priv *priv;
+       int ret;
+
+       if (!pgpio->priv) {
+               ret = nv50_gpio_create(dev);
+               if (ret)
+                       return ret;
+       }
+       priv = pgpio->priv;
 
        /* disable, and ack any pending gpio interrupts */
        nv_wr32(dev, 0xe050, 0x00000000);
@@ -107,5 +223,77 @@ nv50_gpio_init(struct drm_device *dev)
                nv_wr32(dev, 0xe074, 0xffffffff);
        }
 
+       nouveau_irq_register(dev, 21, nv50_gpio_isr);
        return 0;
 }
+
+void
+nv50_gpio_fini(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+
+       nv_wr32(dev, 0xe050, 0x00000000);
+       if (dev_priv->chipset >= 0x90)
+               nv_wr32(dev, 0xe070, 0x00000000);
+       nouveau_irq_unregister(dev, 21);
+
+       nv50_gpio_destroy(dev);
+}
+
+static void
+nv50_gpio_isr_bh(struct work_struct *work)
+{
+       struct nv50_gpio_handler *gpioh =
+               container_of(work, struct nv50_gpio_handler, work);
+       struct drm_nouveau_private *dev_priv = gpioh->dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+       struct nv50_gpio_priv *priv = pgpio->priv;
+       unsigned long flags;
+       int state;
+
+       state = pgpio->get(gpioh->dev, gpioh->gpio->tag);
+       if (state < 0)
+               return;
+
+       gpioh->handler(gpioh->data, state);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       gpioh->inhibit = false;
+       spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void
+nv50_gpio_isr(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
+       struct nv50_gpio_priv *priv = pgpio->priv;
+       struct nv50_gpio_handler *gpioh;
+       u32 intr0, intr1 = 0;
+       u32 hi, lo, ch;
+
+       intr0 = nv_rd32(dev, 0xe054) & nv_rd32(dev, 0xe050);
+       if (dev_priv->chipset >= 0x90)
+               intr1 = nv_rd32(dev, 0xe074) & nv_rd32(dev, 0xe070);
+
+       hi = (intr0 & 0x0000ffff) | (intr1 << 16);
+       lo = (intr0 >> 16) | (intr1 & 0xffff0000);
+       ch = hi | lo;
+
+       nv_wr32(dev, 0xe054, intr0);
+       if (dev_priv->chipset >= 0x90)
+               nv_wr32(dev, 0xe074, intr1);
+
+       spin_lock(&priv->lock);
+       list_for_each_entry(gpioh, &priv->handlers, head) {
+               if (!(ch & (1 << gpioh->gpio->line)))
+                       continue;
+
+               if (gpioh->inhibit)
+                       continue;
+               gpioh->inhibit = true;
+
+               queue_work(dev_priv->wq, &gpioh->work);
+       }
+       spin_unlock(&priv->lock);
+}