diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nouveau_connector.c')
| -rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_connector.c | 287 |
1 files changed, 186 insertions, 101 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index e620ba8271b..1fa222e8f00 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -26,13 +26,15 @@ #include <acpi/button.h> +#include <linux/pm_runtime.h> + #include <drm/drmP.h> #include <drm/drm_edid.h> #include <drm/drm_crtc_helper.h> #include "nouveau_reg.h" #include "nouveau_drm.h" -#include "nouveau_hw.h" +#include "dispnv04/hw.h" #include "nouveau_acpi.h" #include "nouveau_display.h" @@ -42,6 +44,7 @@ #include <subdev/i2c.h> #include <subdev/gpio.h> +#include <engine/disp.h> MODULE_PARM_DESC(tv_disable, "Disable TV-out detection"); static int nouveau_tv_disable = 0; @@ -55,8 +58,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) { @@ -75,7 +76,8 @@ find_encoder(struct drm_connector *connector, int type) continue; nv_encoder = nouveau_encoder(obj_to_encoder(obj)); - if (type == DCB_OUTPUT_ANY || nv_encoder->dcb->type == type) + if (type == DCB_OUTPUT_ANY || + (nv_encoder->dcb && nv_encoder->dcb->type == type)) return nv_encoder; } @@ -100,38 +102,24 @@ 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 != DCB_GPIO_UNUSED) { - gpio->isr_del(gpio, 0, nv_connector->hpd, 0xff, - nouveau_connector_hotplug, connector); - } - + nouveau_event_ref(NULL, &nv_connector->hpd); kfree(nv_connector->edid); drm_sysfs_connector_remove(connector); drm_connector_cleanup(connector); + if (nv_connector->aux.transfer) + drm_dp_aux_unregister(&nv_connector->aux); kfree(connector); } -static struct nouveau_i2c_port * -nouveau_connector_ddc_detect(struct drm_connector *connector, - struct nouveau_encoder **pnv_encoder) +static struct nouveau_encoder * +nouveau_connector_ddc_detect(struct drm_connector *connector) { struct drm_device *dev = connector->dev; struct nouveau_connector *nv_connector = nouveau_connector(connector); struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_gpio *gpio = nouveau_gpio(drm->device); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); - struct nouveau_i2c_port *port = NULL; + struct nouveau_encoder *nv_encoder; + struct drm_mode_object *obj; int i, panel = -ENODEV; /* eDP panels need powering on by us (if the VBIOS doesn't default it @@ -146,13 +134,9 @@ nouveau_connector_ddc_detect(struct drm_connector *connector, } } - for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { - struct nouveau_encoder *nv_encoder; - struct drm_mode_object *obj; - int id; - - id = connector->encoder_ids[i]; - if (!id) + for (i = 0; nv_encoder = NULL, i < DRM_CONNECTOR_MAX_ENCODER; i++) { + int id = connector->encoder_ids[i]; + if (id == 0) break; obj = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER); @@ -160,23 +144,24 @@ nouveau_connector_ddc_detect(struct drm_connector *connector, continue; nv_encoder = nouveau_encoder(obj_to_encoder(obj)); - if (nv_encoder->dcb->i2c_index < 0xf) - port = i2c->find(i2c, nv_encoder->dcb->i2c_index); - if (port && nv_probe_i2c(port, 0x50)) { - *pnv_encoder = nv_encoder; - break; + if (nv_encoder->dcb->type == DCB_OUTPUT_DP) { + int ret = nouveau_dp_detect(nv_encoder); + if (ret == 0) + break; + } else + if (nv_encoder->i2c) { + if (nv_probe_i2c(nv_encoder->i2c, 0x50)) + break; } - - port = NULL; } /* eDP panel not detected, restore panel power GPIO to previous * state to avoid confusing the SOR for other output types. */ - if (!port && panel == 0) + if (!nv_encoder && panel == 0) gpio->set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, panel); - return port; + return nv_encoder; } static struct nouveau_encoder * @@ -232,9 +217,10 @@ nouveau_connector_set_encoder(struct drm_connector *connector, } else { connector->doublescan_allowed = true; if (nv_device(drm->device)->card_type == NV_20 || - (nv_device(drm->device)->card_type == NV_10 && - (dev->pci_device & 0x0ff0) != 0x0100 && - (dev->pci_device & 0x0ff0) != 0x0150)) + ((nv_device(drm->device)->card_type == NV_10 || + nv_device(drm->device)->card_type == NV_11) && + (dev->pdev->device & 0x0ff0) != 0x0100 && + (dev->pdev->device & 0x0ff0) != 0x0150)) /* HW is broken */ connector->interlace_allowed = false; else @@ -260,6 +246,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) struct nouveau_encoder *nv_partner; struct nouveau_i2c_port *i2c; int type; + int ret; + enum drm_connector_status conn_status = connector_status_disconnected; /* Cleanup the previous EDID block. */ if (nv_connector->edid) { @@ -268,24 +256,21 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) nv_connector->edid = NULL; } - i2c = nouveau_connector_ddc_detect(connector, &nv_encoder); - if (i2c) { + ret = pm_runtime_get_sync(connector->dev->dev); + if (ret < 0 && ret != -EACCES) + return conn_status; + + nv_encoder = nouveau_connector_ddc_detect(connector); + if (nv_encoder && (i2c = nv_encoder->i2c) != NULL) { nv_connector->edid = drm_get_edid(connector, &i2c->adapter); drm_mode_connector_update_edid_property(connector, nv_connector->edid); if (!nv_connector->edid) { NV_ERROR(drm, "DDC responded, but no EDID for %s\n", - drm_get_connector_name(connector)); + connector->name); goto detect_analog; } - if (nv_encoder->dcb->type == DCB_OUTPUT_DP && - !nouveau_dp_detect(to_drm_encoder(nv_encoder))) { - NV_ERROR(drm, "Detected %s, but failed init\n", - drm_get_connector_name(connector)); - return connector_status_disconnected; - } - /* Override encoder type for DVI-I based on whether EDID * says the display is digital or analog, both use the * same i2c channel so the value returned from ddc_detect @@ -310,13 +295,15 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) } nouveau_connector_set_encoder(connector, nv_encoder); - return connector_status_connected; + conn_status = connector_status_connected; + goto out; } nv_encoder = nouveau_connector_of_detect(connector); if (nv_encoder) { nouveau_connector_set_encoder(connector, nv_encoder); - return connector_status_connected; + conn_status = connector_status_connected; + goto out; } detect_analog: @@ -331,12 +318,18 @@ detect_analog: if (helper->detect(encoder, connector) == connector_status_connected) { nouveau_connector_set_encoder(connector, nv_encoder); - return connector_status_connected; + conn_status = connector_status_connected; + goto out; } } - return connector_status_disconnected; + out: + + pm_runtime_mark_last_busy(connector->dev->dev); + pm_runtime_put_autosuspend(connector->dev->dev); + + return conn_status; } static enum drm_connector_status @@ -399,9 +392,10 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force) struct edid *edid = (struct edid *)nouveau_bios_embedded_edid(dev); if (edid) { - nv_connector->edid = kmalloc(EDID_LENGTH, GFP_KERNEL); - *(nv_connector->edid) = *edid; - status = connector_status_connected; + nv_connector->edid = + kmemdup(edid, EDID_LENGTH, GFP_KERNEL); + if (nv_connector->edid) + status = connector_status_connected; } } @@ -437,7 +431,7 @@ nouveau_connector_force(struct drm_connector *connector) nv_encoder = find_encoder(connector, type); if (!nv_encoder) { NV_ERROR(drm, "can't find encoder to force %s on!\n", - drm_get_connector_name(connector)); + connector->name); connector->status = connector_status_disconnected; return; } @@ -911,6 +905,106 @@ nouveau_connector_funcs_lvds = { .force = nouveau_connector_force }; +static void +nouveau_connector_dp_dpms(struct drm_connector *connector, int mode) +{ + struct nouveau_encoder *nv_encoder = NULL; + + if (connector->encoder) + nv_encoder = nouveau_encoder(connector->encoder); + if (nv_encoder && nv_encoder->dcb && + nv_encoder->dcb->type == DCB_OUTPUT_DP) { + if (mode == DRM_MODE_DPMS_ON) { + u8 data = DP_SET_POWER_D0; + nv_wraux(nv_encoder->i2c, DP_SET_POWER, &data, 1); + usleep_range(1000, 2000); + } else { + u8 data = DP_SET_POWER_D3; + nv_wraux(nv_encoder->i2c, DP_SET_POWER, &data, 1); + } + } + + drm_helper_connector_dpms(connector, mode); +} + +static const struct drm_connector_funcs +nouveau_connector_funcs_dp = { + .dpms = nouveau_connector_dp_dpms, + .save = NULL, + .restore = NULL, + .detect = nouveau_connector_detect, + .destroy = nouveau_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = nouveau_connector_set_property, + .force = nouveau_connector_force +}; + +static void +nouveau_connector_hotplug_work(struct work_struct *work) +{ + struct nouveau_connector *nv_connector = + container_of(work, typeof(*nv_connector), work); + struct drm_connector *connector = &nv_connector->base; + struct nouveau_drm *drm = nouveau_drm(connector->dev); + const char *name = connector->name; + + if (nv_connector->status & NVKM_HPD_IRQ) { + } else { + bool plugged = (nv_connector->status != NVKM_HPD_UNPLUG); + + NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", name); + + 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(connector->dev); + } + + nouveau_event_get(nv_connector->hpd); +} + +static int +nouveau_connector_hotplug(void *data, u32 type, int index) +{ + struct nouveau_connector *nv_connector = data; + nv_connector->status = type; + schedule_work(&nv_connector->work); + return NVKM_EVENT_DROP; +} + +static ssize_t +nouveau_connector_aux_xfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + struct nouveau_connector *nv_connector = + container_of(aux, typeof(*nv_connector), aux); + struct nouveau_encoder *nv_encoder; + struct nouveau_i2c_port *port; + int ret; + + nv_encoder = find_encoder(&nv_connector->base, DCB_OUTPUT_DP); + if (!nv_encoder || !(port = nv_encoder->i2c)) + return -ENODEV; + if (WARN_ON(msg->size > 16)) + return -E2BIG; + if (msg->size == 0) + return msg->size; + + ret = nouveau_i2c(port)->acquire(port, 0); + if (ret) + return ret; + + ret = port->func->aux(port, false, msg->request, msg->address, + msg->buffer, msg->size); + nouveau_i2c(port)->release(port); + if (ret >= 0) { + msg->reply = ret; + return msg->size; + } + + return ret; +} + static int drm_conntype_from_dcb(enum dcb_connector_type dcb) { @@ -930,7 +1024,8 @@ drm_conntype_from_dcb(enum dcb_connector_type dcb) case DCB_CONNECTOR_DP : return DRM_MODE_CONNECTOR_DisplayPort; case DCB_CONNECTOR_eDP : return DRM_MODE_CONNECTOR_eDP; case DCB_CONNECTOR_HDMI_0 : - case DCB_CONNECTOR_HDMI_1 : return DRM_MODE_CONNECTOR_HDMIA; + case DCB_CONNECTOR_HDMI_1 : + case DCB_CONNECTOR_HDMI_C : return DRM_MODE_CONNECTOR_HDMIA; default: break; } @@ -943,9 +1038,9 @@ nouveau_connector_create(struct drm_device *dev, int index) { const struct drm_connector_funcs *funcs = &nouveau_connector_funcs; struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_gpio *gpio = nouveau_gpio(drm->device); struct nouveau_display *disp = nouveau_display(dev); struct nouveau_connector *nv_connector = NULL; + struct nouveau_disp *pdisp = nouveau_disp(drm->device); struct drm_connector *connector; int type, ret = 0; bool dummy; @@ -966,18 +1061,10 @@ nouveau_connector_create(struct drm_device *dev, int index) /* attempt to parse vbios connector type and hotplug gpio */ nv_connector->dcb = olddcb_conn(dev, index); if (nv_connector->dcb) { - static const u8 hpd[16] = { - 0xff, 0x07, 0x08, 0xff, 0xff, 0x51, 0x52, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x5e, 0x5f, 0x60, - }; - u32 entry = ROM16(nv_connector->dcb[0]); if (olddcb_conntab(dev)[3] >= 4) entry |= (u32)ROM16(nv_connector->dcb[2]) << 16; - nv_connector->hpd = ffs((entry & 0x07033000) >> 12); - nv_connector->hpd = hpd[nv_connector->hpd]; - nv_connector->type = nv_connector->dcb[0]; if (drm_conntype_from_dcb(nv_connector->type) == DRM_MODE_CONNECTOR_Unknown) { @@ -999,7 +1086,6 @@ nouveau_connector_create(struct drm_device *dev, int index) } } else { nv_connector->type = DCB_CONNECTOR_NONE; - nv_connector->hpd = DCB_GPIO_UNUSED; } /* no vbios data, or an unknown dcb connector type - attempt to @@ -1039,8 +1125,8 @@ nouveau_connector_create(struct drm_device *dev, int index) } } - type = drm_conntype_from_dcb(nv_connector->type); - if (type == DRM_MODE_CONNECTOR_LVDS) { + switch ((type = drm_conntype_from_dcb(nv_connector->type))) { + case DRM_MODE_CONNECTOR_LVDS: ret = nouveau_bios_parse_lvds_table(dev, 0, &dummy, &dummy); if (ret) { NV_ERROR(drm, "Error parsing LVDS table, disabling\n"); @@ -1049,8 +1135,23 @@ nouveau_connector_create(struct drm_device *dev, int index) } funcs = &nouveau_connector_funcs_lvds; - } else { + break; + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_eDP: + nv_connector->aux.dev = dev->dev; + nv_connector->aux.transfer = nouveau_connector_aux_xfer; + ret = drm_dp_aux_register(&nv_connector->aux); + if (ret) { + NV_ERROR(drm, "failed to register aux channel\n"); + kfree(nv_connector); + return ERR_PTR(ret); + } + + funcs = &nouveau_connector_funcs_dp; + break; + default: funcs = &nouveau_connector_funcs; + break; } /* defaults, will get overridden in detect() */ @@ -1125,32 +1226,16 @@ nouveau_connector_create(struct drm_device *dev, int index) break; } - connector->polled = DRM_CONNECTOR_POLL_CONNECT; - if (gpio && nv_connector->hpd != DCB_GPIO_UNUSED) { - ret = gpio->isr_add(gpio, 0, nv_connector->hpd, 0xff, - nouveau_connector_hotplug, connector); - if (ret == 0) - connector->polled = DRM_CONNECTOR_POLL_HPD; - } + ret = nouveau_event_new(pdisp->hpd, NVKM_HPD, index, + nouveau_connector_hotplug, + nv_connector, &nv_connector->hpd); + if (ret) + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + else + connector->polled = DRM_CONNECTOR_POLL_HPD; + + INIT_WORK(&nv_connector->work, nouveau_connector_hotplug_work); 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); -} |
