From: Kevin Tang <kevin3.tang@gmail.com>
To: airlied@linux.ie, daniel@ffwll.ch, kevin3.tang@gmail.com
Cc: orsonzhai@gmail.com, dri-devel@lists.freedesktop.org,
linux-kernel@vger.kernel.org, zhang.lyra@gmail.com,
baolin.wang@linaro.org
Subject: [PATCH RFC 8/8] drm/sprd: add Unisoc's drm generic mipi panel driver
Date: Tue, 10 Dec 2019 16:36:35 +0800 [thread overview]
Message-ID: <1575966995-13757-9-git-send-email-kevin3.tang@gmail.com> (raw)
In-Reply-To: <1575966995-13757-1-git-send-email-kevin3.tang@gmail.com>
From: Kevin Tang <kevin.tang@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@gmail.com>
Cc: Baolin Wang <baolin.wang@linaro.org>
Cc: Chunyan Zhang <zhang.lyra@gmail.com>
Signed-off-by: Kevin Tang <kevin.tang@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@unisoc.com>");
+MODULE_AUTHOR("Kevin Tang <kevin.tang@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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel
WARNING: multiple messages have this Message-ID (diff)
From: Kevin Tang <kevin3.tang@gmail.com>
To: airlied@linux.ie, daniel@ffwll.ch, kevin3.tang@gmail.com
Cc: orsonzhai@gmail.com, baolin.wang@linaro.org,
zhang.lyra@gmail.com, linux-kernel@vger.kernel.org,
dri-devel@lists.freedesktop.org
Subject: [PATCH RFC 8/8] drm/sprd: add Unisoc's drm generic mipi panel driver
Date: Tue, 10 Dec 2019 16:36:35 +0800 [thread overview]
Message-ID: <1575966995-13757-9-git-send-email-kevin3.tang@gmail.com> (raw)
In-Reply-To: <1575966995-13757-1-git-send-email-kevin3.tang@gmail.com>
From: Kevin Tang <kevin.tang@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@gmail.com>
Cc: Baolin Wang <baolin.wang@linaro.org>
Cc: Chunyan Zhang <zhang.lyra@gmail.com>
Signed-off-by: Kevin Tang <kevin.tang@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@unisoc.com>");
+MODULE_AUTHOR("Kevin Tang <kevin.tang@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
next prev parent reply other threads:[~2019-12-10 8:41 UTC|newest]
Thread overview: 52+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-12-10 8:36 [PATCH RFC 0/8] Add Unisoc's drm kms module Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-10 8:36 ` [PATCH RFC 1/8] dt-bindings: display: add Unisoc's drm master bindings Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-13 9:42 ` Maxime Ripard
2019-12-13 9:42 ` Maxime Ripard
2019-12-10 8:36 ` [PATCH RFC 2/8] drm/sprd: add Unisoc's drm kms master Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-10 10:32 ` Thomas Zimmermann
2019-12-10 10:32 ` Thomas Zimmermann
2019-12-10 12:38 ` tang pengchuan
2019-12-10 12:47 ` Thomas Zimmermann
2019-12-10 12:47 ` Thomas Zimmermann
2019-12-10 13:42 ` tang pengchuan
2019-12-11 3:00 ` tang pengchuan
2019-12-10 10:41 ` Daniel Vetter
2019-12-10 10:41 ` Daniel Vetter
2019-12-10 11:40 ` tang pengchuan
2019-12-10 16:06 ` Emil Velikov
2019-12-10 16:06 ` Emil Velikov
2019-12-10 22:01 ` Daniel Vetter
2019-12-10 22:01 ` Daniel Vetter
2019-12-10 8:36 ` [PATCH RFC 3/8] dt-bindings: display: add Unisoc's dpu bindings Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-13 9:44 ` Maxime Ripard
2019-12-13 9:44 ` Maxime Ripard
2019-12-10 8:36 ` [PATCH RFC 4/8] drm/sprd: add Unisoc's drm display controller driver Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-10 10:44 ` Thomas Zimmermann
2019-12-10 10:44 ` Thomas Zimmermann
2019-12-10 11:29 ` tang pengchuan
2019-12-10 11:32 ` tang pengchuan
2019-12-10 17:13 ` Emil Velikov
2019-12-10 17:13 ` Emil Velikov
2019-12-11 1:18 ` tang pengchuan
2019-12-11 11:46 ` Emil Velikov
2019-12-11 11:46 ` Emil Velikov
2019-12-10 8:36 ` [PATCH RFC 5/8] dt-bindings: display: add Unisoc's mipi dsi&dphy bindings Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-13 9:46 ` Maxime Ripard
2019-12-13 9:46 ` Maxime Ripard
2019-12-10 8:36 ` [PATCH RFC 6/8] drm/sprd: add Unisoc's drm mipi dsi&dphy driver Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-10 8:36 ` [PATCH RFC 7/8] dt-bindings: display: add Unisoc's generic mipi panel bindings Kevin Tang
2019-12-10 8:36 ` Kevin Tang
2019-12-13 9:48 ` Maxime Ripard
2019-12-13 9:48 ` Maxime Ripard
2019-12-10 8:36 ` Kevin Tang [this message]
2019-12-10 8:36 ` [PATCH RFC 8/8] drm/sprd: add Unisoc's drm generic mipi panel driver Kevin Tang
2019-12-10 18:54 ` [PATCH RFC 0/8] Add Unisoc's drm kms module Sam Ravnborg
2019-12-10 18:54 ` Sam Ravnborg
2019-12-12 14:29 ` tang pengchuan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1575966995-13757-9-git-send-email-kevin3.tang@gmail.com \
--to=kevin3.tang@gmail.com \
--cc=airlied@linux.ie \
--cc=baolin.wang@linaro.org \
--cc=daniel@ffwll.ch \
--cc=dri-devel@lists.freedesktop.org \
--cc=linux-kernel@vger.kernel.org \
--cc=orsonzhai@gmail.com \
--cc=zhang.lyra@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.