2023-05-16 20:27:33

by Jessica Zhang

[permalink] [raw]
Subject: [PATCH 0/2] Add support for Visionox R66451 AMOLED DSI panel

Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
comes with the Qualcomm HDK8350 display expansion pack.

The driver will come with display compression (DSC v1.2) enabled by
default.

Signed-off-by: Jessica Zhang <[email protected]>
---
Jessica Zhang (2):
dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
drm/panel: Add driver for Visionox r66451 panel

.../bindings/display/panel/visionox,r66451.yaml | 59 +++
drivers/gpu/drm/panel/Kconfig | 8 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 +++++++++++++++++++++
4 files changed, 463 insertions(+)
---
base-commit: a5abc0900af0cfb1b8093200a265d2791864f26b
change-id: 20230516-b4-r66451-panel-driver-bf04b5fb3d52

Best regards,
--
Jessica Zhang <[email protected]>



2023-05-16 20:27:35

by Jessica Zhang

[permalink] [raw]
Subject: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
comes with the Qualcomm HDK8350 display expansion pack.

The panel enables display compression (DSC v1.2) by default.

Signed-off-by: Jessica Zhang <[email protected]>
---
drivers/gpu/drm/panel/Kconfig | 8 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
3 files changed, 404 insertions(+)

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 29cf5fa39ff2..9c2c36dbddf3 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
Say Y here if you want to enable support for Visionox
VTDR6130 1080x2400 AMOLED DSI panel.

+config DRM_PANEL_VISIONOX_R66451
+ tristate "Visionox R66451"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ help
+ Say Y here if you want to enable support for Visionox
+ R66451 1080x2340 AMOLED DSI panel.
+
config DRM_PANEL_WIDECHIPS_WS2401
tristate "Widechips WS2401 DPI panel driver"
depends on SPI && GPIOLIB
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index b3e8ba29edd3..e043a92ee676 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
+obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
new file mode 100644
index 000000000000..e3648ead3e84
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
@@ -0,0 +1,395 @@
+//SPDX-License-Identifier: GPL-2.0-only
+//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+
+#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 <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+
+#include <video/mipi_display.h>
+
+struct visionox_r66451 {
+ struct drm_panel panel;
+ struct mipi_dsi_device *dsi;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[2];
+ bool prepared, enabled;
+};
+
+static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
+{
+ return container_of(panel, struct visionox_r66451, panel);
+}
+
+static void visionox_r66451_reset(struct visionox_r66451 *ctx)
+{
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ usleep_range(10000, 10100);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ usleep_range(10000, 10100);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ usleep_range(10000, 10100);
+}
+
+static int visionox_r66451_on(struct visionox_r66451 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
+ mipi_dsi_dcs_write_seq(dsi, 0xc2,
+ 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x09, 0x3c);
+ mipi_dsi_dcs_write_seq(dsi, 0xd7,
+ 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
+ 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
+ mipi_dsi_dcs_write_seq(dsi, 0xde,
+ 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
+ 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
+ mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
+ mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
+ mipi_dsi_dcs_write_seq(dsi, 0xc4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
+ mipi_dsi_dcs_write_seq(dsi, 0xcf,
+ 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
+ mipi_dsi_dcs_write_seq(dsi, 0xd3,
+ 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
+ 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
+ 0x3c, 0x9c);
+ mipi_dsi_dcs_write_seq(dsi, 0xd7,
+ 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
+ 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
+ mipi_dsi_dcs_write_seq(dsi, 0xd8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
+ 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
+ mipi_dsi_dcs_write_seq(dsi, 0xdf,
+ 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+ mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
+ mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
+ mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
+ mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
+ mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
+ mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
+ mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
+ mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
+ mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
+ mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
+ mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
+ mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);
+ mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed on set display on: %d\n", ret);
+ return ret;
+ }
+ msleep(20);
+
+ return 0;
+}
+
+static int visionox_r66451_off(struct visionox_r66451 *ctx)
+{
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct device *dev = &dsi->dev;
+ int ret;
+
+ dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display off: %d\n", ret);
+ return ret;
+ }
+ msleep(20);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
+ return ret;
+ }
+ msleep(120);
+
+ return 0;
+}
+
+static int visionox_r66451_prepare(struct drm_panel *panel)
+{
+ struct visionox_r66451 *ctx = to_visionox_r66451(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ visionox_r66451_reset(ctx);
+
+ ret = visionox_r66451_on(ctx);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize panel: %d\n", ret);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ return ret;
+ }
+
+ ctx->prepared = true;
+ return 0;
+}
+
+static int visionox_r66451_unprepare(struct drm_panel *panel)
+{
+ struct visionox_r66451 *ctx = to_visionox_r66451(panel);
+ struct device *dev = &ctx->dsi->dev;
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ ret = visionox_r66451_off(ctx);
+ if (ret < 0)
+ dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+
+ ctx->prepared = false;
+ return 0;
+}
+
+static const struct drm_display_mode visionox_r66451_mode = {
+ .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
+ .hdisplay = 1080,
+ .hsync_start = 1080 + 95,
+ .hsync_end = 1080 + 95 + 1,
+ .htotal = 1080 + 95 + 1 + 40,
+ .vdisplay = 2340,
+ .vsync_start = 2340 + 25,
+ .vsync_end = 2340 + 25 + 1,
+ .vtotal = 2340 + 25 + 1 + 4,
+ .width_mm = 0,
+ .height_mm = 0,
+};
+
+static int visionox_r66451_enable(struct drm_panel *panel)
+{
+ struct visionox_r66451 *ctx = to_visionox_r66451(panel);
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct drm_dsc_picture_parameter_set pps;
+ int ret;
+
+ if (ctx->enabled)
+ return 0;
+
+ if (!dsi->dsc) {
+ dev_err(&dsi->dev, "DSC not attached to DSI\n");
+ return -ENODEV;
+ }
+
+ drm_dsc_pps_payload_pack(&pps, dsi->dsc);
+ ret = mipi_dsi_picture_parameter_set(dsi, &pps);
+
+ ctx->enabled = true;
+
+ return 0;
+}
+
+static int visionox_r66451_disable(struct drm_panel *panel)
+{
+ struct visionox_r66451 *ctx = to_visionox_r66451(panel);
+
+ ctx->enabled = false;
+
+ return 0;
+}
+
+static int visionox_r66451_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
+ if (!mode)
+ return -ENOMEM;
+
+ drm_mode_set_name(mode);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs visionox_r66451_funcs = {
+ .prepare = visionox_r66451_prepare,
+ .unprepare = visionox_r66451_unprepare,
+ .get_modes = visionox_r66451_get_modes,
+ .enable = visionox_r66451_enable,
+ .disable = visionox_r66451_disable,
+};
+
+static int visionox_r66451_bl_update_status(struct backlight_device *bl)
+{
+ struct mipi_dsi_device *dsi = bl_get_data(bl);
+ u16 brightness = backlight_get_brightness(bl);
+
+ return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
+}
+
+static const struct backlight_ops visionox_r66451_bl_ops = {
+ .update_status = visionox_r66451_bl_update_status,
+};
+
+static struct backlight_device *
+visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ const struct backlight_properties props = {
+ .type = BACKLIGHT_RAW,
+ .brightness = 255,
+ .max_brightness = 4095,
+ };
+
+ return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
+ &visionox_r66451_bl_ops, &props);
+}
+
+static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct visionox_r66451 *ctx;
+ struct drm_dsc_config *dsc;
+ int ret = 0;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
+ if (!dsc)
+ return -ENOMEM;
+
+ /* Set DSC params */
+ dsc->dsc_version_major = 0x1;
+ dsc->dsc_version_minor = 0x2;
+
+ dsc->slice_height = 20;
+ dsc->slice_width = 540;
+ dsc->slice_count = 2;
+ dsc->bits_per_component = 8;
+ dsc->bits_per_pixel = 0x8 << 4;
+ dsc->block_pred_enable = true;
+
+ dsi->dsc = dsc;
+
+ ctx->supplies[0].supply = "vddio";
+ ctx->supplies[1].supply = "vdd";
+
+ ret = devm_regulator_bulk_get(&dsi->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))
+ return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
+
+ ctx->dsi = dsi;
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ dsi->lanes = 4;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+ drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
+ ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
+ if (IS_ERR(ctx->panel.backlight))
+ return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
+ "Failed to create backlight\n");
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
+ drm_panel_remove(&ctx->panel);
+ }
+
+ return ret;
+}
+
+static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
+{
+ struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = mipi_dsi_detach(dsi);
+ if (ret < 0)
+ dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
+
+ drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id visionox_r66451_of_match[] = {
+ {.compatible = "visionox,r66451"},
+ { /*sentinel*/ }
+};
+MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
+
+static struct mipi_dsi_driver visionox_r66451_driver = {
+ .probe = visionox_r66451_probe,
+ .remove = visionox_r66451_remove,
+ .driver = {
+ .name = "panel-visionox-r66451",
+ .of_match_table = visionox_r66451_of_match,
+ },
+};
+
+module_mipi_dsi_driver(visionox_r66451_driver);
+
+MODULE_AUTHOR("Jessica Zhang <[email protected]>");
+MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
+MODULE_LICENSE("GPL");

--
2.40.1


2023-05-16 20:28:28

by Jessica Zhang

[permalink] [raw]
Subject: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings

Signed-off-by: Jessica Zhang <[email protected]>
---
.../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
1 file changed, 59 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
new file mode 100644
index 000000000000..6ba323683921
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/visionox,r66451.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Visionox R66451 AMOLED DSI Panel
+
+maintainers:
+ - Jessica Zhang <[email protected]>
+
+allOf:
+ - $ref: panel-common.yaml#
+
+properties:
+ compatible:
+ const: visionox,r66451
+
+ reg:
+ maxItems: 1
+ description: DSI virtual channel
+
+ vddio-supply: true
+ vdd-supply: true
+ port: true
+ reset-gpios: true
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - vddio-supply
+ - vdd-supply
+ - reset-gpios
+ - port
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ dsi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ panel@0 {
+ compatible = "visionox,r66451";
+ reg = <0>;
+ vddio-supply = <&vreg_l12c_1p8>;
+ vdd-supply = <&vreg_l13c_3p0>;
+
+ reset-gpios = <&tlmm 24 GPIO_ACTIVE_LOW>;
+
+ port {
+ panel0_in: endpoint {
+ remote-endpoint = <&dsi0_out>;
+ };
+ };
+ };
+ };
+...

--
2.40.1


2023-05-17 08:25:11

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

On 16/05/2023 22:20, Jessica Zhang wrote:
> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
>
> Signed-off-by: Jessica Zhang <[email protected]>
> ---
> .../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
> 1 file changed, 59 insertions(+)

If there is going to be new version:
A nit, subject: drop second/last, redundant "bindings". The
"dt-bindings" prefix is already stating that these are bindings.


Reviewed-by: Krzysztof Kozlowski <[email protected]>

Best regards,
Krzysztof


2023-05-18 00:53:25

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 16/05/2023 23:20, Jessica Zhang wrote:
> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
> comes with the Qualcomm HDK8350 display expansion pack.
>
> The panel enables display compression (DSC v1.2) by default.
>
> Signed-off-by: Jessica Zhang <[email protected]>
> ---
> drivers/gpu/drm/panel/Kconfig | 8 +
> drivers/gpu/drm/panel/Makefile | 1 +
> drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
> 3 files changed, 404 insertions(+)
>
> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index 29cf5fa39ff2..9c2c36dbddf3 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
> Say Y here if you want to enable support for Visionox
> VTDR6130 1080x2400 AMOLED DSI panel.
>
> +config DRM_PANEL_VISIONOX_R66451
> + tristate "Visionox R66451"
> + depends on OF
> + depends on DRM_MIPI_DSI
> + help
> + Say Y here if you want to enable support for Visionox
> + R66451 1080x2340 AMOLED DSI panel.
> +
> config DRM_PANEL_WIDECHIPS_WS2401
> tristate "Widechips WS2401 DPI panel driver"
> depends on SPI && GPIOLIB
> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index b3e8ba29edd3..e043a92ee676 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
> obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
> obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
> obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
> obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
> obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> new file mode 100644
> index 000000000000..e3648ead3e84
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> @@ -0,0 +1,395 @@
> +//SPDX-License-Identifier: GPL-2.0-only
> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
> +
> +#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 <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_panel.h>
> +#include <drm/display/drm_dsc.h>
> +#include <drm/display/drm_dsc_helper.h>
> +
> +#include <video/mipi_display.h>
> +
> +struct visionox_r66451 {
> + struct drm_panel panel;
> + struct mipi_dsi_device *dsi;
> + struct gpio_desc *reset_gpio;
> + struct regulator_bulk_data supplies[2];
> + bool prepared, enabled;
> +};
> +
> +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
> +{
> + return container_of(panel, struct visionox_r66451, panel);
> +}
> +
> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
> +{
> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> + usleep_range(10000, 10100);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + usleep_range(10000, 10100);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> + usleep_range(10000, 10100);
> +}
> +
> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
> +{
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct device *dev = &dsi->dev;
> + int ret;
> +
> + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
> +
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xc2,
> + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
> + 0x09, 0x3c);
> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> + mipi_dsi_dcs_write_seq(dsi, 0xde,
> + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
> + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> + mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xc4,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
> + mipi_dsi_dcs_write_seq(dsi, 0xcf,
> + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
> + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
> + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
> + mipi_dsi_dcs_write_seq(dsi, 0xd3,
> + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
> + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
> + 0x3c, 0x9c);
> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> + mipi_dsi_dcs_write_seq(dsi, 0xd8,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
> + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
> + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
> + mipi_dsi_dcs_write_seq(dsi, 0xdf,
> + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
> + mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
> + mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
> + mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);

Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);

> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);

mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);

> +
> + ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
> + return ret;
> + }
> + msleep(120);
> +
> + ret = mipi_dsi_dcs_set_display_on(dsi);

Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
a a part of _enable()? Correspondingly _off should IMO be a part of
_disable callback().

> + if (ret < 0) {
> + dev_err(dev, "Failed on set display on: %d\n", ret);
> + return ret;
> + }
> + msleep(20);


Do we need to add here the following line?

dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;


> +
> + return 0;
> +}
> +
> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
> +{
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct device *dev = &dsi->dev;
> + int ret;
> +
> + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
> +
> + ret = mipi_dsi_dcs_set_display_off(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to set display off: %d\n", ret);
> + return ret;
> + }
> + msleep(20);
> +
> + ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
> + return ret;
> + }
> + msleep(120);
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_prepare(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> + struct device *dev = &ctx->dsi->dev;
> + int ret;
> +
> + if (ctx->prepared)
> + return 0;
> +
> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
> + ctx->supplies);
> + if (ret < 0)
> + return ret;
> +
> + visionox_r66451_reset(ctx);
> +
> + ret = visionox_r66451_on(ctx);
> + if (ret < 0) {
> + dev_err(dev, "Failed to initialize panel: %d\n", ret);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> + return ret;
> + }
> +
> + ctx->prepared = true;
> + return 0;
> +}
> +
> +static int visionox_r66451_unprepare(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> + struct device *dev = &ctx->dsi->dev;
> + int ret;
> +
> + if (!ctx->prepared)
> + return 0;
> +
> + ret = visionox_r66451_off(ctx);
> + if (ret < 0)
> + dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
> +
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> +
> + ctx->prepared = false;
> + return 0;
> +}
> +
> +static const struct drm_display_mode visionox_r66451_mode = {
> + .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
> + .hdisplay = 1080,
> + .hsync_start = 1080 + 95,
> + .hsync_end = 1080 + 95 + 1,
> + .htotal = 1080 + 95 + 1 + 40,
> + .vdisplay = 2340,
> + .vsync_start = 2340 + 25,
> + .vsync_end = 2340 + 25 + 1,
> + .vtotal = 2340 + 25 + 1 + 4,
> + .width_mm = 0,
> + .height_mm = 0,

Please provide real values here.

> +};
> +
> +static int visionox_r66451_enable(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct drm_dsc_picture_parameter_set pps;
> + int ret;
> +
> + if (ctx->enabled)
> + return 0;
> +
> + if (!dsi->dsc) {
> + dev_err(&dsi->dev, "DSC not attached to DSI\n");
> + return -ENODEV;
> + }
> +
> + drm_dsc_pps_payload_pack(&pps, dsi->dsc);
> + ret = mipi_dsi_picture_parameter_set(dsi, &pps);
> +
> + ctx->enabled = true;
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_disable(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> +
> + ctx->enabled = false;
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_get_modes(struct drm_panel *panel,
> + struct drm_connector *connector)
> +{
> + struct drm_display_mode *mode;
> +
> + mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
> + if (!mode)
> + return -ENOMEM;
> +
> + drm_mode_set_name(mode);
> +
> + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> + connector->display_info.width_mm = mode->width_mm;
> + connector->display_info.height_mm = mode->height_mm;
> + drm_mode_probed_add(connector, mode);

Can we use drm_connector_helper_get_modes_fixed() instead?

> +
> + return 1;
> +}
> +
> +static const struct drm_panel_funcs visionox_r66451_funcs = {
> + .prepare = visionox_r66451_prepare,
> + .unprepare = visionox_r66451_unprepare,
> + .get_modes = visionox_r66451_get_modes,
> + .enable = visionox_r66451_enable,
> + .disable = visionox_r66451_disable,
> +};
> +
> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
> +{
> + struct mipi_dsi_device *dsi = bl_get_data(bl);
> + u16 brightness = backlight_get_brightness(bl);
> +
> + return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));

mipi_dsi_dcs_set_display_brightness() already converts the brightness,
so you don't need cpu_to_le16 here.

> +}
> +
> +static const struct backlight_ops visionox_r66451_bl_ops = {
> + .update_status = visionox_r66451_bl_update_status,
> +};
> +
> +static struct backlight_device *
> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
> +{
> + struct device *dev = &dsi->dev;
> + const struct backlight_properties props = {
> + .type = BACKLIGHT_RAW,
> + .brightness = 255,
> + .max_brightness = 4095,
> + };
> +
> + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
> + &visionox_r66451_bl_ops, &props);
> +}
> +
> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
> +{
> + struct device *dev = &dsi->dev;
> + struct visionox_r66451 *ctx;
> + struct drm_dsc_config *dsc;
> + int ret = 0;
> +
> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
> + if (!dsc)
> + return -ENOMEM;
> +
> + /* Set DSC params */
> + dsc->dsc_version_major = 0x1;
> + dsc->dsc_version_minor = 0x2;
> +
> + dsc->slice_height = 20;
> + dsc->slice_width = 540;
> + dsc->slice_count = 2;
> + dsc->bits_per_component = 8;
> + dsc->bits_per_pixel = 0x8 << 4;
> + dsc->block_pred_enable = true;
> +
> + dsi->dsc = dsc;
> +
> + ctx->supplies[0].supply = "vddio";
> + ctx->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(&dsi->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))
> + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
> +
> + ctx->dsi = dsi;
> + mipi_dsi_set_drvdata(dsi, ctx);
> +
> + dsi->lanes = 4;
> + dsi->format = MIPI_DSI_FMT_RGB888;
> + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
> +
> + drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
> + ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
> + if (IS_ERR(ctx->panel.backlight))
> + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
> + "Failed to create backlight\n");
> +
> + drm_panel_add(&ctx->panel);
> +
> + ret = mipi_dsi_attach(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
> + drm_panel_remove(&ctx->panel);
> + }
> +
> + return ret;
> +}
> +
> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
> +{
> + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
> + int ret;
> +
> + ret = mipi_dsi_detach(dsi);
> + if (ret < 0)
> + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
> +
> + drm_panel_remove(&ctx->panel);
> +}
> +
> +static const struct of_device_id visionox_r66451_of_match[] = {
> + {.compatible = "visionox,r66451"},
> + { /*sentinel*/ }
> +};
> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
> +
> +static struct mipi_dsi_driver visionox_r66451_driver = {
> + .probe = visionox_r66451_probe,
> + .remove = visionox_r66451_remove,
> + .driver = {
> + .name = "panel-visionox-r66451",
> + .of_match_table = visionox_r66451_of_match,
> + },
> +};
> +
> +module_mipi_dsi_driver(visionox_r66451_driver);
> +
> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
> +MODULE_LICENSE("GPL");
>

--
With best wishes
Dmitry


2023-05-18 22:34:39

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
> On 16/05/2023 23:20, Jessica Zhang wrote:
>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>> comes with the Qualcomm HDK8350 display expansion pack.
>>
>> The panel enables display compression (DSC v1.2) by default.
>>
>> Signed-off-by: Jessica Zhang <[email protected]>
>> ---
>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>> ++++++++++++++++++++++++++
>>   3 files changed, 404 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/panel/Kconfig
>> b/drivers/gpu/drm/panel/Kconfig
>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>> --- a/drivers/gpu/drm/panel/Kconfig
>> +++ b/drivers/gpu/drm/panel/Kconfig
>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>         Say Y here if you want to enable support for Visionox
>>         VTDR6130 1080x2400 AMOLED DSI panel.
>> +config DRM_PANEL_VISIONOX_R66451
>> +    tristate "Visionox R66451"
>> +    depends on OF
>> +    depends on DRM_MIPI_DSI
>> +    help
>> +      Say Y here if you want to enable support for Visionox
>> +      R66451 1080x2340 AMOLED DSI panel.
>> +
>>   config DRM_PANEL_WIDECHIPS_WS2401
>>       tristate "Widechips WS2401 DPI panel driver"
>>       depends on SPI && GPIOLIB
>> diff --git a/drivers/gpu/drm/panel/Makefile
>> b/drivers/gpu/drm/panel/Makefile
>> index b3e8ba29edd3..e043a92ee676 100644
>> --- a/drivers/gpu/drm/panel/Makefile
>> +++ b/drivers/gpu/drm/panel/Makefile
>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>> panel-tpo-tpg110.o
>>   obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>   obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>   obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>> panel-xinpeng-xpp055c272.o
>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> new file mode 100644
>> index 000000000000..e3648ead3e84
>> --- /dev/null
>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> @@ -0,0 +1,395 @@
>> +//SPDX-License-Identifier: GPL-2.0-only
>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> +
>> +#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 <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_modes.h>
>> +#include <drm/drm_panel.h>
>> +#include <drm/display/drm_dsc.h>
>> +#include <drm/display/drm_dsc_helper.h>
>> +
>> +#include <video/mipi_display.h>
>> +
>> +struct visionox_r66451 {
>> +    struct drm_panel panel;
>> +    struct mipi_dsi_device *dsi;
>> +    struct gpio_desc *reset_gpio;
>> +    struct regulator_bulk_data supplies[2];
>> +    bool prepared, enabled;
>> +};
>> +
>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>> drm_panel *panel)
>> +{
>> +    return container_of(panel, struct visionox_r66451, panel);
>> +}
>> +
>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>> +{
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>> +    usleep_range(10000, 10100);
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +    usleep_range(10000, 10100);
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>> +    usleep_range(10000, 10100);
>> +}
>> +
>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>> +{
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct device *dev = &dsi->dev;
>> +    int ret;
>> +
>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>> +
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
>> +                   0x09, 0x3c);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>> 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
>> +                   0x3c, 0x9c);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
>> 0x39, 0x04, 0x09, 0x34);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>> 0x00, 0x04, 0x37);
>
> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);

Hi Dmitry,

Acked.

>
>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>> 0x00, 0x09, 0x23);
>
> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);

Acked.

>
>> +
>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(120);
>> +
>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>
> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
> a a part of _enable()? Correspondingly _off should IMO be a part of
> _disable callback().

Acked. Looking at the downstream code, it seems that all the dcs on
commands can be sent within _enable() (and similarly for the off
commands with _disable()).

>
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(20);
>
>
> Do we need to add here the following line?
>
> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;

Do you mean within the return on error block?

From my understanding, we should be setting the MIPI_DSI_MODE_LPM for
panel_on() then unsetting MODE_LPM in panel_off().

