2024-03-30 03:59:48

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH 0/3] drm/panel: add support for LG SW43408 panel

The LG SW43408 panel is used on Google Pixel3 devices. For a long time
we could not submit the driver, as the panel was not coming up from the
reset. The panel seems to be picky about some of the delays during init
and it also uses non-standard payload for MIPI_DSI_COMPRESSION_MODE.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
Dmitry Baryshkov (1):
drm/mipi-dsi: add mipi_dsi_compression_mode_raw()

Sumit Semwal (2):
dt-bindings: panel: Add LG SW43408 MIPI-DSI panel
drm: panel: Add LG sw43408 panel driver

.../bindings/display/panel/lg,sw43408.yaml | 37 +++
MAINTAINERS | 8 +
drivers/gpu/drm/drm_mipi_dsi.c | 34 ++-
drivers/gpu/drm/panel/Kconfig | 11 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++
include/drm/drm_mipi_dsi.h | 1 +
7 files changed, 406 insertions(+), 8 deletions(-)
---
base-commit: 13ee4a7161b6fd938aef6688ff43b163f6d83e37
change-id: 20240330-lg-sw43408-panel-b552f411c53e

Best regards,
--
Dmitry Baryshkov <[email protected]>



2024-03-30 03:59:52

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH 1/3] dt-bindings: panel: Add LG SW43408 MIPI-DSI panel

From: Sumit Semwal <[email protected]>

LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel present on Google Pixel 3
phones.

Signed-off-by: Vinod Koul <[email protected]>
Signed-off-by: Sumit Semwal <[email protected]>
[caleb: convert to yaml]
Signed-off-by: Caleb Connolly <[email protected]>
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
.../bindings/display/panel/lg,sw43408.yaml | 37 ++++++++++++++++++++++
1 file changed, 37 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml b/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
new file mode 100644
index 000000000000..f3c9c9343c4f
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/lg,sw43408.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: LG SW43408 1080x2160 DSI panel
+
+maintainers:
+ - Caleb Connolly <[email protected]>
+
+description: |
+ This panel is used on the Pixel 3, it is a 60hz OLED panel which
+ required DSC (Display Stream Compression) and has rounded corners.
+
+allOf:
+ - $ref: panel-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - const: lg,sw43408
+
+ reg: true
+ port: true
+ vddi-supply: true
+ vpnl-supply: true
+ reset-gpios: true
+
+additionalProperties: false
+
+required:
+ - compatible
+ - vddi-supply
+ - vpnl-supply
+ - reset-gpios
+...

--
2.39.2


2024-03-30 03:59:58

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH 2/3] drm/mipi-dsi: add mipi_dsi_compression_mode_raw()

The LG SW43408 panel requires sending non-standard data as a part of the
MIPI_DSI_COMPRESSION_MODE packet. Rather than hacking existing
mipi_dsi_compression_mode() add mipi_dsi_compression_mode_raw(), which
accepts raw data buffer and length.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/gpu/drm/drm_mipi_dsi.c | 34 ++++++++++++++++++++++++++--------
include/drm/drm_mipi_dsi.h | 1 +
2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index ef6e416522f8..f340d1e0a9a5 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -645,29 +645,47 @@ int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);

/**
- * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
+ * mipi_dsi_compression_mode_raw() - control DSC on the peripheral
* @dsi: DSI peripheral device
- * @enable: Whether to enable or disable the DSC
+ * @data: data to be sent to the device
+ * @len: size of the data buffer
*
- * Enable or disable Display Stream Compression on the peripheral using the
+ * Control the Display Stream Compression on the peripheral using the
* default Picture Parameter Set and VESA DSC 1.1 algorithm.
*
* Return: 0 on success or a negative error code on failure.
*/
-ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
+ssize_t mipi_dsi_compression_mode_raw(struct mipi_dsi_device *dsi, void *data, size_t len)
{
- /* Note: Needs updating for non-default PPS or algorithm */
- u8 tx[2] = { enable << 0, 0 };
struct mipi_dsi_msg msg = {
.channel = dsi->channel,
.type = MIPI_DSI_COMPRESSION_MODE,
- .tx_len = sizeof(tx),
- .tx_buf = tx,
+ .tx_len = len,
+ .tx_buf = data,
};
int ret = mipi_dsi_device_transfer(dsi, &msg);

return (ret < 0) ? ret : 0;
}
+EXPORT_SYMBOL(mipi_dsi_compression_mode_raw);
+
+/**
+ * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
+ * @dsi: DSI peripheral device
+ * @enable: Whether to enable or disable the DSC
+ *
+ * Enable or disable Display Stream Compression on the peripheral using the
+ * default Picture Parameter Set and VESA DSC 1.1 algorithm.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
+{
+ /* Note: Needs updating for non-default PPS or algorithm */
+ u8 tx[2] = { enable << 0, 0 };
+
+ return mipi_dsi_compression_mode_raw(dsi, tx, sizeof(tx));
+}
EXPORT_SYMBOL(mipi_dsi_compression_mode);

/**
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
index c0aec0d4d664..321d2b019687 100644
--- a/include/drm/drm_mipi_dsi.h
+++ b/include/drm/drm_mipi_dsi.h
@@ -242,6 +242,7 @@ int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi);
int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
u16 value);
ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable);
+ssize_t mipi_dsi_compression_mode_raw(struct mipi_dsi_device *dsi, void *data, size_t len);
ssize_t mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
const struct drm_dsc_picture_parameter_set *pps);


--
2.39.2


2024-03-30 04:00:29

by Dmitry Baryshkov

[permalink] [raw]
Subject: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

From: Sumit Semwal <[email protected]>

LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
phones.

Whatever init sequence we have for this panel isn't capable of
initialising it completely, toggling the reset gpio ever causes the
panel to die. Until this is resolved we avoid resetting the panel. The
disable/unprepare functions only put the panel to sleep mode and
disable the backlight.

Signed-off-by: Sumit Semwal <[email protected]>
[vinod: Add DSC support]
Signed-off-by: Vinod Koul <[email protected]>
[caleb: cleanup and support turning off the panel]
Signed-off-by: Caleb Connolly <[email protected]>
[DB: partially rewrote the driver and fixed DSC programming]
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
MAINTAINERS | 8 +
drivers/gpu/drm/panel/Kconfig | 11 ++
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
4 files changed, 342 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 4b511a55101c..f4cf7ee97376 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6755,6 +6755,14 @@ S: Maintained
F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c

+DRM DRIVER FOR LG SW43408 PANELS
+M: Sumit Semwal <[email protected]>
+M: Caleb Connolly <[email protected]>
+S: Maintained
+T: git git://anongit.freedesktop.org/drm/drm-misc
+F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
+F: drivers/gpu/drm/panel/panel-lg-sw43408.c
+
DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
M: Paul Kocialkowski <[email protected]>
S: Supported
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d037b3b8b999..f94c702735cb 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
Say Y here if you want to enable support for LG4573 RGB panel.
To compile this driver as a module, choose M here.

+config DRM_PANEL_LG_SW43408
+ tristate "LG SW43408 panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for LG sw43408 panel.
+ The panel has a 1080x2160 resolution and uses
+ 24 bit RGB per pixel. It provides a MIPI DSI interface to
+ the host and has a built-in LED backlight.
+
config DRM_PANEL_MAGNACHIP_D53E6EA8966
tristate "Magnachip D53E6EA8966 DSI panel"
depends on OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index f156d7fa0bcc..a75687d13caf 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
+obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
new file mode 100644
index 000000000000..365d25e14d54
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2024 Linaro Ltd
+ * Author: Sumit Semwal <[email protected]>
+ * Dmitry Baryshkov <[email protected]>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+
+#define NUM_SUPPLIES 2
+
+struct sw43408_panel {
+ struct drm_panel base;
+ struct mipi_dsi_device *link;
+
+ const struct drm_display_mode *mode;
+
+ struct regulator_bulk_data supplies[NUM_SUPPLIES];
+
+ struct gpio_desc *reset_gpio;
+};
+
+static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
+{
+ return container_of(panel, struct sw43408_panel, base);
+}
+
+static int sw43408_unprepare(struct drm_panel *panel)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(ctx->link);
+ if (ret < 0)
+ dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
+ if (ret < 0)
+ dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
+
+ msleep(100);
+
+ gpiod_set_value(ctx->reset_gpio, 1);
+
+ return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int sw43408_program(struct drm_panel *panel)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+ struct drm_dsc_picture_parameter_set pps;
+ u8 dsc_en = 0x11;
+
+ mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
+
+ mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+
+ mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
+ mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
+
+ mipi_dsi_dcs_exit_sleep_mode(ctx->link);
+
+ msleep(135);
+
+ mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);
+
+ mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
+ 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
+ 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
+ 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
+ 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
+ 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
+ 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
+ 0x01);
+ msleep(85);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
+ 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x16, 0x16);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
+ mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
+
+ mipi_dsi_dcs_set_display_on(ctx->link);
+
+ msleep(50);
+
+ ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
+ mipi_dsi_picture_parameter_set(ctx->link, &pps);
+
+ ctx->link->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+static int sw43408_prepare(struct drm_panel *panel)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(5000, 6000);
+
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(9000, 10000);
+ gpiod_set_value(ctx->reset_gpio, 1);
+ usleep_range(1000, 2000);
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(9000, 10000);
+
+ ret = sw43408_program(panel);
+ if (ret)
+ goto poweroff;
+
+ return 0;
+
+poweroff:
+ gpiod_set_value(ctx->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ return ret;
+}
+
+static int sw43408_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+
+ return drm_connector_helper_get_modes_fixed(connector, ctx->mode);
+}
+
+static int sw43408_backlight_update_status(struct backlight_device *bl)
+{
+ struct mipi_dsi_device *dsi = bl_get_data(bl);
+ uint16_t brightness = backlight_get_brightness(bl);
+
+ return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
+}
+
+const struct backlight_ops sw43408_backlight_ops = {
+ .update_status = sw43408_backlight_update_status,
+};
+
+static int sw43408_backlight_init(struct sw43408_panel *ctx)
+{
+ struct device *dev = &ctx->link->dev;
+ const struct backlight_properties props = {
+ .type = BACKLIGHT_PLATFORM,
+ .brightness = 255,
+ .max_brightness = 255,
+ };
+
+ ctx->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev,
+ ctx->link,
+ &sw43408_backlight_ops,
+ &props);
+
+ if (IS_ERR(ctx->base.backlight))
+ return dev_err_probe(dev, PTR_ERR(ctx->base.backlight),
+ "Failed to create backlight\n");
+
+ return 0;
+}
+
+static const struct drm_panel_funcs sw43408_funcs = {
+ .unprepare = sw43408_unprepare,
+ .prepare = sw43408_prepare,
+ .get_modes = sw43408_get_modes,
+};
+
+static const struct drm_display_mode sw43408_default_mode = {
+ .clock = 152340,
+
+ .hdisplay = 1080,
+ .hsync_start = 1080 + 20,
+ .hsync_end = 1080 + 20 + 32,
+ .htotal = 1080 + 20 + 32 + 20,
+
+ .vdisplay = 2160,
+ .vsync_start = 2160 + 20,
+ .vsync_end = 2160 + 20 + 4,
+ .vtotal = 2160 + 20 + 4 + 20,
+
+ .width_mm = 62,
+ .height_mm = 124,
+
+ .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static const struct of_device_id sw43408_of_match[] = {
+ { .compatible = "lg,sw43408", .data = &sw43408_default_mode },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sw43408_of_match);
+
+static int sw43408_add(struct sw43408_panel *ctx)
+{
+ struct device *dev = &ctx->link->dev;
+ int ret;
+
+ ctx->supplies[0].supply = "vddi"; /* 1.88 V */
+ ctx->supplies[0].init_load_uA = 62000;
+ ctx->supplies[1].supply = "vpnl"; /* 3.0 V */
+ ctx->supplies[1].init_load_uA = 857000;
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ dev_err(dev, "cannot get reset gpio %ld\n",
+ PTR_ERR(ctx->reset_gpio));
+ return PTR_ERR(ctx->reset_gpio);
+ }
+
+ ret = sw43408_backlight_init(ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->base.prepare_prev_first = true;
+
+ drm_panel_init(&ctx->base, dev, &sw43408_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ drm_panel_add(&ctx->base);
+ return ret;
+}
+
+static int sw43408_probe(struct mipi_dsi_device *dsi)
+{
+ struct sw43408_panel *ctx;
+ struct drm_dsc_config *dsc;
+ int ret;
+
+ ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->mode = of_device_get_match_data(&dsi->dev);
+ dsi->mode_flags = MIPI_DSI_MODE_LPM;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = 4;
+
+ ctx->link = dsi;
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ret = sw43408_add(ctx);
+ if (ret < 0)
+ return ret;
+
+ /* The panel is DSC panel only, set the dsc params */
+ dsc = devm_kzalloc(&dsi->dev, sizeof(*dsc), GFP_KERNEL);
+ if (!dsc)
+ return -ENOMEM;
+
+ dsc->dsc_version_major = 0x1;
+ dsc->dsc_version_minor = 0x1;
+
+ dsc->slice_height = 16;
+ dsc->slice_width = 540;
+ dsc->slice_count = 2;
+ dsc->bits_per_component = 8;
+ dsc->bits_per_pixel = 8 << 4;
+ dsc->block_pred_enable = true;
+
+ dsi->dsc = dsc;
+
+ return mipi_dsi_attach(dsi);
+}
+
+static void sw43408_remove(struct mipi_dsi_device *dsi)
+{
+ struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = sw43408_unprepare(&ctx->base);
+ if (ret < 0)
+ dev_err(&dsi->dev, "failed to unprepare panel: %d\n",
+ ret);
+
+ ret = mipi_dsi_detach(dsi);
+ if (ret < 0)
+ dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
+
+ drm_panel_remove(&ctx->base);
+}
+
+static struct mipi_dsi_driver sw43408_driver = {
+ .driver = {
+ .name = "panel-lg-sw43408",
+ .of_match_table = sw43408_of_match,
+ },
+ .probe = sw43408_probe,
+ .remove = sw43408_remove,
+};
+module_mipi_dsi_driver(sw43408_driver);
+
+MODULE_AUTHOR("Sumit Semwal <[email protected]>");
+MODULE_DESCRIPTION("LG SW436408 MIPI-DSI LED panel");
+MODULE_LICENSE("GPL");

--
2.39.2


2024-03-30 09:19:16

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 1/3] dt-bindings: panel: Add LG SW43408 MIPI-DSI panel

