Linux Audio

Check our new training course

Linux kernel drivers training

May 6-19, 2025
Register
Loading...
Note: File does not exist in v5.14.15.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (c) 2023 Alexander Warnecke <awarnecke002@hotmail.com>
  4 * Copyright (c) 2023 Manuel Traut <manut@mecka.net>
  5 * Copyright (c) 2023 Dang Huynh <danct12@riseup.net>
  6 */
  7
  8#include <linux/delay.h>
  9#include <linux/gpio/consumer.h>
 10#include <linux/module.h>
 11#include <linux/of.h>
 12#include <linux/of_device.h>
 13#include <linux/regulator/consumer.h>
 14
 15#include <drm/drm_connector.h>
 16#include <drm/drm_mipi_dsi.h>
 17#include <drm/drm_modes.h>
 18#include <drm/drm_panel.h>
 19#include <drm/drm_probe_helper.h>
 20
 21struct boe_th101mb31ig002;
 22
 23struct panel_desc {
 24	const struct drm_display_mode *modes;
 25	unsigned long mode_flags;
 26	enum mipi_dsi_pixel_format format;
 27	int (*init)(struct boe_th101mb31ig002 *ctx);
 28	unsigned int lanes;
 29	bool lp11_before_reset;
 30	unsigned int vcioo_to_lp11_delay_ms;
 31	unsigned int lp11_to_reset_delay_ms;
 32	unsigned int backlight_off_to_display_off_delay_ms;
 33	unsigned int enter_sleep_to_reset_down_delay_ms;
 34	unsigned int power_off_delay_ms;
 35};
 36
 37struct boe_th101mb31ig002 {
 38	struct drm_panel panel;
 39
 40	struct mipi_dsi_device *dsi;
 41
 42	const struct panel_desc *desc;
 43
 44	struct regulator *power;
 45	struct gpio_desc *enable;
 46	struct gpio_desc *reset;
 47
 48	enum drm_panel_orientation orientation;
 49};
 50
 51static void boe_th101mb31ig002_reset(struct boe_th101mb31ig002 *ctx)
 52{
 53	gpiod_direction_output(ctx->reset, 0);
 54	usleep_range(10, 100);
 55	gpiod_direction_output(ctx->reset, 1);
 56	usleep_range(10, 100);
 57	gpiod_direction_output(ctx->reset, 0);
 58	usleep_range(5000, 6000);
 59}
 60
 61static int boe_th101mb31ig002_enable(struct boe_th101mb31ig002 *ctx)
 62{
 63	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
 64
 65	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0xab, 0xba);
 66	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0xba, 0xab);
 67	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x10, 0x01, 0x47, 0xff);
 68	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x0c, 0x14, 0x04, 0x50, 0x50, 0x14);
 69	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x56, 0x53, 0x00);
 70	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x33, 0x30, 0x04);
 71	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0xb0, 0x00, 0x00, 0x10, 0x00, 0x10,
 72					       0x00);
 73	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x05, 0x12, 0x29, 0x49, 0x48, 0x00,
 74					       0x00);
 75	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x7c, 0x65, 0x55, 0x49, 0x46, 0x36,
 76					       0x3b, 0x24, 0x3d, 0x3c, 0x3d, 0x5c, 0x4c,
 77					       0x55, 0x47, 0x46, 0x39, 0x26, 0x06, 0x7c,
 78					       0x65, 0x55, 0x49, 0x46, 0x36, 0x3b, 0x24,
 79					       0x3d, 0x3c, 0x3d, 0x5c, 0x4c, 0x55, 0x47,
 80					       0x46, 0x39, 0x26, 0x06);
 81	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0xff, 0x87, 0x12, 0x34, 0x44, 0x44,
 82					       0x44, 0x44, 0x98, 0x04, 0x98, 0x04, 0x0f,
 83					       0x00, 0x00, 0xc1);
 84	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x54, 0x94, 0x02, 0x85, 0x9f, 0x00,
 85					       0x7f, 0x00, 0x54, 0x00);
 86	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x17, 0x09, 0x08, 0x89, 0x08, 0x11,
 87					       0x22, 0x20, 0x44, 0xff, 0x18, 0x00);
 88	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc3, 0x86, 0x46, 0x05, 0x05, 0x1c, 0x1c,
 89					       0x1d, 0x1d, 0x02, 0x1f, 0x1f, 0x1e, 0x1e,
 90					       0x0f, 0x0f, 0x0d, 0x0d, 0x13, 0x13, 0x11,
 91					       0x11, 0x00);
 92	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc4, 0x07, 0x07, 0x04, 0x04, 0x1c, 0x1c,
 93					       0x1d, 0x1d, 0x02, 0x1f, 0x1f, 0x1e, 0x1e,
 94					       0x0e, 0x0e, 0x0c, 0x0c, 0x12, 0x12, 0x10,
 95					       0x10, 0x00);
 96	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc6, 0x2a, 0x2a);
 97	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc8, 0x21, 0x00, 0x31, 0x42, 0x34, 0x16);
 98	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, 0xcb, 0x43);
 99	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x0e, 0x4b, 0x4b, 0x20, 0x19, 0x6b,
