drm/nouveau/gpio: use event interfaces for interrupt signalling

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
This commit is contained in:
Ben Skeggs 2013-02-03 12:56:16 +10:00
parent 0f0800661a
commit 4f47643dbb
9 changed files with 127 additions and 219 deletions

View File

@ -3,6 +3,7 @@
#include <core/subdev.h>
#include <core/device.h>
#include <core/event.h>
#include <subdev/bios.h>
#include <subdev/bios/gpio.h>
@ -10,28 +11,18 @@
struct nouveau_gpio {
struct nouveau_subdev base;
struct nouveau_event *events;
/* hardware interfaces */
void (*reset)(struct nouveau_gpio *, u8 func);
int (*drive)(struct nouveau_gpio *, int line, int dir, int out);
int (*sense)(struct nouveau_gpio *, int line);
void (*irq_enable)(struct nouveau_gpio *, int line, bool);
/* software interfaces */
int (*find)(struct nouveau_gpio *, int idx, u8 tag, u8 line,
struct dcb_gpio_func *);
int (*set)(struct nouveau_gpio *, int idx, u8 tag, u8 line, int state);
int (*get)(struct nouveau_gpio *, int idx, u8 tag, u8 line);
int (*irq)(struct nouveau_gpio *, int idx, u8 tag, u8 line, bool on);
/* interrupt handling */
struct list_head isr;
spinlock_t lock;
void (*isr_run)(struct nouveau_gpio *, int idx, u32 mask);
int (*isr_add)(struct nouveau_gpio *, int idx, u8 tag, u8 line,
void (*)(void *, int state), void *data);
void (*isr_del)(struct nouveau_gpio *, int idx, u8 tag, u8 line,
void (*)(void *, int state), void *data);
};
static inline struct nouveau_gpio *
@ -42,14 +33,17 @@ nouveau_gpio(void *obj)
#define nouveau_gpio_create(p,e,o,l,d) \
nouveau_gpio_create_((p), (e), (o), (l), sizeof(**d), (void **)d)
#define nouveau_gpio_destroy(p) \
nouveau_subdev_destroy(&(p)->base)
#define nouveau_gpio_destroy(p) ({ \
struct nouveau_gpio *gpio = (p); \
_nouveau_gpio_dtor(nv_object(gpio)); \
})
#define nouveau_gpio_fini(p,s) \
nouveau_subdev_fini(&(p)->base, (s))
int nouveau_gpio_create_(struct nouveau_object *, struct nouveau_object *,
struct nouveau_oclass *, int, int, void **);
int nouveau_gpio_init(struct nouveau_gpio *);
int nouveau_gpio_create_(struct nouveau_object *, struct nouveau_object *,
struct nouveau_oclass *, int, int, void **);
void _nouveau_gpio_dtor(struct nouveau_object *);
int nouveau_gpio_init(struct nouveau_gpio *);
extern struct nouveau_oclass nv10_gpio_oclass;
extern struct nouveau_oclass nv50_gpio_oclass;
@ -59,6 +53,7 @@ void nv50_gpio_dtor(struct nouveau_object *);
int nv50_gpio_init(struct nouveau_object *);
int nv50_gpio_fini(struct nouveau_object *, bool);
void nv50_gpio_intr(struct nouveau_subdev *);
void nv50_gpio_irq_enable(struct nouveau_gpio *, int line, bool);
void nv50_gpio_intr_enable(struct nouveau_event *, int line);
void nv50_gpio_intr_disable(struct nouveau_event *, int line);
#endif

View File

@ -102,129 +102,12 @@ nouveau_gpio_get(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line)
return ret;
}
static int
nouveau_gpio_irq(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, bool on)
void
_nouveau_gpio_dtor(struct nouveau_object *object)
{
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);
}
}
struct nouveau_gpio *gpio = (void *)object;
nouveau_event_destroy(&gpio->events);
nouveau_subdev_destroy(&gpio->base);
}
int
@ -242,15 +125,13 @@ nouveau_gpio_create_(struct nouveau_object *parent,
if (ret)
return ret;
ret = nouveau_event_create(lines, &gpio->events);
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;
}

