diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nouveau_connector.c')
| -rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_connector.c | 218 | 
1 files changed, 146 insertions, 72 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index c5b36f9e9a1..1fa222e8f00 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -44,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; @@ -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,21 +102,24 @@ static void  nouveau_connector_destroy(struct drm_connector *connector)  {  	struct nouveau_connector *nv_connector = nouveau_connector(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_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 @@ -129,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); @@ -143,22 +144,24 @@ nouveau_connector_ddc_detect(struct drm_connector *connector,  			continue;  		nv_encoder = nouveau_encoder(obj_to_encoder(obj)); -		port = nv_encoder->i2c; -		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 * @@ -214,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 @@ -253,28 +257,20 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)  	}  	ret = pm_runtime_get_sync(connector->dev->dev); -	if (ret < 0) +	if (ret < 0 && ret != -EACCES)  		return conn_status; -	i2c = nouveau_connector_ddc_detect(connector, &nv_encoder); -	if (i2c) { +	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)); -			conn_status = connector_status_disconnected; -			goto out; -		} -  		/* 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 @@ -435,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;  	} @@ -910,34 +906,103 @@ nouveau_connector_funcs_lvds = {  };  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, struct nouveau_connector, hpd_work); +		container_of(work, typeof(*nv_connector), 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); +	struct nouveau_drm *drm = nouveau_drm(connector->dev); +	const char *name = connector->name; -	NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", -		 drm_get_connector_name(connector)); +	if (nv_connector->status & NVKM_HPD_IRQ) { +	} else { +		bool plugged = (nv_connector->status != NVKM_HPD_UNPLUG); -	if (plugged) -		drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); -	else -		drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); +		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); +	} -	drm_helper_hpd_irq_event(dev); +	nouveau_event_get(nv_connector->hpd);  }  static int -nouveau_connector_hotplug(struct nouveau_eventh *event, int index) +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(event, struct nouveau_connector, hpd_func); -	schedule_work(&nv_connector->hpd_work); -	return NVKM_EVENT_KEEP; +		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 @@ -959,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;  	} @@ -972,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; @@ -990,27 +1056,15 @@ 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 */  	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; -		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; -  		nv_connector->type = nv_connector->dcb[0];  		if (drm_conntype_from_dcb(nv_connector->type) ==  					  DRM_MODE_CONNECTOR_Unknown) { @@ -1032,7 +1086,6 @@ nouveau_connector_create(struct drm_device *dev, int index)  		}  	} else {  		nv_connector->type = DCB_CONNECTOR_NONE; -		nv_connector->hpd.func = DCB_GPIO_UNUSED;  	}  	/* no vbios data, or an unknown dcb connector type - attempt to @@ -1072,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"); @@ -1082,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() */ @@ -1158,10 +1226,16 @@ nouveau_connector_create(struct drm_device *dev, int index)  		break;  	} -	connector->polled = DRM_CONNECTOR_POLL_CONNECT; -	if (nv_connector->hpd.func != DCB_GPIO_UNUSED) +	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;  }  | 
