Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
0004  */
0005 
0006 #include <linux/delay.h>
0007 #include <linux/gpio/consumer.h>
0008 #include <linux/module.h>
0009 #include <linux/of.h>
0010 #include <linux/of_device.h>
0011 #include <linux/regulator/consumer.h>
0012 
0013 #include <video/mipi_display.h>
0014 
0015 #include <drm/drm_crtc.h>
0016 #include <drm/drm_device.h>
0017 #include <drm/drm_mipi_dsi.h>
0018 #include <drm/drm_modes.h>
0019 #include <drm/drm_panel.h>
0020 
0021 struct panel_init_cmd {
0022     size_t len;
0023     const char *data;
0024 };
0025 
0026 #define _INIT_CMD(...) { \
0027     .len = sizeof((char[]){__VA_ARGS__}), \
0028     .data = (char[]){__VA_ARGS__} }
0029 
0030 struct panel_desc {
0031     const struct drm_display_mode *mode;
0032     unsigned int bpc;
0033     struct {
0034         unsigned int width;
0035         unsigned int height;
0036     } size;
0037 
0038     unsigned long flags;
0039     enum mipi_dsi_pixel_format format;
0040     const struct panel_init_cmd *init_cmds;
0041     unsigned int lanes;
0042     const char * const *supply_names;
0043     unsigned int num_supplies;
0044     unsigned int sleep_mode_delay;
0045     unsigned int power_down_delay;
0046 };
0047 
0048 struct innolux_panel {
0049     struct drm_panel base;
0050     struct mipi_dsi_device *link;
0051     const struct panel_desc *desc;
0052 
0053     struct regulator_bulk_data *supplies;
0054     struct gpio_desc *enable_gpio;
0055 
0056     bool prepared;
0057     bool enabled;
0058 };
0059 
0060 static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel)
0061 {
0062     return container_of(panel, struct innolux_panel, base);
0063 }
0064 
0065 static int innolux_panel_disable(struct drm_panel *panel)
0066 {
0067     struct innolux_panel *innolux = to_innolux_panel(panel);
0068 
0069     if (!innolux->enabled)
0070         return 0;
0071 
0072     innolux->enabled = false;
0073 
0074     return 0;
0075 }
0076 
0077 static int innolux_panel_unprepare(struct drm_panel *panel)
0078 {
0079     struct innolux_panel *innolux = to_innolux_panel(panel);
0080     int err;
0081 
0082     if (!innolux->prepared)
0083         return 0;
0084 
0085     err = mipi_dsi_dcs_set_display_off(innolux->link);
0086     if (err < 0)
0087         dev_err(panel->dev, "failed to set display off: %d\n", err);
0088 
0089     err = mipi_dsi_dcs_enter_sleep_mode(innolux->link);
0090     if (err < 0) {
0091         dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
0092         return err;
0093     }
0094 
0095     if (innolux->desc->sleep_mode_delay)
0096         msleep(innolux->desc->sleep_mode_delay);
0097 
0098     gpiod_set_value_cansleep(innolux->enable_gpio, 0);
0099 
0100     if (innolux->desc->power_down_delay)
0101         msleep(innolux->desc->power_down_delay);
0102 
0103     err = regulator_bulk_disable(innolux->desc->num_supplies,
0104                      innolux->supplies);
0105     if (err < 0)
0106         return err;
0107 
0108     innolux->prepared = false;
0109 
0110     return 0;
0111 }
0112 
0113 static int innolux_panel_prepare(struct drm_panel *panel)
0114 {
0115     struct innolux_panel *innolux = to_innolux_panel(panel);
0116     int err;
0117 
0118     if (innolux->prepared)
0119         return 0;
0120 
0121     gpiod_set_value_cansleep(innolux->enable_gpio, 0);
0122 
0123     err = regulator_bulk_enable(innolux->desc->num_supplies,
0124                     innolux->supplies);
0125     if (err < 0)
0126         return err;
0127 
0128     /* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
0129     usleep_range(20000, 21000);
0130 
0131     gpiod_set_value_cansleep(innolux->enable_gpio, 1);
0132 
0133     /* p079zca: t4, p097pfg: t5 */
0134     usleep_range(20000, 21000);
0135 
0136     if (innolux->desc->init_cmds) {
0137         const struct panel_init_cmd *cmds =
0138                     innolux->desc->init_cmds;
0139         unsigned int i;
0140 
0141         for (i = 0; cmds[i].len != 0; i++) {
0142             const struct panel_init_cmd *cmd = &cmds[i];
0143 
0144             err = mipi_dsi_generic_write(innolux->link, cmd->data,
0145                              cmd->len);
0146             if (err < 0) {
0147                 dev_err(panel->dev, "failed to write command %u\n", i);
0148                 goto poweroff;
0149             }
0150 
0151             /*
0152              * Included by random guessing, because without this
0153              * (or at least, some delay), the panel sometimes
0154              * didn't appear to pick up the command sequence.
0155              */
0156             err = mipi_dsi_dcs_nop(innolux->link);
0157             if (err < 0) {
0158                 dev_err(panel->dev, "failed to send DCS nop: %d\n", err);
0159                 goto poweroff;
0160             }
0161         }
0162     }
0163 
0164     err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
0165     if (err < 0) {
0166         dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
0167         goto poweroff;
0168     }
0169 
0170     /* T6: 120ms - 1000ms*/
0171     msleep(120);
0172 
0173     err = mipi_dsi_dcs_set_display_on(innolux->link);
0174     if (err < 0) {
0175         dev_err(panel->dev, "failed to set display on: %d\n", err);
0176         goto poweroff;
0177     }
0178 
0179     /* T7: 5ms */
0180     usleep_range(5000, 6000);
0181 
0182     innolux->prepared = true;
0183 
0184     return 0;
0185 
0186 poweroff:
0187     gpiod_set_value_cansleep(innolux->enable_gpio, 0);
0188     regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
0189 
0190     return err;
0191 }
0192 
0193 static int innolux_panel_enable(struct drm_panel *panel)
0194 {
0195     struct innolux_panel *innolux = to_innolux_panel(panel);
0196 
0197     if (innolux->enabled)
0198         return 0;
0199 
0200     innolux->enabled = true;
0201 
0202     return 0;
0203 }
0204 
0205 static const char * const innolux_p079zca_supply_names[] = {
0206     "power",
0207 };
0208 
0209 static const struct drm_display_mode innolux_p079zca_mode = {
0210     .clock = 56900,
0211     .hdisplay = 768,
0212     .hsync_start = 768 + 40,
0213     .hsync_end = 768 + 40 + 40,
0214     .htotal = 768 + 40 + 40 + 40,
0215     .vdisplay = 1024,
0216     .vsync_start = 1024 + 20,
0217     .vsync_end = 1024 + 20 + 4,
0218     .vtotal = 1024 + 20 + 4 + 20,
0219 };
0220 
0221 static const struct panel_desc innolux_p079zca_panel_desc = {
0222     .mode = &innolux_p079zca_mode,
0223     .bpc = 8,
0224     .size = {
0225         .width = 120,
0226         .height = 160,
0227     },
0228     .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
0229          MIPI_DSI_MODE_LPM,
0230     .format = MIPI_DSI_FMT_RGB888,
0231     .lanes = 4,
0232     .supply_names = innolux_p079zca_supply_names,
0233     .num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
0234     .power_down_delay = 80, /* T8: 80ms - 1000ms */
0235 };
0236 
0237 static const char * const innolux_p097pfg_supply_names[] = {
0238     "avdd",
0239     "avee",
0240 };
0241 
0242 static const struct drm_display_mode innolux_p097pfg_mode = {
0243     .clock = 229000,
0244     .hdisplay = 1536,
0245     .hsync_start = 1536 + 100,
0246     .hsync_end = 1536 + 100 + 24,
0247     .htotal = 1536 + 100 + 24 + 100,
0248     .vdisplay = 2048,
0249     .vsync_start = 2048 + 100,
0250     .vsync_end = 2048 + 100 + 2,
0251     .vtotal = 2048 + 100 + 2 + 18,
0252 };
0253 
0254 /*
0255  * Display manufacturer failed to provide init sequencing according to
0256  * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/
0257  * so the init sequence stems from a register dump of a working panel.
0258  */
0259 static const struct panel_init_cmd innolux_p097pfg_init_cmds[] = {
0260     /* page 0 */
0261     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
0262     _INIT_CMD(0xB1, 0xE8, 0x11),
0263     _INIT_CMD(0xB2, 0x25, 0x02),
0264     _INIT_CMD(0xB5, 0x08, 0x00),
0265     _INIT_CMD(0xBC, 0x0F, 0x00),
0266     _INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00),
0267     _INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14),
0268     _INIT_CMD(0x6F, 0x01),
0269     _INIT_CMD(0xC0, 0x03),
0270     _INIT_CMD(0x6F, 0x02),
0271     _INIT_CMD(0xC1, 0x0D),
0272     _INIT_CMD(0xD9, 0x01, 0x09, 0x70),
0273     _INIT_CMD(0xC5, 0x12, 0x21, 0x00),
0274     _INIT_CMD(0xBB, 0x93, 0x93),
0275 
0276     /* page 1 */
0277     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
0278     _INIT_CMD(0xB3, 0x3C, 0x3C),
0279     _INIT_CMD(0xB4, 0x0F, 0x0F),
0280     _INIT_CMD(0xB9, 0x45, 0x45),
0281     _INIT_CMD(0xBA, 0x14, 0x14),
0282     _INIT_CMD(0xCA, 0x02),
0283     _INIT_CMD(0xCE, 0x04),
0284     _INIT_CMD(0xC3, 0x9B, 0x9B),
0285     _INIT_CMD(0xD8, 0xC0, 0x03),
0286     _INIT_CMD(0xBC, 0x82, 0x01),
0287     _INIT_CMD(0xBD, 0x9E, 0x01),
0288 
0289     /* page 2 */
0290     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02),
0291     _INIT_CMD(0xB0, 0x82),
0292     _INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5,
0293           0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40),
0294     _INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29,
0295           0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0),
0296     _INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C,
0297           0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC),
0298     _INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF),
0299     _INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5,
0300           0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75),
0301     _INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D,
0302           0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03),
0303     _INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94,
0304           0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED),
0305     _INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF),
0306 
0307     /* page 3 */
0308     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
0309     _INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00),
0310     _INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00),
0311     _INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85),
0312     _INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80),
0313     _INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
0314           0x40, 0x80),
0315     _INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C),
0316     _INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C),
0317     _INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
0318     _INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
0319     _INIT_CMD(0xC4, 0x00, 0x00),
0320     _INIT_CMD(0xEF, 0x41),
0321 
0322     /* page 4 */
0323     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
0324     _INIT_CMD(0xEC, 0x4C),
0325 
0326     /* page 5 */
0327     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05),
0328     _INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01),
0329     _INIT_CMD(0xB1, 0x30, 0x00),
0330     _INIT_CMD(0xB2, 0x02, 0x02, 0x00),
0331     _INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D),
0332     _INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57),
0333     _INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A),
0334     _INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56),
0335     _INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C),
0336     _INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00),
0337     _INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05),
0338     _INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00),
0339     _INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00),
0340     _INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00),
0341 
0342     /* page 6 */
0343     _INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06),
0344     _INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F),
0345     _INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12),
0346     _INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D),
0347     _INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
0348     _INIT_CMD(0xB4, 0x3D, 0x32),
0349     _INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F),
0350     _INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18),
0351     _INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D),
0352     _INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
0353     _INIT_CMD(0xB9, 0x3D, 0x32),
0354     _INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F),
0355     _INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17),
0356     _INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D),
0357     _INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
0358     _INIT_CMD(0xC4, 0x3D, 0x32),
0359     _INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F),
0360     _INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11),
0361     _INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D),
0362     _INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
0363     _INIT_CMD(0xC9, 0x3D, 0x32),
0364 
0365     {},
0366 };
0367 
0368 static const struct panel_desc innolux_p097pfg_panel_desc = {
0369     .mode = &innolux_p097pfg_mode,
0370     .bpc = 8,
0371     .size = {
0372         .width = 147,
0373         .height = 196,
0374     },
0375     .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
0376          MIPI_DSI_MODE_LPM,
0377     .format = MIPI_DSI_FMT_RGB888,
0378     .init_cmds = innolux_p097pfg_init_cmds,
0379     .lanes = 4,
0380     .supply_names = innolux_p097pfg_supply_names,
0381     .num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names),
0382     .sleep_mode_delay = 100, /* T15 */
0383 };
0384 
0385 static int innolux_panel_get_modes(struct drm_panel *panel,
0386                    struct drm_connector *connector)
0387 {
0388     struct innolux_panel *innolux = to_innolux_panel(panel);
0389     const struct drm_display_mode *m = innolux->desc->mode;
0390     struct drm_display_mode *mode;
0391 
0392     mode = drm_mode_duplicate(connector->dev, m);
0393     if (!mode) {
0394         dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
0395             m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
0396         return -ENOMEM;
0397     }
0398 
0399     drm_mode_set_name(mode);
0400 
0401     drm_mode_probed_add(connector, mode);
0402 
0403     connector->display_info.width_mm = innolux->desc->size.width;
0404     connector->display_info.height_mm = innolux->desc->size.height;
0405     connector->display_info.bpc = innolux->desc->bpc;
0406 
0407     return 1;
0408 }
0409 
0410 static const struct drm_panel_funcs innolux_panel_funcs = {
0411     .disable = innolux_panel_disable,
0412     .unprepare = innolux_panel_unprepare,
0413     .prepare = innolux_panel_prepare,
0414     .enable = innolux_panel_enable,
0415     .get_modes = innolux_panel_get_modes,
0416 };
0417 
0418 static const struct of_device_id innolux_of_match[] = {
0419     { .compatible = "innolux,p079zca",
0420       .data = &innolux_p079zca_panel_desc
0421     },
0422     { .compatible = "innolux,p097pfg",
0423       .data = &innolux_p097pfg_panel_desc
0424     },
0425     { }
0426 };
0427 MODULE_DEVICE_TABLE(of, innolux_of_match);
0428 
0429 static int innolux_panel_add(struct mipi_dsi_device *dsi,
0430                  const struct panel_desc *desc)
0431 {
0432     struct innolux_panel *innolux;
0433     struct device *dev = &dsi->dev;
0434     int err, i;
0435 
0436     innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
0437     if (!innolux)
0438         return -ENOMEM;
0439 
0440     innolux->desc = desc;
0441 
0442     innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
0443                      sizeof(*innolux->supplies),
0444                      GFP_KERNEL);
0445     if (!innolux->supplies)
0446         return -ENOMEM;
0447 
0448     for (i = 0; i < desc->num_supplies; i++)
0449         innolux->supplies[i].supply = desc->supply_names[i];
0450 
0451     err = devm_regulator_bulk_get(dev, desc->num_supplies,
0452                       innolux->supplies);
0453     if (err < 0)
0454         return err;
0455 
0456     innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
0457                                GPIOD_OUT_HIGH);
0458     if (IS_ERR(innolux->enable_gpio)) {
0459         err = PTR_ERR(innolux->enable_gpio);
0460         dev_dbg(dev, "failed to get enable gpio: %d\n", err);
0461         innolux->enable_gpio = NULL;
0462     }
0463 
0464     drm_panel_init(&innolux->base, dev, &innolux_panel_funcs,
0465                DRM_MODE_CONNECTOR_DSI);
0466 
0467     err = drm_panel_of_backlight(&innolux->base);
0468     if (err)
0469         return err;
0470 
0471     drm_panel_add(&innolux->base);
0472 
0473     mipi_dsi_set_drvdata(dsi, innolux);
0474     innolux->link = dsi;
0475 
0476     return 0;
0477 }
0478 
0479 static void innolux_panel_del(struct innolux_panel *innolux)
0480 {
0481     drm_panel_remove(&innolux->base);
0482 }
0483 
0484 static int innolux_panel_probe(struct mipi_dsi_device *dsi)
0485 {
0486     const struct panel_desc *desc;
0487     struct innolux_panel *innolux;
0488     int err;
0489 
0490     desc = of_device_get_match_data(&dsi->dev);
0491     dsi->mode_flags = desc->flags;
0492     dsi->format = desc->format;
0493     dsi->lanes = desc->lanes;
0494 
0495     err = innolux_panel_add(dsi, desc);
0496     if (err < 0)
0497         return err;
0498 
0499     err = mipi_dsi_attach(dsi);
0500     if (err < 0) {
0501         innolux = mipi_dsi_get_drvdata(dsi);
0502         innolux_panel_del(innolux);
0503         return err;
0504     }
0505 
0506     return 0;
0507 }
0508 
0509 static int innolux_panel_remove(struct mipi_dsi_device *dsi)
0510 {
0511     struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
0512     int err;
0513 
0514     err = drm_panel_unprepare(&innolux->base);
0515     if (err < 0)
0516         dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
0517 
0518     err = drm_panel_disable(&innolux->base);
0519     if (err < 0)
0520         dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
0521 
0522     err = mipi_dsi_detach(dsi);
0523     if (err < 0)
0524         dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
0525 
0526     innolux_panel_del(innolux);
0527 
0528     return 0;
0529 }
0530 
0531 static void innolux_panel_shutdown(struct mipi_dsi_device *dsi)
0532 {
0533     struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
0534 
0535     drm_panel_unprepare(&innolux->base);
0536     drm_panel_disable(&innolux->base);
0537 }
0538 
0539 static struct mipi_dsi_driver innolux_panel_driver = {
0540     .driver = {
0541         .name = "panel-innolux-p079zca",
0542         .of_match_table = innolux_of_match,
0543     },
0544     .probe = innolux_panel_probe,
0545     .remove = innolux_panel_remove,
0546     .shutdown = innolux_panel_shutdown,
0547 };
0548 module_mipi_dsi_driver(innolux_panel_driver);
0549 
0550 MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
0551 MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
0552 MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
0553 MODULE_LICENSE("GPL v2");