On 30/03/2024 04:59, Dmitry Baryshkov wrote:
> From: Sumit Semwal <[email protected]>
>
> LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel present on Google Pixel 3
> phones.

.. Few nits, which could be ignored, and one thing which is blocking -
missing example.

> +
> +maintainers:
> + - Caleb Connolly <[email protected]>
> +
> +description: |

Do not need '|' unless you need to preserve formatting.

> + This panel is used on the Pixel 3, it is a 60hz OLED panel which
> + required DSC (Display Stream Compression) and has rounded corners.
> +
> +allOf:
> + - $ref: panel-common.yaml#
> +
> +properties:
> + compatible:
> + items:
> + - const: lg,sw43408
> +
> + reg: true
> + port: true
> + vddi-supply: true
> + vpnl-supply: true
> + reset-gpios: true
> +
> +additionalProperties: false

This goes after required:

> +
> +required:
> + - compatible
> + - vddi-supply
> + - vpnl-supply
> + - reset-gpios


Please add example DTS.

Best regards,
Krzysztof


2024-03-30 10:45:28

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

On 2024-03-30 05:59:30, Dmitry Baryshkov wrote:
> From: Sumit Semwal <[email protected]>
>
> LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
> phones.
>
> Whatever init sequence we have for this panel isn't capable of
> initialising it completely, toggling the reset gpio ever causes the
> panel to die. Until this is resolved we avoid resetting the panel. The

Are you sure it is avoided? This patch seems to be toggling reset_gpio in
sw43408_prepare()?

> disable/unprepare functions only put the panel to sleep mode and
> disable the backlight.
>
> Signed-off-by: Sumit Semwal <[email protected]>
> [vinod: Add DSC support]
> Signed-off-by: Vinod Koul <[email protected]>
> [caleb: cleanup and support turning off the panel]
> Signed-off-by: Caleb Connolly <[email protected]>
> [DB: partially rewrote the driver and fixed DSC programming]
> Signed-off-by: Dmitry Baryshkov <[email protected]>
> ---
> MAINTAINERS | 8 +
> drivers/gpu/drm/panel/Kconfig | 11 ++
> drivers/gpu/drm/panel/Makefile | 1 +
> drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
> 4 files changed, 342 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4b511a55101c..f4cf7ee97376 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6755,6 +6755,14 @@ S: Maintained
> F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
> F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
>
> +DRM DRIVER FOR LG SW43408 PANELS
> +M: Sumit Semwal <[email protected]>
> +M: Caleb Connolly <[email protected]>
> +S: Maintained
> +T: git git://anongit.freedesktop.org/drm/drm-misc
> +F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
> +F: drivers/gpu/drm/panel/panel-lg-sw43408.c
> +
> DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
> M: Paul Kocialkowski <[email protected]>
> S: Supported
> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index d037b3b8b999..f94c702735cb 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
> Say Y here if you want to enable support for LG4573 RGB panel.
> To compile this driver as a module, choose M here.
>
> +config DRM_PANEL_LG_SW43408
> + tristate "LG SW43408 panel"
> + depends on OF
> + depends on DRM_MIPI_DSI
> + depends on BACKLIGHT_CLASS_DEVICE
> + help
> + Say Y here if you want to enable support for LG sw43408 panel.
> + The panel has a 1080x2160 resolution and uses
> + 24 bit RGB per pixel. It provides a MIPI DSI interface to
> + the host and has a built-in LED backlight.
> +
> config DRM_PANEL_MAGNACHIP_D53E6EA8966
> tristate "Magnachip D53E6EA8966 DSI panel"
> depends on OF && SPI
> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index f156d7fa0bcc..a75687d13caf 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
> obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
> obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
> obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
> +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
> obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
> obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
> obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
> diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> new file mode 100644
> index 000000000000..365d25e14d54
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> @@ -0,0 +1,322 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019-2024 Linaro Ltd
> + * Author: Sumit Semwal <[email protected]>
> + * Dmitry Baryshkov <[email protected]>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <video/mipi_display.h>
> +
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/display/drm_dsc.h>
> +#include <drm/display/drm_dsc_helper.h>
> +
> +#define NUM_SUPPLIES 2
> +
> +struct sw43408_panel {
> + struct drm_panel base;
> + struct mipi_dsi_device *link;
> +
> + const struct drm_display_mode *mode;
> +
> + struct regulator_bulk_data supplies[NUM_SUPPLIES];
> +
> + struct gpio_desc *reset_gpio;
> +};
> +
> +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
> +{
> + return container_of(panel, struct sw43408_panel, base);
> +}
> +
> +static int sw43408_unprepare(struct drm_panel *panel)
> +{
> + struct sw43408_panel *ctx = to_panel_info(panel);
> + int ret;
> +
> + ret = mipi_dsi_dcs_set_display_off(ctx->link);
> + if (ret < 0)
> + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
> +
> + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
> + if (ret < 0)
> + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
> +
> + msleep(100);
> +
> + gpiod_set_value(ctx->reset_gpio, 1);
> +
> + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> +}
> +
> +static int sw43408_program(struct drm_panel *panel)
> +{
> + struct sw43408_panel *ctx = to_panel_info(panel);
> + struct drm_dsc_picture_parameter_set pps;
> + u8 dsc_en = 0x11;

Yeah, this is completely strange. Bit 0, 0x1, is to enable DSC which is
normal. 0x10 however, which is bit 4, selects PPS table 2. Do you ever set
pps_identifier in struct drm_dsc_picture_parameter_set to 2? Or is the table
that you send below bogus and/or not used? Maybe the Driver IC on the other
end of the DSI link has a default PPS table with identifier 2 that works out of
the box?

> + mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
> +
> + mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> +
> + mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
> + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
> +
> + mipi_dsi_dcs_exit_sleep_mode(ctx->link);
> +
> + msleep(135);
> +
> + mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);

Even though I think we should change this function to describe the known
bit layout of command 0x7 per the VESA DSI spec, for now replace 1 with
sizeof(dsc_en)?

> +
> + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
> + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
> + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
> + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
> + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
> + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
> + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
> + 0x01);
> + msleep(85);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
> + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
> + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
> + 0x16, 0x16);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
> + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
> + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
> +
> + mipi_dsi_dcs_set_display_on(ctx->link);

Any specific reason to not have the (un)blanking sequence in the enable/disable
callbacks and leaving display configuration in (un)prepare?

> + msleep(50);
> +
> + ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
> +
> + drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
> + mipi_dsi_picture_parameter_set(ctx->link, &pps);

I'm always surprised why this is sent _after_ turning the display on (unblanking
it). Wouldn't that cause unnecessary corruption?

