diff options
| author | Chris Morgan <macromorgan@hotmail.com> | 2026-06-10 09:44:05 -0500 |
|---|---|---|
| committer | Neil Armstrong <neil.armstrong@linaro.org> | 2026-06-18 15:47:13 +0200 |
| commit | a81b4fe69e7bd2667524182e76c635f04c1ead7e (patch) | |
| tree | f178457b3e40269b8c4ab5dc0ecb4f48da47e9de /drivers/gpu | |
| parent | 0fd6ead8270270d859a28d0196a4b47e7d6845b8 (diff) | |
drm/panel: anbernic-td4310: Add RG Vita Pro panel
The panel used by Anbernic in the RG Vita-Pro is a DSI panel based
on the TD4310 controller IC. It measures approximately 5.5 inches
diagonally and is 1080x1920 in resolution.
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://patch.msgid.link/20260610144407.438846-5-macroalpha82@gmail.com
Diffstat (limited to 'drivers/gpu')
| -rw-r--r-- | drivers/gpu/drm/panel/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/gpu/drm/panel/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpu/drm/panel/panel-anbernic-td4310.c | 257 |
3 files changed, 268 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 7450b27622a2..3656bfab7712 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -17,6 +17,16 @@ config DRM_PANEL_ABT_Y030XX067A Y030XX067A 320x480 3.0" panel as found in the YLM RG-280M, RG-300 and RG-99 handheld gaming consoles. +config DRM_PANEL_ANBERNIC_TD4310 + tristate "Anbernic TD4310 LCD panel" + depends on GPIOLIB && OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here to enable support for Anbernic designed panels with the + TD4310 panel controller such as the ones used on the Anbernic RG + Vita Pro. + config DRM_PANEL_ARM_VERSATILE tristate "ARM Versatile panel driver" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index c2c5cf817116..3b970085f54f 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_PANEL_ABT_Y030XX067A) += panel-abt-y030xx067a.o +obj-$(CONFIG_DRM_PANEL_ANBERNIC_TD4310) += panel-anbernic-td4310.o 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_AUO_A030JTN01) += panel-auo-a030jtn01.o diff --git a/drivers/gpu/drm/panel/panel-anbernic-td4310.c b/drivers/gpu/drm/panel/panel-anbernic-td4310.c new file mode 100644 index 000000000000..9a1b4525423c --- /dev/null +++ b/drivers/gpu/drm/panel/panel-anbernic-td4310.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Anbernic panels with TD4310 panel controller. + * + * Copyright (C) 2026 Chris Morgan <macromorgan@hotmail.com> + * + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#include <video/mipi_display.h> + +struct anbernic_panel_td4310_info { + const struct drm_display_mode *display_mode; + u16 width_mm; + u16 height_mm; + u32 bus_flags; + unsigned long mode_flags; + u32 format; + u32 lanes; + u16 prepare_delay; + u16 reset_delay; + u16 init_delay; + u16 enable_delay; + u16 disable_delay; + u16 unprepare_delay; +}; + +struct anbernic_panel_td4310 { + struct device *dev; + struct mipi_dsi_device *dsi; + struct drm_panel panel; + const struct anbernic_panel_td4310_info *panel_info; + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + struct regulator *vdd; + enum drm_panel_orientation orientation; +}; + +static inline struct anbernic_panel_td4310 *panel_to_anbernic_panel_td4310(struct drm_panel *panel) +{ + return container_of(panel, struct anbernic_panel_td4310, panel); +} + +static int panel_anbernic_td4310_prepare(struct drm_panel *panel) +{ + struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + int ret; + + ret = regulator_enable(ctx->vdd); + if (ret) + return ret; + + ret = gpiod_set_value_cansleep(ctx->enable_gpio, 1); + if (ret) + goto err_enable; + + if (ctx->panel_info->enable_delay) + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->enable_delay); + + ret = gpiod_set_value_cansleep(ctx->reset_gpio, 1); + if (ret) + goto err_reset; + + mipi_dsi_msleep(&dsi_ctx, 10); + + ret = gpiod_set_value_cansleep(ctx->reset_gpio, 0); + if (ret) + goto err_reset; + + if (ctx->panel_info->reset_delay) + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->reset_delay); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay); + + if (dsi_ctx.accum_err) { + ret = dsi_ctx.accum_err; + goto err_reset; + } + + return 0; + +err_reset: + gpiod_set_value_cansleep(ctx->enable_gpio, 0); +err_enable: + regulator_disable(ctx->vdd); + return ret; +} + +static int panel_anbernic_td4310_unprepare(struct drm_panel *panel) +{ + struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel); + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->unprepare_delay); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->disable_delay); + + gpiod_set_value_cansleep(ctx->enable_gpio, 0); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->vdd); + + return 0; +} + +static int panel_anbernic_td4310_get_mode(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel); + const struct anbernic_panel_td4310_info *panel_info = ctx->panel_info; + + connector->display_info.bpc = 8; + connector->display_info.width_mm = panel_info->width_mm; + connector->display_info.height_mm = panel_info->height_mm; + connector->display_info.bus_flags = panel_info->bus_flags; + + return drm_connector_helper_get_modes_fixed(connector, panel_info->display_mode); +} + +static enum drm_panel_orientation panel_anbernic_td4310_get_orientation(struct drm_panel *panel) +{ + struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel); + + return ctx->orientation; +} + +static const struct drm_panel_funcs panel_anbernic_td4310_funcs = { + .prepare = panel_anbernic_td4310_prepare, + .unprepare = panel_anbernic_td4310_unprepare, + .get_modes = panel_anbernic_td4310_get_mode, + .get_orientation = panel_anbernic_td4310_get_orientation, +}; + +static int panel_anbernic_td4310_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct anbernic_panel_td4310 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct anbernic_panel_td4310, panel, + &panel_anbernic_td4310_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->dev = dev; + + ctx->panel_info = of_device_get_match_data(dev); + if (!ctx->panel_info) + return -EINVAL; + + ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get panel orientation\n"); + + 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), + "Cannot get reset gpio\n"); + + ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(ctx->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio), + "Cannot get enable gpio\n"); + + ctx->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(ctx->vdd)) + return dev_err_probe(dev, PTR_ERR(ctx->vdd), + "Failed to request vdd regulator\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = ctx->panel_info->lanes; + dsi->format = ctx->panel_info->format; + dsi->mode_flags = ctx->panel_info->mode_flags; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + devm_drm_panel_add(dev, &ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + + return 0; +} + +static const struct drm_display_mode anbernic_vitapro_mode = { + .clock = 140020, + .hdisplay = 1080, + .hsync_start = 1080 + 50, + .hsync_end = 1080 + 50 + 4, + .htotal = 1080 + 50 + 4 + 50, + .vdisplay = 1920, + .vsync_start = 1920 + 15, + .vsync_end = 1920 + 15 + 4, + .vtotal = 1920 + 15 + 4 + 32, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, +}; + +static const struct anbernic_panel_td4310_info anbernic_vitapro_info = { + .display_mode = &anbernic_vitapro_mode, + .width_mm = 69, + .height_mm = 121, + .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, + .prepare_delay = 50, + .reset_delay = 220, + .enable_delay = 120, + .disable_delay = 50, + .unprepare_delay = 20, +}; + +static const struct of_device_id panel_anbernic_td4310_of_match[] = { + { + .compatible = "anbernic,panel-vita-pro", + .data = &anbernic_vitapro_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, panel_anbernic_td4310_of_match); + +static struct mipi_dsi_driver anbernic_panel_td4310_driver = { + .driver = { + .name = "panel-anbernic-td4310", + .of_match_table = panel_anbernic_td4310_of_match, + }, + .probe = panel_anbernic_td4310_probe, +}; +module_mipi_dsi_driver(anbernic_panel_td4310_driver); + +MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>"); +MODULE_DESCRIPTION("DRM driver for Anbernic TD4310 MIPI DSI panels"); +MODULE_LICENSE("GPL"); |
