Although the MIPI specifications define how to communicate with a panel
to display an image, some panels still require a panel-specific
initialization sequence to be sent.

This is a driver for such generic MIPI-DSI/DPI panels that require
initialization with a simple command sequence before use.

Its fundamental approach is similar to `panel-mipi-dbi` driver,
which sends an initialization sequence stored in a firmware file.

Moreover, this driver allows display modes, timings, and panel
configuration parameters to be stored in the same file or in DT.

Signed-off-by: Hironori KIKUCHI <kikucha...@gmail.com>
---
 drivers/gpu/drm/panel/Kconfig      |   10 +
 drivers/gpu/drm/panel/Makefile     |    1 +
 drivers/gpu/drm/panel/panel-mipi.c | 1355 ++++++++++++++++++++++++++++
 3 files changed, 1366 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-mipi.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d7469c565d1..46eea1974a0 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -408,6 +408,16 @@ config DRM_PANEL_MANTIX_MLAF057WE51
          has a resolution of 720x1440 pixels, a built in backlight and touch
          controller.
 
+config DRM_PANEL_MIPI
+       tristate "Generic MIPI-DSI/DPI(+SPI) panel"
+       depends on OF
+       depends on SPI || DRM_MIPI_DSI
+       select DRM_MIPI_DBI if SPI
+       depends on BACKLIGHT_CLASS_DEVICE
+       help
+         Say Y here if you want to enable support for Generic MIPI-DSI /
+         MIPI-DPI(+SPI) panels.
+
 config DRM_PANEL_NEC_NL8048HL11
        tristate "NEC NL8048HL11 RGB panel"
        depends on GPIOLIB && OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 7dcf72646ca..22276255a7b 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
 obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
 obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
+obj-$(CONFIG_DRM_PANEL_MIPI) += panel-mipi.o
 obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
 obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
 obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3052C) += panel-newvision-nv3052c.o
