[PATCH V2 4/6] board: rockchip: Add panel auto-detection for Anbernic RGxx3

Kever Yang kever.yang at rock-chips.com
Wed Jul 26 11:12:10 CEST 2023


On 2023/5/16 00:00, Chris Morgan wrote:
> From: Chris Morgan <macromorgan at hotmail.com>
>
> Add support to automatically detect the panel for the Anbernic RGxx3.
> This is done by creating a "pseudo driver" that provides only the bare
> minimum to start the DSI controller and DSI DPHY. Once started, we then
> can query the panel for its panel ID and compare it to a table of known
> values. The panel compatible string (which corresponds to the upstream
> Linux driver) is then defined as an environment variable "panel". The
> panel compatible string is also changed automatically via an
> ft_board_setup() call if what is detected differs from what is in the
> loaded tree. This way, end users can use the same bootloader without
> having to worry about which panel they have (as there is no obvious
> way of knowing).
>
> Signed-off-by: Chris Morgan <macromorgan at hotmail.com>
Reviewed-by: Kever Yang <kever.yang at rock-chips.com>

Thanks,
- Kever
> ---
>   board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c | 196 +++++++++++++++++++++
>   1 file changed, 196 insertions(+)
>
> diff --git a/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c b/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c
> index 4d3c724b9c..3f1a42d184 100644
> --- a/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c
> +++ b/board/anbernic/rgxx3_rk3566/rgxx3-rk3566.c
> @@ -6,21 +6,27 @@
>   #include <abuf.h>
>   #include <adc.h>
>   #include <asm/io.h>
> +#include <display.h>
>   #include <dm.h>
>   #include <dm/lists.h>
>   #include <env.h>
>   #include <fdt_support.h>
>   #include <linux/delay.h>
> +#include <mipi_dsi.h>
>   #include <mmc.h>
> +#include <panel.h>
>   #include <pwm.h>
>   #include <rng.h>
>   #include <stdlib.h>
> +#include <video_bridge.h>
>   
>   #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)
> @@ -86,6 +92,16 @@ static const struct rg3xx_model rg3xx_model_details[] = {
>   	},
>   };
>   
> +struct rg353_panel {
> +	const u16 id;
> +	const char *panel_compat;
> +};
> +
> +static const struct rg353_panel rg353_panel_details[] = {
> +	{ .id = 0x3052, .panel_compat = "newvision,nv3051d"},
> +	{ .id = 0x3821, .panel_compat = "anbernic,rg353v-panel-v2"},
> +};
> +
>   /*
>    * Start LED very early so user knows device is on. Set color
>    * to red.
> @@ -147,6 +163,150 @@ void __maybe_unused startup_buzz(void)
>   	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),
> +};
> +
> +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 at 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]);
> +		env_set("panel", "unknown");
> +		return -EINVAL;
> +	}
> +
> +	env_set("panel", panel->panel_compat);
> +
> +	return 0;
> +}
> +
>   /* 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, the
> @@ -207,6 +367,14 @@ int rgxx3_detect_device(void)
>   		rg3xx_model_details[board_id].board_name);
>   	env_set("fdtfile", rg3xx_model_details[board_id].fdtfile);
>   
> +	/* Detect the panel type for any device that isn't a 503. */
> +	if (board_id == RG503)
> +		return 0;
> +
> +	ret = rgxx3_detect_display();
> +	if (ret)
> +		return ret;
> +
>   	return 0;
>   }
>   
> @@ -232,6 +400,7 @@ int rk_board_late_init(void)
>   
>   int ft_board_setup(void *blob, struct bd_info *bd)
>   {
> +	int node, ret;
>   	char *env;
>   
>   	/* No fixups necessary for the RG503 */
> @@ -245,5 +414,32 @@ int ft_board_setup(void *blob, struct bd_info *bd)
>   			    rg3xx_model_details[RG353M].board_name,
>   			    sizeof(rg3xx_model_details[RG353M].board_name));
>   
> +	/*
> +	 * 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 at fe060000/panel at 0");
> +	if (!(node > 0)) {
> +		printf("Can't find the DSI node\n");
> +		return -ENODEV;
> +	}
> +
> +	env = env_get("panel");
> +	if (!env) {
> +		printf("Can't get panel env\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = fdt_node_check_compatible(blob, node, env);
> +	if (ret < 0)
> +		return -ENODEV;
> +
> +	/* Panels match, return 0. */
> +	if (!ret)
> +		return 0;
> +
> +	do_fixup_by_path_string(blob, "/dsi at fe060000/panel at 0",
> +				"compatible", env);
> +
>   	return 0;
>   }


More information about the U-Boot mailing list