Skip to content

Commit aec8485

Browse files
committed
drm/panel: add BOE tv101wum-ll2 panel driver
Add support for the 1200x1920 BOE TV101WUM-LL2 DSI Display Panel found in the Lenovo Smart Tab M10 tablet. The controller is unknown. Reviewed-by: Douglas Anderson <dianders@chromium.org> Link: https://lore.kernel.org/r/20240828-topic-sdm450-upstream-tbx605f-panel-v3-2-b792f93e1d6b@linaro.org Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org> Link: https://patchwork.freedesktop.org/patch/msgid/20240828-topic-sdm450-upstream-tbx605f-panel-v3-2-b792f93e1d6b@linaro.org
1 parent 1da04ea commit aec8485

3 files changed

Lines changed: 251 additions & 0 deletions

File tree

drivers/gpu/drm/panel/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ config DRM_PANEL_BOE_TV101WUM_NL6
8787
Say Y here if you want to support for BOE TV101WUM and AUO KD101N80
8888
45NA WUXGA PANEL DSI Video Mode panel
8989

90+
config DRM_PANEL_BOE_TV101WUM_LL2
91+
tristate "BOE TV101WUM LL2 1200x1920 panel"
92+
depends on OF
93+
depends on DRM_MIPI_DSI
94+
depends on BACKLIGHT_CLASS_DEVICE
95+
help
96+
Say Y here if you want to support for BOE TV101WUM-LL2
97+
WUXGA PANEL DSI Video Mode panel
98+
9099
config DRM_PANEL_EBBG_FT8719
91100
tristate "EBBG FT8719 panel driver"
92101
depends on OF