100					       0x06, 0xb3);
101	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd2, 0xe3, 0x2b, 0x38, 0x00);
102	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd4, 0x00, 0x01, 0x00, 0x0e, 0x04, 0x44,
103					       0x08, 0x10, 0x00, 0x00, 0x00);
104	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff,
105					       0xff, 0xff);
106	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x12, 0x03, 0x20, 0x00, 0xff);
107	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x00);
108
109	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
110
111	mipi_dsi_msleep(&dsi_ctx, 120);
112
113	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
114
115	return dsi_ctx.accum_err;
116}
117
118static int starry_er88577_init_cmd(struct boe_th101mb31ig002 *ctx)
119{
120	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
121
122	msleep(70);
123
124	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0xab, 0xba);
125	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0xba, 0xab);
126	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb1, 0x10, 0x01, 0x47, 0xff);
127	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x0c, 0x14, 0x04, 0x50, 0x50, 0x14);
128	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x56, 0x53, 0x00);
129	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb4, 0x33, 0x30, 0x04);
130	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0xb0, 0x00, 0x00, 0x10, 0x00, 0x10,
131					       0x00);
132	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb8, 0x05, 0x12, 0x29, 0x49, 0x40);
133	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x7c, 0x61, 0x4f, 0x42, 0x3e, 0x2d,
134					       0x31, 0x1a, 0x33, 0x33, 0x33, 0x52, 0x40,
135					       0x47, 0x38, 0x34, 0x26, 0x0e, 0x06, 0x7c,
136					       0x61, 0x4f, 0x42, 0x3e, 0x2d, 0x31, 0x1a,
137					       0x33, 0x33, 0x33, 0x52, 0x40, 0x47, 0x38,
138					       0x34, 0x26, 0x0e, 0x06);
139	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc0, 0xcc, 0x76, 0x12, 0x34, 0x44, 0x44,
140					       0x44, 0x44, 0x98, 0x04, 0x98, 0x04, 0x0f,
141					       0x00, 0x00, 0xc1);
142	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc1, 0x54, 0x94, 0x02, 0x85, 0x9f, 0x00,
143					       0x6f, 0x00, 0x54, 0x00);
144	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x17, 0x09, 0x08, 0x89, 0x08, 0x11,
145					       0x22, 0x20, 0x44, 0xff, 0x18, 0x00);
146	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc3, 0x87, 0x47, 0x05, 0x05, 0x1c, 0x1c,
147					       0x1d, 0x1d, 0x02, 0x1e, 0x1e, 0x1f, 0x1f,
148					       0x0f, 0x0f, 0x0d, 0x0d, 0x13, 0x13, 0x11,
149					       0x11, 0x24);
150	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc4, 0x06, 0x06, 0x04, 0x04, 0x1c, 0x1c,
151					       0x1d, 0x1d, 0x02, 0x1e, 0x1e, 0x1f, 0x1f,
152					       0x0e, 0x0e, 0x0c, 0x0c, 0x12, 0x12, 0x10,
153					       0x10, 0x24);
154	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc8, 0x21, 0x00, 0x31, 0x42, 0x34, 0x16);
155	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xca, 0xcb, 0x43);
156	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x0e, 0x4b, 0x4b, 0x20, 0x19, 0x6b,
157					       0x06, 0xb3);
158	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd1, 0x40, 0x0d, 0xff, 0x0f);
159	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd2, 0xe3, 0x2b, 0x38, 0x08);
160	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd3, 0x00, 0x00, 0x00, 0x00,
161					       0x00, 0x33, 0x20, 0x3a, 0xd5, 0x86, 0xf3);
162	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xd4, 0x00, 0x01, 0x00, 0x0e, 0x04, 0x44,
163					       0x08, 0x10, 0x00, 0x00, 0x00);
164	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe6, 0x80, 0x09, 0xff, 0xff, 0xff, 0xff,
165					       0xff, 0xff);
166	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x12, 0x03, 0x20, 0x00, 0xff);
167	mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0x00);
168
169	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
170
171	mipi_dsi_msleep(&dsi_ctx, 120);
172
173	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
174
175	mipi_dsi_msleep(&dsi_ctx, 20);
176
177	return dsi_ctx.accum_err;
178}
179
180static int boe_th101mb31ig002_disable(struct drm_panel *panel)
181{
182	struct boe_th101mb31ig002 *ctx = container_of(panel,
183						      struct boe_th101mb31ig002,
184						      panel);
185	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
186
187	if (ctx->desc->backlight_off_to_display_off_delay_ms)
188		mipi_dsi_msleep(&dsi_ctx, ctx->desc->backlight_off_to_display_off_delay_ms);
189
190	mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
191
192	mipi_dsi_msleep(&dsi_ctx, 120);
193
194	mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
195
196	if (ctx->desc->enter_sleep_to_reset_down_delay_ms)
197		mipi_dsi_msleep(&dsi_ctx, ctx->desc->enter_sleep_to_reset_down_delay_ms);
198
199	return dsi_ctx.accum_err;
200}
201
202static int boe_th101mb31ig002_unprepare(struct drm_panel *panel)
203{
204	struct boe_th101mb31ig002 *ctx = container_of(panel,
205						      struct boe_th101mb31ig002,
206						      panel);
207
208	gpiod_set_value_cansleep(ctx->reset, 1);
209	gpiod_set_value_cansleep(ctx->enable, 0);
210	regulator_disable(ctx->power);
211
212	if (ctx->desc->power_off_delay_ms)
213		msleep(ctx->desc->power_off_delay_ms);
214
215	return 0;
216}
217
218static int boe_th101mb31ig002_prepare(struct drm_panel *panel)
219{
220	struct boe_th101mb31ig002 *ctx = container_of(panel,
221						      struct boe_th101mb31ig002,
222						      panel);
223	struct device *dev = &ctx->dsi->dev;
224	int ret;
225
226	ret = regulator_enable(ctx->power);
227	if (ret) {
228		dev_err(dev, "Failed to enable power supply: %d\n", ret);
229		return ret;
230	}
231
232	if (ctx->desc->vcioo_to_lp11_delay_ms)
233		msleep(ctx->desc->vcioo_to_lp11_delay_ms);
234
235	if (ctx->desc->lp11_before_reset) {
236		ret = mipi_dsi_dcs_nop(ctx->dsi);
237		if (ret)
238			return ret;
239	}
240
241	if (ctx->desc->lp11_to_reset_delay_ms)
242		msleep(ctx->desc->lp11_to_reset_delay_ms);
243
244	gpiod_set_value_cansleep(ctx->enable, 1);
245	msleep(50);
246	boe_th101mb31ig002_reset(ctx);
247
248	ret = ctx->desc->init(ctx);
249	if (ret)
250		return ret;
251
252	return 0;
253}
254
255static const struct drm_display_mode boe_th101mb31ig002_default_mode = {
256	.clock		= 73500,
257	.hdisplay	= 800,
258	.hsync_start	= 800 + 64,
259	.hsync_end	= 800 + 64 + 16,
260	.htotal		= 800 + 64 + 16 + 64,
261	.vdisplay	= 1280,
262	.vsync_start	= 1280 + 2,
263	.vsync_end	= 1280 + 2 + 4,
264	.vtotal		= 1280 + 2 + 4 + 12,
265	.width_mm	= 135,
266	.height_mm	= 216,
267	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
268};
269
270static const struct panel_desc boe_th101mb31ig002_desc = {
271	.modes = &boe_th101mb31ig002_default_mode,
272	.lanes = 4,
273	.format = MIPI_DSI_FMT_RGB888,
274	.mode_flags = MIPI_DSI_MODE_VIDEO_BURST |
275			  MIPI_DSI_MODE_NO_EOT_PACKET |
276			  MIPI_DSI_MODE_LPM,
277	.init = boe_th101mb31ig002_enable,
278};
279
280static const struct drm_display_mode starry_er88577_default_mode = {
281	.clock	= (800 + 25 + 25 + 25) * (1280 + 20 + 4 + 12) * 60 / 1000,
282	.hdisplay = 800,
283	.hsync_start = 800 + 25,
284	.hsync_end = 800 + 25 + 25,
285	.htotal = 800 + 25 + 25 + 25,
286	.vdisplay = 1280,
287	.vsync_start = 1280 + 20,
288	.vsync_end = 1280 + 20 + 4,
289	.vtotal = 1280 + 20 + 4 + 12,
290	.width_mm = 135,
291	.height_mm = 216,
292	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
293};
294
295static const struct panel_desc starry_er88577_desc = {
296	.modes = &starry_er88577_default_mode,
297	.lanes = 4,
298	.format = MIPI_DSI_FMT_RGB888,
299	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
300		      MIPI_DSI_MODE_LPM,
301	.init = starry_er88577_init_cmd,
302	.lp11_before_reset = true,
303	.vcioo_to_lp11_delay_ms = 5,
304	.lp11_to_reset_delay_ms = 50,
305	.backlight_off_to_display_off_delay_ms = 100,
306	.enter_sleep_to_reset_down_delay_ms = 100,
307	.power_off_delay_ms = 1000,
308};
309
310static int boe_th101mb31ig002_get_modes(struct drm_panel *panel,
311					struct drm_connector *connector)
312{
313	struct boe_th101mb31ig002 *ctx = container_of(panel,
314						      struct boe_th101mb31ig002,
315						      panel);
316	const struct drm_display_mode *desc_mode = ctx->desc->modes;
317
318	connector->display_info.bpc = 8;
319	/*
320	 * TODO: Remove once all drm drivers call
321	 * drm_connector_set_orientation_from_panel()
322	 */
323	drm_connector_set_panel_orientation(connector, ctx->orientation);
324
325	return drm_connector_helper_get_modes_fixed(connector, desc_mode);
326}
327
328static enum drm_panel_orientation
329boe_th101mb31ig002_get_orientation(struct drm_panel *panel)
330{
331	struct boe_th101mb31ig002 *ctx = container_of(panel,
332						      struct boe_th101mb31ig002,
333						      panel);
334
335	return ctx->orientation;
336}
337
338static const struct drm_panel_funcs boe_th101mb31ig002_funcs = {
339	.prepare = boe_th101mb31ig002_prepare,
340	.unprepare = boe_th101mb31ig002_unprepare,
341	.disable = boe_th101mb31ig002_disable,
342	.get_modes = boe_th101mb31ig002_get_modes,
343	.get_orientation = boe_th101mb31ig002_get_orientation,
344};
345
346static int boe_th101mb31ig002_dsi_probe(struct mipi_dsi_device *dsi)
347{
348	struct boe_th101mb31ig002 *ctx;
349	const struct panel_desc *desc;
350	int ret;
351
352	ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
353	if (!ctx)
354		return -ENOMEM;
355
356	mipi_dsi_set_drvdata(dsi, ctx);
357	ctx->dsi = dsi;
358
359	desc = of_device_get_match_data(&dsi->dev);
360	dsi->lanes = desc->lanes;
361	dsi->format = desc->format;
362	dsi->mode_flags = desc->mode_flags;
363	ctx->desc = desc;
364
365	ctx->power = devm_regulator_get(&dsi->dev, "power");
366	if (IS_ERR(ctx->power))
367		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->power),
368				     "Failed to get power regulator\n");
369
370	ctx->enable = devm_gpiod_get(&dsi->dev, "enable", GPIOD_OUT_LOW);
371	if (IS_ERR(ctx->enable))
372		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->enable),
373				     "Failed to get enable GPIO\n");
374
375	ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_HIGH);
376	if (IS_ERR(ctx->reset))
377		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset),
378				     "Failed to get reset GPIO\n");
379
380	ret = of_drm_get_panel_orientation(dsi->dev.of_node,
381					   &ctx->orientation);
382	if (ret)
383		return dev_err_probe(&dsi->dev, ret,
384				     "Failed to get orientation\n");
385
386	drm_panel_init(&ctx->panel, &dsi->dev, &boe_th101mb31ig002_funcs,
387		       DRM_MODE_CONNECTOR_DSI);
388
389	ret = drm_panel_of_backlight(&ctx->panel);
390	if (ret)
391		return ret;
392
393	drm_panel_add(&ctx->panel);
394
395	ret = mipi_dsi_attach(dsi);
396	if (ret < 0) {
397		dev_err_probe(&dsi->dev, ret,
398			      "Failed to attach panel to DSI host\n");
399		drm_panel_remove(&ctx->panel);
400		return ret;
401	}
402
403	return 0;
404}
405
406static void boe_th101mb31ig002_dsi_remove(struct mipi_dsi_device *dsi)
407{
408	struct boe_th101mb31ig002 *ctx = mipi_dsi_get_drvdata(dsi);
409
410	mipi_dsi_detach(dsi);
411	drm_panel_remove(&ctx->panel);
412}
413
414static const struct of_device_id boe_th101mb31ig002_of_match[] = {
415	{
416		.compatible = "boe,th101mb31ig002-28a",
417		.data = &boe_th101mb31ig002_desc
418	},
419	{
420		.compatible = "starry,er88577",
421		.data = &starry_er88577_desc
422	},
423	{ /* sentinel */ }
424};
425MODULE_DEVICE_TABLE(of, boe_th101mb31ig002_of_match);
426
427static struct mipi_dsi_driver boe_th101mb31ig002_driver = {
428	.driver = {
429		.name = "boe-th101mb31ig002-28a",
430		.of_match_table = boe_th101mb31ig002_of_match,
431	},
432	.probe = boe_th101mb31ig002_dsi_probe,
433	.remove = boe_th101mb31ig002_dsi_remove,
434};
435module_mipi_dsi_driver(boe_th101mb31ig002_driver);
436
437MODULE_AUTHOR("Alexander Warnecke <awarnecke002@hotmail.com>");
438MODULE_DESCRIPTION("BOE TH101MB31IG002-28A MIPI-DSI LCD panel");
439MODULE_LICENSE("GPL");