> +
> + ctx->link->mode_flags |= MIPI_DSI_MODE_LPM;
> +
> + return 0;
> +}
> +
> +static int sw43408_prepare(struct drm_panel *panel)
> +{
> + struct sw43408_panel *ctx = to_panel_info(panel);
> + int ret;
> +
> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 6000);
> +
> + gpiod_set_value(ctx->reset_gpio, 0);
> + usleep_range(9000, 10000);
> + gpiod_set_value(ctx->reset_gpio, 1);
> + usleep_range(1000, 2000);
> + gpiod_set_value(ctx->reset_gpio, 0);
> + usleep_range(9000, 10000);
> +
> + ret = sw43408_program(panel);
> + if (ret)
> + goto poweroff;
> +
> + return 0;
> +
> +poweroff:
> + gpiod_set_value(ctx->reset_gpio, 1);
> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> + return ret;
> +}
> +
> +static int sw43408_get_modes(struct drm_panel *panel,
> + struct drm_connector *connector)
> +{
> + struct sw43408_panel *ctx = to_panel_info(panel);
> +
> + return drm_connector_helper_get_modes_fixed(connector, ctx->mode);
> +}
> +
> +static int sw43408_backlight_update_status(struct backlight_device *bl)
> +{
> + struct mipi_dsi_device *dsi = bl_get_data(bl);
> + uint16_t brightness = backlight_get_brightness(bl);
> +
> + return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
> +}
> +
> +const struct backlight_ops sw43408_backlight_ops = {
> + .update_status = sw43408_backlight_update_status,
> +};
> +
> +static int sw43408_backlight_init(struct sw43408_panel *ctx)
> +{
> + struct device *dev = &ctx->link->dev;
> + const struct backlight_properties props = {
> + .type = BACKLIGHT_PLATFORM,
> + .brightness = 255,
> + .max_brightness = 255,
> + };
> +
> + ctx->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev,
> + ctx->link,
> + &sw43408_backlight_ops,
> + &props);
> +
> + if (IS_ERR(ctx->base.backlight))
> + return dev_err_probe(dev, PTR_ERR(ctx->base.backlight),
> + "Failed to create backlight\n");
> +
> + return 0;
> +}
> +
> +static const struct drm_panel_funcs sw43408_funcs = {
> + .unprepare = sw43408_unprepare,
> + .prepare = sw43408_prepare,
> + .get_modes = sw43408_get_modes,
> +};
> +
> +static const struct drm_display_mode sw43408_default_mode = {
> + .clock = 152340,
> +
> + .hdisplay = 1080,
> + .hsync_start = 1080 + 20,
> + .hsync_end = 1080 + 20 + 32,
> + .htotal = 1080 + 20 + 32 + 20,
> +
> + .vdisplay = 2160,
> + .vsync_start = 2160 + 20,
> + .vsync_end = 2160 + 20 + 4,
> + .vtotal = 2160 + 20 + 4 + 20,
> +
> + .width_mm = 62,
> + .height_mm = 124,
> +
> + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +};
> +
> +static const struct of_device_id sw43408_of_match[] = {
> + { .compatible = "lg,sw43408", .data = &sw43408_default_mode },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, sw43408_of_match);
> +
> +static int sw43408_add(struct sw43408_panel *ctx)
> +{
> + struct device *dev = &ctx->link->dev;
> + int ret;
> +
> + ctx->supplies[0].supply = "vddi"; /* 1.88 V */
> + ctx->supplies[0].init_load_uA = 62000;
> + ctx->supplies[1].supply = "vpnl"; /* 3.0 V */
> + ctx->supplies[1].init_load_uA = 857000;
> +
> + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
> + ctx->supplies);
> + if (ret < 0)
> + return ret;
> +
> + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(ctx->reset_gpio)) {
> + dev_err(dev, "cannot get reset gpio %ld\n",
> + PTR_ERR(ctx->reset_gpio));
> + return PTR_ERR(ctx->reset_gpio);
> + }
> +
> + ret = sw43408_backlight_init(ctx);
> + if (ret < 0)
> + return ret;
> +
> + ctx->base.prepare_prev_first = true;
> +
> + drm_panel_init(&ctx->base, dev, &sw43408_funcs, DRM_MODE_CONNECTOR_DSI);
> +
> + drm_panel_add(&ctx->base);
> + return ret;
> +}
> +
> +static int sw43408_probe(struct mipi_dsi_device *dsi)
> +{
> + struct sw43408_panel *ctx;
> + struct drm_dsc_config *dsc;
> + int ret;
> +
> + ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + ctx->mode = of_device_get_match_data(&dsi->dev);
> + dsi->mode_flags = MIPI_DSI_MODE_LPM;
> + dsi->format = MIPI_DSI_FMT_RGB888;
> + dsi->lanes = 4;
> +
> + ctx->link = dsi;
> + mipi_dsi_set_drvdata(dsi, ctx);
> +
> + ret = sw43408_add(ctx);
> + if (ret < 0)
> + return ret;
> +
> + /* The panel is DSC panel only, set the dsc params */
> + dsc = devm_kzalloc(&dsi->dev, sizeof(*dsc), GFP_KERNEL);

We've recently decided to store struct drm_dsc_config in struct sw43408_panel
and save on an extra allocation.

> + if (!dsc)
> + return -ENOMEM;
> +
> + dsc->dsc_version_major = 0x1;
> + dsc->dsc_version_minor = 0x1;
> +
> + dsc->slice_height = 16;
> + dsc->slice_width = 540;
> + dsc->slice_count = 2;

Maybe incorporate with a comment that slice_count * slice_width == the width of
the mode?

- Marijn

> + dsc->bits_per_component = 8;
> + dsc->bits_per_pixel = 8 << 4;
> + dsc->block_pred_enable = true;
> +
> + dsi->dsc = dsc;
> +
> + return mipi_dsi_attach(dsi);
> +}
> +
> +static void sw43408_remove(struct mipi_dsi_device *dsi)
> +{
> + struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi);
> + int ret;
> +
> + ret = sw43408_unprepare(&ctx->base);
> + if (ret < 0)
> + dev_err(&dsi->dev, "failed to unprepare panel: %d\n",
> + ret);
> +
> + ret = mipi_dsi_detach(dsi);
> + if (ret < 0)
> + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
> +
> + drm_panel_remove(&ctx->base);
> +}
> +
> +static struct mipi_dsi_driver sw43408_driver = {
> + .driver = {
> + .name = "panel-lg-sw43408",
> + .of_match_table = sw43408_of_match,
> + },
> + .probe = sw43408_probe,
> + .remove = sw43408_remove,
> +};
> +module_mipi_dsi_driver(sw43408_driver);
> +
> +MODULE_AUTHOR("Sumit Semwal <[email protected]>");
> +MODULE_DESCRIPTION("LG SW436408 MIPI-DSI LED panel");
> +MODULE_LICENSE("GPL");
>
> --
> 2.39.2
>

2024-03-30 14:37:34

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

On Sat, 30 Mar 2024 at 12:27, Marijn Suijten
<[email protected]> wrote:
>
> On 2024-03-30 05:59:30, Dmitry Baryshkov wrote:
> > From: Sumit Semwal <[email protected]>
> >
> > LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
> > phones.
> >
> > Whatever init sequence we have for this panel isn't capable of
> > initialising it completely, toggling the reset gpio ever causes the
> > panel to die. Until this is resolved we avoid resetting the panel. The
>
> Are you sure it is avoided? This patch seems to be toggling reset_gpio in
> sw43408_prepare()?
>
> > disable/unprepare functions only put the panel to sleep mode and
> > disable the backlight.
> >
> > Signed-off-by: Sumit Semwal <[email protected]>
> > [vinod: Add DSC support]
> > Signed-off-by: Vinod Koul <[email protected]>
> > [caleb: cleanup and support turning off the panel]
> > Signed-off-by: Caleb Connolly <[email protected]>
> > [DB: partially rewrote the driver and fixed DSC programming]
> > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > ---
> > MAINTAINERS | 8 +
> > drivers/gpu/drm/panel/Kconfig | 11 ++
> > drivers/gpu/drm/panel/Makefile | 1 +
> > drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
> > 4 files changed, 342 insertions(+)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 4b511a55101c..f4cf7ee97376 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -6755,6 +6755,14 @@ S: Maintained
> > F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
> > F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
> >
> > +DRM DRIVER FOR LG SW43408 PANELS
> > +M: Sumit Semwal <[email protected]>
> > +M: Caleb Connolly <[email protected]>
> > +S: Maintained
> > +T: git git://anongit.freedesktop.org/drm/drm-misc
> > +F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
> > +F: drivers/gpu/drm/panel/panel-lg-sw43408.c
> > +
> > DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
> > M: Paul Kocialkowski <[email protected]>
> > S: Supported
> > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > index d037b3b8b999..f94c702735cb 100644
> > --- a/drivers/gpu/drm/panel/Kconfig
> > +++ b/drivers/gpu/drm/panel/Kconfig
> > @@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
> > Say Y here if you want to enable support for LG4573 RGB panel.
> > To compile this driver as a module, choose M here.
> >
> > +config DRM_PANEL_LG_SW43408
> > + tristate "LG SW43408 panel"
> > + depends on OF
> > + depends on DRM_MIPI_DSI
> > + depends on BACKLIGHT_CLASS_DEVICE
> > + help
> > + Say Y here if you want to enable support for LG sw43408 panel.
> > + The panel has a 1080x2160 resolution and uses
> > + 24 bit RGB per pixel. It provides a MIPI DSI interface to
> > + the host and has a built-in LED backlight.
> > +
> > config DRM_PANEL_MAGNACHIP_D53E6EA8966
> > tristate "Magnachip D53E6EA8966 DSI panel"
> > depends on OF && SPI
> > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > index f156d7fa0bcc..a75687d13caf 100644
> > --- a/drivers/gpu/drm/panel/Makefile
> > +++ b/drivers/gpu/drm/panel/Makefile
> > @@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
> > obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
> > obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
> > obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
> > +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
> > obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
> > obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
> > obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
> > diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > new file mode 100644
> > index 000000000000..365d25e14d54
> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > @@ -0,0 +1,322 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Copyright (C) 2019-2024 Linaro Ltd
> > + * Author: Sumit Semwal <[email protected]>
> > + * Dmitry Baryshkov <[email protected]>
> > + */
> > +
> > +#include <linux/backlight.h>
> > +#include <linux/delay.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/regulator/consumer.h>
> > +
> > +#include <video/mipi_display.h>
> > +
> > +#include <drm/drm_mipi_dsi.h>
> > +#include <drm/drm_panel.h>
> > +#include <drm/drm_probe_helper.h>
> > +#include <drm/display/drm_dsc.h>
> > +#include <drm/display/drm_dsc_helper.h>
> > +
> > +#define NUM_SUPPLIES 2
> > +
> > +struct sw43408_panel {
> > + struct drm_panel base;
> > + struct mipi_dsi_device *link;
> > +
> > + const struct drm_display_mode *mode;
> > +
> > + struct regulator_bulk_data supplies[NUM_SUPPLIES];
> > +
> > + struct gpio_desc *reset_gpio;
> > +};
> > +
> > +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
> > +{
> > + return container_of(panel, struct sw43408_panel, base);
> > +}
> > +
> > +static int sw43408_unprepare(struct drm_panel *panel)
> > +{
> > + struct sw43408_panel *ctx = to_panel_info(panel);
> > + int ret;
> > +
> > + ret = mipi_dsi_dcs_set_display_off(ctx->link);
> > + if (ret < 0)
> > + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
> > +
> > + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
> > + if (ret < 0)
> > + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
> > +
> > + msleep(100);
> > +
> > + gpiod_set_value(ctx->reset_gpio, 1);
> > +
> > + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > +}
> > +
> > +static int sw43408_program(struct drm_panel *panel)
> > +{
> > + struct sw43408_panel *ctx = to_panel_info(panel);
> > + struct drm_dsc_picture_parameter_set pps;
> > + u8 dsc_en = 0x11;
>
> Yeah, this is completely strange. Bit 0, 0x1, is to enable DSC which is
> normal. 0x10 however, which is bit 4, selects PPS table 2. Do you ever set
> pps_identifier in struct drm_dsc_picture_parameter_set to 2? Or is the table
> that you send below bogus and/or not used? Maybe the Driver IC on the other
> end of the DSI link has a default PPS table with identifier 2 that works out of
> the box?

Note, MIPI standard also requires two bytes argument. I suspect that
LG didn't fully follow the standard here.
Basically that's the reason why I went for the _raw function instead
of adding PPS and codec arguments to the existing function.

>
> > + mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
> > +
> > + mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> > +
> > + mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
> > +
> > + mipi_dsi_dcs_exit_sleep_mode(ctx->link);
> > +
> > + msleep(135);
> > +
> > + mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);
>
> Even though I think we should change this function to describe the known
> bit layout of command 0x7 per the VESA DSI spec, for now replace 1 with
> sizeof(dsc_en)?

If dsc_en were an array, it would have been a proper thing. Maybe I
should change it to the array to remove confusion.

>
> > +
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
> > + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
> > + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
> > + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
> > + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
> > + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
> > + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
> > + 0x01);
> > + msleep(85);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
> > + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
> > + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
> > + 0x16, 0x16);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
> > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
> > +
> > + mipi_dsi_dcs_set_display_on(ctx->link);
>
> Any specific reason to not have the (un)blanking sequence in the enable/disable
> callbacks and leaving display configuration in (un)prepare?

We are back to the question on when it's fine to send the commands. I
think the current agreement is to send everything in the
prepare/unprepare, because of some strange hosts.

> > + msleep(50);
> > +
> > + ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
> > +
> > + drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
> > + mipi_dsi_picture_parameter_set(ctx->link, &pps);
>
> I'm always surprised why this is sent _after_ turning the display on (unblanking
> it). Wouldn't that cause unnecessary corruption?

