drm/nv50: rework PGPIO IRQ handling and hotplug detection
authorBen Skeggs <bskeggs@redhat.com>
Thu, 11 Nov 2010 06:14:56 +0000 (16:14 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Fri, 3 Dec 2010 05:11:45 +0000 (15:11 +1000)
Allows callers to install their own handlers for when a GPIO line
changes state (such as for hotplug detect).

This also fixes a bug where we weren't acknowledging the GPIO IRQ
until after the bottom half had run, causing a severe IRQ storm
in some cases.

Reviewed-by: Francisco Jerez <currojerez@riseup.net>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nouveau_connector.c
drivers/gpu/drm/nouveau/nouveau_dp.c
drivers/gpu/drm/nouveau/nouveau_drv.h
drivers/gpu/drm/nouveau/nouveau_state.c
drivers/gpu/drm/nouveau/nv50_display.c
drivers/gpu/drm/nouveau/nv50_display.h
drivers/gpu/drm/nouveau/nv50_gpio.c

index 52c356e..a21e000 100644 (file)
@@ -37,6 +37,8 @@
 #include "nouveau_connector.h"
 #include "nouveau_hw.h"
 
+static void nouveau_connector_hotplug(void *, int);
+
 static struct nouveau_encoder *
 find_encoder_by_type(struct drm_connector *connector, int type)
 {
@@ -94,22 +96,30 @@ nouveau_connector_bpp(struct drm_connector *connector)
 }
 
 static void
-nouveau_connector_destroy(struct drm_connector *drm_connector)
+nouveau_connector_destroy(struct drm_connector *connector)
 {
-       struct nouveau_connector *nv_connector =
-               nouveau_connector(drm_connector);
+       struct nouveau_connector *nv_connector = nouveau_connector(connector);
+       struct drm_nouveau_private *dev_priv;
+       struct nouveau_gpio_engine *pgpio;
        struct drm_device *dev;
 
        if (!nv_connector)
                return;
 
        dev = nv_connector->base.dev;
+       dev_priv = dev->dev_private;
        NV_DEBUG_KMS(dev, "\n");
 
+       pgpio = &dev_priv->engine.gpio;
+       if (pgpio->irq_unregister) {
+               pgpio->irq_unregister(dev, nv_connector->dcb->gpio_tag,
+                                     nouveau_connector_hotplug, connector);
+       }
+
        kfree(nv_connector->edid);
-       drm_sysfs_connector_remove(drm_connector);
-       drm_connector_cleanup(drm_connector);
-       kfree(drm_connector);
+       drm_sysfs_connector_remove(connector);
+       drm_connector_cleanup(connector);
+       kfree(connector);
 }
 
 static struct nouveau_i2c_chan *
@@ -760,6 +770,7 @@ nouveau_connector_create(struct drm_device *dev, int index)
 {
        const struct drm_connector_funcs *funcs = &nouveau_connector_funcs;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio;
        struct nouveau_connector *nv_connector = NULL;
        struct dcb_connector_table_entry *dcb = NULL;
        struct drm_connector *connector;
@@ -876,6 +887,11 @@ nouveau_connector_create(struct drm_device *dev, int index)
                break;
        }
 
+       if (pgpio->irq_register) {
+               pgpio->irq_register(dev, nv_connector->dcb->gpio_tag,
+                                   nouveau_connector_hotplug, connector);
+       }
+
        drm_sysfs_connector_add(connector);
        dcb->drm = connector;
        return dcb->drm;
@@ -886,3 +902,29 @@ fail:
        return ERR_PTR(ret);
 
 }
+
+static void
+nouveau_connector_hotplug(void *data, int plugged)
+{
+       struct drm_connector *connector = data;
+       struct drm_device *dev = connector->dev;
+
+       NV_INFO(dev, "%splugged %s\n", plugged ? "" : "un",
+               drm_get_connector_name(connector));
+
+       if (connector->encoder && connector->encoder->crtc &&
+           connector->encoder->crtc->enabled) {
+               struct nouveau_encoder *nv_encoder = nouveau_encoder(connector->encoder);
+               struct drm_encoder_helper_funcs *helper =
+                       connector->encoder->helper_private;
+
+               if (nv_encoder->dcb->type == OUTPUT_DP) {
+                       if (plugged)
+                               helper->dpms(connector->encoder, DRM_MODE_DPMS_ON);
+                       else
+                               helper->dpms(connector->encoder, DRM_MODE_DPMS_OFF);
+               }
+       }
+
+       drm_helper_hpd_irq_event(dev);
+}
index 4562f30..38d5995 100644 (file)
@@ -279,7 +279,7 @@ nouveau_dp_link_train(struct drm_encoder *encoder)
        struct bit_displayport_encoder_table *dpe;
        int dpe_headerlen;
        uint8_t config[4], status[3];
-       bool cr_done, cr_max_vs, eq_done;
+       bool cr_done, cr_max_vs, eq_done, hpd_state;
        int ret = 0, i, tries, voltage;
 
        NV_DEBUG_KMS(dev, "link training!!\n");
