blob: e918341c0a32232f0fc45aba8eb77a290064590c [file] [log] [blame]
Miquel Raynal671a0e92025-04-03 09:39:11 +02001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Derived work from:
4 * Philippe Cornu <philippe.cornu@st.com>
5 * Yannick Fertre <yannick.fertre@st.com>
6 * Adapted by Miquel Raynal <miquel.raynal@bootlin.com>
7 */
8
9#define LOG_CATEGORY UCLASS_VIDEO_BRIDGE
10
11#include <clk.h>
12#include <dm.h>
13#include <log.h>
14#include <panel.h>
15#include <video_bridge.h>
16#include <asm/io.h>
17#include <linux/delay.h>
18
19#define LDB_CTRL_CH0_ENABLE BIT(0)
20#define LDB_CTRL_CH1_ENABLE BIT(2)
21#define LDB_CTRL_CH0_DATA_WIDTH BIT(5)
22#define LDB_CTRL_CH0_BIT_MAPPING BIT(6)
23#define LDB_CTRL_CH1_DATA_WIDTH BIT(7)
24#define LDB_CTRL_CH1_BIT_MAPPING BIT(8)
25#define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9)
26#define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10)
27
28#define LVDS_CTRL_CH0_EN BIT(0)
29#define LVDS_CTRL_CH1_EN BIT(1)
30#define LVDS_CTRL_VBG_EN BIT(2)
31#define LVDS_CTRL_PRE_EMPH_EN BIT(4)
32#define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5)
33#define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11)
34
35struct imx_ldb_priv {
36 struct clk ldb_clk;
37 void __iomem *ldb_ctrl;
38 void __iomem *lvds_ctrl;
39 struct udevice *lvds1;
40 struct udevice *lvds2;
41};
42
43static int imx_ldb_set_backlight(struct udevice *dev, int percent)
44{
45 struct imx_ldb_priv *priv = dev_get_priv(dev);
46 int ret;
47
48 if (priv->lvds1) {
49 ret = panel_enable_backlight(priv->lvds1);
50 if (ret) {
51 debug("ldb: Cannot enable lvds1 backlight\n");
52 return ret;
53 }
54
55 ret = panel_set_backlight(priv->lvds1, percent);
56 if (ret)
57 return ret;
58 }
59
60 if (priv->lvds2) {
61 ret = panel_enable_backlight(priv->lvds2);
62 if (ret) {
63 debug("ldb: Cannot enable lvds2 backlight\n");
64 return ret;
65 }
66
67 ret = panel_set_backlight(priv->lvds2, percent);
68 if (ret)
69 return ret;
70 }
71
72 return 0;
73}
74
75static int imx_ldb_of_to_plat(struct udevice *dev)
76{
77 struct imx_ldb_priv *priv = dev_get_priv(dev);
78 int ret;
79
80 uclass_get_device_by_endpoint(UCLASS_PANEL, dev, 1, -1, &priv->lvds1);
81 uclass_get_device_by_endpoint(UCLASS_PANEL, dev, 2, -1, &priv->lvds2);
82 if (!priv->lvds1 && !priv->lvds2) {
83 debug("ldb: No remote panel for '%s' (ret=%d)\n",
84 dev_read_name(dev), ret);
85 return ret;
86 }
87
88 return 0;
89}
90
91/* The block has a mysterious x7 internal divisor (x3.5 in dual configuration) */
92#define IMX_LDB_INTERNAL_DIVISOR(x) (((x) * 70) / 10)
93#define IMX_LDB_INTERNAL_DIVISOR_DUAL(x) (((x) * 35) / 10)
94
95static ulong imx_ldb_input_rate(struct imx_ldb_priv *priv,
96 struct display_timing *timings)
97{
98 ulong target_rate = timings->pixelclock.typ;
99
100 if (priv->lvds1 && priv->lvds2)
101 return IMX_LDB_INTERNAL_DIVISOR_DUAL(target_rate);
102
103 return IMX_LDB_INTERNAL_DIVISOR(target_rate);
104}
105
106static int imx_ldb_attach(struct udevice *dev)
107{
108 struct imx_ldb_priv *priv = dev_get_priv(dev);
109 struct display_timing timings;
110 bool format_jeida = false;
111 bool format_24bpp = true;
112 u32 ldb_ctrl = 0, lvds_ctrl;
113 ulong ldb_rate;
114 int ret;
115
116 /* TODO: update the 24bpp/jeida booleans with proper checks when they
117 * will be supported.
118 */
119 if (priv->lvds1) {
120 ret = panel_get_display_timing(priv->lvds1, &timings);
121 if (ret) {
122 ret = ofnode_decode_display_timing(dev_ofnode(priv->lvds1),
123 0, &timings);
124 if (ret) {
125 printf("Cannot decode lvds1 timings (%d)\n", ret);
126 return ret;
127 }
128 }
129
130 ldb_ctrl |= LDB_CTRL_CH0_ENABLE;
131 if (format_24bpp)
132 ldb_ctrl |= LDB_CTRL_CH0_DATA_WIDTH;
133 if (format_jeida)
134 ldb_ctrl |= LDB_CTRL_CH0_BIT_MAPPING;
135 if (timings.flags & DISPLAY_FLAGS_VSYNC_HIGH)
136 ldb_ctrl |= LDB_CTRL_DI0_VSYNC_POLARITY;
137 }
138
139 if (priv->lvds2) {
140 ret = panel_get_display_timing(priv->lvds2, &timings);
141 if (ret) {
142 ret = ofnode_decode_display_timing(dev_ofnode(priv->lvds2),
143 0, &timings);
144 if (ret) {
145 printf("Cannot decode lvds2 timings (%d)\n", ret);
146 return ret;
147 }
148 }
149
150 ldb_ctrl |= LDB_CTRL_CH1_ENABLE;
151 if (format_24bpp)
152 ldb_ctrl |= LDB_CTRL_CH1_DATA_WIDTH;
153 if (format_jeida)
154 ldb_ctrl |= LDB_CTRL_CH1_BIT_MAPPING;
155 if (timings.flags & DISPLAY_FLAGS_VSYNC_HIGH)
156 ldb_ctrl |= LDB_CTRL_DI1_VSYNC_POLARITY;
157 }
158
159 /*
160 * Not all pixel clocks will work, as the final rate (after internal
161 * integer division) should be identical to the LCDIF clock, otherwise
162 * the rendering will appear resized/shimmering.
163 */
164 ldb_rate = imx_ldb_input_rate(priv, &timings);
165 clk_set_rate(&priv->ldb_clk, ldb_rate);
166
167 writel(ldb_ctrl, priv->ldb_ctrl);
168
169 lvds_ctrl = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN |
170 LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN;
171 writel(lvds_ctrl, priv->lvds_ctrl);
172
173 /* Wait for VBG to stabilize. */
174 udelay(15);
175
176 if (priv->lvds1)
177 lvds_ctrl |= LVDS_CTRL_CH0_EN;
178 if (priv->lvds2)
179 lvds_ctrl |= LVDS_CTRL_CH1_EN;
180
181 writel(lvds_ctrl, priv->lvds_ctrl);
182
183 return 0;
184}
185
186static int imx_ldb_probe(struct udevice *dev)
187{
188 struct imx_ldb_priv *priv = dev_get_priv(dev);
189 struct udevice *parent = dev_get_parent(dev);
190 fdt_addr_t parent_addr, child_addr;
191 int ret;
192
193 ret = clk_get_by_name(dev, "ldb", &priv->ldb_clk);
194 if (ret < 0)
195 return ret;
196
197 parent_addr = dev_read_addr(parent);
198 if (parent_addr == FDT_ADDR_T_NONE)
199 return -EINVAL;
200
201 child_addr = dev_read_addr_name(dev, "ldb");
202 if (child_addr == FDT_ADDR_T_NONE)
203 return -EINVAL;
204
205 priv->ldb_ctrl = map_physmem(parent_addr + child_addr, 0, MAP_NOCACHE);
206 if (!priv->ldb_ctrl)
207 return -EINVAL;
208
209 child_addr = dev_read_addr_name(dev, "lvds");
210 if (child_addr == FDT_ADDR_T_NONE)
211 return -EINVAL;
212
213 priv->lvds_ctrl = map_physmem(parent_addr + child_addr, 0, MAP_NOCACHE);
214 if (!priv->lvds_ctrl)
215 return -EINVAL;
216
217 ret = clk_enable(&priv->ldb_clk);
218 if (ret)
219 return ret;
220
221 ret = video_bridge_set_active(dev, true);
222 if (ret)
223 goto dis_clk;
224
225 return 0;
226
227dis_clk:
228 clk_disable(&priv->ldb_clk);
229
230 return ret;
231}
232
233struct video_bridge_ops imx_ldb_ops = {
234 .attach = imx_ldb_attach,
235 .set_backlight = imx_ldb_set_backlight,
236};
237
238static const struct udevice_id imx_ldb_ids[] = {
239 { .compatible = "fsl,imx8mp-ldb"},
240 { }
241};
242
243U_BOOT_DRIVER(imx_ldb) = {
244 .name = "imx-lvds-display-bridge",
245 .id = UCLASS_VIDEO_BRIDGE,
246 .of_match = imx_ldb_ids,
247 .probe = imx_ldb_probe,
248 .of_to_plat = imx_ldb_of_to_plat,
249 .ops = &imx_ldb_ops,
250 .priv_auto = sizeof(struct imx_ldb_priv),
251};