>
>
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>> +{
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct device *dev = &dsi->dev;
>> +    int ret;
>> +
>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>> +
>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(20);
>> +
>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(120);
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct device *dev = &ctx->dsi->dev;
>> +    int ret;
>> +
>> +    if (ctx->prepared)
>> +        return 0;
>> +
>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>> +                    ctx->supplies);
>> +    if (ret < 0)
>> +        return ret;
>> +
>> +    visionox_r66451_reset(ctx);
>> +
>> +    ret = visionox_r66451_on(ctx);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>> ctx->supplies);
>> +        return ret;
>> +    }
>> +
>> +    ctx->prepared = true;
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct device *dev = &ctx->dsi->dev;
>> +    int ret;
>> +
>> +    if (!ctx->prepared)
>> +        return 0;
>> +
>> +    ret = visionox_r66451_off(ctx);
>> +    if (ret < 0)
>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>> +
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>> +
>> +    ctx->prepared = false;
>> +    return 0;
>> +}
>> +
>> +static const struct drm_display_mode visionox_r66451_mode = {
>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>> +    .hdisplay = 1080,
>> +    .hsync_start = 1080 + 95,
>> +    .hsync_end = 1080 + 95 + 1,
>> +    .htotal = 1080 + 95 + 1 + 40,
>> +    .vdisplay = 2340,
>> +    .vsync_start = 2340 + 25,
>> +    .vsync_end = 2340 + 25 + 1,
>> +    .vtotal = 2340 + 25 + 1 + 4,
>> +    .width_mm = 0,
>> +    .height_mm = 0,
>
> Please provide real values here.

Acked.

>
>> +};
>> +
>> +static int visionox_r66451_enable(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct drm_dsc_picture_parameter_set pps;
>> +    int ret;
>> +
>> +    if (ctx->enabled)
>> +        return 0;
>> +
>> +    if (!dsi->dsc) {
>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>> +        return -ENODEV;
>> +    }
>> +
>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>> +
>> +    ctx->enabled = true;
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_disable(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +
>> +    ctx->enabled = false;
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>> +                    struct drm_connector *connector)
>> +{
>> +    struct drm_display_mode *mode;
>> +
>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>> +    if (!mode)
>> +        return -ENOMEM;
>> +
>> +    drm_mode_set_name(mode);
>> +
>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>> +    connector->display_info.width_mm = mode->width_mm;
>> +    connector->display_info.height_mm = mode->height_mm;
>> +    drm_mode_probed_add(connector, mode);
>
> Can we use drm_connector_helper_get_modes_fixed() instead?

Acked.

>
>> +
>> +    return 1;
>> +}
>> +
>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>> +    .prepare = visionox_r66451_prepare,
>> +    .unprepare = visionox_r66451_unprepare,
>> +    .get_modes = visionox_r66451_get_modes,
>> +    .enable = visionox_r66451_enable,
>> +    .disable = visionox_r66451_disable,
>> +};
>> +
>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>> +{
>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>> +    u16 brightness = backlight_get_brightness(bl);
>> +
>> +    return mipi_dsi_dcs_set_display_brightness(dsi,
>> cpu_to_le16(brightness));
>
> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
> so you don't need cpu_to_le16 here.

Acked.

Thanks,

Jessica Zhang

>
>> +}
>> +
>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>> +    .update_status = visionox_r66451_bl_update_status,
>> +};
>> +
>> +static struct backlight_device *
>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>> +{
>> +    struct device *dev = &dsi->dev;
>> +    const struct backlight_properties props = {
>> +        .type = BACKLIGHT_RAW,
>> +        .brightness = 255,
>> +        .max_brightness = 4095,
>> +    };
>> +
>> +    return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>> +                          &visionox_r66451_bl_ops, &props);
>> +}
>> +
>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>> +{
>> +    struct device *dev = &dsi->dev;
>> +    struct visionox_r66451 *ctx;
>> +    struct drm_dsc_config *dsc;
>> +    int ret = 0;
>> +
>> +    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>> +    if (!ctx)
>> +        return -ENOMEM;
>> +
>> +    dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>> +    if (!dsc)
>> +        return -ENOMEM;
>> +
>> +    /* Set DSC params */
>> +    dsc->dsc_version_major = 0x1;
>> +    dsc->dsc_version_minor = 0x2;
>> +
>> +    dsc->slice_height = 20;
>> +    dsc->slice_width = 540;
>> +    dsc->slice_count = 2;
>> +    dsc->bits_per_component = 8;
>> +    dsc->bits_per_pixel = 0x8 << 4;
>> +    dsc->block_pred_enable = true;
>> +
>> +    dsi->dsc = dsc;
>> +
>> +    ctx->supplies[0].supply = "vddio";
>> +    ctx->supplies[1].supply = "vdd";
>> +
>> +    ret = devm_regulator_bulk_get(&dsi->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))
>> +        return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed
>> to get reset-gpios\n");
>> +
>> +    ctx->dsi = dsi;
>> +    mipi_dsi_set_drvdata(dsi, ctx);
>> +
>> +    dsi->lanes = 4;
>> +    dsi->format = MIPI_DSI_FMT_RGB888;
>> +    dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
>> +
>> +    drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs,
>> DRM_MODE_CONNECTOR_DSI);
>> +    ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>> +    if (IS_ERR(ctx->panel.backlight))
>> +        return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>> +                "Failed to create backlight\n");
>> +
>> +    drm_panel_add(&ctx->panel);
>> +
>> +    ret = mipi_dsi_attach(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>> +        drm_panel_remove(&ctx->panel);
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>> +{
>> +    struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>> +    int ret;
>> +
>> +    ret = mipi_dsi_detach(dsi);
>> +    if (ret < 0)
>> +        dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>> +
>> +    drm_panel_remove(&ctx->panel);
>> +}
>> +
>> +static const struct of_device_id visionox_r66451_of_match[] = {
>> +    {.compatible = "visionox,r66451"},
>> +    { /*sentinel*/ }
>> +};
>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>> +
>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>> +    .probe = visionox_r66451_probe,
>> +    .remove = visionox_r66451_remove,
>> +    .driver = {
>> +        .name = "panel-visionox-r66451",
>> +        .of_match_table = visionox_r66451_of_match,
>> +    },
>> +};
>> +
>> +module_mipi_dsi_driver(visionox_r66451_driver);
>> +
>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
>> panel");
>> +MODULE_LICENSE("GPL");
>>
>
> --
> With best wishes
> Dmitry
>

2023-05-18 22:52:33

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On Fri, 19 May 2023 at 01:17, Jessica Zhang <[email protected]> wrote:
>
>
>
> On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
> > On 16/05/2023 23:20, Jessica Zhang wrote:
> >> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
> >> comes with the Qualcomm HDK8350 display expansion pack.
> >>
> >> The panel enables display compression (DSC v1.2) by default.
> >>
> >> Signed-off-by: Jessica Zhang <[email protected]>
> >> ---
> >> drivers/gpu/drm/panel/Kconfig | 8 +
> >> drivers/gpu/drm/panel/Makefile | 1 +
> >> drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
> >> ++++++++++++++++++++++++++
> >> 3 files changed, 404 insertions(+)
> >>
> >> diff --git a/drivers/gpu/drm/panel/Kconfig
> >> b/drivers/gpu/drm/panel/Kconfig
> >> index 29cf5fa39ff2..9c2c36dbddf3 100644
> >> --- a/drivers/gpu/drm/panel/Kconfig
> >> +++ b/drivers/gpu/drm/panel/Kconfig
> >> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
> >> Say Y here if you want to enable support for Visionox
> >> VTDR6130 1080x2400 AMOLED DSI panel.
> >> +config DRM_PANEL_VISIONOX_R66451
> >> + tristate "Visionox R66451"
> >> + depends on OF
> >> + depends on DRM_MIPI_DSI
> >> + help
> >> + Say Y here if you want to enable support for Visionox
> >> + R66451 1080x2340 AMOLED DSI panel.
> >> +
> >> config DRM_PANEL_WIDECHIPS_WS2401
> >> tristate "Widechips WS2401 DPI panel driver"
> >> depends on SPI && GPIOLIB
> >> diff --git a/drivers/gpu/drm/panel/Makefile
> >> b/drivers/gpu/drm/panel/Makefile
> >> index b3e8ba29edd3..e043a92ee676 100644
> >> --- a/drivers/gpu/drm/panel/Makefile
> >> +++ b/drivers/gpu/drm/panel/Makefile
> >> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
> >> panel-tpo-tpg110.o
> >> obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
> >> obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
> >> obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
> >> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
> >> obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
> >> obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
> >> panel-xinpeng-xpp055c272.o
> >> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
> >> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> >> new file mode 100644
> >> index 000000000000..e3648ead3e84
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> >> @@ -0,0 +1,395 @@
> >> +//SPDX-License-Identifier: GPL-2.0-only
> >> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
> >> reserved.
> >> +
> >> +#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 <drm/drm_mipi_dsi.h>
> >> +#include <drm/drm_modes.h>
> >> +#include <drm/drm_panel.h>
> >> +#include <drm/display/drm_dsc.h>
> >> +#include <drm/display/drm_dsc_helper.h>
> >> +
> >> +#include <video/mipi_display.h>
> >> +
> >> +struct visionox_r66451 {
> >> + struct drm_panel panel;
> >> + struct mipi_dsi_device *dsi;
> >> + struct gpio_desc *reset_gpio;
> >> + struct regulator_bulk_data supplies[2];
> >> + bool prepared, enabled;
> >> +};
> >> +
> >> +static inline struct visionox_r66451 *to_visionox_r66451(struct
> >> drm_panel *panel)
> >> +{
> >> + return container_of(panel, struct visionox_r66451, panel);
> >> +}
> >> +
> >> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
> >> +{
> >> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> >> + usleep_range(10000, 10100);
> >> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> >> + usleep_range(10000, 10100);
> >> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> >> + usleep_range(10000, 10100);
> >> +}
> >> +
> >> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
> >> +{
> >> + struct mipi_dsi_device *dsi = ctx->dsi;
> >> + struct device *dev = &dsi->dev;
> >> + int ret;
> >> +
> >> + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
> >> +
> >> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xc2,
> >> + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
> >> + 0x09, 0x3c);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> >> + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> >> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> >> + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xde,
> >> + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
> >> + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
> >> 0x00, 0x00);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xc4,
> >> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> >> + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xcf,
> >> + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
> >> + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
> >> + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xd3,
> >> + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
> >> + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
> >> + 0x3c, 0x9c);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> >> + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> >> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> >> + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xd8,
> >> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> >> + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
> >> + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> >> + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
> >> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
> >> + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xdf,
> >> + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
> >> + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> >> + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
> >> + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
> >> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
> >> 0x39, 0x04, 0x09, 0x34);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
> >> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
> >> + mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> >> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
> >> 0x00, 0x04, 0x37);
> >
> > Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>
> Hi Dmitry,
>
> Acked.
>
> >
> >> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
> >> 0x00, 0x09, 0x23);
> >
> > mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>
> Acked.
>
> >
> >> +
> >> + ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> >> + if (ret < 0) {
> >> + dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
> >> + return ret;
> >> + }
> >> + msleep(120);
> >> +
> >> + ret = mipi_dsi_dcs_set_display_on(dsi);
> >
> > Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
> > a a part of _enable()? Correspondingly _off should IMO be a part of
> > _disable callback().
>
> Acked. Looking at the downstream code, it seems that all the dcs on
> commands can be sent within _enable() (and similarly for the off
> commands with _disable()).

I wouldn't check against the downstream. The distinction is that
prepare() is called before enabling the video stream and as such it
usually contains commands to set up the panel parameters. The enable()
callback is called after starting the video stream, so it should just
turn on the image output, the backlight, etc..

>
> >
> >> + if (ret < 0) {
> >> + dev_err(dev, "Failed on set display on: %d\n", ret);
> >> + return ret;
> >> + }
> >> + msleep(20);
> >
> >
> > Do we need to add here the following line?
> >
> > dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>
> Do you mean within the return on error block?

Within the return. I think this might depend on the panel itself, see
how other panels handle toggling MIPI_DSI_MODE_LPM. E.g. panel-dsi-cm
or panel-novatek-nt35950 unsets MIPI_DSI_MODE_LPM at the end of
power_on. Some of the panels also toggle LPM while reading or updating
the backlight.

>
> From my understanding, we should be setting the MIPI_DSI_MODE_LPM for
> panel_on() then unsetting MODE_LPM in panel_off().
>
> >
> >
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
> >> +{
> >> + struct mipi_dsi_device *dsi = ctx->dsi;
> >> + struct device *dev = &dsi->dev;
> >> + int ret;
> >> +
> >> + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
> >> +
> >> + ret = mipi_dsi_dcs_set_display_off(dsi);
> >> + if (ret < 0) {
> >> + dev_err(dev, "Failed to set display off: %d\n", ret);
> >> + return ret;
> >> + }
> >> + msleep(20);
> >> +
> >> + ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
> >> + if (ret < 0) {
> >> + dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
> >> + return ret;
> >> + }
> >> + msleep(120);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int visionox_r66451_prepare(struct drm_panel *panel)
> >> +{
> >> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> >> + struct device *dev = &ctx->dsi->dev;
> >> + int ret;
> >> +
> >> + if (ctx->prepared)
> >> + return 0;
> >> +
> >> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
> >> + ctx->supplies);
> >> + if (ret < 0)
> >> + return ret;
> >> +
> >> + visionox_r66451_reset(ctx);
> >> +
> >> + ret = visionox_r66451_on(ctx);
> >> + if (ret < 0) {
> >> + dev_err(dev, "Failed to initialize panel: %d\n", ret);
> >> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> >> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
> >> ctx->supplies);
> >> + return ret;
> >> + }
> >> +
> >> + ctx->prepared = true;
> >> + return 0;
> >> +}
> >> +
> >> +static int visionox_r66451_unprepare(struct drm_panel *panel)
> >> +{
> >> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> >> + struct device *dev = &ctx->dsi->dev;
> >> + int ret;
> >> +
> >> + if (!ctx->prepared)
> >> + return 0;
> >> +
> >> + ret = visionox_r66451_off(ctx);
> >> + if (ret < 0)
> >> + dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
> >> +
> >> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> >> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> >> +
> >> + ctx->prepared = false;
> >> + return 0;
> >> +}
> >> +
> >> +static const struct drm_display_mode visionox_r66451_mode = {
> >> + .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
> >> + .hdisplay = 1080,
> >> + .hsync_start = 1080 + 95,
> >> + .hsync_end = 1080 + 95 + 1,
> >> + .htotal = 1080 + 95 + 1 + 40,
> >> + .vdisplay = 2340,
> >> + .vsync_start = 2340 + 25,
> >> + .vsync_end = 2340 + 25 + 1,
> >> + .vtotal = 2340 + 25 + 1 + 4,
> >> + .width_mm = 0,
> >> + .height_mm = 0,
> >
> > Please provide real values here.
>
> Acked.
>
> >
> >> +};
> >> +
> >> +static int visionox_r66451_enable(struct drm_panel *panel)
> >> +{
> >> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> >> + struct mipi_dsi_device *dsi = ctx->dsi;
> >> + struct drm_dsc_picture_parameter_set pps;
> >> + int ret;
> >> +
> >> + if (ctx->enabled)
> >> + return 0;
> >> +
> >> + if (!dsi->dsc) {
> >> + dev_err(&dsi->dev, "DSC not attached to DSI\n");
> >> + return -ENODEV;
> >> + }
> >> +
> >> + drm_dsc_pps_payload_pack(&pps, dsi->dsc);
> >> + ret = mipi_dsi_picture_parameter_set(dsi, &pps);
> >> +
> >> + ctx->enabled = true;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int visionox_r66451_disable(struct drm_panel *panel)
> >> +{
> >> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> >> +
> >> + ctx->enabled = false;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int visionox_r66451_get_modes(struct drm_panel *panel,
> >> + struct drm_connector *connector)
> >> +{
> >> + struct drm_display_mode *mode;
> >> +
> >> + mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
> >> + if (!mode)
> >> + return -ENOMEM;
> >> +
> >> + drm_mode_set_name(mode);
> >> +
> >> + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> >> + connector->display_info.width_mm = mode->width_mm;
> >> + connector->display_info.height_mm = mode->height_mm;
> >> + drm_mode_probed_add(connector, mode);
> >
> > Can we use drm_connector_helper_get_modes_fixed() instead?
>
> Acked.
>
> >
> >> +
> >> + return 1;
> >> +}
> >> +
> >> +static const struct drm_panel_funcs visionox_r66451_funcs = {
> >> + .prepare = visionox_r66451_prepare,
> >> + .unprepare = visionox_r66451_unprepare,
> >> + .get_modes = visionox_r66451_get_modes,
> >> + .enable = visionox_r66451_enable,
> >> + .disable = visionox_r66451_disable,
> >> +};
> >> +
> >> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
> >> +{
> >> + struct mipi_dsi_device *dsi = bl_get_data(bl);
> >> + u16 brightness = backlight_get_brightness(bl);
> >> +
> >> + return mipi_dsi_dcs_set_display_brightness(dsi,
> >> cpu_to_le16(brightness));
> >
> > mipi_dsi_dcs_set_display_brightness() already converts the brightness,
> > so you don't need cpu_to_le16 here.
>
> Acked.
>
> Thanks,
>
> Jessica Zhang
>
> >
> >> +}
> >> +
> >> +static const struct backlight_ops visionox_r66451_bl_ops = {
> >> + .update_status = visionox_r66451_bl_update_status,
> >> +};
> >> +
> >> +static struct backlight_device *
> >> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
> >> +{
> >> + struct device *dev = &dsi->dev;
> >> + const struct backlight_properties props = {
> >> + .type = BACKLIGHT_RAW,
> >> + .brightness = 255,
> >> + .max_brightness = 4095,
> >> + };
> >> +
> >> + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
> >> + &visionox_r66451_bl_ops, &props);
> >> +}
> >> +
> >> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
> >> +{
> >> + struct device *dev = &dsi->dev;
> >> + struct visionox_r66451 *ctx;
> >> + struct drm_dsc_config *dsc;
> >> + int ret = 0;
> >> +
> >> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> >> + if (!ctx)
> >> + return -ENOMEM;
> >> +
> >> + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
> >> + if (!dsc)
> >> + return -ENOMEM;
> >> +
> >> + /* Set DSC params */
> >> + dsc->dsc_version_major = 0x1;
> >> + dsc->dsc_version_minor = 0x2;
> >> +
> >> + dsc->slice_height = 20;
> >> + dsc->slice_width = 540;
> >> + dsc->slice_count = 2;
> >> + dsc->bits_per_component = 8;
> >> + dsc->bits_per_pixel = 0x8 << 4;
> >> + dsc->block_pred_enable = true;
> >> +
> >> + dsi->dsc = dsc;
> >> +
> >> + ctx->supplies[0].supply = "vddio";
> >> + ctx->supplies[1].supply = "vdd";
> >> +
> >> + ret = devm_regulator_bulk_get(&dsi->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))
> >> + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed
> >> to get reset-gpios\n");
> >> +
> >> + ctx->dsi = dsi;
> >> + mipi_dsi_set_drvdata(dsi, ctx);
> >> +
> >> + dsi->lanes = 4;
> >> + dsi->format = MIPI_DSI_FMT_RGB888;
> >> + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
> >> +
> >> + drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs,
> >> DRM_MODE_CONNECTOR_DSI);
> >> + ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
> >> + if (IS_ERR(ctx->panel.backlight))
> >> + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
> >> + "Failed to create backlight\n");
> >> +
> >> + drm_panel_add(&ctx->panel);
> >> +
> >> + ret = mipi_dsi_attach(dsi);
> >> + if (ret < 0) {
> >> + dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
> >> + drm_panel_remove(&ctx->panel);
> >> + }
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
> >> +{
> >> + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
> >> + int ret;
> >> +
> >> + ret = mipi_dsi_detach(dsi);
> >> + if (ret < 0)
> >> + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
> >> +
> >> + drm_panel_remove(&ctx->panel);
> >> +}
> >> +
> >> +static const struct of_device_id visionox_r66451_of_match[] = {
> >> + {.compatible = "visionox,r66451"},
> >> + { /*sentinel*/ }
> >> +};
> >> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
> >> +
> >> +static struct mipi_dsi_driver visionox_r66451_driver = {
> >> + .probe = visionox_r66451_probe,
> >> + .remove = visionox_r66451_remove,
> >> + .driver = {
> >> + .name = "panel-visionox-r66451",
> >> + .of_match_table = visionox_r66451_of_match,
> >> + },
> >> +};
> >> +
> >> +module_mipi_dsi_driver(visionox_r66451_driver);
> >> +
> >> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
> >> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
> >> panel");
> >> +MODULE_LICENSE("GPL");
> >>
> >
> > --
> > With best wishes
> > Dmitry
> >



--
With best wishes
Dmitry

2023-05-19 00:32:27

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/18/2023 3:37 PM, Dmitry Baryshkov wrote:
> On Fri, 19 May 2023 at 01:17, Jessica Zhang <[email protected]> wrote:
>>
>>
>>
>> On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
>>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>>
>>>> The panel enables display compression (DSC v1.2) by default.
>>>>
>>>> Signed-off-by: Jessica Zhang <[email protected]>
>>>> ---
>>>> drivers/gpu/drm/panel/Kconfig | 8 +
>>>> drivers/gpu/drm/panel/Makefile | 1 +
>>>> drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>>>> ++++++++++++++++++++++++++
>>>> 3 files changed, 404 insertions(+)
>>>>
>>>> diff --git a/drivers/gpu/drm/panel/Kconfig
>>>> b/drivers/gpu/drm/panel/Kconfig
>>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>>> --- a/drivers/gpu/drm/panel/Kconfig
>>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>>> Say Y here if you want to enable support for Visionox
>>>> VTDR6130 1080x2400 AMOLED DSI panel.
>>>> +config DRM_PANEL_VISIONOX_R66451
>>>> + tristate "Visionox R66451"
>>>> + depends on OF
>>>> + depends on DRM_MIPI_DSI
>>>> + help
>>>> + Say Y here if you want to enable support for Visionox
>>>> + R66451 1080x2340 AMOLED DSI panel.
>>>> +
>>>> config DRM_PANEL_WIDECHIPS_WS2401
>>>> tristate "Widechips WS2401 DPI panel driver"
>>>> depends on SPI && GPIOLIB
>>>> diff --git a/drivers/gpu/drm/panel/Makefile
>>>> b/drivers/gpu/drm/panel/Makefile
>>>> index b3e8ba29edd3..e043a92ee676 100644
>>>> --- a/drivers/gpu/drm/panel/Makefile
>>>> +++ b/drivers/gpu/drm/panel/Makefile
>>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>>>> panel-tpo-tpg110.o
>>>> obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>>> obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>>> obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>>> obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>>> obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>>>> panel-xinpeng-xpp055c272.o
>>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> new file mode 100644
>>>> index 000000000000..e3648ead3e84
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> @@ -0,0 +1,395 @@
>>>> +//SPDX-License-Identifier: GPL-2.0-only
>>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>>>> reserved.
>>>> +
>>>> +#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 <drm/drm_mipi_dsi.h>
>>>> +#include <drm/drm_modes.h>
>>>> +#include <drm/drm_panel.h>
>>>> +#include <drm/display/drm_dsc.h>
>>>> +#include <drm/display/drm_dsc_helper.h>
>>>> +
>>>> +#include <video/mipi_display.h>
>>>> +
>>>> +struct visionox_r66451 {
>>>> + struct drm_panel panel;
>>>> + struct mipi_dsi_device *dsi;
>>>> + struct gpio_desc *reset_gpio;
>>>> + struct regulator_bulk_data supplies[2];
>>>> + bool prepared, enabled;
>>>> +};
>>>> +
>>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>>>> drm_panel *panel)
>>>> +{
>>>> + return container_of(panel, struct visionox_r66451, panel);
>>>> +}
>>>> +
>>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>>> +{
>>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>> + usleep_range(10000, 10100);
>>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> + usleep_range(10000, 10100);
>>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>> + usleep_range(10000, 10100);
>>>> +}
>>>> +
>>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>>> +{
>>>> + struct mipi_dsi_device *dsi = ctx->dsi;
>>>> + struct device *dev = &dsi->dev;
>>>> + int ret;
>>>> +
>>>> + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>>> +
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>>> + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
>>>> + 0x09, 0x3c);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>> + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>>>> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>>>> + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xde,
>>>> + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
>>>> + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>>>> 0x00, 0x00);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>>> + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
>>>> + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
>>>> + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>>> + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
>>>> + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
>>>> + 0x3c, 0x9c);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>> + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>>>> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>>>> + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
>>>> + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
>>>> + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>>> + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
>>>> + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
>>>> + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
>>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
>>>> 0x39, 0x04, 0x09, 0x34);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>>> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>>> + mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>>> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>>>> 0x00, 0x04, 0x37);
>>>
>>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>>
>> Hi Dmitry,
>>
>> Acked.
>>
>>>
>>>> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>>>> 0x00, 0x09, 0x23);
>>>
>>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>>
>> Acked.
>>
>>>
>>>> +
>>>> + ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> + msleep(120);
>>>> +
>>>> + ret = mipi_dsi_dcs_set_display_on(dsi);
>>>
>>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
>>> a a part of _enable()? Correspondingly _off should IMO be a part of
>>> _disable callback().
>>
>> Acked. Looking at the downstream code, it seems that all the dcs on
>> commands can be sent within _enable() (and similarly for the off
>> commands with _disable()).
>
> I wouldn't check against the downstream. The distinction is that
> prepare() is called before enabling the video stream and as such it
> usually contains commands to set up the panel parameters. The enable()
> callback is called after starting the video stream, so it should just
> turn on the image output, the backlight, etc..

