Skip to content

Commit 4fca684

Browse files
krzksuperna9999
authored andcommitted
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 <[email protected]> Reviewed-by: Linus Walleij <[email protected]> Signed-off-by: Krzysztof Kozlowski <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Neil Armstrong <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 0311e0f commit 4fca684

File tree

4 files changed

+357
-0
lines changed

4 files changed

+357
-0
lines changed

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7574,6 +7574,12 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
75747574
F: Documentation/devicetree/bindings/display/panel/novatek,nt36672a.yaml
75757575
F: drivers/gpu/drm/panel/panel-novatek-nt36672a.c
75767576

7577+
DRM DRIVER FOR NOVATEK NT37801 PANELS
7578+
M: Krzysztof Kozlowski <[email protected]>
7579+
S: Maintained
7580+
F: Documentation/devicetree/bindings/display/panel/novatek,nt37801.yaml
7581+
F: drivers/gpu/drm/panel/panel-novatek-nt37801.c
7582+
75777583
DRM DRIVER FOR NVIDIA GEFORCE/QUADRO GPUS
75787584
M: Lyude Paul <[email protected]>
75797585
M: Danilo Krummrich <[email protected]>

drivers/gpu/drm/panel/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,16 @@ config DRM_PANEL_NOVATEK_NT36672E
517517
LCD panel module. The panel has a resolution of 1080x2408 and uses 24 bit
518518
RGB per pixel.
519519

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+
520530
config DRM_PANEL_NOVATEK_NT39016
521531
tristate "Novatek NT39016 RGB/SPI panel"
522532
depends on OF && SPI

drivers/gpu/drm/panel/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o
5151
obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o
5252
obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o
5353
obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o
54+
obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37801) += panel-novatek-nt37801.o
5455
obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o
5556
obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o
5657
obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
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 <[email protected]>");
339+
MODULE_DESCRIPTION("Panel driver for the Novatek NT37801/NT37810 AMOLED DSI panel");
340+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)