diff --git a/drivers/gpu/drm/panel/panel-mipi.c 
b/drivers/gpu/drm/panel/panel-mipi.c
new file mode 100644
index 00000000000..bcbaa15b62a
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-mipi.c
@@ -0,0 +1,1355 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Generic MIPI-DSI/DPI(+SPI) Panel Driver
+ *
+ * Supported panels:
+ * - A generic MIPI-DSI panel which implements basic DCS
+ * - A generic MIPI-DPI panel which implements basic DCS over SPI
+ *
+ * Copyright (C) 2025, Hironori KIKUCHI <kikucha...@gmail.com>
+ */
+
+#include <drm/drm_mipi_dbi.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <linux/backlight.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/display_timing.h>
+#include <video/mipi_display.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+/*
+ * The display panel configuration can be stored in a firmware file,
+ * under the firmware directory of the system.
+ *
+ * The name of the file is `panels/<firmware-name>.panel`, where the
+ * 'firmware-name' should be defined as the property of the device
+ * in the Device Tree, otherwise the first `compatible` string is used.
+ *
+ * File Layout:
+ *     A binary file composed with the following data:
+ *         <header>
+ *         <config>
+ *         <timings>
+ *         <init-sequence>
+ *
+ * The 'header':
+ *     A `struct panel_firmware_header`.
+ *     The `file_format_version` must be `1`.
+ *
+ * The 'config':
+ *     A `struct panel_firmware_config`.
+ *     The values are in big-endian.
+ *
+ * The 'timings':
+ *     An array of `struct panel_firmware_panel_timing`.
+ *     Its length is `num_timings` in the config.
+ *     The values are in big-endian.
+ *
+ * The 'init-sequence':
+ *     MIPI commands to execute when the display pipeline is enabled.
+ *     This is used to configure the display controller, and it may
+ *     contains panel specific parameters as well.
+ *
+ *     The commands are encoded and stored in a byte array:
+ *
+ *         0x00             : sleep 10 ms
+ *         0x01 <M>         : MIPI command <M> with no arguments
+ *         0x02 <M> <a>     : MIPI command <M> with 1 argument <a>
+ *         0x03 <M> <a> <b> : MIPI command <M> with 2 arguments <a> <b>
+ *                :
+ *         0x7f <M> <...>   : MIPI command <M> with 126 arguments <...>
+ *         0x80             : sleep 100 ms
+ *         0x81 - 0xff      : reserved
+ *
+ *     Example:
+ *         command 0x11
+ *         sleep 10ms
+ *         command 0xb1 arguments 0x01 0x2c 0x2d
+ *         command 0x29
+ *         sleep 130ms
+ *
+ *     Byte sequence:
+ *         0x01 0x11
+ *         0x00
+ *         0x04 0xb1 0x01 0x2c 0x2d
+ *         0x01 0x29
+ *         0x80 0x00 0x00 0x00
+ *
+ */
+static const u8 panel_firmware_magic[15] = {
+       0x50, 0x41, 0x4e, 0x45, 0x4c, 0x2d, 0x46, 0x49,
+       0x52, 0x4d, 0x57, 0x41, 0x52, 0x45, 0x00,
+};
+
+struct panel_firmware_header {
+       u8 magic[15];
+       u8 file_format_version; /* must be 1 */
+} __packed;
+
+struct panel_firmware_config {
+       u16 width_mm, height_mm;
+       u16 rotation;
+       u8 _reserved_1[2];
+       u8 _reserved_2[8];
+
+       u16 reset_delay; /* delay after the reset command, in ms */
+       u16 init_delay; /* delay for sending the initial command sequence, in 
ms */
+       u16 sleep_delay; /* delay after the sleep command, in ms */
+       u16 backlight_delay; /* delay for enabling the backlight, in ms */
+       u16 _reserved_3[4];
+
+       u16 dsi_lanes; /* unsigned int */
+       u16 dsi_format; /* enum mipi_dsi_pixel_format */
+       u32 dsi_mode_flags; /* unsigned long */
+       u32 bus_flags; /* struct drm_bus_flags */
+       u8 _reserved_4[2];
+       u8 preferred_timing;
+       u8 num_timings;
+} __packed;
+
+struct panel_firmware_panel_timing {
+       u16 hactive;
+       u16 hfp;
+       u16 hslen;
+       u16 hbp;
+
+       u16 vactive;
+       u16 vfp;
+       u16 vslen;
+       u16 vbp;
+
+       u32 dclk; /* in kHz */
+       u32 flags; /* include/drm/drm_modes.h DRM_MODE_FLAG_* */
+
+       u8 _reserved_1[8];
+} __packed;
+
+struct panel_commands {
+       const u8 *data;
+       size_t size;
+};
+
+struct panel_firmware {
+       const struct firmware *blob;
+       const struct panel_firmware_config *config;
+       const struct panel_firmware_panel_timing *timings;
+       struct panel_commands commands;
+};
+
+struct panel_mipi {
+       struct drm_panel panel;
+       struct mipi_dsi_device *dsi;
+       struct mipi_dbi dbi;
+
+       struct mutex lock;
+
+       struct regulator_bulk_data supplies[2];
+       struct gpio_desc *reset;
+
+       unsigned int reset_delay;
+       unsigned int init_delay;
+       unsigned int sleep_delay;
+       unsigned int backlight_delay;
+
+       unsigned int dsi_lanes;
+       enum mipi_dsi_pixel_format dsi_format;
+
+       /* Panel info */
+       u32 width_mm, height_mm;
+       u32 bus_format, bus_flags;
+       enum drm_panel_orientation orientation;
+
+       struct list_head mode_list;
+
+       struct panel_firmware *firmware;
+       struct panel_commands commands;
+
+       int (*write_command)(struct panel_mipi *mipi, const u8 *buf,
+                            size_t len);
+       int (*read_command)(struct panel_mipi *mipi, u8 cmd, u8 *buf,
+                           size_t len);
+
+       /* DMA-able buffer */
+       u8 initial_display_id[3];
+       u8 display_id[3];
+
+       /* debugfs */
+       struct dentry *debugfs;
+       u8 recvbuf[1024];
+       size_t recvlen;
+};
+
+static inline struct panel_mipi *to_panel_mipi(struct drm_panel *panel)
+{
+       return container_of(panel, struct panel_mipi, panel);
+}
+
+static int panel_mipi_dsi_write(struct panel_mipi *mipi, const u8 *buf,
+                               size_t len)
+{
+       int ret;
+
+       ret = mipi_dsi_dcs_write_buffer(mipi->dsi, buf, len);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int panel_mipi_dbi_write(struct panel_mipi *mipi, const u8 *buf,
+                               size_t len)
+{
+       return mipi_dbi_command_stackbuf(&mipi->dbi, buf[0], buf + 1, len - 1);
+}
+
+#define panel_mipi_write(mipi, ...)                                \
+       ({                                                         \
+               u8 _buf[] = { __VA_ARGS__ };                       \
+               mipi->write_command(mipi, _buf, ARRAY_SIZE(_buf)); \
+       })
+
+static int panel_mipi_dsi_read(struct panel_mipi *mipi, u8 cmd, u8 *buf,
+                              size_t len)
+{
+       return mipi_dsi_dcs_read(mipi->dsi, cmd, buf, len);
+}
+
+static int panel_mipi_dbi_read(struct panel_mipi *mipi, u8 cmd, u8 *buf,
+                              size_t len)
+{
+       return mipi_dbi_command_buf(&mipi->dbi, cmd, buf, len);
+}
+
+#define panel_mipi_read(mipi, cmd, buf, len) \
+       (mipi)->read_command((mipi), (cmd), (buf), (len))
+
+static unsigned long panel_mipi_dsi_set_mode_flags(struct panel_mipi *mipi,
+                                                  unsigned long new_flags)
+{
+       unsigned long old_flags;
+
+       if (!mipi->dsi)
+               return 0;
+
+       old_flags = mipi->dsi->mode_flags;
+
+       mipi->dsi->mode_flags = new_flags;
+
+       return old_flags;
+}
+
+static bool panel_mipi_dsi_set_lpm(struct panel_mipi *mipi, bool set)
+{
+       unsigned long old_flags;
+
+       if (!mipi->dsi)
+               return false;
+
+       old_flags = panel_mipi_dsi_set_mode_flags(
+               mipi, set ? mipi->dsi->mode_flags | MIPI_DSI_MODE_LPM :
+                           mipi->dsi->mode_flags & ~MIPI_DSI_MODE_LPM);
+
+       return old_flags & MIPI_DSI_MODE_LPM ? true : false;
+}
+
+#define PANEL_MIPI_TRANSACTION(mipi, stmt)   \
+       do {                                 \
+               mutex_lock(&(mipi)->lock);   \
+               do {                         \
+                       stmt;                \
+               } while (0);                 \
+               mutex_unlock(&(mipi)->lock); \
+       } while (0)
+
+#define PANEL_MIPI_TRANSACTION_LPM(mipi, lpm, stmt)                       \
+       PANEL_MIPI_TRANSACTION(                                           \
+               mipi,                                                     \
+               bool _lpm_backup = panel_mipi_dsi_set_lpm((mipi), (lpm)); \
+               do { stmt; } while (0);                                   \
+               panel_mipi_dsi_set_lpm((mipi), _lpm_backup))
+
+static int panel_mipi_validate_commands(struct panel_mipi *mipi, const u8 
*data,
+                                       size_t size, size_t *valid_len)
+{
+       size_t i = 0;
+
+       /* sanity check */
+       while (i < size) {
+               u8 inst = data[i];
+               bool ext = (inst & 0x80) ? true : false;
+               u8 len = inst & 0x7f;
+
+               if (ext && len > 0)
+                       return -EINVAL; /* reserved */
+
+               if (i + 1 + len > size)
+                       break;
+
+               i += 1 + len;
+       }
+
+       *valid_len = i;
+
+       return 0;
+}
+
+static int panel_mipi_load_commands(struct panel_mipi *mipi, const u8 *data,
+                                   size_t size)
+{
+       int err;
+       size_t valid_len;
+
+       err = panel_mipi_validate_commands(mipi, data, size, &valid_len);
+       if (err)
+               return err;
+
+       if (valid_len != size)
+               return -EINVAL;
+
+       mipi->commands.data = data;
+       mipi->commands.size = size;
+
+       return 0;
+}
+
+static int panel_mipi_write_commands(struct panel_mipi *mipi, const u8 *data,
+                                    size_t size)
+{
+       const u8 *read_commands;
+       unsigned int write_memory_bpw;
+       size_t i = 0;
+       int err = 0;
+
+       if (!data)
+               return 0;
+
+       /*
+        * Disable interpretation for special commands for DBI
+        *
+        * This is required because the vendor specific custom commands
+        * may change its command set to the other.
+        */
+       read_commands = mipi->dbi.read_commands;
+       write_memory_bpw = mipi->dbi.write_memory_bpw;
+       mipi->dbi.read_commands = NULL;
+       mipi->dbi.write_memory_bpw = 8;
+
+       while (i < size) {
+               u8 inst = data[i++];
+               bool ext = (inst & 0x80) ? true : false;
+               u8 len = inst & 0x7f;
+
+               if (len == 0x00) {
+                       msleep(ext ? 100 : 10);
+                       continue;
+               }
+
+               err = mipi->write_command(mipi, data + i, len);
+
+               if (err)
+                       break;
+
+               i += len;
+       }
+
+       /* restore */
+       mipi->dbi.read_commands = read_commands;
+       mipi->dbi.write_memory_bpw = write_memory_bpw;
+
+       return err;
+}
+
+static int panel_mipi_read_firmware(const struct device *dev,
+                                   struct panel_mipi *mipi,
+                                   const struct panel_firmware *firmware)
+{
+       int rotation;
+       int err;
+
+       if (!firmware)
+               return 0;
+
+       err = panel_mipi_load_commands(mipi, firmware->commands.data,
+                                      firmware->commands.size);
+       if (err) {
+               dev_err(dev, "firmware: Malformed command sequence\n");
+               return err;
+       }
+
+       mipi->width_mm = be16_to_cpu(firmware->config->width_mm);
+       mipi->height_mm = be16_to_cpu(firmware->config->height_mm);
+
+       rotation = be16_to_cpu(firmware->config->rotation);
+       if (rotation == 0)
+               mipi->orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL;
+       else if (rotation == 90)
+               mipi->orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
+       else if (rotation == 180)
+               mipi->orientation = DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP;
+       else if (rotation == 270)
+               mipi->orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP;
+       else {
+               dev_err(dev, "firmware: Invalid rotation %u\n", rotation);
+               return -EINVAL;
+       }
+
+       if (firmware->config->reset_delay)
+               mipi->reset_delay = be16_to_cpu(firmware->config->reset_delay);
+       if (firmware->config->init_delay)
+               mipi->init_delay = be16_to_cpu(firmware->config->init_delay);
+       if (firmware->config->sleep_delay)
+               mipi->sleep_delay = be16_to_cpu(firmware->config->sleep_delay);
+       if (firmware->config->backlight_delay)
+               mipi->backlight_delay =
+                       be16_to_cpu(firmware->config->backlight_delay);
+
+       if (firmware->config->bus_flags)
+               mipi->bus_flags = be32_to_cpu(firmware->config->bus_flags);
+
+       return 0;
+}
+
+static struct panel_firmware *panel_mipi_load_firmware(struct device *dev)
+{
+       const struct firmware *blob;
+       const struct panel_firmware_header *hdr;
+       const struct panel_firmware_config *config;
+       const struct panel_firmware_panel_timing *timings;
+       struct panel_firmware *firmware;
+       const char *firmware_name;
+       size_t command_offset;
+       char filename[128];
+       int err;
+
+       err = of_property_read_string_index(dev->of_node, "compatible", 0,
+                                           &firmware_name);
+       if (err)
+               return NULL;
+
+       /* Use firmware-name instead if any */
+       of_property_read_string(dev->of_node, "firmware-name", &firmware_name);
+
+       snprintf(filename, sizeof(filename), "panels/%s.panel", firmware_name);
+       err = firmware_request_nowarn(&blob, filename, dev);
+       if (err)
+               return NULL;
+
+       hdr = (const struct panel_firmware_header *)blob->data;
+
+       if (blob->size < sizeof(*hdr)) {
+               dev_err(dev, "firmware: %s: file size %zu is too small\n",
+                       filename, blob->size);
+               err = -EINVAL;
+               goto err;
+       }
+
+       if (memcmp(hdr->magic, panel_firmware_magic, sizeof(hdr->magic))) {
+               dev_err(dev, "firmware: %s: Bad magic %15ph\n", filename,
+                       hdr->magic);
+               err = -EINVAL;
+               goto err;
+       }
+
+       if (hdr->file_format_version != 1) {
+               dev_err(dev, "firmware: %s: version %u is not supported\n",
+                       filename, hdr->file_format_version);
+               err = -EINVAL;
+               goto err;
+       }
+
+       config = (struct panel_firmware_config *)(blob->data + sizeof(*hdr));
+       timings = (struct panel_firmware_panel_timing *)(blob->data +
+                                                        sizeof(*hdr) +
+                                                        sizeof(*config));
+       command_offset = sizeof(*hdr) + sizeof(*config) +
+                        config->num_timings * sizeof(*timings);
+       if (blob->size < command_offset) {
+               dev_err(dev, "firmware: %s: file size %zu is too small\n",
+                       filename, blob->size);
+               err = -EINVAL;
+               goto err;
+       }
+
+       firmware = devm_kzalloc(dev, sizeof(*firmware), GFP_KERNEL);
+       if (!firmware) {
+               err = -ENOMEM;
+               goto err;
+       }
+
+       firmware->blob = blob;
+       firmware->config = config;
+       firmware->timings = timings;
+       firmware->commands.data = blob->data + command_offset;
+       firmware->commands.size = blob->size - command_offset;
+
+       dev_info(dev, "firmware: %s: loaded successfully\n", filename);
+
+       return firmware;
+
+err:
+       release_firmware(blob);
+
+       return ERR_PTR(err);
+}
+
+static int panel_mipi_read_display_id(struct panel_mipi *mipi, u8 *dest)
+{
+       return panel_mipi_read(mipi, MIPI_DCS_GET_DISPLAY_ID, dest, 3);
+}
+
+static int panel_mipi_enable(struct drm_panel *panel)
+{
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+       int err;
+
+       PANEL_MIPI_TRANSACTION(mipi, {
+               err = panel_mipi_write(mipi, MIPI_DCS_SET_DISPLAY_ON);
+       });
+       if (err)
+               return err;
+
+       if (panel->backlight) {
+               /* Wait for the picture to be ready before enabling backlight */
+               msleep(mipi->backlight_delay);
+       }
+
+       return 0;
+}
+
+static int panel_mipi_disable(struct drm_panel *panel)
+{
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+       int err = 0;
+
+       PANEL_MIPI_TRANSACTION(mipi, {
+               err = panel_mipi_write(mipi, MIPI_DCS_SET_DISPLAY_OFF);
+       });
+
+       return err;
+}
+
+static int panel_mipi_prepare(struct drm_panel *panel)
+{
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+       int err;
+
+       err = regulator_bulk_enable(ARRAY_SIZE(mipi->supplies), mipi->supplies);
+       if (err)
+               return err;
+
+       PANEL_MIPI_TRANSACTION(mipi, {
+               /* Reset the chip */
+               if (mipi->reset) {
+                       gpiod_set_value_cansleep(mipi->reset, 1);
+                       msleep(mipi->reset_delay);
+                       gpiod_set_value_cansleep(mipi->reset, 0);
+               }
+
+               msleep(mipi->init_delay);
+
+               /* Read the Display ID */
+               if (!panel_mipi_read_display_id(mipi,
+                                               mipi->initial_display_id)) {
+                       dev_info(panel->dev, "MIPI Display ID: %3phN\n",
+                                mipi->initial_display_id);
+               }
+
+               /* Write the init-sequence */
+               err = panel_mipi_write_commands(mipi, mipi->commands.data,
+                                               mipi->commands.size);
+               if (err)
+                       break;
+
+               /* Ensure to exit from a sleep mode */
+               err = panel_mipi_write(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
+               if (err)
+                       break;
+               msleep(mipi->sleep_delay);
+
+               /*
+                * Read the Display ID again,
+                * because the init-sequence might have changed the ID.
+                */
+               if (!panel_mipi_read_display_id(mipi, mipi->display_id) &&
+                   memcmp(mipi->initial_display_id, mipi->display_id,
+                          sizeof(mipi->display_id))) {
+                       dev_info(panel->dev,
+                                "MIPI Display ID: %3phN (changed)\n",
+                                mipi->display_id);
+               }
+       });
+
+       if (err) {
+               regulator_bulk_disable(ARRAY_SIZE(mipi->supplies),
+                                      mipi->supplies);
+               return err;
+       }
+
+       return 0;
+}
+
+static int panel_mipi_unprepare(struct drm_panel *panel)
+{
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+       int err;
+
+       PANEL_MIPI_TRANSACTION(mipi, {
+               err = panel_mipi_write(mipi, MIPI_DCS_ENTER_SLEEP_MODE);
+               if (err)
+                       dev_warn(panel->dev, "Failed to enter a sleep mode\n");
+               msleep(mipi->sleep_delay);
+       });
+
+       if (mipi->reset) {
+               gpiod_set_value_cansleep(mipi->reset, 1);
+               msleep(mipi->reset_delay);
+       }
+
+       regulator_bulk_disable(ARRAY_SIZE(mipi->supplies), mipi->supplies);
+
+       return 0;
+}
+
+static int panel_mipi_get_modes(struct drm_panel *panel,
+                               struct drm_connector *connector)
+{
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+       struct drm_display_mode *mode;
+       unsigned int count = 0;
+
+       list_for_each_entry(mode, &mipi->mode_list, head) {
+               struct drm_display_mode *dmode;
+
+               dmode = drm_mode_duplicate(connector->dev, mode);
+               if (!dmode)
+                       return -EINVAL;
+
+               drm_mode_probed_add(connector, dmode);
+               count++;
+       }
+
+       connector->display_info.bpc = 8;
+       connector->display_info.width_mm = mipi->width_mm;
+       connector->display_info.height_mm = mipi->height_mm;
+       connector->display_info.bus_flags = mipi->bus_flags;
+       drm_display_info_set_bus_formats(&connector->display_info,
+                                        &mipi->bus_format, 1);
+
+       /*
+        * TODO: Remove once all drm drivers call
+        * drm_connector_set_orientation_from_panel()
+        */
+       drm_connector_set_panel_orientation(connector, mipi->orientation);
+
+       return count;
+}
+
+static enum drm_panel_orientation
+panel_mipi_get_orientation(struct drm_panel *panel)
+{
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+
+       return mipi->orientation;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int display_id_show(struct seq_file *s, void *data)
+{
+       struct drm_panel *panel = s->private;
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+       int err = 0;
+
+       PANEL_MIPI_TRANSACTION(mipi, {
+               err = panel_mipi_read_display_id(mipi, mipi->display_id);
+               if (err)
+                       break;
+
+               seq_printf(s, "%3phN\n", mipi->display_id);
+       });
+
+       return err;
+}
+DEFINE_SHOW_ATTRIBUTE(display_id);
+
+static int initial_display_id_show(struct seq_file *s, void *data)
+{
+       struct drm_panel *panel = s->private;
+       struct panel_mipi *mipi = to_panel_mipi(panel);
+
+       seq_printf(s, "%3phN\n", mipi->initial_display_id);
+
+       return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(initial_display_id);
+
+static int commands_open(struct inode *inode, struct file *file)
+{
+       struct panel_mipi *mipi = inode->i_private;
+
+       mipi->recvlen = 0;
+       file->private_data = mipi;
+
+       return 0;
+}
+
+static ssize_t commands_write(struct file *file, const char __user *data,
+                             size_t size, loff_t *pos)
+{
+       struct panel_mipi *mipi = file->private_data;
+       ssize_t s = min_t(u64, sizeof(mipi->recvbuf) - mipi->recvlen, size);
+       size_t valid_len;
+       int err;
+
+       if (!s || !mipi)
+               return -EINVAL;
+
+       if (copy_from_user(mipi->recvbuf + mipi->recvlen, data, s))
+               return -EINVAL;
+
+       mipi->recvlen += s;
+
+       err = panel_mipi_validate_commands(mipi, mipi->recvbuf, mipi->recvlen,
+                                          &valid_len);
+       if (err)
+               return err;
+
+       if (valid_len > 0) {
+               PANEL_MIPI_TRANSACTION(mipi, {
+                       err = panel_mipi_write_commands(mipi, mipi->recvbuf,
+                                                       valid_len);
+               });
+               if (err)
+                       return err;
+
+               mipi->recvlen -= valid_len;
+               if (mipi->recvlen)
+                       memmove(mipi->recvbuf, mipi->recvbuf + valid_len,
+                               mipi->recvlen);
+       }
+
+       return s;
+}
+
+static int commands_release(struct inode *inode, struct file *file)
+{
+       struct panel_mipi *mipi = inode->i_private;
+
+       if (mipi->recvlen) {
+               dev_err(mipi->panel.dev, "Premature end of commands\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static const struct file_operations commands_fops = {
+       .owner = THIS_MODULE,
+       .open = commands_open,
+       .write = commands_write,
+       .release = commands_release,
+};
+
+#endif
+
+static void panel_mipi_debugfs_init(struct device *dev, struct panel_mipi 
*mipi)
+{
+#ifdef CONFIG_DEBUG_FS
+       struct dentry *dir;
+
+       mipi->debugfs = debugfs_lookup("panel-mipi", NULL);
+       if (!mipi->debugfs)
+               mipi->debugfs = debugfs_create_dir("panel-mipi", NULL);
+
+       dir = debugfs_create_dir(dev_name(dev), mipi->debugfs);
+       debugfs_create_file("display-id", 0600, dir, mipi, &display_id_fops);
+       debugfs_create_file("initial-display-id", 0600, dir, mipi,
+                           &initial_display_id_fops);
+       debugfs_create_file("commands", 0600, dir, mipi, &commands_fops);
+#endif
+}
+
+static void panel_mipi_debugfs_remove(struct panel_mipi *mipi)
+{
+#ifdef CONFIG_DEBUG_FS
+       debugfs_remove_recursive(mipi->debugfs);
+       mipi->debugfs = NULL;
+#endif
+}
+
+static const struct drm_panel_funcs panel_mipi_funcs = {
+       .prepare = panel_mipi_prepare,
+       .unprepare = panel_mipi_unprepare,
+       .enable = panel_mipi_enable,
+       .disable = panel_mipi_disable,
+       .get_modes = panel_mipi_get_modes,
+       .get_orientation = panel_mipi_get_orientation,
+};
+
+static int panel_mipi_add_mode(struct device *dev, struct panel_mipi *mipi,
+                              const struct drm_display_mode *src_mode,
+                              const char *source)
+{
+       struct drm_display_mode *mode;
+
+       mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
+       if (!mode)
+               return -ENOMEM;
+
+       drm_mode_copy(mode, src_mode);
+
+       if (!mode->clock)
+               mode->clock = mode->htotal * mode->vtotal * 60 / 1000;
+
+       mode->type |= DRM_MODE_TYPE_DRIVER;
+
+       dev_info(dev, "Modeline " DRM_MODE_FMT " added by %s\n",
+                DRM_MODE_ARG(mode), source);
+
+       list_add_tail(&mode->head, &mipi->mode_list);
+
+       return 0;
+}
+
+static int panel_mipi_firmware_read_modes(const struct panel_firmware 
*firmware,
+                                         struct drm_display_mode *mode,
+                                         u32 *bus_flags, unsigned int i)
+{
+       const struct panel_firmware_panel_timing *timings =
+               &firmware->timings[i];
+
+       if (!firmware || i >= firmware->config->num_timings)
+               return -ENOENT;
+
+       if (!timings->hactive || !timings->vactive)
+               return -ENOENT;
+
+       memset(mode, 0, sizeof(*mode));
+
+       mode->clock = be32_to_cpu(timings->dclk);
+
+       mode->hdisplay = be16_to_cpu(timings->hactive);
+       mode->hsync_start = mode->hdisplay + be16_to_cpu(timings->hfp);
+       mode->hsync_end = mode->hsync_start + be16_to_cpu(timings->hslen);
+       mode->htotal = mode->hsync_end + be16_to_cpu(timings->hbp);
+
+       mode->vdisplay = be16_to_cpu(timings->vactive);
+       mode->vsync_start = mode->vdisplay + be16_to_cpu(timings->vfp);
+       mode->vsync_end = mode->vsync_start + be16_to_cpu(timings->vslen);
+       mode->vtotal = mode->vsync_end + be16_to_cpu(timings->vbp);
+
+       mode->flags = be32_to_cpu(timings->flags);
+
+       drm_mode_set_name(mode);
+
+       if (bus_flags && firmware->config->bus_flags)
+               *bus_flags = be32_to_cpu(firmware->config->bus_flags);
+
+       return 0;
+}
+
+static void drm_display_mode_from_timing(const struct display_timing *timing,
+                                        struct drm_display_mode *dmode,
+                                        u32 *bus_flags)
+{
+       struct videomode vm;
+
+       videomode_from_timing(timing, &vm);
+
+       memset(dmode, 0, sizeof(*dmode));
+       drm_display_mode_from_videomode(&vm, dmode);
+
+       if (bus_flags)
+               drm_bus_flags_from_videomode(&vm, bus_flags);
+}
+
+static int panel_mipi_read_of_display_timings(struct device *dev,
+                                             struct panel_mipi *mipi)
+{
+       struct device_node *np = dev->of_node;
+       struct device_node *timings_np;
+       struct display_timings *timings;
+       struct drm_display_mode mode = {};
+       u32 bus_flags;
+       unsigned int i;
+       int err;
+
+       /* To avoid the not found error message */
+       timings_np = of_get_child_by_name(np, "display-timings");
+       if (!timings_np)
+               return 0;
+       of_node_put(timings_np);
+
+       /* Then, load timings as usual */
+       timings = of_get_display_timings(np);
+       if (!timings)
+               return dev_err_probe(dev, -EINVAL,
+                                    "%pOF: Failed to get 'display-timings'\n",
+                                    dev->of_node);
+
+       for (i = 0; i < timings->num_timings; i++) {
+               drm_display_mode_from_timing(timings->timings[i], &mode,
+                                            &bus_flags);
+
+               if (timings->native_mode == i)
+                       mode.type |= DRM_MODE_TYPE_PREFERRED;
+
+               err = panel_mipi_add_mode(dev, mipi, &mode, "display-timings");
+               if (err)
+                       return err;
+               if (bus_flags)
+                       mipi->bus_flags = bus_flags;
+       }
+
+       display_timings_release(timings);
+
+       return 0;
+}
+
+static int panel_mipi_probe_modes(struct device *dev, struct panel_mipi *mipi)
+{
+       unsigned int i = 0;
+       u32 bus_flags = 0;
+       struct drm_display_mode mode = {};
+       struct display_timing timing = {};
+       int err;
+
+       INIT_LIST_HEAD(&mipi->mode_list);
+
+       /* Read firmware first */
+       for (i = 0; mipi->firmware && i < mipi->firmware->config->num_timings;
+            i++) {
+               if (!panel_mipi_firmware_read_modes(mipi->firmware, &mode,
+                                                   &bus_flags, i)) {
+                       if (mipi->firmware->config->preferred_timing == i)
+                               mode.type |= DRM_MODE_TYPE_PREFERRED;
+
+                       err = panel_mipi_add_mode(dev, mipi, &mode, "firmware");
+                       if (err)
+                               return err;
+                       if (bus_flags)
+                               mipi->bus_flags = bus_flags;
+               }
+       }
+
+       /* Read `display-timings` and append if any */
+       err = panel_mipi_read_of_display_timings(dev, mipi);
+       if (err)
+               return err;
+
+       /* Read `panel-timing` and append if any */
+       err = of_get_display_timing(dev->of_node, "panel-timing", &timing);
+       if (err && err != -ENOENT)
+               return err;
+       else if (!err) {
+               drm_display_mode_from_timing(&timing, &mode, &bus_flags);
+               err = panel_mipi_add_mode(dev, mipi, &mode, "panel-timing");
+               if (err)
+                       return err;
+               if (bus_flags)
+                       mipi->bus_flags = bus_flags;
+       }
+
+       if (list_empty(&mipi->mode_list))
+               return dev_err_probe(dev, -EINVAL, "No modes defined\n");
+
+       return 0;
+}
+
+static void panel_mipi_cleanup(void *data)
+{
+       struct panel_mipi *mipi = (struct panel_mipi *)data;
+
+       panel_mipi_debugfs_remove(mipi);
+
+       drm_panel_remove(&mipi->panel);
+       drm_panel_disable(&mipi->panel);
+       drm_panel_unprepare(&mipi->panel);
+
+       if (mipi->firmware)
+               release_firmware(mipi->firmware->blob);
+}
+
+static int
+panel_mipi_backlight_update_status(struct backlight_device *backlight)
+{
+       struct panel_mipi *mipi = dev_get_drvdata(&backlight->dev);
+       int level = backlight_get_brightness(backlight);
+       int err;
+
+       PANEL_MIPI_TRANSACTION_LPM(mipi, false, {
+               err = panel_mipi_write(mipi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+                                      level, level);
+       });
+
+       return err;
+}
+
+static int
+panel_mipi_backlight_get_brightness(struct backlight_device *backlight)
+{
+       return backlight_get_brightness(backlight);
+}
+
+static const struct backlight_ops panel_mipi_backlight_ops = {
+       .get_brightness = panel_mipi_backlight_get_brightness,
+       .update_status = panel_mipi_backlight_update_status,
+};
+
+static int panel_mipi_set_backlight(struct drm_panel *panel, struct device 
*dev,
+                                   struct panel_mipi *mipi)
+{
+       int err;
+
+       err = drm_panel_of_backlight(panel);
+       if (err)
+               return err;
+
+       if (!panel->backlight) {
+               struct backlight_device *backlight = NULL;
+               struct backlight_properties props = {
+                       .max_brightness = 255,
+                       .type = BACKLIGHT_RAW,
+               };
+
+               backlight = devm_backlight_device_register(
+                       dev, dev_name(dev), dev, mipi,
+                       &panel_mipi_backlight_ops, &props);
+               if (IS_ERR(backlight))
+                       return PTR_ERR(backlight);
+
+               panel->backlight = backlight;
+       }
+
+       return 0;
+}
+
+static int panel_mipi_probe(struct device *dev, int connector_type)
+{
+       struct panel_mipi *mipi;
+       int err;
+
+       mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
+       if (!mipi)
+               return -ENOMEM;
+
+       mutex_init(&mipi->lock);
+
+       /* Get `power-supply` and `io-supply` (if any) */
+       mipi->supplies[0].supply = "power";
+       mipi->supplies[1].supply = "io";
+       err = devm_regulator_bulk_get(dev, ARRAY_SIZE(mipi->supplies),
+                                     mipi->supplies);
+       if (err < 0) {
+               return dev_err_probe(dev, err,
+                                    "%pOF: Failed to get regulators\n",
+                                    dev->of_node);
+       }
+
+       /* GPIO for /RESET */
+       mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(mipi->reset))
+               return dev_err_probe(dev, PTR_ERR(mipi->reset),
+                                    "%pOF: Failed to get GPIO for RESET\n",
+                                    dev->of_node);
+
+       /* Default values */
+       mipi->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+       mipi->reset_delay = 1;
+       mipi->init_delay = 10;
+       mipi->sleep_delay = 120;
+       mipi->backlight_delay = 120;
+
+       /* Load from the firmware */
+       mipi->firmware = panel_mipi_load_firmware(dev);
+       if (IS_ERR(mipi->firmware))
+               return dev_err_probe(dev, PTR_ERR(mipi->firmware),
+                                    "Failed to load a firmware\n");
+       err = panel_mipi_read_firmware(dev, mipi, mipi->firmware);
+       if (err)
+               return err;
+
+       /* Load panel modes */
+       err = panel_mipi_probe_modes(dev, mipi);
+       if (err)
+               return err;
+
+       /* Load overrides from the DT */
+       const struct property *prop =
+               of_find_property(dev->of_node, "init-sequence", NULL);
+       if (prop && prop->value && prop->length > 0) {
+               err = panel_mipi_load_commands(mipi, prop->value, prop->length);
+               if (err)
+                       return dev_err_probe(
+                               dev, err, "%pOF: Malformed command sequence\n",
+                               dev->of_node);
+       }
+       of_property_read_u32(dev->of_node, "width-mm", &mipi->width_mm);
+       of_property_read_u32(dev->of_node, "height-mm", &mipi->height_mm);
+       of_property_read_u32(dev->of_node, "reset-delay", &mipi->reset_delay);
+       of_property_read_u32(dev->of_node, "init-delay", &mipi->init_delay);
+       of_property_read_u32(dev->of_node, "sleep-delay", &mipi->sleep_delay);
+       of_property_read_u32(dev->of_node, "backlight-delay",
+                            &mipi->backlight_delay);
+       if (of_find_property(dev->of_node, "rotation", NULL)) {
+               err = of_drm_get_panel_orientation(dev->of_node,
+                                                  &mipi->orientation);
+               if (err)
+                       return dev_err_probe(dev, err,
+                                            "%pOF: Invalid rotation\n",
+                                            dev->of_node);
+       }
+
+       /* DRM panel setup */
+       drm_panel_init(&mipi->panel, dev, &panel_mipi_funcs, connector_type);
+
+       err = panel_mipi_set_backlight(&mipi->panel, dev, mipi);
+       if (err)
+               return dev_err_probe(dev, err, "Failed to set backlight\n");
+
+       drm_panel_add(&mipi->panel);
+
+       dev_set_drvdata(dev, mipi);
+
+       panel_mipi_debugfs_init(dev, mipi);
+
+       return devm_add_action_or_reset(dev, panel_mipi_cleanup, mipi);
+}
+
+static unsigned long panel_mipi_read_dsi_mode_props(struct device_node *np)
+{
+       unsigned long mode = 0;
+
+       mode |= of_property_read_bool(np, "dsi-mode-video") ?
+                       MIPI_DSI_MODE_VIDEO :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-burst") ?
+                       MIPI_DSI_MODE_VIDEO_BURST :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-sync-pulse") ?
+                       MIPI_DSI_MODE_VIDEO_SYNC_PULSE :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-auto-vert") ?
+                       MIPI_DSI_MODE_VIDEO_AUTO_VERT :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-hse") ?
+                       MIPI_DSI_MODE_VIDEO_HSE :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-no-hfp") ?
+                       MIPI_DSI_MODE_VIDEO_NO_HFP :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-no-hbp") ?
+                       MIPI_DSI_MODE_VIDEO_NO_HBP :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-video-no-hsa") ?
+                       MIPI_DSI_MODE_VIDEO_NO_HSA :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-vsync-flush") ?
+                       MIPI_DSI_MODE_VSYNC_FLUSH :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-no-eot-packet") ?
+                       MIPI_DSI_MODE_NO_EOT_PACKET :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-clock-non-continuous") ?
+                       MIPI_DSI_CLOCK_NON_CONTINUOUS :
+                       0;
+       mode |= of_property_read_bool(np, "dsi-mode-lpm") ? MIPI_DSI_MODE_LPM :
+                                                           0;
+       mode |= of_property_read_bool(np, "dsi-hs-pkt-end-aligned") ?
+                       MIPI_DSI_HS_PKT_END_ALIGNED :
+                       0;
+
+       return mode;
+}
+
+static int panel_mipi_dsi_probe(struct mipi_dsi_device *dsi)
+{
+       struct panel_mipi *mipi;
+       const char *format;
+       int err;
+
+       err = panel_mipi_probe(&dsi->dev, DRM_MODE_CONNECTOR_DSI);
+       if (err)
+               return err;
+
+       mipi = dev_get_drvdata(&dsi->dev);
+       mipi->dsi = dsi;
+       mipi->write_command = panel_mipi_dsi_write;
+       mipi->read_command = panel_mipi_dsi_read;
+
+       /* Default values */
+       dsi->lanes = 1;
+       dsi->format = MIPI_DSI_FMT_RGB888;
+       dsi->mode_flags = 0;
+
+       /* Read from the firmware */
+       if (mipi->firmware) {
+               dsi->lanes = be16_to_cpu(mipi->firmware->config->dsi_lanes);
+               dsi->format = be16_to_cpu(mipi->firmware->config->dsi_format);
+               dsi->mode_flags =
+                       be32_to_cpu(mipi->firmware->config->dsi_mode_flags);
+       }
+
+       /* Load from the DT */
+       of_property_read_u32(dsi->dev.of_node, "dsi-lanes", &dsi->lanes);
+       if (!of_property_read_string(dsi->dev.of_node, "dsi-format", &format)) {
+               if (!strcmp(format, "rgb888"))
+                       dsi->format = MIPI_DSI_FMT_RGB888;
+               else if (!strcmp(format, "rgb666"))
+                       dsi->format = MIPI_DSI_FMT_RGB666;
+               else if (!strcmp(format, "rgb666-packed"))
+                       dsi->format = MIPI_DSI_FMT_RGB666_PACKED;
+               else if (!strcmp(format, "rgb565"))
+                       dsi->format = MIPI_DSI_FMT_RGB565;
+               else
+                       return dev_err_probe(&dsi->dev, -EINVAL,
+                                            "%pOF: Unknown dsi-format '%s'\n",
+                                            dsi->dev.of_node, format);
+       }
+       dsi->mode_flags |= panel_mipi_read_dsi_mode_props(dsi->dev.of_node);
+
+       if (!dsi->lanes)
+               return dev_err_probe(&dsi->dev, -EINVAL,
+                                    "dsi-lanes == 0 for DSI panel\n");
+
+       /* Adjust bus_format */
+       switch (dsi->format) {
+       case MIPI_DSI_FMT_RGB888:
+               mipi->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+               break;
+       case MIPI_DSI_FMT_RGB666:
+               mipi->bus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+               break;
+       case MIPI_DSI_FMT_RGB666_PACKED:
+               mipi->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+               break;
+       case MIPI_DSI_FMT_RGB565:
+               mipi->bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+               break;
+       }
+
+       err = mipi_dsi_attach(dsi);
+       if (err)
+               return dev_err_probe(&dsi->dev, err, "Failed to init DSI\n");
+
+       return 0;
+}
+
+static int panel_mipi_spi_probe(struct spi_device *spi)
+{
+       struct panel_mipi *mipi;
+       struct gpio_desc *dc;
+       int err;
+
+       err = panel_mipi_probe(&spi->dev, DRM_MODE_CONNECTOR_DPI);
+       if (err)
+               return err;
+
+       mipi = dev_get_drvdata(&spi->dev);
+       mipi->write_command = panel_mipi_dbi_write;
+       mipi->read_command = panel_mipi_dbi_read;
+
+       dc = devm_gpiod_get_optional(&spi->dev, "dc", GPIOD_OUT_LOW);
+       if (IS_ERR(dc))
+               return dev_err_probe(&spi->dev, PTR_ERR(dc),
+                                    "Failed to get GPIO for D/CX\n");
+
+       err = mipi_dbi_spi_init(spi, &mipi->dbi, dc);
+       if (err)
+               return dev_err_probe(&spi->dev, err, "Failed to init SPI\n");
+
+       return 0;
+}
+
+static void panel_mipi_dsi_remove(struct mipi_dsi_device *dsi)
+{
+       mipi_dsi_detach(dsi);
+}
+
+static const struct of_device_id panel_mipi_dsi_of_match[] = {
+       { .compatible = "panel-mipi-dsi" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, panel_mipi_dsi_of_match);
+
+static const struct of_device_id panel_mipi_spi_of_match[] = {
+       { .compatible = "panel-mipi-dpi-spi" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, panel_mipi_spi_of_match);
+
+static const struct spi_device_id panel_mipi_spi_ids[] = {
+       { "panel-mipi-dpi-spi" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, panel_mipi_spi_ids);
+
+static struct mipi_dsi_driver panel_mipi_dsi_driver = {
+       .probe          = panel_mipi_dsi_probe,
+       .remove         = panel_mipi_dsi_remove,
+       .driver = {
+               .name           = "panel-mipi",
+               .of_match_table = panel_mipi_dsi_of_match,
+       },
+};
+
+static struct spi_driver panel_mipi_spi_driver = {
+       .probe          = panel_mipi_spi_probe,
+       .id_table       = panel_mipi_spi_ids,
+       .driver = {
+               .name           = "panel-mipi",
+               .of_match_table = panel_mipi_spi_of_match,
+       },
+};
+
+static int __init panel_mipi_driver_init(void)
+{
+       int err;
+
+       if (IS_ENABLED(CONFIG_SPI)) {
+               err = spi_register_driver(&panel_mipi_spi_driver);
+               if (err)
+                       return err;
+       }
+
+       if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
+               err = mipi_dsi_driver_register(&panel_mipi_dsi_driver);
+               if (err) {
+                       if (IS_ENABLED(CONFIG_SPI))
+                               spi_unregister_driver(&panel_mipi_spi_driver);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+module_init(panel_mipi_driver_init);
+
+static void __exit panel_mipi_driver_exit(void)
+{
+       if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+               mipi_dsi_driver_unregister(&panel_mipi_dsi_driver);
+
+       if (IS_ENABLED(CONFIG_SPI))
+               spi_unregister_driver(&panel_mipi_spi_driver);
+}
+module_exit(panel_mipi_driver_exit);
+
+MODULE_DESCRIPTION("Generic MIPI-DSI/DPI(+SPI) Panel Driver");
+MODULE_AUTHOR("Hironori KIKUCHI <kikucha...@gmail.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.48.1

Reply via email to