blob: 5c57b902d14d36c77f3197937b3cc86c08370dd0 [file] [log] [blame]
Chris Morgan8c4e3042023-04-21 10:59:19 -05001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (c) 2023 Chris Morgan <macromorgan@hotmail.com>
4 */
5
6#include <abuf.h>
7#include <adc.h>
8#include <asm/io.h>
Chris Morgan3db220e2023-05-15 11:00:30 -05009#include <display.h>
Chris Morgan8c4e3042023-04-21 10:59:19 -050010#include <dm.h>
Chris Morgana824aa62023-05-15 11:00:29 -050011#include <dm/lists.h>
12#include <env.h>
13#include <fdt_support.h>
Chris Morgan8c4e3042023-04-21 10:59:19 -050014#include <linux/delay.h>
Chris Morgan3db220e2023-05-15 11:00:30 -050015#include <mipi_dsi.h>
Chris Morgana824aa62023-05-15 11:00:29 -050016#include <mmc.h>
Chris Morgan3db220e2023-05-15 11:00:30 -050017#include <panel.h>
Chris Morgan8c4e3042023-04-21 10:59:19 -050018#include <pwm.h>
Chris Morgan8c4e3042023-04-21 10:59:19 -050019#include <stdlib.h>
Chris Morgan3db220e2023-05-15 11:00:30 -050020#include <video_bridge.h>
Chris Morgan8c4e3042023-04-21 10:59:19 -050021
22#define GPIO0_BASE 0xfdd60000
Chris Morgan3db220e2023-05-15 11:00:30 -050023#define GPIO4_BASE 0xfe770000
Chris Morgana824aa62023-05-15 11:00:29 -050024#define GPIO_SWPORT_DR_L 0x0000
Chris Morgan8c4e3042023-04-21 10:59:19 -050025#define GPIO_SWPORT_DR_H 0x0004
Chris Morgana824aa62023-05-15 11:00:29 -050026#define GPIO_SWPORT_DDR_L 0x0008
Chris Morgan8c4e3042023-04-21 10:59:19 -050027#define GPIO_SWPORT_DDR_H 0x000c
Chris Morgan3db220e2023-05-15 11:00:30 -050028#define GPIO_A0 BIT(0)
Chris Morgana824aa62023-05-15 11:00:29 -050029#define GPIO_C5 BIT(5)
30#define GPIO_C6 BIT(6)
31#define GPIO_C7 BIT(7)
Chris Morgan8c4e3042023-04-21 10:59:19 -050032
33#define GPIO_WRITEMASK(bits) ((bits) << 16)
34
35#define DTB_DIR "rockchip/"
36
37struct rg3xx_model {
Chris Morgana824aa62023-05-15 11:00:29 -050038 const u16 adc_value;
Chris Morgan8c4e3042023-04-21 10:59:19 -050039 const char *board;
40 const char *board_name;
41 const char *fdtfile;
Chris Morgana63ff502024-01-02 09:46:48 -060042 const bool detect_panel;
Chris Morgan8c4e3042023-04-21 10:59:19 -050043};
44
45enum rgxx3_device_id {
46 RG353M,
47 RG353P,
48 RG353V,
Chris Morgan8c4e3042023-04-21 10:59:19 -050049 RG503,
Chris Morgan95db0c02024-01-02 09:46:53 -060050 RGB30,
51 RK2023,
52 RGARCD,
Chris Morgan4b181142024-02-05 12:58:53 -060053 RGB10MAX3,
Chris Morgana824aa62023-05-15 11:00:29 -050054 /* Devices with duplicate ADC value */
55 RG353PS,
56 RG353VS,
Chris Morgan95db0c02024-01-02 09:46:53 -060057 RGARCS,
Chris Morgan8c4e3042023-04-21 10:59:19 -050058};
59
60static const struct rg3xx_model rg3xx_model_details[] = {
61 [RG353M] = {
Chris Morgana63ff502024-01-02 09:46:48 -060062 .adc_value = 517, /* Observed average from device */
63 .board = "rk3566-anbernic-rg353m",
64 .board_name = "RG353M",
65 /* Device is identical to RG353P. */
66 .fdtfile = DTB_DIR "rk3566-anbernic-rg353p.dtb",
67 .detect_panel = 1,
Chris Morgan8c4e3042023-04-21 10:59:19 -050068 },
69 [RG353P] = {
Chris Morgana63ff502024-01-02 09:46:48 -060070 .adc_value = 860, /* Documented value of 860 */
71 .board = "rk3566-anbernic-rg353p",
72 .board_name = "RG353P",
73 .fdtfile = DTB_DIR "rk3566-anbernic-rg353p.dtb",
74 .detect_panel = 1,
Chris Morgan8c4e3042023-04-21 10:59:19 -050075 },
76 [RG353V] = {
Chris Morgana63ff502024-01-02 09:46:48 -060077 .adc_value = 695, /* Observed average from device */
78 .board = "rk3566-anbernic-rg353v",
79 .board_name = "RG353V",
80 .fdtfile = DTB_DIR "rk3566-anbernic-rg353v.dtb",
81 .detect_panel = 1,
Chris Morgan8c4e3042023-04-21 10:59:19 -050082 },
Chris Morgan8c4e3042023-04-21 10:59:19 -050083 [RG503] = {
Chris Morgana63ff502024-01-02 09:46:48 -060084 .adc_value = 1023, /* Observed average from device */
85 .board = "rk3566-anbernic-rg503",
86 .board_name = "RG503",
87 .fdtfile = DTB_DIR "rk3566-anbernic-rg503.dtb",
88 .detect_panel = 0,
Chris Morgan8c4e3042023-04-21 10:59:19 -050089 },
Chris Morgan95db0c02024-01-02 09:46:53 -060090 [RGB30] = {
91 .adc_value = 383, /* Gathered from second hand information */
92 .board = "rk3566-powkiddy-rgb30",
93 .board_name = "RGB30",
94 .fdtfile = DTB_DIR "rk3566-powkiddy-rgb30.dtb",
95 .detect_panel = 0,
96 },
97 [RK2023] = {
98 .adc_value = 635, /* Observed average from device */
99 .board = "rk3566-powkiddy-rk2023",
100 .board_name = "RK2023",
101 .fdtfile = DTB_DIR "rk3566-powkiddy-rk2023.dtb",
102 .detect_panel = 0,
103 },
104 [RGARCD] = {
105 .adc_value = 183, /* Observed average from device */
106 .board = "rk3566-anbernic-rg-arc-d",
107 .board_name = "Anbernic RG ARC-D",
108 .fdtfile = DTB_DIR "rk3566-anbernic-rg-arc-d.dtb",
109 .detect_panel = 0,
110 },
Chris Morgan4b181142024-02-05 12:58:53 -0600111 [RGB10MAX3] = {
112 .adc_value = 765, /* Observed average from device */
113 .board = "rk3566-powkiddy-rgb10max3",
114 .board_name = "Powkiddy RGB10MAX3",
115 .fdtfile = DTB_DIR "rk3566-powkiddy-rgb10max3.dtb",
116 .detect_panel = 0,
117 },
Chris Morgana824aa62023-05-15 11:00:29 -0500118 /* Devices with duplicate ADC value */
119 [RG353PS] = {
Chris Morgana63ff502024-01-02 09:46:48 -0600120 .adc_value = 860, /* Observed average from device */
121 .board = "rk3566-anbernic-rg353ps",
122 .board_name = "RG353PS",
123 .fdtfile = DTB_DIR "rk3566-anbernic-rg353ps.dtb",
124 .detect_panel = 1,
Chris Morgana824aa62023-05-15 11:00:29 -0500125 },
126 [RG353VS] = {
Chris Morgana63ff502024-01-02 09:46:48 -0600127 .adc_value = 695, /* Gathered from second hand information */
128 .board = "rk3566-anbernic-rg353vs",
129 .board_name = "RG353VS",
130 .fdtfile = DTB_DIR "rk3566-anbernic-rg353vs.dtb",
131 .detect_panel = 1,
Chris Morgana824aa62023-05-15 11:00:29 -0500132 },
Chris Morgan95db0c02024-01-02 09:46:53 -0600133 [RGARCS] = {
134 .adc_value = 183, /* Observed average from device */
135 .board = "rk3566-anbernic-rg-arc-s",
136 .board_name = "Anbernic RG ARC-S",
137 .fdtfile = DTB_DIR "rk3566-anbernic-rg-arc-s.dtb",
138 .detect_panel = 0,
139 },
Chris Morgan8c4e3042023-04-21 10:59:19 -0500140};
141
Chris Morgan3db220e2023-05-15 11:00:30 -0500142struct rg353_panel {
143 const u16 id;
Chris Morgana63ff502024-01-02 09:46:48 -0600144 const char *panel_compat[2];
Chris Morgan3db220e2023-05-15 11:00:30 -0500145};
146
147static const struct rg353_panel rg353_panel_details[] = {
Chris Morgana63ff502024-01-02 09:46:48 -0600148 {
149 .id = 0x3052,
150 .panel_compat[0] = "anbernic,rg353p-panel",
151 .panel_compat[1] = "newvision,nv3051d",
152 },
153 {
154 .id = 0x3821,
155 .panel_compat[0] = "anbernic,rg353v-panel-v2",
156 .panel_compat[1] = NULL,
157 },
Chris Morgan3db220e2023-05-15 11:00:30 -0500158};
159
Chris Morgan8c4e3042023-04-21 10:59:19 -0500160/*
161 * Start LED very early so user knows device is on. Set color
Chris Morgana824aa62023-05-15 11:00:29 -0500162 * to red.
Chris Morgan8c4e3042023-04-21 10:59:19 -0500163 */
164void spl_board_init(void)
165{
Chris Morgana824aa62023-05-15 11:00:29 -0500166 /* Set GPIO0_C5, GPIO0_C6, and GPIO0_C7 to output. */
167 writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | \
168 (GPIO_C7 | GPIO_C6 | GPIO_C5),
Chris Morgan8c4e3042023-04-21 10:59:19 -0500169 (GPIO0_BASE + GPIO_SWPORT_DDR_H));
Chris Morgana824aa62023-05-15 11:00:29 -0500170 /* Set GPIO0_C5 and GPIO_C6 to 0 and GPIO0_C7 to 1. */
171 writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | GPIO_C7,
Chris Morgan8c4e3042023-04-21 10:59:19 -0500172 (GPIO0_BASE + GPIO_SWPORT_DR_H));
173}
174
Chris Morgan8c4e3042023-04-21 10:59:19 -0500175/*
176 * Buzz the buzzer so the user knows something is going on. Make it
177 * optional in case PWM is disabled.
178 */
179void __maybe_unused startup_buzz(void)
180{
181 struct udevice *dev;
182 int err;
183
184 err = uclass_get_device(UCLASS_PWM, 0, &dev);
185 if (err)
186 printf("pwm not found\n");
187
188 pwm_set_enable(dev, 0, 1);
189 mdelay(200);
190 pwm_set_enable(dev, 0, 0);
191}
192
Chris Morgan3db220e2023-05-15 11:00:30 -0500193/*
194 * Provide the bare minimum to identify the panel for the RG353
195 * series. Since we don't have a working framebuffer device, no
196 * need to init the panel; just identify it and provide the
197 * clocks so we know what to set the different clock values to.
198 */
199
200static const struct display_timing rg353_default_timing = {
201 .pixelclock.typ = 24150000,
202 .hactive.typ = 640,
203 .hfront_porch.typ = 40,
204 .hback_porch.typ = 80,
205 .hsync_len.typ = 2,
206 .vactive.typ = 480,
207 .vfront_porch.typ = 18,
208 .vback_porch.typ = 28,
209 .vsync_len.typ = 2,
210 .flags = DISPLAY_FLAGS_HSYNC_HIGH |
211 DISPLAY_FLAGS_VSYNC_HIGH,
212};
213
214static int anbernic_rg353_panel_get_timing(struct udevice *dev,
215 struct display_timing *timings)
216{
217 memcpy(timings, &rg353_default_timing, sizeof(*timings));
218
219 return 0;
220}
221
222static int anbernic_rg353_panel_probe(struct udevice *dev)
223{
224 struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
225
226 plat->lanes = 4;
227 plat->format = MIPI_DSI_FMT_RGB888;
228 plat->mode_flags = MIPI_DSI_MODE_VIDEO |
229 MIPI_DSI_MODE_VIDEO_BURST |
230 MIPI_DSI_MODE_EOT_PACKET |
231 MIPI_DSI_MODE_LPM;
232
233 return 0;
234}
235
236static const struct panel_ops anbernic_rg353_panel_ops = {
237 .get_display_timing = anbernic_rg353_panel_get_timing,
238};
239
240U_BOOT_DRIVER(anbernic_rg353_panel) = {
241 .name = "anbernic_rg353_panel",
242 .id = UCLASS_PANEL,
243 .ops = &anbernic_rg353_panel_ops,
244 .probe = anbernic_rg353_panel_probe,
245 .plat_auto = sizeof(struct mipi_dsi_panel_plat),
246};
247
248int rgxx3_detect_display(void)
249{
250 struct udevice *dev;
251 struct mipi_dsi_device *dsi;
252 struct mipi_dsi_panel_plat *mplat;
253 const struct rg353_panel *panel;
254 int ret = 0;
255 int i;
256 u8 panel_id[2];
257
258 /*
259 * Take panel out of reset status.
260 * Set GPIO4_A0 to output.
261 */
262 writel(GPIO_WRITEMASK(GPIO_A0) | GPIO_A0,
263 (GPIO4_BASE + GPIO_SWPORT_DDR_L));
264 /* Set GPIO4_A0 to 1. */
265 writel(GPIO_WRITEMASK(GPIO_A0) | GPIO_A0,
266 (GPIO4_BASE + GPIO_SWPORT_DR_L));
267
268 /* Probe the DSI controller. */
269 ret = uclass_get_device_by_name(UCLASS_VIDEO_BRIDGE,
270 "dsi@fe060000", &dev);
271 if (ret) {
272 printf("DSI host not probed: %d\n", ret);
273 return ret;
274 }
275
276 /* Probe the DSI panel. */
277 ret = device_bind_driver_to_node(dev, "anbernic_rg353_panel",
278 "anbernic_rg353_panel",
279 dev_ofnode(dev), NULL);
280 if (ret) {
281 printf("Failed to probe RG353 panel: %d\n", ret);
282 return ret;
283 }
284
285 /*
286 * Attach the DSI controller which will also probe and attach
287 * the DSIDPHY.
288 */
289 ret = video_bridge_attach(dev);
290 if (ret) {
291 printf("Failed to attach DSI controller: %d\n", ret);
292 return ret;
293 }
294
295 /*
296 * Get the panel which should have already been probed by the
297 * video_bridge_attach() function.
298 */
299 ret = uclass_first_device_err(UCLASS_PANEL, &dev);
300 if (ret) {
301 printf("Panel device error: %d\n", ret);
302 return ret;
303 }
304
305 /* Now call the panel via DSI commands to get the panel ID. */
306 mplat = dev_get_plat(dev);
307 dsi = mplat->device;
308 mipi_dsi_set_maximum_return_packet_size(dsi, sizeof(panel_id));
309 ret = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_ID, &panel_id,
310 sizeof(panel_id));
311 if (ret < 0) {
312 printf("Unable to read panel ID: %d\n", ret);
313 return ret;
314 }
315
316 /* Get the correct panel compatible from the table. */
317 for (i = 0; i < ARRAY_SIZE(rg353_panel_details); i++) {
318 if (rg353_panel_details[i].id == ((panel_id[0] << 8) |
319 panel_id[1])) {
320 panel = &rg353_panel_details[i];
321 break;
322 }
323 }
324
325 if (!panel) {
326 printf("Unable to identify panel_id %x\n",
327 (panel_id[0] << 8) | panel_id[1]);
Chris Morgan3db220e2023-05-15 11:00:30 -0500328 return -EINVAL;
329 }
330
Chris Morgana63ff502024-01-02 09:46:48 -0600331 env_set("panel", panel->panel_compat[0]);
Chris Morgan3db220e2023-05-15 11:00:30 -0500332
333 return 0;
334}
335
Chris Morgan8c4e3042023-04-21 10:59:19 -0500336/* Detect which Anbernic RGXX3 device we are using so as to load the
337 * correct devicetree for Linux. Set an environment variable once
338 * found. The detection depends on the value of ADC channel 1, the
Chris Morgana824aa62023-05-15 11:00:29 -0500339 * presence of an eMMC on mmc0, and querying the DSI panel.
Chris Morgan8c4e3042023-04-21 10:59:19 -0500340 */
341int rgxx3_detect_device(void)
342{
343 u32 adc_info;
Chris Morgana824aa62023-05-15 11:00:29 -0500344 int ret, i;
Chris Morgan8c4e3042023-04-21 10:59:19 -0500345 int board_id = -ENXIO;
346 struct mmc *mmc;
347
348 ret = adc_channel_single_shot("saradc@fe720000", 1, &adc_info);
349 if (ret) {
350 printf("Read SARADC failed with error %d\n", ret);
351 return ret;
352 }
353
Chris Morgana824aa62023-05-15 11:00:29 -0500354 /*
355 * Get the correct device from the table. The ADC value is
356 * determined by a resistor on ADC channel 0. The hardware
357 * design calls for no more than a 1% variance on the
358 * resistor, so assume a +- value of 15 should be enough.
359 */
360 for (i = 0; i < ARRAY_SIZE(rg3xx_model_details); i++) {
361 u32 adc_min = rg3xx_model_details[i].adc_value - 15;
362 u32 adc_max = rg3xx_model_details[i].adc_value + 15;
363
364 if (adc_min < adc_info && adc_max > adc_info) {
365 board_id = i;
366 break;
367 }
368 }
Chris Morgan8c4e3042023-04-21 10:59:19 -0500369
370 /*
Chris Morgan95db0c02024-01-02 09:46:53 -0600371 * Try to access the eMMC on an RG353V, RG353P, or RG Arc D.
372 * If it's missing, it's an RG353VS, RG353PS, or RG Arc S.
373 * Note we could also check for a touchscreen at 0x1a on i2c2.
Chris Morgan8c4e3042023-04-21 10:59:19 -0500374 */
Chris Morgan95db0c02024-01-02 09:46:53 -0600375 if (board_id == RG353V || board_id == RG353P || board_id == RGARCD) {
Chris Morgan8c4e3042023-04-21 10:59:19 -0500376 mmc = find_mmc_device(0);
377 if (mmc) {
378 ret = mmc_init(mmc);
Chris Morgana824aa62023-05-15 11:00:29 -0500379 if (ret) {
380 if (board_id == RG353V)
381 board_id = RG353VS;
Chris Morgan95db0c02024-01-02 09:46:53 -0600382 else if (board_id == RG353P)
Chris Morgana824aa62023-05-15 11:00:29 -0500383 board_id = RG353PS;
Chris Morgan95db0c02024-01-02 09:46:53 -0600384 else
385 board_id = RGARCS;
Chris Morgana824aa62023-05-15 11:00:29 -0500386 }
Chris Morgan8c4e3042023-04-21 10:59:19 -0500387 }
388 }
389
390 if (board_id < 0)
391 return board_id;
392
393 env_set("board", rg3xx_model_details[board_id].board);
394 env_set("board_name",
395 rg3xx_model_details[board_id].board_name);
396 env_set("fdtfile", rg3xx_model_details[board_id].fdtfile);
397
Chris Morgana63ff502024-01-02 09:46:48 -0600398 /* Skip panel detection for when it is not needed. */
399 if (!rg3xx_model_details[board_id].detect_panel)
Chris Morgan3db220e2023-05-15 11:00:30 -0500400 return 0;
401
Chris Morgana63ff502024-01-02 09:46:48 -0600402 /* Warn but don't fail for errors in auto-detection of the panel. */
Chris Morgan3db220e2023-05-15 11:00:30 -0500403 ret = rgxx3_detect_display();
404 if (ret)
Chris Morgana63ff502024-01-02 09:46:48 -0600405 printf("Failed to detect panel type\n");
Chris Morgan3db220e2023-05-15 11:00:30 -0500406
Chris Morgan8c4e3042023-04-21 10:59:19 -0500407 return 0;
408}
409
410int rk_board_late_init(void)
411{
412 int ret;
413
Chris Morgan8c4e3042023-04-21 10:59:19 -0500414 ret = rgxx3_detect_device();
415 if (ret) {
416 printf("Unable to detect device type: %d\n", ret);
417 return ret;
418 }
419
Chris Morgana824aa62023-05-15 11:00:29 -0500420 /* Turn off red LED and turn on orange LED. */
421 writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | GPIO_C6,
422 (GPIO0_BASE + GPIO_SWPORT_DR_H));
423
Chris Morgan8c4e3042023-04-21 10:59:19 -0500424 if (IS_ENABLED(CONFIG_DM_PWM))
425 startup_buzz();
426
427 return 0;
428}
Chris Morgana824aa62023-05-15 11:00:29 -0500429
430int ft_board_setup(void *blob, struct bd_info *bd)
431{
Chris Morgana63ff502024-01-02 09:46:48 -0600432 const struct rg353_panel *panel = NULL;
433 int node, ret, i;
Chris Morgana824aa62023-05-15 11:00:29 -0500434 char *env;
435
436 /* No fixups necessary for the RG503 */
437 env = env_get("board_name");
438 if (env && (!strcmp(env, rg3xx_model_details[RG503].board_name)))
439 return 0;
440
441 /* Change the model name of the RG353M */
442 if (env && (!strcmp(env, rg3xx_model_details[RG353M].board_name)))
443 fdt_setprop(blob, 0, "model",
444 rg3xx_model_details[RG353M].board_name,
445 sizeof(rg3xx_model_details[RG353M].board_name));
446
Chris Morgana63ff502024-01-02 09:46:48 -0600447 env = env_get("panel");
448 if (!env) {
449 printf("Can't get panel env\n");
450 return 0;
451 }
452
Chris Morgan3db220e2023-05-15 11:00:30 -0500453 /*
454 * Check if the environment variable doesn't equal the panel.
455 * If it doesn't, update the devicetree to the correct panel.
456 */
457 node = fdt_path_offset(blob, "/dsi@fe060000/panel@0");
458 if (!(node > 0)) {
459 printf("Can't find the DSI node\n");
460 return -ENODEV;
461 }
462
Chris Morgan3db220e2023-05-15 11:00:30 -0500463 ret = fdt_node_check_compatible(blob, node, env);
464 if (ret < 0)
465 return -ENODEV;
466
467 /* Panels match, return 0. */
468 if (!ret)
469 return 0;
470
Chris Morgana63ff502024-01-02 09:46:48 -0600471 /* Panels don't match, search by first compatible value. */
472 for (i = 0; i < ARRAY_SIZE(rg353_panel_details); i++) {
473 if (!strcmp(env, rg353_panel_details[i].panel_compat[0])) {
474 panel = &rg353_panel_details[i];
475 break;
476 }
477 }
478
479 if (!panel) {
480 printf("Unable to identify panel by compat string\n");
481 return -ENODEV;
482 }
483
484 /* Set the compatible with the auto-detected values */
485 fdt_setprop_string(blob, node, "compatible", panel->panel_compat[0]);
486 if (panel->panel_compat[1])
487 fdt_appendprop_string(blob, node, "compatible",
488 panel->panel_compat[1]);
Chris Morgan3db220e2023-05-15 11:00:30 -0500489
Chris Morgana824aa62023-05-15 11:00:29 -0500490 return 0;
491}