Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c
52517 views
1
// SPDX-License-Identifier: GPL-2.0+
2
3
/*
4
* Copyright 2020 NXP
5
*/
6
7
#include <linux/firmware/imx/svc/misc.h>
8
#include <linux/media-bus-format.h>
9
#include <linux/mfd/syscon.h>
10
#include <linux/module.h>
11
#include <linux/of.h>
12
#include <linux/of_device.h>
13
#include <linux/of_graph.h>
14
#include <linux/platform_device.h>
15
#include <linux/pm_runtime.h>
16
#include <linux/regmap.h>
17
18
#include <drm/drm_atomic_state_helper.h>
19
#include <drm/drm_bridge.h>
20
#include <drm/drm_of.h>
21
#include <drm/drm_print.h>
22
23
#include <dt-bindings/firmware/imx/rsrc.h>
24
25
#define PXL2DPI_CTRL 0x40
26
#define CFG1_16BIT 0x0
27
#define CFG2_16BIT 0x1
28
#define CFG3_16BIT 0x2
29
#define CFG1_18BIT 0x3
30
#define CFG2_18BIT 0x4
31
#define CFG_24BIT 0x5
32
33
#define DRIVER_NAME "imx8qxp-pxl2dpi"
34
35
struct imx8qxp_pxl2dpi {
36
struct regmap *regmap;
37
struct drm_bridge bridge;
38
struct drm_bridge *companion;
39
struct device *dev;
40
struct imx_sc_ipc *ipc_handle;
41
u32 sc_resource;
42
u32 in_bus_format;
43
u32 out_bus_format;
44
u32 pl_sel;
45
};
46
47
#define bridge_to_p2d(b) container_of(b, struct imx8qxp_pxl2dpi, bridge)
48
49
static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge,
50
struct drm_encoder *encoder,
51
enum drm_bridge_attach_flags flags)
52
{
53
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
54
55
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
56
DRM_DEV_ERROR(p2d->dev,
57
"do not support creating a drm_connector\n");
58
return -EINVAL;
59
}
60
61
return drm_bridge_attach(encoder,
62
p2d->bridge.next_bridge, bridge,
63
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
64
}
65
66
static void imx8qxp_pxl2dpi_bridge_destroy(struct drm_bridge *bridge)
67
{
68
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
69
70
if (!p2d)
71
return;
72
73
drm_bridge_put(p2d->companion);
74
}
75
76
static int
77
imx8qxp_pxl2dpi_bridge_atomic_check(struct drm_bridge *bridge,
78
struct drm_bridge_state *bridge_state,
79
struct drm_crtc_state *crtc_state,
80
struct drm_connector_state *conn_state)
81
{
82
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
83
84
p2d->in_bus_format = bridge_state->input_bus_cfg.format;
85
p2d->out_bus_format = bridge_state->output_bus_cfg.format;
86
87
return 0;
88
}
89
90
static void
91
imx8qxp_pxl2dpi_bridge_mode_set(struct drm_bridge *bridge,
92
const struct drm_display_mode *mode,
93
const struct drm_display_mode *adjusted_mode)
94
{
95
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
96
struct imx8qxp_pxl2dpi *companion_p2d;
97
int ret;
98
99
ret = pm_runtime_get_sync(p2d->dev);
100
if (ret < 0)
101
DRM_DEV_ERROR(p2d->dev,
102
"failed to get runtime PM sync: %d\n", ret);
103
104
ret = imx_sc_misc_set_control(p2d->ipc_handle, p2d->sc_resource,
105
IMX_SC_C_PXL_LINK_SEL, p2d->pl_sel);
106
if (ret)
107
DRM_DEV_ERROR(p2d->dev,
108
"failed to set pixel link selection(%u): %d\n",
109
p2d->pl_sel, ret);
110
111
switch (p2d->out_bus_format) {
112
case MEDIA_BUS_FMT_RGB888_1X24:
113
regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG_24BIT);
114
break;
115
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
116
regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG2_18BIT);
117
break;
118
default:
119
DRM_DEV_ERROR(p2d->dev,
120
"unsupported output bus format 0x%08x\n",
121
p2d->out_bus_format);
122
}
123
124
if (p2d->companion) {
125
companion_p2d = bridge_to_p2d(p2d->companion);
126
127
companion_p2d->in_bus_format = p2d->in_bus_format;
128
companion_p2d->out_bus_format = p2d->out_bus_format;
129
130
p2d->companion->funcs->mode_set(p2d->companion, mode,
131
adjusted_mode);
132
}
133
}
134
135
static void imx8qxp_pxl2dpi_bridge_atomic_disable(struct drm_bridge *bridge,
136
struct drm_atomic_state *state)
137
{
138
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
139
140
pm_runtime_put(p2d->dev);
141
142
if (p2d->companion)
143
p2d->companion->funcs->atomic_disable(p2d->companion, state);
144
}
145
146
static const u32 imx8qxp_pxl2dpi_bus_output_fmts[] = {
147
MEDIA_BUS_FMT_RGB888_1X24,
148
MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
149
};
150
151
static bool imx8qxp_pxl2dpi_bus_output_fmt_supported(u32 fmt)
152
{
153
int i;
154
155
for (i = 0; i < ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); i++) {
156
if (imx8qxp_pxl2dpi_bus_output_fmts[i] == fmt)
157
return true;
158
}
159
160
return false;
161
}
162
163
static u32 *
164
imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
165
struct drm_bridge_state *bridge_state,
166
struct drm_crtc_state *crtc_state,
167
struct drm_connector_state *conn_state,
168
u32 output_fmt,
169
unsigned int *num_input_fmts)
170
{
171
u32 *input_fmts;
172
173
if (!imx8qxp_pxl2dpi_bus_output_fmt_supported(output_fmt))
174
return NULL;
175
176
*num_input_fmts = 1;
177
178
input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
179
if (!input_fmts)
180
return NULL;
181
182
switch (output_fmt) {
183
case MEDIA_BUS_FMT_RGB888_1X24:
184
input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;
185
break;
186
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
187
input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO;
188
break;
189
default:
190
kfree(input_fmts);
191
input_fmts = NULL;
192
break;
193
}
194
195
return input_fmts;
196
}
197
198
static u32 *
199
imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
200
struct drm_bridge_state *bridge_state,
201
struct drm_crtc_state *crtc_state,
202
struct drm_connector_state *conn_state,
203
unsigned int *num_output_fmts)
204
{
205
*num_output_fmts = ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts);
206
return kmemdup(imx8qxp_pxl2dpi_bus_output_fmts,
207
sizeof(imx8qxp_pxl2dpi_bus_output_fmts), GFP_KERNEL);
208
}
209
210
static const struct drm_bridge_funcs imx8qxp_pxl2dpi_bridge_funcs = {
211
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
212
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
213
.atomic_reset = drm_atomic_helper_bridge_reset,
214
.attach = imx8qxp_pxl2dpi_bridge_attach,
215
.destroy = imx8qxp_pxl2dpi_bridge_destroy,
216
.atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check,
217
.mode_set = imx8qxp_pxl2dpi_bridge_mode_set,
218
.atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable,
219
.atomic_get_input_bus_fmts =
220
imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts,
221
.atomic_get_output_bus_fmts =
222
imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts,
223
};
224
225
static struct device_node *
226
imx8qxp_pxl2dpi_get_available_ep_from_port(struct imx8qxp_pxl2dpi *p2d,
227
u32 port_id)
228
{
229
struct device_node *port, *ep;
230
int ep_cnt;
231
232
port = of_graph_get_port_by_id(p2d->dev->of_node, port_id);
233
if (!port) {
234
DRM_DEV_ERROR(p2d->dev, "failed to get port@%u\n", port_id);
235
return ERR_PTR(-ENODEV);
236
}
237
238
ep_cnt = of_get_available_child_count(port);
239
if (ep_cnt == 0) {
240
DRM_DEV_ERROR(p2d->dev, "no available endpoints of port@%u\n",
241
port_id);
242
ep = ERR_PTR(-ENODEV);
243
goto out;
244
} else if (ep_cnt > 1) {
245
DRM_DEV_ERROR(p2d->dev,
246
"invalid available endpoints of port@%u\n",
247
port_id);
248
ep = ERR_PTR(-EINVAL);
249
goto out;
250
}
251
252
ep = of_get_next_available_child(port, NULL);
253
if (!ep) {
254
DRM_DEV_ERROR(p2d->dev,
255
"failed to get available endpoint of port@%u\n",
256
port_id);
257
ep = ERR_PTR(-ENODEV);
258
goto out;
259
}
260
out:
261
of_node_put(port);
262
return ep;
263
}
264
265
static int imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d)
266
{
267
struct device_node *ep __free(device_node) =
268
imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1);
269
if (IS_ERR(ep))
270
return PTR_ERR(ep);
271
272
struct device_node *remote __free(device_node) = of_graph_get_remote_port_parent(ep);
273
if (!remote || !of_device_is_available(remote)) {
274
DRM_DEV_ERROR(p2d->dev, "no available remote\n");
275
return -ENODEV;
276
} else if (!of_device_is_available(remote->parent)) {
277
DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n");
278
return -ENODEV;
279
}
280
281
p2d->bridge.next_bridge = of_drm_find_and_get_bridge(remote);
282
if (!p2d->bridge.next_bridge)
283
return -EPROBE_DEFER;
284
285
return 0;
286
}
287
288
static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d)
289
{
290
struct device_node *ep;
291
struct of_endpoint endpoint;
292
int ret;
293
294
ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 0);
295
if (IS_ERR(ep))
296
return PTR_ERR(ep);
297
298
ret = of_graph_parse_endpoint(ep, &endpoint);
299
if (ret) {
300
DRM_DEV_ERROR(p2d->dev,
301
"failed to parse endpoint of port@0: %d\n", ret);
302
goto out;
303
}
304
305
p2d->pl_sel = endpoint.id;
306
out:
307
of_node_put(ep);
308
309
return ret;
310
}
311
312
static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d)
313
{
314
struct imx8qxp_pxl2dpi *companion_p2d;
315
struct device *dev = p2d->dev;
316
struct device_node *companion;
317
struct device_node *port1, *port2;
318
const struct of_device_id *match;
319
int dual_link;
320
int ret = 0;
321
322
/* Locate the companion PXL2DPI for dual-link operation, if any. */
323
companion = of_parse_phandle(dev->of_node, "fsl,companion-pxl2dpi", 0);
324
if (!companion)
325
return 0;
326
327
if (!of_device_is_available(companion)) {
328
DRM_DEV_ERROR(dev, "companion PXL2DPI is not available\n");
329
ret = -ENODEV;
330
goto out;
331
}
332
333
/*
334
* Sanity check: the companion bridge must have the same compatible
335
* string.
336
*/
337
match = of_match_device(dev->driver->of_match_table, dev);
338
if (!of_device_is_compatible(companion, match->compatible)) {
339
DRM_DEV_ERROR(dev, "companion PXL2DPI is incompatible\n");
340
ret = -ENXIO;
341
goto out;
342
}
343
344
p2d->companion = of_drm_find_and_get_bridge(companion);
345
if (!p2d->companion) {
346
ret = -EPROBE_DEFER;
347
DRM_DEV_DEBUG_DRIVER(p2d->dev,
348
"failed to find companion bridge: %d\n",
349
ret);
350
goto out;
351
}
352
353
companion_p2d = bridge_to_p2d(p2d->companion);
354
355
/*
356
* We need to work out if the sink is expecting us to function in
357
* dual-link mode. We do this by looking at the DT port nodes that
358
* the next bridges are connected to. If they are marked as expecting
359
* even pixels and odd pixels than we need to use the companion PXL2DPI.
360
*/
361
port1 = of_graph_get_port_by_id(p2d->bridge.next_bridge->of_node, 1);
362
port2 = of_graph_get_port_by_id(companion_p2d->bridge.next_bridge->of_node, 1);
363
dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);
364
of_node_put(port1);
365
of_node_put(port2);
366
367
if (dual_link < 0) {
368
ret = dual_link;
369
DRM_DEV_ERROR(dev, "failed to get dual link pixel order: %d\n",
370
ret);
371
goto out;
372
}
373
374
DRM_DEV_DEBUG_DRIVER(dev,
375
"dual-link configuration detected (companion bridge %pOF)\n",
376
companion);
377
out:
378
of_node_put(companion);
379
return ret;
380
}
381
382
static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev)
383
{
384
struct imx8qxp_pxl2dpi *p2d;
385
struct device *dev = &pdev->dev;
386
struct device_node *np = dev->of_node;
387
int ret;
388
389
p2d = devm_drm_bridge_alloc(dev, struct imx8qxp_pxl2dpi, bridge,
390
&imx8qxp_pxl2dpi_bridge_funcs);
391
if (IS_ERR(p2d))
392
return PTR_ERR(p2d);
393
394
p2d->regmap = syscon_node_to_regmap(np->parent);
395
if (IS_ERR(p2d->regmap)) {
396
ret = PTR_ERR(p2d->regmap);
397
if (ret != -EPROBE_DEFER)
398
DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret);
399
return ret;
400
}
401
402
ret = imx_scu_get_handle(&p2d->ipc_handle);
403
if (ret) {
404
if (ret != -EPROBE_DEFER)
405
DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n",
406
ret);
407
return ret;
408
}
409
410
p2d->dev = dev;
411
412
ret = of_property_read_u32(np, "fsl,sc-resource", &p2d->sc_resource);
413
if (ret) {
414
DRM_DEV_ERROR(dev, "failed to get SC resource %d\n", ret);
415
return ret;
416
}
417
418
ret = imx8qxp_pxl2dpi_find_next_bridge(p2d);
419
if (ret)
420
return ret;
421
422
ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d);
423
if (ret)
424
return ret;
425
426
ret = imx8qxp_pxl2dpi_parse_dt_companion(p2d);
427
if (ret)
428
return ret;
429
430
platform_set_drvdata(pdev, p2d);
431
pm_runtime_enable(dev);
432
433
p2d->bridge.driver_private = p2d;
434
p2d->bridge.of_node = np;
435
436
drm_bridge_add(&p2d->bridge);
437
438
return ret;
439
}
440
441
static void imx8qxp_pxl2dpi_bridge_remove(struct platform_device *pdev)
442
{
443
struct imx8qxp_pxl2dpi *p2d = platform_get_drvdata(pdev);
444
445
drm_bridge_remove(&p2d->bridge);
446
447
pm_runtime_disable(&pdev->dev);
448
}
449
450
static const struct of_device_id imx8qxp_pxl2dpi_dt_ids[] = {
451
{ .compatible = "fsl,imx8qxp-pxl2dpi", },
452
{ /* sentinel */ }
453
};
454
MODULE_DEVICE_TABLE(of, imx8qxp_pxl2dpi_dt_ids);
455
456
static struct platform_driver imx8qxp_pxl2dpi_bridge_driver = {
457
.probe = imx8qxp_pxl2dpi_bridge_probe,
458
.remove = imx8qxp_pxl2dpi_bridge_remove,
459
.driver = {
460
.of_match_table = imx8qxp_pxl2dpi_dt_ids,
461
.name = DRIVER_NAME,
462
},
463
};
464
module_platform_driver(imx8qxp_pxl2dpi_bridge_driver);
465
466
MODULE_DESCRIPTION("i.MX8QXP pixel link to DPI bridge driver");
467
MODULE_AUTHOR("Liu Ying <[email protected]>");
468
MODULE_LICENSE("GPL v2");
469
MODULE_ALIAS("platform:" DRIVER_NAME);
470
471