drm/nouveau/i2c: add interfaces to support handling aux channel interrupts
authorBen Skeggs <bskeggs@redhat.com>
Tue, 13 May 2014 04:47:36 +0000 (14:47 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Wed, 11 Jun 2014 06:09:16 +0000 (16:09 +1000)
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/core/include/subdev/i2c.h
drivers/gpu/drm/nouveau/core/subdev/i2c/base.c
drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c
drivers/gpu/drm/nouveau/core/subdev/i2c/priv.h

index 857a11025dac8128970a511a5dc2f2dd7a0bae9f..a4c9c8b5cb70035591dd20956323fc52a4e589df 100644 (file)
 #define NV_I2C_TYPE_EXTDDC(e) (0x0005 | (e) << 8)
 #define NV_I2C_TYPE_EXTAUX(e) (0x0006 | (e) << 8)
 
+enum nvkm_i2c_event {
+       NVKM_I2C_PLUG = 1,
+       NVKM_I2C_UNPLUG = 2,
+       NVKM_I2C_IRQ = 4,
+       NVKM_I2C_DONE = 8,
+       NVKM_I2C_ANY = (NVKM_I2C_PLUG |
+                       NVKM_I2C_UNPLUG |
+                       NVKM_I2C_IRQ |
+                       NVKM_I2C_DONE),
+};
+
 struct nouveau_i2c_port {
        struct nouveau_object base;
        struct i2c_adapter adapter;
 
        struct list_head head;
        u8  index;
+       int aux;
 
        const struct nouveau_i2c_func *func;
 };
@@ -46,6 +58,7 @@ struct nouveau_i2c_board_info {
 
 struct nouveau_i2c {
        struct nouveau_subdev base;
+       struct nouveau_event *ntfy;
 
        struct nouveau_i2c_port *(*find)(struct nouveau_i2c *, u8 index);
        struct nouveau_i2c_port *(*find_type)(struct nouveau_i2c *, u16 type);
index 1c6fa86e9425789ab3a4997ca0e6a54e3b63c4d9..0e36057609e7391c1f6b69912130c01eab707f82 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 #include <core/option.h>
+#include <core/event.h>
 
 #include <subdev/bios.h>
 #include <subdev/bios/dcb.h>
@@ -114,6 +115,7 @@ nouveau_i2c_port_create_(struct nouveau_object *parent,
        port->adapter.owner = THIS_MODULE;
        port->adapter.dev.parent = nv_device_base(device);
        port->index = index;
+       port->aux = -1;
        port->func = func;
 
        if ( algo == &nouveau_i2c_bit_algo &&
@@ -236,11 +238,59 @@ nouveau_i2c_identify(struct nouveau_i2c *i2c, int index, const char *what,
        return -ENODEV;
 }
 
+static void
+nouveau_i2c_intr_disable(struct nouveau_event *event, int type, int index)
+{
+       struct nouveau_i2c *i2c = nouveau_i2c(event->priv);
+       struct nouveau_i2c_port *port = i2c->find(i2c, index);
+       const struct nouveau_i2c_impl *impl = (void *)nv_object(i2c)->oclass;
+       if (port && port->aux >= 0)
+               impl->aux_mask(i2c, type, 1 << port->aux, 0);
+}
+
+static void
+nouveau_i2c_intr_enable(struct nouveau_event *event, int type, int index)
+{
+       struct nouveau_i2c *i2c = nouveau_i2c(event->priv);
+       struct nouveau_i2c_port *port = i2c->find(i2c, index);
+       const struct nouveau_i2c_impl *impl = (void *)nv_object(i2c)->oclass;
+       if (port && port->aux >= 0)
+               impl->aux_mask(i2c, type, 1 << port->aux, 1 << port->aux);
+}
+
+static void
+nouveau_i2c_intr(struct nouveau_subdev *subdev)
+{
+       struct nouveau_i2c_impl *impl = (void *)nv_oclass(subdev);
+       struct nouveau_i2c *i2c = nouveau_i2c(subdev);
+       struct nouveau_i2c_port *port;
+       u32 hi, lo, rq, tx, e;
+
+       if (impl->aux_stat) {
+               impl->aux_stat(i2c, &hi, &lo, &rq, &tx);
+               if (hi || lo || rq || tx) {
+                       list_for_each_entry(port, &i2c->ports, head) {
+                               if (e = 0, port->aux < 0)
+                                       continue;
+
+                               if (hi & (1 << port->aux)) e |= NVKM_I2C_PLUG;
+                               if (lo & (1 << port->aux)) e |= NVKM_I2C_UNPLUG;
+                               if (rq & (1 << port->aux)) e |= NVKM_I2C_IRQ;
+                               if (tx & (1 << port->aux)) e |= NVKM_I2C_DONE;
+
+                               nouveau_event_trigger(i2c->ntfy, e, port->index);
+                       }
+               }
+       }
+}
+
 int
 _nouveau_i2c_fini(struct nouveau_object *object, bool suspend)
 {
+       struct nouveau_i2c_impl *impl = (void *)nv_oclass(object);
        struct nouveau_i2c *i2c = (void *)object;
        struct nouveau_i2c_port *port;
+       u32 mask;
        int ret;
 
        list_for_each_entry(port, &i2c->ports, head) {
@@ -249,6 +299,11 @@ _nouveau_i2c_fini(struct nouveau_object *object, bool suspend)
                        goto fail;
        }
 
+       if ((mask = (1 << impl->aux) - 1), impl->aux_stat) {
+               impl->aux_mask(i2c, NVKM_I2C_ANY, mask, 0);
+               impl->aux_stat(i2c, &mask, &mask, &mask, &mask);
+       }
+
        return nouveau_subdev_fini(&i2c->base, suspend);
 fail:
        list_for_each_entry_continue_reverse(port, &i2c->ports, head) {
@@ -289,6 +344,8 @@ _nouveau_i2c_dtor(struct nouveau_object *object)
        struct nouveau_i2c *i2c = (void *)object;
        struct nouveau_i2c_port *port, *temp;
 
+       nouveau_event_destroy(&i2c->ntfy);
+
        list_for_each_entry_safe(port, temp, &i2c->ports, head) {
                nouveau_object_ref(NULL, (struct nouveau_object **)&port);
        }
@@ -323,6 +380,7 @@ nouveau_i2c_create_(struct nouveau_object *parent,
        if (ret)
                return ret;
 
+       nv_subdev(i2c)->intr = nouveau_i2c_intr;
        i2c->find = nouveau_i2c_find;
        i2c->find_type = nouveau_i2c_find_type;
        i2c->identify = nouveau_i2c_identify;
@@ -379,6 +437,13 @@ nouveau_i2c_create_(struct nouveau_object *parent,
                }
        }
 
+       ret = nouveau_event_create(4, index, &i2c->ntfy);
+       if (ret)
+               return ret;
+
+       i2c->ntfy->priv = i2c;
+       i2c->ntfy->enable = nouveau_i2c_intr_enable;
+       i2c->ntfy->disable = nouveau_i2c_intr_disable;
        return 0;
 }
 
index 768b849a7370cad2b1288cad4aa18fb54b641a68..900f464c716c645542d1cacf7d5653adacd54f89 100644 (file)
@@ -232,6 +232,7 @@ nv94_aux_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
        if (ret)
                return ret;
 
+       port->base.aux = info->drive;
        port->addr = info->drive;
        if (info->share != DCB_I2C_UNUSED) {
                port->ctrl = 0x00e500 + (info->drive * 0x50);
index ec1934205cf5bd3e4348dc04ecb87a930a2033ac..27b9a9717229a9c22d139e67ce74144f433f20cd 100644 (file)
@@ -55,7 +55,22 @@ extern const struct i2c_algorithm nouveau_i2c_aux_algo;
 
 struct nouveau_i2c_impl {
        struct nouveau_oclass base;
+
+       /* supported i2c port classes */
        struct nouveau_oclass *sclass;
+
+       /* number of native dp aux channels present */
+       int aux;
+
+       /* read and ack pending interrupts, returning only data
+        * for ports that have not been masked off, while still
+        * performing the ack for anything that was pending.
+        */
+       void (*aux_stat)(struct nouveau_i2c *, u32 *, u32 *, u32 *, u32 *);
+
+       /* mask on/off interrupt types for a given set of auxch
+        */
+       void (*aux_mask)(struct nouveau_i2c *, u32, u32, u32);
 };
 
 #endif