summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Muellner <christoph.muellner@theobroma-systems.com>2018-12-21 13:26:55 +0100
committerChristoph Müllner <christophm30@gmail.com>2019-04-07 15:54:08 +0200
commit53b8aaf03b57d96ecb53c27e29916a528cff5514 (patch)
tree7008bccf0e12e58908618ef7984cac56ceea5dec
parentb5007a32995bc5bc73561482c3f0e42e9eda24ce (diff)
drm: panel: Add DTS configurable panel driver.
This driver is derived from the panel-simple driver, but instead of hard-coded profiles in the source code, it works based on information found in the DTB. Signed-off-by: Christoph Muellner <christoph.muellner@theobroma-systems.com>
-rw-r--r--drivers/gpu/drm/panel/Kconfig11
-rw-r--r--drivers/gpu/drm/panel/Makefile1
-rw-r--r--drivers/gpu/drm/panel/simple-panel.c970
3 files changed, 982 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 6020c30a33b3..d6e2f54799fb 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -38,6 +38,17 @@ config DRM_PANEL_SIMPLE
that it can be automatically turned off when the panel goes into a
low power state.
+config DRM_SIMPLE_PANEL
+ tristate "support for simple panels configured via DTS"
+ depends on OF
+ depends on BACKLIGHT_CLASS_DEVICE
+ select VIDEOMODE_HELPERS
+ help
+ DRM panel driver for dumb panels that need at most a regulator and
+ a GPIO to be powered up. Optionally a backlight can be attached so
+ that it can be automatically turned off when the panel goes into a
+ low power state. Panel configuration can be achieved by DTS nodes.
+
config DRM_PANEL_ILITEK_IL9322
tristate "Ilitek ILI9322 320x240 QVGA panels"
depends on OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 5ccaaa9d13af..1ce143e8ccd9 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o
obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
+obj-$(CONFIG_DRM_SIMPLE_PANEL) += simple-panel.o
obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
diff --git a/drivers/gpu/drm/panel/simple-panel.c b/drivers/gpu/drm/panel/simple-panel.c
new file mode 100644
index 000000000000..0f1eb6df487e
--- /dev/null
+++ b/drivers/gpu/drm/panel/simple-panel.c
@@ -0,0 +1,970 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 Theobroma Systems Design und Consulting GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/backlight.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#include <video/display_timing.h>
+#include <video/mipi_display.h>
+#include <linux/of_device.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+struct cmd_ctrl_hdr {
+ u8 dtype; /* data type */
+ u8 wait; /* ms */
+ u8 dlen; /* payload len */
+} __packed;
+
+struct cmd_desc {
+ struct cmd_ctrl_hdr dchdr;
+ u8 *payload;
+};
+
+struct panel_cmds {
+ u8 *buf;
+ int blen;
+ struct cmd_desc *cmds;
+ int cmd_cnt;
+};
+
+struct panel_desc {
+ const struct drm_display_mode *modes;
+ unsigned int num_modes;
+ const struct display_timing *timings;
+ unsigned int num_timings;
+
+ unsigned int bpc;
+
+ /**
+ * @width: width (in millimeters) of the panel's active display area
+ * @height: height (in millimeters) of the panel's active display area
+ */
+ struct {
+ unsigned int width;
+ unsigned int height;
+ } size;
+
+ /**
+ * @reset: the time (in milliseconds) indicates the delay time
+ * after the panel to operate reset gpio
+ * @init: the time (in milliseconds) that it takes for the panel to
+ * power on and dsi host can send command to panel
+ * @prepare: the time (in milliseconds) that it takes for the panel to
+ * become ready and start receiving video data
+ * @enable: the time (in milliseconds) that it takes for the panel to
+ * display the first valid frame after starting to receive
+ * video data
+ * @disable: the time (in milliseconds) that it takes for the panel to
+ * turn the display off (no content is visible)
+ * @unprepare: the time (in milliseconds) that it takes for the panel
+ * to power itself down completely
+ */
+ struct {
+ unsigned int reset;
+ unsigned int init;
+ unsigned int prepare;
+ unsigned int enable;
+ unsigned int disable;
+ unsigned int unprepare;
+ } delay;
+
+ u32 bus_format;
+ u32 bus_flags;
+};
+
+struct panel_simple {
+ struct drm_panel base;
+ struct mipi_dsi_device *dsi;
+ bool prepared;
+ bool enabled;
+ bool power_invert;
+
+ struct device *dev;
+ const struct panel_desc *desc;
+
+ struct backlight_device *backlight;
+ struct regulator *supply;
+ struct i2c_adapter *ddc;
+
+ struct gpio_desc *enable_gpio;
+ struct gpio_desc *reset_gpio;
+ int cmd_type;
+
+ struct gpio_desc *spi_sdi_gpio;
+ struct gpio_desc *spi_scl_gpio;
+ struct gpio_desc *spi_cs_gpio;
+
+ struct panel_cmds *on_cmds;
+ struct panel_cmds *off_cmds;
+};
+
+enum rockchip_cmd_type {
+ CMD_TYPE_DEFAULT,
+ CMD_TYPE_SPI,
+ CMD_TYPE_MCU
+};
+
+static inline int get_panel_cmd_type(const char *s)
+{
+ if (!s)
+ return -EINVAL;
+
+ if (strncmp(s, "spi", 3) == 0)
+ return CMD_TYPE_SPI;
+ else if (strncmp(s, "mcu", 3) == 0)
+ return CMD_TYPE_MCU;
+
+ return CMD_TYPE_DEFAULT;
+}
+
+static inline struct panel_simple *to_panel_simple(struct drm_panel *panel)
+{
+ return container_of(panel, struct panel_simple, base);
+}
+
+static void panel_simple_cmds_cleanup(struct panel_simple *p)
+{
+ if (p->on_cmds) {
+ kfree(p->on_cmds->buf);
+ kfree(p->on_cmds->cmds);
+ }
+
+ if (p->off_cmds) {
+ kfree(p->off_cmds->buf);
+ kfree(p->off_cmds->cmds);
+ }
+}
+
+static int panel_simple_parse_cmds(struct device *dev,
+ const u8 *data, int blen,
+ struct panel_cmds *pcmds)
+{
+ int len;
+ char *buf, *bp;
+ struct cmd_ctrl_hdr *dchdr;
+ int i, cnt;
+
+ if (!pcmds)
+ return -EINVAL;
+
+ buf = kmemdup(data, blen, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* scan init commands */
+ bp = buf;
+ len = blen;
+ cnt = 0;
+ while (len > sizeof(*dchdr)) {
+ dchdr = (struct cmd_ctrl_hdr *)bp;
+
+ if (dchdr->dlen > len) {
+ dev_err(dev, "%s: error, len=%d", __func__,
+ dchdr->dlen);
+ return -EINVAL;
+ }
+
+ bp += sizeof(*dchdr);
+ len -= sizeof(*dchdr);
+ bp += dchdr->dlen;
+ len -= dchdr->dlen;
+ cnt++;
+ }
+
+ if (len != 0) {
+ dev_err(dev, "%s: dcs_cmd=%x len=%d error!",
+ __func__, buf[0], blen);
+ kfree(buf);
+ return -EINVAL;
+ }
+
+ pcmds->cmds = kcalloc(cnt, sizeof(struct cmd_desc), GFP_KERNEL);
+ if (!pcmds->cmds) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ pcmds->cmd_cnt = cnt;
+ pcmds->buf = buf;
+ pcmds->blen = blen;
+
+ bp = buf;
+ len = blen;
+ for (i = 0; i < cnt; i++) {
+ dchdr = (struct cmd_ctrl_hdr *)bp;
+ len -= sizeof(*dchdr);
+ bp += sizeof(*dchdr);
+ pcmds->cmds[i].dchdr = *dchdr;
+ pcmds->cmds[i].payload = bp;
+ bp += dchdr->dlen;
+ len -= dchdr->dlen;
+ }
+
+ return 0;
+}
+
+static void panel_simple_spi_write_cmd(struct panel_simple *panel,
+ u8 type, int value)
+{
+ int i;
+
+ gpiod_direction_output(panel->spi_cs_gpio, 0);
+
+ if (type == 0)
+ value &= (~(1 << 8));
+ else
+ value |= (1 << 8);
+
+ for (i = 0; i < 9; i++) {
+ if (value & 0x100)
+ gpiod_direction_output(panel->spi_sdi_gpio, 1);
+ else
+ gpiod_direction_output(panel->spi_sdi_gpio, 0);
+
+ gpiod_direction_output(panel->spi_scl_gpio, 0);
+ udelay(10);
+ gpiod_direction_output(panel->spi_scl_gpio, 1);
+ value <<= 1;
+ udelay(10);
+ }
+
+ gpiod_direction_output(panel->spi_cs_gpio, 1);
+}
+
+static int panel_simple_spi_send_cmds(struct panel_simple *panel,
+ struct panel_cmds *cmds)
+{
+ int i;
+
+ if (!cmds)
+ return -EINVAL;
+
+ for (i = 0; i < cmds->cmd_cnt; i++) {
+ struct cmd_desc *cmd = &cmds->cmds[i];
+ int value = 0;
+
+ if (cmd->dchdr.dlen == 2)
+ value = (cmd->payload[0] << 8) | cmd->payload[1];
+ else
+ value = cmd->payload[0];
+ panel_simple_spi_write_cmd(panel, cmd->dchdr.dtype, value);
+
+ if (cmd->dchdr.wait)
+ msleep(cmd->dchdr.wait);
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_DRM_MIPI_DSI)
+static int panel_simple_dsi_send_cmds(struct panel_simple *panel,
+ struct panel_cmds *cmds)
+{
+ struct mipi_dsi_device *dsi = panel->dsi;
+ int i, err;
+
+ if (!cmds)
+ return -EINVAL;
+
+ for (i = 0; i < cmds->cmd_cnt; i++) {
+ struct cmd_desc *cmd = &cmds->cmds[i];
+
+ switch (cmd->dchdr.dtype) {
+ case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
+ case MIPI_DSI_GENERIC_LONG_WRITE:
+ err = mipi_dsi_generic_write(dsi, cmd->payload,
+ cmd->dchdr.dlen);
+ break;
+ case MIPI_DSI_DCS_SHORT_WRITE:
+ case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
+ case MIPI_DSI_DCS_LONG_WRITE:
+ err = mipi_dsi_dcs_write_buffer(dsi, cmd->payload,
+ cmd->dchdr.dlen);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (err < 0)
+ dev_err(panel->dev, "failed to write dcs cmd: %d\n",
+ err);
+
+ if (cmd->dchdr.wait)
+ msleep(cmd->dchdr.wait);
+ }
+
+ return 0;
+}
+#else
+static inline int panel_simple_dsi_send_cmds(struct panel_simple *panel,
+ struct panel_cmds *cmds)
+{
+ return -EINVAL;
+}
+#endif
+
+static int panel_simple_get_cmds(struct panel_simple *panel)
+{
+ const void *data;
+ int len;
+ int err;
+
+ data = of_get_property(panel->dev->of_node, "panel-init-sequence",
+ &len);
+ if (data) {
+ panel->on_cmds = devm_kzalloc(panel->dev,
+ sizeof(*panel->on_cmds),
+ GFP_KERNEL);
+ if (!panel->on_cmds)
+ return -ENOMEM;
+
+ err = panel_simple_parse_cmds(panel->dev, data, len,
+ panel->on_cmds);
+ if (err) {
+ dev_err(panel->dev, "failed to parse panel init sequence\n");
+ return err;
+ }
+ }
+
+ data = of_get_property(panel->dev->of_node, "panel-exit-sequence",
+ &len);
+ if (data) {
+ panel->off_cmds = devm_kzalloc(panel->dev,
+ sizeof(*panel->off_cmds),
+ GFP_KERNEL);
+ if (!panel->off_cmds)
+ return -ENOMEM;
+
+ err = panel_simple_parse_cmds(panel->dev, data, len,
+ panel->off_cmds);
+ if (err) {
+ dev_err(panel->dev, "failed to parse panel exit sequence\n");
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int panel_simple_get_fixed_modes(struct panel_simple *panel)
+{
+ struct drm_connector *connector = panel->base.connector;
+ struct drm_device *drm = panel->base.drm;
+ struct drm_display_mode *mode;
+ unsigned int i, num = 0;
+
+ if (!panel->desc)
+ return 0;
+
+ for (i = 0; i < panel->desc->num_timings; i++) {
+ const struct display_timing *dt = &panel->desc->timings[i];
+ struct videomode vm;
+
+ videomode_from_timing(dt, &vm);
+ mode = drm_mode_create(drm);
+ if (!mode) {
+ dev_err(drm->dev, "failed to add mode %ux%u\n",
+ dt->hactive.typ, dt->vactive.typ);
+ continue;
+ }
+
+ drm_display_mode_from_videomode(&vm, mode);
+ drm_mode_set_name(mode);
+
+ drm_mode_probed_add(connector, mode);
+ num++;
+ }
+
+ for (i = 0; i < panel->desc->num_modes; i++) {
+ const struct drm_display_mode *m = &panel->desc->modes[i];
+
+ mode = drm_mode_duplicate(drm, m);
+ if (!mode) {
+ dev_err(drm->dev, "failed to add mode %ux%u@%u\n",
+ m->hdisplay, m->vdisplay, m->vrefresh);
+ continue;
+ }
+
+ drm_mode_set_name(mode);
+
+ drm_mode_probed_add(connector, mode);
+ num++;
+ }
+
+ connector->display_info.bpc = panel->desc->bpc;
+ connector->display_info.width_mm = panel->desc->size.width;
+ connector->display_info.height_mm = panel->desc->size.height;
+ if (panel->desc->bus_format)
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &panel->desc->bus_format, 1);
+
+ return num;
+}
+
+static int panel_simple_of_get_native_mode(struct panel_simple *panel)
+{
+ struct drm_connector *connector = panel->base.connector;
+ struct drm_device *drm = panel->base.drm;
+ struct drm_display_mode *mode;
+ struct device_node *timings_np;
+ int ret;
+
+ timings_np = of_get_child_by_name(panel->dev->of_node,
+ "display-timings");
+ if (!timings_np) {
+ dev_dbg(panel->dev, "failed to find display-timings node\n");
+ return 0;
+ }
+
+ of_node_put(timings_np);
+ mode = drm_mode_create(drm);
+ if (!mode)
+ return 0;
+
+ ret = of_get_drm_display_mode(panel->dev->of_node, mode,
+ NULL, OF_USE_NATIVE_MODE);
+ if (ret) {
+ dev_dbg(panel->dev, "failed to find dts display timings\n");
+ drm_mode_destroy(drm, mode);
+ return 0;
+ }
+
+ drm_mode_set_name(mode);
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static int panel_simple_regulator_enable(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int err = 0;
+
+ if (p->power_invert) {
+ if (regulator_is_enabled(p->supply) > 0)
+ regulator_disable(p->supply);
+ } else {
+ err = regulator_enable(p->supply);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable supply: %d\n",
+ err);
+ return err;
+ }
+ }
+
+ return err;
+}
+
+static int panel_simple_regulator_disable(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int err = 0;
+
+ if (p->power_invert) {
+ if (!regulator_is_enabled(p->supply)) {
+ err = regulator_enable(p->supply);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable supply: %d\n",
+ err);
+ return err;
+ }
+ }
+ } else {
+ regulator_disable(p->supply);
+ }
+
+ return err;
+}
+
+static int panel_simple_disable(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+
+ if (!p->enabled)
+ return 0;
+
+ if (p->backlight) {
+ p->backlight->props.power = FB_BLANK_POWERDOWN;
+ backlight_update_status(p->backlight);
+ }
+
+ if (p->desc && p->desc->delay.disable)
+ msleep(p->desc->delay.disable);
+
+ p->enabled = false;
+
+ return 0;
+}
+
+static int panel_simple_unprepare(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int err = 0;
+
+ if (!p->prepared)
+ return 0;
+
+ if (p->off_cmds) {
+ if (p->dsi)
+ err = panel_simple_dsi_send_cmds(p, p->off_cmds);
+ else if (p->cmd_type == CMD_TYPE_SPI)
+ err = panel_simple_spi_send_cmds(p, p->off_cmds);
+ if (err)
+ dev_err(p->dev, "failed to send off cmds\n");
+ }
+
+ if (p->reset_gpio)
+ gpiod_direction_output(p->reset_gpio, 1);
+
+ if (p->enable_gpio)
+ gpiod_direction_output(p->enable_gpio, 0);
+
+ panel_simple_regulator_disable(panel);
+
+ if (p->desc && p->desc->delay.unprepare)
+ msleep(p->desc->delay.unprepare);
+
+ p->prepared = false;
+
+ return 0;
+}
+
+static int panel_simple_prepare(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int err;
+
+ if (p->prepared)
+ return 0;
+
+ err = panel_simple_regulator_enable(panel);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable supply: %d\n", err);
+ return err;
+ }
+
+ if (p->enable_gpio)
+ gpiod_direction_output(p->enable_gpio, 1);
+
+ if (p->desc && p->desc->delay.prepare)
+ msleep(p->desc->delay.prepare);
+
+ if (p->reset_gpio)
+ gpiod_direction_output(p->reset_gpio, 1);
+
+ if (p->desc && p->desc->delay.reset)
+ msleep(p->desc->delay.reset);
+
+ if (p->reset_gpio)
+ gpiod_direction_output(p->reset_gpio, 0);
+
+ if (p->desc && p->desc->delay.init)
+ msleep(p->desc->delay.init);
+
+ if (p->on_cmds) {
+ if (p->dsi)
+ err = panel_simple_dsi_send_cmds(p, p->on_cmds);
+ else if (p->cmd_type == CMD_TYPE_SPI)
+ err = panel_simple_spi_send_cmds(p, p->on_cmds);
+ if (err)
+ dev_err(p->dev, "failed to send on cmds\n");
+ }
+
+ p->prepared = true;
+
+ return 0;
+}
+
+static int panel_simple_enable(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+
+ if (p->enabled)
+ return 0;
+
+ if (p->desc && p->desc->delay.enable)
+ msleep(p->desc->delay.enable);
+
+ if (p->backlight) {
+ p->backlight->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(p->backlight);
+ }
+
+ p->enabled = true;
+
+ return 0;
+}
+
+static int panel_simple_get_modes(struct drm_panel *panel)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ int num = 0;
+
+ /* add device node plane modes */
+ num += panel_simple_of_get_native_mode(p);
+
+ /* add hard-coded panel modes */
+ num += panel_simple_get_fixed_modes(p);
+
+ /* probe EDID if a DDC bus is available */
+ if (p->ddc) {
+ struct edid *edid = drm_get_edid(panel->connector, p->ddc);
+ drm_connector_update_edid_property(panel->connector, edid);
+ if (edid) {
+ num += drm_add_edid_modes(panel->connector, edid);
+ kfree(edid);
+ }
+ }
+
+ return num;
+}
+
+static int panel_simple_get_timings(struct drm_panel *panel,
+ unsigned int num_timings,
+ struct display_timing *timings)
+{
+ struct panel_simple *p = to_panel_simple(panel);
+ unsigned int i;
+
+ if (!p->desc)
+ return 0;
+
+ if (p->desc->num_timings < num_timings)
+ num_timings = p->desc->num_timings;
+
+ if (timings)
+ for (i = 0; i < num_timings; i++)
+ timings[i] = p->desc->timings[i];
+
+ return p->desc->num_timings;
+}
+
+static const struct drm_panel_funcs panel_simple_funcs = {
+ .disable = panel_simple_disable,
+ .unprepare = panel_simple_unprepare,
+ .prepare = panel_simple_prepare,
+ .enable = panel_simple_enable,
+ .get_modes = panel_simple_get_modes,
+ .get_timings = panel_simple_get_timings,
+};
+
+static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
+{
+ struct device_node *backlight, *ddc;
+ struct panel_simple *panel;
+ struct panel_desc *of_desc;
+ const char *cmd_type;
+ u32 val;
+ int err;
+
+ panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ if (!desc)
+ of_desc = devm_kzalloc(dev, sizeof(*of_desc), GFP_KERNEL);
+ else
+ of_desc = devm_kmemdup(dev, desc, sizeof(*of_desc), GFP_KERNEL);
+
+ if (!of_property_read_u32(dev->of_node, "bus-format", &val))
+ of_desc->bus_format = val;
+ if (!of_property_read_u32(dev->of_node, "prepare-delay-ms", &val))
+ of_desc->delay.prepare = val;
+ if (!of_property_read_u32(dev->of_node, "enable-delay-ms", &val))
+ of_desc->delay.enable = val;
+ if (!of_property_read_u32(dev->of_node, "disable-delay-ms", &val))
+ of_desc->delay.disable = val;
+ if (!of_property_read_u32(dev->of_node, "unprepare-delay-ms", &val))
+ of_desc->delay.unprepare = val;
+ if (!of_property_read_u32(dev->of_node, "reset-delay-ms", &val))
+ of_desc->delay.reset = val;
+ if (!of_property_read_u32(dev->of_node, "init-delay-ms", &val))
+ of_desc->delay.init = val;
+ if (!of_property_read_u32(dev->of_node, "width-mm", &val))
+ of_desc->size.width = val;
+ if (!of_property_read_u32(dev->of_node, "height-mm", &val))
+ of_desc->size.height = val;
+
+ panel->enabled = false;
+ panel->prepared = false;
+ panel->desc = of_desc;
+ panel->dev = dev;
+
+ err = panel_simple_get_cmds(panel);
+ if (err) {
+ dev_err(dev, "failed to get init cmd: %d\n", err);
+ return err;
+ }
+ panel->supply = devm_regulator_get(dev, "power");
+ if (IS_ERR(panel->supply))
+ return PTR_ERR(panel->supply);
+
+ panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", 0);
+ if (IS_ERR(panel->enable_gpio)) {
+ err = PTR_ERR(panel->enable_gpio);
+ dev_err(dev, "failed to request enable GPIO: %d\n", err);
+ return err;
+ }
+
+ panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", 0);
+ if (IS_ERR(panel->reset_gpio)) {
+ err = PTR_ERR(panel->reset_gpio);
+ dev_err(dev, "failed to request reset GPIO: %d\n", err);
+ return err;
+ }
+
+ if (of_property_read_string(dev->of_node, "rockchip,cmd-type",
+ &cmd_type))
+ panel->cmd_type = CMD_TYPE_DEFAULT;
+ else
+ panel->cmd_type = get_panel_cmd_type(cmd_type);
+
+ if (panel->cmd_type == CMD_TYPE_SPI) {
+ panel->spi_sdi_gpio =
+ devm_gpiod_get_optional(dev, "spi-sdi", 0);
+ if (IS_ERR(panel->spi_sdi_gpio)) {
+ err = PTR_ERR(panel->spi_sdi_gpio);
+ dev_err(dev, "failed to request spi_sdi: %d\n", err);
+ return err;
+ }
+
+ panel->spi_scl_gpio =
+ devm_gpiod_get_optional(dev, "spi-scl", 0);
+ if (IS_ERR(panel->spi_scl_gpio)) {
+ err = PTR_ERR(panel->spi_scl_gpio);
+ dev_err(dev, "failed to request spi_scl: %d\n", err);
+ return err;
+ }
+
+ panel->spi_cs_gpio = devm_gpiod_get_optional(dev, "spi-cs", 0);
+ if (IS_ERR(panel->spi_cs_gpio)) {
+ err = PTR_ERR(panel->spi_cs_gpio);
+ dev_err(dev, "failed to request spi_cs: %d\n", err);
+ return err;
+ }
+ gpiod_direction_output(panel->spi_cs_gpio, 1);
+ gpiod_direction_output(panel->spi_sdi_gpio, 1);
+ gpiod_direction_output(panel->spi_scl_gpio, 1);
+ }
+ panel->power_invert =
+ of_property_read_bool(dev->of_node, "power-invert");
+
+ backlight = of_parse_phandle(dev->of_node, "backlight", 0);
+ if (backlight) {
+ panel->backlight = of_find_backlight_by_node(backlight);
+ of_node_put(backlight);
+
+ if (!panel->backlight) {
+ return -EPROBE_DEFER;
+ }
+ }
+
+ ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0);
+ if (ddc) {
+ panel->ddc = of_find_i2c_adapter_by_node(ddc);
+ of_node_put(ddc);
+
+ if (!panel->ddc) {
+ err = -EPROBE_DEFER;
+ goto free_backlight;
+ }
+ }
+
+ drm_panel_init(&panel->base);
+ panel->base.dev = dev;
+ panel->base.funcs = &panel_simple_funcs;
+
+ err = drm_panel_add(&panel->base);
+ if (err < 0) {
+ goto free_ddc;
+ }
+
+ dev_set_drvdata(dev, panel);
+
+ return 0;
+
+free_ddc:
+ if (panel->ddc)
+ put_device(&panel->ddc->dev);
+free_backlight:
+ if (panel->backlight)
+ put_device(&panel->backlight->dev);
+
+ return err;
+}
+
+static int panel_simple_remove(struct device *dev)
+{
+ struct panel_simple *panel = dev_get_drvdata(dev);
+
+ drm_panel_detach(&panel->base);
+ drm_panel_remove(&panel->base);
+
+ panel_simple_disable(&panel->base);
+ panel_simple_unprepare(&panel->base);
+
+ if (panel->ddc)
+ put_device(&panel->ddc->dev);
+
+ if (panel->backlight)
+ put_device(&panel->backlight->dev);
+
+ panel_simple_cmds_cleanup(panel);
+
+ return 0;
+}
+
+static void panel_simple_shutdown(struct device *dev)
+{
+ struct panel_simple *panel = dev_get_drvdata(dev);
+
+ panel_simple_disable(&panel->base);
+ panel_simple_unprepare(&panel->base);
+}
+
+struct panel_desc_dsi {
+ struct panel_desc desc;
+
+ unsigned long flags;
+ enum mipi_dsi_pixel_format format;
+ unsigned int lanes;
+};
+
+static const struct of_device_id dsi_of_match[] = {
+ {
+ .compatible = "simple-panel-dt",
+ .data = NULL
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, dsi_of_match);
+
+static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi)
+{
+ struct panel_simple *panel;
+ const struct panel_desc_dsi *desc;
+ const struct of_device_id *id;
+ const struct panel_desc *pdesc;
+ int err;
+ u32 val;
+
+ id = of_match_node(dsi_of_match, dsi->dev.of_node);
+ if (!id) {
+ return -ENODEV;
+ }
+
+ desc = id->data;
+
+ if (desc) {
+ dsi->mode_flags = desc->flags;
+ dsi->format = desc->format;
+ dsi->lanes = desc->lanes;
+ pdesc = &desc->desc;
+ } else {
+ pdesc = NULL;
+ }
+
+ err = panel_simple_probe(&dsi->dev, pdesc);
+ if (err < 0) {
+ return err;
+ }
+
+ panel = dev_get_drvdata(&dsi->dev);
+ panel->dsi = dsi;
+
+ if (!of_property_read_u32(dsi->dev.of_node, "dsi,flags", &val))
+ dsi->mode_flags = val;
+
+ if (!of_property_read_u32(dsi->dev.of_node, "dsi,format", &val))
+ dsi->format = val;
+
+ if (!of_property_read_u32(dsi->dev.of_node, "dsi,lanes", &val))
+ dsi->lanes = val;
+
+ return mipi_dsi_attach(dsi);
+}
+
+static int panel_simple_dsi_remove(struct mipi_dsi_device *dsi)
+{
+ int err;
+
+ err = mipi_dsi_detach(dsi);
+ if (err < 0)
+ dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
+
+ return panel_simple_remove(&dsi->dev);
+}
+
+static void panel_simple_dsi_shutdown(struct mipi_dsi_device *dsi)
+{
+ panel_simple_shutdown(&dsi->dev);
+}
+
+static struct mipi_dsi_driver panel_simple_dsi_driver = {
+ .driver = {
+ .name = "panel-simple-dt",
+ .of_match_table = dsi_of_match,
+ },
+ .probe = panel_simple_dsi_probe,
+ .remove = panel_simple_dsi_remove,
+ .shutdown = panel_simple_dsi_shutdown,
+};
+
+static int __init panel_simple_init(void)
+{
+ int err;
+
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
+ err = mipi_dsi_driver_register(&panel_simple_dsi_driver);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+module_init(panel_simple_init);
+
+static void __exit panel_simple_exit(void)
+{
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_unregister(&panel_simple_dsi_driver);
+}
+module_exit(panel_simple_exit);
+
+MODULE_AUTHOR("Christoph Muellner <christoph.muellner@theobroma-systems.com>");
+MODULE_DESCRIPTION("DRM Driver for Simple Panels with DTS configuration");
+MODULE_LICENSE("GPL and additional rights");