Hi Dmitry,

Got it.

>
>>
>>>
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed on set display on: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> + msleep(20);
>>>
>>>
>>> Do we need to add here the following line?
>>>
>>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>
>> Do you mean within the return on error block?
>
> Within the return. I think this might depend on the panel itself, see
> how other panels handle toggling MIPI_DSI_MODE_LPM. E.g. panel-dsi-cm
> or panel-novatek-nt35950 unsets MIPI_DSI_MODE_LPM at the end of
> power_on. Some of the panels also toggle LPM while reading or updating
> the backlight.

Understood. FWIW, I'm seeing that there are others like
panel-visionox-vtdr6130.c that don't unset MODE_LPM after sending the
DCS commands.

I'll double check what we should be doing for r66451.

Thanks,

Jessica Zhang

>
>>
>> From my understanding, we should be setting the MIPI_DSI_MODE_LPM for
>> panel_on() then unsetting MODE_LPM in panel_off().
>>
>>>
>>>
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>>> +{
>>>> + struct mipi_dsi_device *dsi = ctx->dsi;
>>>> + struct device *dev = &dsi->dev;
>>>> + int ret;
>>>> +
>>>> + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>> +
>>>> + ret = mipi_dsi_dcs_set_display_off(dsi);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to set display off: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> + msleep(20);
>>>> +
>>>> + ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> + msleep(120);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>>> +{
>>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> + struct device *dev = &ctx->dsi->dev;
>>>> + int ret;
>>>> +
>>>> + if (ctx->prepared)
>>>> + return 0;
>>>> +
>>>> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>>> + ctx->supplies);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + visionox_r66451_reset(ctx);
>>>> +
>>>> + ret = visionox_r66451_on(ctx);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>>>> ctx->supplies);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + ctx->prepared = true;
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>>> +{
>>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> + struct device *dev = &ctx->dsi->dev;
>>>> + int ret;
>>>> +
>>>> + if (!ctx->prepared)
>>>> + return 0;
>>>> +
>>>> + ret = visionox_r66451_off(ctx);
>>>> + if (ret < 0)
>>>> + dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>>> +
>>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>>> +
>>>> + ctx->prepared = false;
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>>> + .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>>> + .hdisplay = 1080,
>>>> + .hsync_start = 1080 + 95,
>>>> + .hsync_end = 1080 + 95 + 1,
>>>> + .htotal = 1080 + 95 + 1 + 40,
>>>> + .vdisplay = 2340,
>>>> + .vsync_start = 2340 + 25,
>>>> + .vsync_end = 2340 + 25 + 1,
>>>> + .vtotal = 2340 + 25 + 1 + 4,
>>>> + .width_mm = 0,
>>>> + .height_mm = 0,
>>>
>>> Please provide real values here.
>>
>> Acked.
>>
>>>
>>>> +};
>>>> +
>>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>>> +{
>>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> + struct mipi_dsi_device *dsi = ctx->dsi;
>>>> + struct drm_dsc_picture_parameter_set pps;
>>>> + int ret;
>>>> +
>>>> + if (ctx->enabled)
>>>> + return 0;
>>>> +
>>>> + if (!dsi->dsc) {
>>>> + dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>>> + return -ENODEV;
>>>> + }
>>>> +
>>>> + drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>>> + ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>>>> +
>>>> + ctx->enabled = true;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>>> +{
>>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +
>>>> + ctx->enabled = false;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>>> + struct drm_connector *connector)
>>>> +{
>>>> + struct drm_display_mode *mode;
>>>> +
>>>> + mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>>> + if (!mode)
>>>> + return -ENOMEM;
>>>> +
>>>> + drm_mode_set_name(mode);
>>>> +
>>>> + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>>> + connector->display_info.width_mm = mode->width_mm;
>>>> + connector->display_info.height_mm = mode->height_mm;
>>>> + drm_mode_probed_add(connector, mode);
>>>
>>> Can we use drm_connector_helper_get_modes_fixed() instead?
>>
>> Acked.
>>
>>>
>>>> +
>>>> + return 1;
>>>> +}
>>>> +
>>>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>>>> + .prepare = visionox_r66451_prepare,
>>>> + .unprepare = visionox_r66451_unprepare,
>>>> + .get_modes = visionox_r66451_get_modes,
>>>> + .enable = visionox_r66451_enable,
>>>> + .disable = visionox_r66451_disable,
>>>> +};
>>>> +
>>>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>>>> +{
>>>> + struct mipi_dsi_device *dsi = bl_get_data(bl);
>>>> + u16 brightness = backlight_get_brightness(bl);
>>>> +
>>>> + return mipi_dsi_dcs_set_display_brightness(dsi,
>>>> cpu_to_le16(brightness));
>>>
>>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>>> so you don't need cpu_to_le16 here.
>>
>> Acked.
>>
>> Thanks,
>>
>> Jessica Zhang
>>
>>>
>>>> +}
>>>> +
>>>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>>>> + .update_status = visionox_r66451_bl_update_status,
>>>> +};
>>>> +
>>>> +static struct backlight_device *
>>>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>>>> +{
>>>> + struct device *dev = &dsi->dev;
>>>> + const struct backlight_properties props = {
>>>> + .type = BACKLIGHT_RAW,
>>>> + .brightness = 255,
>>>> + .max_brightness = 4095,
>>>> + };
>>>> +
>>>> + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>>>> + &visionox_r66451_bl_ops, &props);
>>>> +}
>>>> +
>>>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>>>> +{
>>>> + struct device *dev = &dsi->dev;
>>>> + struct visionox_r66451 *ctx;
>>>> + struct drm_dsc_config *dsc;
>>>> + int ret = 0;
>>>> +
>>>> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>>>> + if (!ctx)
>>>> + return -ENOMEM;
>>>> +
>>>> + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>>>> + if (!dsc)
>>>> + return -ENOMEM;
>>>> +
>>>> + /* Set DSC params */
>>>> + dsc->dsc_version_major = 0x1;
>>>> + dsc->dsc_version_minor = 0x2;
>>>> +
>>>> + dsc->slice_height = 20;
>>>> + dsc->slice_width = 540;
>>>> + dsc->slice_count = 2;
>>>> + dsc->bits_per_component = 8;
>>>> + dsc->bits_per_pixel = 0x8 << 4;
>>>> + dsc->block_pred_enable = true;
>>>> +
>>>> + dsi->dsc = dsc;
>>>> +
>>>> + ctx->supplies[0].supply = "vddio";
>>>> + ctx->supplies[1].supply = "vdd";
>>>> +
>>>> + ret = devm_regulator_bulk_get(&dsi->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))
>>>> + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed
>>>> to get reset-gpios\n");
>>>> +
>>>> + ctx->dsi = dsi;
>>>> + mipi_dsi_set_drvdata(dsi, ctx);
>>>> +
>>>> + dsi->lanes = 4;
>>>> + dsi->format = MIPI_DSI_FMT_RGB888;
>>>> + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
>>>> +
>>>> + drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs,
>>>> DRM_MODE_CONNECTOR_DSI);
>>>> + ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>>>> + if (IS_ERR(ctx->panel.backlight))
>>>> + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>>>> + "Failed to create backlight\n");
>>>> +
>>>> + drm_panel_add(&ctx->panel);
>>>> +
>>>> + ret = mipi_dsi_attach(dsi);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>>>> + drm_panel_remove(&ctx->panel);
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>>>> +{
>>>> + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>>>> + int ret;
>>>> +
>>>> + ret = mipi_dsi_detach(dsi);
>>>> + if (ret < 0)
>>>> + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>>>> +
>>>> + drm_panel_remove(&ctx->panel);
>>>> +}
>>>> +
>>>> +static const struct of_device_id visionox_r66451_of_match[] = {
>>>> + {.compatible = "visionox,r66451"},
>>>> + { /*sentinel*/ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>>>> +
>>>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>>>> + .probe = visionox_r66451_probe,
>>>> + .remove = visionox_r66451_remove,
>>>> + .driver = {
>>>> + .name = "panel-visionox-r66451",
>>>> + .of_match_table = visionox_r66451_of_match,
>>>> + },
>>>> +};
>>>> +
>>>> +module_mipi_dsi_driver(visionox_r66451_driver);
>>>> +
>>>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
>>>> panel");
>>>> +MODULE_LICENSE("GPL");
>>>>
>>>
>>> --
>>> With best wishes
>>> Dmitry
>>>
>
>
>
> --
> With best wishes
> Dmitry

2023-05-21 11:46:19

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

On 2023-05-16 13:20:30, Jessica Zhang wrote:
> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
>
> Signed-off-by: Jessica Zhang <[email protected]>
> ---
> .../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
> 1 file changed, 59 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
> new file mode 100644
> index 000000000000..6ba323683921
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
> @@ -0,0 +1,59 @@
> +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/panel/visionox,r66451.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Visionox R66451 AMOLED DSI Panel
> +
> +maintainers:
> + - Jessica Zhang <[email protected]>
> +
> +allOf:
> + - $ref: panel-common.yaml#
> +
> +properties:
> + compatible:
> + const: visionox,r66451
> +
> + reg:
> + maxItems: 1
> + description: DSI virtual channel
> +
> + vddio-supply: true
> + vdd-supply: true
> + port: true
> + reset-gpios: true

Normally for cmd-mode panels there is also a `disp-te` pin which is
optionally registered in dsi_host.c as GPIOD_IN, but on **ALL** my Sony
phones this breaks vsync (as in: mdp5 stops receiving the interrupt, but
we can see disp-te in /proc/interrupts then).

- Marijn

> +additionalProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - vddio-supply
> + - vdd-supply
> + - reset-gpios
> + - port
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + dsi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + panel@0 {
> + compatible = "visionox,r66451";
> + reg = <0>;
> + vddio-supply = <&vreg_l12c_1p8>;
> + vdd-supply = <&vreg_l13c_3p0>;
> +
> + reset-gpios = <&tlmm 24 GPIO_ACTIVE_LOW>;
> +
> + port {
> + panel0_in: endpoint {
> + remote-endpoint = <&dsi0_out>;
> + };
> + };
> + };
> + };
> +...
>
> --
> 2.40.1
>

2023-05-21 12:10:16

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 2023-05-18 03:19:49, Dmitry Baryshkov wrote:
> On 16/05/2023 23:20, Jessica Zhang wrote:
> > Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
> > comes with the Qualcomm HDK8350 display expansion pack.
> >
> > The panel enables display compression (DSC v1.2) by default.
> >
> > Signed-off-by: Jessica Zhang <[email protected]>
> > ---
> > drivers/gpu/drm/panel/Kconfig | 8 +
> > drivers/gpu/drm/panel/Makefile | 1 +
> > drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
> > 3 files changed, 404 insertions(+)
> >
> > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > index 29cf5fa39ff2..9c2c36dbddf3 100644
> > --- a/drivers/gpu/drm/panel/Kconfig
> > +++ b/drivers/gpu/drm/panel/Kconfig
> > @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
> > Say Y here if you want to enable support for Visionox
> > VTDR6130 1080x2400 AMOLED DSI panel.
> >
> > +config DRM_PANEL_VISIONOX_R66451
> > + tristate "Visionox R66451"
> > + depends on OF
> > + depends on DRM_MIPI_DSI
> > + help
> > + Say Y here if you want to enable support for Visionox
> > + R66451 1080x2340 AMOLED DSI panel.
> > +
> > config DRM_PANEL_WIDECHIPS_WS2401
> > tristate "Widechips WS2401 DPI panel driver"
> > depends on SPI && GPIOLIB
> > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > index b3e8ba29edd3..e043a92ee676 100644
> > --- a/drivers/gpu/drm/panel/Makefile
> > +++ b/drivers/gpu/drm/panel/Makefile
> > @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
> > obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
> > obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
> > obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
> > +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
> > obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
> > obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
> > diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> > new file mode 100644
> > index 000000000000..e3648ead3e84
> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> > @@ -0,0 +1,395 @@
> > +//SPDX-License-Identifier: GPL-2.0-only
> > +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
> > +
> > +#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 <drm/drm_mipi_dsi.h>
> > +#include <drm/drm_modes.h>
> > +#include <drm/drm_panel.h>
> > +#include <drm/display/drm_dsc.h>
> > +#include <drm/display/drm_dsc_helper.h>
> > +
> > +#include <video/mipi_display.h>
> > +
> > +struct visionox_r66451 {
> > + struct drm_panel panel;
> > + struct mipi_dsi_device *dsi;
> > + struct gpio_desc *reset_gpio;
> > + struct regulator_bulk_data supplies[2];
> > + bool prepared, enabled;
> > +};
> > +
> > +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
> > +{
> > + return container_of(panel, struct visionox_r66451, panel);
> > +}
> > +
> > +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
> > +{
> > + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> > + usleep_range(10000, 10100);
> > + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> > + usleep_range(10000, 10100);
> > + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> > + usleep_range(10000, 10100);
> > +}
> > +
> > +static int visionox_r66451_on(struct visionox_r66451 *ctx)
> > +{
> > + struct mipi_dsi_device *dsi = ctx->dsi;
> > + struct device *dev = &dsi->dev;
> > + int ret;
> > +
> > + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
> > +
> > + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> > + mipi_dsi_dcs_write_seq(dsi, 0xc2,
> > + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
> > + 0x09, 0x3c);
> > + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> > + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> > + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> > + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> > + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> > + mipi_dsi_dcs_write_seq(dsi, 0xde,
> > + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
> > + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
> > + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> > + mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
> > + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
> > + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> > + mipi_dsi_dcs_write_seq(dsi, 0xc4,
> > + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> > + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
> > + mipi_dsi_dcs_write_seq(dsi, 0xcf,
> > + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
> > + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
> > + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
> > + mipi_dsi_dcs_write_seq(dsi, 0xd3,
> > + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
> > + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
> > + 0x3c, 0x9c);
> > + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> > + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> > + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> > + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> > + mipi_dsi_dcs_write_seq(dsi, 0xd8,
> > + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> > + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
> > + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> > + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
> > + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
> > + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
> > + mipi_dsi_dcs_write_seq(dsi, 0xdf,
> > + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
> > + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> > + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
> > + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
> > + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
> > + mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
> > + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> > + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
> > + mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
> > + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> > + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
> > + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
> > + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
> > + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
> > + mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
> > + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
> > + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
> > + mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> > + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);
>
> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>
> > + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);
>
> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);

Yes please, thanks for already pointing these two out.

>
> > +
> > + ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
> > + return ret;
> > + }
> > + msleep(120);
> > +
> > + ret = mipi_dsi_dcs_set_display_on(dsi);
>
> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
> a a part of _enable()? Correspondingly _off should IMO be a part of
> _disable callback().
>
> > + if (ret < 0) {
> > + dev_err(dev, "Failed on set display on: %d\n", ret);
> > + return ret;
> > + }
> > + msleep(20);
>
>
> Do we need to add here the following line?
>
> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>
>
> > +
> > + return 0;
> > +}
> > +
> > +static int visionox_r66451_off(struct visionox_r66451 *ctx)
> > +{
> > + struct mipi_dsi_device *dsi = ctx->dsi;
> > + struct device *dev = &dsi->dev;
> > + int ret;
> > +
> > + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
> > +
> > + ret = mipi_dsi_dcs_set_display_off(dsi);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to set display off: %d\n", ret);
> > + return ret;
> > + }
> > + msleep(20);
> > +
> > + ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
> > + return ret;
> > + }
> > + msleep(120);
> > +
> > + return 0;
> > +}
> > +
> > +static int visionox_r66451_prepare(struct drm_panel *panel)
> > +{
> > + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> > + struct device *dev = &ctx->dsi->dev;
> > + int ret;
> > +
> > + if (ctx->prepared)
> > + return 0;
> > +
> > + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
> > + ctx->supplies);
> > + if (ret < 0)
> > + return ret;
> > +
> > + visionox_r66451_reset(ctx);
> > +
> > + ret = visionox_r66451_on(ctx);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to initialize panel: %d\n", ret);
> > + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> > + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > + return ret;
> > + }
> > +
> > + ctx->prepared = true;
> > + return 0;
> > +}
> > +
> > +static int visionox_r66451_unprepare(struct drm_panel *panel)
> > +{
> > + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> > + struct device *dev = &ctx->dsi->dev;
> > + int ret;
> > +
> > + if (!ctx->prepared)
> > + return 0;
> > +
> > + ret = visionox_r66451_off(ctx);
> > + if (ret < 0)
> > + dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
> > +
> > + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> > + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> > +
> > + ctx->prepared = false;
> > + return 0;
> > +}
> > +
> > +static const struct drm_display_mode visionox_r66451_mode = {
> > + .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
> > + .hdisplay = 1080,
> > + .hsync_start = 1080 + 95,
> > + .hsync_end = 1080 + 95 + 1,
> > + .htotal = 1080 + 95 + 1 + 40,
> > + .vdisplay = 2340,
> > + .vsync_start = 2340 + 25,
> > + .vsync_end = 2340 + 25 + 1,
> > + .vtotal = 2340 + 25 + 1 + 4,
> > + .width_mm = 0,
> > + .height_mm = 0,
>
> Please provide real values here.
>
> > +};
> > +
> > +static int visionox_r66451_enable(struct drm_panel *panel)
> > +{
> > + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> > + struct mipi_dsi_device *dsi = ctx->dsi;
> > + struct drm_dsc_picture_parameter_set pps;
> > + int ret;
> > +
> > + if (ctx->enabled)
> > + return 0;
> > +
> > + if (!dsi->dsc) {
> > + dev_err(&dsi->dev, "DSC not attached to DSI\n");
> > + return -ENODEV;
> > + }
> > +
> > + drm_dsc_pps_payload_pack(&pps, dsi->dsc);
> > + ret = mipi_dsi_picture_parameter_set(dsi, &pps);

This `ret` value is unused.

Do we also need to call mipi_dsi_compression_mode() to enable
compression? This also selects PPS table 1 (which is supposedly the
default reset value, and the PPS that is uploaded by
mipi_dsi_picture_parameter_set()). Note that it does set the algorithm
to "VESA DSC Standard 1.1" but i am unsure if it makes a difference
(docs for mipi_dsi_picture_parameter_set() say that it is "1.1" as well)

> > +
> > + ctx->enabled = true;
> > +
> > + return 0;
> > +}
> > +
> > +static int visionox_r66451_disable(struct drm_panel *panel)
> > +{
> > + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> > +
> > + ctx->enabled = false;
> > +
> > + return 0;
> > +}
> > +
> > +static int visionox_r66451_get_modes(struct drm_panel *panel,
> > + struct drm_connector *connector)
> > +{
> > + struct drm_display_mode *mode;
> > +
> > + mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
> > + if (!mode)
> > + return -ENOMEM;
> > +
> > + drm_mode_set_name(mode);
> > +
> > + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> > + connector->display_info.width_mm = mode->width_mm;
> > + connector->display_info.height_mm = mode->height_mm;
> > + drm_mode_probed_add(connector, mode);
>
> Can we use drm_connector_helper_get_modes_fixed() instead?

This does not set DRM_MODE_TYPE_DRIVER though; is that preferred?

> > +
> > + return 1;
> > +}
> > +
> > +static const struct drm_panel_funcs visionox_r66451_funcs = {
> > + .prepare = visionox_r66451_prepare,
> > + .unprepare = visionox_r66451_unprepare,
> > + .get_modes = visionox_r66451_get_modes,
> > + .enable = visionox_r66451_enable,
> > + .disable = visionox_r66451_disable,
> > +};
> > +
> > +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
> > +{
> > + struct mipi_dsi_device *dsi = bl_get_data(bl);
> > + u16 brightness = backlight_get_brightness(bl);
> > +
> > + return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
>
> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
> so you don't need cpu_to_le16 here.

Tread carefully here: we've had the same issue and conversation on our
Sony panels where this extra inversion is required.
set_display_brightness() sends the bytes as little-endian to the panel
(and it even assumes little-endian in get_display_brightness()) but the
spec for 16-bit brightness values states that they have to be sent in
big-endian. This is why c9d27c6be518b ("drm/mipi-dsi: Fix byte order of
16-bit DCS set/get brightness") added
mipi_dsi_dcs_set_display_brightness_large().

Jessica, if you need to have the endian swap here (should be very easy
to test with a real panel, but it should be given the max_brightness
value being over 8 bits) please switch to the _large() variant.

> > +}
> > +
> > +static const struct backlight_ops visionox_r66451_bl_ops = {
> > + .update_status = visionox_r66451_bl_update_status,

Should this implement get_brightness?

> > +};
> > +
> > +static struct backlight_device *
> > +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
> > +{
> > + struct device *dev = &dsi->dev;
> > + const struct backlight_properties props = {
> > + .type = BACKLIGHT_RAW,
> > + .brightness = 255,
> > + .max_brightness = 4095,
> > + };
> > +
> > + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
> > + &visionox_r66451_bl_ops, &props);
> > +}
> > +
> > +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
> > +{
> > + struct device *dev = &dsi->dev;
> > + struct visionox_r66451 *ctx;
> > + struct drm_dsc_config *dsc;
> > + int ret = 0;
> > +
> > + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> > + if (!ctx)
> > + return -ENOMEM;
> > +
> > + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
> > + if (!dsc)
> > + return -ENOMEM;
> > +
> > + /* Set DSC params */
> > + dsc->dsc_version_major = 0x1;
> > + dsc->dsc_version_minor = 0x2;
> > +
> > + dsc->slice_height = 20;
> > + dsc->slice_width = 540;
> > + dsc->slice_count = 2;
> > + dsc->bits_per_component = 8;
> > + dsc->bits_per_pixel = 0x8 << 4;

What's the 0x for? bpc is also just "8" and not 0x8.

- Marijn

> > + dsc->block_pred_enable = true;
> > +
> > + dsi->dsc = dsc;
> > +
> > + ctx->supplies[0].supply = "vddio";
> > + ctx->supplies[1].supply = "vdd";
> > +
> > + ret = devm_regulator_bulk_get(&dsi->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))
> > + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
> > +
> > + ctx->dsi = dsi;
> > + mipi_dsi_set_drvdata(dsi, ctx);
> > +
> > + dsi->lanes = 4;
> > + dsi->format = MIPI_DSI_FMT_RGB888;
> > + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
> > +
> > + drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
> > + ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
> > + if (IS_ERR(ctx->panel.backlight))
> > + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
> > + "Failed to create backlight\n");
> > +
> > + drm_panel_add(&ctx->panel);
> > +
> > + ret = mipi_dsi_attach(dsi);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
> > + drm_panel_remove(&ctx->panel);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
> > +{
> > + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
> > + int ret;
> > +
> > + ret = mipi_dsi_detach(dsi);
> > + if (ret < 0)
> > + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
> > +
> > + drm_panel_remove(&ctx->panel);
> > +}
> > +
> > +static const struct of_device_id visionox_r66451_of_match[] = {
> > + {.compatible = "visionox,r66451"},
> > + { /*sentinel*/ }
> > +};
> > +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
> > +
> > +static struct mipi_dsi_driver visionox_r66451_driver = {
> > + .probe = visionox_r66451_probe,
> > + .remove = visionox_r66451_remove,
> > + .driver = {
> > + .name = "panel-visionox-r66451",
> > + .of_match_table = visionox_r66451_of_match,
> > + },
> > +};
> > +
> > +module_mipi_dsi_driver(visionox_r66451_driver);
> > +
> > +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
> > +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
> > +MODULE_LICENSE("GPL");
> >
>
> --
> With best wishes
> Dmitry
>

