mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	 3e6f73b876
			
		
	
	
		3e6f73b876
		
	
	
	
	
		
			
			This is a handful of patches that add bridge support for Tegra devices and fix a couple of minor issues. -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEiOrDCAFJzPfAjcif3SOs138+s6EFAl9ol3oTHHRyZWRpbmdA bnZpZGlhLmNvbQAKCRDdI6zXfz6zoZ9GEACj5op3SIxMvCwQfiAJuXV5JKlAlxa/ cf437ZdWjvgId/afbqh6LX3srOEHdEfpesAthxr/g+KEsy3/Woaq/hNxcut2IAe8 kI1OW2b/bLI9JHC5U61NCcicRpRY/qte/wDON0u6DQ7CS10lhCslTZH3S/iABVM8 rH6nITAf2dnHL0giMM/7ednEJGUB139mpBK/trjzpqaFE+ATUib+fDzVUNxGw6Ju hHo4KUFLkVhqZPeOFdJDEdDdzKax0vTCkHki+eT+JZ1iA2rqRO4P3cTJHVYdxOhH bil6s9RrYaEsmxvuBWZhf0Ku0BlwDN+LZwigvUCWZdJRIrspNrNP36VJWNzKGVIo Niziv71UW8H0Utzoytq1m7MlVYUHn+PNL/58EhRcsbZX2nGqHbS6QVlP1o5tH/7g gD6MaUVt9K55Wh5s6XGoDBT/5xTXiDj64O1zloFd0onAx7/I68zgkwWENNfRKf5x I2c/+hSg273dytat7d4jqWdjWYfvLesb0KgajgBarOHyB4UyZ79V1pfyK3mvxTBS 5mAIoIt/PlLta+kl5zjkSSrYAMfohqVZAhMLqeqqSVoRHw8swwgQMVfn8ICEfrzC pFbyISbfmTYijTWESBXPyz5vzHcvKwuw8U+DuC2dTO2oa7jJVTPKiTpLMjwcQBmo 5IGjsACQROHOeA== =Ojrd -----END PGP SIGNATURE----- Merge tag 'drm/tegra/for-5.10-rc1' of ssh://git.freedesktop.org/git/tegra/linux into drm-next drm/tegra: Changes for v5.10-rc1 This is a handful of patches that add bridge support for Tegra devices and fix a couple of minor issues. Signed-off-by: Dave Airlie <airlied@redhat.com> From: Thierry Reding <thierry.reding@gmail.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200921121245.3953659-1-thierry.reding@gmail.com
		
			
				
	
	
		
			267 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			267 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-only
 | |
| /*
 | |
|  * Copyright (C) 2012 Avionic Design GmbH
 | |
|  * Copyright (C) 2012 NVIDIA CORPORATION.  All rights reserved.
 | |
|  */
 | |
| 
 | |
| #include <drm/drm_atomic_helper.h>
 | |
| #include <drm/drm_of.h>
 | |
| #include <drm/drm_panel.h>
 | |
| #include <drm/drm_simple_kms_helper.h>
 | |
| 
 | |
| #include "drm.h"
 | |
| #include "dc.h"
 | |
| 
 | |
| #include <media/cec-notifier.h>
 | |
| 
 | |
| int tegra_output_connector_get_modes(struct drm_connector *connector)
 | |
| {
 | |
| 	struct tegra_output *output = connector_to_output(connector);
 | |
| 	struct edid *edid = NULL;
 | |
| 	int err = 0;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the panel provides one or more modes, use them exclusively and
 | |
| 	 * ignore any other means of obtaining a mode.
 | |
| 	 */
 | |
| 	if (output->panel) {
 | |
| 		err = drm_panel_get_modes(output->panel, connector);
 | |
| 		if (err > 0)
 | |
| 			return err;
 | |
| 	}
 | |
| 
 | |
| 	if (output->edid)
 | |
| 		edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL);
 | |
| 	else if (output->ddc)
 | |
| 		edid = drm_get_edid(connector, output->ddc);
 | |
| 
 | |
| 	cec_notifier_set_phys_addr_from_edid(output->cec, edid);
 | |
| 	drm_connector_update_edid_property(connector, edid);
 | |
| 
 | |
