dri-devel.lists.freedesktop.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] Add support for Visionox R66451 AMOLED DSI panel
@ 2023-05-16 20:20 Jessica Zhang
  2023-05-16 20:20 ` [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings Jessica Zhang
  2023-05-16 20:20 ` [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel Jessica Zhang
  0 siblings, 2 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-16 20:20 UTC (permalink / raw)
  To: Neil Armstrong, Sam Ravnborg, David Airlie, Daniel Vetter,
	Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Jessica Zhang, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
---
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 <quic_jesszhan@quicinc.com>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-16 20:20 [PATCH 0/2] Add support for Visionox R66451 AMOLED DSI panel Jessica Zhang
@ 2023-05-16 20:20 ` Jessica Zhang
  2023-05-17  8:12   ` Krzysztof Kozlowski
  2023-05-21 10:30   ` Marijn Suijten
  2023-05-16 20:20 ` [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel Jessica Zhang
  1 sibling, 2 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-16 20:20 UTC (permalink / raw)
  To: Neil Armstrong, Sam Ravnborg, David Airlie, Daniel Vetter,
	Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Jessica Zhang, Abhinav Kumar, dri-devel, linux-kernel

Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings

Signed-off-by: Jessica Zhang <quic_jesszhan@quicinc.com>
---
 .../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 <quic_jesszhan@quicinc.com>
+
+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


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-16 20:20 [PATCH 0/2] Add support for Visionox R66451 AMOLED DSI panel Jessica Zhang
  2023-05-16 20:20 ` [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings Jessica Zhang
@ 2023-05-16 20:20 ` Jessica Zhang
  2023-05-18  0:19   ` Dmitry Baryshkov
  2023-05-22  9:09   ` Neil Armstrong
  1 sibling, 2 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-16 20:20 UTC (permalink / raw)
  To: Neil Armstrong, Sam Ravnborg, David Airlie, Daniel Vetter,
	Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Jessica Zhang, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
---
 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 <quic_jesszhan@quicinc.com>");
+MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
+MODULE_LICENSE("GPL");

-- 
2.40.1


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-16 20:20 ` [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings Jessica Zhang
@ 2023-05-17  8:12   ` Krzysztof Kozlowski
  2023-05-21 10:30   ` Marijn Suijten
  1 sibling, 0 replies; 25+ messages in thread
From: Krzysztof Kozlowski @ 2023-05-17  8:12 UTC (permalink / raw)
  To: Jessica Zhang, Neil Armstrong, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel

On 16/05/2023 22:20, Jessica Zhang wrote:
> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
> 
> Signed-off-by: Jessica Zhang <quic_jesszhan@quicinc.com>
> ---
>  .../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 <krzysztof.kozlowski@linaro.org>

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-16 20:20 ` [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel Jessica Zhang
@ 2023-05-18  0:19   ` Dmitry Baryshkov
  2023-05-18 22:17     ` Jessica Zhang
                       ` (2 more replies)
  2023-05-22  9:09   ` Neil Armstrong
  1 sibling, 3 replies; 25+ messages in thread
From: Dmitry Baryshkov @ 2023-05-18  0:19 UTC (permalink / raw)
  To: Jessica Zhang, Neil Armstrong, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
> ---
>   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 <quic_jesszhan@quicinc.com>");
> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
> +MODULE_LICENSE("GPL");
> 

-- 
With best wishes
Dmitry


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-18  0:19   ` Dmitry Baryshkov
@ 2023-05-18 22:17     ` Jessica Zhang
  2023-05-18 22:37       ` Dmitry Baryshkov
  2023-05-30 20:09       ` Jessica Zhang
  2023-05-21 10:28     ` Marijn Suijten
  2023-05-22  9:10     ` Neil Armstrong
  2 siblings, 2 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-18 22:17 UTC (permalink / raw)
  To: Dmitry Baryshkov, Neil Armstrong, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel



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 <quic_jesszhan@quicinc.com>
>> ---
>>   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 <quic_jesszhan@quicinc.com>");
>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI 
>> panel");
>> +MODULE_LICENSE("GPL");
>>
> 
> -- 
> With best wishes
> Dmitry
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-18 22:17     ` Jessica Zhang
@ 2023-05-18 22:37       ` Dmitry Baryshkov
  2023-05-19  0:28         ` Jessica Zhang
  2023-05-30 20:09       ` Jessica Zhang
  1 sibling, 1 reply; 25+ messages in thread
From: Dmitry Baryshkov @ 2023-05-18 22:37 UTC (permalink / raw)
  To: Jessica Zhang
  Cc: Neil Armstrong, devicetree, Sam Ravnborg, Abhinav Kumar,
	dri-devel, linux-kernel, Rob Herring, Krzysztof Kozlowski

On Fri, 19 May 2023 at 01:17, Jessica Zhang <quic_jesszhan@quicinc.com> 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 <quic_jesszhan@quicinc.com>
> >> ---
> >>   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 <quic_jesszhan@quicinc.com>");
> >> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
> >> panel");
> >> +MODULE_LICENSE("GPL");
> >>
> >
> > --
> > With best wishes
> > Dmitry
> >



-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-18 22:37       ` Dmitry Baryshkov
@ 2023-05-19  0:28         ` Jessica Zhang
  0 siblings, 0 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-19  0:28 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Neil Armstrong, devicetree, Sam Ravnborg, Abhinav Kumar,
	dri-devel, linux-kernel, Rob Herring, Krzysztof Kozlowski



On 5/18/2023 3:37 PM, Dmitry Baryshkov wrote:
> On Fri, 19 May 2023 at 01:17, Jessica Zhang <quic_jesszhan@quicinc.com> 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 <quic_jesszhan@quicinc.com>
>>>> ---
>>>>    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 <quic_jesszhan@quicinc.com>");
>>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI
>>>> panel");
>>>> +MODULE_LICENSE("GPL");
>>>>
>>>
>>> --
>>> With best wishes
>>> Dmitry
>>>
> 
> 
> 
> -- 
> With best wishes
> Dmitry

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-18  0:19   ` Dmitry Baryshkov
  2023-05-18 22:17     ` Jessica Zhang
@ 2023-05-21 10:28     ` Marijn Suijten
  2023-05-23 20:33       ` Jessica Zhang
  2023-05-22  9:10     ` Neil Armstrong
  2 siblings, 1 reply; 25+ messages in thread
From: Marijn Suijten @ 2023-05-21 10:28 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Neil Armstrong, Krzysztof Kozlowski, devicetree, Sam Ravnborg,
	Abhinav Kumar, dri-devel, linux-kernel, Rob Herring,
	Jessica Zhang

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 <quic_jesszhan@quicinc.com>
> > ---
> >   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 <quic_jesszhan@quicinc.com>");
> > +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
> > +MODULE_LICENSE("GPL");
> > 
> 
> -- 
> With best wishes
> Dmitry
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-16 20:20 ` [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings Jessica Zhang
  2023-05-17  8:12   ` Krzysztof Kozlowski
@ 2023-05-21 10:30   ` Marijn Suijten
  2023-05-22  9:05     ` Neil Armstrong
  1 sibling, 1 reply; 25+ messages in thread
From: Marijn Suijten @ 2023-05-21 10:30 UTC (permalink / raw)
  To: Jessica Zhang
  Cc: Neil Armstrong, devicetree, Sam Ravnborg, Abhinav Kumar,
	dri-devel, linux-kernel, Rob Herring, Krzysztof Kozlowski

On 2023-05-16 13:20:30, Jessica Zhang wrote:
> Document the 1080x2340 Visionox R66451 AMOLED DSI panel bindings
> 
> Signed-off-by: Jessica Zhang <quic_jesszhan@quicinc.com>
> ---
>  .../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 <quic_jesszhan@quicinc.com>
> +
> +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
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-21 10:30   ` Marijn Suijten
@ 2023-05-22  9:05     ` Neil Armstrong
  2023-05-22 14:51       ` Marijn Suijten
  0 siblings, 1 reply; 25+ messages in thread
From: Neil Armstrong @ 2023-05-22  9:05 UTC (permalink / raw)
  To: Marijn Suijten, Jessica Zhang
  Cc: devicetree, Sam Ravnborg, Abhinav Kumar, dri-devel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski

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 <quic_jesszhan@quicinc.com>
>> ---
>>   .../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 <quic_jesszhan@quicinc.com>
>> +
>> +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
>>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-16 20:20 ` [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel Jessica Zhang
  2023-05-18  0:19   ` Dmitry Baryshkov
@ 2023-05-22  9:09   ` Neil Armstrong
  2023-05-22 17:35     ` Jessica Zhang
  1 sibling, 1 reply; 25+ messages in thread
From: Neil Armstrong @ 2023-05-22  9:09 UTC (permalink / raw)
  To: Jessica Zhang, Sam Ravnborg, David Airlie, Daniel Vetter,
	Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
> ---
>   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 <quic_jesszhan@quicinc.com>");
> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-18  0:19   ` Dmitry Baryshkov
  2023-05-18 22:17     ` Jessica Zhang
  2023-05-21 10:28     ` Marijn Suijten
@ 2023-05-22  9:10     ` Neil Armstrong
  2023-05-22 13:47       ` Dmitry Baryshkov
  2 siblings, 1 reply; 25+ messages in thread
From: Neil Armstrong @ 2023-05-22  9:10 UTC (permalink / raw)
  To: Dmitry Baryshkov, Jessica Zhang, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
>> ---
>>   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 <quic_jesszhan@quicinc.com>");
>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
>> +MODULE_LICENSE("GPL");
>>
> 


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-22  9:10     ` Neil Armstrong
@ 2023-05-22 13:47       ` Dmitry Baryshkov
  0 siblings, 0 replies; 25+ messages in thread
From: Dmitry Baryshkov @ 2023-05-22 13:47 UTC (permalink / raw)
  To: neil.armstrong, Jessica Zhang, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
>>> ---
>>>   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


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-22  9:05     ` Neil Armstrong
@ 2023-05-22 14:51       ` Marijn Suijten
  2023-05-26  7:42         ` Neil Armstrong
  0 siblings, 1 reply; 25+ messages in thread
From: Marijn Suijten @ 2023-05-22 14:51 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: devicetree, Sam Ravnborg, Abhinav Kumar, dri-devel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski, Jessica Zhang

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 <quic_jesszhan@quicinc.com>
> >> ---
> >>   .../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 <quic_jesszhan@quicinc.com>
> >> +
> >> +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>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-22  9:09   ` Neil Armstrong
@ 2023-05-22 17:35     ` Jessica Zhang
  0 siblings, 0 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-22 17:35 UTC (permalink / raw)
  To: neil.armstrong, Sam Ravnborg, David Airlie, Daniel Vetter,
	Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel



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 <quic_jesszhan@quicinc.com>
>> ---
>>   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 <quic_jesszhan@quicinc.com>");
>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI 
>> panel");
>> +MODULE_LICENSE("GPL");
>>
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-21 10:28     ` Marijn Suijten
@ 2023-05-23 20:33       ` Jessica Zhang
  2023-05-25 22:28         ` Jessica Zhang
  0 siblings, 1 reply; 25+ messages in thread
From: Jessica Zhang @ 2023-05-23 20:33 UTC (permalink / raw)
  To: Marijn Suijten, Dmitry Baryshkov
  Cc: Neil Armstrong, devicetree, Sam Ravnborg, Abhinav Kumar,
	dri-devel, linux-kernel, Rob Herring, Krzysztof Kozlowski



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 <quic_jesszhan@quicinc.com>
>>> ---
>>>    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 <quic_jesszhan@quicinc.com>");
>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
>>> +MODULE_LICENSE("GPL");
>>>
>>
>> -- 
>> With best wishes
>> Dmitry
>>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-23 20:33       ` Jessica Zhang
@ 2023-05-25 22:28         ` Jessica Zhang
  2023-05-26  7:32           ` Neil Armstrong
  0 siblings, 1 reply; 25+ messages in thread
From: Jessica Zhang @ 2023-05-25 22:28 UTC (permalink / raw)
  To: Marijn Suijten, Dmitry Baryshkov
  Cc: Neil Armstrong, devicetree, linux-kernel, dri-devel,
	Abhinav Kumar, Rob Herring, Krzysztof Kozlowski, Sam Ravnborg



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 <quic_jesszhan@quicinc.com>
>>>> ---
>>>>    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 <quic_jesszhan@quicinc.com>");
>>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI 
>>>> panel");
>>>> +MODULE_LICENSE("GPL");
>>>>
>>>
>>> -- 
>>> With best wishes
>>> Dmitry
>>>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-25 22:28         ` Jessica Zhang
@ 2023-05-26  7:32           ` Neil Armstrong
  2023-05-29 12:09             ` Marijn Suijten
  0 siblings, 1 reply; 25+ messages in thread
From: Neil Armstrong @ 2023-05-26  7:32 UTC (permalink / raw)
  To: Jessica Zhang, Marijn Suijten, Dmitry Baryshkov
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Sam Ravnborg

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 <quic_jesszhan@quicinc.com>
>>>>> ---
>>>>>    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 <quic_jesszhan@quicinc.com>");
>>>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
>>>>> +MODULE_LICENSE("GPL");
>>>>>
>>>>
>>>> -- 
>>>> With best wishes
>>>> Dmitry
>>>>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-22 14:51       ` Marijn Suijten
@ 2023-05-26  7:42         ` Neil Armstrong
  2023-05-29 12:05           ` Marijn Suijten
  0 siblings, 1 reply; 25+ messages in thread
From: Neil Armstrong @ 2023-05-26  7:42 UTC (permalink / raw)
  To: Marijn Suijten
  Cc: devicetree, Sam Ravnborg, Abhinav Kumar, dri-devel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski, Jessica Zhang

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 <quic_jesszhan@quicinc.com>
>>>> ---
>>>>    .../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 <quic_jesszhan@quicinc.com>
>>>> +
>>>> +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>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings
  2023-05-26  7:42         ` Neil Armstrong
@ 2023-05-29 12:05           ` Marijn Suijten
  0 siblings, 0 replies; 25+ messages in thread
From: Marijn Suijten @ 2023-05-29 12:05 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: devicetree, Sam Ravnborg, Abhinav Kumar, dri-devel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski, Jessica Zhang

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 <quic_jesszhan@quicinc.com>
> >>>> ---
> >>>>    .../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 <quic_jesszhan@quicinc.com>
> >>>> +
> >>>> +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>
> 

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-26  7:32           ` Neil Armstrong
@ 2023-05-29 12:09             ` Marijn Suijten
  2023-05-30 17:43               ` Jessica Zhang
  0 siblings, 1 reply; 25+ messages in thread
From: Marijn Suijten @ 2023-05-29 12:09 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Dmitry Baryshkov, Jessica Zhang,
	Sam Ravnborg

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

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-29 12:09             ` Marijn Suijten
@ 2023-05-30 17:43               ` Jessica Zhang
  0 siblings, 0 replies; 25+ messages in thread
From: Jessica Zhang @ 2023-05-30 17:43 UTC (permalink / raw)
  To: Marijn Suijten, Neil Armstrong
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Dmitry Baryshkov, Sam Ravnborg



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

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-18 22:17     ` Jessica Zhang
  2023-05-18 22:37       ` Dmitry Baryshkov
@ 2023-05-30 20:09       ` Jessica Zhang
  2023-05-30 23:12         ` Dmitry Baryshkov
  1 sibling, 1 reply; 25+ messages in thread
From: Jessica Zhang @ 2023-05-30 20:09 UTC (permalink / raw)
  To: Dmitry Baryshkov, Neil Armstrong, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel



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 <quic_jesszhan@quicinc.com>
>>> ---
>>>   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 <quic_jesszhan@quicinc.com>");
>>> +MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI 
>>> panel");
>>> +MODULE_LICENSE("GPL");
>>>
>>
>> -- 
>> With best wishes
>> Dmitry
>>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel
  2023-05-30 20:09       ` Jessica Zhang
@ 2023-05-30 23:12         ` Dmitry Baryshkov
  0 siblings, 0 replies; 25+ messages in thread
From: Dmitry Baryshkov @ 2023-05-30 23:12 UTC (permalink / raw)
  To: Jessica Zhang, Neil Armstrong, Sam Ravnborg, David Airlie,
	Daniel Vetter, Rob Herring, Krzysztof Kozlowski
  Cc: devicetree, Abhinav Kumar, dri-devel, linux-kernel

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 <quic_jesszhan@quicinc.com>
>>>> ---
>>>>   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


^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2023-05-30 23:12 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-05-16 20:20 [PATCH 0/2] Add support for Visionox R66451 AMOLED DSI panel Jessica Zhang
2023-05-16 20:20 ` [PATCH 1/2] dt-bindings: display: panel: Add Visionox R66451 AMOLED DSI panel bindings Jessica Zhang
2023-05-17  8:12   ` Krzysztof Kozlowski
2023-05-21 10:30   ` Marijn Suijten
2023-05-22  9:05     ` Neil Armstrong
2023-05-22 14:51       ` Marijn Suijten
2023-05-26  7:42         ` Neil Armstrong
2023-05-29 12:05           ` Marijn Suijten
2023-05-16 20:20 ` [PATCH 2/2] drm/panel: Add driver for Visionox r66451 panel Jessica Zhang
2023-05-18  0:19   ` Dmitry Baryshkov
2023-05-18 22:17     ` Jessica Zhang
2023-05-18 22:37       ` Dmitry Baryshkov
2023-05-19  0:28         ` Jessica Zhang
2023-05-30 20:09       ` Jessica Zhang
2023-05-30 23:12         ` Dmitry Baryshkov
2023-05-21 10:28     ` Marijn Suijten
2023-05-23 20:33       ` Jessica Zhang
2023-05-25 22:28         ` Jessica Zhang
2023-05-26  7:32           ` Neil Armstrong
2023-05-29 12:09             ` Marijn Suijten
2023-05-30 17:43               ` Jessica Zhang
2023-05-22  9:10     ` Neil Armstrong
2023-05-22 13:47       ` Dmitry Baryshkov
2023-05-22  9:09   ` Neil Armstrong
2023-05-22 17:35     ` Jessica Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).