2023-05-22 09:22:33

by Neil Armstrong

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

On 21/05/2023 12:30, Marijn Suijten wrote:
> On 2023-05-16 13:20:30, Jessica Zhang wrote:
>> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
>>
>> Signed-off-by: Jessica Zhang <[email protected]>
>> ---
>> .../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
>> 1 file changed, 59 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
>> new file mode 100644
>> index 000000000000..6ba323683921
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
>> @@ -0,0 +1,59 @@
>> +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/display/panel/visionox,r66451.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Visionox R66451 AMOLED DSI Panel
>> +
>> +maintainers:
>> + - Jessica Zhang <[email protected]>
>> +
>> +allOf:
>> + - $ref: panel-common.yaml#
>> +
>> +properties:
>> + compatible:
>> + const: visionox,r66451
>> +
>> + reg:
>> + maxItems: 1
>> + description: DSI virtual channel
>> +
>> + vddio-supply: true
>> + vdd-supply: true
>> + port: true
>> + reset-gpios: true
>
> Normally for cmd-mode panels there is also a `disp-te` pin which is
> optionally registered in dsi_host.c as GPIOD_IN, but on **ALL** my Sony
> phones this breaks vsync (as in: mdp5 stops receiving the interrupt, but
> we can see disp-te in /proc/interrupts then).

Describing it as a gpio is wrong, it should be described as a pinctrl state instead.

Neil

>
> - Marijn
>
>> +additionalProperties: false
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - vddio-supply
>> + - vdd-supply
>> + - reset-gpios
>> + - port
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/gpio/gpio.h>
>> + dsi {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + panel@0 {
>> + compatible = "visionox,r66451";
>> + reg = <0>;
>> + vddio-supply = <&vreg_l12c_1p8>;
>> + vdd-supply = <&vreg_l13c_3p0>;
>> +
>> + reset-gpios = <&tlmm 24 GPIO_ACTIVE_LOW>;
>> +
>> + port {
>> + panel0_in: endpoint {
>> + remote-endpoint = <&dsi0_out>;
>> + };
>> + };
>> + };
>> + };
>> +...
>>
>> --
>> 2.40.1
>>


2023-05-22 09:23:13

by Neil Armstrong

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 18/05/2023 02:19, Dmitry Baryshkov wrote:
> On 16/05/2023 23:20, Jessica Zhang wrote:
>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>> comes with the Qualcomm HDK8350 display expansion pack.
>>
>> The panel enables display compression (DSC v1.2) by default.
>>
>> Signed-off-by: Jessica Zhang <[email protected]>
>> ---
>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
>>   3 files changed, 404 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>> --- a/drivers/gpu/drm/panel/Kconfig
>> +++ b/drivers/gpu/drm/panel/Kconfig
>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>         Say Y here if you want to enable support for Visionox
>>         VTDR6130 1080x2400 AMOLED DSI panel.
>> +config DRM_PANEL_VISIONOX_R66451
>> +    tristate "Visionox R66451"
>> +    depends on OF
>> +    depends on DRM_MIPI_DSI
>> +    help
>> +      Say Y here if you want to enable support for Visionox
>> +      R66451 1080x2340 AMOLED DSI panel.
>> +
>>   config DRM_PANEL_WIDECHIPS_WS2401
>>       tristate "Widechips WS2401 DPI panel driver"
>>       depends on SPI && GPIOLIB
>> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
>> index b3e8ba29edd3..e043a92ee676 100644
>> --- a/drivers/gpu/drm/panel/Makefile
>> +++ b/drivers/gpu/drm/panel/Makefile
>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
>>   obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>   obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>   obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> new file mode 100644
>> index 000000000000..e3648ead3e84
>> --- /dev/null
>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> @@ -0,0 +1,395 @@
>> +//SPDX-License-Identifier: GPL-2.0-only
>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
>> +
>> +#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 <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_modes.h>
>> +#include <drm/drm_panel.h>
>> +#include <drm/display/drm_dsc.h>
>> +#include <drm/display/drm_dsc_helper.h>
>> +
>> +#include <video/mipi_display.h>
>> +
>> +struct visionox_r66451 {
>> +    struct drm_panel panel;
>> +    struct mipi_dsi_device *dsi;
>> +    struct gpio_desc *reset_gpio;
>> +    struct regulator_bulk_data supplies[2];
>> +    bool prepared, enabled;
>> +};
>> +
>> +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
>> +{
>> +    return container_of(panel, struct visionox_r66451, panel);
>> +}
>> +
>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>> +{
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>> +    usleep_range(10000, 10100);
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +    usleep_range(10000, 10100);
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>> +    usleep_range(10000, 10100);
>> +}
>> +
>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>> +{
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct device *dev = &dsi->dev;
>> +    int ret;
>> +
>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>> +
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
>> +                   0x09, 0x3c);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
>> +                   0x3c, 0x9c);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);
>
> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>
>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);
>
> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>
>> +
>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(120);
>> +
>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>
> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be a a part of _enable()? Correspondingly _off should IMO be a part of _disable callback().

Nop, mipi_dsi_dcs_set_display_on() should be part of prepare, to be called *before* the video stream starts, enable is called after in the atomic modeset chain,

>
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(20);
>
>
> Do we need to add here the following line?
>
> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>

No, it will be changed by the DSI core.

>
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>> +{
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct device *dev = &dsi->dev;
>> +    int ret;
>> +
>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>> +
>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(20);
>> +
>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(120);
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct device *dev = &ctx->dsi->dev;
>> +    int ret;
>> +
>> +    if (ctx->prepared)
>> +        return 0;
>> +
>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>> +                    ctx->supplies);
>> +    if (ret < 0)
>> +        return ret;
>> +
>> +    visionox_r66451_reset(ctx);
>> +
>> +    ret = visionox_r66451_on(ctx);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>> +        return ret;
>> +    }
>> +
>> +    ctx->prepared = true;
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct device *dev = &ctx->dsi->dev;
>> +    int ret;
>> +
>> +    if (!ctx->prepared)
>> +        return 0;
>> +
>> +    ret = visionox_r66451_off(ctx);
>> +    if (ret < 0)
>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>> +
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>> +
>> +    ctx->prepared = false;
>> +    return 0;
>> +}
>> +
>> +static const struct drm_display_mode visionox_r66451_mode = {
>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>> +    .hdisplay = 1080,
>> +    .hsync_start = 1080 + 95,
>> +    .hsync_end = 1080 + 95 + 1,
>> +    .htotal = 1080 + 95 + 1 + 40,
>> +    .vdisplay = 2340,
>> +    .vsync_start = 2340 + 25,
>> +    .vsync_end = 2340 + 25 + 1,
>> +    .vtotal = 2340 + 25 + 1 + 4,
>> +    .width_mm = 0,
>> +    .height_mm = 0,
>
> Please provide real values here.
>
>> +};
>> +
>> +static int visionox_r66451_enable(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct drm_dsc_picture_parameter_set pps;
>> +    int ret;
>> +
>> +    if (ctx->enabled)
>> +        return 0;
>> +
>> +    if (!dsi->dsc) {
>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>> +        return -ENODEV;
>> +    }
>> +
>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>> +
>> +    ctx->enabled = true;
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_disable(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +
>> +    ctx->enabled = false;
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>> +                    struct drm_connector *connector)
>> +{
>> +    struct drm_display_mode *mode;
>> +
>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>> +    if (!mode)
>> +        return -ENOMEM;
>> +
>> +    drm_mode_set_name(mode);
>> +
>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>> +    connector->display_info.width_mm = mode->width_mm;
>> +    connector->display_info.height_mm = mode->height_mm;
>> +    drm_mode_probed_add(connector, mode);
>
> Can we use drm_connector_helper_get_modes_fixed() instead?
>
>> +
>> +    return 1;
>> +}
>> +
>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>> +    .prepare = visionox_r66451_prepare,
>> +    .unprepare = visionox_r66451_unprepare,
>> +    .get_modes = visionox_r66451_get_modes,
>> +    .enable = visionox_r66451_enable,
>> +    .disable = visionox_r66451_disable,
>> +};
>> +
>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>> +{
>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>> +    u16 brightness = backlight_get_brightness(bl);
>> +
>> +    return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
>
> mipi_dsi_dcs_set_display_brightness() already converts the brightness, so you don't need cpu_to_le16 here.


The _large variant must be used instead.

Neil

>
>> +}
>> +
>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>> +    .update_status = visionox_r66451_bl_update_status,
>> +};
>> +
>> +static struct backlight_device *
>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>> +{
>> +    struct device *dev = &dsi->dev;
>> +    const struct backlight_properties props = {
>> +        .type = BACKLIGHT_RAW,
>> +        .brightness = 255,
>> +        .max_brightness = 4095,
>> +    };
>> +
>> +    return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>> +                          &visionox_r66451_bl_ops, &props);
>> +}
>> +
>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>> +{
>> +    struct device *dev = &dsi->dev;
>> +    struct visionox_r66451 *ctx;
>> +    struct drm_dsc_config *dsc;
>> +    int ret = 0;
>> +
>> +    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>> +    if (!ctx)
>> +        return -ENOMEM;
>> +
>> +    dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>> +    if (!dsc)
>> +        return -ENOMEM;
>> +
>> +    /* Set DSC params */
>> +    dsc->dsc_version_major = 0x1;
>> +    dsc->dsc_version_minor = 0x2;
>> +
>> +    dsc->slice_height = 20;
>> +    dsc->slice_width = 540;
>> +    dsc->slice_count = 2;
>> +    dsc->bits_per_component = 8;
>> +    dsc->bits_per_pixel = 0x8 << 4;
>> +    dsc->block_pred_enable = true;
>> +
>> +    dsi->dsc = dsc;
>> +
>> +    ctx->supplies[0].supply = "vddio";
>> +    ctx->supplies[1].supply = "vdd";
>> +
>> +    ret = devm_regulator_bulk_get(&dsi->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))
>> +        return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
>> +
>> +    ctx->dsi = dsi;
>> +    mipi_dsi_set_drvdata(dsi, ctx);
>> +
>> +    dsi->lanes = 4;
>> +    dsi->format = MIPI_DSI_FMT_RGB888;
>> +    dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
>> +
>> +    drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
>> +    ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>> +    if (IS_ERR(ctx->panel.backlight))
>> +        return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>> +                "Failed to create backlight\n");
>> +
>> +    drm_panel_add(&ctx->panel);
>> +
>> +    ret = mipi_dsi_attach(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>> +        drm_panel_remove(&ctx->panel);
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>> +{
>> +    struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>> +    int ret;
>> +
>> +    ret = mipi_dsi_detach(dsi);
>> +    if (ret < 0)
>> +        dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>> +
>> +    drm_panel_remove(&ctx->panel);
>> +}
>> +
>> +static const struct of_device_id visionox_r66451_of_match[] = {
>> +    {.compatible = "visionox,r66451"},
>> +    { /*sentinel*/ }
>> +};
>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>> +
>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>> +    .probe = visionox_r66451_probe,
>> +    .remove = visionox_r66451_remove,
>> +    .driver = {
>> +        .name = "panel-visionox-r66451",
>> +        .of_match_table = visionox_r66451_of_match,
>> +    },
>> +};
>> +
>> +module_mipi_dsi_driver(visionox_r66451_driver);
>> +
>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
>> +MODULE_LICENSE("GPL");
>>
>


2023-05-22 09:34:03

by Neil Armstrong

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 16/05/2023 22:20, Jessica Zhang wrote:
> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
> comes with the Qualcomm HDK8350 display expansion pack.
>
> The panel enables display compression (DSC v1.2) by default.
>
> Signed-off-by: Jessica Zhang <[email protected]>
> ---
> drivers/gpu/drm/panel/Kconfig | 8 +
> drivers/gpu/drm/panel/Makefile | 1 +
> drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
> 3 files changed, 404 insertions(+)
>
> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index 29cf5fa39ff2..9c2c36dbddf3 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
> Say Y here if you want to enable support for Visionox
> VTDR6130 1080x2400 AMOLED DSI panel.
>
> +config DRM_PANEL_VISIONOX_R66451
> + tristate "Visionox R66451"
> + depends on OF
> + depends on DRM_MIPI_DSI


You may also want
+ depends on BACKLIGHT_CLASS_DEVICE

> + help
> + Say Y here if you want to enable support for Visionox
> + R66451 1080x2340 AMOLED DSI panel.
> +
> config DRM_PANEL_WIDECHIPS_WS2401
> tristate "Widechips WS2401 DPI panel driver"
> depends on SPI && GPIOLIB
> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index b3e8ba29edd3..e043a92ee676 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
> obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
> obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
> obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
> obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
> obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> new file mode 100644
> index 000000000000..e3648ead3e84
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
> @@ -0,0 +1,395 @@
> +//SPDX-License-Identifier: GPL-2.0-only
> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
> +
> +#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 <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_panel.h>
> +#include <drm/display/drm_dsc.h>
> +#include <drm/display/drm_dsc_helper.h>
> +
> +#include <video/mipi_display.h>
> +
> +struct visionox_r66451 {
> + struct drm_panel panel;
> + struct mipi_dsi_device *dsi;
> + struct gpio_desc *reset_gpio;
> + struct regulator_bulk_data supplies[2];
> + bool prepared, enabled;
> +};
> +
> +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
> +{
> + return container_of(panel, struct visionox_r66451, panel);
> +}
> +
> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
> +{
> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> + usleep_range(10000, 10100);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + usleep_range(10000, 10100);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
> + usleep_range(10000, 10100);
> +}
> +
> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
> +{
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct device *dev = &dsi->dev;
> + int ret;
> +
> + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
> +
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xc2,
> + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
> + 0x09, 0x3c);
> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> + mipi_dsi_dcs_write_seq(dsi, 0xde,
> + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
> + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> + mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xc4,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
> + mipi_dsi_dcs_write_seq(dsi, 0xcf,
> + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
> + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
> + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
> + mipi_dsi_dcs_write_seq(dsi, 0xd3,
> + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
> + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
> + 0x3c, 0x9c);
> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
> + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
> + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
> + mipi_dsi_dcs_write_seq(dsi, 0xd8,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
> + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
> + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
> + mipi_dsi_dcs_write_seq(dsi, 0xdf,
> + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
> + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
> + mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
> + mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
> + mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);
> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);
> +
> + ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
> + return ret;
> + }
> + msleep(120);
> +
> + ret = mipi_dsi_dcs_set_display_on(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed on set display on: %d\n", ret);
> + return ret;
> + }
> + msleep(20);
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
> +{
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct device *dev = &dsi->dev;
> + int ret;
> +
> + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
> +
> + ret = mipi_dsi_dcs_set_display_off(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to set display off: %d\n", ret);
> + return ret;
> + }
> + msleep(20);
> +
> + ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
> + return ret;
> + }
> + msleep(120);
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_prepare(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> + struct device *dev = &ctx->dsi->dev;
> + int ret;
> +
> + if (ctx->prepared)
> + return 0;
> +
> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
> + ctx->supplies);
> + if (ret < 0)
> + return ret;
> +
> + visionox_r66451_reset(ctx);
> +
> + ret = visionox_r66451_on(ctx);
> + if (ret < 0) {
> + dev_err(dev, "Failed to initialize panel: %d\n", ret);
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> + return ret;
> + }
> +
> + ctx->prepared = true;
> + return 0;
> +}
> +
> +static int visionox_r66451_unprepare(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> + struct device *dev = &ctx->dsi->dev;
> + int ret;
> +
> + if (!ctx->prepared)
> + return 0;
> +
> + ret = visionox_r66451_off(ctx);
> + if (ret < 0)
> + dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
> +
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
> +
> + ctx->prepared = false;
> + return 0;
> +}
> +
> +static const struct drm_display_mode visionox_r66451_mode = {
> + .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
> + .hdisplay = 1080,
> + .hsync_start = 1080 + 95,
> + .hsync_end = 1080 + 95 + 1,
> + .htotal = 1080 + 95 + 1 + 40,
> + .vdisplay = 2340,
> + .vsync_start = 2340 + 25,
> + .vsync_end = 2340 + 25 + 1,
> + .vtotal = 2340 + 25 + 1 + 4,
> + .width_mm = 0,
> + .height_mm = 0,
> +};
> +
> +static int visionox_r66451_enable(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct drm_dsc_picture_parameter_set pps;
> + int ret;
> +
> + if (ctx->enabled)
> + return 0;
> +
> + if (!dsi->dsc) {
> + dev_err(&dsi->dev, "DSC not attached to DSI\n");
> + return -ENODEV;
> + }
> +
> + drm_dsc_pps_payload_pack(&pps, dsi->dsc);
> + ret = mipi_dsi_picture_parameter_set(dsi, &pps);
> +
> + ctx->enabled = true;
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_disable(struct drm_panel *panel)
> +{
> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
> +
> + ctx->enabled = false;
> +
> + return 0;
> +}
> +
> +static int visionox_r66451_get_modes(struct drm_panel *panel,
> + struct drm_connector *connector)
> +{
> + struct drm_display_mode *mode;
> +
> + mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
> + if (!mode)
> + return -ENOMEM;
> +
> + drm_mode_set_name(mode);
> +
> + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> + connector->display_info.width_mm = mode->width_mm;
> + connector->display_info.height_mm = mode->height_mm;
> + drm_mode_probed_add(connector, mode);
> +
> + return 1;
> +}
> +
> +static const struct drm_panel_funcs visionox_r66451_funcs = {
> + .prepare = visionox_r66451_prepare,
> + .unprepare = visionox_r66451_unprepare,
> + .get_modes = visionox_r66451_get_modes,
> + .enable = visionox_r66451_enable,
> + .disable = visionox_r66451_disable,
> +};
> +
> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
> +{
> + struct mipi_dsi_device *dsi = bl_get_data(bl);
> + u16 brightness = backlight_get_brightness(bl);
> +
> + return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
> +}
> +
> +static const struct backlight_ops visionox_r66451_bl_ops = {
> + .update_status = visionox_r66451_bl_update_status,
> +};
> +
> +static struct backlight_device *
> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
> +{
> + struct device *dev = &dsi->dev;
> + const struct backlight_properties props = {
> + .type = BACKLIGHT_RAW,
> + .brightness = 255,
> + .max_brightness = 4095,
> + };
> +
> + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
> + &visionox_r66451_bl_ops, &props);
> +}
> +
> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
> +{
> + struct device *dev = &dsi->dev;
> + struct visionox_r66451 *ctx;
> + struct drm_dsc_config *dsc;
> + int ret = 0;
> +
> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
> + if (!dsc)
> + return -ENOMEM;
> +
> + /* Set DSC params */
> + dsc->dsc_version_major = 0x1;
> + dsc->dsc_version_minor = 0x2;
> +
> + dsc->slice_height = 20;
> + dsc->slice_width = 540;
> + dsc->slice_count = 2;
> + dsc->bits_per_component = 8;
> + dsc->bits_per_pixel = 0x8 << 4;
> + dsc->block_pred_enable = true;
> +
> + dsi->dsc = dsc;
> +
> + ctx->supplies[0].supply = "vddio";
> + ctx->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(&dsi->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))
> + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
> +
> + ctx->dsi = dsi;
> + mipi_dsi_set_drvdata(dsi, ctx);
> +
> + dsi->lanes = 4;
> + dsi->format = MIPI_DSI_FMT_RGB888;
> + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
> +
> + drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
> + ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
> + if (IS_ERR(ctx->panel.backlight))
> + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
> + "Failed to create backlight\n");
> +
> + drm_panel_add(&ctx->panel);
> +
> + ret = mipi_dsi_attach(dsi);
> + if (ret < 0) {
> + dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
> + drm_panel_remove(&ctx->panel);
> + }
> +
> + return ret;
> +}
> +
> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
> +{
> + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
> + int ret;
> +
> + ret = mipi_dsi_detach(dsi);
> + if (ret < 0)
> + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
> +
> + drm_panel_remove(&ctx->panel);
> +}
> +
> +static const struct of_device_id visionox_r66451_of_match[] = {
> + {.compatible = "visionox,r66451"},
> + { /*sentinel*/ }
> +};
> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
> +
> +static struct mipi_dsi_driver visionox_r66451_driver = {
> + .probe = visionox_r66451_probe,
> + .remove = visionox_r66451_remove,
> + .driver = {
> + .name = "panel-visionox-r66451",
> + .of_match_table = visionox_r66451_of_match,
> + },
> +};
> +
> +module_mipi_dsi_driver(visionox_r66451_driver);
> +
> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
> +MODULE_LICENSE("GPL");
>


2023-05-22 14:04:16

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 22/05/2023 12:10, Neil Armstrong wrote:
> On 18/05/2023 02:19, Dmitry Baryshkov wrote:
>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>
>>> The panel enables display compression (DSC v1.2) by default.
>>>
>>> Signed-off-by: Jessica Zhang <[email protected]>
>>> ---
>>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>>> ++++++++++++++++++++++++++
>>>   3 files changed, 404 insertions(+)

[skipped]

>>> +
>>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +    msleep(120);
>>> +
>>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>>
>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode)
>> be a a part of _enable()? Correspondingly _off should IMO be a part of
>> _disable callback().
>
> Nop, mipi_dsi_dcs_set_display_on() should be part of prepare, to be
> called *before* the video stream starts, enable is called after in the
> atomic modeset chain,

To summarize our discussion: some controllers do not support sending
commands in non-LPM mode. Thus the panel driver should send all commands
before the DSI host switches from LPM to VIDEO mode.

Is this corret?

>
>>
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +    msleep(20);
>>
>>
>> Do we need to add here the following line?
>>
>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>
>
> No, it will be changed by the DSI core.

Hmm, where?

>
>>
>>> +
>>> +    return 0;
>>> +}

[skipped]