| 	if (edid) {
 | |
| 		err = drm_add_edid_modes(connector, edid);
 | |
| 		kfree(edid);
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| enum drm_connector_status
 | |
| tegra_output_connector_detect(struct drm_connector *connector, bool force)
 | |
| {
 | |
| 	struct tegra_output *output = connector_to_output(connector);
 | |
| 	enum drm_connector_status status = connector_status_unknown;
 | |
| 
 | |
| 	if (output->hpd_gpio) {
 | |
| 		if (gpiod_get_value(output->hpd_gpio) == 0)
 | |
| 			status = connector_status_disconnected;
 | |
| 		else
 | |
| 			status = connector_status_connected;
 | |
| 	} else {
 | |
| 		if (!output->panel)
 | |
| 			status = connector_status_disconnected;
 | |
| 		else
 | |
| 			status = connector_status_connected;
 | |
| 	}
 | |
| 
 | |
| 	if (status != connector_status_connected)
 | |
| 		cec_notifier_phys_addr_invalidate(output->cec);
 | |
| 
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| void tegra_output_connector_destroy(struct drm_connector *connector)
 | |
| {
 | |
| 	struct tegra_output *output = connector_to_output(connector);
 | |
| 
 | |
| 	if (output->cec)
 | |
| 		cec_notifier_conn_unregister(output->cec);
 | |
| 
 | |
| 	drm_connector_unregister(connector);
 | |
| 	drm_connector_cleanup(connector);
 | |
| }
 | |
| 
 | |
| static irqreturn_t hpd_irq(int irq, void *data)
 | |
| {
 | |
| 	struct tegra_output *output = data;
 | |
| 
 | |
| 	if (output->connector.dev)
 | |
| 		drm_helper_hpd_irq_event(output->connector.dev);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| int tegra_output_probe(struct tegra_output *output)
 | |
| {
 | |
| 	struct device_node *ddc, *panel;
 | |
| 	unsigned long flags;
 | |
| 	int err, size;
 | |
| 
 | |
| 	if (!output->of_node)
 | |
| 		output->of_node = output->dev->of_node;
 | |
| 
 | |
| 	err = drm_of_find_panel_or_bridge(output->of_node, -1, -1,
 | |
| 					  &output->panel, &output->bridge);
 | |
| 	if (err && err != -ENODEV)
 | |
| 		return err;
 | |
| 
 | |
| 	panel = of_parse_phandle(output->of_node, "nvidia,panel", 0);
 | |
| 	if (panel) {
 | |
| 		/*
 | |
| 		 * Don't mix nvidia,panel phandle with the graph in a
 | |
| 		 * device-tree.
 | |
| 		 */
 | |
| 		WARN_ON(output->panel || output->bridge);
 | |
| 
 | |
| 		output->panel = of_drm_find_panel(panel);
 | |
| 		of_node_put(panel);
 | |
| 
 | |
| 		if (IS_ERR(output->panel))
 | |
| 			return PTR_ERR(output->panel);
 | |
| 	}
 | |
| 
 | |
| 	output->edid = of_get_property(output->of_node, "nvidia,edid", &size);
 | |
| 
 | |
| 	ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
 | |
| 	if (ddc) {
 | |
| 		output->ddc = of_get_i2c_adapter_by_node(ddc);
 | |
| 		of_node_put(ddc);
 | |
| 
 | |
| 		if (!output->ddc) {
 | |
| 			err = -EPROBE_DEFER;
 | |
| 			of_node_put(ddc);
 | |
| 			return err;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	output->hpd_gpio = devm_gpiod_get_from_of_node(output->dev,
 | |
| 						       output->of_node,
 | |
| 						       "nvidia,hpd-gpio", 0,
 | |
| 						       GPIOD_IN,
 | |
| 						       "HDMI hotplug detect");
 | |
| 	if (IS_ERR(output->hpd_gpio)) {
 | |
| 		if (PTR_ERR(output->hpd_gpio) != -ENOENT)
 | |
| 			return PTR_ERR(output->hpd_gpio);
 | |
| 
 | |
| 		output->hpd_gpio = NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (output->hpd_gpio) {
 | |
| 		err = gpiod_to_irq(output->hpd_gpio);
 | |
| 		if (err < 0) {
 | |
| 			dev_err(output->dev, "gpiod_to_irq(): %d\n", err);
 | |
| 			return err;
 | |
| 		}
 | |
| 
 | |
| 		output->hpd_irq = err;
 | |
| 
 | |
| 		flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
 | |
| 			IRQF_ONESHOT;
 | |
| 
 | |
| 		err = request_threaded_irq(output->hpd_irq, NULL, hpd_irq,
 | |
| 					   flags, "hpd", output);
 | |
| 		if (err < 0) {
 | |
| 			dev_err(output->dev, "failed to request IRQ#%u: %d\n",
 | |
| 				output->hpd_irq, err);
 | |
| 			return err;
 | |
| 		}
 | |
| 
 | |
| 		output->connector.polled = DRM_CONNECTOR_POLL_HPD;
 | |
| 
 | |
| 		/*
 | |
| 		 * Disable the interrupt until the connector has been
 | |
| 		 * initialized to avoid a race in the hotplug interrupt
 | |
| 		 * handler.
 | |
| 		 */
 | |
| 		disable_irq(output->hpd_irq);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void tegra_output_remove(struct tegra_output *output)
 | |
| {
 | |
| 	if (output->hpd_gpio)
 | |
| 		free_irq(output->hpd_irq, output);
 | |
| 
 | |
| 	if (output->ddc)
 | |
| 		i2c_put_adapter(output->ddc);
 | |
| }
 | |
| 
 | |
| int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
 | |
| {
 | |
| 	int connector_type;
 | |
| 
 | |
| 	/*
 | |
| 	 * The connector is now registered and ready to receive hotplug events
 | |
| 	 * so the hotplug interrupt can be enabled.
 | |
| 	 */
 | |
| 	if (output->hpd_gpio)
 | |
| 		enable_irq(output->hpd_irq);
 | |
| 
 | |
| 	connector_type = output->connector.connector_type;
 | |
| 	/*
 | |
| 	 * Create a CEC notifier for HDMI connector.
 | |
| 	 */
 | |
| 	if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
 | |
| 	    connector_type == DRM_MODE_CONNECTOR_HDMIB) {
 | |
| 		struct cec_connector_info conn_info;
 | |
| 
 | |
| 		cec_fill_conn_info_from_drm(&conn_info, &output->connector);
 | |
| 		output->cec = cec_notifier_conn_register(output->dev, NULL,
 | |
| 							 &conn_info);
 | |
| 		if (!output->cec)
 | |
| 			return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void tegra_output_exit(struct tegra_output *output)
 | |
| {
 | |
| 	/*
 | |
| 	 * The connector is going away, so the interrupt must be disabled to
 | |
| 	 * prevent the hotplug interrupt handler from potentially crashing.
 | |
| 	 */
 | |
| 	if (output->hpd_gpio)
 | |
| 		disable_irq(output->hpd_irq);
 | |
| }
 | |
| 
 | |
| void tegra_output_find_possible_crtcs(struct tegra_output *output,
 | |
| 				      struct drm_device *drm)
 | |
| {
 | |
| 	struct device *dev = output->dev;
 | |
| 	struct drm_crtc *crtc;
 | |
| 	unsigned int mask = 0;
 | |
| 
 | |
| 	drm_for_each_crtc(crtc, drm) {
 | |
| 		struct tegra_dc *dc = to_tegra_dc(crtc);
 | |
| 
 | |
| 		if (tegra_dc_has_output(dc, dev))
 | |
| 			mask |= drm_crtc_mask(crtc);
 | |
| 	}
 | |
| 
 | |
| 	if (mask == 0) {
 | |
| 		dev_warn(dev, "missing output definition for heads in DT\n");
 | |
| 		mask = 0x3;
 | |
| 	}
 | |
| 
 | |
| 	output->encoder.possible_crtcs = mask;
 | |
| }
 | |
| 
 | |
| int tegra_output_suspend(struct tegra_output *output)
 | |
| {
 | |
| 	if (output->hpd_irq)
 | |
| 		disable_irq(output->hpd_irq);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int tegra_output_resume(struct tegra_output *output)
 | |
| {
 | |
| 	if (output->hpd_irq)
 | |
| 		enable_irq(output->hpd_irq);
 | |
| 
 | |
| 	return 0;
 | |
| }
 |