No idea. I followed the dowsntream command sequences here. Most likely
the panel is not fully on until it receives the full frame to be
displayed.

> > +
> > + ctx->link->mode_flags |= MIPI_DSI_MODE_LPM;
> > +
> > + return 0;
> > +}
> > +
> > +static int sw43408_prepare(struct drm_panel *panel)
> > +{
> > + struct sw43408_panel *ctx = to_panel_info(panel);
> > + int ret;
> > +
> > + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > + if (ret < 0)
> > + return ret;
> > +
> > + usleep_range(5000, 6000);
> > +
> > + gpiod_set_value(ctx->reset_gpio, 0);
> > + usleep_range(9000, 10000);
> > + gpiod_set_value(ctx->reset_gpio, 1);
> > + usleep_range(1000, 2000);
> > + gpiod_set_value(ctx->reset_gpio, 0);
> > + usleep_range(9000, 10000);
> > +
> > + ret = sw43408_program(panel);
> > + if (ret)
> > + goto poweroff;
> > +
> > + return 0;
> > +
> > +poweroff:
> > + gpiod_set_value(ctx->reset_gpio, 1);
> > + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > + return ret;
> > +}
> > +
> > +static int sw43408_get_modes(struct drm_panel *panel,
> > + struct drm_connector *connector)
> > +{
> > + struct sw43408_panel *ctx = to_panel_info(panel);
> > +
> > + return drm_connector_helper_get_modes_fixed(connector, ctx->mode);
> > +}
> > +
> > +static int sw43408_backlight_update_status(struct backlight_device *bl)
> > +{
> > + struct mipi_dsi_device *dsi = bl_get_data(bl);
> > + uint16_t brightness = backlight_get_brightness(bl);
> > +
> > + return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
> > +}
> > +
> > +const struct backlight_ops sw43408_backlight_ops = {
> > + .update_status = sw43408_backlight_update_status,
> > +};
> > +
> > +static int sw43408_backlight_init(struct sw43408_panel *ctx)
> > +{
> > + struct device *dev = &ctx->link->dev;
> > + const struct backlight_properties props = {
> > + .type = BACKLIGHT_PLATFORM,
> > + .brightness = 255,
> > + .max_brightness = 255,
> > + };
> > +
> > + ctx->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev,
> > + ctx->link,
> > + &sw43408_backlight_ops,
> > + &props);
> > +
> > + if (IS_ERR(ctx->base.backlight))
> > + return dev_err_probe(dev, PTR_ERR(ctx->base.backlight),
> > + "Failed to create backlight\n");
> > +
> > + return 0;
> > +}
> > +
> > +static const struct drm_panel_funcs sw43408_funcs = {
> > + .unprepare = sw43408_unprepare,
> > + .prepare = sw43408_prepare,
> > + .get_modes = sw43408_get_modes,
> > +};
> > +
> > +static const struct drm_display_mode sw43408_default_mode = {
> > + .clock = 152340,
> > +
> > + .hdisplay = 1080,
> > + .hsync_start = 1080 + 20,
> > + .hsync_end = 1080 + 20 + 32,
> > + .htotal = 1080 + 20 + 32 + 20,
> > +
> > + .vdisplay = 2160,
> > + .vsync_start = 2160 + 20,
> > + .vsync_end = 2160 + 20 + 4,
> > + .vtotal = 2160 + 20 + 4 + 20,
> > +
> > + .width_mm = 62,
> > + .height_mm = 124,
> > +
> > + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> > +};
> > +
> > +static const struct of_device_id sw43408_of_match[] = {
> > + { .compatible = "lg,sw43408", .data = &sw43408_default_mode },
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, sw43408_of_match);
> > +
> > +static int sw43408_add(struct sw43408_panel *ctx)
> > +{
> > + struct device *dev = &ctx->link->dev;
> > + int ret;
> > +
> > + ctx->supplies[0].supply = "vddi"; /* 1.88 V */
> > + ctx->supplies[0].init_load_uA = 62000;
> > + ctx->supplies[1].supply = "vpnl"; /* 3.0 V */
> > + ctx->supplies[1].init_load_uA = 857000;
> > +
> > + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
> > + ctx->supplies);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> > + if (IS_ERR(ctx->reset_gpio)) {
> > + dev_err(dev, "cannot get reset gpio %ld\n",
> > + PTR_ERR(ctx->reset_gpio));
> > + return PTR_ERR(ctx->reset_gpio);
> > + }
> > +
> > + ret = sw43408_backlight_init(ctx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ctx->base.prepare_prev_first = true;
> > +
> > + drm_panel_init(&ctx->base, dev, &sw43408_funcs, DRM_MODE_CONNECTOR_DSI);
> > +
> > + drm_panel_add(&ctx->base);
> > + return ret;
> > +}
> > +
> > +static int sw43408_probe(struct mipi_dsi_device *dsi)
> > +{
> > + struct sw43408_panel *ctx;
> > + struct drm_dsc_config *dsc;
> > + int ret;
> > +
> > + ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
> > + if (!ctx)
> > + return -ENOMEM;
> > +
> > + ctx->mode = of_device_get_match_data(&dsi->dev);
> > + dsi->mode_flags = MIPI_DSI_MODE_LPM;
> > + dsi->format = MIPI_DSI_FMT_RGB888;
> > + dsi->lanes = 4;
> > +
> > + ctx->link = dsi;
> > + mipi_dsi_set_drvdata(dsi, ctx);
> > +
> > + ret = sw43408_add(ctx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* The panel is DSC panel only, set the dsc params */
> > + dsc = devm_kzalloc(&dsi->dev, sizeof(*dsc), GFP_KERNEL);
>
> We've recently decided to store struct drm_dsc_config in struct sw43408_panel
> and save on an extra allocation.
>
> > + if (!dsc)
> > + return -ENOMEM;
> > +
> > + dsc->dsc_version_major = 0x1;
> > + dsc->dsc_version_minor = 0x1;
> > +
> > + dsc->slice_height = 16;
> > + dsc->slice_width = 540;
> > + dsc->slice_count = 2;
>
> Maybe incorporate with a comment that slice_count * slice_width == the width of
> the mode?

ack

>
> - Marijn
>
> > + dsc->bits_per_component = 8;
> > + dsc->bits_per_pixel = 8 << 4;
> > + dsc->block_pred_enable = true;
> > +
> > + dsi->dsc = dsc;
> > +
> > + return mipi_dsi_attach(dsi);
> > +}
> > +
> > +static void sw43408_remove(struct mipi_dsi_device *dsi)
> > +{
> > + struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi);
> > + int ret;
> > +
> > + ret = sw43408_unprepare(&ctx->base);
> > + if (ret < 0)
> > + dev_err(&dsi->dev, "failed to unprepare panel: %d\n",
> > + ret);
> > +
> > + ret = mipi_dsi_detach(dsi);
> > + if (ret < 0)
> > + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
> > +
> > + drm_panel_remove(&ctx->base);
> > +}
> > +
> > +static struct mipi_dsi_driver sw43408_driver = {
> > + .driver = {
> > + .name = "panel-lg-sw43408",
> > + .of_match_table = sw43408_of_match,
> > + },
> > + .probe = sw43408_probe,
> > + .remove = sw43408_remove,
> > +};
> > +module_mipi_dsi_driver(sw43408_driver);
> > +
> > +MODULE_AUTHOR("Sumit Semwal <[email protected]>");
> > +MODULE_DESCRIPTION("LG SW436408 MIPI-DSI LED panel");
> > +MODULE_LICENSE("GPL");
> >
> > --
> > 2.39.2
> >



--
With best wishes
Dmitry

2024-04-01 10:30:03

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

On 2024-03-30 16:37:08, Dmitry Baryshkov wrote:
> On Sat, 30 Mar 2024 at 12:27, Marijn Suijten
> <[email protected]> wrote:
> >
> > On 2024-03-30 05:59:30, Dmitry Baryshkov wrote:
> > > From: Sumit Semwal <[email protected]>
> > >
> > > LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
> > > phones.
> > >
> > > Whatever init sequence we have for this panel isn't capable of
> > > initialising it completely, toggling the reset gpio ever causes the
> > > panel to die. Until this is resolved we avoid resetting the panel. The
> >
> > Are you sure it is avoided? This patch seems to be toggling reset_gpio in
> > sw43408_prepare()?
> >
> > > disable/unprepare functions only put the panel to sleep mode and
> > > disable the backlight.
> > >
> > > Signed-off-by: Sumit Semwal <[email protected]>
> > > [vinod: Add DSC support]
> > > Signed-off-by: Vinod Koul <[email protected]>
> > > [caleb: cleanup and support turning off the panel]
> > > Signed-off-by: Caleb Connolly <[email protected]>
> > > [DB: partially rewrote the driver and fixed DSC programming]
> > > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > > ---
> > > MAINTAINERS | 8 +
> > > drivers/gpu/drm/panel/Kconfig | 11 ++
> > > drivers/gpu/drm/panel/Makefile | 1 +
> > > drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
> > > 4 files changed, 342 insertions(+)
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 4b511a55101c..f4cf7ee97376 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -6755,6 +6755,14 @@ S: Maintained
> > > F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
> > > F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
> > >
> > > +DRM DRIVER FOR LG SW43408 PANELS
> > > +M: Sumit Semwal <[email protected]>
> > > +M: Caleb Connolly <[email protected]>
> > > +S: Maintained
> > > +T: git git://anongit.freedesktop.org/drm/drm-misc
> > > +F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
> > > +F: drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > +
> > > DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
> > > M: Paul Kocialkowski <[email protected]>
> > > S: Supported
> > > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > > index d037b3b8b999..f94c702735cb 100644
> > > --- a/drivers/gpu/drm/panel/Kconfig
> > > +++ b/drivers/gpu/drm/panel/Kconfig
> > > @@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
> > > Say Y here if you want to enable support for LG4573 RGB panel.
> > > To compile this driver as a module, choose M here.
> > >
> > > +config DRM_PANEL_LG_SW43408
> > > + tristate "LG SW43408 panel"
> > > + depends on OF
> > > + depends on DRM_MIPI_DSI
> > > + depends on BACKLIGHT_CLASS_DEVICE
> > > + help
> > > + Say Y here if you want to enable support for LG sw43408 panel.
> > > + The panel has a 1080x2160 resolution and uses
> > > + 24 bit RGB per pixel. It provides a MIPI DSI interface to
> > > + the host and has a built-in LED backlight.
> > > +
> > > config DRM_PANEL_MAGNACHIP_D53E6EA8966
> > > tristate "Magnachip D53E6EA8966 DSI panel"
> > > depends on OF && SPI
> > > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > > index f156d7fa0bcc..a75687d13caf 100644
> > > --- a/drivers/gpu/drm/panel/Makefile
> > > +++ b/drivers/gpu/drm/panel/Makefile
> > > @@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
> > > obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
> > > obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
> > > obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
> > > +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
> > > obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
> > > obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
> > > obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
> > > diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > new file mode 100644
> > > index 000000000000..365d25e14d54
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > @@ -0,0 +1,322 @@
> > > +// SPDX-License-Identifier: GPL-2.0+
> > > +/*
> > > + * Copyright (C) 2019-2024 Linaro Ltd
> > > + * Author: Sumit Semwal <[email protected]>
> > > + * Dmitry Baryshkov <[email protected]>
> > > + */
> > > +
> > > +#include <linux/backlight.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/module.h>
> > > +#include <linux/of.h>
> > > +#include <linux/regulator/consumer.h>
> > > +
> > > +#include <video/mipi_display.h>
> > > +
> > > +#include <drm/drm_mipi_dsi.h>
> > > +#include <drm/drm_panel.h>
> > > +#include <drm/drm_probe_helper.h>
> > > +#include <drm/display/drm_dsc.h>
> > > +#include <drm/display/drm_dsc_helper.h>
> > > +
> > > +#define NUM_SUPPLIES 2
> > > +
> > > +struct sw43408_panel {
> > > + struct drm_panel base;
> > > + struct mipi_dsi_device *link;
> > > +
> > > + const struct drm_display_mode *mode;
> > > +
> > > + struct regulator_bulk_data supplies[NUM_SUPPLIES];
> > > +
> > > + struct gpio_desc *reset_gpio;
> > > +};
> > > +
> > > +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
> > > +{
> > > + return container_of(panel, struct sw43408_panel, base);
> > > +}
> > > +
> > > +static int sw43408_unprepare(struct drm_panel *panel)
> > > +{
> > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > + int ret;
> > > +
> > > + ret = mipi_dsi_dcs_set_display_off(ctx->link);
> > > + if (ret < 0)
> > > + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
> > > +
> > > + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
> > > + if (ret < 0)
> > > + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
> > > +
> > > + msleep(100);
> > > +
> > > + gpiod_set_value(ctx->reset_gpio, 1);
> > > +
> > > + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > > +}
> > > +
> > > +static int sw43408_program(struct drm_panel *panel)
> > > +{
> > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > + struct drm_dsc_picture_parameter_set pps;
> > > + u8 dsc_en = 0x11;
> >
> > Yeah, this is completely strange. Bit 0, 0x1, is to enable DSC which is
> > normal. 0x10 however, which is bit 4, selects PPS table 2. Do you ever set
> > pps_identifier in struct drm_dsc_picture_parameter_set to 2? Or is the table
> > that you send below bogus and/or not used? Maybe the Driver IC on the other
> > end of the DSI link has a default PPS table with identifier 2 that works out of
> > the box?
>
> Note, MIPI standard also requires two bytes argument. I suspect that
> LG didn't fully follow the standard here.

Have you read this command from downstream DTS, or have you tried sending 2
bytes and seen the panel breaking? The second byte is marked as reserved and
should be equal to 0; if the Driver IC is okay with sending either 1 or 2 bytes
I'd strive to stick with the defined length of 2 bytes for this DCS.

Have you played around with the PPS table? What if you change
drm_dsc_picture_paremeter_set::pps_identifier to the second table, will the
panel stop working as expected again? This could indicate that the PPS that is
sent is incorrect (even though the information in the original DSC config was
enough to set up the DPU and DSI correctly).

According to the DSI spec it is allowed to have a pre-stored/pre-programmed
PPS table, which could be used here making the current call to
mipi_dsi_picture_parameter_set() useless and "confusing"?

> Basically that's the reason why I went for the _raw function instead
> of adding PPS and codec arguments to the existing function.
>
> >
> > > + mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
> > > +
> > > + mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> > > +
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
> > > +
> > > + mipi_dsi_dcs_exit_sleep_mode(ctx->link);
> > > +
> > > + msleep(135);
> > > +
> > > + mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);
> >
> > Even though I think we should change this function to describe the known
> > bit layout of command 0x7 per the VESA DSI spec, for now replace 1 with
> > sizeof(dsc_en)?
>
> If dsc_en were an array, it would have been a proper thing. Maybe I
> should change it to the array to remove confusion.

