// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2023 Chris Morgan */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; #define GPIO0_BASE 0xfdd60000 #define GPIO4_BASE 0xfe770000 #define GPIO_SWPORT_DR_L 0x0000 #define GPIO_SWPORT_DR_H 0x0004 #define GPIO_SWPORT_DDR_L 0x0008 #define GPIO_SWPORT_DDR_H 0x000c #define GPIO_A0 BIT(0) #define GPIO_C5 BIT(5) #define GPIO_C6 BIT(6) #define GPIO_C7 BIT(7) #define GPIO_WRITEMASK(bits) ((bits) << 16) #define DTB_DIR "rockchip/" struct rg3xx_model { const u16 adc_value; const char *board; const char *board_name; const char *fdtfile; const bool detect_panel; const bool detect_regulator; const bool uart_con; }; enum rgxx3_device_id { RG353M = 1, RG353P, RG353V, RG503, RGB30, RK2023, RGARCD, RGB10MAX3, /* Devices with duplicate ADC value */ RG353PS, RG353VS, RGARCS, }; static const struct rg3xx_model rg3xx_model_details[] = { [RG353M] = { .adc_value = 517, /* Observed average from device */ .board = "rk3566-anbernic-rg353m", .board_name = "Anbernic RG353M", /* Device is identical to RG353P. */ .fdtfile = DTB_DIR "rk3566-anbernic-rg353p.dtb", .detect_panel = 1, .detect_regulator = 0, .uart_con = 1, }, [RG353P] = { .adc_value = 860, /* Documented value of 860 */ .board = "rk3566-anbernic-rg353p", .board_name = "Anbernic RG353P", .fdtfile = DTB_DIR "rk3566-anbernic-rg353p.dtb", .detect_panel = 1, .detect_regulator = 0, .uart_con = 1, }, [RG353V] = { .adc_value = 695, /* Observed average from device */ .board = "rk3566-anbernic-rg353v", .board_name = "Anbernic RG353V", .fdtfile = DTB_DIR "rk3566-anbernic-rg353v.dtb", .detect_panel = 1, .detect_regulator = 0, .uart_con = 1, }, [RG503] = { .adc_value = 1023, /* Observed average from device */ .board = "rk3566-anbernic-rg503", .board_name = "Anbernic RG503", .fdtfile = DTB_DIR "rk3566-anbernic-rg503.dtb", .detect_panel = 0, .detect_regulator = 0, .uart_con = 1, }, [RGB30] = { .adc_value = 383, /* Gathered from second hand information */ .board = "rk3566-powkiddy-rgb30", .board_name = "Powkiddy RGB30", .fdtfile = DTB_DIR "rk3566-powkiddy-rgb30.dtb", .detect_panel = 0, .detect_regulator = 1, .uart_con = 0, }, [RK2023] = { .adc_value = 635, /* Observed average from device */ .board = "rk3566-powkiddy-rk2023", .board_name = "Powkiddy RK2023", .fdtfile = DTB_DIR "rk3566-powkiddy-rk2023.dtb", .detect_panel = 0, .detect_regulator = 1, .uart_con = 0, }, [RGARCD] = { .adc_value = 183, /* Observed average from device */ .board = "rk3566-anbernic-rg-arc-d", .board_name = "Anbernic RG ARC-D", .fdtfile = DTB_DIR "rk3566-anbernic-rg-arc-d.dtb", .detect_panel = 0, .detect_regulator = 0, .uart_con = 1, }, [RGB10MAX3] = { .adc_value = 765, /* Observed average from device */ .board = "rk3566-powkiddy-rgb10max3", .board_name = "Powkiddy RGB10MAX3", .fdtfile = DTB_DIR "rk3566-powkiddy-rgb10max3.dtb", .detect_panel = 0, .detect_regulator = 1, .uart_con = 0, }, /* Devices with duplicate ADC value */ [RG353PS] = { .adc_value = 860, /* Observed average from device */ .board = "rk3566-anbernic-rg353ps", .board_name = "Anbernic RG353PS", .fdtfile = DTB_DIR "rk3566-anbernic-rg353ps.dtb", .detect_panel = 1, .detect_regulator = 0, .uart_con = 1, }, [RG353VS] = { .adc_value = 695, /* Gathered from second hand information */ .board = "rk3566-anbernic-rg353vs", .board_name = "Anbernic RG353VS", .fdtfile = DTB_DIR "rk3566-anbernic-rg353vs.dtb", .detect_panel = 1, .detect_regulator = 0, .uart_con = 1, }, [RGARCS] = { .adc_value = 183, /* Observed average from device */ .board = "rk3566-anbernic-rg-arc-s", .board_name = "Anbernic RG ARC-S", .fdtfile = DTB_DIR "rk3566-anbernic-rg-arc-s.dtb", .detect_panel = 0, .detect_regulator = 0, .uart_con = 1, }, }; struct rg353_panel { const u16 id; const char *panel_compat[2]; }; static const struct rg353_panel rg353_panel_details[] = { { .id = 0x3052, .panel_compat[0] = "anbernic,rg353p-panel", .panel_compat[1] = "newvision,nv3051d", }, { .id = 0x3821, .panel_compat[0] = "anbernic,rg353v-panel-v2", .panel_compat[1] = NULL, }, }; struct powkiddy_regulators { const u8 addr; const char *regulator_compat; }; static const struct powkiddy_regulators regulator_details[] = { { .addr = 0x1c, .regulator_compat = "tcs,tcs4525", }, { .addr = 0x40, .regulator_compat = "fcs,fan53555", }, }; /* * Start LED very early so user knows device is on. Set color * to red. */ void spl_board_init(void) { /* Set GPIO0_C5, GPIO0_C6, and GPIO0_C7 to output. */ writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | (GPIO_C7 | GPIO_C6 | GPIO_C5), (GPIO0_BASE + GPIO_SWPORT_DDR_H)); /* Set GPIO0_C5 and GPIO_C6 to 0 and GPIO0_C7 to 1. */ writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | GPIO_C7, (GPIO0_BASE + GPIO_SWPORT_DR_H)); } /* * Buzz the buzzer so the user knows something is going on. Make it * optional in case PWM is disabled or if CONFIG_DM_PWM is not * enabled. */ void __maybe_unused startup_buzz(void) { struct udevice *dev; int err; if (!IS_ENABLED(CONFIG_DM_PWM)) return; /* Probe the PWM controller. */ err = uclass_get_device_by_name(UCLASS_PWM, "pwm@fe6e0010", &dev); if (err) return; pwm_set_enable(dev, 0, 1); mdelay(200); pwm_set_enable(dev, 0, 0); } /* * Provide the bare minimum to identify the panel for the RG353 * series. Since we don't have a working framebuffer device, no * need to init the panel; just identify it and provide the * clocks so we know what to set the different clock values to. */ static const struct display_timing rg353_default_timing = { .pixelclock.typ = 24150000, .hactive.typ = 640, .hfront_porch.typ = 40, .hback_porch.typ = 80, .hsync_len.typ = 2, .vactive.typ = 480, .vfront_porch.typ = 18, .vback_porch.typ = 28, .vsync_len.typ = 2, .flags = DISPLAY_FLAGS_HSYNC_HIGH | DISPLAY_FLAGS_VSYNC_HIGH, }; static int anbernic_rg353_panel_get_timing(struct udevice *dev, struct display_timing *timings) { memcpy(timings, &rg353_default_timing, sizeof(*timings)); return 0; } static int anbernic_rg353_panel_probe(struct udevice *dev) { struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); plat->lanes = 4; plat->format = MIPI_DSI_FMT_RGB888; plat->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_LPM; return 0; } static const struct panel_ops anbernic_rg353_panel_ops = { .get_display_timing = anbernic_rg353_panel_get_timing, }; U_BOOT_DRIVER(anbernic_rg353_panel) = { .name = "anbernic_rg353_panel", .id = UCLASS_PANEL, .ops = &anbernic_rg353_panel_ops, .probe = anbernic_rg353_panel_probe, .plat_auto = sizeof(struct mipi_dsi_panel_plat), }; /* * The Anbernic 353 series shipped with 2 distinct displays requiring * 2 distinct drivers, with no way for a user to know which panel is * which. This function queries the DSI panel for the panel ID to * determine which panel is present so the device-tree can be corrected * automatically. */ int rgxx3_detect_display(void) { struct udevice *dev; struct mipi_dsi_device *dsi; struct mipi_dsi_panel_plat *mplat; const struct rg353_panel *panel; int ret = 0; int i; u8 panel_id[2]; /* * Take panel out of reset status. * Set GPIO4_A0 to output. */ writel(GPIO_WRITEMASK(GPIO_A0) | GPIO_A0, (GPIO4_BASE + GPIO_SWPORT_DDR_L)); /* Set GPIO4_A0 to 1. */ writel(GPIO_WRITEMASK(GPIO_A0) | GPIO_A0, (GPIO4_BASE + GPIO_SWPORT_DR_L)); /* Probe the DSI controller. */ ret = uclass_get_device_by_name(UCLASS_VIDEO_BRIDGE, "dsi@fe060000", &dev); if (ret) { printf("DSI host not probed: %d\n", ret); return ret; } /* Probe the DSI panel. */ ret = device_bind_driver_to_node(dev, "anbernic_rg353_panel", "anbernic_rg353_panel", dev_ofnode(dev), NULL); if (ret) { printf("Failed to probe RG353 panel: %d\n", ret); return ret; } /* * Attach the DSI controller which will also probe and attach * the DSIDPHY. */ ret = video_bridge_attach(dev); if (ret) { printf("Failed to attach DSI controller: %d\n", ret); return ret; } /* * Get the panel which should have already been probed by the * video_bridge_attach() function. */ ret = uclass_first_device_err(UCLASS_PANEL, &dev); if (ret) { printf("Panel device error: %d\n", ret); return ret; } /* Now call the panel via DSI commands to get the panel ID. */ mplat = dev_get_plat(dev); dsi = mplat->device; mipi_dsi_set_maximum_return_packet_size(dsi, sizeof(panel_id)); ret = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_ID, &panel_id, sizeof(panel_id)); if (ret < 0) { printf("Unable to read panel ID: %d\n", ret); return ret; } /* Get the correct panel compatible from the table. */ for (i = 0; i < ARRAY_SIZE(rg353_panel_details); i++) { if (rg353_panel_details[i].id == ((panel_id[0] << 8) | panel_id[1])) { panel = &rg353_panel_details[i]; break; } } if (!panel) { printf("Unable to identify panel_id %x\n", (panel_id[0] << 8) | panel_id[1]); return -EINVAL; } env_set("panel", panel->panel_compat[0]); return 0; } /* * Some of the Powkiddy devices switched the CPU regulator, but users * are not able to determine this by looking at their hardware. * Attempt to auto-detect this situation and fixup the device-tree. */ int rgxx3_detect_regulator(void) { struct udevice *bus; struct udevice *chip; u8 val; int ret; /* Get the correct i2c bus (i2c0). */ ret = uclass_get_device_by_name(UCLASS_I2C, "i2c@fdd40000", &bus); if (ret) return ret; /* * Check for all vdd_cpu regulators and read an arbitrary * register to confirm it's present. */ for (int i = 0; i < ARRAY_SIZE(regulator_details); i++) { ret = i2c_get_chip(bus, regulator_details[i].addr, 1, &chip); if (ret) return ret; ret = dm_i2c_read(chip, 0, &val, 1); if (!ret) { env_set("vdd_cpu", regulator_details[i].regulator_compat); break; } } return 0; } int rgxx3_read_board_id(void) { u32 adc_info; int ret; ret = adc_channel_single_shot("saradc@fe720000", 1, &adc_info); if (ret) { printf("Read SARADC failed with error %d\n", ret); return ret; } /* * Get the correct device from the table. The ADC value is * determined by a resistor on ADC channel 0. The hardware * design calls for no more than a 1% variance on the * resistor, so assume a +- value of 15 should be enough. */ for (int i = 0; i < ARRAY_SIZE(rg3xx_model_details); i++) { u32 adc_min = rg3xx_model_details[i].adc_value - 15; u32 adc_max = rg3xx_model_details[i].adc_value + 15; if (adc_min < adc_info && adc_max > adc_info) return i; } return -ENODEV; } /* Detect which Anbernic RGXX3 device we are using so as to load the * correct devicetree for Linux. Set an environment variable once * found. The detection depends on the value of ADC channel 1 and the * presence of an eMMC on mmc0. */ int rgxx3_detect_device(void) { int ret; int board_id; struct mmc *mmc; board_id = rgxx3_read_board_id(); if (board_id < 0) return board_id; /* * Try to access the eMMC on an RG353V, RG353P, or RG Arc D. * If it's missing, it's an RG353VS, RG353PS, or RG Arc S. * Note we could also check for a touchscreen at 0x1a on i2c2. */ if (board_id == RG353V || board_id == RG353P || board_id == RGARCD) { mmc = find_mmc_device(0); if (mmc) { ret = mmc_init(mmc); if (ret) { if (board_id == RG353V) board_id = RG353VS; else if (board_id == RG353P) board_id = RG353PS; else board_id = RGARCS; } } } return board_id; } /* * Check the loaded device tree to set the correct gd->board_type. * Disable the console if the board doesn't support a console. */ int set_gd_value(void) { const char *model; model = fdt_getprop(gd->fdt_blob, 0, "model", NULL); for (int i = 0; i < ARRAY_SIZE(rg3xx_model_details); i++) { if (strcmp(rg3xx_model_details[i].board_name, model) == 0) { gd->board_type = i; if (!rg3xx_model_details[i].uart_con) gd->flags |= GD_FLG_SILENT | GD_FLG_DISABLE_CONSOLE; return 0; } } return -ENODEV; } int rk_board_late_init(void) { int ret; ret = set_gd_value(); if (ret) { printf("Unable to auto-detect device\n"); goto end; } /* * Change the model number on the RG353M since it uses the same * tree as the RG353P. */ if (gd->board_type == RG353P) { ret = rgxx3_read_board_id(); if (ret > 0) gd->board_type = ret; } env_set("board", rg3xx_model_details[gd->board_type].board); env_set("board_name", rg3xx_model_details[gd->board_type].board_name); env_set("fdtfile", rg3xx_model_details[gd->board_type].fdtfile); /* * Skip panel detection if not needed. Warn but don't fail for * errors in auto-detection of the panel. */ if (rg3xx_model_details[gd->board_type].detect_panel) { ret = rgxx3_detect_display(); if (ret) printf("Failed to detect panel type\n"); } /* * Skip vdd_cpu regulator detection if not needed. Warn but * don't fail for errors in auto-detection of regulator. */ if (rg3xx_model_details[gd->board_type].detect_regulator) { ret = rgxx3_detect_regulator(); if (ret) printf("Unable to detect vdd_cpu regulator\n"); } end: /* Turn off red LED and turn on orange LED. */ writel(GPIO_WRITEMASK(GPIO_C7 | GPIO_C6 | GPIO_C5) | GPIO_C6, (GPIO0_BASE + GPIO_SWPORT_DR_H)); startup_buzz(); return 0; } int rgxx3_panel_fixup(void *blob) { const struct rg353_panel *panel = NULL; int node, ret; char *env; env = env_get("panel"); if (!env) { printf("Can't get panel env\n"); return -EINVAL; } /* * Check if the environment variable doesn't equal the panel. * If it doesn't, update the devicetree to the correct panel. */ node = fdt_path_offset(blob, "/dsi@fe060000/panel@0"); if (!(node > 0)) { printf("Can't find the DSI node\n"); return -ENODEV; } ret = fdt_node_check_compatible(blob, node, env); if (ret < 0) return -ENODEV; /* Panels match, return 0. */ if (!ret) return 0; /* Panels don't match, search by first compatible value. */ for (int i = 0; i < ARRAY_SIZE(rg353_panel_details); i++) { if (!strcmp(env, rg353_panel_details[i].panel_compat[0])) { panel = &rg353_panel_details[i]; break; } } if (!panel) { printf("Unable to identify panel by compat string\n"); return -ENODEV; } /* Set the compatible with the auto-detected values */ fdt_setprop_string(blob, node, "compatible", panel->panel_compat[0]); if (panel->panel_compat[1]) fdt_appendprop_string(blob, node, "compatible", panel->panel_compat[1]); return 0; } int rgxx3_regulator_fixup(void *blob) { const struct powkiddy_regulators *vdd_cpu = NULL; int node, ret, i; char path[] = "/i2c@fdd40000/regulator@00"; char name[] = "regulator@00"; char *env; env = env_get("vdd_cpu"); if (!env) { printf("Can't get vdd_cpu env\n"); return -EINVAL; } /* * Find the device we have in our tree, which may or may not * be present. */ for (i = 0; i < ARRAY_SIZE(regulator_details); i++) { sprintf(path, "/i2c@fdd40000/regulator@%02x", regulator_details[i].addr); node = fdt_path_offset(blob, path); if (node > 0) break; printf("Unable to find vdd_cpu\n"); return -ENODEV; } node = fdt_path_offset(blob, path); if (!(node > 0)) { printf("Can't find the vdd_cpu node\n"); return -ENODEV; } ret = fdt_node_check_compatible(blob, node, env); if (ret < 0) return -ENODEV; /* vdd_cpu regulators match, return 0. */ if (!ret) return 0; /* Regulators don't match, search by first compatible value. */ for (i = 0; i < ARRAY_SIZE(regulator_details); i++) { if (!strcmp(env, regulator_details[i].regulator_compat)) { vdd_cpu = ®ulator_details[i]; break; } } if (!vdd_cpu) { printf("Unable to identify vdd_cpu by compat string\n"); return -ENODEV; } /* Set the compatible and reg with the auto-detected values */ fdt_setprop_string(blob, node, "compatible", vdd_cpu->regulator_compat); fdt_setprop_u32(blob, node, "reg", vdd_cpu->addr); sprintf(name, "regulator@%02x", vdd_cpu->addr); fdt_set_name(blob, node, name); return 0; } int ft_board_setup(void *blob, struct bd_info *bd) { int ret; if (gd->board_type == RG353M) fdt_setprop(blob, 0, "model", rg3xx_model_details[RG353M].board_name, sizeof(rg3xx_model_details[RG353M].board_name)); if (rg3xx_model_details[gd->board_type].detect_panel) { ret = rgxx3_panel_fixup(blob); if (ret) printf("Unable to update panel compat\n"); } if (rg3xx_model_details[gd->board_type].detect_regulator) { ret = rgxx3_regulator_fixup(blob); if (ret) printf("Unable to update vdd_cpu compat\n"); } return 0; } int board_fit_config_name_match(const char *name) { int ret; if (gd->board_type == 0) { ret = rgxx3_detect_device(); if (ret < 0) return ret; gd->board_type = ret; } if (strcmp(name, rg3xx_model_details[gd->board_type].fdtfile) == 0) return 0; return -ENXIO; }