View File

@ -82,15 +82,6 @@ nv10_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out)
return 0;
}
static void
nv10_gpio_irq_enable(struct nouveau_gpio *gpio, int line, bool on)
{
u32 mask = 0x00010001 << line;
nv_wr32(gpio, 0x001104, mask);
nv_mask(gpio, 0x001144, mask, on ? mask : 0);
}
static void
nv10_gpio_intr(struct nouveau_subdev *subdev)
{
@ -98,12 +89,30 @@ nv10_gpio_intr(struct nouveau_subdev *subdev)
u32 intr = nv_rd32(priv, 0x001104);
u32 hi = (intr & 0x0000ffff) >> 0;
u32 lo = (intr & 0xffff0000) >> 16;
int i;
priv->base.isr_run(&priv->base, 0, hi | lo);
for (i = 0; (hi | lo) && i < 32; i++) {
if ((hi | lo) & (1 << i))
nouveau_event_trigger(priv->base.events, i);
}
nv_wr32(priv, 0x001104, intr);
}
static void
nv10_gpio_intr_enable(struct nouveau_event *event, int line)
{
nv_wr32(event->priv, 0x001104, 0x00010001 << line);
nv_mask(event->priv, 0x001144, 0x00010001 << line, 0x00010001 << line);
}
static void
nv10_gpio_intr_disable(struct nouveau_event *event, int line)
{
nv_wr32(event->priv, 0x001104, 0x00010001 << line);
nv_mask(event->priv, 0x001144, 0x00010001 << line, 0x00000000);
}
static int
nv10_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
struct nouveau_oclass *oclass, void *data, u32 size,
@ -119,7 +128,9 @@ nv10_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
priv->base.drive = nv10_gpio_drive;
priv->base.sense = nv10_gpio_sense;
priv->base.irq_enable = nv10_gpio_irq_enable;
priv->base.events->priv = priv;
priv->base.events->enable = nv10_gpio_intr_enable;
priv->base.events->disable = nv10_gpio_intr_disable;
nv_subdev(priv)->intr = nv10_gpio_intr;
return 0;
}

View File

@ -94,22 +94,13 @@ nv50_gpio_sense(struct nouveau_gpio *gpio, int line)
return !!(nv_rd32(gpio, reg) & (4 << shift));
}
void
nv50_gpio_irq_enable(struct nouveau_gpio *gpio, int line, bool on)
{
u32 reg = line < 16 ? 0xe050 : 0xe070;
u32 mask = 0x00010001 << (line & 0xf);
nv_wr32(gpio, reg + 4, mask);
nv_mask(gpio, reg + 0, mask, on ? mask : 0);
}
void
nv50_gpio_intr(struct nouveau_subdev *subdev)
{
struct nv50_gpio_priv *priv = (void *)subdev;
u32 intr0, intr1 = 0;
u32 hi, lo;
int i;
intr0 = nv_rd32(priv, 0xe054) & nv_rd32(priv, 0xe050);
if (nv_device(priv)->chipset >= 0x90)
@ -117,13 +108,35 @@ nv50_gpio_intr(struct nouveau_subdev *subdev)
hi = (intr0 & 0x0000ffff) | (intr1 << 16);
lo = (intr0 >> 16) | (intr1 & 0xffff0000);
priv->base.isr_run(&priv->base, 0, hi | lo);
for (i = 0; (hi | lo) && i < 32; i++) {
if ((hi | lo) & (1 << i))
nouveau_event_trigger(priv->base.events, i);
}
nv_wr32(priv, 0xe054, intr0);
if (nv_device(priv)->chipset >= 0x90)
nv_wr32(priv, 0xe074, intr1);
}
void
nv50_gpio_intr_enable(struct nouveau_event *event, int line)
{
const u32 addr = line < 16 ? 0xe050 : 0xe070;
const u32 mask = 0x00010001 << (line & 0xf);
nv_wr32(event->priv, addr + 0x04, mask);
nv_mask(event->priv, addr + 0x00, mask, mask);
}
void
nv50_gpio_intr_disable(struct nouveau_event *event, int line)
{
const u32 addr = line < 16 ? 0xe050 : 0xe070;
const u32 mask = 0x00010001 << (line & 0xf);
nv_wr32(event->priv, addr + 0x04, mask);
nv_mask(event->priv, addr + 0x00, mask, 0x00000000);
}
static int
nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
struct nouveau_oclass *oclass, void *data, u32 size,
@ -142,7 +155,9 @@ nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
priv->base.reset = nv50_gpio_reset;
priv->base.drive = nv50_gpio_drive;
priv->base.sense = nv50_gpio_sense;
priv->base.irq_enable = nv50_gpio_irq_enable;
priv->base.events->priv = priv;
priv->base.events->enable = nv50_gpio_intr_enable;
priv->base.events->disable = nv50_gpio_intr_disable;
nv_subdev(priv)->intr = nv50_gpio_intr;
return 0;
}