drivers/gpu/drm/panel/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_PANEL_AUO_A030JTN01) += panel-auo-a030jtn01.o
66
obj-$(CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0) += panel-boe-bf060y8m-aj0.o
77
obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o
88
obj-$(CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A) += panel-boe-th101mb31ig002-28a.o
9+
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_LL2) += panel-boe-tv101wum-ll2.o
910
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o
1011
obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o
1112
obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
// Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree:
3+
// Copyright (c) 2013, The Linux Foundation. All rights reserved.
4+
// Copyright (c) 2024, Neil Armstrong <neil.armstrong@linaro.org>
5+
6+
#include <linux/delay.h>
7+
#include <linux/gpio/consumer.h>
8+
#include <linux/module.h>
9+
#include <linux/of.h>
10+
#include <linux/regulator/consumer.h>
11+
12+
#include <drm/drm_mipi_dsi.h>
13+
#include <drm/drm_modes.h>
14+
#include <drm/drm_panel.h>
15+
#include <drm/drm_probe_helper.h>
16+
17+
struct boe_tv101wum_ll2 {
18+
struct drm_panel panel;
19+
struct mipi_dsi_device *dsi;
20+
struct gpio_desc *reset_gpio;
21+
struct regulator_bulk_data *supplies;
22+
};
23+
24+
static const struct regulator_bulk_data boe_tv101wum_ll2_supplies[] = {
25+
{ .supply = "vsp" },
26+
{ .supply = "vsn" },
27+
};
28+
29+
static inline struct boe_tv101wum_ll2 *to_boe_tv101wum_ll2(struct drm_panel *panel)
30+
{
31+
return container_of(panel, struct boe_tv101wum_ll2, panel);
32+
}
33+
34+
static void boe_tv101wum_ll2_reset(struct boe_tv101wum_ll2 *ctx)
35+
{
36+
gpiod_set_value_cansleep(ctx->reset_gpio, 0);
37+
usleep_range(5000, 6000);
38+
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
39+
usleep_range(5000, 6000);
40+
gpiod_set_value_cansleep(ctx->reset_gpio, 0);
41+
42+
msleep(120);
43+
}
44+
45+
static int boe_tv101wum_ll2_on(struct boe_tv101wum_ll2 *ctx)
46+
{
47+
struct mipi_dsi_device *dsi = ctx->dsi;
48+
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
49+
50+
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
51+
52+
mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
53+
54+
mipi_dsi_msleep(&dsi_ctx, 120);
55+
56+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x0e);
57+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0xff, 0x81, 0x68, 0x6c, 0x22,
58+
0x6d, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00);
59+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x23);
60+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x00, 0x00);
61+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x94, 0x2c, 0x00);
62+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x19);
63+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xa2, 0x38);
64+
65+
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x50, 0x5a, 0x0c);
66+
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x80, 0xfd);
67+
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x50, 0x00);
68+
69+
mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
70+
71+
mipi_dsi_msleep(&dsi_ctx, 20);
72+
73+
return dsi_ctx.accum_err;
74+
}
75+
76+
static void boe_tv101wum_ll2_off(struct boe_tv101wum_ll2 *ctx)
77+
{
78+
struct mipi_dsi_device *dsi = ctx->dsi;
79+
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
80+
81+
dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
82+
83+
mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
84+
85+
mipi_dsi_msleep(&dsi_ctx, 70);
86+
87+
mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
88+
89+
mipi_dsi_msleep(&dsi_ctx, 20);
90+
91+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x5a);
92+
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x5a);
93+
94+
mipi_dsi_msleep(&dsi_ctx, 150);
95+
}
96+
97+
static int boe_tv101wum_ll2_prepare(struct drm_panel *panel)
98+
{
99+
struct boe_tv101wum_ll2 *ctx = to_boe_tv101wum_ll2(panel);
100+
int ret;
101+
102+
ret = regulator_bulk_enable(ARRAY_SIZE(boe_tv101wum_ll2_supplies),
103+
ctx->supplies);
104+
if (ret < 0)
105+
return ret;
106+
107+
boe_tv101wum_ll2_reset(ctx);
108+
109+
ret = boe_tv101wum_ll2_on(ctx);
110+
if (ret < 0) {
111+
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
112+
regulator_bulk_disable(ARRAY_SIZE(boe_tv101wum_ll2_supplies),
113+
ctx->supplies);
114+
return ret;
115+
}
116+
117+
return 0;
118+
}
119+
120+
static int boe_tv101wum_ll2_unprepare(struct drm_panel *panel)
121+
{
122+
struct boe_tv101wum_ll2 *ctx = to_boe_tv101wum_ll2(panel);
123+
124+
/* Ignore errors on failure, in any case set gpio and disable regulators */
125+
boe_tv101wum_ll2_off(ctx);
126+
127+
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
128+
129+
regulator_bulk_disable(ARRAY_SIZE(boe_tv101wum_ll2_supplies),
130+
ctx->supplies);
131+
132+
return 0;
133+
}
134+
135+
static const struct drm_display_mode boe_tv101wum_ll2_mode = {
136+
.clock = (1200 + 27 + 8 + 12) * (1920 + 155 + 8 + 32) * 60 / 1000,
137+
.hdisplay = 1200,
138+
.hsync_start = 1200 + 27,
139+
.hsync_end = 1200 + 27 + 8,
140+
.htotal = 1200 + 27 + 8 + 12,
141+
.vdisplay = 1920,
142+
.vsync_start = 1920 + 155,
143+
.vsync_end = 1920 + 155 + 8,
144+
.vtotal = 1920 + 155 + 8 + 32,
145+
.width_mm = 136,
146+
.height_mm = 217,
147+
.type = DRM_MODE_TYPE_DRIVER,
148+
};
149+
150+
static int boe_tv101wum_ll2_get_modes(struct drm_panel *panel,
151+
struct drm_connector *connector)
152+
{
153+
/* We do not set display_info.bpc since unset value is bpc=8 by default */
154+
return drm_connector_helper_get_modes_fixed(connector, &boe_tv101wum_ll2_mode);
155+
}
156+
157+
static const struct drm_panel_funcs boe_tv101wum_ll2_panel_funcs = {
158+
.prepare = boe_tv101wum_ll2_prepare,
159+
.unprepare = boe_tv101wum_ll2_unprepare,
160+
.get_modes = boe_tv101wum_ll2_get_modes,
161+
};
162+
163+
static int boe_tv101wum_ll2_probe(struct mipi_dsi_device *dsi)
164+
{
165+
struct device *dev = &dsi->dev;
166+
struct boe_tv101wum_ll2 *ctx;
167+
int ret;
168+
169+
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
170+
if (!ctx)
171+
return -ENOMEM;
172+
173+
ret = devm_regulator_bulk_get_const(&dsi->dev,
174+
ARRAY_SIZE(boe_tv101wum_ll2_supplies),
175+
boe_tv101wum_ll2_supplies,
176+
&ctx->supplies);
177+
if (ret < 0)
178+
return ret;
179+
180+
ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
181+
if (IS_ERR(ctx->reset_gpio))
182+
return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
183+
"Failed to get reset-gpios\n");
184+
185+
ctx->dsi = dsi;
186+
mipi_dsi_set_drvdata(dsi, ctx);
187+
188+
dsi->lanes = 4;
189+
dsi->format = MIPI_DSI_FMT_RGB888;
190+
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
191+
MIPI_DSI_MODE_VIDEO_HSE;
192+
193+
drm_panel_init(&ctx->panel, dev, &boe_tv101wum_ll2_panel_funcs,
194+
DRM_MODE_CONNECTOR_DSI);
195+
ctx->panel.prepare_prev_first = true;
196+
197+
ret = drm_panel_of_backlight(&ctx->panel);
198+
if (ret)
199+
return dev_err_probe(dev, ret, "Failed to get backlight\n");
200+
201+
drm_panel_add(&ctx->panel);
202+
203+
ret = mipi_dsi_attach(dsi);
204+
if (ret < 0) {
205+
drm_panel_remove(&ctx->panel);
206+
return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
207+
}
208+
209+
return 0;
210+
}
211+
212+
static void boe_tv101wum_ll2_remove(struct mipi_dsi_device *dsi)
213+
{
214+
struct boe_tv101wum_ll2 *ctx = mipi_dsi_get_drvdata(dsi);
215+
int ret;
216+
217+
ret = mipi_dsi_detach(dsi);
218+
if (ret < 0)
219+
dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
220+
221+
drm_panel_remove(&ctx->panel);
222+
}
223+
224+
static const struct of_device_id boe_tv101wum_ll2_of_match[] = {
225+
{ .compatible = "boe,tv101wum-ll2" },
226+
{ /* sentinel */ }
227+
};
228+
MODULE_DEVICE_TABLE(of, boe_tv101wum_ll2_of_match);
229+
230+
static struct mipi_dsi_driver boe_tv101wum_ll2_driver = {
231+
.probe = boe_tv101wum_ll2_probe,
232+
.remove = boe_tv101wum_ll2_remove,
233+
.driver = {
234+
.name = "panel-boe-tv101wum_ll2",
235+
.of_match_table = boe_tv101wum_ll2_of_match,
236+
},
237+
};
238+
module_mipi_dsi_driver(boe_tv101wum_ll2_driver);
239+
240+
MODULE_DESCRIPTION("DRM driver for BOE TV101WUM-LL2 Panel");
241+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)