[RESEND v2 1/2] drm/panel: Add inx Himax8279d MIPI-DSI LCD panel driver
Linus Walleij
linus.walleij at linaro.org
Wed Apr 20 20:41:27 UTC 2022
On Thu, Apr 14, 2022 at 10:19 AM xiazhengqiao
<xiazhengqiao at huaqin.corp-partner.google.com> wrote:
> Add STARRY 2081101QFH032011-53G 10.1" WUXGA TFT LCD panel
>
> Signed-off-by: xiazhengqiao <xiazhengqiao at huaqin.corp-partner.google.com>
> Tested-by: Hsin-Yi Wang <hsinyi at chromium.org>
> Reviewed-by: Hsin-Yi Wang <hsinyi at chromium.org>
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 at huaqin.corp-partner.google.com>");
> +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 at huaqin.corp-partner.google.com>");
> +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
More information about the dri-devel
mailing list