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

Kevin Tang kevin3.tang at gmail.com
Tue Dec 10 08:36:35 UTC 2019


From: Kevin Tang <kevin.tang at unisoc.com>

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 at gmail.com>
Cc: Baolin Wang <baolin.wang at linaro.org>
Cc: Chunyan Zhang <zhang.lyra at gmail.com>
Signed-off-by: Kevin Tang <kevin.tang at unisoc.com>
---
 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 at unisoc.com>");
+MODULE_AUTHOR("Kevin Tang <kevin.tang at unisoc.com>");
+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



More information about the dri-devel mailing list