[PATCH RFC 8/8] drm/sprd: add Unisoc's drm generic mipi panel driver

From: Kevin Tang
Date: Tue Dec 10 2019 - 03:37:25 EST


From: Kevin Tang <kevin.tang@xxxxxxxxxx>

This is a generic mipi dsi panel driver, All the parameters related
to lcd panel, we are placed in the DTS to configure,
for exampleïlcd display timing, dpi parameter and more.

Cc: Orson Zhai <orsonzhai@xxxxxxxxx>
Cc: Baolin Wang <baolin.wang@xxxxxxxxxx>
Cc: Chunyan Zhang <zhang.lyra@xxxxxxxxx>
Signed-off-by: Kevin Tang <kevin.tang@xxxxxxxxxx>
---
drivers/gpu/drm/sprd/Makefile | 3 +-
drivers/gpu/drm/sprd/sprd_panel.c | 778 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/sprd/sprd_panel.h | 114 ++++++
3 files changed, 894 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/sprd/sprd_panel.c
create mode 100644 drivers/gpu/drm/sprd/sprd_panel.h

diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
index 78d3ddb..30a581c 100644
--- a/drivers/gpu/drm/sprd/Makefile
+++ b/drivers/gpu/drm/sprd/Makefile
@@ -8,7 +8,8 @@ obj-y := sprd_drm.o \
sprd_gem.o \
sprd_dpu.o \
sprd_dsi.o \
- sprd_dphy.o
+ sprd_dphy.o \
+ sprd_panel.o

