blob: 3d0c6146233059f53f8ef7b57a1784f8da4bcb88 [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>
19#include <rng.h>
20#include <stdlib.h>
Chris Morgan3db220e2023-05-15 11:00:30 -050021#include <video_bridge.h>
Chris Morgan8c4e3042023-04-21 10:59:19 -050022
23#define GPIO0_BASE 0xfdd60000
Chris Morgan3db220e2023-05-15 11:00:30 -050024#define GPIO4_BASE 0xfe770000
Chris Morgana824aa62023-05-15 11:00:29 -050025#define GPIO_SWPORT_DR_L 0x0000
Chris Morgan8c4e3042023-04-21 10:59:19 -050026#define GPIO_SWPORT_DR_H 0x0004
Chris Morgana824aa62023-05-15 11:00:29 -050027#define GPIO_SWPORT_DDR_L 0x0008
Chris Morgan8c4e3042023-04-21 10:59:19 -050028#define GPIO_SWPORT_DDR_H 0x000c
Chris Morgan3db220e2023-05-15 11:00:30 -050029#define GPIO_A0 BIT(0)
Chris Morgana824aa62023-05-15 11:00:29 -050030#define GPIO_C5 BIT(5)
31#define GPIO_C6 BIT(6)
32#define GPIO_C7 BIT(7)
Chris Morgan8c4e3042023-04-21 10:59:19 -050033
34#define GPIO_WRITEMASK(bits) ((bits) << 16)
35
36#define DTB_DIR "rockchip/"
37
38struct rg3xx_model {
Chris Morgana824aa62023-05-15 11:00:29 -050039 const u16 adc_value;
Chris Morgan8c4e3042023-04-21 10:59:19 -050040 const char *board;
41 const char *board_name;
42 const char *fdtfile;
Chris Morgana63ff502024-01-02 09:46:48 -060043 const bool detect_panel;
Chris Morgan8c4e3042023-04-21 10:59:19 -050044};
45
46enum rgxx3_device_id {
47 RG353M,
48 RG353P,
49 RG353V,
Chris Morgan8c4e3042023-04-21 10:59:19 -050050 RG503,
Chris Morgana824aa62023-05-15 11:00:29 -050051 /* Devices with duplicate ADC value */
52 RG353PS,
53 RG353VS,
Chris Morgan8c4e3042023-04-21 10:59:19 -050054};
55
56static const struct rg3xx_model rg3xx_model_details[] = {
57 [RG353M] = {
Chris Morgana63ff502024-01-02 09:46:48 -060058 .adc_value = 517, /* Observed average from device */
59 .board = "rk3566-anbernic-rg353m",
60 .board_name = "RG353M",
61 /* Device is identical to RG353P. */
62 .fdtfile = DTB_DIR "rk3566-anbernic-rg353p.dtb",
63 .detect_panel = 1,
Chris Morgan8c4e3042023-04-21 10:59:19 -050064 },
65 [RG353P] = {
Chris Morgana63ff502024-01-02 09:46:48 -060066 .adc_value = 860, /* Documented value of 860 */
67 .board = "rk3566-anbernic-rg353p",
68 .board_name = "RG353P",
69 .fdtfile = DTB_DIR "rk3566-anbernic-rg353p.dtb",
70 .detect_panel = 1,
Chris Morgan8c4e3042023-04-21 10:59:19 -050071 },
72 [RG353V] = {
Chris Morgana63ff502024-01-02 09:46:48 -060073 .adc_value = 695, /* Observed average from device */
74 .board = "rk3566-anbernic-rg353v",
75 .board_name = "RG353V",
76 .fdtfile = DTB_DIR "rk3566-anbernic-rg353v.dtb",
77 .detect_panel = 1,
Chris Morgan8c4e3042023-04-21 10:59:19 -050078 },
Chris Morgan8c4e3042023-04-21 10:59:19 -050079 [RG503] = {
Chris Morgana63ff502024-01-02 09:46:48 -060080 .adc_value = 1023, /* Observed average from device */
81 .board = "rk3566-anbernic-rg503",
82 .board_name = "RG503",
83 .fdtfile = DTB_DIR "rk3566-anbernic-rg503.dtb",
84 .detect_panel = 0,
Chris Morgan8c4e3042023-04-21 10:59:19 -050085 },
Chris Morgana824aa62023-05-15 11:00:29 -050086 /* Devices with duplicate ADC value */
87 [RG353PS] = {
Chris Morgana63ff502024-01-02 09:46:48 -060088 .adc_value = 860, /* Observed average from device */
89 .board = "rk3566-anbernic-rg353ps",
90 .board_name = "RG353PS",
91 .fdtfile = DTB_DIR "rk3566-anbernic-rg353ps.dtb",
92 .detect_panel = 1,
Chris Morgana824aa62023-05-15 11:00:29 -050093 },
94 [RG353VS] = {
Chris Morgana63ff502024-01-02 09:46:48 -060095 .adc_value = 695, /* Gathered from second hand information */
96 .board = "rk3566-anbernic-rg353vs",
97 .board_name = "RG353VS",
98 .fdtfile = DTB_DIR "rk3566-anbernic-rg353vs.dtb",
99 .detect_panel = 1,
Chris Morgana824aa62023-05-15 11:00:29 -0500100 },
Chris Morgan8c4e3042023-04-21 10:59:19 -0500101};
102
Chris Morgan3db220e2023-05-15 11:00:30 -0500103struct rg353_panel {
104 const u16 id;
Chris Morgana63ff502024-01-02 09:46:48 -0600105 const char *panel_compat[2];
Chris Morgan3db220e2023-05-15 11:00:30 -0500106};
107
108static const struct rg353_panel rg353_panel_details[] = {
Chris Morgana63ff502024-01-02 09:46:48 -0600109 {
110 .id = 0x3052,
111 .panel_compat[0] = "anbernic,rg353p-panel",
112 .panel_compat[1] = "newvision,nv3051d",
113 },
114 {
115 .id = 0x3821,
116 .panel_compat[0] = "anbernic,rg353v-panel-v2",
117 .panel_compat[1] = NULL,
118 },
Chris Morgan3db220e2023-05-15 11:00:30 -0500119};
120
Chris Morgan8c4e3042023-04-21 10:59:19 -0500121/*
122 * Start LED very early so user knows device is on. Set color
Chris Morgana824aa62023-05-15 11:00:29 -0500123 * to red.
Chris Morgan8c4e3042023-04-21 10:59:19 -0500124 */
125void spl_board_init(void)
126{
Chris Morgana824aa62023-05-15 11:00:29 -0500127 /* Set GPIO0_C5, GPIO0_C6, and GPIO0_C7 to output. */
128 writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | \
129 (GPIO_C7 | GPIO_C6 | GPIO_C5),
Chris Morgan8c4e3042023-04-21 10:59:19 -0500130 (GPIO0_BASE + GPIO_SWPORT_DDR_H));
Chris Morgana824aa62023-05-15 11:00:29 -0500131 /* Set GPIO0_C5 and GPIO_C6 to 0 and GPIO0_C7 to 1. */
132 writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | GPIO_C7,
Chris Morgan8c4e3042023-04-21 10:59:19 -0500133 (GPIO0_BASE + GPIO_SWPORT_DR_H));
134}
135
136/* Use hardware rng to seed Linux random. */
137int board_rng_seed(struct abuf *buf)
138{
139 struct udevice *dev;
140 size_t len = 0x8;
141 u64 *data;
142
143 data = malloc(len);
144 if (!data) {
145 printf("Out of memory\n");
146 return -ENOMEM;
147 }
148
149 if (uclass_get_device(UCLASS_RNG, 0, &dev) || !dev) {
150 printf("No RNG device\n");
151 return -ENODEV;
152 }
153
154 if (dm_rng_read(dev, data, len)) {
155 printf("Reading RNG failed\n");
156 return -EIO;
157 }
158
159 abuf_init_set(buf, data, len);
160
161 return 0;
162}
163
164/*
165 * Buzz the buzzer so the user knows something is going on. Make it
166 * optional in case PWM is disabled.
167 */
168void __maybe_unused startup_buzz(void)
169{
170 struct udevice *dev;
171 int err;
172
173 err = uclass_get_device(UCLASS_PWM, 0, &dev);
174 if (err)
175 printf("pwm not found\n");
176
177 pwm_set_enable(dev, 0, 1);
178 mdelay(200);
179 pwm_set_enable(dev, 0, 0);
180}
181
Chris Morgan3db220e2023-05-15 11:00:30 -0500182/*
183 * Provide the bare minimum to identify the panel for the RG353
184 * series. Since we don't have a working framebuffer device, no
185 * need to init the panel; just identify it and provide the
186 * clocks so we know what to set the different clock values to.
187 */
188
189static const struct display_timing rg353_default_timing = {
190 .pixelclock.typ = 24150000,
191 .hactive.typ = 640,
192 .hfront_porch.typ = 40,
193 .hback_porch.typ = 80,
194 .hsync_len.typ = 2,
195 .vactive.typ = 480,
196 .vfront_porch.typ = 18,
197 .vback_porch.typ = 28,
198 .vsync_len.typ = 2,
199 .flags = DISPLAY_FLAGS_HSYNC_HIGH |
200 DISPLAY_FLAGS_VSYNC_HIGH,
201};
202
203static int anbernic_rg353_panel_get_timing(struct udevice *dev,
204 struct display_timing *timings)
205{
206 memcpy(timings, &rg353_default_timing, sizeof(*timings));
207
208 return 0;
209}
210
211static int anbernic_rg353_panel_probe(struct udevice *dev)
212{
213 struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
214
215 plat->lanes = 4;
216 plat->format = MIPI_DSI_FMT_RGB888;
217 plat->mode_flags = MIPI_DSI_MODE_VIDEO |
218 MIPI_DSI_MODE_VIDEO_BURST |
219 MIPI_DSI_MODE_EOT_PACKET |
220 MIPI_DSI_MODE_LPM;
221
222 return 0;
223}
224
225static const struct panel_ops anbernic_rg353_panel_ops = {
226 .get_display_timing = anbernic_rg353_panel_get_timing,
227};
228
229U_BOOT_DRIVER(anbernic_rg353_panel) = {
230 .name = "anbernic_rg353_panel",
231 .id = UCLASS_PANEL,
232 .ops = &anbernic_rg353_panel_ops,
233 .probe = anbernic_rg353_panel_probe,
234 .plat_auto = sizeof(struct mipi_dsi_panel_plat),
235};
236
237int rgxx3_detect_display(void)
238{
239 struct udevice *dev;
240 struct mipi_dsi_device *dsi;
241 struct mipi_dsi_panel_plat *mplat;
242 const struct rg353_panel *panel;
243 int ret = 0;
244 int i;
245 u8 panel_id[2];
246
247 /*
248 * Take panel out of reset status.
249 * Set GPIO4_A0 to output.
250 */
251 writel(GPIO_WRITEMASK(GPIO_A0) | GPIO_A0,
252 (GPIO4_BASE + GPIO_SWPORT_DDR_L));
253 /* Set GPIO4_A0 to 1. */
254 writel(GPIO_WRITEMASK(GPIO_A0) | GPIO_A0,
255 (GPIO4_BASE + GPIO_SWPORT_DR_L));
256
257 /* Probe the DSI controller. */
258 ret = uclass_get_device_by_name(UCLASS_VIDEO_BRIDGE,
259 "dsi@fe060000", &dev);
260 if (ret) {
261 printf("DSI host not probed: %d\n", ret);
262 return ret;
263 }
264
265 /* Probe the DSI panel. */
266 ret = device_bind_driver_to_node(dev, "anbernic_rg353_panel",
267 "anbernic_rg353_panel",
268 dev_ofnode(dev), NULL);
269 if (ret) {
270 printf("Failed to probe RG353 panel: %d\n", ret);
271 return ret;
272 }
273
274 /*
275 * Attach the DSI controller which will also probe and attach
276 * the DSIDPHY.
277 */
278 ret = video_bridge_attach(dev);
279 if (ret) {
280 printf("Failed to attach DSI controller: %d\n", ret);
281 return ret;
282 }
283
284 /*
285 * Get the panel which should have already been probed by the
286 * video_bridge_attach() function.
287 */
288 ret = uclass_first_device_err(UCLASS_PANEL, &dev);
289 if (ret) {
290 printf("Panel device error: %d\n", ret);
291 return ret;
292 }
293
294 /* Now call the panel via DSI commands to get the panel ID. */
295 mplat = dev_get_plat(dev);
296 dsi = mplat->device;
297 mipi_dsi_set_maximum_return_packet_size(dsi, sizeof(panel_id));
298 ret = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_ID, &panel_id,
299 sizeof(panel_id));
300 if (ret < 0) {
301 printf("Unable to read panel ID: %d\n", ret);
302 return ret;
303 }
304
305 /* Get the correct panel compatible from the table. */
306 for (i = 0; i < ARRAY_SIZE(rg353_panel_details); i++) {
307 if (rg353_panel_details[i].id == ((panel_id[0] << 8) |
308 panel_id[1])) {
309 panel = &rg353_panel_details[i];
310 break;
311 }
312 }
313
314 if (!panel) {
315 printf("Unable to identify panel_id %x\n",
316 (panel_id[0] << 8) | panel_id[1]);
Chris Morgan3db220e2023-05-15 11:00:30 -0500317 return -EINVAL;
318 }
319
Chris Morgana63ff502024-01-02 09:46:48 -0600320 env_set("panel", panel->panel_compat[0]);
Chris Morgan3db220e2023-05-15 11:00:30 -0500321
322 return 0;
323}
324
Chris Morgan8c4e3042023-04-21 10:59:19 -0500325/* Detect which Anbernic RGXX3 device we are using so as to load the
326 * correct devicetree for Linux. Set an environment variable once
327 * found. The detection depends on the value of ADC channel 1, the
Chris Morgana824aa62023-05-15 11:00:29 -0500328 * presence of an eMMC on mmc0, and querying the DSI panel.
Chris Morgan8c4e3042023-04-21 10:59:19 -0500329 */
330int rgxx3_detect_device(void)
331{
332 u32 adc_info;
Chris Morgana824aa62023-05-15 11:00:29 -0500333 int ret, i;
Chris Morgan8c4e3042023-04-21 10:59:19 -0500334 int board_id = -ENXIO;
335 struct mmc *mmc;
336
337 ret = adc_channel_single_shot("saradc@fe720000", 1, &adc_info);
338 if (ret) {
339 printf("Read SARADC failed with error %d\n", ret);
340 return ret;
341 }
342
Chris Morgana824aa62023-05-15 11:00:29 -0500343 /*
344 * Get the correct device from the table. The ADC value is
345 * determined by a resistor on ADC channel 0. The hardware
346 * design calls for no more than a 1% variance on the
347 * resistor, so assume a +- value of 15 should be enough.
348 */
349 for (i = 0; i < ARRAY_SIZE(rg3xx_model_details); i++) {
350 u32 adc_min = rg3xx_model_details[i].adc_value - 15;
351 u32 adc_max = rg3xx_model_details[i].adc_value + 15;
352
353 if (adc_min < adc_info && adc_max > adc_info) {
354 board_id = i;
355 break;
356 }
357 }
Chris Morgan8c4e3042023-04-21 10:59:19 -0500358
359 /*
Chris Morgana824aa62023-05-15 11:00:29 -0500360 * Try to access the eMMC on an RG353V or RG353P. If it's
361 * missing, it's an RG353VS or RG353PS. Note we could also
362 * check for a touchscreen at 0x1a on i2c2.
Chris Morgan8c4e3042023-04-21 10:59:19 -0500363 */
Chris Morgana824aa62023-05-15 11:00:29 -0500364 if (board_id == RG353V || board_id == RG353P) {
Chris Morgan8c4e3042023-04-21 10:59:19 -0500365 mmc = find_mmc_device(0);
366 if (mmc) {
367 ret = mmc_init(mmc);
Chris Morgana824aa62023-05-15 11:00:29 -0500368 if (ret) {
369 if (board_id == RG353V)
370 board_id = RG353VS;
371 else
372 board_id = RG353PS;
373 }
Chris Morgan8c4e3042023-04-21 10:59:19 -0500374 }
375 }
376
377 if (board_id < 0)
378 return board_id;
379
380 env_set("board", rg3xx_model_details[board_id].board);
381 env_set("board_name",
382 rg3xx_model_details[board_id].board_name);
383 env_set("fdtfile", rg3xx_model_details[board_id].fdtfile);
384
Chris Morgana63ff502024-01-02 09:46:48 -0600385 /* Skip panel detection for when it is not needed. */
386 if (!rg3xx_model_details[board_id].detect_panel)
Chris Morgan3db220e2023-05-15 11:00:30 -0500387 return 0;
388
Chris Morgana63ff502024-01-02 09:46:48 -0600389 /* Warn but don't fail for errors in auto-detection of the panel. */
Chris Morgan3db220e2023-05-15 11:00:30 -0500390 ret = rgxx3_detect_display();
391 if (ret)
Chris Morgana63ff502024-01-02 09:46:48 -0600392 printf("Failed to detect panel type\n");
Chris Morgan3db220e2023-05-15 11:00:30 -0500393
Chris Morgan8c4e3042023-04-21 10:59:19 -0500394 return 0;
395}
396
397int rk_board_late_init(void)
398{
399 int ret;
400
Chris Morgan8c4e3042023-04-21 10:59:19 -0500401 ret = rgxx3_detect_device();
402 if (ret) {
403 printf("Unable to detect device type: %d\n", ret);
404 return ret;
405 }
406
Chris Morgana824aa62023-05-15 11:00:29 -0500407 /* Turn off red LED and turn on orange LED. */
408 writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | GPIO_C6,
409 (GPIO0_BASE + GPIO_SWPORT_DR_H));
410
Chris Morgan8c4e3042023-04-21 10:59:19 -0500411 if (IS_ENABLED(CONFIG_DM_PWM))
412 startup_buzz();
413
414 return 0;
415}
Chris Morgana824aa62023-05-15 11:00:29 -0500416
417int ft_board_setup(void *blob, struct bd_info *bd)
418{
Chris Morgana63ff502024-01-02 09:46:48 -0600419 const struct rg353_panel *panel = NULL;
420 int node, ret, i;
Chris Morgana824aa62023-05-15 11:00:29 -0500421 char *env;
422
423 /* No fixups necessary for the RG503 */
424 env = env_get("board_name");
425 if (env && (!strcmp(env, rg3xx_model_details[RG503].board_name)))
426 return 0;
427
428 /* Change the model name of the RG353M */
429 if (env && (!strcmp(env, rg3xx_model_details[RG353M].board_name)))
430 fdt_setprop(blob, 0, "model",
431 rg3xx_model_details[RG353M].board_name,
432 sizeof(rg3xx_model_details[RG353M].board_name));
433
Chris Morgana63ff502024-01-02 09:46:48 -0600434 env = env_get("panel");
435 if (!env) {
436 printf("Can't get panel env\n");
437 return 0;
438 }
439
Chris Morgan3db220e2023-05-15 11:00:30 -0500440 /*
441 * Check if the environment variable doesn't equal the panel.
442 * If it doesn't, update the devicetree to the correct panel.
443 */
444 node = fdt_path_offset(blob, "/dsi@fe060000/panel@0");
445 if (!(node > 0)) {
446 printf("Can't find the DSI node\n");
447 return -ENODEV;
448 }
449
Chris Morgan3db220e2023-05-15 11:00:30 -0500450 ret = fdt_node_check_compatible(blob, node, env);
451 if (ret < 0)
452 return -ENODEV;
453
454 /* Panels match, return 0. */
455 if (!ret)
456 return 0;
457
Chris Morgana63ff502024-01-02 09:46:48 -0600458 /* Panels don't match, search by first compatible value. */
459 for (i = 0; i < ARRAY_SIZE(rg353_panel_details); i++) {
460 if (!strcmp(env, rg353_panel_details[i].panel_compat[0])) {
461 panel = &rg353_panel_details[i];
462 break;
463 }
464 }
465
466 if (!panel) {
467 printf("Unable to identify panel by compat string\n");
468 return -ENODEV;
469 }
470
471 /* Set the compatible with the auto-detected values */
472 fdt_setprop_string(blob, node, "compatible", panel->panel_compat[0]);
473 if (panel->panel_compat[1])
474 fdt_appendprop_string(blob, node, "compatible",
475 panel->panel_compat[1]);
Chris Morgan3db220e2023-05-15 11:00:30 -0500476
Chris Morgana824aa62023-05-15 11:00:29 -0500477 return 0;
478}