It should work even with a single byte, just to clarify to readers that the 3rd
argument is the byte-size of the input.

> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
> > > + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
> > > + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
> > > + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
> > > + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
> > > + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
> > > + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
> > > + 0x01);
> > > + msleep(85);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
> > > + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > + 0x16, 0x16);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
> > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
> > > +
> > > + mipi_dsi_dcs_set_display_on(ctx->link);
> >
> > Any specific reason to not have the (un)blanking sequence in the enable/disable
> > callbacks and leaving display configuration in (un)prepare?
>
> We are back to the question on when it's fine to send the commands. I
> think the current agreement is to send everything in the
> prepare/unprepare, because of some strange hosts.

For my panel drivers I'm sticking with having `post-on` commands (from
downstream) in `enable/disable`, which is typically only `set_display_on`. In
hopes of proposing a `prepare_atomic()` some time to allow mode selection.

In a short test on recent -next I am once again allowed to send DSI commands in
both .disable and .unprepare, making both functions a "clean" inverse of .enable
and .prepare respectively.

> > > + msleep(50);
> > > +
> > > + ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
> > > +
> > > + drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
> > > + mipi_dsi_picture_parameter_set(ctx->link, &pps);
> >
> > I'm always surprised why this is sent _after_ turning the display on (unblanking
> > it). Wouldn't that cause unnecessary corruption?
>
> No idea. I followed the dowsntream command sequences here. Most likely
> the panel is not fully on until it receives the full frame to be
> displayed.

According to the DSI spec a PPS update is allowed to happen every frame, and
(for cmdmode panels) will take effect after the next TE trigger. Unsure if a TE
event happens before the first frame, otherwise this may start taking effect
on the second frame onwards only.

If there's no corruption on the first frame there might be a pre-programmed PPS
table in slot 2, supporting the theory above.

- Marijn

2024-04-01 10:38:30

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 2/3] drm/mipi-dsi: add mipi_dsi_compression_mode_raw()

On 2024-03-30 05:59:29, Dmitry Baryshkov wrote:
> The LG SW43408 panel requires sending non-standard data as a part of the
> MIPI_DSI_COMPRESSION_MODE packet. Rather than hacking existing
> mipi_dsi_compression_mode() add mipi_dsi_compression_mode_raw(), which
> accepts raw data buffer and length.

Even though I doubt the usefulness of this _raw() command before further
understanding the panel and driver (according the the review-followup sent a few
minutes ago), let me review this a little bit.

