1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (c) 2025 Svyatoslav Ryhel <clamor95@gmail.com>
4  * Loosely based on Linux lvds-codec.c driver
5  */
6 
7 #include <dm.h>
8 #include <dm/ofnode_graph.h>
9 #include <log.h>
10 #include <panel.h>
11 #include <video_bridge.h>
12 #include <asm/gpio.h>
13 #include <power/regulator.h>
14 
15 struct lvds_codec_priv {
16 	struct udevice *panel;
17 	struct display_timing timing;
18 
19 	struct gpio_desc powerdown_gpio;
20 	struct udevice *power;
21 };
22 
lvds_codec_attach(struct udevice * dev)23 static int lvds_codec_attach(struct udevice *dev)
24 {
25 	struct lvds_codec_priv *priv = dev_get_priv(dev);
26 
27 	regulator_set_enable_if_allowed(priv->power, 1);
28 	dm_gpio_set_value(&priv->powerdown_gpio, 0);
29 
30 	return panel_enable_backlight(priv->panel);
31 }
32 
lvds_codec_set_panel(struct udevice * dev,int percent)33 static int lvds_codec_set_panel(struct udevice *dev, int percent)
34 {
35 	struct lvds_codec_priv *priv = dev_get_priv(dev);
36 
37 	return panel_set_backlight(priv->panel, percent);
38 }
39 
lvds_codec_panel_timings(struct udevice * dev,struct display_timing * timing)40 static int lvds_codec_panel_timings(struct udevice *dev,
41 				    struct display_timing *timing)
42 {
43 	struct lvds_codec_priv *priv = dev_get_priv(dev);
44 
45 	memcpy(timing, &priv->timing, sizeof(*timing));
46 
47 	return 0;
48 }
49 
50 /* Function is purely for sandbox testing */
lvds_codec_read_edid(struct udevice * dev,u8 * buf,int buf_size)51 static int lvds_codec_read_edid(struct udevice *dev, u8 *buf, int buf_size)
52 {
53 	return 0;
54 }
55 
lvds_codec_get_panel(struct udevice * dev)56 static int lvds_codec_get_panel(struct udevice *dev)
57 {
58 	struct lvds_codec_priv *priv = dev_get_priv(dev);
59 	int i, ret;
60 
61 	u32 num = ofnode_graph_get_port_count(dev_ofnode(dev));
62 
63 	for (i = 0; i < num; i++) {
64 		ofnode remote = ofnode_graph_get_remote_node(dev_ofnode(dev), i, -1);
65 
66 		ret = uclass_get_device_by_of_offset(UCLASS_PANEL,
67 						     ofnode_to_offset(remote),
68 						     &priv->panel);
69 		if (!ret)
70 			return 0;
71 	}
72 
73 	/* If this point is reached, no panels were found */
74 	return -ENODEV;
75 }
76 
lvds_codec_probe(struct udevice * dev)77 static int lvds_codec_probe(struct udevice *dev)
78 {
79 	struct lvds_codec_priv *priv = dev_get_priv(dev);
80 	int ret;
81 
82 	ret = lvds_codec_get_panel(dev);
83 	if (ret) {
84 		log_debug("%s: cannot get panel: ret=%d\n", __func__, ret);
85 		return log_ret(ret);
86 	}
87 
88 	panel_get_display_timing(priv->panel, &priv->timing);
89 
90 	ret = gpio_request_by_name(dev, "powerdown-gpios", 0,
91 				   &priv->powerdown_gpio, GPIOD_IS_OUT);
92 	if (ret) {
93 		log_debug("%s: could not get powerdown-gpios (%d)\n", __func__, ret);
94 		if (ret != -ENOENT)
95 			return log_ret(ret);
96 	}
97 
98 	ret = device_get_supply_regulator(dev, "power-supply", &priv->power);
99 	if (ret) {
100 		log_debug("%s: power regulator error: %d\n", __func__, ret);
101 		if (ret != -ENOENT)
102 			return log_ret(ret);
103 	}
104 
105 	return 0;
106 }
107 
108 static const struct video_bridge_ops lvds_codec_ops = {
109 	.attach			= lvds_codec_attach,
110 	.set_backlight		= lvds_codec_set_panel,
111 	.get_display_timing	= lvds_codec_panel_timings,
112 	.read_edid		= lvds_codec_read_edid,
113 };
114 
115 static const struct udevice_id lvds_codec_ids[] = {
116 	{ .compatible = "lvds-decoder" },
117 	{ .compatible = "lvds-encoder" },
118 	{ }
119 };
120 
121 U_BOOT_DRIVER(lvds_codec) = {
122 	.name		= "lvds_codec",
123 	.id		= UCLASS_VIDEO_BRIDGE,
124 	.of_match	= lvds_codec_ids,
125 	.ops		= &lvds_codec_ops,
126 	.probe		= lvds_codec_probe,
127 	.priv_auto	= sizeof(struct lvds_codec_priv),
128 };
129