@@ -297,7 +297,7 @@ nouveau_dp_link_train(struct drm_encoder *encoder)
        /* disable hotplug detect, this flips around on some panels during
         * link training.
         */
-       pgpio->irq_enable(dev, nv_connector->dcb->gpio_tag, false);
+       hpd_state = pgpio->irq_enable(dev, nv_connector->dcb->gpio_tag, false);
 
        if (dpe->script0) {
                NV_DEBUG_KMS(dev, "SOR-%d: running DP script 0\n", nv_encoder->or);
@@ -439,7 +439,7 @@ stop:
        }
 
        /* re-enable hotplug detect */
-       pgpio->irq_enable(dev, nv_connector->dcb->gpio_tag, true);
+       pgpio->irq_enable(dev, nv_connector->dcb->gpio_tag, hpd_state);
 
        return eq_done;
 }
index b19ef7f..912c9f7 100644 (file)
@@ -376,13 +376,19 @@ struct nouveau_display_engine {
 };
 
 struct nouveau_gpio_engine {
+       void *priv;
+
        int  (*init)(struct drm_device *);
        void (*takedown)(struct drm_device *);
 
        int  (*get)(struct drm_device *, enum dcb_gpio_tag);
        int  (*set)(struct drm_device *, enum dcb_gpio_tag, int state);
 
-       void (*irq_enable)(struct drm_device *, enum dcb_gpio_tag, bool on);
+       int  (*irq_register)(struct drm_device *, enum dcb_gpio_tag,
+                            void (*)(void *, int), void *);
+       void (*irq_unregister)(struct drm_device *, enum dcb_gpio_tag,
+                              void (*)(void *, int), void *);
+       bool (*irq_enable)(struct drm_device *, enum dcb_gpio_tag, bool on);
 };
 
 struct nouveau_pm_voltage_level {
@@ -619,13 +625,6 @@ struct drm_nouveau_private {
        bool msi_enabled;
        struct workqueue_struct *wq;
        struct work_struct irq_work;
-       struct work_struct hpd_work;
-
-       struct {
-               spinlock_t lock;
-               uint32_t hpd0_bits;
-               uint32_t hpd1_bits;
-       } hpd_state;
 
        struct list_head vbl_waiting;
 
@@ -1366,7 +1365,11 @@ int nv50_gpio_init(struct drm_device *dev);
 void nv50_gpio_fini(struct drm_device *dev);
 int nv50_gpio_get(struct drm_device *dev, enum dcb_gpio_tag tag);
 int nv50_gpio_set(struct drm_device *dev, enum dcb_gpio_tag tag, int state);
-void nv50_gpio_irq_enable(struct drm_device *, enum dcb_gpio_tag, bool on);
+int  nv50_gpio_irq_register(struct drm_device *, enum dcb_gpio_tag,
+                           void (*)(void *, int), void *);
+void nv50_gpio_irq_unregister(struct drm_device *, enum dcb_gpio_tag,
+                             void (*)(void *, int), void *);
+bool nv50_gpio_irq_enable(struct drm_device *, enum dcb_gpio_tag, bool on);
 
 /* nv50_calc. */
 int nv50_calc_pll(struct drm_device *, struct pll_lims *, int clk,
index 262545b..b26b34c 100644 (file)
@@ -396,6 +396,8 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
                engine->gpio.takedown           = nv50_gpio_fini;
                engine->gpio.get                = nv50_gpio_get;
                engine->gpio.set                = nv50_gpio_set;
+               engine->gpio.irq_register       = nv50_gpio_irq_register;
+               engine->gpio.irq_unregister     = nv50_gpio_irq_unregister;
                engine->gpio.irq_enable         = nv50_gpio_irq_enable;
                switch (dev_priv->chipset) {
                case 0x84:
@@ -487,6 +489,8 @@ static int nouveau_init_engine_ptrs(struct drm_device *dev)
                engine->gpio.takedown           = nouveau_stub_takedown;
                engine->gpio.get                = nv50_gpio_get;
                engine->gpio.set                = nv50_gpio_set;
+               engine->gpio.irq_register       = nv50_gpio_irq_register;
+               engine->gpio.irq_unregister     = nv50_gpio_irq_unregister;
                engine->gpio.irq_enable         = nv50_gpio_irq_enable;
                engine->crypt.init              = nouveau_stub_init;
                engine->crypt.takedown          = nouveau_stub_takedown;
index e5dbd17..7cc94ed 100644 (file)
@@ -804,71 +804,6 @@ nv50_display_error_handler(struct drm_device *dev)
        }
 }
 
-void
-nv50_display_irq_hotplug_bh(struct work_struct *work)
-{
-       struct drm_nouveau_private *dev_priv =
-               container_of(work, struct drm_nouveau_private, hpd_work);
-       struct drm_device *dev = dev_priv->dev;
-       struct drm_connector *connector;
-       const uint32_t gpio_reg[4] = { 0xe104, 0xe108, 0xe280, 0xe284 };
-       uint32_t unplug_mask, plug_mask, change_mask;
-       uint32_t hpd0, hpd1;
-
-       spin_lock_irq(&dev_priv->hpd_state.lock);
-       hpd0 = dev_priv->hpd_state.hpd0_bits;
-       dev_priv->hpd_state.hpd0_bits = 0;
-       hpd1 = dev_priv->hpd_state.hpd1_bits;
-       dev_priv->hpd_state.hpd1_bits = 0;
-       spin_unlock_irq(&dev_priv->hpd_state.lock);
-
-       hpd0 &= nv_rd32(dev, 0xe050);
-       if (dev_priv->chipset >= 0x90)
-               hpd1 &= nv_rd32(dev, 0xe070);
-
-       plug_mask   = (hpd0 & 0x0000ffff) | (hpd1 << 16);
-       unplug_mask = (hpd0 >> 16) | (hpd1 & 0xffff0000);
-       change_mask = plug_mask | unplug_mask;
-
-       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
-               struct drm_encoder_helper_funcs *helper;
-               struct nouveau_connector *nv_connector =
-                       nouveau_connector(connector);
-               struct nouveau_encoder *nv_encoder;
-               struct dcb_gpio_entry *gpio;
-               uint32_t reg;
-               bool plugged;
-
-               if (!nv_connector->dcb)
-                       continue;
-
-               gpio = nouveau_bios_gpio_entry(dev, nv_connector->dcb->gpio_tag);
-               if (!gpio || !(change_mask & (1 << gpio->line)))
-                       continue;
-
-               reg = nv_rd32(dev, gpio_reg[gpio->line >> 3]);
-               plugged = !!(reg & (4 << ((gpio->line & 7) << 2)));
-               NV_INFO(dev, "%splugged %s\n", plugged ? "" : "un",
-                       drm_get_connector_name(connector)) ;
-
-               if (!connector->encoder || !connector->encoder->crtc ||
-                   !connector->encoder->crtc->enabled)
-                       continue;
-               nv_encoder = nouveau_encoder(connector->encoder);
-               helper = connector->encoder->helper_private;
-
-               if (nv_encoder->dcb->type != OUTPUT_DP)
-                       continue;
-
-               if (plugged)
-                       helper->dpms(connector->encoder, DRM_MODE_DPMS_ON);
-               else
-                       helper->dpms(connector->encoder, DRM_MODE_DPMS_OFF);
-       }
-
-       drm_helper_hpd_irq_event(dev);
-}
-
 static void
 nv50_display_isr(struct drm_device *dev)
 {
@@ -918,4 +853,3 @@ nv50_display_isr(struct drm_device *dev)
                }
        }
 }