> Signed-off-by: Dmitry Baryshkov <[email protected]>
> ---
> drivers/gpu/drm/drm_mipi_dsi.c | 34 ++++++++++++++++++++++++++--------
> include/drm/drm_mipi_dsi.h | 1 +
> 2 files changed, 27 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
> index ef6e416522f8..f340d1e0a9a5 100644
> --- a/drivers/gpu/drm/drm_mipi_dsi.c
> +++ b/drivers/gpu/drm/drm_mipi_dsi.c
> @@ -645,29 +645,47 @@ int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
> EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);
>
> /**
> - * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
> + * mipi_dsi_compression_mode_raw() - control DSC on the peripheral
> * @dsi: DSI peripheral device
> - * @enable: Whether to enable or disable the DSC
> + * @data: data to be sent to the device
> + * @len: size of the data buffer
> *
> - * Enable or disable Display Stream Compression on the peripheral using the
> + * Control the Display Stream Compression on the peripheral using the

+ mode?

> * default Picture Parameter Set and VESA DSC 1.1 algorithm.

This is no longer true. Both the algoritm identifier and "default Picture
Parameter Set" (which I assume means table *index*!) are described by the
custom/raw bytes that one is allowed to pass.

In fact, in the SW43408 driver that you reference in the commit message the
custom data passed to the _raw() function is used to select the second PPS
table (unless the panel interprets the input data in a non-standard way...), and
further sets the PPS for the first table only :)

> *
> * Return: 0 on success or a negative error code on failure.
> */
> -ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
> +ssize_t mipi_dsi_compression_mode_raw(struct mipi_dsi_device *dsi, void *data, size_t len)
> {
> - /* Note: Needs updating for non-default PPS or algorithm */
> - u8 tx[2] = { enable << 0, 0 };
> struct mipi_dsi_msg msg = {
> .channel = dsi->channel,
> .type = MIPI_DSI_COMPRESSION_MODE,
> - .tx_len = sizeof(tx),
> - .tx_buf = tx,
> + .tx_len = len,
> + .tx_buf = data,
> };
> int ret = mipi_dsi_device_transfer(dsi, &msg);
>
> return (ret < 0) ? ret : 0;
> }
> +EXPORT_SYMBOL(mipi_dsi_compression_mode_raw);
> +
> +/**
> + * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
> + * @dsi: DSI peripheral device
> + * @enable: Whether to enable or disable the DSC
> + *
> + * Enable or disable Display Stream Compression on the peripheral using the
> + * default Picture Parameter Set and VESA DSC 1.1 algorithm.

And while fixing this up, let's make it clear that this doesn't change the
PPS, just the *index* of which PPS to use (the PPS is updated with a different
command).

- Marijn

> + *
> + * Return: 0 on success or a negative error code on failure.
> + */
> +ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
> +{
> + /* Note: Needs updating for non-default PPS or algorithm */
> + u8 tx[2] = { enable << 0, 0 };
> +
> + return mipi_dsi_compression_mode_raw(dsi, tx, sizeof(tx));
> +}
> EXPORT_SYMBOL(mipi_dsi_compression_mode);
>
> /**
> diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
> index c0aec0d4d664..321d2b019687 100644
> --- a/include/drm/drm_mipi_dsi.h
> +++ b/include/drm/drm_mipi_dsi.h
> @@ -242,6 +242,7 @@ int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi);
> int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
> u16 value);
> ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable);
> +ssize_t mipi_dsi_compression_mode_raw(struct mipi_dsi_device *dsi, void *data, size_t len);
> ssize_t mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
> const struct drm_dsc_picture_parameter_set *pps);
>
>
> --
> 2.39.2
>

2024-04-01 19:12:10

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

On Mon, 1 Apr 2024 at 13:29, Marijn Suijten
<[email protected]> wrote:
>
> On 2024-03-30 16:37:08, Dmitry Baryshkov wrote:
> > On Sat, 30 Mar 2024 at 12:27, Marijn Suijten
> > <[email protected]> wrote:
> > >
> > > On 2024-03-30 05:59:30, Dmitry Baryshkov wrote:
> > > > From: Sumit Semwal <[email protected]>
> > > >
> > > > LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
> > > > phones.
> > > >
> > > > Whatever init sequence we have for this panel isn't capable of
> > > > initialising it completely, toggling the reset gpio ever causes the
> > > > panel to die. Until this is resolved we avoid resetting the panel. The
> > >
> > > Are you sure it is avoided? This patch seems to be toggling reset_gpio in
> > > sw43408_prepare()?
> > >
> > > > disable/unprepare functions only put the panel to sleep mode and
> > > > disable the backlight.
> > > >
> > > > Signed-off-by: Sumit Semwal <[email protected]>
> > > > [vinod: Add DSC support]
> > > > Signed-off-by: Vinod Koul <[email protected]>
> > > > [caleb: cleanup and support turning off the panel]
> > > > Signed-off-by: Caleb Connolly <[email protected]>
> > > > [DB: partially rewrote the driver and fixed DSC programming]
> > > > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > > > ---
> > > > MAINTAINERS | 8 +
> > > > drivers/gpu/drm/panel/Kconfig | 11 ++
> > > > drivers/gpu/drm/panel/Makefile | 1 +
> > > > drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
> > > > 4 files changed, 342 insertions(+)
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index 4b511a55101c..f4cf7ee97376 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -6755,6 +6755,14 @@ S: Maintained
> > > > F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
> > > > F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
> > > >
> > > > +DRM DRIVER FOR LG SW43408 PANELS
> > > > +M: Sumit Semwal <[email protected]>
> > > > +M: Caleb Connolly <[email protected]>
> > > > +S: Maintained
> > > > +T: git git://anongit.freedesktop.org/drm/drm-misc
> > > > +F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
> > > > +F: drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > +
> > > > DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
> > > > M: Paul Kocialkowski <[email protected]>
> > > > S: Supported
> > > > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > > > index d037b3b8b999..f94c702735cb 100644
> > > > --- a/drivers/gpu/drm/panel/Kconfig
> > > > +++ b/drivers/gpu/drm/panel/Kconfig
> > > > @@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
> > > > Say Y here if you want to enable support for LG4573 RGB panel.
> > > > To compile this driver as a module, choose M here.
> > > >
> > > > +config DRM_PANEL_LG_SW43408
> > > > + tristate "LG SW43408 panel"
> > > > + depends on OF
> > > > + depends on DRM_MIPI_DSI
> > > > + depends on BACKLIGHT_CLASS_DEVICE
> > > > + help
> > > > + Say Y here if you want to enable support for LG sw43408 panel.
> > > > + The panel has a 1080x2160 resolution and uses
> > > > + 24 bit RGB per pixel. It provides a MIPI DSI interface to
> > > > + the host and has a built-in LED backlight.
> > > > +
> > > > config DRM_PANEL_MAGNACHIP_D53E6EA8966
> > > > tristate "Magnachip D53E6EA8966 DSI panel"
> > > > depends on OF && SPI
> > > > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > > > index f156d7fa0bcc..a75687d13caf 100644
> > > > --- a/drivers/gpu/drm/panel/Makefile
> > > > +++ b/drivers/gpu/drm/panel/Makefile
> > > > @@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
> > > > obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
> > > > obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
> > > > obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
> > > > +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
> > > > obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
> > > > obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
> > > > obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
> > > > diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > new file mode 100644
> > > > index 000000000000..365d25e14d54
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > @@ -0,0 +1,322 @@
> > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > +/*
> > > > + * Copyright (C) 2019-2024 Linaro Ltd
> > > > + * Author: Sumit Semwal <[email protected]>
> > > > + * Dmitry Baryshkov <[email protected]>
> > > > + */
> > > > +
> > > > +#include <linux/backlight.h>
> > > > +#include <linux/delay.h>
> > > > +#include <linux/gpio/consumer.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/of.h>
> > > > +#include <linux/regulator/consumer.h>
> > > > +
> > > > +#include <video/mipi_display.h>
> > > > +
> > > > +#include <drm/drm_mipi_dsi.h>
> > > > +#include <drm/drm_panel.h>
> > > > +#include <drm/drm_probe_helper.h>
> > > > +#include <drm/display/drm_dsc.h>
> > > > +#include <drm/display/drm_dsc_helper.h>
> > > > +
> > > > +#define NUM_SUPPLIES 2
> > > > +
> > > > +struct sw43408_panel {
> > > > + struct drm_panel base;
> > > > + struct mipi_dsi_device *link;
> > > > +
> > > > + const struct drm_display_mode *mode;
> > > > +
> > > > + struct regulator_bulk_data supplies[NUM_SUPPLIES];
> > > > +
> > > > + struct gpio_desc *reset_gpio;
> > > > +};
> > > > +
> > > > +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
> > > > +{
> > > > + return container_of(panel, struct sw43408_panel, base);
> > > > +}
> > > > +
> > > > +static int sw43408_unprepare(struct drm_panel *panel)
> > > > +{
> > > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > > + int ret;
> > > > +
> > > > + ret = mipi_dsi_dcs_set_display_off(ctx->link);
> > > > + if (ret < 0)
> > > > + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
> > > > +
> > > > + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
> > > > + if (ret < 0)
> > > > + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
> > > > +
> > > > + msleep(100);
> > > > +
> > > > + gpiod_set_value(ctx->reset_gpio, 1);
> > > > +
> > > > + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > > > +}
> > > > +
> > > > +static int sw43408_program(struct drm_panel *panel)
> > > > +{
> > > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > > + struct drm_dsc_picture_parameter_set pps;
> > > > + u8 dsc_en = 0x11;
> > >
> > > Yeah, this is completely strange. Bit 0, 0x1, is to enable DSC which is
> > > normal. 0x10 however, which is bit 4, selects PPS table 2. Do you ever set
> > > pps_identifier in struct drm_dsc_picture_parameter_set to 2? Or is the table
> > > that you send below bogus and/or not used? Maybe the Driver IC on the other
> > > end of the DSI link has a default PPS table with identifier 2 that works out of
> > > the box?
> >
> > Note, MIPI standard also requires two bytes argument. I suspect that
> > LG didn't fully follow the standard here.
>
> Have you read this command from downstream DTS, or have you tried sending 2
> bytes and seen the panel breaking? The second byte is marked as reserved and
> should be equal to 0; if the Driver IC is okay with sending either 1 or 2 bytes
> I'd strive to stick with the defined length of 2 bytes for this DCS.
>
> Have you played around with the PPS table? What if you change
> drm_dsc_picture_paremeter_set::pps_identifier to the second table, will the
> panel stop working as expected again? This could indicate that the PPS that is
> sent is incorrect (even though the information in the original DSC config was
> enough to set up the DPU and DSI correctly).
>
> According to the DSI spec it is allowed to have a pre-stored/pre-programmed
> PPS table, which could be used here making the current call to
> mipi_dsi_picture_parameter_set() useless and "confusing"?

Ok, some short summary of my tests.

Skipping PPS doesn't work at all, so there is no default.

Adding a second zero byte doesn't seem to change anything. Dropping
the 0x1 bit ('enable') doesn't seem to change anything.

If I send COMPRESSION_MODE before sending the PPS, various combinations work.
If I send COMPRESSION_MODE after sending the PPS, the follow combos work:

pps_identifier = 0x0, COMPRESSION_MODE = 0x11
pps_identifier = 0x1, COMPRESSION_MODE = 0x21

>
> > Basically that's the reason why I went for the _raw function instead
> > of adding PPS and codec arguments to the existing function.
> >
> > >
> > > > + mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
> > > > +
> > > > + mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> > > > +
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
> > > > +
> > > > + mipi_dsi_dcs_exit_sleep_mode(ctx->link);
> > > > +
> > > > + msleep(135);
> > > > +
> > > > + mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);
> > >
> > > Even though I think we should change this function to describe the known
> > > bit layout of command 0x7 per the VESA DSI spec, for now replace 1 with
> > > sizeof(dsc_en)?
> >
> > If dsc_en were an array, it would have been a proper thing. Maybe I
> > should change it to the array to remove confusion.
>
> It should work even with a single byte, just to clarify to readers that the 3rd
> argument is the byte-size of the input.
>
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
> > > > + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
> > > > + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
> > > > + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
> > > > + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
> > > > + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
> > > > + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
> > > > + 0x01);
> > > > + msleep(85);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
> > > > + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > > + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > > + 0x16, 0x16);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
> > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
> > > > +
> > > > + mipi_dsi_dcs_set_display_on(ctx->link);
> > >
> > > Any specific reason to not have the (un)blanking sequence in the enable/disable
> > > callbacks and leaving display configuration in (un)prepare?
> >
> > We are back to the question on when it's fine to send the commands. I
> > think the current agreement is to send everything in the
> > prepare/unprepare, because of some strange hosts.
>
> For my panel drivers I'm sticking with having `post-on` commands (from
> downstream) in `enable/disable`, which is typically only `set_display_on`. In
> hopes of proposing a `prepare_atomic()` some time to allow mode selection.
>
> In a short test on recent -next I am once again allowed to send DSI commands in
> both .disable and .unprepare, making both functions a "clean" inverse of .enable
> and .prepare respectively.

The world isn't limited to the MSM hosts.

>
> > > > + msleep(50);
> > > > +
> > > > + ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
> > > > +
> > > > + drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
> > > > + mipi_dsi_picture_parameter_set(ctx->link, &pps);
> > >
> > > I'm always surprised why this is sent _after_ turning the display on (unblanking
> > > it). Wouldn't that cause unnecessary corruption?
> >
> > No idea. I followed the dowsntream command sequences here. Most likely
> > the panel is not fully on until it receives the full frame to be
> > displayed.
>
> According to the DSI spec a PPS update is allowed to happen every frame, and
> (for cmdmode panels) will take effect after the next TE trigger. Unsure if a TE
> event happens before the first frame, otherwise this may start taking effect
> on the second frame onwards only.
>
> If there's no corruption on the first frame there might be a pre-programmed PPS
> table in slot 2, supporting the theory above.



--
With best wishes
Dmitry

2024-04-02 20:57:43

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

