On Thu, Apr 14, 2022 at 10:19 AM xiazhengqiao <xiazhengqiao@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx> wrote: > Add STARRY 2081101QFH032011-53G 10.1" WUXGA TFT LCD panel > > Signed-off-by: xiazhengqiao <xiazhengqiao@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx> > Tested-by: Hsin-Yi Wang <hsinyi@xxxxxxxxxxxx> > Reviewed-by: Hsin-Yi Wang <hsinyi@xxxxxxxxxxxx> OK cool... Do you know the name of the display controller used in this panel? We tend to name the implementation, Kconfig symbols etc after the display controller such as panel-ilitek-* panel-truly-* panel-novatek-* etc. This would be panel-innolux-xyznnnn.c in that case. We already have: panel-innolux-ej030na.c panel-innolux-p079zca.c > +struct panel_desc { Name it after the panel. struct inx_config to reflect other code in this subsystem. > + const struct drm_display_mode *modes; > + unsigned int bpc; > + > + /** > + * @width_mm: width of the panel's active display area > + * @height_mm: height of the panel's active display area > + */ > + struct { > + unsigned int width_mm; > + unsigned int height_mm; > + } size; This seems a bit overdesigned, why not just put these two unsigned in the strict as is without an anonymous struct in the struct? > + unsigned long mode_flags; > + enum mipi_dsi_pixel_format format; > + const struct panel_init_cmd *init_cmds; OK so a sequence of commands per panel type I get it. > + unsigned int lanes; > + bool discharge_on_disable; > +}; > + > +struct inx_panel { > + struct drm_panel base; > + struct mipi_dsi_device *dsi; > + > + const struct panel_desc *desc; > + > + enum drm_panel_orientation orientation; > + struct regulator *pp1800; > + struct regulator *avee; > + struct regulator *avdd; These match the pin names on the chip? (Just checking.) > + struct gpio_desc *enable_gpio; > + > + bool prepared; > +}; > + > +enum dsi_cmd_type { > + INIT_DCS_CMD, > + DELAY_CMD, > +}; I've seen this comand/delay sequencing before. Maybe something we should generalize as it keeps popping up? > +struct panel_init_cmd { > + enum dsi_cmd_type type; > + size_t len; > + const char *data; > +}; > + > +#define _INIT_DCS_CMD(...) { \ > + .type = INIT_DCS_CMD, \ > + .len = sizeof((char[]){__VA_ARGS__}), \ > + .data = (char[]){__VA_ARGS__} } > + > +#define _INIT_DELAY_CMD(...) { \ > + .type = DELAY_CMD,\ > + .len = sizeof((char[]){__VA_ARGS__}), \ > + .data = (char[]){__VA_ARGS__} } These defines are massive overkill, especially .len, if you look below it is clear that all of them have ... len = 2 and two that have len = 1. If these macros are just for this driver ... drop them, just do something like: struct inx_init_tuple { u8 cmd; u8 val; }; static const struct inx_tuple inx_init_seq[] = { { .cmd = ..., .val = ...} ... }; Then iterate over this array of structs until you reach ARRAY_SIZE(inx_init_seq). Just hardcode the delays etc in the end. > +static const struct panel_init_cmd starry_qfh032011_53g_init_cmd[] = { > + _INIT_DCS_CMD(0xB0, 0x01), All of these opaque 0xB0 etc, create a proper #define for that parameter using the name in the datasheet. It's especially helpful if the datasheet is not public which is usually the case, but I think that you have it? It's really hard for the next system using this to alter things if they have no idea what the different parameters are. > + _INIT_DCS_CMD(0xC3, 0x4F), > + _INIT_DCS_CMD(0xC4, 0x40), > + _INIT_DCS_CMD(0xC5, 0x40), > + _INIT_DCS_CMD(0xC6, 0x40), > + _INIT_DCS_CMD(0xC7, 0x40), > + _INIT_DCS_CMD(0xC8, 0x4D), > + _INIT_DCS_CMD(0xC9, 0x52), > + _INIT_DCS_CMD(0xCA, 0x51), > + _INIT_DCS_CMD(0xCD, 0x5D), > + _INIT_DCS_CMD(0xCE, 0x5B), > + _INIT_DCS_CMD(0xCF, 0x4B), > + _INIT_DCS_CMD(0xD0, 0x49), > + _INIT_DCS_CMD(0xD1, 0x47), > + _INIT_DCS_CMD(0xD2, 0x45), > + _INIT_DCS_CMD(0xD3, 0x41), > + _INIT_DCS_CMD(0xD7, 0x50), > + _INIT_DCS_CMD(0xD8, 0x40), > + _INIT_DCS_CMD(0xD9, 0x40), > + _INIT_DCS_CMD(0xDA, 0x40), > + _INIT_DCS_CMD(0xDB, 0x40), > + _INIT_DCS_CMD(0xDC, 0x4E), > + _INIT_DCS_CMD(0xDD, 0x52), > + _INIT_DCS_CMD(0xDE, 0x51), > + _INIT_DCS_CMD(0xE1, 0x5E), > + _INIT_DCS_CMD(0xE2, 0x5C), > + _INIT_DCS_CMD(0xE3, 0x4C), > + _INIT_DCS_CMD(0xE4, 0x4A), > + _INIT_DCS_CMD(0xE5, 0x48), > + _INIT_DCS_CMD(0xE6, 0x46), > + _INIT_DCS_CMD(0xE7, 0x42), > + _INIT_DCS_CMD(0xB0, 0x03), > + _INIT_DCS_CMD(0xBE, 0x03), > + _INIT_DCS_CMD(0xCC, 0x44), > + _INIT_DCS_CMD(0xC8, 0x07), > + _INIT_DCS_CMD(0xC9, 0x05), > + _INIT_DCS_CMD(0xCA, 0x42), > + _INIT_DCS_CMD(0xCD, 0x3E), > + _INIT_DCS_CMD(0xCF, 0x60), > + _INIT_DCS_CMD(0xD2, 0x04), > + _INIT_DCS_CMD(0xD3, 0x04), > + _INIT_DCS_CMD(0xD4, 0x01), > + _INIT_DCS_CMD(0xD5, 0x00), > + _INIT_DCS_CMD(0xD6, 0x03), > + _INIT_DCS_CMD(0xD7, 0x04), > + _INIT_DCS_CMD(0xD9, 0x01), > + _INIT_DCS_CMD(0xDB, 0x01), > + _INIT_DCS_CMD(0xE4, 0xF0), > + _INIT_DCS_CMD(0xE5, 0x0A), > + _INIT_DCS_CMD(0xB0, 0x00), > + _INIT_DCS_CMD(0xCC, 0x08), > + _INIT_DCS_CMD(0xC2, 0x08), > + _INIT_DCS_CMD(0xC4, 0x10), > + _INIT_DCS_CMD(0xB0, 0x02), > + _INIT_DCS_CMD(0xC0, 0x00), > + _INIT_DCS_CMD(0xC1, 0x0A), > + _INIT_DCS_CMD(0xC2, 0x20), > + _INIT_DCS_CMD(0xC3, 0x24), > + _INIT_DCS_CMD(0xC4, 0x23), > + _INIT_DCS_CMD(0xC5, 0x29), > + _INIT_DCS_CMD(0xC6, 0x23), > + _INIT_DCS_CMD(0xC7, 0x1C), > + _INIT_DCS_CMD(0xC8, 0x19), > + _INIT_DCS_CMD(0xC9, 0x17), > + _INIT_DCS_CMD(0xCA, 0x17), > + _INIT_DCS_CMD(0xCB, 0x18), > + _INIT_DCS_CMD(0xCC, 0x1A), > + _INIT_DCS_CMD(0xCD, 0x1E), > + _INIT_DCS_CMD(0xCE, 0x20), > + _INIT_DCS_CMD(0xCF, 0x23), > + _INIT_DCS_CMD(0xD0, 0x07), > + _INIT_DCS_CMD(0xD1, 0x00), > + _INIT_DCS_CMD(0xD2, 0x00), > + _INIT_DCS_CMD(0xD3, 0x0A), > + _INIT_DCS_CMD(0xD4, 0x13), > + _INIT_DCS_CMD(0xD5, 0x1C), > + _INIT_DCS_CMD(0xD6, 0x1A), > + _INIT_DCS_CMD(0xD7, 0x13), > + _INIT_DCS_CMD(0xD8, 0x17), > + _INIT_DCS_CMD(0xD9, 0x1C), > + _INIT_DCS_CMD(0xDA, 0x19), > + _INIT_DCS_CMD(0xDB, 0x17), > + _INIT_DCS_CMD(0xDC, 0x17), > + _INIT_DCS_CMD(0xDD, 0x18), > + _INIT_DCS_CMD(0xDE, 0x1A), > + _INIT_DCS_CMD(0xDF, 0x1E), > + _INIT_DCS_CMD(0xE0, 0x20), > + _INIT_DCS_CMD(0xE1, 0x23), > + _INIT_DCS_CMD(0xE2, 0x07), So make all of this use that struct then hardcode the rest. > + _INIT_DCS_CMD(0X11), Just call mipi_dsi_dcs_exit_sleep_mode(dsi) which sends exactly this command over DSI. > + _INIT_DELAY_CMD(120), Just harcode this. > + _INIT_DCS_CMD(0X29), This command is a bit weird since it means MIPI_DSI_GENERIC_LONG_WRITE and it is strange to have this and nothing else at the end. At least use the define from <video/mipi_display.h> > + _INIT_DELAY_CMD(80), Just hardcode this. > +static int inx_panel_enter_sleep_mode(struct inx_panel *inx) > +{ > + struct mipi_dsi_device *dsi = inx->dsi; > + int ret; > + > + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; > + > + ret = mipi_dsi_dcs_set_display_off(dsi); > + if (ret < 0) > + return ret; > + > + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); > + if (ret < 0) > + return ret; > + > + return 0; > +} This looks good. > +static int inx_panel_prepare(struct drm_panel *panel) > +{ > + struct inx_panel *inx = to_inx_panel(panel); > + int ret; > + > + if (inx->prepared) > + return 0; This is unnecessary. Trust the framework to keep track of this and drop that prepared member of inx. > + gpiod_set_value(inx->enable_gpio, 0); > + usleep_range(1000, 1500); > + > + ret = regulator_enable(inx->pp1800); > + if (ret < 0) > + return ret; > + > + usleep_range(3000, 5000); > + > + ret = regulator_enable(inx->avdd);> + > +static struct mipi_dsi_driver inx_panel_driver = { > + .driver = { > + .name = "panel-innolux-himax8279d", > + .of_match_table = inx_of_match, > + }, > + .probe = inx_panel_probe, > + .remove = inx_panel_remove, > + .shutdown = inx_panel_shutdown, > +}; > +module_mipi_dsi_driver(inx_panel_driver); > + > +MODULE_AUTHOR("Zhengqiao Xia <xiazhengqiao@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("INNOLUX HIMAX8279D 1200x1920 video mode panel driver"); > +MODULE_LICENSE("GPL v2"); > -- > 2.17.1 > + if (ret < 0) > + goto poweroff1v8; > + ret = regulator_enable(inx->avee); > + if (ret < 0) > + goto poweroffavdd; > + > + usleep_range(5000, 10000); > + > + gpiod_set_value(inx->enable_gpio, 1); > + usleep_range(1000, 2000); > + gpiod_set_value(inx->enable_gpio, 0); > + usleep_range(1000, 2000); > + gpiod_set_value(inx->enable_gpio, 1); > + usleep_range(6000, 10000); Care to explain what is happening here? Add a comment about this wire shake. Honestly this looks more like a RESET_N signal (active low reset) than an enable, so in that case rename it reset_gpiod? If this is an active low reset, invert the values and tag the reset line with GPIO_ACTIVE_LOW in the device tree and mention this in the DT bindings. > +static int inx_panel_enable(struct drm_panel *panel) > +{ > + msleep(130); > + return 0; > +} This looks pretty pointless. Can't you just stick this msleep(130) and the end of .prepare() and skip this? > + inx->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); Suspect this is reset_gpio and should be flagged active low in the device tree and requested GPIOD_OUT_HIGH instead. > + if (IS_ERR(inx->enable_gpio)) { > + dev_err(dev, "cannot get reset-gpios %ld\n", You even say it is reset-gpios ... :P> + > +static struct mipi_dsi_driver inx_panel_driver = { > + .driver = { > + .name = "panel-innolux-himax8279d", > + .of_match_table = inx_of_match, > + }, > + .probe = inx_panel_probe, > + .remove = inx_panel_remove, > + .shutdown = inx_panel_shutdown, > +}; > +module_mipi_dsi_driver(inx_panel_driver); > + > +MODULE_AUTHOR("Zhengqiao Xia <xiazhengqiao@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("INNOLUX HIMAX8279D 1200x1920 video mode panel driver"); > +MODULE_LICENSE("GPL v2"); > -- > 2.17.1 > +static const struct of_device_id inx_of_match[] = { > + { .compatible = "starry,2081101qfh032011-53g", > + .data = &starry_qfh032011_53g_desc > + }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, inx_of_match); It's really nice that you make it possible to use more displays with the same display controller! Yours, Linus Walleij