Add support for the DSI panels used in the virtual reality device.
The device features two physical panels, each providing a MIPI DSI
interface and a built-in LED backlight and expose a single logical
panel to the DRM framework.

Signed-off-by: Jun Nie <[email protected]>
---
 drivers/gpu/drm/panel/Kconfig                  |   9 +
 drivers/gpu/drm/panel/Makefile                 |   1 +
 drivers/gpu/drm/panel/panel-synaptics-r63455.c | 631 +++++++++++++++++++++++++
 3 files changed, 641 insertions(+)

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 7c2d9feb4064e..1ef3f51ec6593 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -1227,6 +1227,15 @@ config DRM_PANEL_SYNAPTICS_TDDI
          namesake, with varying resolutions and data lanes. They also have a
          built-in LED backlight and a touch controller.
 
+config DRM_PANEL_SYNAPTICS_R63455
+       tristate "Synaptics R63455-based panels"
+       depends on OF
+       depends on DRM_MIPI_DSI
+       depends on BACKLIGHT_CLASS_DEVICE
+       help
+         Say Y if you want to enable support for panels based on the
+         Synaptics R63455 controller.
+
 config DRM_PANEL_TDO_TL070WSH30
        tristate "TDO TL070WSH30 DSI panel"
        depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index dc8a05f2c34d9..ee6b41151804c 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -115,6 +115,7 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += 
panel-sitronix-st7789v.o
 obj-$(CONFIG_DRM_PANEL_SUMMIT) += panel-summit.o
 obj-$(CONFIG_DRM_PANEL_SYNAPTICS_R63353) += panel-synaptics-r63353.o
 obj-$(CONFIG_DRM_PANEL_SYNAPTICS_TDDI) += panel-synaptics-tddi.o
+obj-$(CONFIG_DRM_PANEL_SYNAPTICS_R63455) += panel-synaptics-r63455.o
 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
 obj-$(CONFIG_DRM_PANEL_SONY_TD4353_JDI) += panel-sony-td4353-jdi.o
 obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += 
panel-sony-tulip-truly-nt35521.o
diff --git a/drivers/gpu/drm/panel/panel-synaptics-r63455.c 
b/drivers/gpu/drm/panel/panel-synaptics-r63455.c
new file mode 100644
index 0000000000000..e429c28c7efc9
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-synaptics-r63455.c
@@ -0,0 +1,631 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026, Linaro Ltd. 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/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#define R63455_MF_CMD_ACCESS_PROTECT   0xb0
+#define R63455_SEQ_CTL                 0xd6
+#define R63455_DSI_CTL                 0xb6
+#define R63455_DISP_MODE               0xb7
+#define R63455_GEN_OUTPIN_SET          0xb9
+#define R63455_DISP_SET1               0xc0
+#define R63455_DISP_SET2               0xf1
+#define R63455_DISP_SET3               0xc6
+#define R63455_DISP_SET3_2             0xcd
+#define R63455_DISP_SET4               0xcf
+#define R63455_DISP_SET5               0xec
+#define R63455_DISP_SET6               0xef
+#define R63455_TE_GPIO_CTL             0xbe
+#define R63455_PPS_SET                 0xe6
+
+#define BRIGHTNESS_DEFAULT             256
+#define BRIGHTNESS_MAX_120             367
+
+#define VBP                            100
+#define VFP                            700
+#define VID_VS_DELAY                   1112
+#define GPO1_TES1                      4068
+#define RTN                            59
+
+static const char * const vdda_driver[] = {
+       "vdda",
+};
+
+static const char * const dual_backlight_driver[] = {
+       "left-backlight",
+       "right-backlight",
+};
+
+static const char * const dual_lcd_bias_pos[] = {
+       "left-pos",
+       "right-pos",
+};
+
+static const char * const dual_lcd_bias_neg[] = {
+       "left-neg",
+       "right-neg"
+};
+
+struct r63455_ctx {
+       struct device *dev;
+       struct drm_panel panel;
+
+       struct regulator_bulk_data reg_vdda[ARRAY_SIZE(vdda_driver)];
+       struct regulator_bulk_data reg_bl[ARRAY_SIZE(dual_backlight_driver)];
+       struct regulator_bulk_data 
reg_lcd_bias_pos[ARRAY_SIZE(dual_lcd_bias_pos)];
+       struct regulator_bulk_data 
reg_lcd_bias_neg[ARRAY_SIZE(dual_lcd_bias_neg)];
+
+       struct gpio_descs *reset_gpios;
+       int (*dsi_init_seq)(struct drm_panel *panel);
+       struct backlight_device *backlight;
+       struct mipi_dsi_device *dsi[2];
+       struct drm_dsc_config dsc_cfg;
+};
+
+static inline struct r63455_ctx *panel_to_ctx(struct drm_panel *panel)
+{
+       return container_of(panel, struct r63455_ctx, panel);
+}
+
+static const struct drm_display_mode modes = {
+       .name = "4320x2160_120",
+       .clock = (2160 + 24 + 20 + 20) * (2160 + 404 + 1 + 20) * 120 * 2 / 1000,
+       .hdisplay = 2160 * 2,
+       .hsync_start = (2160 + 24) * 2,
+       .hsync_end = (2160 + 24 + 20) * 2,
+       .htotal = (2160 + 24 + 20 + 20) * 2,
+       .vdisplay = 2160,
+       .vsync_start = 2160 + 404,
+       .vsync_end = 2160 + 404 + 1,
+       .vtotal = 2160 + 404 + 1 + 20,
+       .width_mm = 50 * 2,
+       .height_mm = 50,
+       .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+#define r63455_dsi_write_seq(ctx, dsi_ctx, cmd, seq...)                        
                \
+       do {                                                                    
        \
+               u8 d[] = {cmd, seq};                                            
        \
+               dsi_ctx.dsi = ctx->dsi[0];                                      
        \
+               mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, d, ARRAY_SIZE(d));    
        \
+               dsi_ctx.dsi = ctx->dsi[1];                                      
        \
+               mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, d, ARRAY_SIZE(d));    
        \
+       } while (0)
+
+#define r63455_dsi_write_buffer(ctx, dsi_ctx, d)                               
        \
+       do {                                                                    
        \
+               dsi_ctx.dsi = ctx->dsi[0];                                      
        \
+               mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, d, ARRAY_SIZE(d));    
        \
+               dsi_ctx.dsi = ctx->dsi[1];                                      
        \
+               mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, d, ARRAY_SIZE(d));    
        \
+       } while (0)
+
+#define LE16_BYTE0(val) (le16_to_cpu(val) & 0xff)
+#define LE16_BYTE1(val) ((le16_to_cpu(val) >> 8) & 0xff)
+#define BE16_BYTE0(val) (be16_to_cpu(val) & 0xff)
+#define BE16_BYTE1(val) ((be16_to_cpu(val) >> 8) & 0xff)
+
+static int r63455_dsi_populate_dsc_params(struct r63455_ctx *ctx)
+{
+       int ret;
+       struct drm_dsc_config *dsc = &ctx->dsc_cfg;
+
+       dsc->simple_422 = 0;
+       dsc->convert_rgb = 1;
+       dsc->vbr_enable = 0;
+
+       drm_dsc_set_const_params(dsc);
+       drm_dsc_set_rc_buf_thresh(dsc);
+
+       /* handle only bpp = bpc = 8, pre-SCR panels */
+       ret = drm_dsc_setup_rc_params(dsc, DRM_DSC_1_1_PRE_SCR);
+       if (ret < 0)
+               DRM_DEV_ERROR(ctx->dev, "failed to setup dsc params\n");
+
+       dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
+       dsc->line_buf_depth = dsc->bits_per_component + 1;
+
+       return drm_dsc_compute_rc_parameters(dsc);
+}
+
+static int r63455_init_dsc_config(struct r63455_ctx *ctx)
+{
+       ctx->dsc_cfg = (struct drm_dsc_config) {
+               .dsc_version_major = 0x1,
+               .dsc_version_minor = 0x2,
+               .dsc_slice_per_pkt = 4,
+               .slice_height = 48,
+               .slice_width = 540,
+               .slice_count = 4,
+               .bits_per_component = 8,
+               .bits_per_pixel = 8 << 4,
+               .block_pred_enable = true,
+
+               .pic_width = 2160,
+               .pic_height = 2160,
+       };
+
+       return r63455_dsi_populate_dsc_params(ctx);
+}
+
+static int r63455_panel_on_sharp_ls026b3sa06(struct drm_panel *panel)
+{
+       struct r63455_ctx *ctx = panel_to_ctx(panel);
+       struct mipi_dsi_multi_context dsi_ctx = { 0 };
+
+       u8 pps_cmd[1 + sizeof(struct drm_dsc_picture_parameter_set)];
+
+       drm_dsc_pps_payload_pack((void *)&pps_cmd[1], &ctx->dsc_cfg);
+       pps_cmd[0] = R63455_PPS_SET;
+
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_MF_CMD_ACCESS_PROTECT, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_SEQ_CTL, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx,
+                            R63455_DSI_CTL,
+                            0x20, 0x6b, 0x80, 0x06, 0x33, 0x9a, 0x00, 0x1a,
+                            0x7a);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_MODE,
+                            0x54, 0x00, 0x00, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_GEN_OUTPIN_SET,
+                            0xf, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0xf, 0xb2, 0x00, 0x64);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET3,
+                            0x08, 0x70, 0x28, 0x48, 0x00, 0x00, 0x13, 0x21,
+                            0xff, 0x00, 0x0f, 0x01, 0x14, 0x17, 0x00, 0x00,
+                            0x00, 0x02, 0x40, 0x0C, 0x00, 0x00, 0x00, 0x20,
+                            0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x70, 0x08,
+                            0xD0, 0x02, 0x21, 0x6F, 0x08, 0x5A, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET1,
+                            RTN, 0x86, LE16_BYTE0(VBP), LE16_BYTE1(VBP), 0x08,
+                            0x70, BE16_BYTE0(VFP), BE16_BYTE1(VFP), 0x00,
+                            0x00, 0x08, 0x3B, 0x00, 0x00, 0x19, 0x01, 0x22);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET3_2, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET4,
+                            0x8b, 0x00, 0x80, 0x46, 0x61, 0x00, 0x8b);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET5,
+                            BE16_BYTE0(VID_VS_DELAY),
+                            BE16_BYTE1(VID_VS_DELAY),
+                            0x00, 0x00, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET6,
+                            0x00, 0x24, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x0A, 0x0A, 0x00, 0x00, 0x00, 0x03,
+                            0x1D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+                            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_TE_GPIO_CTL,
+                            0x00, 0x6A, 0x02);
+       r63455_dsi_write_buffer(ctx, dsi_ctx, pps_cmd);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_TEAR_ON, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x40);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+       mipi_dsi_msleep(&dsi_ctx, 170);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_DISPLAY_ON);
+       mipi_dsi_msleep(&dsi_ctx, 200);
+
+       return dsi_ctx.accum_err;
+}
+
+static int r63455_panel_on_boe_vs026c4m_n52_26000(struct drm_panel *panel)
+{
+       struct r63455_ctx *ctx = panel_to_ctx(panel);
+       struct mipi_dsi_multi_context dsi_ctx = { 0 };
+
+       u8 pps_cmd[1 + sizeof(struct drm_dsc_picture_parameter_set)];
+
+       drm_dsc_pps_payload_pack((void *)&pps_cmd[1], &ctx->dsc_cfg);
+       pps_cmd[0] = R63455_PPS_SET;
+
+       r63455_dsi_write_seq(ctx, dsi_ctx,
+                            R63455_MF_CMD_ACCESS_PROTECT, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_SEQ_CTL, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DSI_CTL,
+                            0x20, 0x6b, 0x80, 0x06, 0x33, 0x9A, 0x00,
+                            0x1a, 0x7a);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_MODE,
+                            0x54, 0x00, 0x00, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_GEN_OUTPIN_SET,
+                            0xf, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0xf, 0xb2,
+                            0xf, 0xb2, 0x00, 0x64);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET3,
+                            0x0B, 0x70, 0x08, 0x48, 0x00, 0x00, 0x13, 0x01,
+                            0xFF, 0x00, 0x01, 0x09, 0x16, 0x16, 0x00, 0x00,
+                            0x00, 0x01, 0x40, 0x0C, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x70, 0x08,
+                            0xD0, 0x02, 0x21, 0x6F, 0x08, 0x5A, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET1,
+                            RTN, 0x86, LE16_BYTE0(VBP), LE16_BYTE1(VBP), 0x08,
+                            0x70, BE16_BYTE0(VFP), BE16_BYTE1(VFP), 0x00,
+                            0x00, 0x08, 0x3A, 0x00, 0x00, 0x17, 0x00, 0x17);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET3_2, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET4,
+                            0x8b, 0x00, 0x80, 0x46, 0x61, 0x00, 0x8b);
+       r63455_dsi_write_seq(ctx, dsi_ctx, R63455_DISP_SET5,
+                            BE16_BYTE0(VID_VS_DELAY), BE16_BYTE1(VID_VS_DELAY),
+                            0x00, 0x00, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx,
+                            R63455_TE_GPIO_CTL, 0x00, 0x6A, 0x02);
+       r63455_dsi_write_buffer(ctx, dsi_ctx, pps_cmd);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_TEAR_ON, 0x00);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_EXIT_SLEEP_MODE);
+       mipi_dsi_msleep(&dsi_ctx, 170);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_DISPLAY_ON);
+       mipi_dsi_msleep(&dsi_ctx, 200);
+
+       return dsi_ctx.accum_err;
+}
+
+static int r63455_set_brightness(struct r63455_ctx *ctx, u16 brightness,
+                                 u16 pulse_offset_rows)
+{
+       struct mipi_dsi_multi_context dsi_ctx = { 0 };
+       u16 gpo1_tew1;
+
+       gpo1_tew1 = brightness > BRIGHTNESS_MAX_120 ?
+                   BRIGHTNESS_MAX_120 : brightness;
+
+       ctx->dsi[0]->mode_flags &= ~MIPI_DSI_MODE_LPM;
+       ctx->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+       r63455_dsi_write_seq(ctx, dsi_ctx,
+                            R63455_GEN_OUTPIN_SET,
+                            0x0f, 0xe4,
+                            BE16_BYTE0(gpo1_tew1),
+                            BE16_BYTE1(gpo1_tew1),
+                            0x00, 0x00, 0x00, 0x00, 0x0f, 0xb2,
+                            BE16_BYTE0(brightness),
+                            BE16_BYTE1(brightness));
+
+       ctx->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM;
+       ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM;
+
+       return dsi_ctx.accum_err;
+}
+
+static int r63455_disable(struct drm_panel *panel)
+{
+       struct r63455_ctx *ctx = panel_to_ctx(panel);
+       struct mipi_dsi_multi_context dsi_ctx = { 0 };
+
+       ctx->dsi[0]->mode_flags &= ~MIPI_DSI_MODE_LPM;
+       ctx->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_DISPLAY_OFF);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_SET_TEAR_OFF);
+       r63455_dsi_write_seq(ctx, dsi_ctx, MIPI_DCS_ENTER_SLEEP_MODE);
+
+       mipi_dsi_msleep(&dsi_ctx, 200);
+
+       if (dsi_ctx.accum_err)
+               return dsi_ctx.accum_err;
+
+       return backlight_disable(ctx->backlight);
+}
+
+static int r63455_unprepare(struct drm_panel *panel)
+{
+       struct r63455_ctx *ctx = panel_to_ctx(panel);
+       int ret;
+
+       gpiod_set_value(ctx->reset_gpios->desc[0], 0);
+       gpiod_set_value(ctx->reset_gpios->desc[1], 0);
+
+       msleep(25);
+       ret = regulator_bulk_disable(ARRAY_SIZE(ctx->reg_lcd_bias_neg),
+                                    ctx->reg_lcd_bias_neg);
+       if (ret)
+               dev_err(ctx->dev, "Could not disable bias negative\n");
+
+       usleep_range(1000, 2000);
+       ret = regulator_bulk_disable(ARRAY_SIZE(ctx->reg_lcd_bias_pos),
+                                    ctx->reg_lcd_bias_pos);
+       if (ret)
+               dev_err(ctx->dev, "Could not disable bias positive\n");
+
+       msleep(20);
+
+       ret = regulator_bulk_disable(ARRAY_SIZE(ctx->reg_vdda), ctx->reg_vdda);
+       if (ret)
+               dev_err(ctx->dev, "Could not disable vdda\n");
+
+       ret = regulator_bulk_disable(ARRAY_SIZE(ctx->reg_bl), ctx->reg_bl);
+       if (ret)
+               dev_err(ctx->dev,
+                       "failed to disable regulator backlight: %d\n", ret);
+
+       return ret;
+}
+
+static int r63455_enable(struct drm_panel *panel)
+{
+       struct r63455_ctx *ctx = panel_to_ctx(panel);
+       int ret;
+
+       ctx->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM;
+       ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM;
+
+       msleep(20);
+       ret = ctx->dsi_init_seq(panel);
+       if (ret < 0) {
+               DRM_DEV_ERROR(ctx->dev, "dsi init failed\n");
+               return ret;
+       }
+       ctx->backlight->props.pulse_offset_rows = GPO1_TES1;
+
+       ret = backlight_enable(ctx->backlight);
+       if (ret < 0) {
+               DRM_DEV_ERROR(ctx->dev, "failed to enable backlight\n");
+               return ret;
+       }
+
+       return ret;
+}
+
+static int r63455_prepare(struct drm_panel *panel)
+{
+       struct r63455_ctx *ctx = panel_to_ctx(panel);
+       int ret;
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(ctx->reg_bl), ctx->reg_bl);
+       if (ret) {
+               dev_err(ctx->dev, "Could not enable backlight\n");
+               return ret;
+       }
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(ctx->reg_vdda), ctx->reg_vdda);
+       if (ret) {
+               dev_err(ctx->dev, "Could not enable vdda\n");
+               goto bl_off;
+       }
+
+       usleep_range(1000, 2000);
+       ret = regulator_bulk_enable(ARRAY_SIZE(ctx->reg_lcd_bias_pos),
+                                   ctx->reg_lcd_bias_pos);
+       if (ret) {
+               dev_err(ctx->dev, "Could not enable bias positive\n");
+               goto vdda_off;
+       }
+       usleep_range(1000, 2000);
+       ret = regulator_bulk_enable(ARRAY_SIZE(ctx->reg_lcd_bias_neg),
+                                   ctx->reg_lcd_bias_neg);
+       if (ret) {
+               dev_err(ctx->dev, "Could not enable bias negative\n");
+               goto bias_off;
+       }
+
+       msleep(20);
+       gpiod_set_value(ctx->reset_gpios->desc[0], 1);
+       gpiod_set_value(ctx->reset_gpios->desc[1], 1);
+
+       return 0;
+
+bl_off:
+       regulator_bulk_disable(ARRAY_SIZE(ctx->reg_bl), ctx->reg_bl);
+vdda_off:
+       regulator_bulk_disable(ARRAY_SIZE(ctx->reg_vdda), ctx->reg_vdda);
+bias_off:
+       regulator_bulk_disable(ARRAY_SIZE(ctx->reg_lcd_bias_pos),
+                              ctx->reg_lcd_bias_pos);
+       return ret;
+}
+
+static int r63455_bl_update_status(struct backlight_device *bl)
+{
+       struct mipi_dsi_device *dsi = bl_get_data(bl);
+       struct r63455_ctx *r63455_ctx = mipi_dsi_get_drvdata(dsi);
+
+       return r63455_set_brightness(r63455_ctx, bl->props.brightness,
+                                    bl->props.pulse_offset_rows);
+}
+
+static const struct backlight_ops r63455_bl_ops = {
+       .update_status = r63455_bl_update_status,
+};
+
+static int r63455_get_modes(struct drm_panel *panel,
+                                struct drm_connector *connector)
+{
+       return drm_connector_helper_get_modes_fixed(connector, &modes);
+}
+
+static const struct drm_panel_funcs r63455_drm_funcs = {
+       .disable = r63455_disable,
+       .enable = r63455_enable,
+       .unprepare = r63455_unprepare,
+       .prepare = r63455_prepare,
+       .get_modes = r63455_get_modes,
+};
+
+static int r63455_panel_add(struct r63455_ctx *ctx)
+{
+       struct device *dev = ctx->dev;
+       struct backlight_properties bl_props;
+       int ret, i;
+
+       for (i = 0; i < ARRAY_SIZE(ctx->reg_vdda); i++)
+               ctx->reg_vdda[i].supply = vdda_driver[i];
+
+       ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->reg_vdda),
+                                     ctx->reg_vdda);
+       if (ret < 0)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(dual_backlight_driver); i++)
+               ctx->reg_bl[i].supply = dual_backlight_driver[i];
+
+       ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->reg_bl),
+                                     ctx->reg_bl);
+       if (ret < 0)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(ctx->reg_lcd_bias_pos); i++)
+               ctx->reg_lcd_bias_pos[i].supply = dual_lcd_bias_pos[i];
+
+       ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->reg_lcd_bias_pos),
+                                     ctx->reg_lcd_bias_pos);
+       if (ret < 0)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(ctx->reg_lcd_bias_neg); i++)
+               ctx->reg_lcd_bias_neg[i].supply = dual_lcd_bias_neg[i];
+
+       ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->reg_lcd_bias_neg),
+                                     ctx->reg_lcd_bias_neg);
+       if (ret < 0)
+               return ret;
+
+       ctx->reset_gpios = devm_gpiod_get_array(dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(ctx->reset_gpios))
+               return PTR_ERR(ctx->reset_gpios);
+
+       memset(&bl_props, 0, sizeof(bl_props));
+       bl_props.type = BACKLIGHT_RAW;
+       bl_props.brightness = BRIGHTNESS_DEFAULT;
+       bl_props.max_brightness = BRIGHTNESS_MAX_120;
+
+       ctx->backlight = devm_backlight_device_register(dev, dev_name(dev),
+                                                       dev, ctx->dsi[0],
+                                                       &r63455_bl_ops,
+                                                       &bl_props);
+       if (IS_ERR(ctx->backlight)) {
+               ret = PTR_ERR(ctx->backlight);
+               dev_err(dev, "Failed to register backlight (%d)\n", ret);
+               return ret;
+       }
+
+       ctx->panel.prepare_prev_first = true;
+       return devm_drm_panel_add(dev, &ctx->panel);
+}
+
+static int r63455_probe(struct mipi_dsi_device *dsi)
+{
+       struct device *dev = &dsi->dev;
+       struct r63455_ctx *ctx;
+       struct mipi_dsi_device *dsi1_device;
+       struct device_node *dsi1;
+       struct mipi_dsi_host *dsi1_host;
+       struct mipi_dsi_device *dsi_dev;
+       int ret = 0;
+       int i;
+
+       const struct mipi_dsi_device_info info = {
+               .type = "r63455-dsi-panel",
+               .channel = 0,
+               .node = NULL,
+       };
+
+       ctx = devm_drm_panel_alloc(&dsi->dev, __typeof(*ctx), panel,
+                                  &r63455_drm_funcs, DRM_MODE_CONNECTOR_DSI);
+
+       ctx->dsi_init_seq = of_device_get_match_data(dev);
+       if (!ctx->dsi_init_seq)
+               return -ENODEV;
+
+       dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1);
+       if (!dsi1) {
+               DRM_DEV_ERROR(dev, "failed to get secondary dsi\n");
+               return -ENODEV;
+       }
+
+       dsi1_host = of_find_mipi_dsi_host_by_node(dsi1);
+       of_node_put(dsi1);
+       if (!dsi1_host) {
+               DRM_DEV_ERROR(dev, "failed to find secondary dsi host\n");
+               return -EPROBE_DEFER;
+       }
+
+       dsi1_device = mipi_dsi_device_register_full(dsi1_host, &info);
+       if (IS_ERR(dsi1_device)) {
+               DRM_DEV_ERROR(dev, "failed to create dsi device\n");
+               return PTR_ERR(dsi1_device);
+       }
+
+       ctx->dsi[1] = dsi1_device;
+       mipi_dsi_set_drvdata(dsi, ctx);
+       ctx->dev = dev;
+       ctx->dsi[0] = dsi;
+
+       ret = r63455_init_dsc_config(ctx);
+       if (ret)
+               return ret;
+
+       ctx->dsi[0]->dsc = &ctx->dsc_cfg;
+       ctx->dsi[0]->dual_panel = true;
+       ctx->dsi[1]->dual_panel = true;
+       ctx->dsi[1]->dsc = &ctx->dsc_cfg;
+
+       ret = r63455_panel_add(ctx);
+       if (ret) {
+               DRM_DEV_ERROR(dev, "failed to add panel %d\n", ret);
+               return ret;
+       }
+
+       for (i = 0; i < 2; i++) {
+               dsi_dev = ctx->dsi[i];
+               dsi_dev->lanes = 3;
+               dsi_dev->format = MIPI_DSI_FMT_RGB888;
+               dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM |
+                       MIPI_DSI_CLOCK_NON_CONTINUOUS;
+               ret = devm_mipi_dsi_attach(dev, dsi_dev);
+               if (ret < 0) {
+                       DRM_DEV_ERROR(dev, "dsi attach failed i = %d\n", i);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static const struct of_device_id r63455_of_match[] = {
+       {
+               .compatible = "sharp,ls026b3sa06",
+               .data = r63455_panel_on_sharp_ls026b3sa06,
+       },
+       {
+               .compatible = "boe,vs026c4m-n52-6000",
+               .data = r63455_panel_on_boe_vs026c4m_n52_26000,
+       },
+       { }
+};
+MODULE_DEVICE_TABLE(of, r63455_of_match);
+
+static struct mipi_dsi_driver r63455_driver = {
+       .driver = {
+               .name = "panel-synaptics-r63455",
+               .of_match_table = r63455_of_match,
+       },
+       .probe = r63455_probe,
+};
+module_mipi_dsi_driver(r63455_driver);
+
+MODULE_DESCRIPTION("Synaptics R63455 DSI Panel Driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0

Reply via email to