On 2024-04-01 22:11:48, Dmitry Baryshkov wrote:
> On Mon, 1 Apr 2024 at 13:29, Marijn Suijten
> <[email protected]> wrote:
> >
> > On 2024-03-30 16:37:08, Dmitry Baryshkov wrote:
> > > On Sat, 30 Mar 2024 at 12:27, Marijn Suijten
> > > <[email protected]> wrote:
> > > >
> > > > On 2024-03-30 05:59:30, Dmitry Baryshkov wrote:
> > > > > From: Sumit Semwal <[email protected]>
> > > > >
> > > > > LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
> > > > > phones.
> > > > >
> > > > > Whatever init sequence we have for this panel isn't capable of
> > > > > initialising it completely, toggling the reset gpio ever causes the
> > > > > panel to die. Until this is resolved we avoid resetting the panel. The
> > > >
> > > > Are you sure it is avoided? This patch seems to be toggling reset_gpio in
> > > > sw43408_prepare()?
> > > >
> > > > > disable/unprepare functions only put the panel to sleep mode and
> > > > > disable the backlight.
> > > > >
> > > > > Signed-off-by: Sumit Semwal <[email protected]>
> > > > > [vinod: Add DSC support]
> > > > > Signed-off-by: Vinod Koul <[email protected]>
> > > > > [caleb: cleanup and support turning off the panel]
> > > > > Signed-off-by: Caleb Connolly <[email protected]>
> > > > > [DB: partially rewrote the driver and fixed DSC programming]
> > > > > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > > > > ---
> > > > > MAINTAINERS | 8 +
> > > > > drivers/gpu/drm/panel/Kconfig | 11 ++
> > > > > drivers/gpu/drm/panel/Makefile | 1 +
> > > > > drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
> > > > > 4 files changed, 342 insertions(+)
> > > > >
> > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > index 4b511a55101c..f4cf7ee97376 100644
> > > > > --- a/MAINTAINERS
> > > > > +++ b/MAINTAINERS
> > > > > @@ -6755,6 +6755,14 @@ S: Maintained
> > > > > F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
> > > > > F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
> > > > >
> > > > > +DRM DRIVER FOR LG SW43408 PANELS
> > > > > +M: Sumit Semwal <[email protected]>
> > > > > +M: Caleb Connolly <[email protected]>
> > > > > +S: Maintained
> > > > > +T: git git://anongit.freedesktop.org/drm/drm-misc
> > > > > +F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
> > > > > +F: drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > > +
> > > > > DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
> > > > > M: Paul Kocialkowski <[email protected]>
> > > > > S: Supported
> > > > > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > > > > index d037b3b8b999..f94c702735cb 100644
> > > > > --- a/drivers/gpu/drm/panel/Kconfig
> > > > > +++ b/drivers/gpu/drm/panel/Kconfig
> > > > > @@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
> > > > > Say Y here if you want to enable support for LG4573 RGB panel.
> > > > > To compile this driver as a module, choose M here.
> > > > >
> > > > > +config DRM_PANEL_LG_SW43408
> > > > > + tristate "LG SW43408 panel"
> > > > > + depends on OF
> > > > > + depends on DRM_MIPI_DSI
> > > > > + depends on BACKLIGHT_CLASS_DEVICE
> > > > > + help
> > > > > + Say Y here if you want to enable support for LG sw43408 panel.
> > > > > + The panel has a 1080x2160 resolution and uses
> > > > > + 24 bit RGB per pixel. It provides a MIPI DSI interface to
> > > > > + the host and has a built-in LED backlight.
> > > > > +
> > > > > config DRM_PANEL_MAGNACHIP_D53E6EA8966
> > > > > tristate "Magnachip D53E6EA8966 DSI panel"
> > > > > depends on OF && SPI
> > > > > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > > > > index f156d7fa0bcc..a75687d13caf 100644
> > > > > --- a/drivers/gpu/drm/panel/Makefile
> > > > > +++ b/drivers/gpu/drm/panel/Makefile
> > > > > @@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
> > > > > obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
> > > > > obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
> > > > > obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
> > > > > +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
> > > > > obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
> > > > > obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
> > > > > obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
> > > > > diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > > new file mode 100644
> > > > > index 000000000000..365d25e14d54
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > > @@ -0,0 +1,322 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > > +/*
> > > > > + * Copyright (C) 2019-2024 Linaro Ltd
> > > > > + * Author: Sumit Semwal <[email protected]>
> > > > > + * Dmitry Baryshkov <[email protected]>
> > > > > + */
> > > > > +
> > > > > +#include <linux/backlight.h>
> > > > > +#include <linux/delay.h>
> > > > > +#include <linux/gpio/consumer.h>
> > > > > +#include <linux/module.h>
> > > > > +#include <linux/of.h>
> > > > > +#include <linux/regulator/consumer.h>
> > > > > +
> > > > > +#include <video/mipi_display.h>
> > > > > +
> > > > > +#include <drm/drm_mipi_dsi.h>
> > > > > +#include <drm/drm_panel.h>
> > > > > +#include <drm/drm_probe_helper.h>
> > > > > +#include <drm/display/drm_dsc.h>
> > > > > +#include <drm/display/drm_dsc_helper.h>
> > > > > +
> > > > > +#define NUM_SUPPLIES 2
> > > > > +
> > > > > +struct sw43408_panel {
> > > > > + struct drm_panel base;
> > > > > + struct mipi_dsi_device *link;
> > > > > +
> > > > > + const struct drm_display_mode *mode;
> > > > > +
> > > > > + struct regulator_bulk_data supplies[NUM_SUPPLIES];
> > > > > +
> > > > > + struct gpio_desc *reset_gpio;
> > > > > +};
> > > > > +
> > > > > +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
> > > > > +{
> > > > > + return container_of(panel, struct sw43408_panel, base);
> > > > > +}
> > > > > +
> > > > > +static int sw43408_unprepare(struct drm_panel *panel)
> > > > > +{
> > > > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > > > + int ret;
> > > > > +
> > > > > + ret = mipi_dsi_dcs_set_display_off(ctx->link);
> > > > > + if (ret < 0)
> > > > > + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
> > > > > +
> > > > > + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
> > > > > + if (ret < 0)
> > > > > + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
> > > > > +
> > > > > + msleep(100);
> > > > > +
> > > > > + gpiod_set_value(ctx->reset_gpio, 1);
> > > > > +
> > > > > + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > > > > +}
> > > > > +
> > > > > +static int sw43408_program(struct drm_panel *panel)
> > > > > +{
> > > > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > > > + struct drm_dsc_picture_parameter_set pps;
> > > > > + u8 dsc_en = 0x11;
> > > >
> > > > Yeah, this is completely strange. Bit 0, 0x1, is to enable DSC which is
> > > > normal. 0x10 however, which is bit 4, selects PPS table 2. Do you ever set
> > > > pps_identifier in struct drm_dsc_picture_parameter_set to 2? Or is the table
> > > > that you send below bogus and/or not used? Maybe the Driver IC on the other
> > > > end of the DSI link has a default PPS table with identifier 2 that works out of
> > > > the box?
> > >
> > > Note, MIPI standard also requires two bytes argument. I suspect that
> > > LG didn't fully follow the standard here.
> >
> > Have you read this command from downstream DTS, or have you tried sending 2
> > bytes and seen the panel breaking? The second byte is marked as reserved and
> > should be equal to 0; if the Driver IC is okay with sending either 1 or 2 bytes
> > I'd strive to stick with the defined length of 2 bytes for this DCS.
> >
> > Have you played around with the PPS table? What if you change
> > drm_dsc_picture_paremeter_set::pps_identifier to the second table, will the
> > panel stop working as expected again? This could indicate that the PPS that is
> > sent is incorrect (even though the information in the original DSC config was
> > enough to set up the DPU and DSI correctly).
> >
> > According to the DSI spec it is allowed to have a pre-stored/pre-programmed
> > PPS table, which could be used here making the current call to
> > mipi_dsi_picture_parameter_set() useless and "confusing"?
>
> Ok, some short summary of my tests.
>
> Skipping PPS doesn't work at all, so there is no default.
>
> Adding a second zero byte doesn't seem to change anything. Dropping
> the 0x1 bit ('enable') doesn't seem to change anything.
>
> If I send COMPRESSION_MODE before sending the PPS, various combinations work.
> If I send COMPRESSION_MODE after sending the PPS, the follow combos work:
>
> pps_identifier = 0x0, COMPRESSION_MODE = 0x11
> pps_identifier = 0x1, COMPRESSION_MODE = 0x21

Thanks, this must really be an off-by-one table identifier. I presume you've
tested pps_identifier=0x2 with COMPRESSION_MODE=0x31, and that there are only 2
tables and not 3 or 4?

From this we can also assume that sending a new PPS will automatically switch
the compression mode to the pps_identifier in that PPS, COMPRESSION_MODE doesn't
seem to affect it when sent too early.

> > > Basically that's the reason why I went for the _raw function instead
> > > of adding PPS and codec arguments to the existing function.
> > >
> > > >
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
> > > > > +
> > > > > + mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> > > > > +
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
> > > > > +
> > > > > + mipi_dsi_dcs_exit_sleep_mode(ctx->link);
> > > > > +
> > > > > + msleep(135);
> > > > > +
> > > > > + mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);
> > > >
> > > > Even though I think we should change this function to describe the known
> > > > bit layout of command 0x7 per the VESA DSI spec, for now replace 1 with
> > > > sizeof(dsc_en)?
> > >
> > > If dsc_en were an array, it would have been a proper thing. Maybe I
> > > should change it to the array to remove confusion.
> >
> > It should work even with a single byte, just to clarify to readers that the 3rd
> > argument is the byte-size of the input.
> >
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
> > > > > + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
> > > > > + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
> > > > > + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
> > > > > + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
> > > > > + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
> > > > > + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
> > > > > + 0x01);
> > > > > + msleep(85);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
> > > > > + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > > > + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > > > + 0x16, 0x16);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
> > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
> > > > > +
> > > > > + mipi_dsi_dcs_set_display_on(ctx->link);
> > > >
> > > > Any specific reason to not have the (un)blanking sequence in the enable/disable
> > > > callbacks and leaving display configuration in (un)prepare?
> > >
> > > We are back to the question on when it's fine to send the commands. I
> > > think the current agreement is to send everything in the
> > > prepare/unprepare, because of some strange hosts.
> >
> > For my panel drivers I'm sticking with having `post-on` commands (from
> > downstream) in `enable/disable`, which is typically only `set_display_on`. In
> > hopes of proposing a `prepare_atomic()` some time to allow mode selection.
> >
> > In a short test on recent -next I am once again allowed to send DSI commands in
> > both .disable and .unprepare, making both functions a "clean" inverse of .enable
> > and .prepare respectively.
>
> The world isn't limited to the MSM hosts.

If I'm not mistaken this was an ordering issue in the drm_bridge implementation.

But you are right that some hosts might not be all too happy with sending
commands (like unblanking?) after the cmd/video stream started, and before the
stream stops. Which, as far as I know, are what .enable and .disable do. On
the other hand, I was under the impression that this split mainly existed to do
all the heavy/required lifting up-front, and only unblank when there's a video
signal to combat any possible observed corruption?

In the end I'm just curious if there's a specific reason - that I need
to take into account when resending all my panel patches - to /not/
use .enable/.disable?

- Marijn

> > > > > + msleep(50);
> > > > > +
> > > > > + ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
> > > > > +
> > > > > + drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
> > > > > + mipi_dsi_picture_parameter_set(ctx->link, &pps);
> > > >
> > > > I'm always surprised why this is sent _after_ turning the display on (unblanking
> > > > it). Wouldn't that cause unnecessary corruption?
> > >
> > > No idea. I followed the dowsntream command sequences here. Most likely
> > > the panel is not fully on until it receives the full frame to be
> > > displayed.
> >
> > According to the DSI spec a PPS update is allowed to happen every frame, and
> > (for cmdmode panels) will take effect after the next TE trigger. Unsure if a TE
> > event happens before the first frame, otherwise this may start taking effect
> > on the second frame onwards only.
> >
> > If there's no corruption on the first frame there might be a pre-programmed PPS
> > table in slot 2, supporting the theory above.
>
>
>
> --
> With best wishes
> Dmitry

2024-04-03 00:51:51

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 3/3] drm: panel: Add LG sw43408 panel driver

