162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2012 Avionic Design GmbH 462306a36Sopenharmony_ci * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/i2c.h> 862306a36Sopenharmony_ci#include <linux/of.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h> 1162306a36Sopenharmony_ci#include <drm/drm_of.h> 1262306a36Sopenharmony_ci#include <drm/drm_panel.h> 1362306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "drm.h" 1662306a36Sopenharmony_ci#include "dc.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <media/cec-notifier.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ciint tegra_output_connector_get_modes(struct drm_connector *connector) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci struct tegra_output *output = connector_to_output(connector); 2362306a36Sopenharmony_ci struct edid *edid = NULL; 2462306a36Sopenharmony_ci int err = 0; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci /* 2762306a36Sopenharmony_ci * If the panel provides one or more modes, use them exclusively and 2862306a36Sopenharmony_ci * ignore any other means of obtaining a mode. 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_ci if (output->panel) { 3162306a36Sopenharmony_ci err = drm_panel_get_modes(output->panel, connector); 3262306a36Sopenharmony_ci if (err > 0) 3362306a36Sopenharmony_ci return err; 3462306a36Sopenharmony_ci } 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (output->edid) 3762306a36Sopenharmony_ci edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL); 3862306a36Sopenharmony_ci else if (output->ddc) 3962306a36Sopenharmony_ci edid = drm_get_edid(connector, output->ddc); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci cec_notifier_set_phys_addr_from_edid(output->cec, edid); 4262306a36Sopenharmony_ci drm_connector_update_edid_property(connector, edid); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci if (edid) { 4562306a36Sopenharmony_ci err = drm_add_edid_modes(connector, edid); 4662306a36Sopenharmony_ci kfree(edid); 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci return err; 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cienum drm_connector_status 5362306a36Sopenharmony_citegra_output_connector_detect(struct drm_connector *connector, bool force) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct tegra_output *output = connector_to_output(connector); 5662306a36Sopenharmony_ci enum drm_connector_status status = connector_status_unknown; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci if (output->hpd_gpio) { 5962306a36Sopenharmony_ci if (gpiod_get_value(output->hpd_gpio) == 0) 6062306a36Sopenharmony_ci status = connector_status_disconnected; 6162306a36Sopenharmony_ci else 6262306a36Sopenharmony_ci status = connector_status_connected; 6362306a36Sopenharmony_ci } else { 6462306a36Sopenharmony_ci if (!output->panel) 6562306a36Sopenharmony_ci status = connector_status_disconnected; 6662306a36Sopenharmony_ci else 6762306a36Sopenharmony_ci status = connector_status_connected; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (status != connector_status_connected) 7162306a36Sopenharmony_ci cec_notifier_phys_addr_invalidate(output->cec); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return status; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_civoid tegra_output_connector_destroy(struct drm_connector *connector) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct tegra_output *output = connector_to_output(connector); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (output->cec) 8162306a36Sopenharmony_ci cec_notifier_conn_unregister(output->cec); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci drm_connector_unregister(connector); 8462306a36Sopenharmony_ci drm_connector_cleanup(connector); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic irqreturn_t hpd_irq(int irq, void *data) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci struct tegra_output *output = data; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (output->connector.dev) 9262306a36Sopenharmony_ci drm_helper_hpd_irq_event(output->connector.dev); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return IRQ_HANDLED; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ciint tegra_output_probe(struct tegra_output *output) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct device_node *ddc, *panel; 10062306a36Sopenharmony_ci unsigned long flags; 10162306a36Sopenharmony_ci int err, size; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (!output->of_node) 10462306a36Sopenharmony_ci output->of_node = output->dev->of_node; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci err = drm_of_find_panel_or_bridge(output->of_node, -1, -1, 10762306a36Sopenharmony_ci &output->panel, &output->bridge); 10862306a36Sopenharmony_ci if (err && err != -ENODEV) 10962306a36Sopenharmony_ci return err; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci panel = of_parse_phandle(output->of_node, "nvidia,panel", 0); 11262306a36Sopenharmony_ci if (panel) { 11362306a36Sopenharmony_ci /* 11462306a36Sopenharmony_ci * Don't mix nvidia,panel phandle with the graph in a 11562306a36Sopenharmony_ci * device-tree. 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_ci WARN_ON(output->panel || output->bridge); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci output->panel = of_drm_find_panel(panel); 12062306a36Sopenharmony_ci of_node_put(panel); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (IS_ERR(output->panel)) 12362306a36Sopenharmony_ci return PTR_ERR(output->panel); 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci output->edid = of_get_property(output->of_node, "nvidia,edid", &size); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0); 12962306a36Sopenharmony_ci if (ddc) { 13062306a36Sopenharmony_ci output->ddc = of_get_i2c_adapter_by_node(ddc); 13162306a36Sopenharmony_ci of_node_put(ddc); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!output->ddc) { 13462306a36Sopenharmony_ci err = -EPROBE_DEFER; 13562306a36Sopenharmony_ci return err; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci output->hpd_gpio = devm_fwnode_gpiod_get(output->dev, 14062306a36Sopenharmony_ci of_fwnode_handle(output->of_node), 14162306a36Sopenharmony_ci "nvidia,hpd", 14262306a36Sopenharmony_ci GPIOD_IN, 14362306a36Sopenharmony_ci "HDMI hotplug detect"); 14462306a36Sopenharmony_ci if (IS_ERR(output->hpd_gpio)) { 14562306a36Sopenharmony_ci if (PTR_ERR(output->hpd_gpio) != -ENOENT) { 14662306a36Sopenharmony_ci err = PTR_ERR(output->hpd_gpio); 14762306a36Sopenharmony_ci goto put_i2c; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci output->hpd_gpio = NULL; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (output->hpd_gpio) { 15462306a36Sopenharmony_ci err = gpiod_to_irq(output->hpd_gpio); 15562306a36Sopenharmony_ci if (err < 0) { 15662306a36Sopenharmony_ci dev_err(output->dev, "gpiod_to_irq(): %d\n", err); 15762306a36Sopenharmony_ci goto put_i2c; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci output->hpd_irq = err; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | 16362306a36Sopenharmony_ci IRQF_ONESHOT; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci err = request_threaded_irq(output->hpd_irq, NULL, hpd_irq, 16662306a36Sopenharmony_ci flags, "hpd", output); 16762306a36Sopenharmony_ci if (err < 0) { 16862306a36Sopenharmony_ci dev_err(output->dev, "failed to request IRQ#%u: %d\n", 16962306a36Sopenharmony_ci output->hpd_irq, err); 17062306a36Sopenharmony_ci goto put_i2c; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci output->connector.polled = DRM_CONNECTOR_POLL_HPD; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* 17662306a36Sopenharmony_ci * Disable the interrupt until the connector has been 17762306a36Sopenharmony_ci * initialized to avoid a race in the hotplug interrupt 17862306a36Sopenharmony_ci * handler. 17962306a36Sopenharmony_ci */ 18062306a36Sopenharmony_ci disable_irq(output->hpd_irq); 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciput_i2c: 18662306a36Sopenharmony_ci if (output->ddc) 18762306a36Sopenharmony_ci i2c_put_adapter(output->ddc); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return err; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_civoid tegra_output_remove(struct tegra_output *output) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci if (output->hpd_gpio) 19562306a36Sopenharmony_ci free_irq(output->hpd_irq, output); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (output->ddc) 19862306a36Sopenharmony_ci i2c_put_adapter(output->ddc); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ciint tegra_output_init(struct drm_device *drm, struct tegra_output *output) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci int connector_type; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci /* 20662306a36Sopenharmony_ci * The connector is now registered and ready to receive hotplug events 20762306a36Sopenharmony_ci * so the hotplug interrupt can be enabled. 20862306a36Sopenharmony_ci */ 20962306a36Sopenharmony_ci if (output->hpd_gpio) 21062306a36Sopenharmony_ci enable_irq(output->hpd_irq); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci connector_type = output->connector.connector_type; 21362306a36Sopenharmony_ci /* 21462306a36Sopenharmony_ci * Create a CEC notifier for HDMI connector. 21562306a36Sopenharmony_ci */ 21662306a36Sopenharmony_ci if (connector_type == DRM_MODE_CONNECTOR_HDMIA || 21762306a36Sopenharmony_ci connector_type == DRM_MODE_CONNECTOR_HDMIB) { 21862306a36Sopenharmony_ci struct cec_connector_info conn_info; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci cec_fill_conn_info_from_drm(&conn_info, &output->connector); 22162306a36Sopenharmony_ci output->cec = cec_notifier_conn_register(output->dev, NULL, 22262306a36Sopenharmony_ci &conn_info); 22362306a36Sopenharmony_ci if (!output->cec) 22462306a36Sopenharmony_ci return -ENOMEM; 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_civoid tegra_output_exit(struct tegra_output *output) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci /* 23362306a36Sopenharmony_ci * The connector is going away, so the interrupt must be disabled to 23462306a36Sopenharmony_ci * prevent the hotplug interrupt handler from potentially crashing. 23562306a36Sopenharmony_ci */ 23662306a36Sopenharmony_ci if (output->hpd_gpio) 23762306a36Sopenharmony_ci disable_irq(output->hpd_irq); 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_civoid tegra_output_find_possible_crtcs(struct tegra_output *output, 24162306a36Sopenharmony_ci struct drm_device *drm) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci struct device *dev = output->dev; 24462306a36Sopenharmony_ci struct drm_crtc *crtc; 24562306a36Sopenharmony_ci unsigned int mask = 0; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci drm_for_each_crtc(crtc, drm) { 24862306a36Sopenharmony_ci struct tegra_dc *dc = to_tegra_dc(crtc); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci if (tegra_dc_has_output(dc, dev)) 25162306a36Sopenharmony_ci mask |= drm_crtc_mask(crtc); 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (mask == 0) { 25562306a36Sopenharmony_ci dev_warn(dev, "missing output definition for heads in DT\n"); 25662306a36Sopenharmony_ci mask = 0x3; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci output->encoder.possible_crtcs = mask; 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ciint tegra_output_suspend(struct tegra_output *output) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci if (output->hpd_irq) 26562306a36Sopenharmony_ci disable_irq(output->hpd_irq); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ciint tegra_output_resume(struct tegra_output *output) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci if (output->hpd_irq) 27362306a36Sopenharmony_ci enable_irq(output->hpd_irq); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci return 0; 27662306a36Sopenharmony_ci} 277