diff options
Diffstat (limited to 'drivers/gpu/drm/panel')
-rw-r--r-- | drivers/gpu/drm/panel/Kconfig | 20 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-dsi-cm.c | 665 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-khadas-ts050.c | 870 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c | 39 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-samsung-s6e63m0.c | 42 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-simple.c | 221 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/panel-sitronix-st7703.c | 24 |
8 files changed, 1808 insertions, 75 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index b4e021ea30f9..4894913936e9 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -57,6 +57,15 @@ config DRM_PANEL_BOE_TV101WUM_NL6 Say Y here if you want to support for BOE TV101WUM and AUO KD101N80 45NA WUXGA PANEL DSI Video Mode panel +config DRM_PANEL_DSI_CM + tristate "Generic DSI command mode panels" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + DRM panel driver for DSI command mode panels with support for + embedded and external backlights. + config DRM_PANEL_LVDS tristate "Generic LVDS panel driver" depends on OF @@ -145,6 +154,17 @@ config DRM_PANEL_JDI_LT070ME05000 The panel has a 1200(RGB)×1920 (WUXGA) resolution and uses 24 bit per pixel. +config DRM_PANEL_KHADAS_TS050 + tristate "Khadas TS050 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Khadas TS050 TFT-LCD + panel module. The panel has a 1080x1920 resolution and uses + 24 bit RGB per pixel. It provides a MIPI DSI interface to + the host, a built-in LED backlight and touch controller. + config DRM_PANEL_KINGDISPLAY_KD097D04 tristate "Kingdisplay kd097d04 panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index ebbf488c7eac..cae4d976c069 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o obj-$(CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596) += panel-asus-z00t-tm5p5-n35596.o obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o +obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o @@ -13,6 +14,7 @@ obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o +obj-$(CONFIG_DRM_PANEL_KHADAS_TS050) += panel-khadas-ts050.o obj-$(CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04) += panel-kingdisplay-kd097d04.o obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o diff --git a/drivers/gpu/drm/panel/panel-dsi-cm.c b/drivers/gpu/drm/panel/panel-dsi-cm.c new file mode 100644 index 000000000000..af381d756ac1 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-dsi-cm.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic DSI Command Mode panel driver + * + * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_connector.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +#define DCS_GET_ID1 0xda +#define DCS_GET_ID2 0xdb +#define DCS_GET_ID3 0xdc + +#define DCS_REGULATOR_SUPPLY_NUM 2 + +static const struct of_device_id dsicm_of_match[]; + +struct dsic_panel_data { + u32 xres; + u32 yres; + u32 refresh; + u32 width_mm; + u32 height_mm; + u32 max_hs_rate; + u32 max_lp_rate; +}; + +struct panel_drv_data { + struct mipi_dsi_device *dsi; + struct drm_panel panel; + struct drm_display_mode mode; + + struct mutex lock; + + struct backlight_device *bldev; + struct backlight_device *extbldev; + + unsigned long hw_guard_end; /* next value of jiffies when we can + * issue the next sleep in/out command + */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + const struct dsic_panel_data *panel_data; + + struct gpio_desc *reset_gpio; + + struct regulator_bulk_data supplies[DCS_REGULATOR_SUPPLY_NUM]; + + bool use_dsi_backlight; + + /* runtime variables */ + bool enabled; + + bool intro_printed; +}; + +static inline struct panel_drv_data *panel_to_ddata(struct drm_panel *panel) +{ + return container_of(panel, struct panel_drv_data, panel); +} + +static void dsicm_bl_power(struct panel_drv_data *ddata, bool enable) +{ + struct backlight_device *backlight; + + if (ddata->bldev) + backlight = ddata->bldev; + else if (ddata->extbldev) + backlight = ddata->extbldev; + else + return; + + if (enable) { + backlight->props.fb_blank = FB_BLANK_UNBLANK; + backlight->props.state = ~(BL_CORE_FBBLANK | BL_CORE_SUSPENDED); + backlight->props.power = FB_BLANK_UNBLANK; + } else { + backlight->props.fb_blank = FB_BLANK_NORMAL; + backlight->props.power = FB_BLANK_POWERDOWN; + backlight->props.state |= BL_CORE_FBBLANK | BL_CORE_SUSPENDED; + } + + backlight_update_status(backlight); +} + +static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec) +{ + ddata->hw_guard_wait = msecs_to_jiffies(guard_msec); + ddata->hw_guard_end = jiffies + ddata->hw_guard_wait; +} + +static void hw_guard_wait(struct panel_drv_data *ddata) +{ + unsigned long wait = ddata->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= ddata->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static int dsicm_dcs_read_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 *data) +{ + return mipi_dsi_dcs_read(ddata->dsi, dcs_cmd, data, 1); +} + +static int dsicm_dcs_write_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 param) +{ + return mipi_dsi_dcs_write(ddata->dsi, dcs_cmd, ¶m, 1); +} + +static int dsicm_sleep_in(struct panel_drv_data *ddata) + +{ + int r; + + hw_guard_wait(ddata); + + r = mipi_dsi_dcs_enter_sleep_mode(ddata->dsi); + if (r) + return r; + + hw_guard_start(ddata, 120); + + usleep_range(5000, 10000); + + return 0; +} + +static int dsicm_sleep_out(struct panel_drv_data *ddata) +{ + int r; + + hw_guard_wait(ddata); + + r = mipi_dsi_dcs_exit_sleep_mode(ddata->dsi); + if (r) + return r; + + hw_guard_start(ddata, 120); + + usleep_range(5000, 10000); + + return 0; +} + +static int dsicm_get_id(struct panel_drv_data *ddata, u8 *id1, u8 *id2, u8 *id3) +{ + int r; + + r = dsicm_dcs_read_1(ddata, DCS_GET_ID1, id1); + if (r) + return r; + r = dsicm_dcs_read_1(ddata, DCS_GET_ID2, id2); + if (r) + return r; + r = dsicm_dcs_read_1(ddata, DCS_GET_ID3, id3); + if (r) + return r; + + return 0; +} + +static int dsicm_set_update_window(struct panel_drv_data *ddata) +{ + struct mipi_dsi_device *dsi = ddata->dsi; + int r; + + r = mipi_dsi_dcs_set_column_address(dsi, 0, ddata->mode.hdisplay - 1); + if (r < 0) + return r; + + r = mipi_dsi_dcs_set_page_address(dsi, 0, ddata->mode.vdisplay - 1); + if (r < 0) + return r; + + return 0; +} + +static int dsicm_bl_update_status(struct backlight_device *dev) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); + int r = 0; + int level; + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + level = dev->props.brightness; + else + level = 0; + + dev_dbg(&ddata->dsi->dev, "update brightness to %d\n", level); + + mutex_lock(&ddata->lock); + + if (ddata->enabled) + r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, + level); + + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_bl_get_intensity(struct backlight_device *dev) +{ + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + return dev->props.brightness; + + return 0; +} + +static const struct backlight_ops dsicm_bl_ops = { + .get_brightness = dsicm_bl_get_intensity, + .update_status = dsicm_bl_update_status, +}; + +static ssize_t num_dsi_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + u8 errors = 0; + int r = -ENODEV; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) + r = dsicm_dcs_read_1(ddata, MIPI_DCS_GET_ERROR_COUNT_ON_DSI, &errors); + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%d\n", errors); +} + +static ssize_t hw_revision_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct panel_drv_data *ddata = dev_get_drvdata(dev); + u8 id1, id2, id3; + int r = -ENODEV; + + mutex_lock(&ddata->lock); + + if (ddata->enabled) + r = dsicm_get_id(ddata, &id1, &id2, &id3); + + mutex_unlock(&ddata->lock); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3); +} + +static DEVICE_ATTR_RO(num_dsi_errors); +static DEVICE_ATTR_RO(hw_revision); + +static struct attribute *dsicm_attrs[] = { + &dev_attr_num_dsi_errors.attr, + &dev_attr_hw_revision.attr, + NULL, +}; + +static const struct attribute_group dsicm_attr_group = { + .attrs = dsicm_attrs, +}; + +static void dsicm_hw_reset(struct panel_drv_data *ddata) +{ + gpiod_set_value(ddata->reset_gpio, 1); + udelay(10); + /* reset the panel */ + gpiod_set_value(ddata->reset_gpio, 0); + /* assert reset */ + udelay(10); + gpiod_set_value(ddata->reset_gpio, 1); + /* wait after releasing reset */ + usleep_range(5000, 10000); +} + +static int dsicm_power_on(struct panel_drv_data *ddata) +{ + u8 id1, id2, id3; + int r; + + dsicm_hw_reset(ddata); + + ddata->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + r = dsicm_sleep_out(ddata); + if (r) + goto err; + + r = dsicm_get_id(ddata, &id1, &id2, &id3); + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff); + if (r) + goto err; + + r = dsicm_dcs_write_1(ddata, MIPI_DCS_WRITE_CONTROL_DISPLAY, + (1<<2) | (1<<5)); /* BL | BCTRL */ + if (r) + goto err; + + r = mipi_dsi_dcs_set_pixel_format(ddata->dsi, MIPI_DCS_PIXEL_FMT_24BIT); + if (r) + goto err; + + r = dsicm_set_update_window(ddata); + if (r) + goto err; + + r = mipi_dsi_dcs_set_display_on(ddata->dsi); + if (r) + goto err; + + r = mipi_dsi_dcs_set_tear_on(ddata->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (r) + goto err; + + /* possible panel bug */ + msleep(100); + + ddata->enabled = true; + + if (!ddata->intro_printed) { + dev_info(&ddata->dsi->dev, "panel revision %02x.%02x.%02x\n", + id1, id2, id3); + ddata->intro_printed = true; + } + + ddata->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + return 0; +err: + dev_err(&ddata->dsi->dev, "error while enabling panel, issuing HW reset\n"); + + dsicm_hw_reset(ddata); + + return r; +} + +static int dsicm_power_off(struct panel_drv_data *ddata) +{ + int r; + + ddata->enabled = false; + + r = mipi_dsi_dcs_set_display_off(ddata->dsi); + if (!r) + r = dsicm_sleep_in(ddata); + + if (r) { + dev_err(&ddata->dsi->dev, + "error disabling panel, issuing HW reset\n"); + dsicm_hw_reset(ddata); + } + + return r; +} + +static int dsicm_prepare(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + r = regulator_bulk_enable(ARRAY_SIZE(ddata->supplies), ddata->supplies); + if (r) + dev_err(&ddata->dsi->dev, "failed to enable supplies: %d\n", r); + + return r; +} + +static int dsicm_enable(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + mutex_lock(&ddata->lock); + + r = dsicm_power_on(ddata); + if (r) + goto err; + + mutex_unlock(&ddata->lock); + + dsicm_bl_power(ddata, true); + + return 0; +err: + dev_err(&ddata->dsi->dev, "enable failed (%d)\n", r); + mutex_unlock(&ddata->lock); + return r; +} + +static int dsicm_unprepare(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + r = regulator_bulk_disable(ARRAY_SIZE(ddata->supplies), ddata->supplies); + if (r) + dev_err(&ddata->dsi->dev, "failed to disable supplies: %d\n", r); + + return r; +} + +static int dsicm_disable(struct drm_panel *panel) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + int r; + + dsicm_bl_power(ddata, false); + + mutex_lock(&ddata->lock); + + r = dsicm_power_off(ddata); + + mutex_unlock(&ddata->lock); + + return r; +} + +static int dsicm_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_drv_data *ddata = panel_to_ddata(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &ddata->mode); + if (!mode) { + dev_err(&ddata->dsi->dev, "failed to add mode %ux%ux@%u kHz\n", + ddata->mode.hdisplay, ddata->mode.vdisplay, + ddata->mode.clock); + return -ENOMEM; + } + + connector->display_info.width_mm = ddata->panel_data->width_mm; + connector->display_info.height_mm = ddata->panel_data->height_mm; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs dsicm_panel_funcs = { + .unprepare = dsicm_unprepare, + .disable = dsicm_disable, + .prepare = dsicm_prepare, + .enable = dsicm_enable, + .get_modes = dsicm_get_modes, +}; + +static int dsicm_probe_of(struct mipi_dsi_device *dsi) +{ + struct backlight_device *backlight; + struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi); + int err; + struct drm_display_mode *mode = &ddata->mode; + + ddata->reset_gpio = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ddata->reset_gpio)) { + err = PTR_ERR(ddata->reset_gpio); + dev_err(&dsi->dev, "reset gpio request failed: %d", err); + return err; + } + + mode->hdisplay = mode->hsync_start = mode->hsync_end = mode->htotal = + ddata->panel_data->xres; + mode->vdisplay = mode->vsync_start = mode->vsync_end = mode->vtotal = + ddata->panel_data->yres; + mode->clock = ddata->panel_data->xres * ddata->panel_data->yres * + ddata->panel_data->refresh / 1000; + mode->width_mm = ddata->panel_data->width_mm; + mode->height_mm = ddata->panel_data->height_mm; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + + ddata->supplies[0].supply = "vpnl"; + ddata->supplies[1].supply = "vddi"; + err = devm_regulator_bulk_get(&dsi->dev, ARRAY_SIZE(ddata->supplies), + ddata->supplies); + if (err) + return err; + + backlight = devm_of_find_backlight(&dsi->dev); + if (IS_ERR(backlight)) + return PTR_ERR(backlight); + + /* If no backlight device is found assume native backlight support */ + if (backlight) + ddata->extbldev = backlight; + else + ddata->use_dsi_backlight = true; + + return 0; +} + +static int dsicm_probe(struct mipi_dsi_device *dsi) +{ + struct panel_drv_data *ddata; + struct backlight_device *bldev = NULL; + struct device *dev = &dsi->dev; + int r; + + dev_dbg(dev, "probe\n"); + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, ddata); + ddata->dsi = dsi; + + ddata->panel_data = of_device_get_match_data(dev); + if (!ddata->panel_data) + return -ENODEV; + + r = dsicm_probe_of(dsi); + if (r) + return r; + + mutex_init(&ddata->lock); + + dsicm_hw_reset(ddata); + + drm_panel_init(&ddata->panel, dev, &dsicm_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + if (ddata->use_dsi_backlight) { + struct backlight_properties props = { 0 }; + props.max_brightness = 255; + props.type = BACKLIGHT_RAW; + + bldev = devm_backlight_device_register(dev, dev_name(dev), + dev, ddata, &dsicm_bl_ops, &props); + if (IS_ERR(bldev)) { + r = PTR_ERR(bldev); + goto err_bl; + } + + ddata->bldev = bldev; + } + + r = sysfs_create_group(&dev->kobj, &dsicm_attr_group); + if (r) { + dev_err(dev, "failed to create sysfs files\n"); + goto err_bl; + } + + dsi->lanes = 2; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_EOT_PACKET; + dsi->hs_rate = ddata->panel_data->max_hs_rate; + dsi->lp_rate = ddata->panel_data->max_lp_rate; + + drm_panel_add(&ddata->panel); + + r = mipi_dsi_attach(dsi); + if (r < 0) + goto err_dsi_attach; + + return 0; + +err_dsi_attach: + drm_panel_remove(&ddata->panel); + sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group); +err_bl: + if (ddata->extbldev) + put_device(&ddata->extbldev->dev); + + return r; +} + +static int dsicm_remove(struct mipi_dsi_device *dsi) +{ + struct panel_drv_data *ddata = mipi_dsi_get_drvdata(dsi); + + dev_dbg(&dsi->dev, "remove\n"); + + mipi_dsi_detach(dsi); + + drm_panel_remove(&ddata->panel); + + sysfs_remove_group(&dsi->dev.kobj, &dsicm_attr_group); + + if (ddata->extbldev) + put_device(&ddata->extbldev->dev); + + return 0; +} + +static const struct dsic_panel_data taal_data = { + .xres = 864, + .yres = 480, + .refresh = 60, + .width_mm = 0, + .height_mm = 0, + .max_hs_rate = 300000000, + .max_lp_rate = 10000000, +}; + +static const struct dsic_panel_data himalaya_data = { + .xres = 480, + .yres = 864, + .refresh = 60, + .width_mm = 49, + .height_mm = 88, + .max_hs_rate = 300000000, + .max_lp_rate = 10000000, +}; + +static const struct dsic_panel_data droid4_data = { + .xres = 540, + .yres = 960, + .refresh = 60, + .width_mm = 50, + .height_mm = 89, + .max_hs_rate = 300000000, + .max_lp_rate = 10000000, +}; + +static const struct of_device_id dsicm_of_match[] = { + { .compatible = "tpo,taal", .data = &taal_data }, + { .compatible = "nokia,himalaya", &himalaya_data }, + { .compatible = "motorola,droid4-panel", &droid4_data }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dsicm_of_match); + +static struct mipi_dsi_driver dsicm_driver = { + .probe = dsicm_probe, + .remove = dsicm_remove, + .driver = { + .name = "panel-dsi-cm", + .of_match_table = dsicm_of_match, + }, +}; +module_mipi_dsi_driver(dsicm_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); +MODULE_DESCRIPTION("Generic DSI Command Mode Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-khadas-ts050.c b/drivers/gpu/drm/panel/panel-khadas-ts050.c new file mode 100644 index 000000000000..8f6ac1a40c31 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-khadas-ts050.c @@ -0,0 +1,870 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct khadas_ts050_panel { + struct drm_panel base; + struct mipi_dsi_device *link; + + struct regulator *supply; + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + + bool prepared; + bool enabled; +}; + +struct khadas_ts050_panel_cmd { + u8 cmd; + u8 data; +}; + +/* Only the CMD1 User Command set is documented */ +static const struct khadas_ts050_panel_cmd init_code[] = { + /* Select Unknown CMD Page (Undocumented) */ + {0xff, 0xee}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + {0x1f, 0x45}, + {0x24, 0x4f}, + {0x38, 0xc8}, + {0x39, 0x27}, + {0x1e, 0x77}, + {0x1d, 0x0f}, + {0x7e, 0x71}, + {0x7c, 0x03}, + {0xff, 0x00}, + {0xfb, 0x01}, + {0x35, 0x01}, + /* Select CMD2 Page0 (Undocumented) */ + {0xff, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + {0x00, 0x01}, + {0x01, 0x55}, + {0x02, 0x40}, + {0x05, 0x40}, + {0x06, 0x4a}, + {0x07, 0x24}, + {0x08, 0x0c}, + {0x0b, 0x7d}, + {0x0c, 0x7d}, + {0x0e, 0xb0}, + {0x0f, 0xae}, + {0x11, 0x10}, + {0x12, 0x10}, + {0x13, 0x03}, + {0x14, 0x4a}, + {0x15, 0x12}, + {0x16, 0x12}, + {0x18, 0x00}, + {0x19, 0x77}, + {0x1a, 0x55}, + {0x1b, 0x13}, + {0x1c, 0x00}, + {0x1d, 0x00}, + {0x1e, 0x13}, + {0x1f, 0x00}, + {0x23, 0x00}, + {0x24, 0x00}, + {0x25, 0x00}, + {0x26, 0x00}, + {0x27, 0x00}, + {0x28, 0x00}, + {0x35, 0x00}, + {0x66, 0x00}, + {0x58, 0x82}, + {0x59, 0x02}, + {0x5a, 0x02}, + {0x5b, 0x02}, + {0x5c, 0x82}, + {0x5d, 0x82}, + {0x5e, 0x02}, + {0x5f, 0x02}, + {0x72, 0x31}, + /* Select CMD2 Page4 (Undocumented) */ + {0xff, 0x05}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + {0x00, 0x01}, + {0x01, 0x0b}, + {0x02, 0x0c}, + {0x03, 0x09}, + {0x04, 0x0a}, + {0x05, 0x00}, + {0x06, 0x0f}, + {0x07, 0x10}, + {0x08, 0x00}, + {0x09, 0x00}, + {0x0a, 0x00}, + {0x0b, 0x00}, + {0x0c, 0x00}, + {0x0d, 0x13}, + {0x0e, 0x15}, + {0x0f, 0x17}, + {0x10, 0x01}, + {0x11, 0x0b}, + {0x12, 0x0c}, + {0x13, 0x09}, + {0x14, 0x0a}, + {0x15, 0x00}, + {0x16, 0x0f}, + {0x17, 0x10}, + {0x18, 0x00}, + {0x19, 0x00}, + {0x1a, 0x00}, + {0x1b, 0x00}, + {0x1c, 0x00}, + {0x1d, 0x13}, + {0x1e, 0x15}, + {0x1f, 0x17}, + {0x20, 0x00}, + {0x21, 0x03}, + {0x22, 0x01}, + {0x23, 0x40}, + {0x24, 0x40}, + {0x25, 0xed}, + {0x29, 0x58}, + {0x2a, 0x12}, + {0x2b, 0x01}, + {0x4b, 0x06}, + {0x4c, 0x11}, + {0x4d, 0x20}, + {0x4e, 0x02}, + {0x4f, 0x02}, + {0x50, 0x20}, + {0x51, 0x61}, + {0x52, 0x01}, + {0x53, 0x63}, + {0x54, 0x77}, + {0x55, 0xed}, + {0x5b, 0x00}, + {0x5c, 0x00}, + {0x5d, 0x00}, + {0x5e, 0x00}, + {0x5f, 0x15}, + {0x60, 0x75}, + {0x61, 0x00}, + {0x62, 0x00}, + {0x63, 0x00}, + {0x64, 0x00}, + {0x65, 0x00}, + {0x66, 0x00}, + {0x67, 0x00}, + {0x68, 0x04}, + {0x69, 0x00}, + {0x6a, 0x00}, + {0x6c, 0x40}, + {0x75, 0x01}, + {0x76, 0x01}, + {0x7a, 0x80}, + {0x7b, 0xa3}, + {0x7c, 0xd8}, + {0x7d, 0x60}, + {0x7f, 0x15}, + {0x80, 0x81}, + {0x83, 0x05}, + {0x93, 0x08}, + {0x94, 0x10}, + {0x8a, 0x00}, + {0x9b, 0x0f}, + {0xea, 0xff}, + {0xec, 0x00}, + /* Select CMD2 Page0 (Undocumented) */ + {0xff, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + {0x75, 0x00}, + {0x76, 0xdf}, + {0x77, 0x00}, + {0x78, 0xe4}, + {0x79, 0x00}, + {0x7a, 0xed}, + {0x7b, 0x00}, + {0x7c, 0xf6}, + {0x7d, 0x00}, + {0x7e, 0xff}, + {0x7f, 0x01}, + {0x80, 0x07}, + {0x81, 0x01}, + {0x82, 0x10}, + {0x83, 0x01}, + {0x84, 0x18}, + {0x85, 0x01}, + {0x86, 0x20}, + {0x87, 0x01}, + {0x88, 0x3d}, + {0x89, 0x01}, + {0x8a, 0x56}, + {0x8b, 0x01}, + {0x8c, 0x84}, + {0x8d, 0x01}, + {0x8e, 0xab}, + {0x8f, 0x01}, + {0x90, 0xec}, + {0x91, 0x02}, + {0x92, 0x22}, + {0x93, 0x02}, + {0x94, 0x23}, + {0x95, 0x02}, + {0x96, 0x55}, + {0x97, 0x02}, + {0x98, 0x8b}, + {0x99, 0x02}, + {0x9a, 0xaf}, + {0x9b, 0x02}, + {0x9c, 0xdf}, + {0x9d, 0x03}, + {0x9e, 0x01}, + {0x9f, 0x03}, + {0xa0, 0x2c}, + {0xa2, 0x03}, + {0xa3, 0x39}, + {0xa4, 0x03}, + {0xa5, 0x47}, + {0xa6, 0x03}, + {0xa7, 0x56}, + {0xa9, 0x03}, + {0xaa, 0x66}, + {0xab, 0x03}, + {0xac, 0x76}, + {0xad, 0x03}, + {0xae, 0x85}, + {0xaf, 0x03}, + {0xb0, 0x90}, + {0xb1, 0x03}, + {0xb2, 0xcb}, + {0xb3, 0x00}, + {0xb4, 0xdf}, + {0xb5, 0x00}, + {0xb6, 0xe4}, + {0xb7, 0x00}, + {0xb8, 0xed}, + {0xb9, 0x00}, + {0xba, 0xf6}, + {0xbb, 0x00}, + {0xbc, 0xff}, + {0xbd, 0x01}, + {0xbe, 0x07}, + {0xbf, 0x01}, + {0xc0, 0x10}, + {0xc1, 0x01}, + {0xc2, 0x18}, + {0xc3, 0x01}, + {0xc4, 0x20}, + {0xc5, 0x01}, + {0xc6, 0x3d}, + {0xc7, 0x01}, + {0xc8, 0x56}, + {0xc9, 0x01}, + {0xca, 0x84}, + {0xcb, 0x01}, + {0xcc, 0xab}, + {0xcd, 0x01}, + {0xce, 0xec}, + {0xcf, 0x02}, + {0xd0, 0x22}, + {0xd1, 0x02}, + {0xd2, 0x23}, + {0xd3, 0x02}, + {0xd4, 0x55}, + {0xd5, 0x02}, + {0xd6, 0x8b}, + {0xd7, 0x02}, + {0xd8, 0xaf}, + {0xd9, 0x02}, + {0xda, 0xdf}, + {0xdb, 0x03}, + {0xdc, 0x01}, + {0xdd, 0x03}, + {0xde, 0x2c}, + {0xdf, 0x03}, + {0xe0, 0x39}, + {0xe1, 0x03}, + {0xe2, 0x47}, + {0xe3, 0x03}, + {0xe4, 0x56}, + {0xe5, 0x03}, + {0xe6, 0x66}, + {0xe7, 0x03}, + {0xe8, 0x76}, + {0xe9, 0x03}, + {0xea, 0x85}, + {0xeb, 0x03}, + {0xec, 0x90}, + {0xed, 0x03}, + {0xee, 0xcb}, + {0xef, 0x00}, + {0xf0, 0xbb}, + {0xf1, 0x00}, + {0xf2, 0xc0}, + {0xf3, 0x00}, + {0xf4, 0xcc}, + {0xf5, 0x00}, + {0xf6, 0xd6}, + {0xf7, 0x00}, + {0xf8, 0xe1}, + {0xf9, 0x00}, + {0xfa, 0xea}, + /* Select CMD2 Page2 (Undocumented) */ + {0xff, 0x02}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + {0x00, 0x00}, + {0x01, 0xf4}, + {0x02, 0x00}, + {0x03, 0xef}, + {0x04, 0x01}, + {0x05, 0x07}, + {0x06, 0x01}, + {0x07, 0x28}, + {0x08, 0x01}, + {0x09, 0x44}, + {0x0a, 0x01}, + {0x0b, 0x76}, + {0x0c, 0x01}, + {0x0d, 0xa0}, + {0x0e, 0x01}, + {0x0f, 0xe7}, + {0x10, 0x02}, + {0x11, 0x1f}, + {0x12, 0x02}, + {0x13, 0x22}, + {0x14, 0x02}, + {0x15, 0x54}, + {0x16, 0x02}, + {0x17, 0x8b}, + {0x18, 0x02}, + {0x19, 0xaf}, + {0x1a, 0x02}, + {0x1b, 0xe0}, + {0x1c, 0x03}, + {0x1d, 0x01}, + {0x1e, 0x03}, + {0x1f, 0x2d}, + {0x20, 0x03}, + {0x21, 0x39}, + {0x22, 0x03}, + {0x23, 0x47}, + {0x24, 0x03}, + {0x25, 0x57}, + {0x26, 0x03}, + {0x27, 0x65}, + {0x28, 0x03}, + {0x29, 0x77}, + {0x2a, 0x03}, + {0x2b, 0x85}, + {0x2d, 0x03}, + {0x2f, 0x8f}, + {0x30, 0x03}, + {0x31, 0xcb}, + {0x32, 0x00}, + {0x33, 0xbb}, + {0x34, 0x00}, + {0x35, 0xc0}, + {0x36, 0x00}, + {0x37, 0xcc}, + {0x38, 0x00}, + {0x39, 0xd6}, + {0x3a, 0x00}, + {0x3b, 0xe1}, + {0x3d, 0x00}, + {0x3f, 0xea}, + {0x40, 0x00}, + {0x41, 0xf4}, + {0x42, 0x00}, + {0x43, 0xfe}, + {0x44, 0x01}, + {0x45, 0x07}, + {0x46, 0x01}, + {0x47, 0x28}, + {0x48, 0x01}, + {0x49, 0x44}, + {0x4a, 0x01}, + {0x4b, 0x76}, + {0x4c, 0x01}, + {0x4d, 0xa0}, + {0x4e, 0x01}, + {0x4f, 0xe7}, + {0x50, 0x02}, + {0x51, 0x1f}, + {0x52, 0x02}, + {0x53, 0x22}, + {0x54, 0x02}, + {0x55, 0x54}, + {0x56, 0x02}, + {0x58, 0x8b}, + {0x59, 0x02}, + {0x5a, 0xaf}, + {0x5b, 0x02}, + {0x5c, 0xe0}, + {0x5d, 0x03}, + {0x5e, 0x01}, + {0x5f, 0x03}, + {0x60, 0x2d}, + {0x61, 0x03}, + {0x62, 0x39}, + {0x63, 0x03}, + {0x64, 0x47}, + {0x65, 0x03}, + {0x66, 0x57}, + {0x67, 0x03}, + {0x68, 0x65}, + {0x69, 0x03}, + {0x6a, 0x77}, + {0x6b, 0x03}, + {0x6c, 0x85}, + {0x6d, 0x03}, + {0x6e, 0x8f}, + {0x6f, 0x03}, + {0x70, 0xcb}, + {0x71, 0x00}, + {0x72, 0x00}, + {0x73, 0x00}, + {0x74, 0x21}, + {0x75, 0x00}, + {0x76, 0x4c}, + {0x77, 0x00}, + {0x78, 0x6b}, + {0x79, 0x00}, + {0x7a, 0x85}, + {0x7b, 0x00}, + {0x7c, 0x9a}, + {0x7d, 0x00}, + {0x7e, 0xad}, + {0x7f, 0x00}, + {0x80, 0xbe}, + {0x81, 0x00}, + {0x82, 0xcd}, + {0x83, 0x01}, + {0x84, 0x01}, + {0x85, 0x01}, + {0x86, 0x29}, + {0x87, 0x01}, + {0x88, 0x68}, + {0x89, 0x01}, + {0x8a, 0x98}, + {0x8b, 0x01}, + {0x8c, 0xe5}, + {0x8d, 0x02}, + {0x8e, 0x1e}, + {0x8f, 0x02}, + {0x90, 0x30}, + {0x91, 0x02}, + {0x92, 0x52}, + {0x93, 0x02}, + {0x94, 0x88}, + {0x95, 0x02}, + {0x96, 0xaa}, + {0x97, 0x02}, + {0x98, 0xd7}, + {0x99, 0x02}, + {0x9a, 0xf7}, + {0x9b, 0x03}, + {0x9c, 0x21}, + {0x9d, 0x03}, + {0x9e, 0x2e}, + {0x9f, 0x03}, + {0xa0, 0x3d}, + {0xa2, 0x03}, + {0xa3, 0x4c}, + {0xa4, 0x03}, + {0xa5, 0x5e}, + {0xa6, 0x03}, + {0xa7, 0x71}, + {0xa9, 0x03}, + {0xaa, 0x86}, + {0xab, 0x03}, + {0xac, 0x94}, + {0xad, 0x03}, + {0xae, 0xfa}, + {0xaf, 0x00}, + {0xb0, 0x00}, + {0xb1, 0x00}, + {0xb2, 0x21}, + {0xb3, 0x00}, + {0xb4, 0x4c}, + {0xb5, 0x00}, + {0xb6, 0x6b}, + {0xb7, 0x00}, + {0xb8, 0x85}, + {0xb9, 0x00}, + {0xba, 0x9a}, + {0xbb, 0x00}, + {0xbc, 0xad}, + {0xbd, 0x00}, + {0xbe, 0xbe}, + {0xbf, 0x00}, + {0xc0, 0xcd}, + {0xc1, 0x01}, + {0xc2, 0x01}, + {0xc3, 0x01}, + {0xc4, 0x29}, + {0xc5, 0x01}, + {0xc6, 0x68}, + {0xc7, 0x01}, + {0xc8, 0x98}, + {0xc9, 0x01}, + {0xca, 0xe5}, + {0xcb, 0x02}, + {0xcc, 0x1e}, + {0xcd, 0x02}, + {0xce, 0x20}, + {0xcf, 0x02}, + {0xd0, 0x52}, + {0xd1, 0x02}, + {0xd2, 0x88}, + {0xd3, 0x02}, + {0xd4, 0xaa}, + {0xd5, 0x02}, + {0xd6, 0xd7}, + {0xd7, 0x02}, + {0xd8, 0xf7}, + {0xd9, 0x03}, + {0xda, 0x21}, + {0xdb, 0x03}, + {0xdc, 0x2e}, + {0xdd, 0x03}, + {0xde, 0x3d}, + {0xdf, 0x03}, + {0xe0, 0x4c}, + {0xe1, 0x03}, + {0xe2, 0x5e}, + {0xe3, 0x03}, + {0xe4, 0x71}, + {0xe5, 0x03}, + {0xe6, 0x86}, + {0xe7, 0x03}, + {0xe8, 0x94}, + {0xe9, 0x03}, + {0xea, 0xfa}, + /* Select CMD2 Page0 (Undocumented) */ + {0xff, 0x01}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + /* Select CMD2 Page1 (Undocumented) */ + {0xff, 0x02}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + /* Select CMD2 Page3 (Undocumented) */ + {0xff, 0x04}, + /* Reload CMD1: Don't reload default value to register */ + {0xfb, 0x01}, + /* Select CMD1 */ + {0xff, 0x00}, + {0xd3, 0x05}, /* RGBMIPICTRL: VSYNC back porch = 5 */ + {0xd4, 0x04}, /* RGBMIPICTRL: VSYNC front porch = 4 */ +}; + +static inline +struct khadas_ts050_panel *to_khadas_ts050_panel(struct drm_panel *panel) +{ + return container_of(panel, struct khadas_ts050_panel, base); +} + +static int khadas_ts050_panel_prepare(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + unsigned int i; + int err; + + if (khadas_ts050->prepared) + return 0; + + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 0); + + err = regulator_enable(khadas_ts050->supply); + if (err < 0) + return err; + + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 1); + + msleep(60); + + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 1); + + usleep_range(10000, 11000); + + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 0); + + /* Select CMD2 page 4 (Undocumented) */ + mipi_dsi_dcs_write(khadas_ts050->link, 0xff, (u8[]){ 0x05 }, 1); + + /* Reload CMD1: Don't reload default value to register */ + mipi_dsi_dcs_write(khadas_ts050->link, 0xfb, (u8[]){ 0x01 }, 1); + + mipi_dsi_dcs_write(khadas_ts050->link, 0xc5, (u8[]){ 0x01 }, 1); + + msleep(100); + + for (i = 0; i < ARRAY_SIZE(init_code); i++) { + err = mipi_dsi_dcs_write(khadas_ts050->link, + init_code[i].cmd, + &init_code[i].data, 1); + if (err < 0) { + dev_err(panel->dev, "failed write cmds: %d\n", err); + goto poweroff; + } + } + + err = mipi_dsi_dcs_exit_sleep_mode(khadas_ts050->link); + if (err < 0) { + dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); + goto poweroff; + } + + msleep(120); + + /* Select CMD1 */ + mipi_dsi_dcs_write(khadas_ts050->link, 0xff, (u8[]){ 0x00 }, 1); + + err = mipi_dsi_dcs_set_tear_on(khadas_ts050->link, + MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (err < 0) { + dev_err(panel->dev, "failed to set tear on: %d\n", err); + goto poweroff; + } + + err = mipi_dsi_dcs_set_display_on(khadas_ts050->link); + if (err < 0) { + dev_err(panel->dev, "failed to set display on: %d\n", err); + goto poweroff; + } + + usleep_range(10000, 11000); + + khadas_ts050->prepared = true; + + return 0; + +poweroff: + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 0); + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 1); + + regulator_disable(khadas_ts050->supply); + + return err; +} + +static int khadas_ts050_panel_unprepare(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + int err; + + if (!khadas_ts050->prepared) + return 0; + + khadas_ts050->prepared = false; + + err = mipi_dsi_dcs_enter_sleep_mode(khadas_ts050->link); + if (err < 0) + dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); + + msleep(150); + + gpiod_set_value_cansleep(khadas_ts050->enable_gpio, 0); + gpiod_set_value_cansleep(khadas_ts050->reset_gpio, 1); + + err = regulator_disable(khadas_ts050->supply); + if (err < 0) + return err; + + return 0; +} + +static int khadas_ts050_panel_enable(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + + khadas_ts050->enabled = true; + + return 0; +} + +static int khadas_ts050_panel_disable(struct drm_panel *panel) +{ + struct khadas_ts050_panel *khadas_ts050 = to_khadas_ts050_panel(panel); + int err; + + if (!khadas_ts050->enabled) + return 0; + + err = mipi_dsi_dcs_set_display_off(khadas_ts050->link); + if (err < 0) + dev_err(panel->dev, "failed to set display off: %d\n", err); + + usleep_range(10000, 11000); + + khadas_ts050->enabled = false; + + return 0; +} + +static const struct drm_display_mode default_mode = { + .clock = 120000, + .hdisplay = 1088, + .hsync_start = 1088 + 104, + .hsync_end = 1088 + 104 + 4, + .htotal = 1088 + 104 + 4 + 127, + .vdisplay = 1920, + .vsync_start = 1920 + 4, + .vsync_end = 1920 + 4 + 2, + .vtotal = 1920 + 4 + 2 + 3, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static int khadas_ts050_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = 64; + connector->display_info.height_mm = 118; + connector->display_info.bpc = 8; + + return 1; +} + +static const struct drm_panel_funcs khadas_ts050_panel_funcs = { + .prepare = khadas_ts050_panel_prepare, + .unprepare = khadas_ts050_panel_unprepare, + .enable = khadas_ts050_panel_enable, + .disable = khadas_ts050_panel_disable, + .get_modes = khadas_ts050_panel_get_modes, +}; + +static const struct of_device_id khadas_ts050_of_match[] = { + { .compatible = "khadas,ts050", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, khadas_ts050_of_match); + +static int khadas_ts050_panel_add(struct khadas_ts050_panel *khadas_ts050) +{ + struct device *dev = &khadas_ts050->link->dev; + int err; + + khadas_ts050->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(khadas_ts050->supply)) + return dev_err_probe(dev, PTR_ERR(khadas_ts050->supply), + "failed to get power supply"); + + khadas_ts050->reset_gpio = devm_gpiod_get(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(khadas_ts050->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(khadas_ts050->reset_gpio), + "failed to get reset gpio"); + + khadas_ts050->enable_gpio = devm_gpiod_get(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(khadas_ts050->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(khadas_ts050->enable_gpio), + "failed to get enable gpio"); + + drm_panel_init(&khadas_ts050->base, &khadas_ts050->link->dev, + &khadas_ts050_panel_funcs, DRM_MODE_CONNECTOR_DSI); + + err = drm_panel_of_backlight(&khadas_ts050->base); + if (err) + return err; + + drm_panel_add(&khadas_ts050->base); + + return 0; +} + +static int khadas_ts050_panel_probe(struct mipi_dsi_device *dsi) +{ + struct khadas_ts050_panel *khadas_ts050; + int err; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET; + + khadas_ts050 = devm_kzalloc(&dsi->dev, sizeof(*khadas_ts050), + GFP_KERNEL); + if (!khadas_ts050) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, khadas_ts050); + khadas_ts050->link = dsi; + + err = khadas_ts050_panel_add(khadas_ts050); + if (err < 0) + return err; + + err = mipi_dsi_attach(dsi); + if (err) + drm_panel_remove(&khadas_ts050->base); + + return err; +} + +static int khadas_ts050_panel_remove(struct mipi_dsi_device *dsi) +{ + struct khadas_ts050_panel *khadas_ts050 = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&khadas_ts050->base); + drm_panel_disable(&khadas_ts050->base); + drm_panel_unprepare(&khadas_ts050->base); + + return 0; +} + +static void khadas_ts050_panel_shutdown(struct mipi_dsi_device *dsi) +{ + struct khadas_ts050_panel *khadas_ts050 = mipi_dsi_get_drvdata(dsi); + + drm_panel_disable(&khadas_ts050->base); + drm_panel_unprepare(&khadas_ts050->base); +} + +static struct mipi_dsi_driver khadas_ts050_panel_driver = { + .driver = { + .name = "panel-khadas-ts050", + .of_match_table = khadas_ts050_of_match, + }, + .probe = khadas_ts050_panel_probe, + .remove = khadas_ts050_panel_remove, + .shutdown = khadas_ts050_panel_shutdown, +}; +module_mipi_dsi_driver(khadas_ts050_panel_driver); + +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Khadas TS050 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c b/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c index 0c5f22e95c2d..30f28ad4df6b 100644 --- a/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c +++ b/drivers/gpu/drm/panel/panel-mantix-mlaf057we51.c @@ -9,6 +9,7 @@ #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/regulator/consumer.h> #include <video/mipi_display.h> @@ -22,6 +23,7 @@ /* Manufacturer specific Commands send via DSI */ #define MANTIX_CMD_OTP_STOP_RELOAD_MIPI 0x41 #define MANTIX_CMD_INT_CANCEL 0x4C +#define MANTIX_CMD_SPI_FINISH 0x90 struct mantix { struct device *dev; @@ -33,6 +35,8 @@ struct mantix { struct regulator *avdd; struct regulator *avee; struct regulator *vddi; + + const struct drm_display_mode *default_mode; }; static inline struct mantix *panel_to_mantix(struct drm_panel *panel) @@ -66,6 +70,10 @@ static int mantix_init_sequence(struct mantix *ctx) dsi_generic_write_seq(dsi, 0x80, 0x64, 0x00, 0x64, 0x00, 0x00); msleep(20); + dsi_generic_write_seq(dsi, MANTIX_CMD_SPI_FINISH, 0xA5); + dsi_generic_write_seq(dsi, MANTIX_CMD_OTP_STOP_RELOAD_MIPI, 0x00, 0x2F); + msleep(20); + dev_dbg(dev, "Panel init sequence done\n"); return 0; } @@ -182,7 +190,7 @@ static int mantix_prepare(struct drm_panel *panel) return 0; } -static const struct drm_display_mode default_mode = { +static const struct drm_display_mode default_mode_mantix = { .hdisplay = 720, .hsync_start = 720 + 45, .hsync_end = 720 + 45 + 14, @@ -197,17 +205,32 @@ static const struct drm_display_mode default_mode = { .height_mm = 130, }; +static const struct drm_display_mode default_mode_ys = { + .hdisplay = 720, + .hsync_start = 720 + 45, + .hsync_end = 720 + 45 + 14, + .htotal = 720 + 45 + 14 + 25, + .vdisplay = 1440, + .vsync_start = 1440 + 175, + .vsync_end = 1440 + 175 + 8, + .vtotal = 1440 + 175 + 8 + 50, + .clock = 85298, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 65, + .height_mm = 130, +}; + static int mantix_get_modes(struct drm_panel *panel, struct drm_connector *connector) { struct mantix *ctx = panel_to_mantix(panel); struct drm_display_mode *mode; - mode = drm_mode_duplicate(connector->dev, &default_mode); + mode = drm_mode_duplicate(connector->dev, ctx->default_mode); if (!mode) { dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", - default_mode.hdisplay, default_mode.vdisplay, - drm_mode_vrefresh(&default_mode)); + ctx->default_mode->hdisplay, ctx->default_mode->vdisplay, + drm_mode_vrefresh(ctx->default_mode)); return -ENOMEM; } @@ -238,6 +261,7 @@ static int mantix_probe(struct mipi_dsi_device *dsi) ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + ctx->default_mode = of_device_get_match_data(dev); ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(ctx->reset_gpio)) { @@ -288,8 +312,8 @@ static int mantix_probe(struct mipi_dsi_device *dsi) } dev_info(dev, "%ux%u@%u %ubpp dsi %udl - ready\n", - default_mode.hdisplay, default_mode.vdisplay, - drm_mode_vrefresh(&default_mode), + ctx->default_mode->hdisplay, ctx->default_mode->vdisplay, + drm_mode_vrefresh(ctx->default_mode), mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes); return 0; @@ -316,7 +340,8 @@ static int mantix_remove(struct mipi_dsi_device *dsi) } static const struct of_device_id mantix_of_match[] = { - { .compatible = "mantix,mlaf057we51-x" }, + { .compatible = "mantix,mlaf057we51-x", .data = &default_mode_mantix }, + { .compatible = "ys,ys57pss36bh5gq", .data = &default_mode_ys }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mantix_of_match); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c index 6b4e97bfd46e..bf6d704d4d27 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c @@ -25,6 +25,14 @@ /* Manufacturer Command Set */ #define MCS_ELVSS_ON 0xb1 #define MCS_TEMP_SWIRE 0xb2 +#define MCS_PENTILE_1 0xb3 +#define MCS_PENTILE_2 0xb4 +#define MCS_GAMMA_DELTA_Y_RED 0xb5 +#define MCS_GAMMA_DELTA_X_RED 0xb6 +#define MCS_GAMMA_DELTA_Y_GREEN 0xb7 +#define MCS_GAMMA_DELTA_X_GREEN 0xb8 +#define MCS_GAMMA_DELTA_Y_BLUE 0xb9 +#define MCS_GAMMA_DELTA_X_BLUE 0xba #define MCS_MIECTL1 0xc0 #define MCS_BCMODE 0xc1 #define MCS_ERROR_CHECK 0xd5 @@ -281,6 +289,7 @@ struct s6e63m0 { struct backlight_device *bl_dev; u8 lcd_type; u8 elvss_pulse; + bool dsi_mode; struct regulator_bulk_data supplies[2]; struct gpio_desc *reset_gpio; @@ -395,9 +404,21 @@ static int s6e63m0_check_lcd_type(struct s6e63m0 *ctx) static void s6e63m0_init(struct s6e63m0 *ctx) { - s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL, - 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f, - 0x63, 0x8f, 0x1a, 0x33, 0x0d, 0x00, 0x00); + /* + * We do not know why there is a difference in the DSI mode. + * (No datasheet.) + * + * In the vendor driver this sequence is called + * "SEQ_PANEL_CONDITION_SET" or "DCS_CMD_SEQ_PANEL_COND_SET". + */ + if (ctx->dsi_mode) + s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL, + 0x01, 0x2c, 0x2c, 0x07, 0x07, 0x5f, 0xb3, + 0x6d, 0x97, 0x1d, 0x3a, 0x0f, 0x00, 0x00); + else + s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL, + 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f, + 0x63, 0x8f, 0x1a, 0x33, 0x0d, 0x00, 0x00); s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL, 0x02, 0x03, 0x1c, 0x10, 0x10); @@ -414,40 +435,40 @@ static void s6e63m0_init(struct s6e63m0 *ctx) s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL, 0x00, 0x8e, 0x07); - s6e63m0_dcs_write_seq_static(ctx, 0xb3, 0x6c); + s6e63m0_dcs_write_seq_static(ctx, MCS_PENTILE_1, 0x6c); - s6e63m0_dcs_write_seq_static(ctx, 0xb5, + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_RED, 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17, 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b, 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a, 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23, 0x21, 0x20, 0x1e, 0x1e); - s6e63m0_dcs_write_seq_static(ctx, 0xb6, + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_RED, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66); - s6e63m0_dcs_write_seq_static(ctx, 0xb7, + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_GREEN, 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17, 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b, 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a, 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23, 0x21, 0x20, 0x1e, 0x1e); - s6e63m0_dcs_write_seq_static(ctx, 0xb8, + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_GREEN, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66); - s6e63m0_dcs_write_seq_static(ctx, 0xb9, + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_BLUE, 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17, 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b, 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a, 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23, 0x21, 0x20, 0x1e, 0x1e); - s6e63m0_dcs_write_seq_static(ctx, 0xba, + s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_BLUE, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66); @@ -704,6 +725,7 @@ int s6e63m0_probe(struct device *dev, if (!ctx) return -ENOMEM; + ctx->dsi_mode = dsi_mode; ctx->dcs_read = dcs_read; ctx->dcs_write = dcs_write; dev_set_drvdata(dev, ctx); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 41bbec72b2da..71ae200ac48a 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -39,72 +39,145 @@ #include <drm/drm_panel.h> /** - * struct panel_desc - * @modes: Pointer to array of fixed modes appropriate for this panel. If - * only one mode then this can just be the address of this the mode. - * NOTE: cannot be used with "timings" and also if this is specified - * then you cannot override the mode in the device tree. - * @num_modes: Number of elements in modes array. - * @timings: Pointer to array of display timings. NOTE: cannot be used with - * "modes" and also these will be used to validate a device tree - * override if one is present. - * @num_timings: Number of elements in timings array. - * @bpc: Bits per color. - * @size: Structure containing the physical size of this panel. - * @delay: Structure containing various delay values for this panel. - * @bus_format: See MEDIA_BUS_FMT_... defines. - * @bus_flags: See DRM_BUS_FLAG_... defines. - * @connector_type: LVDS, eDP, DSI, DPI, etc. + * struct panel_desc - Describes a simple panel. */ struct panel_desc { + /** + * @modes: Pointer to array of fixed modes appropriate for this panel. + * + * If only one mode then this can just be the address of the mode. + * NOTE: cannot be used with "timings" and also if this is specified + * then you cannot override the mode in the device tree. + */ const struct drm_display_mode *modes; + + /** @num_modes: Number of elements in modes array. */ unsigned int num_modes; + + /** + * @timings: Pointer to array of display timings + * + * NOTE: cannot be used with "modes" and also these will be used to + * validate a device tree override if one is present. + */ const struct display_timing *timings; + + /** @num_timings: Number of elements in timings array. */ unsigned int num_timings; + /** @bpc: Bits per color. */ unsigned int bpc; - /** - * @width: width (in millimeters) of the panel's active display area - * @height: height (in millimeters) of the panel's active display area - */ + /** @size: Structure containing the physical size of this panel. */ struct { + /** + * @size.width: Width (in mm) of the active display area. + */ unsigned int width; + + /** + * @size.height: Height (in mm) of the active display area. + */ unsigned int height; } size; - /** - * @prepare: the time (in milliseconds) that it takes for the panel to - * become ready and start receiving video data - * @hpd_absent_delay: Add this to the prepare delay if we know Hot - * Plug Detect isn't used. - * @enable: the time (in milliseconds) that it takes for the panel to - * display the first valid frame after starting to receive - * video data - * @disable: the time (in milliseconds) that it takes for the panel to - * turn the display off (no content is visible) - * @unprepare: the time (in milliseconds) that it takes for the panel - * to power itself down completely - */ + /** @delay: Structure containing various delay values for this panel. */ struct { + /** + * @delay.prepare: Time for the panel to become ready. + * + * The time (in milliseconds) that it takes for the panel to + * become ready and start receiving video data + */ unsigned int prepare; + + /** + * @delay.hpd_absent_delay: Time to wait if HPD isn't hooked up. + * + * Add this to the prepare delay if we know Hot Plug Detect + * isn't used. + */ unsigned int hpd_absent_delay; + + /** + * @delay.prepare_to_enable: Time between prepare and enable. + * + * The minimum time, in milliseconds, that needs to have passed + * between when prepare finished and enable may begin. If at + * enable time less time has passed since prepare finished, + * the driver waits for the remaining time. + * + * If a fixed enable delay is also specified, we'll start + * counting before delaying for the fixed delay. + * + * If a fixed prepare delay is also specified, we won't start + * counting until after the fixed delay. We can't overlap this + * fixed delay with the min time because the fixed delay + * doesn't happen at the end of the function if a HPD GPIO was + * specified. + * + * In other words: + * prepare() + * ... + * // do fixed prepare delay + * // wait for HPD GPIO if applicable + * // start counting for prepare_to_enable + * + * enable() + * // do fixed enable delay + * // enforce prepare_to_enable min time + */ + unsigned int prepare_to_enable; + + /** + * @delay.enable: Time for the panel to display a valid frame. + * + * The time (in milliseconds) that it takes for the panel to + * display the first valid frame after starting to receive + * video data. + */ unsigned int enable; + + /** + * @delay.disable: Time for the panel to turn the display off. + * + * The time (in milliseconds) that it takes for the panel to + * turn the display off (no content is visible). + */ unsigned int disable; + + /** + * @delay.unprepare: Time to power down completely. + * + * The time (in milliseconds) that it takes for the panel + * to power itself down completely. + * + * This time is used to prevent a future "prepare" from + * starting until at least this many milliseconds has passed. + * If at prepare time less time has passed since unprepare + * finished, the driver waits for the remaining time. + */ unsigned int unprepare; } delay; + /** @bus_format: See MEDIA_BUS_FMT_... defines. */ u32 bus_format; + + /** @bus_flags: See DRM_BUS_FLAG_... defines. */ u32 bus_flags; + + /** @connector_type: LVDS, eDP, DSI, DPI, etc. */ int connector_type; }; struct panel_simple { struct drm_panel base; - bool prepared; bool enabled; bool no_hpd; + ktime_t prepared_time; + ktime_t unprepared_time; + const struct panel_desc *desc; struct regulator *supply; @@ -232,6 +305,20 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, return num; } +static void panel_simple_wait(ktime_t start_ktime, unsigned int min_ms) +{ + ktime_t now_ktime, min_ktime; + + if (!min_ms) + return; + + min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms)); + now_ktime = ktime_get(); + + if (ktime_before(now_ktime, min_ktime)) + msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1); +} + static int panel_simple_disable(struct drm_panel *panel) { struct panel_simple *p = to_panel_simple(panel); @@ -251,17 +338,15 @@ static int panel_simple_unprepare(struct drm_panel *panel) { struct panel_simple *p = to_panel_simple(panel); - if (!p->prepared) + if (p->prepared_time == 0) return 0; gpiod_set_value_cansleep(p->enable_gpio, 0); regulator_disable(p->supply); - if (p->desc->delay.unprepare) - msleep(p->desc->delay.unprepare); - - p->prepared = false; + p->prepared_time = 0; + p->unprepared_time = ktime_get(); return 0; } @@ -298,9 +383,11 @@ static int panel_simple_prepare(struct drm_panel *panel) int err; int hpd_asserted; - if (p->prepared) + if (p->prepared_time != 0) return 0; + panel_simple_wait(p->unprepared_time, p->desc->delay.unprepare); + err = regulator_enable(p->supply); if (err < 0) { dev_err(panel->dev, "failed to enable supply: %d\n", err); @@ -335,7 +422,7 @@ static int panel_simple_prepare(struct drm_panel *panel) } } - p->prepared = true; + p->prepared_time = ktime_get(); return 0; } @@ -350,6 +437,8 @@ static int panel_simple_enable(struct drm_panel *panel) if (p->desc->delay.enable) msleep(p->desc->delay.enable); + panel_simple_wait(p->prepared_time, p->desc->delay.prepare_to_enable); + p->enabled = true; return 0; @@ -516,7 +605,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) return -ENOMEM; panel->enabled = false; - panel->prepared = false; + panel->prepared_time = 0; panel->desc = desc; panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); @@ -1318,6 +1407,51 @@ static const struct panel_desc boe_nv101wxmn51 = { }, }; +static const struct drm_display_mode boe_nv110wtm_n61_modes[] = { + { + .clock = 207800, + .hdisplay = 2160, + .hsync_start = 2160 + 48, + .hsync_end = 2160 + 48 + 32, + .htotal = 2160 + 48 + 32 + 100, + .vdisplay = 1440, + .vsync_start = 1440 + 3, + .vsync_end = 1440 + 3 + 6, + .vtotal = 1440 + 3 + 6 + 31, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { + .clock = 138500, + .hdisplay = 2160, + .hsync_start = 2160 + 48, + .hsync_end = 2160 + 48 + 32, + .htotal = 2160 + 48 + 32 + 100, + .vdisplay = 1440, + .vsync_start = 1440 + 3, + .vsync_end = 1440 + 3 + 6, + .vtotal = 1440 + 3 + 6 + 31, + .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct panel_desc boe_nv110wtm_n61 = { + .modes = boe_nv110wtm_n61_modes, + .num_modes = ARRAY_SIZE(boe_nv110wtm_n61_modes), + .bpc = 8, + .size = { + .width = 233, + .height = 155, + }, + .delay = { + .hpd_absent_delay = 200, + .prepare_to_enable = 80, + .unprepare = 500, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DATA_MSB_TO_LSB, + .connector_type = DRM_MODE_CONNECTOR_eDP, +}; + /* Also used for boe_nv133fhm_n62 */ static const struct drm_display_mode boe_nv133fhm_n61_modes = { .clock = 147840, @@ -4034,6 +4168,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "boe,nv101wxmn51", .data = &boe_nv101wxmn51, }, { + .compatible = "boe,nv110wtm-n61", + .data = &boe_nv110wtm_n61, + }, { .compatible = "boe,nv133fhm-n61", .data = &boe_nv133fhm_n61, }, { diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7703.c b/drivers/gpu/drm/panel/panel-sitronix-st7703.c index b30510b1696a..a2c303e5732c 100644 --- a/drivers/gpu/drm/panel/panel-sitronix-st7703.c +++ b/drivers/gpu/drm/panel/panel-sitronix-st7703.c @@ -530,10 +530,8 @@ static int st7703_probe(struct mipi_dsi_device *dsi) return -ENOMEM; ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->reset_gpio)) { - dev_err(dev, "cannot get reset gpio\n"); - return PTR_ERR(ctx->reset_gpio); - } + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "Failed to get reset gpio\n"); mipi_dsi_set_drvdata(dsi, ctx); @@ -545,19 +543,13 @@ static int st7703_probe(struct mipi_dsi_device *dsi) dsi->lanes = ctx->desc->lanes; ctx->vcc = devm_regulator_get(dev, "vcc"); - if (IS_ERR(ctx->vcc)) { - ret = PTR_ERR(ctx->vcc); - if (ret != -EPROBE_DEFER) - dev_err(dev, "Failed to request vcc regulator: %d\n", ret); - return ret; - } + if (IS_ERR(ctx->vcc)) + return dev_err_probe(dev, PTR_ERR(ctx->vcc), "Failed to request vcc regulator\n"); + ctx->iovcc = devm_regulator_get(dev, "iovcc"); - if (IS_ERR(ctx->iovcc)) { - ret = PTR_ERR(ctx->iovcc); - if (ret != -EPROBE_DEFER) - dev_err(dev, "Failed to request iovcc regulator: %d\n", ret); - return ret; - } + if (IS_ERR(ctx->iovcc)) + return dev_err_probe(dev, PTR_ERR(ctx->iovcc), + "Failed to request iovcc regulator\n"); drm_panel_init(&ctx->panel, dev, &st7703_drm_funcs, DRM_MODE_CONNECTOR_DSI); |