Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (c) 2018, The Linux Foundation. All rights reserved.
0004  */
0005 
0006 #include <linux/backlight.h>
0007 #include <linux/delay.h>
0008 #include <linux/gpio/consumer.h>
0009 #include <linux/module.h>
0010 #include <linux/of_device.h>
0011 #include <linux/of_graph.h>
0012 #include <linux/pinctrl/consumer.h>
0013 #include <linux/regulator/consumer.h>
0014 
0015 #include <video/mipi_display.h>
0016 
0017 #include <drm/drm_mipi_dsi.h>
0018 #include <drm/drm_modes.h>
0019 #include <drm/drm_panel.h>
0020 
0021 static const char * const regulator_names[] = {
0022     "vdda",
0023     "vdispp",
0024     "vdispn",
0025 };
0026 
0027 static unsigned long const regulator_enable_loads[] = {
0028     62000,
0029     100000,
0030     100000,
0031 };
0032 
0033 static unsigned long const regulator_disable_loads[] = {
0034     80,
0035     100,
0036     100,
0037 };
0038 
0039 struct cmd_set {
0040     u8 commands[4];
0041     u8 size;
0042 };
0043 
0044 struct nt35597_config {
0045     u32 width_mm;
0046     u32 height_mm;
0047     const char *panel_name;
0048     const struct cmd_set *panel_on_cmds;
0049     u32 num_on_cmds;
0050     const struct drm_display_mode *dm;
0051 };
0052 
0053 struct truly_nt35597 {
0054     struct device *dev;
0055     struct drm_panel panel;
0056 
0057     struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)];
0058 
0059     struct gpio_desc *reset_gpio;
0060     struct gpio_desc *mode_gpio;
0061 
0062     struct backlight_device *backlight;
0063 
0064     struct mipi_dsi_device *dsi[2];
0065 
0066     const struct nt35597_config *config;
0067     bool prepared;
0068     bool enabled;
0069 };
0070 
0071 static inline struct truly_nt35597 *panel_to_ctx(struct drm_panel *panel)
0072 {
0073     return container_of(panel, struct truly_nt35597, panel);
0074 }
0075 
0076 static const struct cmd_set qcom_2k_panel_magic_cmds[] = {
0077     /* CMD2_P0 */
0078     { { 0xff, 0x20 }, 2 },
0079     { { 0xfb, 0x01 }, 2 },
0080     { { 0x00, 0x01 }, 2 },
0081     { { 0x01, 0x55 }, 2 },
0082     { { 0x02, 0x45 }, 2 },
0083     { { 0x05, 0x40 }, 2 },
0084     { { 0x06, 0x19 }, 2 },
0085     { { 0x07, 0x1e }, 2 },
0086     { { 0x0b, 0x73 }, 2 },
0087     { { 0x0c, 0x73 }, 2 },
0088     { { 0x0e, 0xb0 }, 2 },
0089     { { 0x0f, 0xae }, 2 },
0090     { { 0x11, 0xb8 }, 2 },
0091     { { 0x13, 0x00 }, 2 },
0092     { { 0x58, 0x80 }, 2 },
0093     { { 0x59, 0x01 }, 2 },
0094     { { 0x5a, 0x00 }, 2 },
0095     { { 0x5b, 0x01 }, 2 },
0096     { { 0x5c, 0x80 }, 2 },
0097     { { 0x5d, 0x81 }, 2 },
0098     { { 0x5e, 0x00 }, 2 },
0099     { { 0x5f, 0x01 }, 2 },
0100     { { 0x72, 0x11 }, 2 },
0101     { { 0x68, 0x03 }, 2 },
0102     /* CMD2_P4 */
0103     { { 0xFF, 0x24 }, 2 },
0104     { { 0xFB, 0x01 }, 2 },
0105     { { 0x00, 0x1C }, 2 },
0106     { { 0x01, 0x0B }, 2 },
0107     { { 0x02, 0x0C }, 2 },
0108     { { 0x03, 0x01 }, 2 },
0109     { { 0x04, 0x0F }, 2 },
0110     { { 0x05, 0x10 }, 2 },
0111     { { 0x06, 0x10 }, 2 },
0112     { { 0x07, 0x10 }, 2 },
0113     { { 0x08, 0x89 }, 2 },
0114     { { 0x09, 0x8A }, 2 },
0115     { { 0x0A, 0x13 }, 2 },
0116     { { 0x0B, 0x13 }, 2 },
0117     { { 0x0C, 0x15 }, 2 },
0118     { { 0x0D, 0x15 }, 2 },
0119     { { 0x0E, 0x17 }, 2 },
0120     { { 0x0F, 0x17 }, 2 },
0121     { { 0x10, 0x1C }, 2 },
0122     { { 0x11, 0x0B }, 2 },
0123     { { 0x12, 0x0C }, 2 },
0124     { { 0x13, 0x01 }, 2 },
0125     { { 0x14, 0x0F }, 2 },
0126     { { 0x15, 0x10 }, 2 },
0127     { { 0x16, 0x10 }, 2 },
0128     { { 0x17, 0x10 }, 2 },
0129     { { 0x18, 0x89 }, 2 },
0130     { { 0x19, 0x8A }, 2 },
0131     { { 0x1A, 0x13 }, 2 },
0132     { { 0x1B, 0x13 }, 2 },
0133     { { 0x1C, 0x15 }, 2 },
0134     { { 0x1D, 0x15 }, 2 },
0135     { { 0x1E, 0x17 }, 2 },
0136     { { 0x1F, 0x17 }, 2 },
0137     /* STV */
0138     { { 0x20, 0x40 }, 2 },
0139     { { 0x21, 0x01 }, 2 },
0140     { { 0x22, 0x00 }, 2 },
0141     { { 0x23, 0x40 }, 2 },
0142     { { 0x24, 0x40 }, 2 },
0143     { { 0x25, 0x6D }, 2 },
0144     { { 0x26, 0x40 }, 2 },
0145     { { 0x27, 0x40 }, 2 },
0146     /* Vend */
0147     { { 0xE0, 0x00 }, 2 },
0148     { { 0xDC, 0x21 }, 2 },
0149     { { 0xDD, 0x22 }, 2 },
0150     { { 0xDE, 0x07 }, 2 },
0151     { { 0xDF, 0x07 }, 2 },
0152     { { 0xE3, 0x6D }, 2 },
0153     { { 0xE1, 0x07 }, 2 },
0154     { { 0xE2, 0x07 }, 2 },
0155     /* UD */
0156     { { 0x29, 0xD8 }, 2 },
0157     { { 0x2A, 0x2A }, 2 },
0158     /* CLK */
0159     { { 0x4B, 0x03 }, 2 },
0160     { { 0x4C, 0x11 }, 2 },
0161     { { 0x4D, 0x10 }, 2 },
0162     { { 0x4E, 0x01 }, 2 },
0163     { { 0x4F, 0x01 }, 2 },
0164     { { 0x50, 0x10 }, 2 },
0165     { { 0x51, 0x00 }, 2 },
0166     { { 0x52, 0x80 }, 2 },
0167     { { 0x53, 0x00 }, 2 },
0168     { { 0x56, 0x00 }, 2 },
0169     { { 0x54, 0x07 }, 2 },
0170     { { 0x58, 0x07 }, 2 },
0171     { { 0x55, 0x25 }, 2 },
0172     /* Reset XDONB */
0173     { { 0x5B, 0x43 }, 2 },
0174     { { 0x5C, 0x00 }, 2 },
0175     { { 0x5F, 0x73 }, 2 },
0176     { { 0x60, 0x73 }, 2 },
0177     { { 0x63, 0x22 }, 2 },
0178     { { 0x64, 0x00 }, 2 },
0179     { { 0x67, 0x08 }, 2 },
0180     { { 0x68, 0x04 }, 2 },
0181     /* Resolution:1440x2560 */
0182     { { 0x72, 0x02 }, 2 },
0183     /* mux */
0184     { { 0x7A, 0x80 }, 2 },
0185     { { 0x7B, 0x91 }, 2 },
0186     { { 0x7C, 0xD8 }, 2 },
0187     { { 0x7D, 0x60 }, 2 },
0188     { { 0x7F, 0x15 }, 2 },
0189     { { 0x75, 0x15 }, 2 },
0190     /* ABOFF */
0191     { { 0xB3, 0xC0 }, 2 },
0192     { { 0xB4, 0x00 }, 2 },
0193     { { 0xB5, 0x00 }, 2 },
0194     /* Source EQ */
0195     { { 0x78, 0x00 }, 2 },
0196     { { 0x79, 0x00 }, 2 },
0197     { { 0x80, 0x00 }, 2 },
0198     { { 0x83, 0x00 }, 2 },
0199     /* FP BP */
0200     { { 0x93, 0x0A }, 2 },
0201     { { 0x94, 0x0A }, 2 },
0202     /* Inversion Type */
0203     { { 0x8A, 0x00 }, 2 },
0204     { { 0x9B, 0xFF }, 2 },
0205     /* IMGSWAP =1 @PortSwap=1 */
0206     { { 0x9D, 0xB0 }, 2 },
0207     { { 0x9F, 0x63 }, 2 },
0208     { { 0x98, 0x10 }, 2 },
0209     /* FRM */
0210     { { 0xEC, 0x00 }, 2 },
0211     /* CMD1 */
0212     { { 0xFF, 0x10 }, 2 },
0213     /* VBP+VSA=,VFP = 10H */
0214     { { 0x3B, 0x03, 0x0A, 0x0A }, 4 },
0215     /* FTE on */
0216     { { 0x35, 0x00 }, 2 },
0217     /* EN_BK =1(auto black) */
0218     { { 0xE5, 0x01 }, 2 },
0219     /* CMD mode(10) VDO mode(03) */
0220     { { 0xBB, 0x03 }, 2 },
0221     /* Non Reload MTP */
0222     { { 0xFB, 0x01 }, 2 },
0223 };
0224 
0225 static int truly_dcs_write(struct drm_panel *panel, u32 command)
0226 {
0227     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0228     int i, ret;
0229 
0230     for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) {
0231         ret = mipi_dsi_dcs_write(ctx->dsi[i], command, NULL, 0);
0232         if (ret < 0) {
0233             dev_err(ctx->dev, "cmd 0x%x failed for dsi = %d\n", command, i);
0234         }
0235     }
0236 
0237     return ret;
0238 }
0239 
0240 static int truly_dcs_write_buf(struct drm_panel *panel,
0241     u32 size, const u8 *buf)
0242 {
0243     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0244     int ret = 0;
0245     int i;
0246 
0247     for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) {
0248         ret = mipi_dsi_dcs_write_buffer(ctx->dsi[i], buf, size);
0249         if (ret < 0) {
0250             dev_err(ctx->dev, "failed to tx cmd [%d], err: %d\n", i, ret);
0251             return ret;
0252         }
0253     }
0254 
0255     return ret;
0256 }
0257 
0258 static int truly_35597_power_on(struct truly_nt35597 *ctx)
0259 {
0260     int ret, i;
0261 
0262     for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) {
0263         ret = regulator_set_load(ctx->supplies[i].consumer,
0264                     regulator_enable_loads[i]);
0265         if (ret)
0266             return ret;
0267     }
0268 
0269     ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
0270     if (ret < 0)
0271         return ret;
0272 
0273     /*
0274      * Reset sequence of truly panel requires the panel to be
0275      * out of reset for 10ms, followed by being held in reset
0276      * for 10ms and then out again
0277      */
0278     gpiod_set_value(ctx->reset_gpio, 0);
0279     usleep_range(10000, 20000);
0280     gpiod_set_value(ctx->reset_gpio, 1);
0281     usleep_range(10000, 20000);
0282     gpiod_set_value(ctx->reset_gpio, 0);
0283     usleep_range(10000, 20000);
0284 
0285     return 0;
0286 }
0287 
0288 static int truly_nt35597_power_off(struct truly_nt35597 *ctx)
0289 {
0290     int ret = 0;
0291     int i;
0292 
0293     gpiod_set_value(ctx->reset_gpio, 1);
0294 
0295     for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) {
0296         ret = regulator_set_load(ctx->supplies[i].consumer,
0297                 regulator_disable_loads[i]);
0298         if (ret) {
0299             dev_err(ctx->dev, "regulator_set_load failed %d\n", ret);
0300             return ret;
0301         }
0302     }
0303 
0304     ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
0305     if (ret) {
0306         dev_err(ctx->dev, "regulator_bulk_disable failed %d\n", ret);
0307     }
0308     return ret;
0309 }
0310 
0311 static int truly_nt35597_disable(struct drm_panel *panel)
0312 {
0313     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0314     int ret;
0315 
0316     if (!ctx->enabled)
0317         return 0;
0318 
0319     if (ctx->backlight) {
0320         ret = backlight_disable(ctx->backlight);
0321         if (ret < 0)
0322             dev_err(ctx->dev, "backlight disable failed %d\n", ret);
0323     }
0324 
0325     ctx->enabled = false;
0326     return 0;
0327 }
0328 
0329 static int truly_nt35597_unprepare(struct drm_panel *panel)
0330 {
0331     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0332     int ret = 0;
0333 
0334     if (!ctx->prepared)
0335         return 0;
0336 
0337     ctx->dsi[0]->mode_flags = 0;
0338     ctx->dsi[1]->mode_flags = 0;
0339 
0340     ret = truly_dcs_write(panel, MIPI_DCS_SET_DISPLAY_OFF);
0341     if (ret < 0) {
0342         dev_err(ctx->dev, "set_display_off cmd failed ret = %d\n", ret);
0343     }
0344 
0345     /* 120ms delay required here as per DCS spec */
0346     msleep(120);
0347 
0348     ret = truly_dcs_write(panel, MIPI_DCS_ENTER_SLEEP_MODE);
0349     if (ret < 0) {
0350         dev_err(ctx->dev, "enter_sleep cmd failed ret = %d\n", ret);
0351     }
0352 
0353     ret = truly_nt35597_power_off(ctx);
0354     if (ret < 0)
0355         dev_err(ctx->dev, "power_off failed ret = %d\n", ret);
0356 
0357     ctx->prepared = false;
0358     return ret;
0359 }
0360 
0361 static int truly_nt35597_prepare(struct drm_panel *panel)
0362 {
0363     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0364     int ret;
0365     int i;
0366     const struct cmd_set *panel_on_cmds;
0367     const struct nt35597_config *config;
0368     u32 num_cmds;
0369 
0370     if (ctx->prepared)
0371         return 0;
0372 
0373     ret = truly_35597_power_on(ctx);
0374     if (ret < 0)
0375         return ret;
0376 
0377     ctx->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM;
0378     ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM;
0379 
0380     config = ctx->config;
0381     panel_on_cmds = config->panel_on_cmds;
0382     num_cmds = config->num_on_cmds;
0383 
0384     for (i = 0; i < num_cmds; i++) {
0385         ret = truly_dcs_write_buf(panel,
0386                 panel_on_cmds[i].size,
0387                     panel_on_cmds[i].commands);
0388         if (ret < 0) {
0389             dev_err(ctx->dev, "cmd set tx failed i = %d ret = %d\n", i, ret);
0390             goto power_off;
0391         }
0392     }
0393 
0394     ret = truly_dcs_write(panel, MIPI_DCS_EXIT_SLEEP_MODE);
0395     if (ret < 0) {
0396         dev_err(ctx->dev, "exit_sleep_mode cmd failed ret = %d\n", ret);
0397         goto power_off;
0398     }
0399 
0400     /* Per DSI spec wait 120ms after sending exit sleep DCS command */
0401     msleep(120);
0402 
0403     ret = truly_dcs_write(panel, MIPI_DCS_SET_DISPLAY_ON);
0404     if (ret < 0) {
0405         dev_err(ctx->dev, "set_display_on cmd failed ret = %d\n", ret);
0406         goto power_off;
0407     }
0408 
0409     /* Per DSI spec wait 120ms after sending set_display_on DCS command */
0410     msleep(120);
0411 
0412     ctx->prepared = true;
0413 
0414     return 0;
0415 
0416 power_off:
0417     if (truly_nt35597_power_off(ctx))
0418         dev_err(ctx->dev, "power_off failed\n");
0419     return ret;
0420 }
0421 
0422 static int truly_nt35597_enable(struct drm_panel *panel)
0423 {
0424     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0425     int ret;
0426 
0427     if (ctx->enabled)
0428         return 0;
0429 
0430     if (ctx->backlight) {
0431         ret = backlight_enable(ctx->backlight);
0432         if (ret < 0)
0433             dev_err(ctx->dev, "backlight enable failed %d\n", ret);
0434     }
0435 
0436     ctx->enabled = true;
0437 
0438     return 0;
0439 }
0440 
0441 static int truly_nt35597_get_modes(struct drm_panel *panel,
0442                    struct drm_connector *connector)
0443 {
0444     struct truly_nt35597 *ctx = panel_to_ctx(panel);
0445     struct drm_display_mode *mode;
0446     const struct nt35597_config *config;
0447 
0448     config = ctx->config;
0449     mode = drm_mode_duplicate(connector->dev, config->dm);
0450     if (!mode) {
0451         dev_err(ctx->dev, "failed to create a new display mode\n");
0452         return 0;
0453     }
0454 
0455     connector->display_info.width_mm = config->width_mm;
0456     connector->display_info.height_mm = config->height_mm;
0457     mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
0458     drm_mode_probed_add(connector, mode);
0459 
0460     return 1;
0461 }
0462 
0463 static const struct drm_panel_funcs truly_nt35597_drm_funcs = {
0464     .disable = truly_nt35597_disable,
0465     .unprepare = truly_nt35597_unprepare,
0466     .prepare = truly_nt35597_prepare,
0467     .enable = truly_nt35597_enable,
0468     .get_modes = truly_nt35597_get_modes,
0469 };
0470 
0471 static int truly_nt35597_panel_add(struct truly_nt35597 *ctx)
0472 {
0473     struct device *dev = ctx->dev;
0474     int ret, i;
0475 
0476     for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++)
0477         ctx->supplies[i].supply = regulator_names[i];
0478 
0479     ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
0480                       ctx->supplies);
0481     if (ret < 0)
0482         return ret;
0483 
0484     ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
0485     if (IS_ERR(ctx->reset_gpio)) {
0486         dev_err(dev, "cannot get reset gpio %ld\n", PTR_ERR(ctx->reset_gpio));
0487         return PTR_ERR(ctx->reset_gpio);
0488     }
0489 
0490     ctx->mode_gpio = devm_gpiod_get(dev, "mode", GPIOD_OUT_LOW);
0491     if (IS_ERR(ctx->mode_gpio)) {
0492         dev_err(dev, "cannot get mode gpio %ld\n", PTR_ERR(ctx->mode_gpio));
0493         return PTR_ERR(ctx->mode_gpio);
0494     }
0495 
0496     /* dual port */
0497     gpiod_set_value(ctx->mode_gpio, 0);
0498 
0499     drm_panel_init(&ctx->panel, dev, &truly_nt35597_drm_funcs,
0500                DRM_MODE_CONNECTOR_DSI);
0501     drm_panel_add(&ctx->panel);
0502 
0503     return 0;
0504 }
0505 
0506 static const struct drm_display_mode qcom_sdm845_mtp_2k_mode = {
0507     .name = "1440x2560",
0508     .clock = 268316,
0509     .hdisplay = 1440,
0510     .hsync_start = 1440 + 200,
0511     .hsync_end = 1440 + 200 + 32,
0512     .htotal = 1440 + 200 + 32 + 64,
0513     .vdisplay = 2560,
0514     .vsync_start = 2560 + 8,
0515     .vsync_end = 2560 + 8 + 1,
0516     .vtotal = 2560 + 8 + 1 + 7,
0517     .flags = 0,
0518 };
0519 
0520 static const struct nt35597_config nt35597_dir = {
0521     .width_mm = 74,
0522     .height_mm = 131,
0523     .panel_name = "qcom_sdm845_mtp_2k_panel",
0524     .dm = &qcom_sdm845_mtp_2k_mode,
0525     .panel_on_cmds = qcom_2k_panel_magic_cmds,
0526     .num_on_cmds = ARRAY_SIZE(qcom_2k_panel_magic_cmds),
0527 };
0528 
0529 static int truly_nt35597_probe(struct mipi_dsi_device *dsi)
0530 {
0531     struct device *dev = &dsi->dev;
0532     struct truly_nt35597 *ctx;
0533     struct mipi_dsi_device *dsi1_device;
0534     struct device_node *dsi1;
0535     struct mipi_dsi_host *dsi1_host;
0536     struct mipi_dsi_device *dsi_dev;
0537     int ret = 0;
0538     int i;
0539 
0540     const struct mipi_dsi_device_info info = {
0541         .type = "trulynt35597",
0542         .channel = 0,
0543         .node = NULL,
0544     };
0545 
0546     ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
0547 
0548     if (!ctx)
0549         return -ENOMEM;
0550 
0551     /*
0552      * This device represents itself as one with two input ports which are
0553      * fed by the output ports of the two DSI controllers . The DSI0 is
0554      * the master controller and has most of the panel related info in its
0555      * child node.
0556      */
0557 
0558     ctx->config = of_device_get_match_data(dev);
0559 
0560     if (!ctx->config) {
0561         dev_err(dev, "missing device configuration\n");
0562         return -ENODEV;
0563     }
0564 
0565     dsi1 = of_graph_get_remote_node(dsi->dev.of_node, 1, -1);
0566     if (!dsi1) {
0567         dev_err(dev, "failed to get remote node for dsi1_device\n");
0568         return -ENODEV;
0569     }
0570 
0571     dsi1_host = of_find_mipi_dsi_host_by_node(dsi1);
0572     of_node_put(dsi1);
0573     if (!dsi1_host) {
0574         dev_err(dev, "failed to find dsi host\n");
0575         return -EPROBE_DEFER;
0576     }
0577 
0578     /* register the second DSI device */
0579     dsi1_device = mipi_dsi_device_register_full(dsi1_host, &info);
0580     if (IS_ERR(dsi1_device)) {
0581         dev_err(dev, "failed to create dsi device\n");
0582         return PTR_ERR(dsi1_device);
0583     }
0584 
0585     mipi_dsi_set_drvdata(dsi, ctx);
0586 
0587     ctx->dev = dev;
0588     ctx->dsi[0] = dsi;
0589     ctx->dsi[1] = dsi1_device;
0590 
0591     ret = truly_nt35597_panel_add(ctx);
0592     if (ret) {
0593         dev_err(dev, "failed to add panel\n");
0594         goto err_panel_add;
0595     }
0596 
0597     for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) {
0598         dsi_dev = ctx->dsi[i];
0599         dsi_dev->lanes = 4;
0600         dsi_dev->format = MIPI_DSI_FMT_RGB888;
0601         dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM |
0602             MIPI_DSI_CLOCK_NON_CONTINUOUS;
0603         ret = mipi_dsi_attach(dsi_dev);
0604         if (ret < 0) {
0605             dev_err(dev, "dsi attach failed i = %d\n", i);
0606             goto err_dsi_attach;
0607         }
0608     }
0609 
0610     return 0;
0611 
0612 err_dsi_attach:
0613     drm_panel_remove(&ctx->panel);
0614 err_panel_add:
0615     mipi_dsi_device_unregister(dsi1_device);
0616     return ret;
0617 }
0618 
0619 static int truly_nt35597_remove(struct mipi_dsi_device *dsi)
0620 {
0621     struct truly_nt35597 *ctx = mipi_dsi_get_drvdata(dsi);
0622 
0623     if (ctx->dsi[0])
0624         mipi_dsi_detach(ctx->dsi[0]);
0625     if (ctx->dsi[1]) {
0626         mipi_dsi_detach(ctx->dsi[1]);
0627         mipi_dsi_device_unregister(ctx->dsi[1]);
0628     }
0629 
0630     drm_panel_remove(&ctx->panel);
0631     return 0;
0632 }
0633 
0634 static const struct of_device_id truly_nt35597_of_match[] = {
0635     {
0636         .compatible = "truly,nt35597-2K-display",
0637         .data = &nt35597_dir,
0638     },
0639     { }
0640 };
0641 MODULE_DEVICE_TABLE(of, truly_nt35597_of_match);
0642 
0643 static struct mipi_dsi_driver truly_nt35597_driver = {
0644     .driver = {
0645         .name = "panel-truly-nt35597",
0646         .of_match_table = truly_nt35597_of_match,
0647     },
0648     .probe = truly_nt35597_probe,
0649     .remove = truly_nt35597_remove,
0650 };
0651 module_mipi_dsi_driver(truly_nt35597_driver);
0652 
0653 MODULE_DESCRIPTION("Truly NT35597 DSI Panel Driver");
0654 MODULE_LICENSE("GPL v2");