On Tue, 2 Apr 2024 at 23:57, Marijn Suijten
<[email protected]> wrote:
>
> On 2024-04-01 22:11:48, Dmitry Baryshkov wrote:
> > On Mon, 1 Apr 2024 at 13:29, Marijn Suijten
> > <[email protected]> wrote:
> > >
> > > On 2024-03-30 16:37:08, Dmitry Baryshkov wrote:
> > > > On Sat, 30 Mar 2024 at 12:27, Marijn Suijten
> > > > <[email protected]> wrote:
> > > > >
> > > > > On 2024-03-30 05:59:30, Dmitry Baryshkov wrote:
> > > > > > From: Sumit Semwal <[email protected]>
> > > > > >
> > > > > > LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
> > > > > > phones.
> > > > > >
> > > > > > Whatever init sequence we have for this panel isn't capable of
> > > > > > initialising it completely, toggling the reset gpio ever causes the
> > > > > > panel to die. Until this is resolved we avoid resetting the panel. The
> > > > >
> > > > > Are you sure it is avoided? This patch seems to be toggling reset_gpio in
> > > > > sw43408_prepare()?
> > > > >
> > > > > > disable/unprepare functions only put the panel to sleep mode and
> > > > > > disable the backlight.
> > > > > >
> > > > > > Signed-off-by: Sumit Semwal <[email protected]>
> > > > > > [vinod: Add DSC support]
> > > > > > Signed-off-by: Vinod Koul <[email protected]>
> > > > > > [caleb: cleanup and support turning off the panel]
> > > > > > Signed-off-by: Caleb Connolly <[email protected]>
> > > > > > [DB: partially rewrote the driver and fixed DSC programming]
> > > > > > Signed-off-by: Dmitry Baryshkov <[email protected]>
> > > > > > ---
> > > > > > MAINTAINERS | 8 +
> > > > > > drivers/gpu/drm/panel/Kconfig | 11 ++
> > > > > > drivers/gpu/drm/panel/Makefile | 1 +
> > > > > > drivers/gpu/drm/panel/panel-lg-sw43408.c | 322 +++++++++++++++++++++++++++++++
> > > > > > 4 files changed, 342 insertions(+)
> > > > > >
> > > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > > index 4b511a55101c..f4cf7ee97376 100644
> > > > > > --- a/MAINTAINERS
> > > > > > +++ b/MAINTAINERS
> > > > > > @@ -6755,6 +6755,14 @@ S: Maintained
> > > > > > F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
> > > > > > F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
> > > > > >
> > > > > > +DRM DRIVER FOR LG SW43408 PANELS
> > > > > > +M: Sumit Semwal <[email protected]>
> > > > > > +M: Caleb Connolly <[email protected]>
> > > > > > +S: Maintained
> > > > > > +T: git git://anongit.freedesktop.org/drm/drm-misc
> > > > > > +F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
> > > > > > +F: drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > > > +
> > > > > > DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
> > > > > > M: Paul Kocialkowski <[email protected]>
> > > > > > S: Supported
> > > > > > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > > > > > index d037b3b8b999..f94c702735cb 100644
> > > > > > --- a/drivers/gpu/drm/panel/Kconfig
> > > > > > +++ b/drivers/gpu/drm/panel/Kconfig
> > > > > > @@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
> > > > > > Say Y here if you want to enable support for LG4573 RGB panel.
> > > > > > To compile this driver as a module, choose M here.
> > > > > >
> > > > > > +config DRM_PANEL_LG_SW43408
> > > > > > + tristate "LG SW43408 panel"
> > > > > > + depends on OF
> > > > > > + depends on DRM_MIPI_DSI
> > > > > > + depends on BACKLIGHT_CLASS_DEVICE
> > > > > > + help
> > > > > > + Say Y here if you want to enable support for LG sw43408 panel.
> > > > > > + The panel has a 1080x2160 resolution and uses
> > > > > > + 24 bit RGB per pixel. It provides a MIPI DSI interface to
> > > > > > + the host and has a built-in LED backlight.
> > > > > > +
> > > > > > config DRM_PANEL_MAGNACHIP_D53E6EA8966
> > > > > > tristate "Magnachip D53E6EA8966 DSI panel"
> > > > > > depends on OF && SPI
> > > > > > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > > > > > index f156d7fa0bcc..a75687d13caf 100644
> > > > > > --- a/drivers/gpu/drm/panel/Makefile
> > > > > > +++ b/drivers/gpu/drm/panel/Makefile
> > > > > > @@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
> > > > > > obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
> > > > > > obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
> > > > > > obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
> > > > > > +obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
> > > > > > obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
> > > > > > obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
> > > > > > obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
> > > > > > diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > > > new file mode 100644
> > > > > > index 000000000000..365d25e14d54
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
> > > > > > @@ -0,0 +1,322 @@
> > > > > > +// SPDX-License-Identifier: GPL-2.0+
> > > > > > +/*
> > > > > > + * Copyright (C) 2019-2024 Linaro Ltd
> > > > > > + * Author: Sumit Semwal <[email protected]>
> > > > > > + * Dmitry Baryshkov <[email protected]>
> > > > > > + */
> > > > > > +
> > > > > > +#include <linux/backlight.h>
> > > > > > +#include <linux/delay.h>
> > > > > > +#include <linux/gpio/consumer.h>
> > > > > > +#include <linux/module.h>
> > > > > > +#include <linux/of.h>
> > > > > > +#include <linux/regulator/consumer.h>
> > > > > > +
> > > > > > +#include <video/mipi_display.h>
> > > > > > +
> > > > > > +#include <drm/drm_mipi_dsi.h>
> > > > > > +#include <drm/drm_panel.h>
> > > > > > +#include <drm/drm_probe_helper.h>
> > > > > > +#include <drm/display/drm_dsc.h>
> > > > > > +#include <drm/display/drm_dsc_helper.h>
> > > > > > +
> > > > > > +#define NUM_SUPPLIES 2
> > > > > > +
> > > > > > +struct sw43408_panel {
> > > > > > + struct drm_panel base;
> > > > > > + struct mipi_dsi_device *link;
> > > > > > +
> > > > > > + const struct drm_display_mode *mode;
> > > > > > +
> > > > > > + struct regulator_bulk_data supplies[NUM_SUPPLIES];
> > > > > > +
> > > > > > + struct gpio_desc *reset_gpio;
> > > > > > +};
> > > > > > +
> > > > > > +static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
> > > > > > +{
> > > > > > + return container_of(panel, struct sw43408_panel, base);
> > > > > > +}
> > > > > > +
> > > > > > +static int sw43408_unprepare(struct drm_panel *panel)
> > > > > > +{
> > > > > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > > > > + int ret;
> > > > > > +
> > > > > > + ret = mipi_dsi_dcs_set_display_off(ctx->link);
> > > > > > + if (ret < 0)
> > > > > > + dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
> > > > > > +
> > > > > > + ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
> > > > > > + if (ret < 0)
> > > > > > + dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
> > > > > > +
> > > > > > + msleep(100);
> > > > > > +
> > > > > > + gpiod_set_value(ctx->reset_gpio, 1);
> > > > > > +
> > > > > > + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > > > > > +}
> > > > > > +
> > > > > > +static int sw43408_program(struct drm_panel *panel)
> > > > > > +{
> > > > > > + struct sw43408_panel *ctx = to_panel_info(panel);
> > > > > > + struct drm_dsc_picture_parameter_set pps;
> > > > > > + u8 dsc_en = 0x11;
> > > > >
> > > > > Yeah, this is completely strange. Bit 0, 0x1, is to enable DSC which is
> > > > > normal. 0x10 however, which is bit 4, selects PPS table 2. Do you ever set
> > > > > pps_identifier in struct drm_dsc_picture_parameter_set to 2? Or is the table
> > > > > that you send below bogus and/or not used? Maybe the Driver IC on the other
> > > > > end of the DSI link has a default PPS table with identifier 2 that works out of
> > > > > the box?
> > > >
> > > > Note, MIPI standard also requires two bytes argument. I suspect that
> > > > LG didn't fully follow the standard here.
> > >
> > > Have you read this command from downstream DTS, or have you tried sending 2
> > > bytes and seen the panel breaking? The second byte is marked as reserved and
> > > should be equal to 0; if the Driver IC is okay with sending either 1 or 2 bytes
> > > I'd strive to stick with the defined length of 2 bytes for this DCS.
> > >
> > > Have you played around with the PPS table? What if you change
> > > drm_dsc_picture_paremeter_set::pps_identifier to the second table, will the
> > > panel stop working as expected again? This could indicate that the PPS that is
> > > sent is incorrect (even though the information in the original DSC config was
> > > enough to set up the DPU and DSI correctly).
> > >
> > > According to the DSI spec it is allowed to have a pre-stored/pre-programmed
> > > PPS table, which could be used here making the current call to
> > > mipi_dsi_picture_parameter_set() useless and "confusing"?
> >
> > Ok, some short summary of my tests.
> >
> > Skipping PPS doesn't work at all, so there is no default.
> >
> > Adding a second zero byte doesn't seem to change anything. Dropping
> > the 0x1 bit ('enable') doesn't seem to change anything.
> >
> > If I send COMPRESSION_MODE before sending the PPS, various combinations work.
> > If I send COMPRESSION_MODE after sending the PPS, the follow combos work:
> >
> > pps_identifier = 0x0, COMPRESSION_MODE = 0x11
> > pps_identifier = 0x1, COMPRESSION_MODE = 0x21
>
> Thanks, this must really be an off-by-one table identifier. I presume you've
> tested pps_identifier=0x2 with COMPRESSION_MODE=0x31, and that there are only 2
> tables and not 3 or 4?

I was not able to get either pps_identifier = 0x2 or
COMPRESSION_MODE=0x31 to work.

>
> From this we can also assume that sending a new PPS will automatically switch
> the compression mode to the pps_identifier in that PPS, COMPRESSION_MODE doesn't
> seem to affect it when sent too early.

No. Omitting COMPRESSION_MODE packet breaks the display. Actually,
this was one of the issues: we were sending an incorrect packet. So
both are required.

>
> > > > Basically that's the reason why I went for the _raw function instead
> > > > of adding PPS and codec arguments to the existing function.
> > > >
> > > > >
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
> > > > > > +
> > > > > > + mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> > > > > > +
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
> > > > > > +
> > > > > > + mipi_dsi_dcs_exit_sleep_mode(ctx->link);
> > > > > > +
> > > > > > + msleep(135);
> > > > > > +
> > > > > > + mipi_dsi_compression_mode_raw(ctx->link, &dsc_en, 1);
> > > > >
> > > > > Even though I think we should change this function to describe the known
> > > > > bit layout of command 0x7 per the VESA DSI spec, for now replace 1 with
> > > > > sizeof(dsc_en)?
> > > >
> > > > If dsc_en were an array, it would have been a proper thing. Maybe I
> > > > should change it to the array to remove confusion.
> > >
> > > It should work even with a single byte, just to clarify to readers that the 3rd
> > > argument is the byte-size of the input.
> > >
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
> > > > > > + 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
> > > > > > + 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
> > > > > > + 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
> > > > > > + 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
> > > > > > + 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
> > > > > > + 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
> > > > > > + 0x01);
> > > > > > + msleep(85);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
> > > > > > + 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > > > > + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
> > > > > > + 0x16, 0x16);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
> > > > > > + mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
> > > > > > +
> > > > > > + mipi_dsi_dcs_set_display_on(ctx->link);
> > > > >
> > > > > Any specific reason to not have the (un)blanking sequence in the enable/disable
> > > > > callbacks and leaving display configuration in (un)prepare?
> > > >
> > > > We are back to the question on when it's fine to send the commands. I
> > > > think the current agreement is to send everything in the
> > > > prepare/unprepare, because of some strange hosts.
> > >
> > > For my panel drivers I'm sticking with having `post-on` commands (from
> > > downstream) in `enable/disable`, which is typically only `set_display_on`. In
> > > hopes of proposing a `prepare_atomic()` some time to allow mode selection.
> > >
> > > In a short test on recent -next I am once again allowed to send DSI commands in
> > > both .disable and .unprepare, making both functions a "clean" inverse of .enable
> > > and .prepare respectively.
> >
> > The world isn't limited to the MSM hosts.
>
> If I'm not mistaken this was an ordering issue in the drm_bridge implementation.

No. I think sunxi didn't support sending DSI commands after starting
the video stream.

>
> But you are right that some hosts might not be all too happy with sending
> commands (like unblanking?) after the cmd/video stream started, and before the
> stream stops. Which, as far as I know, are what .enable and .disable do. On
> the other hand, I was under the impression that this split mainly existed to do
> all the heavy/required lifting up-front, and only unblank when there's a video
> signal to combat any possible observed corruption?

In the ideal world that would be true. But we are not living in the
ideal world. I still have hopes to get back to the idea of reworling
this part of the DSI framework.

> In the end I'm just curious if there's a specific reason - that I need
> to take into account when resending all my panel patches - to /not/
> use .enable/.disable?

I have bookmarked this email as a main reference for the topic:
https://lore.kernel.org/dri-devel/CAPY8ntBrhYAmsraDqJGuTrSL6VjGXBAMVoN7xweV7E4qZv+v3Q@mail.gmail.com/

>
> - Marijn
>
> > > > > > + msleep(50);
> > > > > > +
> > > > > > + ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
> > > > > > +
> > > > > > + drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
> > > > > > + mipi_dsi_picture_parameter_set(ctx->link, &pps);
> > > > >
> > > > > I'm always surprised why this is sent _after_ turning the display on (unblanking
> > > > > it). Wouldn't that cause unnecessary corruption?
> > > >
> > > > No idea. I followed the dowsntream command sequences here. Most likely
> > > > the panel is not fully on until it receives the full frame to be
> > > > displayed.
> > >
> > > According to the DSI spec a PPS update is allowed to happen every frame, and
> > > (for cmdmode panels) will take effect after the next TE trigger. Unsure if a TE
> > > event happens before the first frame, otherwise this may start taking effect
> > > on the second frame onwards only.
> > >
> > > If there's no corruption on the first frame there might be a pre-programmed PPS
> > > table in slot 2, supporting the theory above.
> >
> >
> >
> > --
> > With best wishes
> > Dmitry



--
With best wishes
Dmitry