>>> +static int visionox_r66451_bl_update_status(struct backlight_device
>>> *bl)
>>> +{
>>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>>> +    u16 brightness = backlight_get_brightness(bl);
>>> +
>>> +    return mipi_dsi_dcs_set_display_brightness(dsi,
>>> cpu_to_le16(brightness));
>>
>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>> so you don't need cpu_to_le16 here.
>
>
> The _large variant must be used instead.

But cpu_to_le16 is NOP, isn't it?

--
With best wishes
Dmitry


2023-05-22 14:57:58

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

On 2023-05-22 11:05:38, Neil Armstrong wrote:
> On 21/05/2023 12:30, Marijn Suijten wrote:
> > On 2023-05-16 13:20:30, Jessica Zhang wrote:
> >> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
> >>
> >> Signed-off-by: Jessica Zhang <[email protected]>
> >> ---
> >> .../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
> >> 1 file changed, 59 insertions(+)
> >>
> >> diff --git a/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
> >> new file mode 100644
> >> index 000000000000..6ba323683921
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
> >> @@ -0,0 +1,59 @@
> >> +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
> >> +%YAML 1.2
> >> +---
> >> +$id: http://devicetree.org/schemas/display/panel/visionox,r66451.yaml#
> >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >> +
> >> +title: Visionox R66451 AMOLED DSI Panel
> >> +
> >> +maintainers:
> >> + - Jessica Zhang <[email protected]>
> >> +
> >> +allOf:
> >> + - $ref: panel-common.yaml#
> >> +
> >> +properties:
> >> + compatible:
> >> + const: visionox,r66451
> >> +
> >> + reg:
> >> + maxItems: 1
> >> + description: DSI virtual channel
> >> +
> >> + vddio-supply: true
> >> + vdd-supply: true
> >> + port: true
> >> + reset-gpios: true
> >
> > Normally for cmd-mode panels there is also a `disp-te` pin which is
> > optionally registered in dsi_host.c as GPIOD_IN, but on **ALL** my Sony
> > phones this breaks vsync (as in: mdp5 stops receiving the interrupt, but
> > we can see disp-te in /proc/interrupts then).
>
> Describing it as a gpio is wrong, it should be described as a pinctrl state instead.

We defined both in our DTS, what weirdness does it cause when then
requested using GPIOD_IN? It'd still be beneficial to see the vsync
interrupt raise in /proc/interrupts (but it's just a waste of CPU cycles
OTOH, this is all handled in the MDP hardware after all, so it's not
something I'd like to enable by default).

Anyway, this is what we ended up doing to "fix" the bug (only bias the
pin via pinctrl, omit the disp-te DTS property). Thanks for confirming!

- Marijn

>
> Neil

<snip>

2023-05-22 17:42:32

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/22/2023 2:09 AM, Neil Armstrong wrote:
> On 16/05/2023 22:20, Jessica Zhang wrote:
>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>> comes with the Qualcomm HDK8350 display expansion pack.
>>
>> The panel enables display compression (DSC v1.2) by default.
>>
>> Signed-off-by: Jessica Zhang <[email protected]>
>> ---
>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>> ++++++++++++++++++++++++++
>>   3 files changed, 404 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/panel/Kconfig
>> b/drivers/gpu/drm/panel/Kconfig
>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>> --- a/drivers/gpu/drm/panel/Kconfig
>> +++ b/drivers/gpu/drm/panel/Kconfig
>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>         Say Y here if you want to enable support for Visionox
>>         VTDR6130 1080x2400 AMOLED DSI panel.
>> +config DRM_PANEL_VISIONOX_R66451
>> +    tristate "Visionox R66451"
>> +    depends on OF
>> +    depends on DRM_MIPI_DSI
>
>
> You may also want
> +    depends on BACKLIGHT_CLASS_DEVICE

Hi Neil,

Acked.

Thanks,

Jessica Zhang

>
>> +    help
>> +      Say Y here if you want to enable support for Visionox
>> +      R66451 1080x2340 AMOLED DSI panel.
>> +
>>   config DRM_PANEL_WIDECHIPS_WS2401
>>       tristate "Widechips WS2401 DPI panel driver"
>>       depends on SPI && GPIOLIB
>> diff --git a/drivers/gpu/drm/panel/Makefile
>> b/drivers/gpu/drm/panel/Makefile
>> index b3e8ba29edd3..e043a92ee676 100644
>> --- a/drivers/gpu/drm/panel/Makefile
>> +++ b/drivers/gpu/drm/panel/Makefile
>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>> panel-tpo-tpg110.o
>>   obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>   obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>   obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>> panel-xinpeng-xpp055c272.o
>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> new file mode 100644
>> index 000000000000..e3648ead3e84
>> --- /dev/null
>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>> @@ -0,0 +1,395 @@
>> +//SPDX-License-Identifier: GPL-2.0-only
>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>> reserved.
>> +
>> +#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 <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_modes.h>
>> +#include <drm/drm_panel.h>
>> +#include <drm/display/drm_dsc.h>
>> +#include <drm/display/drm_dsc_helper.h>
>> +
>> +#include <video/mipi_display.h>
>> +
>> +struct visionox_r66451 {
>> +    struct drm_panel panel;
>> +    struct mipi_dsi_device *dsi;
>> +    struct gpio_desc *reset_gpio;
>> +    struct regulator_bulk_data supplies[2];
>> +    bool prepared, enabled;
>> +};
>> +
>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>> drm_panel *panel)
>> +{
>> +    return container_of(panel, struct visionox_r66451, panel);
>> +}
>> +
>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>> +{
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>> +    usleep_range(10000, 10100);
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +    usleep_range(10000, 10100);
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>> +    usleep_range(10000, 10100);
>> +}
>> +
>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>> +{
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct device *dev = &dsi->dev;
>> +    int ret;
>> +
>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>> +
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
>> +                   0x09, 0x3c);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>> 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
>> +                   0x3c, 0x9c);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
>> 0x39, 0x04, 0x09, 0x34);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>> 0x00, 0x04, 0x37);
>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>> 0x00, 0x09, 0x23);
>> +
>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(120);
>> +
>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(20);
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>> +{
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct device *dev = &dsi->dev;
>> +    int ret;
>> +
>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>> +
>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(20);
>> +
>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>> +        return ret;
>> +    }
>> +    msleep(120);
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct device *dev = &ctx->dsi->dev;
>> +    int ret;
>> +
>> +    if (ctx->prepared)
>> +        return 0;
>> +
>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>> +                    ctx->supplies);
>> +    if (ret < 0)
>> +        return ret;
>> +
>> +    visionox_r66451_reset(ctx);
>> +
>> +    ret = visionox_r66451_on(ctx);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>> ctx->supplies);
>> +        return ret;
>> +    }
>> +
>> +    ctx->prepared = true;
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct device *dev = &ctx->dsi->dev;
>> +    int ret;
>> +
>> +    if (!ctx->prepared)
>> +        return 0;
>> +
>> +    ret = visionox_r66451_off(ctx);
>> +    if (ret < 0)
>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>> +
>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>> +
>> +    ctx->prepared = false;
>> +    return 0;
>> +}
>> +
>> +static const struct drm_display_mode visionox_r66451_mode = {
>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>> +    .hdisplay = 1080,
>> +    .hsync_start = 1080 + 95,
>> +    .hsync_end = 1080 + 95 + 1,
>> +    .htotal = 1080 + 95 + 1 + 40,
>> +    .vdisplay = 2340,
>> +    .vsync_start = 2340 + 25,
>> +    .vsync_end = 2340 + 25 + 1,
>> +    .vtotal = 2340 + 25 + 1 + 4,
>> +    .width_mm = 0,
>> +    .height_mm = 0,
>> +};
>> +
>> +static int visionox_r66451_enable(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>> +    struct drm_dsc_picture_parameter_set pps;
>> +    int ret;
>> +
>> +    if (ctx->enabled)
>> +        return 0;
>> +
>> +    if (!dsi->dsc) {
>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>> +        return -ENODEV;
>> +    }
>> +
>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>> +
>> +    ctx->enabled = true;
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_disable(struct drm_panel *panel)
>> +{
>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>> +
>> +    ctx->enabled = false;
>> +
>> +    return 0;
>> +}
>> +
>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>> +                    struct drm_connector *connector)
>> +{
>> +    struct drm_display_mode *mode;
>> +
>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>> +    if (!mode)
>> +        return -ENOMEM;
>> +
>> +    drm_mode_set_name(mode);
>> +
>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>> +    connector->display_info.width_mm = mode->width_mm;
>> +    connector->display_info.height_mm = mode->height_mm;
>> +    drm_mode_probed_add(connector, mode);
>> +
>> +    return 1;
>> +}
>> +
>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>> +    .prepare = visionox_r66451_prepare,
>> +    .unprepare = visionox_r66451_unprepare,
>> +    .get_modes = visionox_r66451_get_modes,
>> +    .enable = visionox_r66451_enable,
>> +    .disable = visionox_r66451_disable,
>> +};
>> +
>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>> +{
>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>> +    u16 brightness = backlight_get_brightness(bl);
>> +
>> +    return mipi_dsi_dcs_set_display_brightness(dsi,
>> cpu_to_le16(brightness));
>> +}
>> +
>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>> +    .update_status = visionox_r66451_bl_update_status,
>> +};
>> +
>> +static struct backlight_device *
>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>> +{
>> +    struct device *dev = &dsi->dev;
>> +    const struct backlight_properties props = {
>> +        .type = BACKLIGHT_RAW,
>> +        .brightness = 255,
>> +        .max_brightness = 4095,
>> +    };
>> +
>> +    return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>> +                          &visionox_r66451_bl_ops, &props);
>> +}
>> +
>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>> +{
>> +    struct device *dev = &dsi->dev;
>> +    struct visionox_r66451 *ctx;
>> +    struct drm_dsc_config *dsc;
>> +    int ret = 0;
>> +
>> +    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>> +    if (!ctx)
>> +        return -ENOMEM;
>> +
>> +    dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>> +    if (!dsc)
>> +        return -ENOMEM;
>> +
>> +    /* Set DSC params */
>> +    dsc->dsc_version_major = 0x1;
>> +    dsc->dsc_version_minor = 0x2;
>> +
>> +    dsc->slice_height = 20;
>> +    dsc->slice_width = 540;
>> +    dsc->slice_count = 2;
>> +    dsc->bits_per_component = 8;
>> +    dsc->bits_per_pixel = 0x8 << 4;
>> +    dsc->block_pred_enable = true;
>> +
>> +    dsi->dsc = dsc;
>> +
>> +    ctx->supplies[0].supply = "vddio";
>> +    ctx->supplies[1].supply = "vdd";
>> +
>> +    ret = devm_regulator_bulk_get(&dsi->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))
>> +        return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed
>> to get reset-gpios\n");
>> +
>> +    ctx->dsi = dsi;
>> +    mipi_dsi_set_drvdata(dsi, ctx);
>> +
>> +    dsi->lanes = 4;
>> +    dsi->format = MIPI_DSI_FMT_RGB888;
>> +    dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
>> +
>> +    drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs,
>> DRM_MODE_CONNECTOR_DSI);
>> +    ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>> +    if (IS_ERR(ctx->panel.backlight))
>> +        return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>> +                "Failed to create backlight\n");
>> +
>> +    drm_panel_add(&ctx->panel);
>> +
>> +    ret = mipi_dsi_attach(dsi);
>> +    if (ret < 0) {
>> +        dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>> +        drm_panel_remove(&ctx->panel);
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>> +{
>> +    struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>> +    int ret;
>> +
>> +    ret = mipi_dsi_detach(dsi);
>> +    if (ret < 0)
>> +        dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>> +
>> +    drm_panel_remove(&ctx->panel);
>> +}
>> +
>> +static const struct of_device_id visionox_r66451_of_match[] = {
>> +    {.compatible = "visionox,r66451"},
>> +    { /*sentinel*/ }
>> +};
>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>> +
>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>> +    .probe = visionox_r66451_probe,
>> +    .remove = visionox_r66451_remove,
>> +    .driver = {
>> +        .name = "panel-visionox-r66451",
>> +        .of_match_table = visionox_r66451_of_match,
>> +    },
>> +};
>> +
>> +module_mipi_dsi_driver(visionox_r66451_driver);
>> +
>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
>> panel");
>> +MODULE_LICENSE("GPL");
>>
>

