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 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel