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
365
366
367
368
369
370
371
372
373
374
|
// SPDX-License-Identifier: GPL-2.0
/*
* Panel driver for the ARM Versatile family reference designs from
* ARM Limited.
*
* Author:
* Linus Walleij <linus.wallei@linaro.org>
*
* On the Versatile AB, these panels come mounted on daughterboards
* named "IB1" or "IB2" (Interface Board 1 & 2 respectively.) They
* are documented in ARM DUI 0225D Appendix C and D. These daughter
* boards support TFT display panels.
*
* - The IB1 is a passive board where the display connector defines a
* few wires for encoding the display type for autodetection,
* suitable display settings can then be looked up from this setting.
* The magic bits can be read out from the system controller.
*
* - The IB2 is a more complex board intended for GSM phone development
* with some logic and a control register, which needs to be accessed
* and the board display needs to be turned on explicitly.
*
* On the Versatile PB, a special CLCD adaptor board is available
* supporting the same displays as the Versatile AB, plus one more
* Epson QCIF display.
*
*/
#include <linux/bitops.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
/*
* This configuration register in the Versatile and RealView
* family is uniformly present but appears more and more
* unutilized starting with the RealView series.
*/
#define SYS_CLCD 0x50
/* The Versatile can detect the connected panel type */
#define SYS_CLCD_CLCDID_MASK (BIT(8)|BIT(9)|BIT(10)|BIT(11)|BIT(12))
#define SYS_CLCD_ID_SANYO_3_8 (0x00 << 8)
#define SYS_CLCD_ID_SHARP_8_4 (0x01 << 8)
#define SYS_CLCD_ID_EPSON_2_2 (0x02 << 8)
#define SYS_CLCD_ID_SANYO_2_5 (0x07 << 8)
#define SYS_CLCD_ID_VGA (0x1f << 8)
/* IB2 control register for the Versatile daughterboard */
#define IB2_CTRL 0x00
#define IB2_CTRL_LCD_SD BIT(1) /* 1 = shut down LCD */
#define IB2_CTRL_LCD_BL_ON BIT(0)
#define IB2_CTRL_LCD_MASK (BIT(0)|BIT(1))
/**
* struct versatile_panel_type - lookup struct for the supported panels
*/
struct versatile_panel_type {
/**
* @name: the name of this panel
*/
const char *name;
/**
* @magic: the magic value from the detection register
*/
u32 magic;
/**
* @mode: the DRM display mode for this panel
*/
struct drm_display_mode mode;
/**
* @bus_flags: the DRM bus flags for this panel e.g. inverted clock
*/
u32 bus_flags;
/**
* @width_mm: the panel width in mm
*/
u32 width_mm;
/**
* @height_mm: the panel height in mm
*/
u32 height_mm;
/**
* @ib2: the panel may be connected on an IB2 daughterboard
*/
bool ib2;
};
/**
* struct versatile_panel - state container for the Versatile panels
*/
struct versatile_panel {
/**
* @dev: the container device
*/
struct device *dev;
/**
* @panel: the DRM panel instance for this device
*/
struct drm_panel panel;
/**
* @panel_type: the Versatile panel type as detected
*/
const struct versatile_panel_type *panel_type;
/**
* @map: map to the parent syscon where the main register reside
*/
struct regmap *map;
/**
* @ib2_map: map to the IB2 syscon, if applicable
*/
struct regmap *ib2_map;
};
static const struct versatile_panel_type versatile_panels[] = {
/*
* Sanyo TM38QV67A02A - 3.8 inch QVGA (320x240) Color TFT
* found on the Versatile AB IB1 connector or the Versatile
* PB adaptor board connector.
*/
{
.name = "Sanyo TM38QV67A02A",
.magic = SYS_CLCD_ID_SANYO_3_8,
.width_mm = 79,
.height_mm = 54,
.mode = {
.clock = 10000,
.hdisplay = 320,
.hsync_start = 320 + 6,
.hsync_end = 320 + 6 + 6,
.htotal = 320 + 6 + 6 + 6,
.vdisplay = 240,
.vsync_start = 240 + 5,
.vsync_end = 240 + 5 + 6,
.vtotal = 240 + 5 + 6 + 5,
.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
},
},
/*
* Sharp LQ084V1DG21 640x480 VGA Color TFT module
* found on the Versatile AB IB1 connector or the Versatile
* PB adaptor board connector.
*/
{
.name = "Sharp LQ084V1DG21",
.magic = SYS_CLCD_ID_SHARP_8_4,
.width_mm = 171,
.height_mm = 130,
.mode = {
.clock = 25000,
.hdisplay = 640,
.hsync_start = 640 + 24,
.hsync_end = 640 + 24 + 96,
.htotal = 640 + 24 + 96 + 24,
.vdisplay = 480,
.vsync_start = 480 + 11,
.vsync_end = 480 + 11 + 2,
.vtotal = 480 + 11 + 2 + 32,
.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
},
},
/*
* Epson L2F50113T00 - 2.2 inch QCIF 176x220 Color TFT
* found on the Versatile PB adaptor board connector.
*/
{
.name = "Epson L2F50113T00",
.magic = SYS_CLCD_ID_EPSON_2_2,
.width_mm = 34,
.height_mm = 45,
.mode = {
.clock = 62500,
.hdisplay = 176,
.hsync_start = 176 + 2,
.hsync_end = 176 + 2 + 3,
.htotal = 176 + 2 + 3 + 3,
.vdisplay = 220,
.vsync_start = 220 + 0,
.vsync_end = 220 + 0 + 2,
.vtotal = 220 + 0 + 2 + 1,
.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
},
.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
},
/*
* Sanyo ALR252RGT 240x320 portrait display found on the
* Versatile AB IB2 daughterboard for GSM prototyping.
*/
{
.name = "Sanyo ALR252RGT",
.magic = SYS_CLCD_ID_SANYO_2_5,
.width_mm = 37,
.height_mm = 50,
.mode = {
.clock = 5400,
.hdisplay = 240,
.hsync_start = 240 + 10,
.hsync_end = 240 + 10 + 10,
.htotal = 240 + 10 + 10 + 20,
.vdisplay = 320,
.vsync_start = 320 + 2,
.vsync_end = 320 + 2 + 2,
.vtotal = 320 + 2 + 2 + 2,
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
},
.bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
.ib2 = true,
},
};
static inline struct versatile_panel *
to_versatile_panel(struct drm_panel *panel)
{
return container_of(panel, struct versatile_panel, panel);
}
static int versatile_panel_disable(struct drm_panel *panel)
{
struct versatile_panel *vpanel = to_versatile_panel(panel);
/* If we're on an IB2 daughterboard, turn off display */
if (vpanel->ib2_map) {
dev_dbg(vpanel->dev, "disable IB2 display\n");
regmap_update_bits(vpanel->ib2_map,
IB2_CTRL,
IB2_CTRL_LCD_MASK,
IB2_CTRL_LCD_SD);
}
return 0;
}
static int versatile_panel_enable(struct drm_panel *panel)
{
struct versatile_panel *vpanel = to_versatile_panel(panel);
/* If we're on an IB2 daughterboard, turn on display */
if (vpanel->ib2_map) {
dev_dbg(vpanel->dev, "enable IB2 display\n");
regmap_update_bits(vpanel->ib2_map,
IB2_CTRL,
IB2_CTRL_LCD_MASK,
IB2_CTRL_LCD_BL_ON);
}
return 0;
}
static int versatile_panel_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct versatile_panel *vpanel = to_versatile_panel(panel);
struct drm_display_mode *mode;
connector->display_info.width_mm = vpanel->panel_type->width_mm;
connector->display_info.height_mm = vpanel->panel_type->height_mm;
connector->display_info.bus_flags = vpanel->panel_type->bus_flags;
mode = drm_mode_duplicate(connector->dev, &vpanel->panel_type->mode);
drm_mode_set_name(mode);
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
mode->width_mm = vpanel->panel_type->width_mm;
mode->height_mm = vpanel->panel_type->height_mm;
drm_mode_probed_add(connector, mode);
return 1;
}
static const struct drm_panel_funcs versatile_panel_drm_funcs = {
.disable = versatile_panel_disable,
.enable = versatile_panel_enable,
.get_modes = versatile_panel_get_modes,
};
static int versatile_panel_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct versatile_panel *vpanel;
struct device *parent;
struct regmap *map;
int ret;
u32 val;
int i;
parent = dev->parent;
if (!parent) {
dev_err(dev, "no parent for versatile panel\n");
return -ENODEV;
}
map = syscon_node_to_regmap(parent->of_node);
if (IS_ERR(map)) {
dev_err(dev, "no regmap for versatile panel parent\n");
return PTR_ERR(map);
}
vpanel = devm_kzalloc(dev, sizeof(*vpanel), GFP_KERNEL);
if (!vpanel)
return -ENOMEM;
ret = regmap_read(map, SYS_CLCD, &val);
if (ret) {
dev_err(dev, "cannot access syscon regs\n");
return ret;
}
val &= SYS_CLCD_CLCDID_MASK;
for (i = 0; i < ARRAY_SIZE(versatile_panels); i++) {
const struct versatile_panel_type *pt;
pt = &versatile_panels[i];
if (pt->magic == val) {
vpanel->panel_type = pt;
break;
}
}
/* No panel detected or VGA, let's leave this show */
if (i == ARRAY_SIZE(versatile_panels)) {
dev_info(dev, "no panel detected\n");
return -ENODEV;
}
dev_info(dev, "detected: %s\n", vpanel->panel_type->name);
vpanel->dev = dev;
vpanel->map = map;
/* Check if the panel is mounted on an IB2 daughterboard */
if (vpanel->panel_type->ib2) {
vpanel->ib2_map = syscon_regmap_lookup_by_compatible(
"arm,versatile-ib2-syscon");
if (IS_ERR(vpanel->ib2_map))
vpanel->ib2_map = NULL;
else
dev_info(dev, "panel mounted on IB2 daughterboard\n");
}
drm_panel_init(&vpanel->panel, dev, &versatile_panel_drm_funcs,
DRM_MODE_CONNECTOR_DPI);
drm_panel_add(&vpanel->panel);
return 0;
}
static const struct of_device_id versatile_panel_match[] = {
{ .compatible = "arm,versatile-tft-panel", },
{},
};
MODULE_DEVICE_TABLE(of, versatile_panel_match);
static struct platform_driver versatile_panel_driver = {
.probe = versatile_panel_probe,
.driver = {
.name = "versatile-tft-panel",
.of_match_table = versatile_panel_match,
},
};
module_platform_driver(versatile_panel_driver);
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("ARM Versatile panel driver");
MODULE_LICENSE("GPL v2");
|