View File

@ -88,7 +88,9 @@ nvd0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
priv->base.reset = nvd0_gpio_reset;
priv->base.drive = nvd0_gpio_drive;
priv->base.sense = nvd0_gpio_sense;
priv->base.irq_enable = nv50_gpio_irq_enable;
priv->base.events->priv = priv;
priv->base.events->enable = nv50_gpio_intr_enable;
priv->base.events->disable = nv50_gpio_intr_disable;
nv_subdev(priv)->intr = nv50_gpio_intr;
return 0;
}

View File

@ -55,8 +55,6 @@ MODULE_PARM_DESC(duallink, "Allow dual-link TMDS (default: enabled)");
static int nouveau_duallink = 1;
module_param_named(duallink, nouveau_duallink, int, 0400);
static void nouveau_connector_hotplug(void *, int);
struct nouveau_encoder *
find_encoder(struct drm_connector *connector, int type)
{
@ -100,22 +98,6 @@ static void
nouveau_connector_destroy(struct drm_connector *connector)
{
struct nouveau_connector *nv_connector = nouveau_connector(connector);
struct nouveau_gpio *gpio;
struct nouveau_drm *drm;
struct drm_device *dev;
if (!nv_connector)
return;
dev = nv_connector->base.dev;
drm = nouveau_drm(dev);
gpio = nouveau_gpio(drm->device);
if (gpio && nv_connector->hpd.func != DCB_GPIO_UNUSED) {
gpio->isr_del(gpio, 0, nv_connector->hpd.func, 0xff,
nouveau_connector_hotplug, connector);
}
kfree(nv_connector->edid);
drm_sysfs_connector_remove(connector);
drm_connector_cleanup(connector);
@ -912,6 +894,37 @@ nouveau_connector_funcs_lvds = {
.force = nouveau_connector_force
};
static void
nouveau_connector_hotplug_work(struct work_struct *work)
{
struct nouveau_connector *nv_connector =
container_of(work, struct nouveau_connector, hpd_work);
struct drm_connector *connector = &nv_connector->base;
struct drm_device *dev = connector->dev;
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_gpio *gpio = nouveau_gpio(drm->device);
bool plugged = gpio->get(gpio, 0, nv_connector->hpd.func, 0xff);
NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un",
drm_get_connector_name(connector));
if (plugged)
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON);
else
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
drm_helper_hpd_irq_event(dev);
}
static int
nouveau_connector_hotplug(struct nouveau_eventh *event, int index)
{
struct nouveau_connector *nv_connector =
container_of(event, struct nouveau_connector, hpd_func);
schedule_work(&nv_connector->hpd_work);
return NVKM_EVENT_KEEP;
}
static int
drm_conntype_from_dcb(enum dcb_connector_type dcb)
{
@ -962,6 +975,7 @@ nouveau_connector_create(struct drm_device *dev, int index)
return ERR_PTR(-ENOMEM);
connector = &nv_connector->base;
INIT_WORK(&nv_connector->hpd_work, nouveau_connector_hotplug_work);
nv_connector->index = index;
/* attempt to parse vbios connector type and hotplug gpio */
@ -978,6 +992,7 @@ nouveau_connector_create(struct drm_device *dev, int index)
ret = gpio->find(gpio, 0, hpd[ffs((entry & 0x07033000) >> 12)],
DCB_GPIO_UNUSED, &nv_connector->hpd);
nv_connector->hpd_func.func = nouveau_connector_hotplug;
if (ret)
nv_connector->hpd.func = DCB_GPIO_UNUSED;
@ -1129,31 +1144,9 @@ nouveau_connector_create(struct drm_device *dev, int index)
}
connector->polled = DRM_CONNECTOR_POLL_CONNECT;
if (gpio && nv_connector->hpd.func != DCB_GPIO_UNUSED) {
ret = gpio->isr_add(gpio, 0, nv_connector->hpd.func, 0xff,
nouveau_connector_hotplug, connector);
if (ret == 0)
connector->polled = DRM_CONNECTOR_POLL_HPD;
}
if (nv_connector->hpd.func != DCB_GPIO_UNUSED)
connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_sysfs_connector_add(connector);
return connector;
}
static void
nouveau_connector_hotplug(void *data, int plugged)
{
struct drm_connector *connector = data;
struct drm_device *dev = connector->dev;
struct nouveau_drm *drm = nouveau_drm(dev);
NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un",
drm_get_connector_name(connector));
if (plugged)
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON);
else
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
drm_helper_hpd_irq_event(dev);
}

