Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c
52241 views
1
// SPDX-License-Identifier: GPL-2.0+
2
3
/*
4
* Copyright 2020 NXP
5
*/
6
7
#include <linux/bitfield.h>
8
#include <linux/clk.h>
9
#include <linux/delay.h>
10
#include <linux/io.h>
11
#include <linux/media-bus-format.h>
12
#include <linux/module.h>
13
#include <linux/of.h>
14
#include <linux/of_graph.h>
15
#include <linux/platform_device.h>
16
#include <linux/pm_runtime.h>
17
18
#include <drm/drm_atomic_state_helper.h>
19
#include <drm/drm_bridge.h>
20
#include <drm/drm_print.h>
21
22
#define PC_CTRL_REG 0x0
23
#define PC_COMBINE_ENABLE BIT(0)
24
#define PC_DISP_BYPASS(n) BIT(1 + 21 * (n))
25
#define PC_DISP_HSYNC_POLARITY(n) BIT(2 + 11 * (n))
26
#define PC_DISP_HSYNC_POLARITY_POS(n) DISP_HSYNC_POLARITY(n)
27
#define PC_DISP_VSYNC_POLARITY(n) BIT(3 + 11 * (n))
28
#define PC_DISP_VSYNC_POLARITY_POS(n) DISP_VSYNC_POLARITY(n)
29
#define PC_DISP_DVALID_POLARITY(n) BIT(4 + 11 * (n))
30
#define PC_DISP_DVALID_POLARITY_POS(n) DISP_DVALID_POLARITY(n)
31
#define PC_VSYNC_MASK_ENABLE BIT(5)
32
#define PC_SKIP_MODE BIT(6)
33
#define PC_SKIP_NUMBER_MASK GENMASK(12, 7)
34
#define PC_SKIP_NUMBER(n) FIELD_PREP(PC_SKIP_NUMBER_MASK, (n))
35
#define PC_DISP0_PIX_DATA_FORMAT_MASK GENMASK(18, 16)
36
#define PC_DISP0_PIX_DATA_FORMAT(fmt) \
37
FIELD_PREP(PC_DISP0_PIX_DATA_FORMAT_MASK, (fmt))
38
#define PC_DISP1_PIX_DATA_FORMAT_MASK GENMASK(21, 19)
39
#define PC_DISP1_PIX_DATA_FORMAT(fmt) \
40
FIELD_PREP(PC_DISP1_PIX_DATA_FORMAT_MASK, (fmt))
41
42
#define PC_SW_RESET_REG 0x20
43
#define PC_SW_RESET_N BIT(0)
44
#define PC_DISP_SW_RESET_N(n) BIT(1 + (n))
45
#define PC_FULL_RESET_N (PC_SW_RESET_N | \
46
PC_DISP_SW_RESET_N(0) | \
47
PC_DISP_SW_RESET_N(1))
48
49
#define PC_REG_SET 0x4
50
#define PC_REG_CLR 0x8
51
52
#define DRIVER_NAME "imx8qxp-pixel-combiner"
53
54
enum imx8qxp_pc_pix_data_format {
55
RGB,
56
YUV444,
57
YUV422,
58
SPLIT_RGB,
59
};
60
61
struct imx8qxp_pc_channel {
62
struct drm_bridge bridge;
63
struct imx8qxp_pc *pc;
64
unsigned int stream_id;
65
};
66
67
struct imx8qxp_pc {
68
struct device *dev;
69
struct imx8qxp_pc_channel *ch[2];
70
struct clk *clk_apb;
71
void __iomem *base;
72
};
73
74
static inline u32 imx8qxp_pc_read(struct imx8qxp_pc *pc, unsigned int offset)
75
{
76
return readl(pc->base + offset);
77
}
78
79
static inline void
80
imx8qxp_pc_write(struct imx8qxp_pc *pc, unsigned int offset, u32 value)
81
{
82
writel(value, pc->base + offset);
83
}
84
85
static inline void
86
imx8qxp_pc_write_set(struct imx8qxp_pc *pc, unsigned int offset, u32 value)
87
{
88
imx8qxp_pc_write(pc, offset + PC_REG_SET, value);
89
}
90
91
static inline void
92
imx8qxp_pc_write_clr(struct imx8qxp_pc *pc, unsigned int offset, u32 value)
93
{
94
imx8qxp_pc_write(pc, offset + PC_REG_CLR, value);
95
}
96
97
static enum drm_mode_status
98
imx8qxp_pc_bridge_mode_valid(struct drm_bridge *bridge,
99
const struct drm_display_info *info,
100
const struct drm_display_mode *mode)
101
{
102
if (mode->hdisplay > 2560)
103
return MODE_BAD_HVALUE;
104
105
return MODE_OK;
106
}
107
108
static int imx8qxp_pc_bridge_attach(struct drm_bridge *bridge,
109
struct drm_encoder *encoder,
110
enum drm_bridge_attach_flags flags)
111
{
112
struct imx8qxp_pc_channel *ch = bridge->driver_private;
113
struct imx8qxp_pc *pc = ch->pc;
114
115
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
116
DRM_DEV_ERROR(pc->dev,
117
"do not support creating a drm_connector\n");
118
return -EINVAL;
119
}
120
121
return drm_bridge_attach(encoder,
122
ch->bridge.next_bridge, bridge,
123
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
124
}
125
126
static void
127
imx8qxp_pc_bridge_mode_set(struct drm_bridge *bridge,
128
const struct drm_display_mode *mode,
129
const struct drm_display_mode *adjusted_mode)
130
{
131
struct imx8qxp_pc_channel *ch = bridge->driver_private;
132
struct imx8qxp_pc *pc = ch->pc;
133
u32 val;
134
int ret;
135
136
ret = pm_runtime_get_sync(pc->dev);
137
if (ret < 0)
138
DRM_DEV_ERROR(pc->dev,
139
"failed to get runtime PM sync: %d\n", ret);
140
141
ret = clk_prepare_enable(pc->clk_apb);
142
if (ret)
143
DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",
144
__func__, ret);
145
146
/* HSYNC to pixel link is active low. */
147
imx8qxp_pc_write_clr(pc, PC_CTRL_REG,
148
PC_DISP_HSYNC_POLARITY(ch->stream_id));
149
150
/* VSYNC to pixel link is active low. */
151
imx8qxp_pc_write_clr(pc, PC_CTRL_REG,
152
PC_DISP_VSYNC_POLARITY(ch->stream_id));
153
154
/* Data enable to pixel link is active high. */
155
imx8qxp_pc_write_set(pc, PC_CTRL_REG,
156
PC_DISP_DVALID_POLARITY(ch->stream_id));
157
158
/* Mask the first frame output which may be incomplete. */
159
imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_VSYNC_MASK_ENABLE);
160
161
/* Only support RGB currently. */
162
val = imx8qxp_pc_read(pc, PC_CTRL_REG);
163
if (ch->stream_id == 0) {
164
val &= ~PC_DISP0_PIX_DATA_FORMAT_MASK;
165
val |= PC_DISP0_PIX_DATA_FORMAT(RGB);
166
} else {
167
val &= ~PC_DISP1_PIX_DATA_FORMAT_MASK;
168
val |= PC_DISP1_PIX_DATA_FORMAT(RGB);
169
}
170
imx8qxp_pc_write(pc, PC_CTRL_REG, val);
171
172
/* Only support bypass mode currently. */
173
imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_DISP_BYPASS(ch->stream_id));
174
175
clk_disable_unprepare(pc->clk_apb);
176
}
177
178
static void imx8qxp_pc_bridge_atomic_disable(struct drm_bridge *bridge,
179
struct drm_atomic_state *state)
180
{
181
struct imx8qxp_pc_channel *ch = bridge->driver_private;
182
struct imx8qxp_pc *pc = ch->pc;
183
184
pm_runtime_put(pc->dev);
185
}
186
187
static const u32 imx8qxp_pc_bus_output_fmts[] = {
188
MEDIA_BUS_FMT_RGB888_1X36_CPADLO,
189
MEDIA_BUS_FMT_RGB666_1X36_CPADLO,
190
};
191
192
static bool imx8qxp_pc_bus_output_fmt_supported(u32 fmt)
193
{
194
int i;
195
196
for (i = 0; i < ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); i++) {
197
if (imx8qxp_pc_bus_output_fmts[i] == fmt)
198
return true;
199
}
200
201
return false;
202
}
203
204
static u32 *
205
imx8qxp_pc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
206
struct drm_bridge_state *bridge_state,
207
struct drm_crtc_state *crtc_state,
208
struct drm_connector_state *conn_state,
209
u32 output_fmt,
210
unsigned int *num_input_fmts)
211
{
212
u32 *input_fmts;
213
214
if (!imx8qxp_pc_bus_output_fmt_supported(output_fmt))
215
return NULL;
216
217
*num_input_fmts = 1;
218
219
input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
220
if (!input_fmts)
221
return NULL;
222
223
switch (output_fmt) {
224
case MEDIA_BUS_FMT_RGB888_1X36_CPADLO:
225
input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X30_CPADLO;
226
break;
227
case MEDIA_BUS_FMT_RGB666_1X36_CPADLO:
228
input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X30_CPADLO;
229
break;
230
default:
231
kfree(input_fmts);
232
input_fmts = NULL;
233
break;
234
}
235
236
return input_fmts;
237
}
238
239
static u32 *
240
imx8qxp_pc_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
241
struct drm_bridge_state *bridge_state,
242
struct drm_crtc_state *crtc_state,
243
struct drm_connector_state *conn_state,
244
unsigned int *num_output_fmts)
245
{
246
*num_output_fmts = ARRAY_SIZE(imx8qxp_pc_bus_output_fmts);
247
return kmemdup(imx8qxp_pc_bus_output_fmts,
248
sizeof(imx8qxp_pc_bus_output_fmts), GFP_KERNEL);
249
}
250
251
static const struct drm_bridge_funcs imx8qxp_pc_bridge_funcs = {
252
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
253
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
254
.atomic_reset = drm_atomic_helper_bridge_reset,
255
.mode_valid = imx8qxp_pc_bridge_mode_valid,
256
.attach = imx8qxp_pc_bridge_attach,
257
.mode_set = imx8qxp_pc_bridge_mode_set,
258
.atomic_disable = imx8qxp_pc_bridge_atomic_disable,
259
.atomic_get_input_bus_fmts =
260
imx8qxp_pc_bridge_atomic_get_input_bus_fmts,
261
.atomic_get_output_bus_fmts =
262
imx8qxp_pc_bridge_atomic_get_output_bus_fmts,
263
};
264
265
static int imx8qxp_pc_bridge_probe(struct platform_device *pdev)
266
{
267
struct imx8qxp_pc *pc;
268
struct imx8qxp_pc_channel *ch;
269
struct device *dev = &pdev->dev;
270
struct device_node *np = dev->of_node;
271
struct device_node *child, *remote;
272
u32 i;
273
int ret;
274
275
pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
276
if (!pc)
277
return -ENOMEM;
278
279
pc->base = devm_platform_ioremap_resource(pdev, 0);
280
if (IS_ERR(pc->base))
281
return PTR_ERR(pc->base);
282
283
pc->dev = dev;
284
285
pc->clk_apb = devm_clk_get(dev, "apb");
286
if (IS_ERR(pc->clk_apb)) {
287
ret = PTR_ERR(pc->clk_apb);
288
if (ret != -EPROBE_DEFER)
289
DRM_DEV_ERROR(dev, "failed to get apb clock: %d\n", ret);
290
return ret;
291
}
292
293
platform_set_drvdata(pdev, pc);
294
pm_runtime_enable(dev);
295
296
for_each_available_child_of_node(np, child) {
297
ret = of_property_read_u32(child, "reg", &i);
298
if (ret || i > 1) {
299
ret = -EINVAL;
300
DRM_DEV_ERROR(dev,
301
"invalid channel(%u) node address\n", i);
302
goto free_child;
303
}
304
305
ch = devm_drm_bridge_alloc(dev, struct imx8qxp_pc_channel, bridge,
306
&imx8qxp_pc_bridge_funcs);
307
if (IS_ERR(ch)) {
308
ret = PTR_ERR(ch);
309
goto free_child;
310
}
311
312
pc->ch[i] = ch;
313
ch->pc = pc;
314
ch->stream_id = i;
315
316
remote = of_graph_get_remote_node(child, 1, 0);
317
if (!remote) {
318
ret = -ENODEV;
319
DRM_DEV_ERROR(dev,
320
"channel%u failed to get port1's remote node: %d\n",
321
i, ret);
322
goto free_child;
323
}
324
325
ch->bridge.next_bridge = of_drm_find_and_get_bridge(remote);
326
if (!ch->bridge.next_bridge) {
327
of_node_put(remote);
328
ret = -EPROBE_DEFER;
329
DRM_DEV_DEBUG_DRIVER(dev,
330
"channel%u failed to find next bridge: %d\n",
331
i, ret);
332
goto free_child;
333
}
334
335
of_node_put(remote);
336
337
ch->bridge.driver_private = ch;
338
ch->bridge.of_node = child;
339
340
drm_bridge_add(&ch->bridge);
341
}
342
343
return 0;
344
345
free_child:
346
of_node_put(child);
347
348
if (i == 1 && pc->ch[0] && pc->ch[0]->bridge.next_bridge)
349
drm_bridge_remove(&pc->ch[0]->bridge);
350
351
pm_runtime_disable(dev);
352
return ret;
353
}
354
355
static void imx8qxp_pc_bridge_remove(struct platform_device *pdev)
356
{
357
struct imx8qxp_pc *pc = platform_get_drvdata(pdev);
358
struct imx8qxp_pc_channel *ch;
359
int i;
360
361
for (i = 0; i < 2; i++) {
362
ch = pc->ch[i];
363
364
if (ch)
365
drm_bridge_remove(&ch->bridge);
366
}
367
368
pm_runtime_disable(&pdev->dev);
369
}
370
371
static int imx8qxp_pc_runtime_suspend(struct device *dev)
372
{
373
struct platform_device *pdev = to_platform_device(dev);
374
struct imx8qxp_pc *pc = platform_get_drvdata(pdev);
375
int ret;
376
377
ret = clk_prepare_enable(pc->clk_apb);
378
if (ret)
379
DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",
380
__func__, ret);
381
382
/* Disable pixel combiner by full reset. */
383
imx8qxp_pc_write_clr(pc, PC_SW_RESET_REG, PC_FULL_RESET_N);
384
385
clk_disable_unprepare(pc->clk_apb);
386
387
/* Ensure the reset takes effect. */
388
usleep_range(10, 20);
389
390
return ret;
391
}
392
393
static int imx8qxp_pc_runtime_resume(struct device *dev)
394
{
395
struct platform_device *pdev = to_platform_device(dev);
396
struct imx8qxp_pc *pc = platform_get_drvdata(pdev);
397
int ret;
398
399
ret = clk_prepare_enable(pc->clk_apb);
400
if (ret) {
401
DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n",
402
__func__, ret);
403
return ret;
404
}
405
406
/* out of reset */
407
imx8qxp_pc_write_set(pc, PC_SW_RESET_REG, PC_FULL_RESET_N);
408
409
clk_disable_unprepare(pc->clk_apb);
410
411
return ret;
412
}
413
414
static const struct dev_pm_ops imx8qxp_pc_pm_ops = {
415
RUNTIME_PM_OPS(imx8qxp_pc_runtime_suspend, imx8qxp_pc_runtime_resume, NULL)
416
};
417
418
static const struct of_device_id imx8qxp_pc_dt_ids[] = {
419
{ .compatible = "fsl,imx8qm-pixel-combiner", },
420
{ .compatible = "fsl,imx8qxp-pixel-combiner", },
421
{ /* sentinel */ }
422
};
423
MODULE_DEVICE_TABLE(of, imx8qxp_pc_dt_ids);
424
425
static struct platform_driver imx8qxp_pc_bridge_driver = {
426
.probe = imx8qxp_pc_bridge_probe,
427
.remove = imx8qxp_pc_bridge_remove,
428
.driver = {
429
.pm = pm_ptr(&imx8qxp_pc_pm_ops),
430
.name = DRIVER_NAME,
431
.of_match_table = imx8qxp_pc_dt_ids,
432
},
433
};
434
module_platform_driver(imx8qxp_pc_bridge_driver);
435
436
MODULE_DESCRIPTION("i.MX8QM/QXP pixel combiner bridge driver");
437
MODULE_AUTHOR("Liu Ying <[email protected]>");
438
MODULE_LICENSE("GPL v2");
439
MODULE_ALIAS("platform:" DRIVER_NAME);
440
441