1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2022 Marek Vasut <marex@denx.de> |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/media-bus-format.h> |
8 | #include <linux/mfd/syscon.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_graph.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/regmap.h> |
14 | |
15 | #include <drm/drm_atomic_helper.h> |
16 | #include <drm/drm_bridge.h> |
17 | #include <drm/drm_of.h> |
18 | #include <drm/drm_panel.h> |
19 | |
20 | #define LDB_CTRL_CH0_ENABLE BIT(0) |
21 | #define LDB_CTRL_CH0_DI_SELECT BIT(1) |
22 | #define LDB_CTRL_CH1_ENABLE BIT(2) |
23 | #define LDB_CTRL_CH1_DI_SELECT BIT(3) |
24 | #define LDB_CTRL_SPLIT_MODE BIT(4) |
25 | #define LDB_CTRL_CH0_DATA_WIDTH BIT(5) |
26 | #define LDB_CTRL_CH0_BIT_MAPPING BIT(6) |
27 | #define LDB_CTRL_CH1_DATA_WIDTH BIT(7) |
28 | #define LDB_CTRL_CH1_BIT_MAPPING BIT(8) |
29 | #define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9) |
30 | #define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10) |
31 | #define LDB_CTRL_REG_CH0_FIFO_RESET BIT(11) |
32 | #define LDB_CTRL_REG_CH1_FIFO_RESET BIT(12) |
33 | #define LDB_CTRL_ASYNC_FIFO_ENABLE BIT(24) |
34 | #define LDB_CTRL_ASYNC_FIFO_THRESHOLD_MASK GENMASK(27, 25) |
35 | |
36 | #define LVDS_CTRL_CH0_EN BIT(0) |
37 | #define LVDS_CTRL_CH1_EN BIT(1) |
38 | /* |
39 | * LVDS_CTRL_LVDS_EN bit is poorly named in i.MX93 reference manual. |
40 | * Clear it to enable LVDS and set it to disable LVDS. |
41 | */ |
42 | #define LVDS_CTRL_LVDS_EN BIT(1) |
43 | #define LVDS_CTRL_VBG_EN BIT(2) |
44 | #define LVDS_CTRL_HS_EN BIT(3) |
45 | #define LVDS_CTRL_PRE_EMPH_EN BIT(4) |
46 | #define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5) |
47 | #define LVDS_CTRL_PRE_EMPH_ADJ_MASK GENMASK(7, 5) |
48 | #define LVDS_CTRL_CM_ADJ(n) (((n) & 0x7) << 8) |
49 | #define LVDS_CTRL_CM_ADJ_MASK GENMASK(10, 8) |
50 | #define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11) |
51 | #define LVDS_CTRL_CC_ADJ_MASK GENMASK(13, 11) |
52 | #define LVDS_CTRL_SLEW_ADJ(n) (((n) & 0x7) << 14) |
53 | #define LVDS_CTRL_SLEW_ADJ_MASK GENMASK(16, 14) |
54 | #define LVDS_CTRL_VBG_ADJ(n) (((n) & 0x7) << 17) |
55 | #define LVDS_CTRL_VBG_ADJ_MASK GENMASK(19, 17) |
56 | |
57 | enum fsl_ldb_devtype { |
58 | IMX6SX_LDB, |
59 | IMX8MP_LDB, |
60 | IMX93_LDB, |
61 | }; |
62 | |
63 | struct fsl_ldb_devdata { |
64 | u32 ldb_ctrl; |
65 | u32 lvds_ctrl; |
66 | bool lvds_en_bit; |
67 | bool single_ctrl_reg; |
68 | }; |
69 | |
70 | static const struct fsl_ldb_devdata fsl_ldb_devdata[] = { |
71 | [IMX6SX_LDB] = { |
72 | .ldb_ctrl = 0x18, |
73 | .single_ctrl_reg = true, |
74 | }, |
75 | [IMX8MP_LDB] = { |
76 | .ldb_ctrl = 0x5c, |
77 | .lvds_ctrl = 0x128, |
78 | }, |
79 | [IMX93_LDB] = { |
80 | .ldb_ctrl = 0x20, |
81 | .lvds_ctrl = 0x24, |
82 | .lvds_en_bit = true, |
83 | }, |
84 | }; |
85 | |
86 | struct fsl_ldb { |
87 | struct device *dev; |
88 | struct drm_bridge bridge; |
89 | struct drm_bridge *panel_bridge; |
90 | struct clk *clk; |
91 | struct regmap *regmap; |
92 | const struct fsl_ldb_devdata *devdata; |
93 | bool ch0_enabled; |
94 | bool ch1_enabled; |
95 | }; |
96 | |
97 | static bool fsl_ldb_is_dual(const struct fsl_ldb *fsl_ldb) |
98 | { |
99 | return (fsl_ldb->ch0_enabled && fsl_ldb->ch1_enabled); |
100 | } |
101 | |
102 | static inline struct fsl_ldb *to_fsl_ldb(struct drm_bridge *bridge) |
103 | { |
104 | return container_of(bridge, struct fsl_ldb, bridge); |
105 | } |
106 | |
107 | static unsigned long fsl_ldb_link_frequency(struct fsl_ldb *fsl_ldb, int clock) |
108 | { |
109 | if (fsl_ldb_is_dual(fsl_ldb)) |
110 | return clock * 3500; |
111 | else |
112 | return clock * 7000; |
113 | } |
114 | |
115 | static int fsl_ldb_attach(struct drm_bridge *bridge, |
116 | enum drm_bridge_attach_flags flags) |
117 | { |
118 | struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); |
119 | |
120 | return drm_bridge_attach(encoder: bridge->encoder, bridge: fsl_ldb->panel_bridge, |
121 | previous: bridge, flags); |
122 | } |
123 | |
124 | static void fsl_ldb_atomic_enable(struct drm_bridge *bridge, |
125 | struct drm_bridge_state *old_bridge_state) |
126 | { |
127 | struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); |
128 | struct drm_atomic_state *state = old_bridge_state->base.state; |
129 | const struct drm_bridge_state *bridge_state; |
130 | const struct drm_crtc_state *crtc_state; |
131 | const struct drm_display_mode *mode; |
132 | struct drm_connector *connector; |
133 | struct drm_crtc *crtc; |
134 | unsigned long configured_link_freq; |
135 | unsigned long requested_link_freq; |
136 | bool lvds_format_24bpp; |
137 | bool lvds_format_jeida; |
138 | u32 reg; |
139 | |
140 | /* Get the LVDS format from the bridge state. */ |
141 | bridge_state = drm_atomic_get_new_bridge_state(state, bridge); |
142 | |
143 | switch (bridge_state->output_bus_cfg.format) { |
144 | case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: |
145 | lvds_format_24bpp = false; |
146 | lvds_format_jeida = true; |
147 | break; |
148 | case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: |
149 | lvds_format_24bpp = true; |
150 | lvds_format_jeida = true; |
151 | break; |
152 | case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: |
153 | lvds_format_24bpp = true; |
154 | lvds_format_jeida = false; |
155 | break; |
156 | default: |
157 | /* |
158 | * Some bridges still don't set the correct LVDS bus pixel |
159 | * format, use SPWG24 default format until those are fixed. |
160 | */ |
161 | lvds_format_24bpp = true; |
162 | lvds_format_jeida = false; |
163 | dev_warn(fsl_ldb->dev, |
164 | "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n" , |
165 | bridge_state->output_bus_cfg.format); |
166 | break; |
167 | } |
168 | |
169 | /* |
170 | * Retrieve the CRTC adjusted mode. This requires a little dance to go |
171 | * from the bridge to the encoder, to the connector and to the CRTC. |
172 | */ |
173 | connector = drm_atomic_get_new_connector_for_encoder(state, |
174 | encoder: bridge->encoder); |
175 | crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; |
176 | crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
177 | mode = &crtc_state->adjusted_mode; |
178 | |
179 | requested_link_freq = fsl_ldb_link_frequency(fsl_ldb, clock: mode->clock); |
180 | clk_set_rate(clk: fsl_ldb->clk, rate: requested_link_freq); |
181 | |
182 | configured_link_freq = clk_get_rate(clk: fsl_ldb->clk); |
183 | if (configured_link_freq != requested_link_freq) |
184 | dev_warn(fsl_ldb->dev, "Configured LDB clock (%lu Hz) does not match requested LVDS clock: %lu Hz\n" , |
185 | configured_link_freq, |
186 | requested_link_freq); |
187 | |
188 | clk_prepare_enable(clk: fsl_ldb->clk); |
189 | |
190 | /* Program LDB_CTRL */ |
191 | reg = (fsl_ldb->ch0_enabled ? LDB_CTRL_CH0_ENABLE : 0) | |
192 | (fsl_ldb->ch1_enabled ? LDB_CTRL_CH1_ENABLE : 0) | |
193 | (fsl_ldb_is_dual(fsl_ldb) ? LDB_CTRL_SPLIT_MODE : 0); |
194 | |
195 | if (lvds_format_24bpp) |
196 | reg |= (fsl_ldb->ch0_enabled ? LDB_CTRL_CH0_DATA_WIDTH : 0) | |
197 | (fsl_ldb->ch1_enabled ? LDB_CTRL_CH1_DATA_WIDTH : 0); |
198 | |
199 | if (lvds_format_jeida) |
200 | reg |= (fsl_ldb->ch0_enabled ? LDB_CTRL_CH0_BIT_MAPPING : 0) | |
201 | (fsl_ldb->ch1_enabled ? LDB_CTRL_CH1_BIT_MAPPING : 0); |
202 | |
203 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
204 | reg |= (fsl_ldb->ch0_enabled ? LDB_CTRL_DI0_VSYNC_POLARITY : 0) | |
205 | (fsl_ldb->ch1_enabled ? LDB_CTRL_DI1_VSYNC_POLARITY : 0); |
206 | |
207 | regmap_write(map: fsl_ldb->regmap, reg: fsl_ldb->devdata->ldb_ctrl, val: reg); |
208 | |
209 | if (fsl_ldb->devdata->single_ctrl_reg) |
210 | return; |
211 | |
212 | /* Program LVDS_CTRL */ |
213 | reg = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN | |
214 | LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN; |
215 | regmap_write(map: fsl_ldb->regmap, reg: fsl_ldb->devdata->lvds_ctrl, val: reg); |
216 | |
217 | /* Wait for VBG to stabilize. */ |
218 | usleep_range(min: 15, max: 20); |
219 | |
220 | reg |= (fsl_ldb->ch0_enabled ? LVDS_CTRL_CH0_EN : 0) | |
221 | (fsl_ldb->ch1_enabled ? LVDS_CTRL_CH1_EN : 0); |
222 | |
223 | regmap_write(map: fsl_ldb->regmap, reg: fsl_ldb->devdata->lvds_ctrl, val: reg); |
224 | } |
225 | |
226 | static void fsl_ldb_atomic_disable(struct drm_bridge *bridge, |
227 | struct drm_bridge_state *old_bridge_state) |
228 | { |
229 | struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); |
230 | |
231 | /* Stop channel(s). */ |
232 | if (fsl_ldb->devdata->lvds_en_bit) |
233 | /* Set LVDS_CTRL_LVDS_EN bit to disable. */ |
234 | regmap_write(map: fsl_ldb->regmap, reg: fsl_ldb->devdata->lvds_ctrl, |
235 | LVDS_CTRL_LVDS_EN); |
236 | else |
237 | if (!fsl_ldb->devdata->single_ctrl_reg) |
238 | regmap_write(map: fsl_ldb->regmap, reg: fsl_ldb->devdata->lvds_ctrl, val: 0); |
239 | regmap_write(map: fsl_ldb->regmap, reg: fsl_ldb->devdata->ldb_ctrl, val: 0); |
240 | |
241 | clk_disable_unprepare(clk: fsl_ldb->clk); |
242 | } |
243 | |
244 | #define MAX_INPUT_SEL_FORMATS 1 |
245 | static u32 * |
246 | fsl_ldb_atomic_get_input_bus_fmts(struct drm_bridge *bridge, |
247 | struct drm_bridge_state *bridge_state, |
248 | struct drm_crtc_state *crtc_state, |
249 | struct drm_connector_state *conn_state, |
250 | u32 output_fmt, |
251 | unsigned int *num_input_fmts) |
252 | { |
253 | u32 *input_fmts; |
254 | |
255 | *num_input_fmts = 0; |
256 | |
257 | input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, size: sizeof(*input_fmts), |
258 | GFP_KERNEL); |
259 | if (!input_fmts) |
260 | return NULL; |
261 | |
262 | input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; |
263 | *num_input_fmts = MAX_INPUT_SEL_FORMATS; |
264 | |
265 | return input_fmts; |
266 | } |
267 | |
268 | static enum drm_mode_status |
269 | fsl_ldb_mode_valid(struct drm_bridge *bridge, |
270 | const struct drm_display_info *info, |
271 | const struct drm_display_mode *mode) |
272 | { |
273 | struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); |
274 | |
275 | if (mode->clock > (fsl_ldb_is_dual(fsl_ldb) ? 160000 : 80000)) |
276 | return MODE_CLOCK_HIGH; |
277 | |
278 | return MODE_OK; |
279 | } |
280 | |
281 | static const struct drm_bridge_funcs funcs = { |
282 | .attach = fsl_ldb_attach, |
283 | .atomic_enable = fsl_ldb_atomic_enable, |
284 | .atomic_disable = fsl_ldb_atomic_disable, |
285 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, |
286 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, |
287 | .atomic_get_input_bus_fmts = fsl_ldb_atomic_get_input_bus_fmts, |
288 | .atomic_reset = drm_atomic_helper_bridge_reset, |
289 | .mode_valid = fsl_ldb_mode_valid, |
290 | }; |
291 | |
292 | static int fsl_ldb_probe(struct platform_device *pdev) |
293 | { |
294 | struct device *dev = &pdev->dev; |
295 | struct device_node *panel_node; |
296 | struct device_node *remote1, *remote2; |
297 | struct drm_panel *panel; |
298 | struct fsl_ldb *fsl_ldb; |
299 | int dual_link; |
300 | |
301 | fsl_ldb = devm_kzalloc(dev, size: sizeof(*fsl_ldb), GFP_KERNEL); |
302 | if (!fsl_ldb) |
303 | return -ENOMEM; |
304 | |
305 | fsl_ldb->devdata = of_device_get_match_data(dev); |
306 | if (!fsl_ldb->devdata) |
307 | return -EINVAL; |
308 | |
309 | fsl_ldb->dev = &pdev->dev; |
310 | fsl_ldb->bridge.funcs = &funcs; |
311 | fsl_ldb->bridge.of_node = dev->of_node; |
312 | |
313 | fsl_ldb->clk = devm_clk_get(dev, id: "ldb" ); |
314 | if (IS_ERR(ptr: fsl_ldb->clk)) |
315 | return PTR_ERR(ptr: fsl_ldb->clk); |
316 | |
317 | fsl_ldb->regmap = syscon_node_to_regmap(np: dev->of_node->parent); |
318 | if (IS_ERR(ptr: fsl_ldb->regmap)) |
319 | return PTR_ERR(ptr: fsl_ldb->regmap); |
320 | |
321 | /* Locate the remote ports and the panel node */ |
322 | remote1 = of_graph_get_remote_node(node: dev->of_node, port: 1, endpoint: 0); |
323 | remote2 = of_graph_get_remote_node(node: dev->of_node, port: 2, endpoint: 0); |
324 | fsl_ldb->ch0_enabled = (remote1 != NULL); |
325 | fsl_ldb->ch1_enabled = (remote2 != NULL); |
326 | panel_node = of_node_get(node: remote1 ? remote1 : remote2); |
327 | of_node_put(node: remote1); |
328 | of_node_put(node: remote2); |
329 | |
330 | if (!fsl_ldb->ch0_enabled && !fsl_ldb->ch1_enabled) { |
331 | of_node_put(node: panel_node); |
332 | return dev_err_probe(dev, err: -ENXIO, fmt: "No panel node found" ); |
333 | } |
334 | |
335 | dev_dbg(dev, "Using %s\n" , |
336 | fsl_ldb_is_dual(fsl_ldb) ? "dual-link mode" : |
337 | fsl_ldb->ch0_enabled ? "channel 0" : "channel 1" ); |
338 | |
339 | panel = of_drm_find_panel(np: panel_node); |
340 | of_node_put(node: panel_node); |
341 | if (IS_ERR(ptr: panel)) |
342 | return PTR_ERR(ptr: panel); |
343 | |
344 | fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel); |
345 | if (IS_ERR(ptr: fsl_ldb->panel_bridge)) |
346 | return PTR_ERR(ptr: fsl_ldb->panel_bridge); |
347 | |
348 | |
349 | if (fsl_ldb_is_dual(fsl_ldb)) { |
350 | struct device_node *port1, *port2; |
351 | |
352 | port1 = of_graph_get_port_by_id(node: dev->of_node, id: 1); |
353 | port2 = of_graph_get_port_by_id(node: dev->of_node, id: 2); |
354 | dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); |
355 | of_node_put(node: port1); |
356 | of_node_put(node: port2); |
357 | |
358 | if (dual_link < 0) |
359 | return dev_err_probe(dev, err: dual_link, |
360 | fmt: "Error getting dual link configuration\n" ); |
361 | |
362 | /* Only DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS is supported */ |
363 | if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) { |
364 | dev_err(dev, "LVDS channel pixel swap not supported.\n" ); |
365 | return -EINVAL; |
366 | } |
367 | } |
368 | |
369 | platform_set_drvdata(pdev, data: fsl_ldb); |
370 | |
371 | drm_bridge_add(bridge: &fsl_ldb->bridge); |
372 | |
373 | return 0; |
374 | } |
375 | |
376 | static void fsl_ldb_remove(struct platform_device *pdev) |
377 | { |
378 | struct fsl_ldb *fsl_ldb = platform_get_drvdata(pdev); |
379 | |
380 | drm_bridge_remove(bridge: &fsl_ldb->bridge); |
381 | } |
382 | |
383 | static const struct of_device_id fsl_ldb_match[] = { |
384 | { .compatible = "fsl,imx6sx-ldb" , |
385 | .data = &fsl_ldb_devdata[IMX6SX_LDB], }, |
386 | { .compatible = "fsl,imx8mp-ldb" , |
387 | .data = &fsl_ldb_devdata[IMX8MP_LDB], }, |
388 | { .compatible = "fsl,imx93-ldb" , |
389 | .data = &fsl_ldb_devdata[IMX93_LDB], }, |
390 | { /* sentinel */ }, |
391 | }; |
392 | MODULE_DEVICE_TABLE(of, fsl_ldb_match); |
393 | |
394 | static struct platform_driver fsl_ldb_driver = { |
395 | .probe = fsl_ldb_probe, |
396 | .remove_new = fsl_ldb_remove, |
397 | .driver = { |
398 | .name = "fsl-ldb" , |
399 | .of_match_table = fsl_ldb_match, |
400 | }, |
401 | }; |
402 | module_platform_driver(fsl_ldb_driver); |
403 | |
404 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>" ); |
405 | MODULE_DESCRIPTION("Freescale i.MX8MP LDB" ); |
406 | MODULE_LICENSE("GPL" ); |
407 | |