View File

@ -30,6 +30,8 @@
#include <drm/drm_edid.h>
#include "nouveau_crtc.h"
#include <core/event.h>
#include <subdev/bios.h>
#include <subdev/bios/gpio.h>
@ -62,10 +64,13 @@ enum nouveau_dithering_depth {
struct nouveau_connector {
struct drm_connector base;
enum dcb_connector_type type;
struct dcb_gpio_func hpd;
u8 index;
u8 *dcb;
struct dcb_gpio_func hpd;
struct work_struct hpd_work;
struct nouveau_eventh hpd_func;
int dithering_mode;
int dithering_depth;
int scaling_mode;

View File

@ -233,8 +233,10 @@ nouveau_display_init(struct drm_device *dev)
/* enable hotplug interrupts */
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
struct nouveau_connector *conn = nouveau_connector(connector);
if (gpio)
gpio->irq(gpio, 0, conn->hpd.func, 0xff, true);
if (gpio && conn->hpd.func != DCB_GPIO_UNUSED) {
nouveau_event_get(gpio->events, conn->hpd.line,
&conn->hpd_func);
}
}
return ret;
@ -251,8 +253,10 @@ nouveau_display_fini(struct drm_device *dev)
/* disable hotplug interrupts */
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
struct nouveau_connector *conn = nouveau_connector(connector);
if (gpio)
gpio->irq(gpio, 0, conn->hpd.func, 0xff, false);
if (gpio && conn->hpd.func != DCB_GPIO_UNUSED) {
nouveau_event_put(gpio->events, conn->hpd.line,
&conn->hpd_func);
}
}
drm_kms_helper_poll_disable(dev);

View File

@ -260,7 +260,8 @@ nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate,
* we take during link training (DP_SET_POWER is one), we need
* to ignore them for the moment to avoid races.
*/
gpio->irq(gpio, 0, nv_connector->hpd.func, 0xff, false);
nouveau_event_put(gpio->events, nv_connector->hpd.line,
&nv_connector->hpd_func);
/* enable down-spreading and execute pre-train script from vbios */
dp_link_train_init(dev, &dp, nv_encoder->dp.dpcd[3] & 1);
@ -300,7 +301,8 @@ nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate,
dp_link_train_fini(dev, &dp);
/* re-enable hotplug detect */
gpio->irq(gpio, 0, nv_connector->hpd.func, 0xff, true);
nouveau_event_get(gpio->events, nv_connector->hpd.line,
&nv_connector->hpd_func);
return true;
}