13eae8250SKaustabh Chakraborty // SPDX-License-Identifier: GPL-2.0 23eae8250SKaustabh Chakraborty /* 33eae8250SKaustabh Chakraborty * Synaptics TDDI display panel driver. 43eae8250SKaustabh Chakraborty * 53eae8250SKaustabh Chakraborty * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org> 63eae8250SKaustabh Chakraborty */ 73eae8250SKaustabh Chakraborty 83eae8250SKaustabh Chakraborty #include <linux/backlight.h> 93eae8250SKaustabh Chakraborty #include <linux/gpio/consumer.h> 103eae8250SKaustabh Chakraborty #include <linux/module.h> 113eae8250SKaustabh Chakraborty #include <linux/of.h> 12*66610c08SStephen Rothwell #include <linux/regulator/consumer.h> 133eae8250SKaustabh Chakraborty 143eae8250SKaustabh Chakraborty #include <video/mipi_display.h> 153eae8250SKaustabh Chakraborty 163eae8250SKaustabh Chakraborty #include <drm/drm_mipi_dsi.h> 173eae8250SKaustabh Chakraborty #include <drm/drm_modes.h> 183eae8250SKaustabh Chakraborty #include <drm/drm_panel.h> 193eae8250SKaustabh Chakraborty #include <drm/drm_probe_helper.h> 203eae8250SKaustabh Chakraborty 213eae8250SKaustabh Chakraborty struct tddi_panel_data { 223eae8250SKaustabh Chakraborty u8 lanes; 233eae8250SKaustabh Chakraborty /* wait timings for panel enable */ 243eae8250SKaustabh Chakraborty u8 delay_ms_sleep_exit; 253eae8250SKaustabh Chakraborty u8 delay_ms_display_on; 263eae8250SKaustabh Chakraborty /* wait timings for panel disable */ 273eae8250SKaustabh Chakraborty u8 delay_ms_display_off; 283eae8250SKaustabh Chakraborty u8 delay_ms_sleep_enter; 293eae8250SKaustabh Chakraborty }; 303eae8250SKaustabh Chakraborty 313eae8250SKaustabh Chakraborty struct tddi_ctx { 323eae8250SKaustabh Chakraborty struct drm_panel panel; 333eae8250SKaustabh Chakraborty struct mipi_dsi_device *dsi; 343eae8250SKaustabh Chakraborty struct drm_display_mode mode; 353eae8250SKaustabh Chakraborty struct backlight_device *backlight; 363eae8250SKaustabh Chakraborty const struct tddi_panel_data *data; 373eae8250SKaustabh Chakraborty struct regulator_bulk_data *supplies; 383eae8250SKaustabh Chakraborty struct gpio_desc *reset_gpio; 393eae8250SKaustabh Chakraborty struct gpio_desc *backlight_gpio; 403eae8250SKaustabh Chakraborty }; 413eae8250SKaustabh Chakraborty 423eae8250SKaustabh Chakraborty static const struct regulator_bulk_data tddi_supplies[] = { 433eae8250SKaustabh Chakraborty { .supply = "vio" }, 443eae8250SKaustabh Chakraborty { .supply = "vsn" }, 453eae8250SKaustabh Chakraborty { .supply = "vsp" }, 463eae8250SKaustabh Chakraborty }; 473eae8250SKaustabh Chakraborty 483eae8250SKaustabh Chakraborty static inline struct tddi_ctx *to_tddi_ctx(struct drm_panel *panel) 493eae8250SKaustabh Chakraborty { 503eae8250SKaustabh Chakraborty return container_of(panel, struct tddi_ctx, panel); 513eae8250SKaustabh Chakraborty } 523eae8250SKaustabh Chakraborty 533eae8250SKaustabh Chakraborty static int tddi_update_status(struct backlight_device *backlight) 543eae8250SKaustabh Chakraborty { 553eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = bl_get_data(backlight); 563eae8250SKaustabh Chakraborty struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; 573eae8250SKaustabh Chakraborty u8 brightness = backlight_get_brightness(backlight); 583eae8250SKaustabh Chakraborty 593eae8250SKaustabh Chakraborty if (!ctx->panel.enabled) 603eae8250SKaustabh Chakraborty return 0; 613eae8250SKaustabh Chakraborty 623eae8250SKaustabh Chakraborty mipi_dsi_dcs_set_display_brightness_multi(&dsi, brightness); 633eae8250SKaustabh Chakraborty 643eae8250SKaustabh Chakraborty return dsi.accum_err; 653eae8250SKaustabh Chakraborty } 663eae8250SKaustabh Chakraborty 673eae8250SKaustabh Chakraborty static int tddi_prepare(struct drm_panel *panel) 683eae8250SKaustabh Chakraborty { 693eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = to_tddi_ctx(panel); 703eae8250SKaustabh Chakraborty struct device *dev = &ctx->dsi->dev; 713eae8250SKaustabh Chakraborty int ret; 723eae8250SKaustabh Chakraborty 733eae8250SKaustabh Chakraborty ret = regulator_bulk_enable(ARRAY_SIZE(tddi_supplies), ctx->supplies); 743eae8250SKaustabh Chakraborty if (ret < 0) { 753eae8250SKaustabh Chakraborty dev_err(dev, "failed to enable regulators: %d\n", ret); 763eae8250SKaustabh Chakraborty return ret; 773eae8250SKaustabh Chakraborty } 783eae8250SKaustabh Chakraborty 793eae8250SKaustabh Chakraborty gpiod_set_value_cansleep(ctx->reset_gpio, 0); 803eae8250SKaustabh Chakraborty usleep_range(5000, 6000); 813eae8250SKaustabh Chakraborty gpiod_set_value_cansleep(ctx->reset_gpio, 1); 823eae8250SKaustabh Chakraborty usleep_range(5000, 6000); 833eae8250SKaustabh Chakraborty gpiod_set_value_cansleep(ctx->reset_gpio, 0); 843eae8250SKaustabh Chakraborty usleep_range(10000, 11000); 853eae8250SKaustabh Chakraborty 863eae8250SKaustabh Chakraborty gpiod_set_value_cansleep(ctx->backlight_gpio, 0); 873eae8250SKaustabh Chakraborty usleep_range(5000, 6000); 883eae8250SKaustabh Chakraborty 893eae8250SKaustabh Chakraborty return 0; 903eae8250SKaustabh Chakraborty } 913eae8250SKaustabh Chakraborty 923eae8250SKaustabh Chakraborty static int tddi_unprepare(struct drm_panel *panel) 933eae8250SKaustabh Chakraborty { 943eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = to_tddi_ctx(panel); 953eae8250SKaustabh Chakraborty 963eae8250SKaustabh Chakraborty gpiod_set_value_cansleep(ctx->backlight_gpio, 1); 973eae8250SKaustabh Chakraborty usleep_range(5000, 6000); 983eae8250SKaustabh Chakraborty 993eae8250SKaustabh Chakraborty gpiod_set_value_cansleep(ctx->reset_gpio, 1); 1003eae8250SKaustabh Chakraborty usleep_range(5000, 6000); 1013eae8250SKaustabh Chakraborty 1023eae8250SKaustabh Chakraborty regulator_bulk_disable(ARRAY_SIZE(tddi_supplies), ctx->supplies); 1033eae8250SKaustabh Chakraborty 1043eae8250SKaustabh Chakraborty return 0; 1053eae8250SKaustabh Chakraborty } 1063eae8250SKaustabh Chakraborty 1073eae8250SKaustabh Chakraborty static int tddi_enable(struct drm_panel *panel) 1083eae8250SKaustabh Chakraborty { 1093eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = to_tddi_ctx(panel); 1103eae8250SKaustabh Chakraborty struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; 1113eae8250SKaustabh Chakraborty u8 brightness = ctx->backlight->props.brightness; 1123eae8250SKaustabh Chakraborty 1133eae8250SKaustabh Chakraborty mipi_dsi_dcs_write_seq_multi(&dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); 1143eae8250SKaustabh Chakraborty mipi_dsi_dcs_write_seq_multi(&dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x0c); 1153eae8250SKaustabh Chakraborty 1163eae8250SKaustabh Chakraborty mipi_dsi_dcs_exit_sleep_mode_multi(&dsi); 1173eae8250SKaustabh Chakraborty mipi_dsi_msleep(&dsi, ctx->data->delay_ms_sleep_exit); 1183eae8250SKaustabh Chakraborty 1193eae8250SKaustabh Chakraborty /* sync the panel with the backlight's brightness level */ 1203eae8250SKaustabh Chakraborty mipi_dsi_dcs_set_display_brightness_multi(&dsi, brightness); 1213eae8250SKaustabh Chakraborty 1223eae8250SKaustabh Chakraborty mipi_dsi_dcs_set_display_on_multi(&dsi); 1233eae8250SKaustabh Chakraborty mipi_dsi_msleep(&dsi, ctx->data->delay_ms_display_on); 1243eae8250SKaustabh Chakraborty 1253eae8250SKaustabh Chakraborty return dsi.accum_err; 1263eae8250SKaustabh Chakraborty }; 1273eae8250SKaustabh Chakraborty 1283eae8250SKaustabh Chakraborty static int tddi_disable(struct drm_panel *panel) 1293eae8250SKaustabh Chakraborty { 1303eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = to_tddi_ctx(panel); 1313eae8250SKaustabh Chakraborty struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; 1323eae8250SKaustabh Chakraborty 1333eae8250SKaustabh Chakraborty mipi_dsi_dcs_set_display_off_multi(&dsi); 1343eae8250SKaustabh Chakraborty mipi_dsi_msleep(&dsi, ctx->data->delay_ms_display_off); 1353eae8250SKaustabh Chakraborty 1363eae8250SKaustabh Chakraborty mipi_dsi_dcs_enter_sleep_mode_multi(&dsi); 1373eae8250SKaustabh Chakraborty mipi_dsi_msleep(&dsi, ctx->data->delay_ms_sleep_enter); 1383eae8250SKaustabh Chakraborty 1393eae8250SKaustabh Chakraborty return dsi.accum_err; 1403eae8250SKaustabh Chakraborty } 1413eae8250SKaustabh Chakraborty 1423eae8250SKaustabh Chakraborty static int tddi_get_modes(struct drm_panel *panel, 1433eae8250SKaustabh Chakraborty struct drm_connector *connector) 1443eae8250SKaustabh Chakraborty { 1453eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = to_tddi_ctx(panel); 1463eae8250SKaustabh Chakraborty 1473eae8250SKaustabh Chakraborty return drm_connector_helper_get_modes_fixed(connector, &ctx->mode); 1483eae8250SKaustabh Chakraborty } 1493eae8250SKaustabh Chakraborty 1503eae8250SKaustabh Chakraborty static const struct backlight_ops tddi_bl_ops = { 1513eae8250SKaustabh Chakraborty .update_status = tddi_update_status, 1523eae8250SKaustabh Chakraborty }; 1533eae8250SKaustabh Chakraborty 1543eae8250SKaustabh Chakraborty static const struct backlight_properties tddi_bl_props = { 1553eae8250SKaustabh Chakraborty .type = BACKLIGHT_PLATFORM, 1563eae8250SKaustabh Chakraborty .brightness = 255, 1573eae8250SKaustabh Chakraborty .max_brightness = 255, 1583eae8250SKaustabh Chakraborty }; 1593eae8250SKaustabh Chakraborty 1603eae8250SKaustabh Chakraborty static const struct drm_panel_funcs tddi_drm_panel_funcs = { 1613eae8250SKaustabh Chakraborty .prepare = tddi_prepare, 1623eae8250SKaustabh Chakraborty .unprepare = tddi_unprepare, 1633eae8250SKaustabh Chakraborty .enable = tddi_enable, 1643eae8250SKaustabh Chakraborty .disable = tddi_disable, 1653eae8250SKaustabh Chakraborty .get_modes = tddi_get_modes, 1663eae8250SKaustabh Chakraborty }; 1673eae8250SKaustabh Chakraborty 1683eae8250SKaustabh Chakraborty static int tddi_probe(struct mipi_dsi_device *dsi) 1693eae8250SKaustabh Chakraborty { 1703eae8250SKaustabh Chakraborty struct device *dev = &dsi->dev; 1713eae8250SKaustabh Chakraborty struct tddi_ctx *ctx; 1723eae8250SKaustabh Chakraborty int ret; 1733eae8250SKaustabh Chakraborty 1743eae8250SKaustabh Chakraborty ctx = devm_drm_panel_alloc(dev, struct tddi_ctx, panel, 1753eae8250SKaustabh Chakraborty &tddi_drm_panel_funcs, DRM_MODE_CONNECTOR_DSI); 1763eae8250SKaustabh Chakraborty if (IS_ERR(ctx)) 1773eae8250SKaustabh Chakraborty return PTR_ERR(ctx); 1783eae8250SKaustabh Chakraborty 1793eae8250SKaustabh Chakraborty ctx->data = of_device_get_match_data(dev); 1803eae8250SKaustabh Chakraborty 1813eae8250SKaustabh Chakraborty ctx->dsi = dsi; 1823eae8250SKaustabh Chakraborty mipi_dsi_set_drvdata(dsi, ctx); 1833eae8250SKaustabh Chakraborty 1843eae8250SKaustabh Chakraborty ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(tddi_supplies), 1853eae8250SKaustabh Chakraborty tddi_supplies, &ctx->supplies); 1863eae8250SKaustabh Chakraborty if (ret < 0) 1873eae8250SKaustabh Chakraborty return dev_err_probe(dev, ret, "failed to get regulators\n"); 1883eae8250SKaustabh Chakraborty 1893eae8250SKaustabh Chakraborty ctx->backlight_gpio = devm_gpiod_get_optional(dev, "backlight", GPIOD_ASIS); 1903eae8250SKaustabh Chakraborty if (IS_ERR(ctx->backlight_gpio)) 1913eae8250SKaustabh Chakraborty return dev_err_probe(dev, PTR_ERR(ctx->backlight_gpio), 1923eae8250SKaustabh Chakraborty "failed to get backlight-gpios\n"); 1933eae8250SKaustabh Chakraborty 1943eae8250SKaustabh Chakraborty ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); 1953eae8250SKaustabh Chakraborty if (IS_ERR(ctx->reset_gpio)) 1963eae8250SKaustabh Chakraborty return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 1973eae8250SKaustabh Chakraborty "failed to get reset-gpios\n"); 1983eae8250SKaustabh Chakraborty 1993eae8250SKaustabh Chakraborty ret = of_get_drm_panel_display_mode(dev->of_node, &ctx->mode, NULL); 2003eae8250SKaustabh Chakraborty if (ret < 0) 2013eae8250SKaustabh Chakraborty return dev_err_probe(dev, ret, "failed to get panel timings\n"); 2023eae8250SKaustabh Chakraborty 2033eae8250SKaustabh Chakraborty ctx->backlight = devm_backlight_device_register(dev, dev_name(dev), dev, 2043eae8250SKaustabh Chakraborty ctx, &tddi_bl_ops, 2053eae8250SKaustabh Chakraborty &tddi_bl_props); 2063eae8250SKaustabh Chakraborty if (IS_ERR(ctx->backlight)) 2073eae8250SKaustabh Chakraborty return dev_err_probe(dev, PTR_ERR(ctx->backlight), 2083eae8250SKaustabh Chakraborty "failed to register backlight device"); 2093eae8250SKaustabh Chakraborty 2103eae8250SKaustabh Chakraborty dsi->lanes = ctx->data->lanes; 2113eae8250SKaustabh Chakraborty dsi->format = MIPI_DSI_FMT_RGB888; 2123eae8250SKaustabh Chakraborty dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | 2133eae8250SKaustabh Chakraborty MIPI_DSI_MODE_VIDEO_NO_HFP; 2143eae8250SKaustabh Chakraborty 2153eae8250SKaustabh Chakraborty ctx->panel.prepare_prev_first = true; 2163eae8250SKaustabh Chakraborty drm_panel_add(&ctx->panel); 2173eae8250SKaustabh Chakraborty 2183eae8250SKaustabh Chakraborty ret = devm_mipi_dsi_attach(dev, dsi); 2193eae8250SKaustabh Chakraborty if (ret < 0) { 2203eae8250SKaustabh Chakraborty drm_panel_remove(&ctx->panel); 2213eae8250SKaustabh Chakraborty return dev_err_probe(dev, ret, "failed to attach to DSI host\n"); 2223eae8250SKaustabh Chakraborty } 2233eae8250SKaustabh Chakraborty 2243eae8250SKaustabh Chakraborty return 0; 2253eae8250SKaustabh Chakraborty } 2263eae8250SKaustabh Chakraborty 2273eae8250SKaustabh Chakraborty static void tddi_remove(struct mipi_dsi_device *dsi) 2283eae8250SKaustabh Chakraborty { 2293eae8250SKaustabh Chakraborty struct tddi_ctx *ctx = mipi_dsi_get_drvdata(dsi); 2303eae8250SKaustabh Chakraborty 2313eae8250SKaustabh Chakraborty drm_panel_remove(&ctx->panel); 2323eae8250SKaustabh Chakraborty } 2333eae8250SKaustabh Chakraborty 2343eae8250SKaustabh Chakraborty static const struct tddi_panel_data td4101_panel_data = { 2353eae8250SKaustabh Chakraborty .lanes = 2, 2363eae8250SKaustabh Chakraborty /* wait timings for panel enable */ 2373eae8250SKaustabh Chakraborty .delay_ms_sleep_exit = 100, 2383eae8250SKaustabh Chakraborty .delay_ms_display_on = 0, 2393eae8250SKaustabh Chakraborty /* wait timings for panel disable */ 2403eae8250SKaustabh Chakraborty .delay_ms_display_off = 20, 2413eae8250SKaustabh Chakraborty .delay_ms_sleep_enter = 90, 2423eae8250SKaustabh Chakraborty }; 2433eae8250SKaustabh Chakraborty 2443eae8250SKaustabh Chakraborty static const struct tddi_panel_data td4300_panel_data = { 2453eae8250SKaustabh Chakraborty .lanes = 4, 2463eae8250SKaustabh Chakraborty /* wait timings for panel enable */ 2473eae8250SKaustabh Chakraborty .delay_ms_sleep_exit = 100, 2483eae8250SKaustabh Chakraborty .delay_ms_display_on = 0, 2493eae8250SKaustabh Chakraborty /* wait timings for panel disable */ 2503eae8250SKaustabh Chakraborty .delay_ms_display_off = 0, 2513eae8250SKaustabh Chakraborty .delay_ms_sleep_enter = 0, 2523eae8250SKaustabh Chakraborty }; 2533eae8250SKaustabh Chakraborty 2543eae8250SKaustabh Chakraborty static const struct of_device_id tddi_of_device_id[] = { 2553eae8250SKaustabh Chakraborty { 2563eae8250SKaustabh Chakraborty .compatible = "syna,td4101-panel", 2573eae8250SKaustabh Chakraborty .data = &td4101_panel_data, 2583eae8250SKaustabh Chakraborty }, { 2593eae8250SKaustabh Chakraborty .compatible = "syna,td4300-panel", 2603eae8250SKaustabh Chakraborty .data = &td4300_panel_data, 2613eae8250SKaustabh Chakraborty }, { } 2623eae8250SKaustabh Chakraborty }; 2633eae8250SKaustabh Chakraborty MODULE_DEVICE_TABLE(of, tddi_of_device_id); 2643eae8250SKaustabh Chakraborty 2653eae8250SKaustabh Chakraborty static struct mipi_dsi_driver tddi_dsi_driver = { 2663eae8250SKaustabh Chakraborty .probe = tddi_probe, 2673eae8250SKaustabh Chakraborty .remove = tddi_remove, 2683eae8250SKaustabh Chakraborty .driver = { 2693eae8250SKaustabh Chakraborty .name = "panel-synaptics-tddi", 2703eae8250SKaustabh Chakraborty .of_match_table = tddi_of_device_id, 2713eae8250SKaustabh Chakraborty }, 2723eae8250SKaustabh Chakraborty }; 2733eae8250SKaustabh Chakraborty module_mipi_dsi_driver(tddi_dsi_driver); 2743eae8250SKaustabh Chakraborty 2753eae8250SKaustabh Chakraborty MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); 2763eae8250SKaustabh Chakraborty MODULE_DESCRIPTION("Synaptics TDDI Display Panel Driver"); 2773eae8250SKaustabh Chakraborty MODULE_LICENSE("GPL"); 278