Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

drm/panel: Add Novatek NT37801 panel driver

Add driver for the Novatek NT37801 or NT37810 AMOLED DSI 1440x3200
panel in CMD mode, used on Qualcomm MTP8750 board (SM8750).

Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Link: https://lore.kernel.org/r/20250508-sm8750-display-panel-v2-2-3ca072e3d1fa@linaro.org
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://lore.kernel.org/r/20250508-sm8750-display-panel-v2-2-3ca072e3d1fa@linaro.org

authored by

Krzysztof Kozlowski and committed by
Neil Armstrong
4fca6849 0311e0fd

+357
+6
MAINTAINERS
··· 7574 7574 F: Documentation/devicetree/bindings/display/panel/novatek,nt36672a.yaml 7575 7575 F: drivers/gpu/drm/panel/panel-novatek-nt36672a.c 7576 7576 7577 + DRM DRIVER FOR NOVATEK NT37801 PANELS 7578 + M: Krzysztof Kozlowski <krzk@kernel.org> 7579 + S: Maintained 7580 + F: Documentation/devicetree/bindings/display/panel/novatek,nt37801.yaml 7581 + F: drivers/gpu/drm/panel/panel-novatek-nt37801.c 7582 + 7577 7583 DRM DRIVER FOR NVIDIA GEFORCE/QUADRO GPUS 7578 7584 M: Lyude Paul <lyude@redhat.com> 7579 7585 M: Danilo Krummrich <dakr@kernel.org>
+10
drivers/gpu/drm/panel/Kconfig
··· 517 517 LCD panel module. The panel has a resolution of 1080x2408 and uses 24 bit 518 518 RGB per pixel. 519 519 520 + config DRM_PANEL_NOVATEK_NT37801 521 + tristate "Novatek NT37801/NT37810 AMOLED DSI panel" 522 + depends on OF 523 + depends on DRM_MIPI_DSI 524 + depends on BACKLIGHT_CLASS_DEVICE 525 + help 526 + Say Y here if you want to enable support for Novatek NT37801 (or 527 + NT37810) AMOLED DSI Video Mode LCD panel module with 1440x3200 528 + resolution. 529 + 520 530 config DRM_PANEL_NOVATEK_NT39016 521 531 tristate "Novatek NT39016 RGB/SPI panel" 522 532 depends on OF && SPI
+1
drivers/gpu/drm/panel/Makefile
··· 51 51 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o 52 52 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o 53 53 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o 54 + obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37801) += panel-novatek-nt37801.o 54 55 obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o 55 56 obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o 56 57 obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
+340
drivers/gpu/drm/panel/panel-novatek-nt37801.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + // Copyright (c) 2024 Linaro Limited 3 + 4 + #include <linux/backlight.h> 5 + #include <linux/delay.h> 6 + #include <linux/gpio/consumer.h> 7 + #include <linux/regulator/consumer.h> 8 + #include <linux/mod_devicetable.h> 9 + #include <linux/module.h> 10 + 11 + #include <drm/display/drm_dsc.h> 12 + #include <drm/display/drm_dsc_helper.h> 13 + #include <drm/drm_mipi_dsi.h> 14 + #include <drm/drm_modes.h> 15 + #include <drm/drm_panel.h> 16 + #include <drm/drm_probe_helper.h> 17 + 18 + #include <video/mipi_display.h> 19 + 20 + struct novatek_nt37801 { 21 + struct drm_panel panel; 22 + struct mipi_dsi_device *dsi; 23 + struct drm_dsc_config dsc; 24 + struct gpio_desc *reset_gpio; 25 + struct regulator_bulk_data *supplies; 26 + }; 27 + 28 + static const struct regulator_bulk_data novatek_nt37801_supplies[] = { 29 + { .supply = "vddio" }, 30 + { .supply = "vci" }, 31 + { .supply = "vdd" }, 32 + }; 33 + 34 + static inline struct novatek_nt37801 *to_novatek_nt37801(struct drm_panel *panel) 35 + { 36 + return container_of(panel, struct novatek_nt37801, panel); 37 + } 38 + 39 + static void novatek_nt37801_reset(struct novatek_nt37801 *ctx) 40 + { 41 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 42 + usleep_range(10000, 21000); 43 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 44 + usleep_range(10000, 21000); 45 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 46 + usleep_range(10000, 21000); 47 + } 48 + 49 + #define NT37801_DCS_SWITCH_PAGE 0xf0 50 + 51 + #define novatek_nt37801_switch_page(dsi_ctx, page) \ 52 + mipi_dsi_dcs_write_seq_multi((dsi_ctx), NT37801_DCS_SWITCH_PAGE, \ 53 + 0x55, 0xaa, 0x52, 0x08, (page)) 54 + 55 + static int novatek_nt37801_on(struct novatek_nt37801 *ctx) 56 + { 57 + struct mipi_dsi_device *dsi = ctx->dsi; 58 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 59 + 60 + dsi->mode_flags |= MIPI_DSI_MODE_LPM; 61 + 62 + novatek_nt37801_switch_page(&dsi_ctx, 0x01); 63 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01); 64 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc5, 0x0b, 0x0b, 0x0b); 65 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80); 66 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02); 67 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf5, 0x10); 68 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1b); 69 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x55); 70 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x18); 71 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x19); 72 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0f); 73 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x00); 74 + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x059f); 75 + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0c7f); 76 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03, 0x03); 77 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, 78 + 0x89, 0x28, 0x00, 0x28, 0xc2, 0x00, 0x02, 79 + 0x68, 0x04, 0x6c, 0x00, 0x0a, 0x02, 0x77, 80 + 0x01, 0xe9, 0x10, 0xf0); 81 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); 82 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x23); 83 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 84 + 0x00, 0x01, 0x00, 0x11, 0x33, 0x33, 0x33, 85 + 0x55, 0x57, 0xd0, 0x00, 0x00, 0x44, 0x56, 86 + 0x77, 0x78, 0x9a, 0xbc, 0xdd, 0xf0); 87 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x06); 88 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0xdc); 89 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x00); 90 + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 91 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x00, 0x18, 0x00, 0x10); 92 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 93 + 0x20); 94 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 95 + 0x07, 0xff, 0x07, 0xff, 0x0f, 0xff); 96 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x01); 97 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); 98 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9c, 0x01); 99 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); 100 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x00); 101 + 102 + novatek_nt37801_switch_page(&dsi_ctx, 0x01); 103 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x55, 0x01, 0xff, 0x03); 104 + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 105 + mipi_dsi_msleep(&dsi_ctx, 120); 106 + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 107 + mipi_dsi_msleep(&dsi_ctx, 20); 108 + 109 + return dsi_ctx.accum_err; 110 + } 111 + 112 + static int novatek_nt37801_off(struct novatek_nt37801 *ctx) 113 + { 114 + struct mipi_dsi_device *dsi = ctx->dsi; 115 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 116 + 117 + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 118 + 119 + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 120 + mipi_dsi_msleep(&dsi_ctx, 20); 121 + 122 + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 123 + mipi_dsi_msleep(&dsi_ctx, 120); 124 + 125 + return dsi_ctx.accum_err; 126 + } 127 + 128 + static int novatek_nt37801_prepare(struct drm_panel *panel) 129 + { 130 + struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); 131 + struct device *dev = &ctx->dsi->dev; 132 + struct drm_dsc_picture_parameter_set pps; 133 + int ret; 134 + 135 + ret = regulator_bulk_enable(ARRAY_SIZE(novatek_nt37801_supplies), 136 + ctx->supplies); 137 + if (ret < 0) 138 + return ret; 139 + 140 + novatek_nt37801_reset(ctx); 141 + 142 + ret = novatek_nt37801_on(ctx); 143 + if (ret < 0) 144 + goto err; 145 + 146 + drm_dsc_pps_payload_pack(&pps, &ctx->dsc); 147 + 148 + ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps); 149 + if (ret < 0) { 150 + dev_err(panel->dev, "failed to transmit PPS: %d\n", ret); 151 + goto err; 152 + } 153 + 154 + ret = mipi_dsi_compression_mode(ctx->dsi, true); 155 + if (ret < 0) { 156 + dev_err(dev, "failed to enable compression mode: %d\n", ret); 157 + goto err; 158 + } 159 + 160 + msleep(28); 161 + 162 + return 0; 163 + 164 + err: 165 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 166 + regulator_bulk_disable(ARRAY_SIZE(novatek_nt37801_supplies), 167 + ctx->supplies); 168 + 169 + return ret; 170 + } 171 + 172 + static int novatek_nt37801_unprepare(struct drm_panel *panel) 173 + { 174 + struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); 175 + struct device *dev = &ctx->dsi->dev; 176 + int ret; 177 + 178 + ret = novatek_nt37801_off(ctx); 179 + if (ret < 0) 180 + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); 181 + 182 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 183 + 184 + regulator_bulk_disable(ARRAY_SIZE(novatek_nt37801_supplies), 185 + ctx->supplies); 186 + 187 + return 0; 188 + } 189 + 190 + static const struct drm_display_mode novatek_nt37801_mode = { 191 + .clock = (1440 + 20 + 4 + 20) * (3200 + 20 + 2 + 18) * 120 / 1000, 192 + .hdisplay = 1440, 193 + .hsync_start = 1440 + 20, 194 + .hsync_end = 1440 + 20 + 4, 195 + .htotal = 1440 + 20 + 4 + 20, 196 + .vdisplay = 3200, 197 + .vsync_start = 3200 + 20, 198 + .vsync_end = 3200 + 20 + 2, 199 + .vtotal = 3200 + 20 + 2 + 18, 200 + .type = DRM_MODE_TYPE_DRIVER, 201 + }; 202 + 203 + static int novatek_nt37801_get_modes(struct drm_panel *panel, 204 + struct drm_connector *connector) 205 + { 206 + return drm_connector_helper_get_modes_fixed(connector, 207 + &novatek_nt37801_mode); 208 + } 209 + 210 + static const struct drm_panel_funcs novatek_nt37801_panel_funcs = { 211 + .prepare = novatek_nt37801_prepare, 212 + .unprepare = novatek_nt37801_unprepare, 213 + .get_modes = novatek_nt37801_get_modes, 214 + }; 215 + 216 + static int novatek_nt37801_bl_update_status(struct backlight_device *bl) 217 + { 218 + struct mipi_dsi_device *dsi = bl_get_data(bl); 219 + u16 brightness = backlight_get_brightness(bl); 220 + int ret; 221 + 222 + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 223 + 224 + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); 225 + if (ret < 0) 226 + return ret; 227 + 228 + dsi->mode_flags |= MIPI_DSI_MODE_LPM; 229 + 230 + return 0; 231 + } 232 + 233 + static const struct backlight_ops novatek_nt37801_bl_ops = { 234 + .update_status = novatek_nt37801_bl_update_status, 235 + }; 236 + 237 + static struct backlight_device * 238 + novatek_nt37801_create_backlight(struct mipi_dsi_device *dsi) 239 + { 240 + struct device *dev = &dsi->dev; 241 + const struct backlight_properties props = { 242 + .type = BACKLIGHT_RAW, 243 + .brightness = 4095, 244 + .max_brightness = 4095, 245 + }; 246 + 247 + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 248 + &novatek_nt37801_bl_ops, &props); 249 + } 250 + 251 + static int novatek_nt37801_probe(struct mipi_dsi_device *dsi) 252 + { 253 + struct device *dev = &dsi->dev; 254 + struct novatek_nt37801 *ctx; 255 + int ret; 256 + 257 + ctx = devm_drm_panel_alloc(dev, struct novatek_nt37801, panel, 258 + &novatek_nt37801_panel_funcs, 259 + DRM_MODE_CONNECTOR_DSI); 260 + if (!ctx) 261 + return -ENOMEM; 262 + 263 + ret = devm_regulator_bulk_get_const(dev, 264 + ARRAY_SIZE(novatek_nt37801_supplies), 265 + novatek_nt37801_supplies, 266 + &ctx->supplies); 267 + if (ret < 0) 268 + return ret; 269 + 270 + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 271 + if (IS_ERR(ctx->reset_gpio)) 272 + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 273 + "Failed to get reset-gpios\n"); 274 + 275 + ctx->dsi = dsi; 276 + mipi_dsi_set_drvdata(dsi, ctx); 277 + 278 + dsi->lanes = 4; 279 + dsi->format = MIPI_DSI_FMT_RGB888; 280 + dsi->mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS; 281 + 282 + ctx->panel.prepare_prev_first = true; 283 + ctx->panel.backlight = novatek_nt37801_create_backlight(dsi); 284 + if (IS_ERR(ctx->panel.backlight)) 285 + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 286 + "Failed to create backlight\n"); 287 + 288 + drm_panel_add(&ctx->panel); 289 + 290 + /* This panel only supports DSC; unconditionally enable it */ 291 + dsi->dsc = &ctx->dsc; 292 + ctx->dsc.dsc_version_major = 1; 293 + ctx->dsc.dsc_version_minor = 1; 294 + ctx->dsc.slice_height = 40; 295 + ctx->dsc.slice_width = 720; 296 + ctx->dsc.slice_count = 1440 / ctx->dsc.slice_width; 297 + ctx->dsc.bits_per_component = 8; 298 + ctx->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ 299 + ctx->dsc.block_pred_enable = true; 300 + 301 + ret = mipi_dsi_attach(dsi); 302 + if (ret < 0) { 303 + drm_panel_remove(&ctx->panel); 304 + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); 305 + } 306 + 307 + return 0; 308 + } 309 + 310 + static void novatek_nt37801_remove(struct mipi_dsi_device *dsi) 311 + { 312 + struct novatek_nt37801 *ctx = mipi_dsi_get_drvdata(dsi); 313 + int ret; 314 + 315 + ret = mipi_dsi_detach(dsi); 316 + if (ret < 0) 317 + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 318 + 319 + drm_panel_remove(&ctx->panel); 320 + } 321 + 322 + static const struct of_device_id novatek_nt37801_of_match[] = { 323 + { .compatible = "novatek,nt37801" }, 324 + {} 325 + }; 326 + MODULE_DEVICE_TABLE(of, novatek_nt37801_of_match); 327 + 328 + static struct mipi_dsi_driver novatek_nt37801_driver = { 329 + .probe = novatek_nt37801_probe, 330 + .remove = novatek_nt37801_remove, 331 + .driver = { 332 + .name = "panel-novatek-nt37801", 333 + .of_match_table = novatek_nt37801_of_match, 334 + }, 335 + }; 336 + module_mipi_dsi_driver(novatek_nt37801_driver); 337 + 338 + MODULE_AUTHOR("Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>"); 339 + MODULE_DESCRIPTION("Panel driver for the Novatek NT37801/NT37810 AMOLED DSI panel"); 340 + MODULE_LICENSE("GPL");