Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v5.4-rc2 154 lines 4.0 kB view raw
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com> 4 */ 5 6#include <linux/gpio/consumer.h> 7#include <linux/module.h> 8#include <linux/of.h> 9#include <linux/of_graph.h> 10#include <linux/platform_device.h> 11 12#include <drm/drm_bridge.h> 13#include <drm/drm_panel.h> 14 15struct lvds_encoder { 16 struct drm_bridge bridge; 17 struct drm_bridge *panel_bridge; 18 struct gpio_desc *powerdown_gpio; 19}; 20 21static int lvds_encoder_attach(struct drm_bridge *bridge) 22{ 23 struct lvds_encoder *lvds_encoder = container_of(bridge, 24 struct lvds_encoder, 25 bridge); 26 27 return drm_bridge_attach(bridge->encoder, lvds_encoder->panel_bridge, 28 bridge); 29} 30 31static void lvds_encoder_enable(struct drm_bridge *bridge) 32{ 33 struct lvds_encoder *lvds_encoder = container_of(bridge, 34 struct lvds_encoder, 35 bridge); 36 37 if (lvds_encoder->powerdown_gpio) 38 gpiod_set_value_cansleep(lvds_encoder->powerdown_gpio, 0); 39} 40 41static void lvds_encoder_disable(struct drm_bridge *bridge) 42{ 43 struct lvds_encoder *lvds_encoder = container_of(bridge, 44 struct lvds_encoder, 45 bridge); 46 47 if (lvds_encoder->powerdown_gpio) 48 gpiod_set_value_cansleep(lvds_encoder->powerdown_gpio, 1); 49} 50 51static struct drm_bridge_funcs funcs = { 52 .attach = lvds_encoder_attach, 53 .enable = lvds_encoder_enable, 54 .disable = lvds_encoder_disable, 55}; 56 57static int lvds_encoder_probe(struct platform_device *pdev) 58{ 59 struct device *dev = &pdev->dev; 60 struct device_node *port; 61 struct device_node *endpoint; 62 struct device_node *panel_node; 63 struct drm_panel *panel; 64 struct lvds_encoder *lvds_encoder; 65 66 lvds_encoder = devm_kzalloc(dev, sizeof(*lvds_encoder), GFP_KERNEL); 67 if (!lvds_encoder) 68 return -ENOMEM; 69 70 lvds_encoder->powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown", 71 GPIOD_OUT_HIGH); 72 if (IS_ERR(lvds_encoder->powerdown_gpio)) { 73 int err = PTR_ERR(lvds_encoder->powerdown_gpio); 74 75 if (err != -EPROBE_DEFER) 76 dev_err(dev, "powerdown GPIO failure: %d\n", err); 77 return err; 78 } 79 80 /* Locate the panel DT node. */ 81 port = of_graph_get_port_by_id(dev->of_node, 1); 82 if (!port) { 83 dev_dbg(dev, "port 1 not found\n"); 84 return -ENXIO; 85 } 86 87 endpoint = of_get_child_by_name(port, "endpoint"); 88 of_node_put(port); 89 if (!endpoint) { 90 dev_dbg(dev, "no endpoint for port 1\n"); 91 return -ENXIO; 92 } 93 94 panel_node = of_graph_get_remote_port_parent(endpoint); 95 of_node_put(endpoint); 96 if (!panel_node) { 97 dev_dbg(dev, "no remote endpoint for port 1\n"); 98 return -ENXIO; 99 } 100 101 panel = of_drm_find_panel(panel_node); 102 of_node_put(panel_node); 103 if (IS_ERR(panel)) { 104 dev_dbg(dev, "panel not found, deferring probe\n"); 105 return PTR_ERR(panel); 106 } 107 108 lvds_encoder->panel_bridge = 109 devm_drm_panel_bridge_add(dev, panel, DRM_MODE_CONNECTOR_LVDS); 110 if (IS_ERR(lvds_encoder->panel_bridge)) 111 return PTR_ERR(lvds_encoder->panel_bridge); 112 113 /* The panel_bridge bridge is attached to the panel's of_node, 114 * but we need a bridge attached to our of_node for our user 115 * to look up. 116 */ 117 lvds_encoder->bridge.of_node = dev->of_node; 118 lvds_encoder->bridge.funcs = &funcs; 119 drm_bridge_add(&lvds_encoder->bridge); 120 121 platform_set_drvdata(pdev, lvds_encoder); 122 123 return 0; 124} 125 126static int lvds_encoder_remove(struct platform_device *pdev) 127{ 128 struct lvds_encoder *lvds_encoder = platform_get_drvdata(pdev); 129 130 drm_bridge_remove(&lvds_encoder->bridge); 131 132 return 0; 133} 134 135static const struct of_device_id lvds_encoder_match[] = { 136 { .compatible = "lvds-encoder" }, 137 { .compatible = "thine,thc63lvdm83d" }, 138 {}, 139}; 140MODULE_DEVICE_TABLE(of, lvds_encoder_match); 141 142static struct platform_driver lvds_encoder_driver = { 143 .probe = lvds_encoder_probe, 144 .remove = lvds_encoder_remove, 145 .driver = { 146 .name = "lvds-encoder", 147 .of_match_table = lvds_encoder_match, 148 }, 149}; 150module_platform_driver(lvds_encoder_driver); 151 152MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); 153MODULE_DESCRIPTION("Transparent parallel to LVDS encoder"); 154MODULE_LICENSE("GPL");