mbox series

[0/2] Add support for Visionox R66451 AMOLED DSI panel

Message ID 20230516-b4-r66451-panel-driver-v1-0-4210bcbb1649@quicinc.com
Headers show
Series Add support for Visionox R66451 AMOLED DSI panel | expand

Message

Jessica Zhang May 16, 2023, 8:20 p.m. UTC
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,

Comments

Dmitry Baryshkov May 18, 2023, 12:19 a.m. UTC | #1
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");
>
Jessica Zhang May 18, 2023, 10:17 p.m. UTC | #2
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
>
Dmitry Baryshkov May 18, 2023, 10:37 p.m. UTC | #3
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
> >
Jessica Zhang May 19, 2023, 12:28 a.m. UTC | #4
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
Marijn Suijten May 21, 2023, 10:28 a.m. UTC | #5
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
>
Neil Armstrong May 22, 2023, 9:09 a.m. UTC | #6
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");
>
Neil Armstrong May 22, 2023, 9:10 a.m. UTC | #7
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");
>>
>
Dmitry Baryshkov May 22, 2023, 1:47 p.m. UTC | #8
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?
Jessica Zhang May 22, 2023, 5:35 p.m. UTC | #9
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");
>>
>
Jessica Zhang May 23, 2023, 8:33 p.m. UTC | #10
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
>>
Jessica Zhang May 25, 2023, 10:28 p.m. UTC | #11
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
>>>
Neil Armstrong May 26, 2023, 7:32 a.m. UTC | #12
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
>>>>
Marijn Suijten May 29, 2023, 12:09 p.m. UTC | #13
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
Jessica Zhang May 30, 2023, 5:43 p.m. UTC | #14
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
Jessica Zhang May 30, 2023, 8:09 p.m. UTC | #15
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
>>
Dmitry Baryshkov May 30, 2023, 11:12 p.m. UTC | #16
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.
Jessica Zhang May 31, 2023, 5:45 p.m. UTC | #17
On 5/30/2023 4:12 PM, Dmitry Baryshkov wrote:
> On 30/05/2023 23:09, Jessica Zhang wrote:
>>
>>
>> On 5/18/2023 3:17 PM, Jessica Zhang wrote:
>>>
>>>
>>> On 5/17/2023 5:19 PM, Dmitry Baryshkov wrote:
>>>> On 16/05/2023 23:20, Jessica Zhang wrote:
>>>>> Add support for the 1080x2340 Visionox R66451 AMOLED DSI panel that
>>>>> comes with the Qualcomm HDK8350 display expansion pack.
>>>>>
>>>>> The panel enables display compression (DSC v1.2) by default.
>>>>>
>>>>> Signed-off-by: Jessica Zhang <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?

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

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

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

Thanks,

Jessica Zhang

> 
> -- 
> With best wishes
> Dmitry
>