-
index a269fcc..f0e30b7 100644 (file)
@@ -36,7 +36,6 @@
 #include "nv50_evo.h"
 
 void nv50_display_irq_handler_bh(struct work_struct *work);
-void nv50_display_irq_hotplug_bh(struct work_struct *work);
 int nv50_display_early_init(struct drm_device *dev);
 void nv50_display_late_takedown(struct drm_device *dev);
 int nv50_display_create(struct drm_device *dev);
index 87266d1..6b149c0 100644 (file)
 #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)
@@ -79,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);
@@ -111,8 +223,6 @@ nv50_gpio_init(struct drm_device *dev)
                nv_wr32(dev, 0xe074, 0xffffffff);
        }
 
-       INIT_WORK(&dev_priv->hpd_work, nv50_display_irq_hotplug_bh);
-       spin_lock_init(&dev_priv->hpd_state.lock);
        nouveau_irq_register(dev, 21, nv50_gpio_isr);
        return 0;
 }
@@ -126,26 +236,64 @@ nv50_gpio_fini(struct drm_device *dev)
        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;
-       uint32_t hpd0_bits, hpd1_bits = 0;
+       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;
 
-       hpd0_bits = nv_rd32(dev, 0xe054);
-       nv_wr32(dev, 0xe054, hpd0_bits);
+       intr0 = nv_rd32(dev, 0xe054) & nv_rd32(dev, 0xe050);
+       if (dev_priv->chipset >= 0x90)
+               intr1 = nv_rd32(dev, 0xe074) & nv_rd32(dev, 0xe070);
 
-       if (dev_priv->chipset >= 0x90) {
-               hpd1_bits = nv_rd32(dev, 0xe074);
-               nv_wr32(dev, 0xe074, hpd1_bits);
-       }
+       hi = (intr0 & 0x0000ffff) | (intr1 << 16);
+       lo = (intr0 >> 16) | (intr1 & 0xffff0000);
+       ch = hi | lo;
 
-       spin_lock(&dev_priv->hpd_state.lock);
-       dev_priv->hpd_state.hpd0_bits |= hpd0_bits;
-       dev_priv->hpd_state.hpd1_bits |= hpd1_bits;
-       spin_unlock(&dev_priv->hpd_state.lock);
+       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;
 
-       queue_work(dev_priv->wq, &dev_priv->hpd_work);
+               if (gpioh->inhibit)
+                       continue;
+               gpioh->inhibit = true;
+
+               queue_work(dev_priv->wq, &gpioh->work);
+       }
+       spin_unlock(&priv->lock);
 }