1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2022-2025 NXP
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/media-bus-format.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#define IMX93_DISPLAY_MUX_REG 0x60
#define PARALLEL_DISP_FORMAT GENMASK(10, 8)
#define FORMAT_RGB888_TO_RGB888 FIELD_PREP(PARALLEL_DISP_FORMAT, 0)
#define FORMAT_RGB888_TO_RGB666 FIELD_PREP(PARALLEL_DISP_FORMAT, 1)
#define FORMAT_RGB565_TO_RGB565 FIELD_PREP(PARALLEL_DISP_FORMAT, 2)
struct imx93_pdfc {
struct drm_bridge bridge;
struct device *dev;
struct regmap *regmap;
u32 phy_bus_width;
};
static struct imx93_pdfc *bridge_to_imx93_pdfc(struct drm_bridge *bridge)
{
return container_of(bridge, struct imx93_pdfc, bridge);
}
static int
imx93_pdfc_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder,
enum drm_bridge_attach_flags flags)
{
return drm_bridge_attach(bridge->encoder, bridge->next_bridge, bridge, flags);
}
static void imx93_pdfc_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
struct imx93_pdfc *pdfc = bridge_to_imx93_pdfc(bridge);
const struct drm_bridge_state *bridge_state;
unsigned int mask = PARALLEL_DISP_FORMAT;
unsigned int val;
bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
switch (bridge_state->output_bus_cfg.format) {
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_FIXED:
val = FORMAT_RGB888_TO_RGB888;
if (pdfc->phy_bus_width == 18) {
/*
* Can be valid if physical bus limitation exists,
* therefore use dev_dbg().
*/
dev_dbg(pdfc->dev, "Truncate two LSBs from each color\n");
val = FORMAT_RGB888_TO_RGB666;
}
break;
case MEDIA_BUS_FMT_RGB666_1X18:
val = FORMAT_RGB888_TO_RGB666;
break;
case MEDIA_BUS_FMT_RGB565_1X16:
val = FORMAT_RGB565_TO_RGB565;
break;
}
regmap_update_bits(pdfc->regmap, IMX93_DISPLAY_MUX_REG, mask, val);
}
/* TODO: Add YUV formats */
static const u32 imx93_pdfc_bus_output_fmts[] = {
MEDIA_BUS_FMT_FIXED,
MEDIA_BUS_FMT_RGB888_1X24,
MEDIA_BUS_FMT_RGB666_1X18,
MEDIA_BUS_FMT_RGB565_1X16,
};
static bool imx93_pdfc_bus_output_fmt_supported(u32 fmt)
{
int i;
for (i = 0; i < ARRAY_SIZE(imx93_pdfc_bus_output_fmts); i++) {
if (imx93_pdfc_bus_output_fmts[i] == fmt)
return true;
}
return false;
}
static u32 *
imx93_pdfc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
u32 output_fmt,
unsigned int *num_input_fmts)
{
u32 *input_fmts;
*num_input_fmts = 0;
if (!imx93_pdfc_bus_output_fmt_supported(output_fmt))
return NULL;
input_fmts = kmalloc_obj(*input_fmts);
if (!input_fmts)
return NULL;
*num_input_fmts = 1;
switch (output_fmt) {
case MEDIA_BUS_FMT_RGB888_1X24:
case MEDIA_BUS_FMT_RGB565_1X16:
input_fmts[0] = output_fmt;
break;
case MEDIA_BUS_FMT_RGB666_1X18:
case MEDIA_BUS_FMT_FIXED:
input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
break;
}
return input_fmts;
}
static int imx93_pdfc_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct imx93_pdfc *pdfc = bridge_to_imx93_pdfc(bridge);
u32 format = bridge_state->output_bus_cfg.format;
if (imx93_pdfc_bus_output_fmt_supported(format))
return 0;
dev_warn(pdfc->dev, "Unsupported output bus format: 0x%x\n", format);
return -EINVAL;
}
static const struct drm_bridge_funcs funcs = {
.attach = imx93_pdfc_bridge_attach,
.atomic_enable = imx93_pdfc_bridge_atomic_enable,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_get_input_bus_fmts = imx93_pdfc_bridge_atomic_get_input_bus_fmts,
.atomic_check = imx93_pdfc_bridge_atomic_check,
.atomic_create_state = drm_atomic_helper_bridge_create_state,
};
static int imx93_pdfc_bridge_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct drm_bridge *next_bridge;
struct imx93_pdfc *pdfc;
struct device_node *ep;
int err;
pdfc = devm_drm_bridge_alloc(dev, struct imx93_pdfc, bridge, &funcs);
if (IS_ERR(pdfc))
return PTR_ERR(pdfc);
pdfc->regmap = syscon_node_to_regmap(dev->of_node->parent);
if (IS_ERR(pdfc->regmap))
return dev_err_probe(dev, PTR_ERR(pdfc->regmap),
"failed to get regmap\n");
/* No limits per default */
pdfc->phy_bus_width = 24;
/* Get output ep (port1/endpoint) */
ep = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
if (ep) {
err = of_property_read_u32(ep, "bus-width", &pdfc->phy_bus_width);
of_node_put(ep);
/* bus-width is optional but it must have valid data if present */
if (err && err != -EINVAL)
return dev_err_probe(dev, err,
"failed to query bus-width\n");
}
next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);
if (IS_ERR(next_bridge))
return dev_err_probe(dev, PTR_ERR(next_bridge),
"failed to get next bridge\n");
pdfc->dev = dev;
pdfc->bridge.of_node = dev->of_node;
pdfc->bridge.type = DRM_MODE_CONNECTOR_DPI;
pdfc->bridge.next_bridge = drm_bridge_get(next_bridge);
return devm_drm_bridge_add(dev, &pdfc->bridge);
}
static const struct of_device_id imx93_pdfc_dt_ids[] = {
{ .compatible = "nxp,imx91-pdfc", },
{ .compatible = "nxp,imx93-pdfc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx93_pdfc_dt_ids);
static struct platform_driver imx93_pdfc_bridge_driver = {
.probe = imx93_pdfc_bridge_probe,
.driver = {
.of_match_table = imx93_pdfc_dt_ids,
.name = "imx93_pdfc",
},
};
module_platform_driver(imx93_pdfc_bridge_driver);
MODULE_DESCRIPTION("NXP i.MX93 parallel display format configuration driver");
MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
MODULE_LICENSE("GPL");
|