2023-05-23 20:54:03

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/21/2023 3:28 AM, Marijn Suijten wrote:
> On 2023-05-18 03:19:49, Dmitry Baryshkov wrote:
>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>
>>> The panel enables display compression (DSC v1.2) by default.
>>>
>>> Signed-off-by: Jessica Zhang <[email protected]>
>>> ---
>>> drivers/gpu/drm/panel/Kconfig | 8 +
>>> drivers/gpu/drm/panel/Makefile | 1 +
>>> drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
>>> 3 files changed, 404 insertions(+)
>>>
>>> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>> --- a/drivers/gpu/drm/panel/Kconfig
>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>> Say Y here if you want to enable support for Visionox
>>> VTDR6130 1080x2400 AMOLED DSI panel.
>>>
>>> +config DRM_PANEL_VISIONOX_R66451
>>> + tristate "Visionox R66451"
>>> + depends on OF
>>> + depends on DRM_MIPI_DSI
>>> + help
>>> + Say Y here if you want to enable support for Visionox
>>> + R66451 1080x2340 AMOLED DSI panel.
>>> +
>>> config DRM_PANEL_WIDECHIPS_WS2401
>>> tristate "Widechips WS2401 DPI panel driver"
>>> depends on SPI && GPIOLIB
>>> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
>>> index b3e8ba29edd3..e043a92ee676 100644
>>> --- a/drivers/gpu/drm/panel/Makefile
>>> +++ b/drivers/gpu/drm/panel/Makefile
>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
>>> obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>> obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>> obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>> obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>> obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>> new file mode 100644
>>> index 000000000000..e3648ead3e84
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>> @@ -0,0 +1,395 @@
>>> +//SPDX-License-Identifier: GPL-2.0-only
>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
>>> +
>>> +#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 <drm/drm_mipi_dsi.h>
>>> +#include <drm/drm_modes.h>
>>> +#include <drm/drm_panel.h>
>>> +#include <drm/display/drm_dsc.h>
>>> +#include <drm/display/drm_dsc_helper.h>
>>> +
>>> +#include <video/mipi_display.h>
>>> +
>>> +struct visionox_r66451 {
>>> + struct drm_panel panel;
>>> + struct mipi_dsi_device *dsi;
>>> + struct gpio_desc *reset_gpio;
>>> + struct regulator_bulk_data supplies[2];
>>> + bool prepared, enabled;
>>> +};
>>> +
>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
>>> +{
>>> + return container_of(panel, struct visionox_r66451, panel);
>>> +}
>>> +
>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>> +{
>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>> + usleep_range(10000, 10100);
>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>> + usleep_range(10000, 10100);
>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>> + usleep_range(10000, 10100);
>>> +}
>>> +
>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>> +{
>>> + struct mipi_dsi_device *dsi = ctx->dsi;
>>> + struct device *dev = &dsi->dev;
>>> + int ret;
>>> +
>>> + dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>> +
>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>> + 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
>>> + 0x09, 0x3c);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>> + 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>>> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>>> + 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xde,
>>> + 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
>>> + 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>> + 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
>>> + 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
>>> + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>> + 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
>>> + 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
>>> + 0x3c, 0x9c);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>> + 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>>> + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>>> + 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> + 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
>>> + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
>>> + 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>> + 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
>>> + 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> + 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
>>> + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
>>> + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>> + mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>> + mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);
>>
>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>>
>>> + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);
>>
>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>
> Yes please, thanks for already pointing these two out.
>
>>
>>> +
>>> + ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>> + if (ret < 0) {
>>> + dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>> + return ret;
>>> + }
>>> + msleep(120);
>>> +
>>> + ret = mipi_dsi_dcs_set_display_on(dsi);
>>
>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
>> a a part of _enable()? Correspondingly _off should IMO be a part of
>> _disable callback().
>>
>>> + if (ret < 0) {
>>> + dev_err(dev, "Failed on set display on: %d\n", ret);
>>> + return ret;
>>> + }
>>> + msleep(20);
>>
>>
>> Do we need to add here the following line?
>>
>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>
>>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>> +{
>>> + struct mipi_dsi_device *dsi = ctx->dsi;
>>> + struct device *dev = &dsi->dev;
>>> + int ret;
>>> +
>>> + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>> +
>>> + ret = mipi_dsi_dcs_set_display_off(dsi);
>>> + if (ret < 0) {
>>> + dev_err(dev, "Failed to set display off: %d\n", ret);
>>> + return ret;
>>> + }
>>> + msleep(20);
>>> +
>>> + ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>> + if (ret < 0) {
>>> + dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>> + return ret;
>>> + }
>>> + msleep(120);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>> +{
>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> + struct device *dev = &ctx->dsi->dev;
>>> + int ret;
>>> +
>>> + if (ctx->prepared)
>>> + return 0;
>>> +
>>> + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>> + ctx->supplies);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + visionox_r66451_reset(ctx);
>>> +
>>> + ret = visionox_r66451_on(ctx);
>>> + if (ret < 0) {
>>> + dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>> + return ret;
>>> + }
>>> +
>>> + ctx->prepared = true;
>>> + return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>> +{
>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> + struct device *dev = &ctx->dsi->dev;
>>> + int ret;
>>> +
>>> + if (!ctx->prepared)
>>> + return 0;
>>> +
>>> + ret = visionox_r66451_off(ctx);
>>> + if (ret < 0)
>>> + dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>> +
>>> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>> + regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>> +
>>> + ctx->prepared = false;
>>> + return 0;
>>> +}
>>> +
>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>> + .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>> + .hdisplay = 1080,
>>> + .hsync_start = 1080 + 95,
>>> + .hsync_end = 1080 + 95 + 1,
>>> + .htotal = 1080 + 95 + 1 + 40,
>>> + .vdisplay = 2340,
>>> + .vsync_start = 2340 + 25,
>>> + .vsync_end = 2340 + 25 + 1,
>>> + .vtotal = 2340 + 25 + 1 + 4,
>>> + .width_mm = 0,
>>> + .height_mm = 0,
>>
>> Please provide real values here.
>>
>>> +};
>>> +
>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>> +{
>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> + struct mipi_dsi_device *dsi = ctx->dsi;
>>> + struct drm_dsc_picture_parameter_set pps;
>>> + int ret;
>>> +
>>> + if (ctx->enabled)
>>> + return 0;
>>> +
>>> + if (!dsi->dsc) {
>>> + dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>> + return -ENODEV;
>>> + }
>>> +
>>> + drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>> + ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>
> This `ret` value is unused.

Hi Marijn,

Acked.

>
> Do we also need to call mipi_dsi_compression_mode() to enable
> compression?

From my understanding, DSC is enabled by default for this panel, so
it's not necessary to explicitly set it.

> This also selects PPS table 1 (which is supposedly the
> default reset value, and the PPS that is uploaded by
> mipi_dsi_picture_parameter_set()). Note that it does set the algorithm
> to "VESA DSC Standard 1.1" but i am unsure if it makes a difference
> (docs for mipi_dsi_picture_parameter_set() say that it is "1.1" as well)

We noticed the same thing when trying to debug an unrelated panel issue.
FWIW, the raw PPS values being sent to the panel are correct and it's
not breaking DSC for us.

>
>>> +
>>> + ctx->enabled = true;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>> +{
>>> + struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> +
>>> + ctx->enabled = false;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>> + struct drm_connector *connector)
>>> +{
>>> + struct drm_display_mode *mode;
>>> +
>>> + mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>> + if (!mode)
>>> + return -ENOMEM;
>>> +
>>> + drm_mode_set_name(mode);
>>> +
>>> + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>> + connector->display_info.width_mm = mode->width_mm;
>>> + connector->display_info.height_mm = mode->height_mm;
>>> + drm_mode_probed_add(connector, mode);
>>
>> Can we use drm_connector_helper_get_modes_fixed() instead?
>
> This does not set DRM_MODE_TYPE_DRIVER though; is that preferred?
>
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>>> + .prepare = visionox_r66451_prepare,
>>> + .unprepare = visionox_r66451_unprepare,
>>> + .get_modes = visionox_r66451_get_modes,
>>> + .enable = visionox_r66451_enable,
>>> + .disable = visionox_r66451_disable,
>>> +};
>>> +
>>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>>> +{
>>> + struct mipi_dsi_device *dsi = bl_get_data(bl);
>>> + u16 brightness = backlight_get_brightness(bl);
>>> +
>>> + return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
>>
>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>> so you don't need cpu_to_le16 here.
>
> Tread carefully here: we've had the same issue and conversation on our
> Sony panels where this extra inversion is required.
> set_display_brightness() sends the bytes as little-endian to the panel
> (and it even assumes little-endian in get_display_brightness()) but the
> spec for 16-bit brightness values states that they have to be sent in
> big-endian. This is why c9d27c6be518b ("drm/mipi-dsi: Fix byte order of
> 16-bit DCS set/get brightness") added
> mipi_dsi_dcs_set_display_brightness_large().
>
> Jessica, if you need to have the endian swap here (should be very easy
> to test with a real panel, but it should be given the max_brightness
> value being over 8 bits) please switch to the _large() variant.

Got it, thanks for the heads up!

>
>>> +}
>>> +
>>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>>> + .update_status = visionox_r66451_bl_update_status,
>
> Should this implement get_brightness?

I don't believe so. As far as I know, get_brightness() is an optional op
for if the panel has a special HW readback for getting the brightness
value, and this panel doesn't have that. So the default behavior of
getting the current brightness property value should be fine.

>
>>> +};
>>> +
>>> +static struct backlight_device *
>>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>>> +{
>>> + struct device *dev = &dsi->dev;
>>> + const struct backlight_properties props = {
>>> + .type = BACKLIGHT_RAW,
>>> + .brightness = 255,
>>> + .max_brightness = 4095,
>>> + };
>>> +
>>> + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>>> + &visionox_r66451_bl_ops, &props);
>>> +}
>>> +
>>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>>> +{
>>> + struct device *dev = &dsi->dev;
>>> + struct visionox_r66451 *ctx;
>>> + struct drm_dsc_config *dsc;
>>> + int ret = 0;
>>> +
>>> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>>> + if (!ctx)
>>> + return -ENOMEM;
>>> +
>>> + dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>>> + if (!dsc)
>>> + return -ENOMEM;
>>> +
>>> + /* Set DSC params */
>>> + dsc->dsc_version_major = 0x1;
>>> + dsc->dsc_version_minor = 0x2;
>>> +
>>> + dsc->slice_height = 20;
>>> + dsc->slice_width = 540;
>>> + dsc->slice_count = 2;
>>> + dsc->bits_per_component = 8;
>>> + dsc->bits_per_pixel = 0x8 << 4;
>
> What's the 0x for? bpc is also just "8" and not 0x8.

Is there a functional difference between 0x8 vs 8? I can drop the "0x"
if you prefer it as decimal instead of hex.

Thanks,

Jessica Zhang

>
> - Marijn
>
>>> + dsc->block_pred_enable = true;
>>> +
>>> + dsi->dsc = dsc;
>>> +
>>> + ctx->supplies[0].supply = "vddio";
>>> + ctx->supplies[1].supply = "vdd";
>>> +
>>> + ret = devm_regulator_bulk_get(&dsi->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))
>>> + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
>>> +
>>> + ctx->dsi = dsi;
>>> + mipi_dsi_set_drvdata(dsi, ctx);
>>> +
>>> + dsi->lanes = 4;
>>> + dsi->format = MIPI_DSI_FMT_RGB888;
>>> + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
>>> +
>>> + drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
>>> + ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>>> + if (IS_ERR(ctx->panel.backlight))
>>> + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>>> + "Failed to create backlight\n");
>>> +
>>> + drm_panel_add(&ctx->panel);
>>> +
>>> + ret = mipi_dsi_attach(dsi);
>>> + if (ret < 0) {
>>> + dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>>> + drm_panel_remove(&ctx->panel);
>>> + }
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>>> +{
>>> + struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>>> + int ret;
>>> +
>>> + ret = mipi_dsi_detach(dsi);
>>> + if (ret < 0)
>>> + dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>>> +
>>> + drm_panel_remove(&ctx->panel);
>>> +}
>>> +
>>> +static const struct of_device_id visionox_r66451_of_match[] = {
>>> + {.compatible = "visionox,r66451"},
>>> + { /*sentinel*/ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>>> +
>>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>>> + .probe = visionox_r66451_probe,
>>> + .remove = visionox_r66451_remove,
>>> + .driver = {
>>> + .name = "panel-visionox-r66451",
>>> + .of_match_table = visionox_r66451_of_match,
>>> + },
>>> +};
>>> +
>>> +module_mipi_dsi_driver(visionox_r66451_driver);
>>> +
>>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
>>> +MODULE_LICENSE("GPL");
>>>
>>
>> --
>> With best wishes
>> Dmitry
>>

2023-05-25 22:59:30

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/23/2023 1:33 PM, Jessica Zhang wrote:
>
>
> On 5/21/2023 3:28 AM, Marijn Suijten wrote:
>> On 2023-05-18 03:19:49, Dmitry Baryshkov wrote:
>>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>>
>>>> The panel enables display compression (DSC v1.2) by default.
>>>>
>>>> Signed-off-by: Jessica Zhang <[email protected]>
>>>> ---
>>>>    drivers/gpu/drm/panel/Kconfig                 |   8 +
>>>>    drivers/gpu/drm/panel/Makefile                |   1 +
>>>>    drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>>>> ++++++++++++++++++++++++++
>>>>    3 files changed, 404 insertions(+)
>>>>
>>>> diff --git a/drivers/gpu/drm/panel/Kconfig
>>>> b/drivers/gpu/drm/panel/Kconfig
>>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>>> --- a/drivers/gpu/drm/panel/Kconfig
>>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>>>          Say Y here if you want to enable support for Visionox
>>>>          VTDR6130 1080x2400 AMOLED DSI panel.
>>>> +config DRM_PANEL_VISIONOX_R66451
>>>> +    tristate "Visionox R66451"
>>>> +    depends on OF
>>>> +    depends on DRM_MIPI_DSI
>>>> +    help
>>>> +      Say Y here if you want to enable support for Visionox
>>>> +      R66451 1080x2340 AMOLED DSI panel.
>>>> +
>>>>    config DRM_PANEL_WIDECHIPS_WS2401
>>>>        tristate "Widechips WS2401 DPI panel driver"
>>>>        depends on SPI && GPIOLIB
>>>> diff --git a/drivers/gpu/drm/panel/Makefile
>>>> b/drivers/gpu/drm/panel/Makefile
>>>> index b3e8ba29edd3..e043a92ee676 100644
>>>> --- a/drivers/gpu/drm/panel/Makefile
>>>> +++ b/drivers/gpu/drm/panel/Makefile
>>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>>>> panel-tpo-tpg110.o
>>>>    obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>>>    obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>>>    obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) +=
>>>> panel-visionox-vtdr6130.o
>>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>>>    obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>>>    obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>>>> panel-xinpeng-xpp055c272.o
>>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> new file mode 100644
>>>> index 000000000000..e3648ead3e84
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> @@ -0,0 +1,395 @@
>>>> +//SPDX-License-Identifier: GPL-2.0-only
>>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>>>> reserved.
>>>> +
>>>> +#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 <drm/drm_mipi_dsi.h>
>>>> +#include <drm/drm_modes.h>
>>>> +#include <drm/drm_panel.h>
>>>> +#include <drm/display/drm_dsc.h>
>>>> +#include <drm/display/drm_dsc_helper.h>
>>>> +
>>>> +#include <video/mipi_display.h>
>>>> +
>>>> +struct visionox_r66451 {
>>>> +    struct drm_panel panel;
>>>> +    struct mipi_dsi_device *dsi;
>>>> +    struct gpio_desc *reset_gpio;
>>>> +    struct regulator_bulk_data supplies[2];
>>>> +    bool prepared, enabled;
>>>> +};
>>>> +
>>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>>>> drm_panel *panel)
>>>> +{
>>>> +    return container_of(panel, struct visionox_r66451, panel);
>>>> +}
>>>> +
>>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>>> +{
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>> +    usleep_range(10000, 10100);
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> +    usleep_range(10000, 10100);
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>> +    usleep_range(10000, 10100);
>>>> +}
>>>> +
>>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>>> +{
>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>> +    struct device *dev = &dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>>> +
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x09, 0x3c);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>>> 0x0a,
>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x19,
>>>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>>>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00,
>>>> 0x18,
>>>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>>>> 0x00, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x08,
>>>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01,
>>>> 0x01,
>>>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15,
>>>> 0x07,
>>>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00,
>>>> 0xa0,
>>>> +                   0x3c, 0x9c);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>>> 0x0a,
>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x19,
>>>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00,
>>>> 0x3a,
>>>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x0a,
>>>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
>>>> 0x39, 0x04, 0x09, 0x34);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>>>> 0x00, 0x04, 0x37);
>>>
>>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>>>
>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>>>> 0x00, 0x09, 0x23);
>>>
>>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>>
>> Yes please, thanks for already pointing these two out.
>>
>>>
>>>> +
>>>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(120);
>>>> +
>>>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>>>
>>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
>>> a a part of _enable()? Correspondingly _off should IMO be a part of
>>> _disable callback().
>>>
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(20);
>>>
>>>
>>> Do we need to add here the following line?
>>>
>>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>
>>>
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>>> +{
>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>> +    struct device *dev = &dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>> +
>>>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(20);
>>>> +
>>>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(120);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +    struct device *dev = &ctx->dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    if (ctx->prepared)
>>>> +        return 0;
>>>> +
>>>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>>> +                    ctx->supplies);
>>>> +    if (ret < 0)
>>>> +        return ret;
>>>> +
>>>> +    visionox_r66451_reset(ctx);
>>>> +
>>>> +    ret = visionox_r66451_on(ctx);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>>>> ctx->supplies);
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    ctx->prepared = true;
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +    struct device *dev = &ctx->dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    if (!ctx->prepared)
>>>> +        return 0;
>>>> +
>>>> +    ret = visionox_r66451_off(ctx);
>>>> +    if (ret < 0)
>>>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>>> +
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>>> +
>>>> +    ctx->prepared = false;
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>>> +    .hdisplay = 1080,
>>>> +    .hsync_start = 1080 + 95,
>>>> +    .hsync_end = 1080 + 95 + 1,
>>>> +    .htotal = 1080 + 95 + 1 + 40,
>>>> +    .vdisplay = 2340,
>>>> +    .vsync_start = 2340 + 25,
>>>> +    .vsync_end = 2340 + 25 + 1,
>>>> +    .vtotal = 2340 + 25 + 1 + 4,
>>>> +    .width_mm = 0,
>>>> +    .height_mm = 0,
>>>
>>> Please provide real values here.
>>>
>>>> +};
>>>> +
>>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>> +    struct drm_dsc_picture_parameter_set pps;
>>>> +    int ret;
>>>> +
>>>> +    if (ctx->enabled)
>>>> +        return 0;
>>>> +
>>>> +    if (!dsi->dsc) {
>>>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>>> +        return -ENODEV;
>>>> +    }
>>>> +
>>>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>>
>> This `ret` value is unused.
>
> Hi Marijn,
>
> Acked.
>
>>
>> Do we also need to call mipi_dsi_compression_mode() to enable
>> compression?
>
> From my understanding, DSC is enabled by default for this panel, so
> it's not necessary to explicitly set it.
>
>> This also selects PPS table 1 (which is supposedly the
>> default reset value, and the PPS that is uploaded by
>> mipi_dsi_picture_parameter_set()).  Note that it does set the algorithm
>> to "VESA DSC Standard 1.1" but i am unsure if it makes a difference
>> (docs for mipi_dsi_picture_parameter_set() say that it is "1.1" as well)
>
> We noticed the same thing when trying to debug an unrelated panel issue.
> FWIW, the raw PPS values being sent to the panel are correct and it's
> not breaking DSC for us.
>
>>
>>>> +
>>>> +    ctx->enabled = true;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +
>>>> +    ctx->enabled = false;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>>> +                    struct drm_connector *connector)
>>>> +{
>>>> +    struct drm_display_mode *mode;
>>>> +
>>>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>>> +    if (!mode)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    drm_mode_set_name(mode);
>>>> +
>>>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>>> +    connector->display_info.width_mm = mode->width_mm;
>>>> +    connector->display_info.height_mm = mode->height_mm;
>>>> +    drm_mode_probed_add(connector, mode);
>>>
>>> Can we use drm_connector_helper_get_modes_fixed() instead?
>>
>> This does not set DRM_MODE_TYPE_DRIVER though; is that preferred?
>>
>>>> +
>>>> +    return 1;
>>>> +}
>>>> +
>>>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>>>> +    .prepare = visionox_r66451_prepare,
>>>> +    .unprepare = visionox_r66451_unprepare,
>>>> +    .get_modes = visionox_r66451_get_modes,
>>>> +    .enable = visionox_r66451_enable,
>>>> +    .disable = visionox_r66451_disable,
>>>> +};
>>>> +
>>>> +static int visionox_r66451_bl_update_status(struct backlight_device
>>>> *bl)
>>>> +{
>>>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>>>> +    u16 brightness = backlight_get_brightness(bl);
>>>> +
>>>> +    return mipi_dsi_dcs_set_display_brightness(dsi,
>>>> cpu_to_le16(brightness));
>>>
>>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>>> so you don't need cpu_to_le16 here.
>>
>> Tread carefully here: we've had the same issue and conversation on our
>> Sony panels where this extra inversion is required.
>> set_display_brightness() sends the bytes as little-endian to the panel
>> (and it even assumes little-endian in get_display_brightness()) but the
>> spec for 16-bit brightness values states that they have to be sent in
>> big-endian.  This is why c9d27c6be518b ("drm/mipi-dsi: Fix byte order of
>> 16-bit DCS set/get brightness") added
>> mipi_dsi_dcs_set_display_brightness_large().
>>
>> Jessica, if you need to have the endian swap here (should be very easy
>> to test with a real panel, but it should be given the max_brightness
>> value being over 8 bits) please switch to the _large() variant.
>
> Got it, thanks for the heads up!

Hi Marijn,

Just wanted to update this thread -- I've checked the backlight
brightness values in the sysfs and it matches the value being given in
the panel driver (255), so I think it should be fine to use
*_set_display_brightness() without the _large() variant.

Thanks,

Jessica Zhang

>
>>
>>>> +}
>>>> +
>>>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>>>> +    .update_status = visionox_r66451_bl_update_status,
>>
>> Should this implement get_brightness?
>
> I don't believe so. As far as I know, get_brightness() is an optional op
> for if the panel has a special HW readback for getting the brightness
> value, and this panel doesn't have that. So the default behavior of
> getting the current brightness property value should be fine.
>
>>
>>>> +};
>>>> +
>>>> +static struct backlight_device *
>>>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>>>> +{
>>>> +    struct device *dev = &dsi->dev;
>>>> +    const struct backlight_properties props = {
>>>> +        .type = BACKLIGHT_RAW,
>>>> +        .brightness = 255,
>>>> +        .max_brightness = 4095,
>>>> +    };
>>>> +
>>>> +    return devm_backlight_device_register(dev, dev_name(dev), dev,
>>>> dsi,
>>>> +                          &visionox_r66451_bl_ops, &props);
>>>> +}
>>>> +
>>>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>>>> +{
>>>> +    struct device *dev = &dsi->dev;
>>>> +    struct visionox_r66451 *ctx;
>>>> +    struct drm_dsc_config *dsc;
>>>> +    int ret = 0;
>>>> +
>>>> +    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>>>> +    if (!ctx)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>>>> +    if (!dsc)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    /* Set DSC params */
>>>> +    dsc->dsc_version_major = 0x1;
>>>> +    dsc->dsc_version_minor = 0x2;
>>>> +
>>>> +    dsc->slice_height = 20;
>>>> +    dsc->slice_width = 540;
>>>> +    dsc->slice_count = 2;
>>>> +    dsc->bits_per_component = 8;
>>>> +    dsc->bits_per_pixel = 0x8 << 4;
>>
>> What's the 0x for?  bpc is also just "8" and not 0x8.
>
> Is there a functional difference between 0x8 vs 8? I can drop the "0x"
> if you prefer it as decimal instead of hex.
>
> Thanks,
>
> Jessica Zhang
>
>>
>> - Marijn
>>
>>>> +    dsc->block_pred_enable = true;
>>>> +
>>>> +    dsi->dsc = dsc;
>>>> +
>>>> +    ctx->supplies[0].supply = "vddio";
>>>> +    ctx->supplies[1].supply = "vdd";
>>>> +
>>>> +    ret = devm_regulator_bulk_get(&dsi->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))
>>>> +        return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed
>>>> to get reset-gpios\n");
>>>> +
>>>> +    ctx->dsi = dsi;
>>>> +    mipi_dsi_set_drvdata(dsi, ctx);
>>>> +
>>>> +    dsi->lanes = 4;
>>>> +    dsi->format = MIPI_DSI_FMT_RGB888;
>>>> +    dsi->mode_flags = MIPI_DSI_MODE_LPM |
>>>> MIPI_DSI_CLOCK_NON_CONTINUOUS;
>>>> +
>>>> +    drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs,
>>>> DRM_MODE_CONNECTOR_DSI);
>>>> +    ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>>>> +    if (IS_ERR(ctx->panel.backlight))
>>>> +        return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>>>> +                "Failed to create backlight\n");
>>>> +
>>>> +    drm_panel_add(&ctx->panel);
>>>> +
>>>> +    ret = mipi_dsi_attach(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>>>> +        drm_panel_remove(&ctx->panel);
>>>> +    }
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>>>> +    int ret;
>>>> +
>>>> +    ret = mipi_dsi_detach(dsi);
>>>> +    if (ret < 0)
>>>> +        dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>>>> +
>>>> +    drm_panel_remove(&ctx->panel);
>>>> +}
>>>> +
>>>> +static const struct of_device_id visionox_r66451_of_match[] = {
>>>> +    {.compatible = "visionox,r66451"},
>>>> +    { /*sentinel*/ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>>>> +
>>>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>>>> +    .probe = visionox_r66451_probe,
>>>> +    .remove = visionox_r66451_remove,
>>>> +    .driver = {
>>>> +        .name = "panel-visionox-r66451",
>>>> +        .of_match_table = visionox_r66451_of_match,
>>>> +    },
>>>> +};
>>>> +
>>>> +module_mipi_dsi_driver(visionox_r66451_driver);
>>>> +
>>>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
>>>> panel");
>>>> +MODULE_LICENSE("GPL");
>>>>
>>>
>>> --
>>> With best wishes
>>> Dmitry
>>>

2023-05-26 07:36:18

by Neil Armstrong

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 26/05/2023 00:28, Jessica Zhang wrote:
>
>
> On 5/23/2023 1:33 PM, Jessica Zhang wrote:
>>
>>
>> On 5/21/2023 3:28 AM, Marijn Suijten wrote:
>>> On 2023-05-18 03:19:49, Dmitry Baryshkov wrote:
>>>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>>>
>>>>> The panel enables display compression (DSC v1.2) by default.
>>>>>
>>>>> Signed-off-by: Jessica Zhang <[email protected]>
>>>>> ---
>>>>>    drivers/gpu/drm/panel/Kconfig                 |   8 +
>>>>>    drivers/gpu/drm/panel/Makefile                |   1 +
>>>>>    drivers/gpu/drm/panel/panel-visionox-r66451.c | 395 ++++++++++++++++++++++++++
>>>>>    3 files changed, 404 insertions(+)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
>>>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>>>> --- a/drivers/gpu/drm/panel/Kconfig
>>>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>>>>          Say Y here if you want to enable support for Visionox
>>>>>          VTDR6130 1080x2400 AMOLED DSI panel.
>>>>> +config DRM_PANEL_VISIONOX_R66451
>>>>> +    tristate "Visionox R66451"
>>>>> +    depends on OF
>>>>> +    depends on DRM_MIPI_DSI
>>>>> +    help
>>>>> +      Say Y here if you want to enable support for Visionox
>>>>> +      R66451 1080x2340 AMOLED DSI panel.
>>>>> +
>>>>>    config DRM_PANEL_WIDECHIPS_WS2401
>>>>>        tristate "Widechips WS2401 DPI panel driver"
>>>>>        depends on SPI && GPIOLIB
>>>>> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
>>>>> index b3e8ba29edd3..e043a92ee676 100644
>>>>> --- a/drivers/gpu/drm/panel/Makefile
>>>>> +++ b/drivers/gpu/drm/panel/Makefile
>>>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
>>>>>    obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>>>>    obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>>>>    obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>>>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>>>>    obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>>>>    obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
>>>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>>> new file mode 100644
>>>>> index 000000000000..e3648ead3e84
>>>>> --- /dev/null
>>>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>>> @@ -0,0 +1,395 @@
>>>>> +//SPDX-License-Identifier: GPL-2.0-only
>>>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
>>>>> +
>>>>> +#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 <drm/drm_mipi_dsi.h>
>>>>> +#include <drm/drm_modes.h>
>>>>> +#include <drm/drm_panel.h>
>>>>> +#include <drm/display/drm_dsc.h>
>>>>> +#include <drm/display/drm_dsc_helper.h>
>>>>> +
>>>>> +#include <video/mipi_display.h>
>>>>> +
>>>>> +struct visionox_r66451 {
>>>>> +    struct drm_panel panel;
>>>>> +    struct mipi_dsi_device *dsi;
>>>>> +    struct gpio_desc *reset_gpio;
>>>>> +    struct regulator_bulk_data supplies[2];
>>>>> +    bool prepared, enabled;
>>>>> +};
>>>>> +
>>>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
>>>>> +{
>>>>> +    return container_of(panel, struct visionox_r66451, panel);
>>>>> +}
>>>>> +
>>>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>>>> +{
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>>> +    usleep_range(10000, 10100);
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>>> +    usleep_range(10000, 10100);
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>>> +    usleep_range(10000, 10100);
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>>>> +{
>>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>>> +    struct device *dev = &dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>>>> +
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>>>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
>>>>> +                   0x09, 0x3c);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>>>>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>>>>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
>>>>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>>>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
>>>>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
>>>>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>>>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
>>>>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
>>>>> +                   0x3c, 0x9c);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
>>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
>>>>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
>>>>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
>>>>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>>>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
>>>>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>>>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37);
>>>>
>>>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>>>>
>>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x23);
>>>>
>>>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>>>
>>> Yes please, thanks for already pointing these two out.
>>>
>>>>
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(120);
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>>>>
>>>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode) be
>>>> a a part of _enable()? Correspondingly _off should IMO be a part of
>>>> _disable callback().
>>>>
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(20);
>>>>
>>>>
>>>> Do we need to add here the following line?
>>>>
>>>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>>
>>>>
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>>>> +{
>>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>>> +    struct device *dev = &dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(20);
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(120);
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +    struct device *dev = &ctx->dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (ctx->prepared)
>>>>> +        return 0;
>>>>> +
>>>>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>>>> +                    ctx->supplies);
>>>>> +    if (ret < 0)
>>>>> +        return ret;
>>>>> +
>>>>> +    visionox_r66451_reset(ctx);
>>>>> +
>>>>> +    ret = visionox_r66451_on(ctx);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>>>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>>>> +        return ret;
>>>>> +    }
>>>>> +
>>>>> +    ctx->prepared = true;
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +    struct device *dev = &ctx->dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (!ctx->prepared)
>>>>> +        return 0;
>>>>> +
>>>>> +    ret = visionox_r66451_off(ctx);
>>>>> +    if (ret < 0)
>>>>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>>>> +
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>>>> +
>>>>> +    ctx->prepared = false;
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>>>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>>>> +    .hdisplay = 1080,
>>>>> +    .hsync_start = 1080 + 95,
>>>>> +    .hsync_end = 1080 + 95 + 1,
>>>>> +    .htotal = 1080 + 95 + 1 + 40,
>>>>> +    .vdisplay = 2340,
>>>>> +    .vsync_start = 2340 + 25,
>>>>> +    .vsync_end = 2340 + 25 + 1,
>>>>> +    .vtotal = 2340 + 25 + 1 + 4,
>>>>> +    .width_mm = 0,
>>>>> +    .height_mm = 0,
>>>>
>>>> Please provide real values here.
>>>>
>>>>> +};
>>>>> +
>>>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>>> +    struct drm_dsc_picture_parameter_set pps;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (ctx->enabled)
>>>>> +        return 0;
>>>>> +
>>>>> +    if (!dsi->dsc) {
>>>>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>>>> +        return -ENODEV;
>>>>> +    }
>>>>> +
>>>>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>>>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>>>
>>> This `ret` value is unused.
>>
>> Hi Marijn,
>>
>> Acked.
>>
>>>
>>> Do we also need to call mipi_dsi_compression_mode() to enable
>>> compression?
>>
>>  From my understanding, DSC is enabled by default for this panel, so it's not necessary to explicitly set it.
>>
>>> This also selects PPS table 1 (which is supposedly the
>>> default reset value, and the PPS that is uploaded by
>>> mipi_dsi_picture_parameter_set()).  Note that it does set the algorithm
>>> to "VESA DSC Standard 1.1" but i am unsure if it makes a difference
>>> (docs for mipi_dsi_picture_parameter_set() say that it is "1.1" as well)
>>
>> We noticed the same thing when trying to debug an unrelated panel issue. FWIW, the raw PPS values being sent to the panel are correct and it's not breaking DSC for us.
>>
>>>
>>>>> +
>>>>> +    ctx->enabled = true;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +
>>>>> +    ctx->enabled = false;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>>>> +                    struct drm_connector *connector)
>>>>> +{
>>>>> +    struct drm_display_mode *mode;
>>>>> +
>>>>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>>>> +    if (!mode)
>>>>> +        return -ENOMEM;
>>>>> +
>>>>> +    drm_mode_set_name(mode);
>>>>> +
>>>>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>>>> +    connector->display_info.width_mm = mode->width_mm;
>>>>> +    connector->display_info.height_mm = mode->height_mm;
>>>>> +    drm_mode_probed_add(connector, mode);
>>>>
>>>> Can we use drm_connector_helper_get_modes_fixed() instead?
>>>
>>> This does not set DRM_MODE_TYPE_DRIVER though; is that preferred?
>>>
>>>>> +
>>>>> +    return 1;
>>>>> +}
>>>>> +
>>>>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>>>>> +    .prepare = visionox_r66451_prepare,
>>>>> +    .unprepare = visionox_r66451_unprepare,
>>>>> +    .get_modes = visionox_r66451_get_modes,
>>>>> +    .enable = visionox_r66451_enable,
>>>>> +    .disable = visionox_r66451_disable,
>>>>> +};
>>>>> +
>>>>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>>>>> +{
>>>>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>>>>> +    u16 brightness = backlight_get_brightness(bl);
>>>>> +
>>>>> +    return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
>>>>
>>>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>>>> so you don't need cpu_to_le16 here.
>>>
>>> Tread carefully here: we've had the same issue and conversation on our
>>> Sony panels where this extra inversion is required.
>>> set_display_brightness() sends the bytes as little-endian to the panel
>>> (and it even assumes little-endian in get_display_brightness()) but the
>>> spec for 16-bit brightness values states that they have to be sent in
>>> big-endian.  This is why c9d27c6be518b ("drm/mipi-dsi: Fix byte order of
>>> 16-bit DCS set/get brightness") added
>>> mipi_dsi_dcs_set_display_brightness_large().
>>>
>>> Jessica, if you need to have the endian swap here (should be very easy
>>> to test with a real panel, but it should be given the max_brightness
>>> value being over 8 bits) please switch to the _large() variant.
>>
>> Got it, thanks for the heads up!
>
> Hi Marijn,
>
> Just wanted to update this thread -- I've checked the backlight brightness values in the sysfs and it matches the value being given in the panel driver (255), so I think it should be fine to use *_set_display_brightness() without the _large() variant.

Sure, I was also misleaded by you using cpu_to_le16() but as Dmitry said it's already
done in mipi_dsi_dcs_set_display_brightness() and a no-op on LE arm64 platforms anyway.

Neil

>
> Thanks,
>
> Jessica Zhang
>
>>
>>>
>>>>> +}
>>>>> +
>>>>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>>>>> +    .update_status = visionox_r66451_bl_update_status,
>>>
>>> Should this implement get_brightness?
>>
>> I don't believe so. As far as I know, get_brightness() is an optional op for if the panel has a special HW readback for getting the brightness value, and this panel doesn't have that. So the default behavior of getting the current brightness property value should be fine.
>>
>>>
>>>>> +};
>>>>> +
>>>>> +static struct backlight_device *
>>>>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>>>>> +{
>>>>> +    struct device *dev = &dsi->dev;
>>>>> +    const struct backlight_properties props = {
>>>>> +        .type = BACKLIGHT_RAW,
>>>>> +        .brightness = 255,
>>>>> +        .max_brightness = 4095,
>>>>> +    };
>>>>> +
>>>>> +    return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>>>>> +                          &visionox_r66451_bl_ops, &props);
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>>>>> +{
>>>>> +    struct device *dev = &dsi->dev;
>>>>> +    struct visionox_r66451 *ctx;
>>>>> +    struct drm_dsc_config *dsc;
>>>>> +    int ret = 0;
>>>>> +
>>>>> +    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>>>>> +    if (!ctx)
>>>>> +        return -ENOMEM;
>>>>> +
>>>>> +    dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>>>>> +    if (!dsc)
>>>>> +        return -ENOMEM;
>>>>> +
>>>>> +    /* Set DSC params */
>>>>> +    dsc->dsc_version_major = 0x1;
>>>>> +    dsc->dsc_version_minor = 0x2;
>>>>> +
>>>>> +    dsc->slice_height = 20;
>>>>> +    dsc->slice_width = 540;
>>>>> +    dsc->slice_count = 2;
>>>>> +    dsc->bits_per_component = 8;
>>>>> +    dsc->bits_per_pixel = 0x8 << 4;
>>>
>>> What's the 0x for?  bpc is also just "8" and not 0x8.
>>
>> Is there a functional difference between 0x8 vs 8? I can drop the "0x" if you prefer it as decimal instead of hex.
>>
>> Thanks,
>>
>> Jessica Zhang
>>
>>>
>>> - Marijn
>>>
>>>>> +    dsc->block_pred_enable = true;
>>>>> +
>>>>> +    dsi->dsc = dsc;
>>>>> +
>>>>> +    ctx->supplies[0].supply = "vddio";
>>>>> +    ctx->supplies[1].supply = "vdd";
>>>>> +
>>>>> +    ret = devm_regulator_bulk_get(&dsi->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))
>>>>> +        return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset-gpios\n");
>>>>> +
>>>>> +    ctx->dsi = dsi;
>>>>> +    mipi_dsi_set_drvdata(dsi, ctx);
>>>>> +
>>>>> +    dsi->lanes = 4;
>>>>> +    dsi->format = MIPI_DSI_FMT_RGB888;
>>>>> +    dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
>>>>> +
>>>>> +    drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
>>>>> +    ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>>>>> +    if (IS_ERR(ctx->panel.backlight))
>>>>> +        return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>>>>> +                "Failed to create backlight\n");
>>>>> +
>>>>> +    drm_panel_add(&ctx->panel);
>>>>> +
>>>>> +    ret = mipi_dsi_attach(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>>>>> +        drm_panel_remove(&ctx->panel);
>>>>> +    }
>>>>> +
>>>>> +    return ret;
>>>>> +}
>>>>> +
>>>>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>>>>> +    int ret;
>>>>> +
>>>>> +    ret = mipi_dsi_detach(dsi);
>>>>> +    if (ret < 0)
>>>>> +        dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>>>>> +
>>>>> +    drm_panel_remove(&ctx->panel);
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id visionox_r66451_of_match[] = {
>>>>> +    {.compatible = "visionox,r66451"},
>>>>> +    { /*sentinel*/ }
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>>>>> +
>>>>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>>>>> +    .probe = visionox_r66451_probe,
>>>>> +    .remove = visionox_r66451_remove,
>>>>> +    .driver = {
>>>>> +        .name = "panel-visionox-r66451",
>>>>> +        .of_match_table = visionox_r66451_of_match,
>>>>> +    },
>>>>> +};
>>>>> +
>>>>> +module_mipi_dsi_driver(visionox_r66451_driver);
>>>>> +
>>>>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>>>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
>>>>> +MODULE_LICENSE("GPL");
>>>>>
>>>>
>>>> --
>>>> With best wishes
>>>> Dmitry
>>>>


