Message ID | 20230515160032.126742-5-macroalpha82@gmail.com |
---|---|
State | Accepted |
Commit | ff27afedff95f25bcd3c3ce90a4f35ea16b37e55 |
Delegated to: | Kever Yang |
Headers | show |
Series | Add Support for RG353PS and Panel Auto Detection | expand |
On 2023/5/16 00:00, Chris Morgan wrote: > From: Chris Morgan <macromorgan@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@hotmail.com> Reviewed-by: Kever Yang <kever.yang@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@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@fe060000/panel@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@fe060000/panel@0", > + "compatible", env); > + > return 0; > }
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@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@fe060000/panel@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@fe060000/panel@0", + "compatible", env); + return 0; }