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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
|
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2025 Texas Instruments Incorporated - https://www.ti.com/
* Swamil Jain <[email protected]>
*
* based on the linux tidss_oldi.c, which is
*
* Copyright (C) 2024 - Texas Instruments Incorporated
* Author: Aradhya Bhatia <[email protected]>
*/
#include <dm.h>
#include <malloc.h>
#include <syscon.h>
#include <clk.h>
#include <regmap.h>
#include <dm/ofnode_graph.h>
#include <dm/ofnode.h>
#include <dm/device_compat.h>
#include <linux/bug.h>
#include "tidss_oldi.h"
enum tidss_oldi_pixels {
OLDI_PIXELS_EVEN = BIT(0),
OLDI_PIXELS_ODD = BIT(1),
};
/**
* enum tidss_oldi_dual_link_pixels - Pixel order of an OLDI dual-link connection
* @TIDSS_OLDI_DUAL_LINK_EVEN_ODD_PIXELS: Even pixels are expected to be generated
* from the first port, odd pixels from the second port
* @TIDSS_OLDI_DUAL_LINK_ODD_EVEN_PIXELS: Odd pixels are expected to be generated
* from the first port, even pixels from the second port
*/
enum tidss_oldi_dual_link_pixels {
TIDSS_OLDI_DUAL_LINK_EVEN_ODD_PIXELS = 0,
TIDSS_OLDI_DUAL_LINK_ODD_EVEN_PIXELS = 1,
};
static int tidss_oldi_get_port_pixels_type(ofnode port_node)
{
bool even_pixels =
ofnode_has_property(port_node, "dual-lvds-even-pixels");
bool odd_pixels =
ofnode_has_property(port_node, "dual-lvds-odd-pixels");
return (even_pixels ? OLDI_PIXELS_EVEN : 0) |
(odd_pixels ? OLDI_PIXELS_ODD : 0);
}
static int tidss_oldi_get_remote_pixels_type(ofnode port_node)
{
ofnode endpoint = ofnode_null();
int pixels_type = -EPIPE;
ofnode_for_each_subnode(endpoint, port_node) {
ofnode remote_port;
int current_pt;
if (!ofnode_name_eq(endpoint, "endpoint"))
continue;
remote_port = ofnode_graph_get_remote_port(endpoint);
if (!ofnode_valid(remote_port))
return -EPIPE;
current_pt = tidss_oldi_get_port_pixels_type(remote_port);
if (pixels_type < 0)
pixels_type = current_pt;
if (!current_pt || pixels_type != current_pt)
return -EINVAL;
}
return pixels_type;
}
int tidss_oldi_get_dual_link_pixel_order(ofnode port1, ofnode port2)
{
int remote_p1_pt, remote_p2_pt;
if (!ofnode_valid(port1) || !ofnode_valid(port2))
return -EINVAL;
remote_p1_pt = tidss_oldi_get_remote_pixels_type(port1);
if (remote_p1_pt < 0)
return remote_p1_pt;
remote_p2_pt = tidss_oldi_get_remote_pixels_type(port2);
if (remote_p2_pt < 0)
return remote_p2_pt;
/*
* A valid dual-lVDS bus is found when one remote port is marked with
* "dual-lvds-even-pixels", and the other remote port is marked with
* "dual-lvds-odd-pixels", bail out if the markers are not right.
*/
if (remote_p1_pt + remote_p2_pt != OLDI_PIXELS_EVEN + OLDI_PIXELS_ODD)
return -EINVAL;
return remote_p1_pt == OLDI_PIXELS_EVEN ?
TIDSS_OLDI_DUAL_LINK_EVEN_ODD_PIXELS :
TIDSS_OLDI_DUAL_LINK_ODD_EVEN_PIXELS;
}
static int get_oldi_mode(ofnode oldi_tx, u32 *companion_instance)
{
ofnode companion;
ofnode port0, port1;
u32 companion_reg;
bool secondary_oldi = false;
int pixel_order;
/*
* Find if the OLDI is paired with another OLDI for combined OLDI
* operation (dual-lvds or clone).
*/
companion = ofnode_parse_phandle(oldi_tx, "ti,companion-oldi", 0);
if (!ofnode_valid(companion))
/*
* OLDI TXes in Single Link mode do not have companion
* OLDI TXes and, Secondary OLDI nodes don't need this
* information.
*/
return OLDI_MODE_SINGLE_LINK;
if (ofnode_read_u32(companion, "reg", &companion_reg))
return OLDI_MODE_UNSUPPORTED;
if (companion_reg > (TIDSS_MAX_OLDI_TXES - 1))
/* Invalid companion OLDI reg value. */
return OLDI_MODE_UNSUPPORTED;
*companion_instance = companion_reg;
if (ofnode_read_bool(oldi_tx, "ti,secondary-oldi"))
secondary_oldi = true;
/*
* We need to work out if the sink is expecting us to function in
* dual-link mode. We do this by looking at the DT port nodes we are
* connected to, if they are marked as expecting even pixels and
* odd pixels than we need to enable vertical stripe output.
*/
port0 = ofnode_graph_get_port_by_id(oldi_tx, 1);
port1 = ofnode_graph_get_port_by_id(companion, 1);
pixel_order = tidss_oldi_get_dual_link_pixel_order(port0, port1);
switch (pixel_order) {
case -EINVAL:
/*
* The dual link properties were not found in at least
* one of the sink nodes. Since 2 OLDI ports are present
* in the DT, it can be safely assumed that the required
* configuration is Clone Mode.
*/
return (secondary_oldi ? OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK :
OLDI_MODE_CLONE_SINGLE_LINK);
case TIDSS_OLDI_DUAL_LINK_ODD_EVEN_PIXELS:
/*
* Primary OLDI can only support "ODD" pixels. So, from its
* perspective, the pixel order has to be ODD-EVEN.
*/
return (secondary_oldi ? OLDI_MODE_UNSUPPORTED :
OLDI_MODE_DUAL_LINK);
/* Unsupported OLDI Modes */
case TIDSS_OLDI_DUAL_LINK_EVEN_ODD_PIXELS:
/*
* Secondary OLDI can only support "EVEN" pixels. So, from its
* perspective, the pixel order has to be EVEN-ODD.
*/
return (secondary_oldi ? OLDI_MODE_SECONDARY_DUAL_LINK :
OLDI_MODE_UNSUPPORTED);
default:
return OLDI_MODE_UNSUPPORTED;
}
}
static int get_parent_dss_vp(ofnode oldi_tx, u32 *parent_vp)
{
ofnode ep, dss_port;
int ret;
ep = ofnode_graph_get_endpoint_by_regs(oldi_tx, 0, -1);
if (ofnode_valid(ep)) {
dss_port = ofnode_graph_get_remote_port(ep);
if (!ofnode_valid(dss_port))
ret = -ENODEV;
ret = ofnode_read_u32(dss_port, "reg", parent_vp);
if (ret)
return -ENODEV;
return 0;
}
return -ENODEV;
}
static int tidss_init_oldi_io_ctrl(struct udevice *dev, struct tidss_oldi *tidss_oldi)
{
struct udevice *syscon;
struct regmap *regmap = NULL;
int ret = 0;
ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, "ti,am65x-oldi-io-ctrl",
&syscon);
if (ret) {
debug("unable to find ti,am65x-oldi-io-ctrl syscon device (%d)\n", ret);
return ret;
}
/* get grf-reg base address */
regmap = syscon_get_regmap(syscon);
if (!regmap) {
debug("unable to find rockchip grf regmap\n");
return -ENODEV;
}
tidss_oldi->io_ctrl = regmap;
return 0;
}
int tidss_oldi_init(struct udevice *dev)
{
struct tidss_drv_priv *priv = dev_get_priv(dev);
u32 parent_vp, oldi_instance, companion_instance;
int ret, tidss_oldi_panel_count = 0;
enum tidss_oldi_link_type link_type;
struct tidss_oldi *tidss_oldi;
struct clk *serial;
ofnode child;
ofnode oldi_parent = ofnode_find_subnode(dev_ofnode(dev), "oldi-transmitters");
if (!ofnode_valid(oldi_parent))
/* Return gracefully */
return 0;
ofnode_for_each_subnode(child, oldi_parent) {
priv->oldis[tidss_oldi_panel_count] = NULL;
ret = get_parent_dss_vp(child, &parent_vp);
if (ret == -ENODEV) {
/*
* ENODEV means that this particular OLDI node
* is not connected with the DSS, which is not
* a harmful case. There could be another OLDI
* which may still be connected.
* Continue to search for that.
*/
ret = 0;
continue;
}
ret = ofnode_read_u32(child, "reg", &oldi_instance);
if (ret) {
ret = -ENODEV;
break;
}
link_type = get_oldi_mode(child, &companion_instance);
if (link_type == OLDI_MODE_UNSUPPORTED) {
dev_warn(dev, "OLDI%u: Unsupported OLDI connection.\n", oldi_instance);
ret = OLDI_MODE_UNSUPPORTED;
/* Return gracefully, no supported OLDI panel found */
goto return_err;
} else if ((link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) ||
(link_type == OLDI_MODE_CLONE_SINGLE_LINK)) {
/*
* The OLDI driver cannot support OLDI clone mode
* properly at present.
* The clone mode requires 2 working encoder-bridge
* pipelines, generating from the same crtc. The DRM
* framework does not support this at present. If
* there were to be, say, 2 OLDI sink bridges each
* connected to an OLDI TXes, they couldn't both be
* supported simultaneously.
* This driver still has some code pertaining to OLDI
* clone mode configuration in DSS hardware for future,
* when there is a better infrastructure in the DRM
* framework to support 2 encoder-bridge pipelines
* simultaneously.
* Till that time, this driver shall error out if it
* detects a clone mode configuration.
*/
ret = -EOPNOTSUPP;
goto return_err;
} else if (link_type == OLDI_MODE_SECONDARY_DUAL_LINK) {
/*
* This is the secondary OLDI node, which serves as a
* companion to the primary OLDI, when it is configured
* for the dual-link mode. Since the primary OLDI will
* be a part of bridge chain, no need to put this one
* too. Continue onto the next OLDI node.
*/
ret = 0;
continue;
}
serial = malloc(sizeof(struct clk));
ret = clk_get_by_name_nodev(child, "serial", serial);
if (ret) {
dev_err(dev, "video port %d clock enable error %d\n", parent_vp, ret);
free(serial);
goto return_err;
}
tidss_oldi = malloc(sizeof(struct tidss_oldi));
ret = tidss_init_oldi_io_ctrl(dev, tidss_oldi);
if (ret) {
debug("tidss could not initialize oldi_io_ctrl\n");
free(serial);
free(tidss_oldi);
goto return_err;
}
tidss_oldi->dev = dev;
tidss_oldi->parent_vp = parent_vp;
tidss_oldi->oldi_instance = oldi_instance;
tidss_oldi->companion_instance = companion_instance;
tidss_oldi->link_type = link_type;
tidss_oldi->serial = serial;
priv->oldis[tidss_oldi_panel_count] = tidss_oldi;
priv->oldi_mode = link_type;
tidss_oldi_panel_count++;
}
priv->num_oldis = tidss_oldi_panel_count;
ret = 0;
return_err:
return ret;
}
void dss_oldi_tx_power(struct tidss_drv_priv *priv, bool power)
{
u32 val;
if (WARN_ON(!priv->oldis[0]->io_ctrl))
return;
if (priv->feat->subrev == DSS_AM625) {
if (power) {
switch (priv->oldi_mode) {
case OLDI_MODE_SINGLE_LINK:
/* Power down OLDI TX 1 */
val = OLDI1_PWRDN_TX;
break;
case OLDI_MODE_DUAL_LINK:
/* No Power down */
val = 0;
break;
default:
/* Power down both the OLDI TXes */
val = OLDI_BANDGAP_PWR | OLDI0_PWRDN_TX | OLDI1_PWRDN_TX;
break;
}
} else {
val = OLDI_BANDGAP_PWR | OLDI0_PWRDN_TX | OLDI1_PWRDN_TX;
}
regmap_update_bits(priv->oldis[0]->io_ctrl, OLDI_PD_CTRL,
OLDI_BANDGAP_PWR | OLDI0_PWRDN_TX | OLDI1_PWRDN_TX, val);
}
}
|