From: =?utf-8?q?Kamil_Trzci=C5=84ski?= <ayufan@ayufan.eu> Date: Wed, 8 Sep 2021 13:50:04 +0200 Subject: [PATCH 05/36] drm: panel: hx8394: Add driver for HX8394 based HannStar HSD060BHW4 panel MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit ... Signed-off-by: Kamil Trzciński <ayufan@ayufan.eu> Signed-off-by: Ondrej Jirman <megous@megous.com> --- drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-himax-hx8394.c | 410 +++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-himax-hx8394.c diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index cfc8d64..166bd4a 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -129,6 +129,15 @@ config DRM_PANEL_FEIYANG_FY07024DI26A30D Say Y if you want to enable support for panels based on the Feiyang FY07024DI26A30-D MIPI-DSI interface. +config DRM_PANEL_HIMAX_HX8394 + tristate "HIMAX HX8394 MIPI-DSI LCD panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for panels based on the + HIMAX HX8394 MIPI-DSI interface. + config DRM_PANEL_ILITEK_IL9322 tristate "Ilitek ILI9322 320x240 QVGA panels" depends on OF && SPI diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index bca4cc1..486926c 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o diff --git a/drivers/gpu/drm/panel/panel-himax-hx8394.c b/drivers/gpu/drm/panel/panel-himax-hx8394.c new file mode 100644 index 00000000..14659cb --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx8394.c @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for panels based on Himax HX8394 controller, souch as: + * + * - HannStar HSD060BHW4 5.99" MIPI-DSI panel + * + * Copyright (C) Kamil Trzciński + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/media-bus-format.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> + +#include <video/display_timing.h> +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#define DRV_NAME "panel-himax-hx8394" + +struct hx8394 { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *reset_gpio; + struct regulator *vcc; + struct regulator *iovcc; + bool prepared; + + struct dentry *debugfs; + const struct hx8394_panel_desc *desc; +}; + +struct hx8394_panel_desc { + const struct drm_display_mode *mode; + unsigned int lanes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + int (*init_sequence)(struct hx8394 *ctx); +}; + +static inline struct hx8394 *panel_to_hx8394(struct drm_panel *panel) +{ + return container_of(panel, struct hx8394, panel); +} + +#define dsi_generic_write_seq(dsi, seq...) do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ + } while (0) + +#define dsi_dcs_write_seq(dsi, cmd, seq...) do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ + } while (0) + + +static int hsd060bhw4_init_sequence(struct hx8394 *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + + dsi_dcs_write_seq(dsi, 0xb9, 0xff, 0x83, 0x94); + dsi_dcs_write_seq(dsi, 0xb1, 0x48, 0x11, 0x71, 0x09, 0x32, 0x24, 0x71, 0x31, 0x55, 0x30); + dsi_dcs_write_seq(dsi, 0xba, 0x63, 0x03, 0x68, 0x6b, 0xb2, 0xc0); + dsi_dcs_write_seq(dsi, 0xb2, 0x00, 0x80, 0x78, 0x0c, 0x07); + dsi_dcs_write_seq(dsi, 0xb4, 0x12, 0x63, 0x12, 0x63, 0x12, 0x63, 0x01, 0x0c, 0x7c, 0x55, 0x00, 0x3f, 0x12, 0x6b, 0x12, 0x6b, 0x12, 0x6b, 0x01, 0x0c, 0x7c); + dsi_dcs_write_seq(dsi, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1c, 0x00, 0x00, 0x32, 0x10, 0x09, 0x00, 0x09, 0x32, 0x15, 0xad, 0x05, 0xad, 0x32, 0x00, 0x00, 0x00, 0x00, 0x37, 0x03, 0x0b, 0x0b, 0x37, 0x00, 0x00, 0x00, 0x0c, 0x40); + dsi_dcs_write_seq(dsi, 0xd5, 0x19, 0x19, 0x18, 0x18, 0x1b, 0x1b, 0x1a, 0x1a, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x20, 0x21, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x24, 0x25, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + dsi_dcs_write_seq(dsi, 0xd6, 0x18, 0x18, 0x19, 0x19, 0x1b, 0x1b, 0x1a, 0x1a, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x25, 0x24, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18); + dsi_dcs_write_seq(dsi, 0xe0, 0x00, 0x04, 0x0c, 0x12, 0x14, 0x18, 0x1a, 0x18, 0x31, 0x3f, 0x4d, 0x4c, 0x54, 0x65, 0x6b, 0x70, 0x7f, 0x82, 0x7e, 0x8a, 0x99, 0x4a, 0x48, 0x49, 0x4b, 0x4a, 0x4c, 0x4b, 0x7f, 0x00, 0x04, 0x0c, 0x11, 0x13, 0x17, 0x1a, 0x18, 0x31, 0x3f, 0x4d, 0x4c, 0x54, 0x65, 0x6b, 0x70, 0x7f, 0x82, 0x7e, 0x8a, 0x99, 0x4a, 0x48, 0x49, 0x4b, 0x4a, 0x4c, 0x4b, 0x7f); + dsi_dcs_write_seq(dsi, 0xcc, 0x0b); + dsi_dcs_write_seq(dsi, 0xc0, 0x1f, 0x31); + dsi_dcs_write_seq(dsi, 0xb6, 0x7d, 0x7d); + dsi_dcs_write_seq(dsi, 0xd4, 0x02); + dsi_dcs_write_seq(dsi, 0xbd, 0x01); + dsi_dcs_write_seq(dsi, 0xb1, 0x00); + dsi_dcs_write_seq(dsi, 0xbd, 0x00); + dsi_dcs_write_seq(dsi, 0xc6, 0xed); + + // msleep(0xfa); + // dsi_dcs_write_seq(dsi, 0x11); + // msleep(0x32); + // dsi_dcs_write_seq(dsi, 0x29); + + return 0; +} + +static const struct drm_display_mode hsd060bhw4_mode = { + .hdisplay = 720, + .hsync_start = 720 + 40, + .hsync_end = 720 + 40 + 40, + .htotal = 720 + 40 + 40 + 40, + .vdisplay = 1440, + .vsync_start = 1440 + 18, + .vsync_end = 1440 + 18 + 10, + .vtotal = 1440 + 18 + 10 + 17, + .clock = 69000, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 68, + .height_mm = 136, +}; + +static const struct drm_display_mode hsd060bhw4_mode2 = { + .hdisplay = 720, + .hsync_start = 720 + 50, + .hsync_end = 720 + 50 + 10, + .htotal = 720 + 50 + 10 + 50, + .vdisplay = 1440, + .vsync_start = 1440 + 17, + .vsync_end = 1440 + 17 + 4, + .vtotal = 1440 + 17 + 4 + 10, + .clock = 60000, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 68, + .height_mm = 136, +}; + +static const struct hx8394_panel_desc hsd060bhw4_desc = { + .mode = &hsd060bhw4_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST,// | MIPI_DSI_MODE_LPM,//, // 0x843 + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = hsd060bhw4_init_sequence, +}; + +static int hx8394_enable(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + ret = ctx->desc->init_sequence(ctx); + if (ret < 0) { + dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret); + return ret; + } + + msleep(20); + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(ctx->dev, "Failed to exit sleep mode: %d\n", ret); + return ret; + } + + /* Panel is operational 120 msec after reset */ + msleep(60); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret) + return ret; + + dev_dbg(ctx->dev, "Panel init sequence done\n"); + + return 0; +} + +static int hx8394_disable(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) + dev_err(ctx->dev, "Failed to turn off the display: %d\n", ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) + dev_err(ctx->dev, "Failed to enter sleep mode: %d\n", ret); + + return 0; +} + +static int hx8394_unprepare(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + + if (!ctx->prepared) + return 0; + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + regulator_disable(ctx->iovcc); + regulator_disable(ctx->vcc); + ctx->prepared = false; + + return 0; +} + +static int hx8394_prepare(struct drm_panel *panel) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + int ret; + + if (ctx->prepared) + return 0; + + dev_dbg(ctx->dev, "Resetting the panel\n"); + ret = regulator_enable(ctx->vcc); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable vcc supply: %d\n", ret); + return ret; + } + ret = regulator_enable(ctx->iovcc); + if (ret < 0) { + dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", ret); + goto disable_vcc; + } + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + mdelay(0x32); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(0x64); + + ctx->prepared = true; + + return 0; + +disable_vcc: + regulator_disable(ctx->vcc); + return ret; +} + +static int hx8394_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct hx8394 *ctx = panel_to_hx8394(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ctx->desc->mode); + if (!mode) { + dev_err(ctx->dev, "Failed to add mode %ux%u@%u\n", + ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs hx8394_drm_funcs = { + .disable = hx8394_disable, + .unprepare = hx8394_unprepare, + .prepare = hx8394_prepare, + .enable = hx8394_enable, + .get_modes = hx8394_get_modes, +}; + +static int allpixelson_set(void *data, u64 val) +{ + struct hx8394 *ctx = data; + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + + dev_dbg(ctx->dev, "Setting all pixels on\n"); + dsi_generic_write_seq(dsi, 0x23); + msleep(val * 1000); + /* Reset the panel to get video back */ + drm_panel_disable(&ctx->panel); + drm_panel_unprepare(&ctx->panel); + drm_panel_prepare(&ctx->panel); + drm_panel_enable(&ctx->panel); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(allpixelson_fops, NULL, + allpixelson_set, "%llu\n"); + +static void hx8394_debugfs_init(struct hx8394 *ctx) +{ + ctx->debugfs = debugfs_create_dir(DRV_NAME, NULL); + + debugfs_create_file("allpixelson", 0600, ctx->debugfs, ctx, + &allpixelson_fops); +} + +static void hx8394_debugfs_remove(struct hx8394 *ctx) +{ + debugfs_remove_recursive(ctx->debugfs); + ctx->debugfs = NULL; +} + +static int hx8394_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct hx8394 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + 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); + + ctx->dev = dev; + ctx->desc = of_device_get_match_data(dev); + + dsi->mode_flags = ctx->desc->mode_flags; + dsi->format = ctx->desc->format; + dsi->lanes = ctx->desc->lanes; + + ctx->vcc = devm_regulator_get(dev, "vcc"); + 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)) + return dev_err_probe(dev, PTR_ERR(ctx->iovcc), + "Failed to request iovcc regulator\n"); + + drm_panel_init(&ctx->panel, dev, &hx8394_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach failed (%d). Is host ready?\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + dev_info(dev, "%ux%u@%u %ubpp dsi %udl - ready\n", + ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay, + drm_mode_vrefresh(ctx->desc->mode), + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes); + + hx8394_debugfs_init(ctx); + return 0; +} + +static void hx8394_shutdown(struct mipi_dsi_device *dsi) +{ + struct hx8394 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = drm_panel_unprepare(&ctx->panel); + if (ret < 0) + dev_err(&dsi->dev, "Failed to unprepare panel: %d\n", ret); + + ret = drm_panel_disable(&ctx->panel); + if (ret < 0) + dev_err(&dsi->dev, "Failed to disable panel: %d\n", ret); +} + +static int hx8394_remove(struct mipi_dsi_device *dsi) +{ + struct hx8394 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + hx8394_shutdown(dsi); + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); + + hx8394_debugfs_remove(ctx); + + return 0; +} + +static const struct of_device_id hx8394_of_match[] = { + { .compatible = "hannstar,hsd060bhw4", .data = &hsd060bhw4_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, hx8394_of_match); + +static struct mipi_dsi_driver hx8394_driver = { + .probe = hx8394_probe, + .remove = hx8394_remove, + .shutdown = hx8394_shutdown, + .driver = { + .name = DRV_NAME, + .of_match_table = hx8394_of_match, + }, +}; +module_mipi_dsi_driver(hx8394_driver); + +MODULE_AUTHOR("Kamil Trzciński <ayufan@ayufan.eu>"); +MODULE_DESCRIPTION("DRM driver for Himax HX8394 based MIPI DSI panels"); +MODULE_LICENSE("GPL v2");