obj-y += disp_lib.o
obj-y += dpu/
diff --git a/drivers/gpu/drm/sprd/sprd_panel.c b/drivers/gpu/drm/sprd/sprd_panel.c
new file mode 100644
index 0000000..4a70a20
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_panel.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <linux/backlight.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+#include <video/mipi_display.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "sprd_dpu.h"
+#include "sprd_panel.h"
+#include "dsi/sprd_dsi_api.h"
+
+#define SPRD_MIPI_DSI_FMT_DSC 0xff
+static DEFINE_MUTEX(panel_lock);
+
+static const char *lcd_name;
+
+static inline struct sprd_panel *to_sprd_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct sprd_panel, base);
+}
+
+static int sprd_panel_send_cmds(struct mipi_dsi_device *dsi,
+ const void *data, int size)
+{
+ struct sprd_panel *panel;
+ const struct dsi_cmd_desc *cmds = data;
+ u16 len;
+
+ if (cmds == NULL || dsi == NULL)
+ return -EINVAL;
+
+ panel = mipi_dsi_get_drvdata(dsi);
+
+ while (size > 0) {
+ len = (cmds->wc_h << 8) | cmds->wc_l;
+
+ if (panel->info.use_dcs)
+ mipi_dsi_dcs_write_buffer(dsi, cmds->payload, len);
+ else
+ mipi_dsi_generic_write(dsi, cmds->payload, len);
+
+ if (cmds->wait)
+ msleep(cmds->wait);
+ cmds = (const struct dsi_cmd_desc *)(cmds->payload + len);
+ size -= (len + 4);
+ }
+
+ return 0;
+}
+
+static int sprd_panel_unprepare(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+ struct gpio_timing *timing;
+ int items, i;
+
+ DRM_INFO("%s()\n", __func__);
+
+ if (panel->info.avee_gpio) {
+ gpiod_direction_output(panel->info.avee_gpio, 0);
+ mdelay(5);
+ }
+
+ if (panel->info.avdd_gpio) {
+ gpiod_direction_output(panel->info.avdd_gpio, 0);
+ mdelay(5);
+ }
+
+ if (panel->info.reset_gpio) {
+ items = panel->info.rst_off_seq.items;
+ timing = panel->info.rst_off_seq.timing;
+ for (i = 0; i < items; i++) {
+ gpiod_direction_output(panel->info.reset_gpio,
+ timing[i].level);
+ mdelay(timing[i].delay);
+ }
+ }
+
+ regulator_disable(panel->supply);
+
+ return 0;
+}
+
+static int sprd_panel_prepare(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+ struct gpio_timing *timing;
+ int items, i, ret;
+
+ DRM_INFO("%s()\n", __func__);
+
+ ret = regulator_enable(panel->supply);
+ if (ret < 0) {
+ DRM_ERROR("enable lcd regulator failed\n");
+ return ret;
+ }
+
+ if (panel->info.avdd_gpio) {
+ gpiod_direction_output(panel->info.avdd_gpio, 1);
+ mdelay(5);
+ }
+
+ if (panel->info.avee_gpio) {
+ gpiod_direction_output(panel->info.avee_gpio, 1);
+ mdelay(5);
+ }
+
+ if (panel->info.reset_gpio) {
+ items = panel->info.rst_on_seq.items;
+ timing = panel->info.rst_on_seq.timing;
+ for (i = 0; i < items; i++) {
+ gpiod_direction_output(panel->info.reset_gpio,
+ timing[i].level);
+ mdelay(timing[i].delay);
+ }
+ }
+
+ return 0;
+}
+
+static int sprd_panel_disable(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+
+ DRM_INFO("%s()\n", __func__);
+
+ mutex_lock(&panel_lock);
+ /*
+ * FIXME:
+ * The cancel work should be executed before DPU stop,
+ * otherwise the esd check will be failed if the DPU
+ * stopped in video mode and the DSI has not change to
+ * CMD mode yet. Since there is no VBLANK timing for
+ * LP cmd transmission.
+ */
+ if (panel->esd_work_pending) {
+ cancel_delayed_work_sync(&panel->esd_work);
+ panel->esd_work_pending = false;
+ }
+
+ if (panel->backlight) {
+ panel->backlight->props.power = FB_BLANK_POWERDOWN;
+ panel->backlight->props.state |= BL_CORE_FBBLANK;
+ backlight_update_status(panel->backlight);
+ }
+
+ sprd_panel_send_cmds(panel->slave,
+ panel->info.cmds[CMD_CODE_SLEEP_IN],
+ panel->info.cmds_len[CMD_CODE_SLEEP_IN]);
+
+ panel->is_enabled = false;
+ mutex_unlock(&panel_lock);
+
+ return 0;
+}
+
+static int sprd_panel_enable(struct drm_panel *p)
+{
+ struct sprd_panel *panel = to_sprd_panel(p);
+
+ DRM_INFO("%s()\n", __func__);
+
+ mutex_lock(&panel_lock);
+ sprd_panel_send_cmds(panel->slave,
+ panel->info.cmds[CMD_CODE_INIT],
+ panel->info.cmds_len[CMD_CODE_INIT]);
+
+ if (panel->backlight) {
+ panel->backlight->props.power = FB_BLANK_UNBLANK;
+ panel->backlight->props.state &= ~BL_CORE_FBBLANK;
+ backlight_update_status(panel->backlight);
+ }
+
+ if (panel->info.esd_check_en) {
+ schedule_delayed_work(&panel->esd_work,
+ msecs_to_jiffies(1000));
+ panel->esd_work_pending = true;
+ }
+
+ panel->is_enabled = true;
+ mutex_unlock(&panel_lock);
+
+ return 0;
+}
+
+static int sprd_panel_get_modes(struct drm_panel *p)
+{
+ struct drm_display_mode *mode;
+ struct sprd_panel *panel = to_sprd_panel(p);
+ struct device_node *np = panel->slave->dev.of_node;
+ u32 surface_width = 0, surface_height = 0;
+ int i, mode_count = 0;
+
+ DRM_INFO("%s()\n", __func__);
+ mode = drm_mode_duplicate(p->drm, &panel->info.mode);
+ if (!mode) {
+ DRM_ERROR("failed to alloc mode %s\n", panel->info.mode.name);
+ return 0;
+ }
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(p->connector, mode);
+ mode_count++;
+
+ for (i = 1; i < panel->info.num_buildin_modes; i++) {
+ mode = drm_mode_duplicate(p->drm,
+ &(panel->info.buildin_modes[i]));
+ if (!mode) {
+ DRM_ERROR("failed to alloc mode %s\n",
+ panel->info.buildin_modes[i].name);
+ return 0;
+ }
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_DEFAULT;
+ drm_mode_probed_add(p->connector, mode);
+ mode_count++;
+ }
+
+ of_property_read_u32(np, "sprd,surface-width", &surface_width);
+ of_property_read_u32(np, "sprd,surface-height", &surface_height);
+ if (surface_width && surface_height) {
+ struct videomode vm = {};
+
+ vm.hactive = surface_width;
+ vm.vactive = surface_height;
+ vm.pixelclock = surface_width * surface_height * 60;
+
+ mode = drm_mode_create(p->drm);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_BUILTIN |
+ DRM_MODE_TYPE_CRTC_C;
+ mode->vrefresh = 60;
+ drm_display_mode_from_videomode(&vm, mode);
+ drm_mode_probed_add(p->connector, mode);
+ mode_count++;
+ }
+
+ p->connector->display_info.width_mm = panel->info.mode.width_mm;
+ p->connector->display_info.height_mm = panel->info.mode.height_mm;
+
+ return mode_count;
+}
+
+static const struct drm_panel_funcs sprd_panel_funcs = {
+ .get_modes = sprd_panel_get_modes,
+ .enable = sprd_panel_enable,
+ .disable = sprd_panel_disable,
+ .prepare = sprd_panel_prepare,
+ .unprepare = sprd_panel_unprepare,
+};
+
+static int sprd_panel_esd_check(struct sprd_panel *panel)
+{
+ struct panel_info *info = &panel->info;
+ u8 read_val = 0;
+
+ /* FIXME: we should enable HS cmd tx here */
+ mipi_dsi_set_maximum_return_packet_size(panel->slave, 1);
+ mipi_dsi_dcs_read(panel->slave, info->esd_check_reg,
+ &read_val, 1);
+
+ /*
+ * TODO:
+ * Should we support multi-registers check in the future?
+ */
+ if (read_val != info->esd_check_val) {
+ DRM_ERROR("esd check failed, read value = 0x%02x\n",
+ read_val);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sprd_panel_te_check(struct sprd_panel *panel)
+{
+ static int te_wq_inited;
+ struct sprd_dpu *dpu;
+ int ret;
+ bool irq_occur;
+
+ if (!panel->base.connector ||
+ !panel->base.connector->encoder ||
+ !panel->base.connector->encoder->crtc) {
+ return 0;
+ }
+
+ dpu = container_of(panel->base.connector->encoder->crtc,
+ struct sprd_dpu, crtc);
+
+ if (!te_wq_inited) {
+ init_waitqueue_head(&dpu->ctx.te_wq);
+ te_wq_inited = 1;
+ dpu->ctx.evt_te = false;
+ DRM_INFO("%s init te waitqueue\n", __func__);
+ }
+
+ /* DPU TE irq maybe enabled in kernel */
+ if (!dpu->ctx.is_inited)
+ return 0;
+
+ dpu->ctx.te_check_en = true;
+
+ /* wait for TE interrupt */
+ ret = wait_event_interruptible_timeout(dpu->ctx.te_wq,
+ dpu->ctx.evt_te, msecs_to_jiffies(500));
+ if (!ret) {
+ /* double check TE interrupt through dpu_int_raw register */
+ if (dpu->core && dpu->core->check_raw_int) {
+ irq_occur = dpu->core->check_raw_int(&dpu->ctx,
+ DISPC_INT_TE_MASK);
+ if (!irq_occur) {
+ DRM_ERROR("TE esd timeout.\n");
+ ret = -ETIMEDOUT;
+ } else
+ DRM_WARN("TE occur, but isr schedule delay\n");
+ } else {
+ DRM_ERROR("TE esd timeout.\n");
+ ret = -ETIMEDOUT;
+ }
+ }
+
+ dpu->ctx.te_check_en = false;
+ dpu->ctx.evt_te = false;
+
+ return ret < 0 ? ret : 0;
+}
+
+static void sprd_panel_esd_work_func(struct work_struct *work)
+{
+ struct sprd_panel *panel = container_of(work, struct sprd_panel,
+ esd_work.work);
+ struct panel_info *info = &panel->info;
+ int ret;
+
+ if (info->esd_check_mode == ESD_MODE_REG_CHECK)
+ ret = sprd_panel_esd_check(panel);
+ else if (info->esd_check_mode == ESD_MODE_TE_CHECK)
+ ret = sprd_panel_te_check(panel);
+ else {
+ DRM_ERROR("unknown esd check mode:%d\n", info->esd_check_mode);
+ return;
+ }
+
+ if (ret && panel->base.connector && panel->base.connector->encoder) {
+ const struct drm_encoder_helper_funcs *funcs;
+ struct drm_encoder *encoder;
+
+ encoder = panel->base.connector->encoder;
+ funcs = encoder->helper_private;
+ panel->esd_work_pending = false;
+
+ if (encoder->crtc && encoder->crtc->state &&
+ !encoder->crtc->state->active) {
+ DRM_INFO("skip esd recovery during panel suspend\n");
+ return;
+ }
+
+ DRM_INFO("====== esd recovery start ========\n");
+ funcs->disable(encoder);
+
+ if (!encoder->crtc->state->active) {
+ DRM_INFO("skip esd recovery if panel suspend\n");
+ return;
+ }
+ funcs->enable(encoder);
+ DRM_INFO("======= esd recovery end =========\n");
+ } else
+ schedule_delayed_work(&panel->esd_work,
+ msecs_to_jiffies(info->esd_check_period));
+}
+
+static int sprd_panel_gpio_request(struct device *dev,
+ struct sprd_panel *panel)
+{
+ panel->info.avdd_gpio = devm_gpiod_get_optional(dev,
+ "avdd", GPIOD_ASIS);
+ if (IS_ERR_OR_NULL(panel->info.avdd_gpio))
+ DRM_WARN("can't get panel avdd gpio: %ld\n",
+ PTR_ERR(panel->info.avdd_gpio));
+
+ panel->info.avee_gpio = devm_gpiod_get_optional(dev,
+ "avee", GPIOD_ASIS);
+ if (IS_ERR_OR_NULL(panel->info.avee_gpio))
+ DRM_WARN("can't get panel avee gpio: %ld\n",
+ PTR_ERR(panel->info.avee_gpio));
+
+ panel->info.reset_gpio = devm_gpiod_get_optional(dev,
+ "reset", GPIOD_ASIS);
+ if (IS_ERR_OR_NULL(panel->info.reset_gpio))
+ DRM_WARN("can't get panel reset gpio: %ld\n",
+ PTR_ERR(panel->info.reset_gpio));
+
+ return 0;
+}
+
+static int of_parse_reset_seq(struct device_node *np,
+ struct panel_info *info)
+{
+ struct property *prop;
+ int bytes, rc;
+ u32 *p;
+
+ prop = of_find_property(np, "sprd,reset-on-sequence", &bytes);
+ if (!prop) {
+ DRM_ERROR("sprd,reset-on-sequence property not found\n");
+ return -EINVAL;
+ }
+
+ p = kzalloc(bytes, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ rc = of_property_read_u32_array(np, "sprd,reset-on-sequence",
+ p, bytes / 4);
+ if (rc) {
+ DRM_ERROR("parse sprd,reset-on-sequence failed\n");
+ kfree(p);
+ return rc;
+ }
+
+ info->rst_on_seq.items = bytes / 8;
+ info->rst_on_seq.timing = (struct gpio_timing *)p;
+
+ prop = of_find_property(np, "sprd,reset-off-sequence", &bytes);
+ if (!prop) {
+ DRM_ERROR("sprd,reset-off-sequence property not found\n");
+ return -EINVAL;
+ }
+
+ p = kzalloc(bytes, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ rc = of_property_read_u32_array(np, "sprd,reset-off-sequence",
+ p, bytes / 4);
+ if (rc) {
+ DRM_ERROR("parse sprd,reset-off-sequence failed\n");
+ kfree(p);
+ return rc;
+ }
+
+ info->rst_off_seq.items = bytes / 8;
+ info->rst_off_seq.timing = (struct gpio_timing *)p;
+
+ return 0;
+}
+
+static int of_parse_buildin_modes(struct panel_info *info,
+ struct device_node *lcd_node)
+{
+ int i, rc, num_timings;
+ struct device_node *timings_np;
+
+
+ timings_np = of_get_child_by_name(lcd_node, "display-timings");
+ if (!timings_np) {
+ DRM_ERROR("%s: can not find display-timings node\n",
+ lcd_node->name);
+ return -ENODEV;
+ }
+
+ num_timings = of_get_child_count(timings_np);
+ if (num_timings == 0) {
+ /* should never happen, as entry was already found above */
+ DRM_ERROR("%s: no timings specified\n", lcd_node->name);
+ goto done;
+ }
+
+ info->buildin_modes = kzalloc(sizeof(struct drm_display_mode) *
+ num_timings, GFP_KERNEL);
+
+ for (i = 0; i < num_timings; i++) {
+ rc = of_get_drm_display_mode(lcd_node,
+ &info->buildin_modes[i], NULL, i);
+ if (rc) {
+ DRM_ERROR("get display timing failed\n");
+ goto entryfail;
+ }
+
+ info->buildin_modes[i].width_mm = info->mode.width_mm;
+ info->buildin_modes[i].height_mm = info->mode.height_mm;
+ info->buildin_modes[i].vrefresh = info->mode.vrefresh;
+ }
+ info->num_buildin_modes = num_timings;
+ DRM_INFO("info->num_buildin_modes = %d\n", num_timings);
+ goto done;
+
+entryfail:
+ kfree(info->buildin_modes);
+done:
+ of_node_put(timings_np);
+
+ return 0;
+}
+
+static int sprd_panel_parse_dt(struct device_node *np, struct sprd_panel *panel)
+{
+ u32 val;
+ struct device_node *lcd_node;
+ struct panel_info *info = &panel->info;
+ int bytes, rc;
+ const void *p;
+ const char *str;
+ char lcd_path[60];
+
+ sprintf(lcd_path, "/lcds/%s", lcd_name);
+ lcd_node = of_find_node_by_path(lcd_path);
+ if (!lcd_node) {
+ DRM_ERROR("%pOF: could not find %s node\n", np, lcd_name);
+ return -ENODEV;
+ }
+ info->of_node = lcd_node;
+
+ rc = of_property_read_u32(lcd_node, "sprd,dsi-work-mode", &val);
+ if (!rc) {
+ if (val == SPRD_DSI_MODE_CMD)
+ info->mode_flags = 0;
+ else if (val == SPRD_DSI_MODE_VIDEO_BURST)
+ info->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST;
+ else if (val == SPRD_DSI_MODE_VIDEO_SYNC_PULSE)
+ info->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ else if (val == SPRD_DSI_MODE_VIDEO_SYNC_EVENT)
+ info->mode_flags = MIPI_DSI_MODE_VIDEO;
+ } else {
+ DRM_ERROR("dsi work mode is not found! use video mode\n");
+ info->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_BURST;
+ }
+
+ if (of_property_read_bool(lcd_node, "sprd,dsi-non-continuous-clock"))
+ info->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
+
+ rc = of_property_read_u32(lcd_node, "sprd,dsi-lane-number", &val);
+ if (!rc)
+ info->lanes = val;
+ else
+ info->lanes = 4;
+
+ rc = of_property_read_string(lcd_node, "sprd,dsi-color-format", &str);
+ if (rc)
+ info->format = MIPI_DSI_FMT_RGB888;
+ else if (!strcmp(str, "rgb888"))
+ info->format = MIPI_DSI_FMT_RGB888;
+ else if (!strcmp(str, "rgb666"))
+ info->format = MIPI_DSI_FMT_RGB666;
+ else if (!strcmp(str, "rgb666_packed"))
+ info->format = MIPI_DSI_FMT_RGB666_PACKED;
+ else if (!strcmp(str, "rgb565"))
+ info->format = MIPI_DSI_FMT_RGB565;
+ else if (!strcmp(str, "dsc"))
+ info->format = SPRD_MIPI_DSI_FMT_DSC;
+ else
+ DRM_ERROR("dsi-color-format (%s) is not supported\n", str);
+
+ rc = of_property_read_u32(lcd_node, "width-mm", &val);
+ if (!rc)
+ info->mode.width_mm = val;
+ else
+ info->mode.width_mm = 68;
+
+ rc = of_property_read_u32(lcd_node, "height-mm", &val);
+ if (!rc)
+ info->mode.height_mm = val;
+ else
+ info->mode.height_mm = 121;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-enable", &val);
+ if (!rc)
+ info->esd_check_en = val;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-mode", &val);
+ if (!rc)
+ info->esd_check_mode = val;
+ else
+ info->esd_check_mode = 1;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-period", &val);
+ if (!rc)
+ info->esd_check_period = val;
+ else
+ info->esd_check_period = 1000;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-register", &val);
+ if (!rc)
+ info->esd_check_reg = val;
+ else
+ info->esd_check_reg = 0x0A;
+
+ rc = of_property_read_u32(lcd_node, "sprd,esd-check-value", &val);
+ if (!rc)
+ info->esd_check_val = val;
+ else
+ info->esd_check_val = 0x9C;
+
+ if (of_property_read_bool(lcd_node, "sprd,use-dcs-write"))
+ info->use_dcs = true;
+ else
+ info->use_dcs = false;
+
+ rc = of_parse_reset_seq(lcd_node, info);
+ if (rc)
+ DRM_ERROR("parse lcd reset sequence failed\n");
+
+ p = of_get_property(lcd_node, "sprd,initial-command", &bytes);
+ if (p) {
+ info->cmds[CMD_CODE_INIT] = p;
+ info->cmds_len[CMD_CODE_INIT] = bytes;
+ } else
+ DRM_ERROR("can't find sprd,initial-command property\n");
+
+ p = of_get_property(lcd_node, "sprd,sleep-in-command", &bytes);
+ if (p) {
+ info->cmds[CMD_CODE_SLEEP_IN] = p;
+ info->cmds_len[CMD_CODE_SLEEP_IN] = bytes;
+ } else
+ DRM_ERROR("can't find sprd,sleep-in-command property\n");
+
+ p = of_get_property(lcd_node, "sprd,sleep-out-command", &bytes);
+ if (p) {
+ info->cmds[CMD_CODE_SLEEP_OUT] = p;
+ info->cmds_len[CMD_CODE_SLEEP_OUT] = bytes;
+ } else
+ DRM_ERROR("can't find sprd,sleep-out-command property\n");
+
+ rc = of_get_drm_display_mode(lcd_node, &info->mode, 0,
+ OF_USE_NATIVE_MODE);
+ if (rc) {
+ DRM_ERROR("get display timing failed\n");
+ return rc;
+ }
+
+ info->mode.vrefresh = drm_mode_vrefresh(&info->mode);
+ of_parse_buildin_modes(info, lcd_node);
+
+ return 0;
+}
+
+static int sprd_panel_probe(struct mipi_dsi_device *slave)
+{
+ int ret;
+ struct sprd_panel *panel;
+ struct device_node *bl_node;
+
+ panel = devm_kzalloc(&slave->dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ bl_node = of_parse_phandle(slave->dev.of_node,
+ "sprd,backlight", 0);
+ if (bl_node) {
+ panel->backlight = of_find_backlight_by_node(bl_node);
+ of_node_put(bl_node);
+
+ if (panel->backlight) {
+ panel->backlight->props.state &= ~BL_CORE_FBBLANK;
+ panel->backlight->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(panel->backlight);
+ } else {
+ DRM_WARN("backlight is not ready, panel probe deferred\n");
+ return -EPROBE_DEFER;
+ }
+ } else
+ DRM_WARN("backlight node not found\n");
+
+ panel->supply = devm_regulator_get(&slave->dev, "power");
+ if (IS_ERR(panel->supply)) {
+ if (PTR_ERR(panel->supply) == -EPROBE_DEFER)
+ DRM_ERROR("regulator driver not initialized, probe deffer\n");
+ else
+ DRM_ERROR("can't get regulator: %ld\n", PTR_ERR(panel->supply));
+
+ return PTR_ERR(panel->supply);
+ }
+
+ INIT_DELAYED_WORK(&panel->esd_work, sprd_panel_esd_work_func);
+
+ ret = sprd_panel_parse_dt(slave->dev.of_node, panel);
+ if (ret) {
+ DRM_ERROR("parse panel info failed\n");
+ return ret;
+ }
+
+ ret = sprd_panel_gpio_request(&slave->dev, panel);
+ if (ret) {
+ DRM_WARN("gpio is not ready, panel probe deferred\n");
+ return -EPROBE_DEFER;
+ }
+
+ drm_panel_init(&panel->base, &panel->dev,
+ &sprd_panel_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_add(&panel->base);
+ if (ret) {
+ DRM_ERROR("drm_panel_add() failed\n");
+ return ret;
+ }
+
+ slave->lanes = panel->info.lanes;
+ slave->format = panel->info.format;
+ slave->mode_flags = panel->info.mode_flags;
+
+ ret = mipi_dsi_attach(slave);
+ if (ret) {
+ DRM_ERROR("failed to attach dsi panel to host\n");
+ drm_panel_remove(&panel->base);
+ return ret;
+ }
+ panel->slave = slave;
+
+ mipi_dsi_set_drvdata(slave, panel);
+
+ /*
+ * FIXME:
+ * The esd check work should not be scheduled in probe
+ * function. It should be scheduled in the enable()
+ * callback function. But the dsi encoder will not call
+ * drm_panel_enable() the first time in encoder_enable().
+ */
+ if (panel->info.esd_check_en) {
+ schedule_delayed_work(&panel->esd_work,
+ msecs_to_jiffies(2000));
+ panel->esd_work_pending = true;
+ }
+
+ panel->is_enabled = true;
+
+ DRM_INFO("panel driver probe success\n");
+
+ return 0;
+}
+
+static int sprd_panel_remove(struct mipi_dsi_device *slave)
+{
+ struct sprd_panel *panel = mipi_dsi_get_drvdata(slave);
+ int ret;
+
+ DRM_INFO("%s()\n", __func__);
+
+ sprd_panel_disable(&panel->base);
+ sprd_panel_unprepare(&panel->base);
+
+ ret = mipi_dsi_detach(slave);
+ if (ret < 0)
+ DRM_ERROR("failed to detach from DSI host: %d\n", ret);
+
+ drm_panel_detach(&panel->base);
+ drm_panel_remove(&panel->base);
+
+ return 0;
+}
+
+static const struct of_device_id panel_of_match[] = {
+ { .compatible = "sprd,generic-mipi-panel", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+static struct mipi_dsi_driver sprd_panel_driver = {
+ .driver = {
+ .name = "sprd-mipi-panel-drv",
+ .of_match_table = panel_of_match,
+ },
+ .probe = sprd_panel_probe,
+ .remove = sprd_panel_remove,
+};
+module_mipi_dsi_driver(sprd_panel_driver);
+
+MODULE_AUTHOR("Leon He <leon.he@xxxxxxxxxx>");
+MODULE_AUTHOR("Kevin Tang <kevin.tang@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Unisoc MIPI DSI Panel Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_panel.h b/drivers/gpu/drm/sprd/sprd_panel.h
new file mode 100644
index 0000000..216cd4b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_panel.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Unisoc Inc.
+ */
+
+#ifndef _SPRD_PANEL_H_
+#define _SPRD_PANEL_H_
+
+#include <linux/backlight.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+
+enum {
+ CMD_CODE_INIT = 0,
+ CMD_CODE_SLEEP_IN,
+ CMD_CODE_SLEEP_OUT,
+ CMD_OLED_BRIGHTNESS,
+ CMD_OLED_REG_LOCK,
+ CMD_OLED_REG_UNLOCK,
+ CMD_CODE_RESERVED0,
+ CMD_CODE_RESERVED1,
+ CMD_CODE_RESERVED2,
+ CMD_CODE_RESERVED3,
+ CMD_CODE_RESERVED4,
+ CMD_CODE_RESERVED5,
+ CMD_CODE_MAX,
+};
+
+enum {
+ SPRD_DSI_MODE_CMD = 0,
+ SPRD_DSI_MODE_VIDEO_BURST,
+ SPRD_DSI_MODE_VIDEO_SYNC_PULSE,
+ SPRD_DSI_MODE_VIDEO_SYNC_EVENT,
+};
+
+enum {
+ ESD_MODE_REG_CHECK,
+ ESD_MODE_TE_CHECK,
+};
+
+struct dsi_cmd_desc {
+ u8 data_type;
+ u8 wait;
+ u8 wc_h;
+ u8 wc_l;
+ u8 payload[];
+};
+
+struct gpio_timing {
+ u32 level;
+ u32 delay;
+};
+
+struct reset_sequence {
+ u32 items;
+ struct gpio_timing *timing;
+};
+
+struct panel_info {
+ /* common parameters */
+ struct device_node *of_node;
+ struct drm_display_mode mode;
+ struct drm_display_mode *buildin_modes;
+ int num_buildin_modes;
+ struct gpio_desc *avdd_gpio;
+ struct gpio_desc *avee_gpio;
+ struct gpio_desc *reset_gpio;
+ struct reset_sequence rst_on_seq;
+ struct reset_sequence rst_off_seq;
+ const void *cmds[CMD_CODE_MAX];
+ int cmds_len[CMD_CODE_MAX];
+
+ /* esd check parameters*/
+ bool esd_check_en;
+ u8 esd_check_mode;
+ u16 esd_check_period;
+ u32 esd_check_reg;
+ u32 esd_check_val;
+
+ /* MIPI DSI specific parameters */
+ u32 format;
+ u32 lanes;
+ u32 mode_flags;
+ bool use_dcs;
+};
+
+struct sprd_panel {
+ struct device dev;
+ struct drm_panel base;
+ struct mipi_dsi_device *slave;
+ struct panel_info info;
+ struct backlight_device *backlight;
+ struct regulator *supply;
+ struct delayed_work esd_work;
+ bool esd_work_pending;
+ bool is_enabled;
+};
+
+struct sprd_oled {
+ struct backlight_device *bdev;
+ struct sprd_panel *panel;
+ struct dsi_cmd_desc *cmds[255];
+ int cmd_len;
+ int cmds_total;
+ int max_level;
+};
+
+#endif
--
2.7.4