2023-05-26 07:55:32

by Neil Armstrong

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

On 22/05/2023 16:51, Marijn Suijten wrote:
> On 2023-05-22 11:05:38, Neil Armstrong wrote:
>> On 21/05/2023 12:30, Marijn Suijten wrote:
>>> On 2023-05-16 13:20:30, Jessica Zhang wrote:
>>>> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
>>>>
>>>> Signed-off-by: Jessica Zhang <[email protected]>
>>>> ---
>>>> .../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
>>>> 1 file changed, 59 insertions(+)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
>>>> new file mode 100644
>>>> index 000000000000..6ba323683921
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
>>>> @@ -0,0 +1,59 @@
>>>> +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
>>>> +%YAML 1.2
>>>> +---
>>>> +$id: http://devicetree.org/schemas/display/panel/visionox,r66451.yaml#
>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>> +
>>>> +title: Visionox R66451 AMOLED DSI Panel
>>>> +
>>>> +maintainers:
>>>> + - Jessica Zhang <[email protected]>
>>>> +
>>>> +allOf:
>>>> + - $ref: panel-common.yaml#
>>>> +
>>>> +properties:
>>>> + compatible:
>>>> + const: visionox,r66451
>>>> +
>>>> + reg:
>>>> + maxItems: 1
>>>> + description: DSI virtual channel
>>>> +
>>>> + vddio-supply: true
>>>> + vdd-supply: true
>>>> + port: true
>>>> + reset-gpios: true
>>>
>>> Normally for cmd-mode panels there is also a `disp-te` pin which is
>>> optionally registered in dsi_host.c as GPIOD_IN, but on **ALL** my Sony
>>> phones this breaks vsync (as in: mdp5 stops receiving the interrupt, but
>>> we can see disp-te in /proc/interrupts then).
>>
>> Describing it as a gpio is wrong, it should be described as a pinctrl state instead.
>
> We defined both in our DTS, what weirdness does it cause when then
> requested using GPIOD_IN? It'd still be beneficial to see the vsync
> interrupt raise in /proc/interrupts (but it's just a waste of CPU cycles
> OTOH, this is all handled in the MDP hardware after all, so it's not
> something I'd like to enable by default).

Sure, but it's a sw hack, the pin has a TE function which directly goes to
the DSI logic, claiming it as a GPIO will set it as GPIO function.

On some platforms, PINMUX is only on output and input is always directed
to all HW blocks, seems it's not the case here !

>
> Anyway, this is what we ended up doing to "fix" the bug (only bias the
> pin via pinctrl, omit the disp-te DTS property). Thanks for confirming!
>
> - Marijn
>
>>
>> Neil
>
> <snip>


2023-05-29 12:06:50

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings

On 2023-05-26 09:42:33, Neil Armstrong wrote:
> On 22/05/2023 16:51, Marijn Suijten wrote:
> > On 2023-05-22 11:05:38, Neil Armstrong wrote:
> >> On 21/05/2023 12:30, Marijn Suijten wrote:
> >>> On 2023-05-16 13:20:30, Jessica Zhang wrote:
> >>>> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
> >>>>
> >>>> Signed-off-by: Jessica Zhang <[email protected]>
> >>>> ---
> >>>> .../bindings/display/panel/visionox,r66451.yaml | 59 ++++++++++++++++++++++
> >>>> 1 file changed, 59 insertions(+)
> >>>>
> >>>> diff --git a/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
> >>>> new file mode 100644
> >>>> index 000000000000..6ba323683921
> >>>> --- /dev/null
> >>>> +++ b/Documentation/devicetree/bindings/display/panel/visionox,r66451.yaml
> >>>> @@ -0,0 +1,59 @@
> >>>> +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
> >>>> +%YAML 1.2
> >>>> +---
> >>>> +$id: http://devicetree.org/schemas/display/panel/visionox,r66451.yaml#
> >>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >>>> +
> >>>> +title: Visionox R66451 AMOLED DSI Panel
> >>>> +
> >>>> +maintainers:
> >>>> + - Jessica Zhang <[email protected]>
> >>>> +
> >>>> +allOf:
> >>>> + - $ref: panel-common.yaml#
> >>>> +
> >>>> +properties:
> >>>> + compatible:
> >>>> + const: visionox,r66451
> >>>> +
> >>>> + reg:
> >>>> + maxItems: 1
> >>>> + description: DSI virtual channel
> >>>> +
> >>>> + vddio-supply: true
> >>>> + vdd-supply: true
> >>>> + port: true
> >>>> + reset-gpios: true
> >>>
> >>> Normally for cmd-mode panels there is also a `disp-te` pin which is
> >>> optionally registered in dsi_host.c as GPIOD_IN, but on **ALL** my Sony
> >>> phones this breaks vsync (as in: mdp5 stops receiving the interrupt, but
> >>> we can see disp-te in /proc/interrupts then).
> >>
> >> Describing it as a gpio is wrong, it should be described as a pinctrl state instead.
> >
> > We defined both in our DTS, what weirdness does it cause when then
> > requested using GPIOD_IN? It'd still be beneficial to see the vsync
> > interrupt raise in /proc/interrupts (but it's just a waste of CPU cycles
> > OTOH, this is all handled in the MDP hardware after all, so it's not
> > something I'd like to enable by default).
>
> Sure, but it's a sw hack, the pin has a TE function which directly goes to
> the DSI logic, claiming it as a GPIO will set it as GPIO function.
>
> On some platforms, PINMUX is only on output and input is always directed
> to all HW blocks, seems it's not the case here !

Ah that makes total sense! The PINGROUP() is only passed this mdp_vsync
function but internally provides the gpio function as well, which it'd
have to use to read it as GPIO from the SoC-side: and this indeed seems
to "disconnect" that pin from the MDP HW block. Thanks for mentioning
this, I totally overlooked it.

Should we document/clarify this in any way, or perhaps remove the
disp-te handling altogether (dsi_host.c doesn't use this interrupt for
anything, though we could leave it for debug purposes if describing /
wrapping it more clearly). Downstream also sets this pin in DT but
doesn't ever request a GPIO/IRQ on it, afaik.

- Marijn

> > Anyway, this is what we ended up doing to "fix" the bug (only bias the
> > pin via pinctrl, omit the disp-te DTS property). Thanks for confirming!
> >
> > - Marijn
> >
> >>
> >> Neil
> >
> > <snip>
>

2023-05-29 12:24:30

by Marijn Suijten

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 2023-05-26 09:32:45, Neil Armstrong wrote:
<snip>
> >>>>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
> >>>>> +{
> >>>>> +??? struct mipi_dsi_device *dsi = bl_get_data(bl);
> >>>>> +??? u16 brightness = backlight_get_brightness(bl);
> >>>>> +
> >>>>> +??? return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
> >>>>
> >>>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
> >>>> so you don't need cpu_to_le16 here.
> >>>
> >>> Tread carefully here: we've had the same issue and conversation on our
> >>> Sony panels where this extra inversion is required.
> >>> set_display_brightness() sends the bytes as little-endian to the panel
> >>> (and it even assumes little-endian in get_display_brightness()) but the
> >>> spec for 16-bit brightness values states that they have to be sent in
> >>> big-endian.? This is why c9d27c6be518b ("drm/mipi-dsi: Fix byte order of
> >>> 16-bit DCS set/get brightness") added
> >>> mipi_dsi_dcs_set_display_brightness_large().
> >>>
> >>> Jessica, if you need to have the endian swap here (should be very easy
> >>> to test with a real panel, but it should be given the max_brightness
> >>> value being over 8 bits) please switch to the _large() variant.
> >>
> >> Got it, thanks for the heads up!
> >
> > Hi Marijn,
> >
> > Just wanted to update this thread -- I've checked the backlight brightness values in the sysfs and it matches the value being given in the panel driver (255), so I think it should be fine to use *_set_display_brightness() without the _large() variant.
>
> Sure, I was also misleaded by you using cpu_to_le16() but as Dmitry said it's already
> done in mipi_dsi_dcs_set_display_brightness() and a no-op on LE arm64 platforms anyway.

Yuck, right, it's cpu_to_le16 here and not cpu_to_be16. @Jessica, can
you please remove this misleading conversion?
mipi_dsi_dcs_set_display_brightness() takes a native u16, not a specific
__le16.

- Marijn

2023-05-30 17:58:20

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/29/2023 5:09 AM, Marijn Suijten wrote:
> On 2023-05-26 09:32:45, Neil Armstrong wrote:
> <snip>
>>>>>>> +static int visionox_r66451_bl_update_status(struct backlight_device *bl)
>>>>>>> +{
>>>>>>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>>>>>>> +    u16 brightness = backlight_get_brightness(bl);
>>>>>>> +
>>>>>>> +    return mipi_dsi_dcs_set_display_brightness(dsi, cpu_to_le16(brightness));
>>>>>>
>>>>>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>>>>>> so you don't need cpu_to_le16 here.
>>>>>
>>>>> Tread carefully here: we've had the same issue and conversation on our
>>>>> Sony panels where this extra inversion is required.
>>>>> set_display_brightness() sends the bytes as little-endian to the panel
>>>>> (and it even assumes little-endian in get_display_brightness()) but the
>>>>> spec for 16-bit brightness values states that they have to be sent in
>>>>> big-endian.  This is why c9d27c6be518b ("drm/mipi-dsi: Fix byte order of
>>>>> 16-bit DCS set/get brightness") added
>>>>> mipi_dsi_dcs_set_display_brightness_large().
>>>>>
>>>>> Jessica, if you need to have the endian swap here (should be very easy
>>>>> to test with a real panel, but it should be given the max_brightness
>>>>> value being over 8 bits) please switch to the _large() variant.
>>>>
>>>> Got it, thanks for the heads up!
>>>
>>> Hi Marijn,
>>>
>>> Just wanted to update this thread -- I've checked the backlight brightness values in the sysfs and it matches the value being given in the panel driver (255), so I think it should be fine to use *_set_display_brightness() without the _large() variant.
>>
>> Sure, I was also misleaded by you using cpu_to_le16() but as Dmitry said it's already
>> done in mipi_dsi_dcs_set_display_brightness() and a no-op on LE arm64 platforms anyway.
>
> Yuck, right, it's cpu_to_le16 here and not cpu_to_be16. @Jessica, can
> you please remove this misleading conversion?
> mipi_dsi_dcs_set_display_brightness() takes a native u16, not a specific
> __le16.

Hi Marijn,

Acked, will drop the conversion.

Thanks,

Jessica Zhang

>
> - Marijn

2023-05-30 20:14:08

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/18/2023 3:17 PM, Jessica Zhang wrote:
>
>
> On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>
>>> The panel enables display compression (DSC v1.2) by default.
>>>
>>> Signed-off-by: Jessica Zhang <[email protected]>
>>> ---
>>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>>> ++++++++++++++++++++++++++
>>>   3 files changed, 404 insertions(+)
>>>
>>> diff --git a/drivers/gpu/drm/panel/Kconfig
>>> b/drivers/gpu/drm/panel/Kconfig
>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>> --- a/drivers/gpu/drm/panel/Kconfig
>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>>         Say Y here if you want to enable support for Visionox
>>>         VTDR6130 1080x2400 AMOLED DSI panel.
>>> +config DRM_PANEL_VISIONOX_R66451
>>> +    tristate "Visionox R66451"
>>> +    depends on OF
>>> +    depends on DRM_MIPI_DSI
>>> +    help
>>> +      Say Y here if you want to enable support for Visionox
>>> +      R66451 1080x2340 AMOLED DSI panel.
>>> +
>>>   config DRM_PANEL_WIDECHIPS_WS2401
>>>       tristate "Widechips WS2401 DPI panel driver"
>>>       depends on SPI && GPIOLIB
>>> diff --git a/drivers/gpu/drm/panel/Makefile
>>> b/drivers/gpu/drm/panel/Makefile
>>> index b3e8ba29edd3..e043a92ee676 100644
>>> --- a/drivers/gpu/drm/panel/Makefile
>>> +++ b/drivers/gpu/drm/panel/Makefile
>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>>> panel-tpo-tpg110.o
>>>   obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>>   obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>>   obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>>> panel-xinpeng-xpp055c272.o
>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>> new file mode 100644
>>> index 000000000000..e3648ead3e84
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>> @@ -0,0 +1,395 @@
>>> +//SPDX-License-Identifier: GPL-2.0-only
>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>>> reserved.
>>> +
>>> +#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 <drm/drm_mipi_dsi.h>
>>> +#include <drm/drm_modes.h>
>>> +#include <drm/drm_panel.h>
>>> +#include <drm/display/drm_dsc.h>
>>> +#include <drm/display/drm_dsc_helper.h>
>>> +
>>> +#include <video/mipi_display.h>
>>> +
>>> +struct visionox_r66451 {
>>> +    struct drm_panel panel;
>>> +    struct mipi_dsi_device *dsi;
>>> +    struct gpio_desc *reset_gpio;
>>> +    struct regulator_bulk_data supplies[2];
>>> +    bool prepared, enabled;
>>> +};
>>> +
>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>>> drm_panel *panel)
>>> +{
>>> +    return container_of(panel, struct visionox_r66451, panel);
>>> +}
>>> +
>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>> +{
>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>> +    usleep_range(10000, 10100);
>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>> +    usleep_range(10000, 10100);
>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>> +    usleep_range(10000, 10100);
>>> +}
>>> +
>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>> +{
>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>> +    struct device *dev = &dsi->dev;
>>> +    int ret;
>>> +
>>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>> +
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00,
>>> 0x00,
>>> +                   0x09, 0x3c);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>> 0x0a,
>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x19,
>>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00,
>>> 0x18,
>>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>>> 0x00, 0x00);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x00,
>>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x08,
>>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01,
>>> 0x01,
>>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15,
>>> 0x07,
>>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00,
>>> 0xa0,
>>> +                   0x3c, 0x9c);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>> 0x0a,
>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x19,
>>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x00,
>>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00,
>>> 0x3a,
>>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x00,
>>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
>>> 0x00,
>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x0a,
>>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00,
>>> 0x00,
>>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
>>> 0x00,
>>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00,
>>> 0x00,
>>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00,
>>> 0x00,
>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
>>> 0x39, 0x04, 0x09, 0x34);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>>> 0x00, 0x04, 0x37);
>>
>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>
> Hi Dmitry,
>
> Acked.
>
>>
>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>>> 0x00, 0x09, 0x23);
>>
>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>
> Acked.
>
>>
>>> +
>>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +    msleep(120);
>>> +
>>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>>
>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode)
>> be a a part of _enable()? Correspondingly _off should IMO be a part of
>> _disable callback().
>
> Acked. Looking at the downstream code, it seems that all the dcs on
> commands can be sent within _enable() (and similarly for the off
> commands with _disable()).
>
>>
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +    msleep(20);
>>
>>
>> Do we need to add here the following line?
>>
>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>
> Do you mean within the return on error block?
>
> From my understanding, we should be setting the MIPI_DSI_MODE_LPM for
> panel_on() then unsetting MODE_LPM in panel_off().
>
>>
>>
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>> +{
>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>> +    struct device *dev = &dsi->dev;
>>> +    int ret;
>>> +
>>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>> +
>>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +    msleep(20);
>>> +
>>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +    msleep(120);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>> +{
>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> +    struct device *dev = &ctx->dsi->dev;
>>> +    int ret;
>>> +
>>> +    if (ctx->prepared)
>>> +        return 0;
>>> +
>>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>> +                    ctx->supplies);
>>> +    if (ret < 0)
>>> +        return ret;
>>> +
>>> +    visionox_r66451_reset(ctx);
>>> +
>>> +    ret = visionox_r66451_on(ctx);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>>> ctx->supplies);
>>> +        return ret;
>>> +    }
>>> +
>>> +    ctx->prepared = true;
>>> +    return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>> +{
>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> +    struct device *dev = &ctx->dsi->dev;
>>> +    int ret;
>>> +
>>> +    if (!ctx->prepared)
>>> +        return 0;
>>> +
>>> +    ret = visionox_r66451_off(ctx);
>>> +    if (ret < 0)
>>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>> +
>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>> +
>>> +    ctx->prepared = false;
>>> +    return 0;
>>> +}
>>> +
>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>> +    .hdisplay = 1080,
>>> +    .hsync_start = 1080 + 95,
>>> +    .hsync_end = 1080 + 95 + 1,
>>> +    .htotal = 1080 + 95 + 1 + 40,
>>> +    .vdisplay = 2340,
>>> +    .vsync_start = 2340 + 25,
>>> +    .vsync_end = 2340 + 25 + 1,
>>> +    .vtotal = 2340 + 25 + 1 + 4,
>>> +    .width_mm = 0,
>>> +    .height_mm = 0,
>>
>> Please provide real values here.
>
> Acked.
>
>>
>>> +};
>>> +
>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>> +{
>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>> +    struct drm_dsc_picture_parameter_set pps;
>>> +    int ret;
>>> +
>>> +    if (ctx->enabled)
>>> +        return 0;
>>> +
>>> +    if (!dsi->dsc) {
>>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>> +        return -ENODEV;
>>> +    }
>>> +
>>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>>> +
>>> +    ctx->enabled = true;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>> +{
>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>> +
>>> +    ctx->enabled = false;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>> +                    struct drm_connector *connector)
>>> +{
>>> +    struct drm_display_mode *mode;
>>> +
>>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>> +    if (!mode)
>>> +        return -ENOMEM;
>>> +
>>> +    drm_mode_set_name(mode);
>>> +
>>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>> +    connector->display_info.width_mm = mode->width_mm;
>>> +    connector->display_info.height_mm = mode->height_mm;
>>> +    drm_mode_probed_add(connector, mode);
>>
>> Can we use drm_connector_helper_get_modes_fixed() instead?
>
> Acked.

Hi Dmitry,

Just wanted to follow up on this -- As Marijn mentioned in a separated
thread, DRM_MODE_TYPE_DRIVER is not set within
drm_connector_helper_get_modes_fixed(), and looking at the kernel docs,
that's a required flag for this mode.

Since I'm not able to set the *_TYPE_DRIVER flag before
*_get_modes_fixed() is called, I'll stick to using the previous method
of calling drm_mode_probed_add() directly in
visionox_r66451_get_modes(). (FWIW, other drivers such as
panel-visionox-vtdr6130, panel-truly-nt35597, and panel-visionox-rm69299
also don't use *_get_modes_fixed() and call drm_mode_probed_add()
directly in their get_mode() implementations.)

Thanks,

Jessica Zhang

>
>>
>>> +
>>> +    return 1;
>>> +}
>>> +
>>> +static const struct drm_panel_funcs visionox_r66451_funcs = {
>>> +    .prepare = visionox_r66451_prepare,
>>> +    .unprepare = visionox_r66451_unprepare,
>>> +    .get_modes = visionox_r66451_get_modes,
>>> +    .enable = visionox_r66451_enable,
>>> +    .disable = visionox_r66451_disable,
>>> +};
>>> +
>>> +static int visionox_r66451_bl_update_status(struct backlight_device
>>> *bl)
>>> +{
>>> +    struct mipi_dsi_device *dsi = bl_get_data(bl);
>>> +    u16 brightness = backlight_get_brightness(bl);
>>> +
>>> +    return mipi_dsi_dcs_set_display_brightness(dsi,
>>> cpu_to_le16(brightness));
>>
>> mipi_dsi_dcs_set_display_brightness() already converts the brightness,
>> so you don't need cpu_to_le16 here.
>
> Acked.
>
> Thanks,
>
> Jessica Zhang
>
>>
>>> +}
>>> +
>>> +static const struct backlight_ops visionox_r66451_bl_ops = {
>>> +    .update_status = visionox_r66451_bl_update_status,
>>> +};
>>> +
>>> +static struct backlight_device *
>>> +visionox_r66451_create_backlight(struct mipi_dsi_device *dsi)
>>> +{
>>> +    struct device *dev = &dsi->dev;
>>> +    const struct backlight_properties props = {
>>> +        .type = BACKLIGHT_RAW,
>>> +        .brightness = 255,
>>> +        .max_brightness = 4095,
>>> +    };
>>> +
>>> +    return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
>>> +                          &visionox_r66451_bl_ops, &props);
>>> +}
>>> +
>>> +static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
>>> +{
>>> +    struct device *dev = &dsi->dev;
>>> +    struct visionox_r66451 *ctx;
>>> +    struct drm_dsc_config *dsc;
>>> +    int ret = 0;
>>> +
>>> +    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
>>> +    if (!ctx)
>>> +        return -ENOMEM;
>>> +
>>> +    dsc = devm_kzalloc(dev, sizeof(*dsc), GFP_KERNEL);
>>> +    if (!dsc)
>>> +        return -ENOMEM;
>>> +
>>> +    /* Set DSC params */
>>> +    dsc->dsc_version_major = 0x1;
>>> +    dsc->dsc_version_minor = 0x2;
>>> +
>>> +    dsc->slice_height = 20;
>>> +    dsc->slice_width = 540;
>>> +    dsc->slice_count = 2;
>>> +    dsc->bits_per_component = 8;
>>> +    dsc->bits_per_pixel = 0x8 << 4;
>>> +    dsc->block_pred_enable = true;
>>> +
>>> +    dsi->dsc = dsc;
>>> +
>>> +    ctx->supplies[0].supply = "vddio";
>>> +    ctx->supplies[1].supply = "vdd";
>>> +
>>> +    ret = devm_regulator_bulk_get(&dsi->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))
>>> +        return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed
>>> to get reset-gpios\n");
>>> +
>>> +    ctx->dsi = dsi;
>>> +    mipi_dsi_set_drvdata(dsi, ctx);
>>> +
>>> +    dsi->lanes = 4;
>>> +    dsi->format = MIPI_DSI_FMT_RGB888;
>>> +    dsi->mode_flags = MIPI_DSI_MODE_LPM |
>>> MIPI_DSI_CLOCK_NON_CONTINUOUS;
>>> +
>>> +    drm_panel_init(&ctx->panel, dev, &visionox_r66451_funcs,
>>> DRM_MODE_CONNECTOR_DSI);
>>> +    ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
>>> +    if (IS_ERR(ctx->panel.backlight))
>>> +        return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
>>> +                "Failed to create backlight\n");
>>> +
>>> +    drm_panel_add(&ctx->panel);
>>> +
>>> +    ret = mipi_dsi_attach(dsi);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
>>> +        drm_panel_remove(&ctx->panel);
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
>>> +{
>>> +    struct visionox_r66451 *ctx = mipi_dsi_get_drvdata(dsi);
>>> +    int ret;
>>> +
>>> +    ret = mipi_dsi_detach(dsi);
>>> +    if (ret < 0)
>>> +        dev_err(&dsi->dev, "Failed to detach DSI host: %d\n", ret);
>>> +
>>> +    drm_panel_remove(&ctx->panel);
>>> +}
>>> +
>>> +static const struct of_device_id visionox_r66451_of_match[] = {
>>> +    {.compatible = "visionox,r66451"},
>>> +    { /*sentinel*/ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
>>> +
>>> +static struct mipi_dsi_driver visionox_r66451_driver = {
>>> +    .probe = visionox_r66451_probe,
>>> +    .remove = visionox_r66451_remove,
>>> +    .driver = {
>>> +        .name = "panel-visionox-r66451",
>>> +        .of_match_table = visionox_r66451_of_match,
>>> +    },
>>> +};
>>> +
>>> +module_mipi_dsi_driver(visionox_r66451_driver);
>>> +
>>> +MODULE_AUTHOR("Jessica Zhang <[email protected]>");
>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
>>> panel");
>>> +MODULE_LICENSE("GPL");
>>>
>>
>> --
>> With best wishes
>> Dmitry
>>

2023-05-30 23:31:34

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel

On 30/05/2023 23:09, Jessica Zhang wrote:
>
>
> On 5/18/2023 3:17 PM, Jessica Zhang wrote:
>>
>>
>> On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
>>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>>
>>>> The panel enables display compression (DSC v1.2) by default.
>>>>
>>>> Signed-off-by: Jessica Zhang <[email protected]>
>>>> ---
>>>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>>>> ++++++++++++++++++++++++++
>>>>   3 files changed, 404 insertions(+)
>>>>
>>>> diff --git a/drivers/gpu/drm/panel/Kconfig
>>>> b/drivers/gpu/drm/panel/Kconfig
>>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>>> --- a/drivers/gpu/drm/panel/Kconfig
>>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>>>         Say Y here if you want to enable support for Visionox
>>>>         VTDR6130 1080x2400 AMOLED DSI panel.
>>>> +config DRM_PANEL_VISIONOX_R66451
>>>> +    tristate "Visionox R66451"
>>>> +    depends on OF
>>>> +    depends on DRM_MIPI_DSI
>>>> +    help
>>>> +      Say Y here if you want to enable support for Visionox
>>>> +      R66451 1080x2340 AMOLED DSI panel.
>>>> +
>>>>   config DRM_PANEL_WIDECHIPS_WS2401
>>>>       tristate "Widechips WS2401 DPI panel driver"
>>>>       depends on SPI && GPIOLIB
>>>> diff --git a/drivers/gpu/drm/panel/Makefile
>>>> b/drivers/gpu/drm/panel/Makefile
>>>> index b3e8ba29edd3..e043a92ee676 100644
>>>> --- a/drivers/gpu/drm/panel/Makefile
>>>> +++ b/drivers/gpu/drm/panel/Makefile
>>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>>>> panel-tpo-tpg110.o
>>>>   obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) +=
>>>> panel-visionox-vtdr6130.o
>>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>>>   obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>>>   obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>>>> panel-xinpeng-xpp055c272.o
>>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> new file mode 100644
>>>> index 000000000000..e3648ead3e84
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>> @@ -0,0 +1,395 @@
>>>> +//SPDX-License-Identifier: GPL-2.0-only
>>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>>>> reserved.
>>>> +
>>>> +#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 <drm/drm_mipi_dsi.h>
>>>> +#include <drm/drm_modes.h>
>>>> +#include <drm/drm_panel.h>
>>>> +#include <drm/display/drm_dsc.h>
>>>> +#include <drm/display/drm_dsc_helper.h>
>>>> +
>>>> +#include <video/mipi_display.h>
>>>> +
>>>> +struct visionox_r66451 {
>>>> +    struct drm_panel panel;
>>>> +    struct mipi_dsi_device *dsi;
>>>> +    struct gpio_desc *reset_gpio;
>>>> +    struct regulator_bulk_data supplies[2];
>>>> +    bool prepared, enabled;
>>>> +};
>>>> +
>>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>>>> drm_panel *panel)
>>>> +{
>>>> +    return container_of(panel, struct visionox_r66451, panel);
>>>> +}
>>>> +
>>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>>> +{
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>> +    usleep_range(10000, 10100);
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> +    usleep_range(10000, 10100);
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>> +    usleep_range(10000, 10100);
>>>> +}
>>>> +
>>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>>> +{
>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>> +    struct device *dev = &dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>>> +
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x09, 0x3c);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>>> 0x0a,
>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x19,
>>>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>>>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00,
>>>> 0x18,
>>>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>>>> 0x00, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x08,
>>>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01,
>>>> 0x01,
>>>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15,
>>>> 0x07,
>>>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00,
>>>> 0xa0,
>>>> +                   0x3c, 0x9c);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>>> 0x0a,
>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x19,
>>>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00,
>>>> 0x3a,
>>>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x0a,
>>>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00,
>>>> 0x00,
>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00,
>>>> 0x39, 0x04, 0x09, 0x34);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>>>> 0x00, 0x04, 0x37);
>>>
>>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>>
>> Hi Dmitry,
>>
>> Acked.
>>
>>>
>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>>>> 0x00, 0x09, 0x23);
>>>
>>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>>
>> Acked.
>>
>>>
>>>> +
>>>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(120);
>>>> +
>>>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>>>
>>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode)
>>> be a a part of _enable()? Correspondingly _off should IMO be a part
>>> of _disable callback().
>>
>> Acked. Looking at the downstream code, it seems that all the dcs on
>> commands can be sent within _enable() (and similarly for the off
>> commands with _disable()).
>>
>>>
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(20);
>>>
>>>
>>> Do we need to add here the following line?
>>>
>>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>
>> Do you mean within the return on error block?
>>
>>  From my understanding, we should be setting the MIPI_DSI_MODE_LPM for
>> panel_on() then unsetting MODE_LPM in panel_off().
>>
>>>
>>>
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>>> +{
>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>> +    struct device *dev = &dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>> +
>>>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(20);
>>>> +
>>>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +    msleep(120);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +    struct device *dev = &ctx->dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    if (ctx->prepared)
>>>> +        return 0;
>>>> +
>>>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>>> +                    ctx->supplies);
>>>> +    if (ret < 0)
>>>> +        return ret;
>>>> +
>>>> +    visionox_r66451_reset(ctx);
>>>> +
>>>> +    ret = visionox_r66451_on(ctx);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>>>> ctx->supplies);
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    ctx->prepared = true;
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +    struct device *dev = &ctx->dsi->dev;
>>>> +    int ret;
>>>> +
>>>> +    if (!ctx->prepared)
>>>> +        return 0;
>>>> +
>>>> +    ret = visionox_r66451_off(ctx);
>>>> +    if (ret < 0)
>>>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>>> +
>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>>> +
>>>> +    ctx->prepared = false;
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>>> +    .hdisplay = 1080,
>>>> +    .hsync_start = 1080 + 95,
>>>> +    .hsync_end = 1080 + 95 + 1,
>>>> +    .htotal = 1080 + 95 + 1 + 40,
>>>> +    .vdisplay = 2340,
>>>> +    .vsync_start = 2340 + 25,
>>>> +    .vsync_end = 2340 + 25 + 1,
>>>> +    .vtotal = 2340 + 25 + 1 + 4,
>>>> +    .width_mm = 0,
>>>> +    .height_mm = 0,
>>>
>>> Please provide real values here.
>>
>> Acked.
>>
>>>
>>>> +};
>>>> +
>>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>> +    struct drm_dsc_picture_parameter_set pps;
>>>> +    int ret;
>>>> +
>>>> +    if (ctx->enabled)
>>>> +        return 0;
>>>> +
>>>> +    if (!dsi->dsc) {
>>>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>>> +        return -ENODEV;
>>>> +    }
>>>> +
>>>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>>>> +
>>>> +    ctx->enabled = true;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>>> +{
>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>> +
>>>> +    ctx->enabled = false;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>>> +                    struct drm_connector *connector)
>>>> +{
>>>> +    struct drm_display_mode *mode;
>>>> +
>>>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>>> +    if (!mode)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    drm_mode_set_name(mode);
>>>> +
>>>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>>> +    connector->display_info.width_mm = mode->width_mm;
>>>> +    connector->display_info.height_mm = mode->height_mm;
>>>> +    drm_mode_probed_add(connector, mode);
>>>
>>> Can we use drm_connector_helper_get_modes_fixed() instead?
>>
>> Acked.
>
> Hi Dmitry,
>
> Just wanted to follow up on this -- As Marijn mentioned in a separated
> thread, DRM_MODE_TYPE_DRIVER is not set within
> drm_connector_helper_get_modes_fixed(), and looking at the kernel docs,
> that's a required flag for this mode.

You can set this flag in the visionox_r66451_mode, can you not?

> Since I'm not able to set the *_TYPE_DRIVER flag before
> *_get_modes_fixed() is called, I'll stick to using the previous method
> of calling drm_mode_probed_add() directly in
> visionox_r66451_get_modes(). (FWIW, other drivers such as
> panel-visionox-vtdr6130, panel-truly-nt35597, and panel-visionox-rm69299
> also don't use *_get_modes_fixed() and call drm_mode_probed_add()
> directly in their get_mode() implementations.)

That doesn't mean that we should increase the amount of the c&p code.

If I have time, I'try doing a rework of existing panel drivers.

--
With best wishes
Dmitry


2023-05-31 18:13:02

by Jessica Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel



On 5/30/2023 4:12 PM, Dmitry Baryshkov wrote:
> On 30/05/2023 23:09, Jessica Zhang wrote:
>>
>>
>> On 5/18/2023 3:17 PM, Jessica Zhang wrote:
>>>
>>>
>>> On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
>>>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>>>
>>>>> The panel enables display compression (DSC v1.2) by default.
>>>>>
>>>>> Signed-off-by: Jessica Zhang <[email protected]>
>>>>> ---
>>>>>   drivers/gpu/drm/panel/Kconfig                 |   8 +
>>>>>   drivers/gpu/drm/panel/Makefile                |   1 +
>>>>>   drivers/gpu/drm/panel/panel-visionox-r66451.c | 395
>>>>> ++++++++++++++++++++++++++
>>>>>   3 files changed, 404 insertions(+)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/panel/Kconfig
>>>>> b/drivers/gpu/drm/panel/Kconfig
>>>>> index 29cf5fa39ff2..9c2c36dbddf3 100644
>>>>> --- a/drivers/gpu/drm/panel/Kconfig
>>>>> +++ b/drivers/gpu/drm/panel/Kconfig
>>>>> @@ -766,6 +766,14 @@ config DRM_PANEL_VISIONOX_VTDR6130
>>>>>         Say Y here if you want to enable support for Visionox
>>>>>         VTDR6130 1080x2400 AMOLED DSI panel.
>>>>> +config DRM_PANEL_VISIONOX_R66451
>>>>> +    tristate "Visionox R66451"
>>>>> +    depends on OF
>>>>> +    depends on DRM_MIPI_DSI
>>>>> +    help
>>>>> +      Say Y here if you want to enable support for Visionox
>>>>> +      R66451 1080x2340 AMOLED DSI panel.
>>>>> +
>>>>>   config DRM_PANEL_WIDECHIPS_WS2401
>>>>>       tristate "Widechips WS2401 DPI panel driver"
>>>>>       depends on SPI && GPIOLIB
>>>>> diff --git a/drivers/gpu/drm/panel/Makefile
>>>>> b/drivers/gpu/drm/panel/Makefile
>>>>> index b3e8ba29edd3..e043a92ee676 100644
>>>>> --- a/drivers/gpu/drm/panel/Makefile
>>>>> +++ b/drivers/gpu/drm/panel/Makefile
>>>>> @@ -78,5 +78,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TPG110) +=
>>>>> panel-tpo-tpg110.o
>>>>>   obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
>>>>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
>>>>>   obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) +=
>>>>> panel-visionox-vtdr6130.o
>>>>> +obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
>>>>>   obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
>>>>>   obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) +=
>>>>> panel-xinpeng-xpp055c272.o
>>>>> diff --git a/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>>> b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>>> new file mode 100644
>>>>> index 000000000000..e3648ead3e84
>>>>> --- /dev/null
>>>>> +++ b/drivers/gpu/drm/panel/panel-visionox-r66451.c
>>>>> @@ -0,0 +1,395 @@
>>>>> +//SPDX-License-Identifier: GPL-2.0-only
>>>>> +//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights
>>>>> reserved.
>>>>> +
>>>>> +#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 <drm/drm_mipi_dsi.h>
>>>>> +#include <drm/drm_modes.h>
>>>>> +#include <drm/drm_panel.h>
>>>>> +#include <drm/display/drm_dsc.h>
>>>>> +#include <drm/display/drm_dsc_helper.h>
>>>>> +
>>>>> +#include <video/mipi_display.h>
>>>>> +
>>>>> +struct visionox_r66451 {
>>>>> +    struct drm_panel panel;
>>>>> +    struct mipi_dsi_device *dsi;
>>>>> +    struct gpio_desc *reset_gpio;
>>>>> +    struct regulator_bulk_data supplies[2];
>>>>> +    bool prepared, enabled;
>>>>> +};
>>>>> +
>>>>> +static inline struct visionox_r66451 *to_visionox_r66451(struct
>>>>> drm_panel *panel)
>>>>> +{
>>>>> +    return container_of(panel, struct visionox_r66451, panel);
>>>>> +}
>>>>> +
>>>>> +static void visionox_r66451_reset(struct visionox_r66451 *ctx)
>>>>> +{
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>>> +    usleep_range(10000, 10100);
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>>> +    usleep_range(10000, 10100);
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 0);
>>>>> +    usleep_range(10000, 10100);
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_on(struct visionox_r66451 *ctx)
>>>>> +{
>>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>>> +    struct device *dev = &dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    dsi->mode_flags |= MIPI_DSI_MODE_LPM;
>>>>> +
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc2,
>>>>> +                   0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x09, 0x3c);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>>> +                   0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>>>> 0x0a,
>>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x19,
>>>>> +                   0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xde,
>>>>> +                   0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00,
>>>>> 0x18,
>>>>> +                   0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02,
>>>>> 0x00, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xc4,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xcf,
>>>>> +                   0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x08,
>>>>> +                   0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01,
>>>>> 0x01,
>>>>> +                   0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd3,
>>>>> +                   0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15,
>>>>> 0x07,
>>>>> +                   0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00,
>>>>> 0xa0,
>>>>> +                   0x3c, 0x9c);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd7,
>>>>> +                   0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0,
>>>>> 0x0a,
>>>>> +                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x19,
>>>>> +                   0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xd8,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00,
>>>>> 0x3a,
>>>>> +                   0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x0a,
>>>>> +                   0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf,
>>>>> +                   0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00,
>>>>> 0x00,
>>>>> +                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00,
>>>>> 0x00, 0x39, 0x04, 0x09, 0x34);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
>>>>> +    mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
>>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, 0x00,
>>>>> 0x00, 0x04, 0x37);
>>>>
>>>> Please use mipi_dsi_dcs_set_column_address(dsi, 0, 1080 - 1);
>>>
>>> Hi Dmitry,
>>>
>>> Acked.
>>>
>>>>
>>>>> +    mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PAGE_ADDRESS, 0x00,
>>>>> 0x00, 0x09, 0x23);
>>>>
>>>> mipi_dsi_dcs_set_page_address(dsi, 0, 2340 - 1);
>>>
>>> Acked.
>>>
>>>>
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(120);
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_set_display_on(dsi);
>>>>
>>>> Should the mipi_dsi_dcs_set_display_on() (and maybe exit sleep mode)
>>>> be a a part of _enable()? Correspondingly _off should IMO be a part
>>>> of _disable callback().
>>>
>>> Acked. Looking at the downstream code, it seems that all the dcs on
>>> commands can be sent within _enable() (and similarly for the off
>>> commands with _disable()).
>>>
>>>>
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed on set display on: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(20);
>>>>
>>>>
>>>> Do we need to add here the following line?
>>>>
>>>> dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>
>>> Do you mean within the return on error block?
>>>
>>>  From my understanding, we should be setting the MIPI_DSI_MODE_LPM
>>> for panel_on() then unsetting MODE_LPM in panel_off().
>>>
>>>>
>>>>
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_off(struct visionox_r66451 *ctx)
>>>>> +{
>>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>>> +    struct device *dev = &dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_set_display_off(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to set display off: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(20);
>>>>> +
>>>>> +    ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
>>>>> +        return ret;
>>>>> +    }
>>>>> +    msleep(120);
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_prepare(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +    struct device *dev = &ctx->dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (ctx->prepared)
>>>>> +        return 0;
>>>>> +
>>>>> +    ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
>>>>> +                    ctx->supplies);
>>>>> +    if (ret < 0)
>>>>> +        return ret;
>>>>> +
>>>>> +    visionox_r66451_reset(ctx);
>>>>> +
>>>>> +    ret = visionox_r66451_on(ctx);
>>>>> +    if (ret < 0) {
>>>>> +        dev_err(dev, "Failed to initialize panel: %d\n", ret);
>>>>> +        gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>>> +        regulator_bulk_disable(ARRAY_SIZE(ctx->supplies),
>>>>> ctx->supplies);
>>>>> +        return ret;
>>>>> +    }
>>>>> +
>>>>> +    ctx->prepared = true;
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_unprepare(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +    struct device *dev = &ctx->dsi->dev;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (!ctx->prepared)
>>>>> +        return 0;
>>>>> +
>>>>> +    ret = visionox_r66451_off(ctx);
>>>>> +    if (ret < 0)
>>>>> +        dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
>>>>> +
>>>>> +    gpiod_set_value_cansleep(ctx->reset_gpio, 1);
>>>>> +    regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
>>>>> +
>>>>> +    ctx->prepared = false;
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct drm_display_mode visionox_r66451_mode = {
>>>>> +    .clock = (1080 + 95 + 1 + 40) * (2340 + 25 + 1 + 4) * 120 / 1000,
>>>>> +    .hdisplay = 1080,
>>>>> +    .hsync_start = 1080 + 95,
>>>>> +    .hsync_end = 1080 + 95 + 1,
>>>>> +    .htotal = 1080 + 95 + 1 + 40,
>>>>> +    .vdisplay = 2340,
>>>>> +    .vsync_start = 2340 + 25,
>>>>> +    .vsync_end = 2340 + 25 + 1,
>>>>> +    .vtotal = 2340 + 25 + 1 + 4,
>>>>> +    .width_mm = 0,
>>>>> +    .height_mm = 0,
>>>>
>>>> Please provide real values here.
>>>
>>> Acked.
>>>
>>>>
>>>>> +};
>>>>> +
>>>>> +static int visionox_r66451_enable(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +    struct mipi_dsi_device *dsi = ctx->dsi;
>>>>> +    struct drm_dsc_picture_parameter_set pps;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (ctx->enabled)
>>>>> +        return 0;
>>>>> +
>>>>> +    if (!dsi->dsc) {
>>>>> +        dev_err(&dsi->dev, "DSC not attached to DSI\n");
>>>>> +        return -ENODEV;
>>>>> +    }
>>>>> +
>>>>> +    drm_dsc_pps_payload_pack(&pps, dsi->dsc);
>>>>> +    ret = mipi_dsi_picture_parameter_set(dsi, &pps);
>>>>> +
>>>>> +    ctx->enabled = true;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_disable(struct drm_panel *panel)
>>>>> +{
>>>>> +    struct visionox_r66451 *ctx = to_visionox_r66451(panel);
>>>>> +
>>>>> +    ctx->enabled = false;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int visionox_r66451_get_modes(struct drm_panel *panel,
>>>>> +                    struct drm_connector *connector)
>>>>> +{
>>>>> +    struct drm_display_mode *mode;
>>>>> +
>>>>> +    mode = drm_mode_duplicate(connector->dev, &visionox_r66451_mode);
>>>>> +    if (!mode)
>>>>> +        return -ENOMEM;
>>>>> +
>>>>> +    drm_mode_set_name(mode);
>>>>> +
>>>>> +    mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>>>>> +    connector->display_info.width_mm = mode->width_mm;
>>>>> +    connector->display_info.height_mm = mode->height_mm;
>>>>> +    drm_mode_probed_add(connector, mode);
>>>>
>>>> Can we use drm_connector_helper_get_modes_fixed() instead?
>>>
>>> Acked.
>>
>> Hi Dmitry,
>>
>> Just wanted to follow up on this -- As Marijn mentioned in a separated
>> thread, DRM_MODE_TYPE_DRIVER is not set within
>> drm_connector_helper_get_modes_fixed(), and looking at the kernel
>> docs, that's a required flag for this mode.
>
> You can set this flag in the visionox_r66451_mode, can you not?

Sure, I can set it during initialization then; just didn't see a
precedent for it in existing drivers.

>
>> Since I'm not able to set the *_TYPE_DRIVER flag before
>> *_get_modes_fixed() is called, I'll stick to using the previous method
>> of calling drm_mode_probed_add() directly in
>> visionox_r66451_get_modes(). (FWIW, other drivers such as
>> panel-visionox-vtdr6130, panel-truly-nt35597, and
>> panel-visionox-rm69299 also don't use *_get_modes_fixed() and call
>> drm_mode_probed_add() directly in their get_mode() implementations.)
>
> That doesn't mean that we should increase the amount of the c&p code.
>
> If I have time, I'try doing a rework of existing panel drivers.

Sounds good -- I think that would help get rid of a lot of duplicate code.

Thanks,

Jessica Zhang

>
> --
> With best wishes
> Dmitry
>