Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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
 20struct boe_th101mb31ig002 {
 21	struct drm_panel panel;
 22
 23	struct mipi_dsi_device *dsi;
 24
 25	struct regulator *power;
 26	struct gpio_desc *enable;
 27	struct gpio_desc *reset;
 28
 29	enum drm_panel_orientation orientation;
 30};
 31
 32static void boe_th101mb31ig002_reset(struct boe_th101mb31ig002 *ctx)
 33{
 34	gpiod_direction_output(ctx->reset, 0);
 35	usleep_range(10, 100);
 36	gpiod_direction_output(ctx->reset, 1);
 37	usleep_range(10, 100);
 38	gpiod_direction_output(ctx->reset, 0);
 39	usleep_range(5000, 6000);
 40}
 41
 42static int boe_th101mb31ig002_enable(struct drm_panel *panel)
 43{
 44	struct boe_th101mb31ig002 *ctx = container_of(panel,
 45						      struct boe_th101mb31ig002,
 46						      panel);
 47	struct mipi_dsi_device *dsi = ctx->dsi;
 48	struct device *dev = &dsi->dev;
 49	int ret;
 50
 51	mipi_dsi_dcs_write_seq(dsi, 0xE0, 0xAB, 0xBA);
 52	mipi_dsi_dcs_write_seq(dsi, 0xE1, 0xBA, 0xAB);
 53	mipi_dsi_dcs_write_seq(dsi, 0xB1, 0x10, 0x01, 0x47, 0xFF);
 54	mipi_dsi_dcs_write_seq(dsi, 0xB2, 0x0C, 0x14, 0x04, 0x50, 0x50, 0x14);
 55	mipi_dsi_dcs_write_seq(dsi, 0xB3, 0x56, 0x53, 0x00);
 56	mipi_dsi_dcs_write_seq(dsi, 0xB4, 0x33, 0x30, 0x04);
 57	mipi_dsi_dcs_write_seq(dsi, 0xB6, 0xB0, 0x00, 0x00, 0x10, 0x00, 0x10,
 58				    0x00);
 59	mipi_dsi_dcs_write_seq(dsi, 0xB8, 0x05, 0x12, 0x29, 0x49, 0x48, 0x00,
 60				    0x00);
 61	mipi_dsi_dcs_write_seq(dsi, 0xB9, 0x7C, 0x65, 0x55, 0x49, 0x46, 0x36,
 62				    0x3B, 0x24, 0x3D, 0x3C, 0x3D, 0x5C, 0x4C,
 63				    0x55, 0x47, 0x46, 0x39, 0x26, 0x06, 0x7C,
 64				    0x65, 0x55, 0x49, 0x46, 0x36, 0x3B, 0x24,
 65				    0x3D, 0x3C, 0x3D, 0x5C, 0x4C, 0x55, 0x47,
 66				    0x46, 0x39, 0x26, 0x06);
 67	mipi_dsi_dcs_write_seq(dsi, 0x00, 0xFF, 0x87, 0x12, 0x34, 0x44, 0x44,
 68				    0x44, 0x44, 0x98, 0x04, 0x98, 0x04, 0x0F,
 69				    0x00, 0x00, 0xC1);
 70	mipi_dsi_dcs_write_seq(dsi, 0xC1, 0x54, 0x94, 0x02, 0x85, 0x9F, 0x00,
 71				    0x7F, 0x00, 0x54, 0x00);
 72	mipi_dsi_dcs_write_seq(dsi, 0xC2, 0x17, 0x09, 0x08, 0x89, 0x08, 0x11,
 73				    0x22, 0x20, 0x44, 0xFF, 0x18, 0x00);
 74	mipi_dsi_dcs_write_seq(dsi, 0xC3, 0x86, 0x46, 0x05, 0x05, 0x1C, 0x1C,
 75				    0x1D, 0x1D, 0x02, 0x1F, 0x1F, 0x1E, 0x1E,
 76				    0x0F, 0x0F, 0x0D, 0x0D, 0x13, 0x13, 0x11,
 77				    0x11, 0x00);
 78	mipi_dsi_dcs_write_seq(dsi, 0xC4, 0x07, 0x07, 0x04, 0x04, 0x1C, 0x1C,
 79				    0x1D, 0x1D, 0x02, 0x1F, 0x1F, 0x1E, 0x1E,
 80				    0x0E, 0x0E, 0x0C, 0x0C, 0x12, 0x12, 0x10,
 81				    0x10, 0x00);
 82	mipi_dsi_dcs_write_seq(dsi, 0xC6, 0x2A, 0x2A);
 83	mipi_dsi_dcs_write_seq(dsi, 0xC8, 0x21, 0x00, 0x31, 0x42, 0x34, 0x16);
 84	mipi_dsi_dcs_write_seq(dsi, 0xCA, 0xCB, 0x43);
 85	mipi_dsi_dcs_write_seq(dsi, 0xCD, 0x0E, 0x4B, 0x4B, 0x20, 0x19, 0x6B,
 86				    0x06, 0xB3);
 87	mipi_dsi_dcs_write_seq(dsi, 0xD2, 0xE3, 0x2B, 0x38, 0x00);
 88	mipi_dsi_dcs_write_seq(dsi, 0xD4, 0x00, 0x01, 0x00, 0x0E, 0x04, 0x44,
 89				    0x08, 0x10, 0x00, 0x00, 0x00);
 90	mipi_dsi_dcs_write_seq(dsi, 0xE6, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
 91				    0xFF, 0xFF);
 92	mipi_dsi_dcs_write_seq(dsi, 0xF0, 0x12, 0x03, 0x20, 0x00, 0xFF);
 93	mipi_dsi_dcs_write_seq(dsi, 0xF3, 0x00);
 94
 95	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
 96	if (ret < 0) {
 97		dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
 98		return ret;
 99	}
100
101	msleep(120);
102
103	ret = mipi_dsi_dcs_set_display_on(dsi);
104	if (ret < 0) {
105		dev_err(dev, "Failed to set panel on: %d\n", ret);
106		return ret;
107	}
108
109	return 0;
110}
111
112static int boe_th101mb31ig002_disable(struct drm_panel *panel)
113{
114	struct boe_th101mb31ig002 *ctx = container_of(panel,
115						      struct boe_th101mb31ig002,
116						      panel);
117	struct mipi_dsi_device *dsi = ctx->dsi;
118	struct device *dev = &dsi->dev;
119	int ret;
120
121	ret = mipi_dsi_dcs_set_display_off(dsi);
122	if (ret < 0)
123		dev_err(dev, "Failed to set panel off: %d\n", ret);
124
125	msleep(120);
126
127	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
128	if (ret < 0)
129		dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
130
131	return 0;
132}
133
134static int boe_th101mb31ig002_unprepare(struct drm_panel *panel)
135{
136	struct boe_th101mb31ig002 *ctx = container_of(panel,
137						      struct boe_th101mb31ig002,
138						      panel);
139
140	gpiod_set_value_cansleep(ctx->reset, 1);
141	gpiod_set_value_cansleep(ctx->enable, 0);
142	regulator_disable(ctx->power);
143
144	return 0;
145}
146
147static int boe_th101mb31ig002_prepare(struct drm_panel *panel)
148{
149	struct boe_th101mb31ig002 *ctx = container_of(panel,
150						      struct boe_th101mb31ig002,
151						      panel);
152	struct device *dev = &ctx->dsi->dev;
153	int ret;
154
155	ret = regulator_enable(ctx->power);
156	if (ret) {
157		dev_err(dev, "Failed to enable power supply: %d\n", ret);
158		return ret;
159	}
160
161	gpiod_set_value_cansleep(ctx->enable, 1);
162	msleep(50);
163	boe_th101mb31ig002_reset(ctx);
164	boe_th101mb31ig002_enable(panel);
165
166	return 0;
167}
168
169static const struct drm_display_mode boe_th101mb31ig002_default_mode = {
170	.clock		= 73500,
171	.hdisplay	= 800,
172	.hsync_start	= 800 + 64,
173	.hsync_end	= 800 + 64 + 16,
174	.htotal		= 800 + 64 + 16 + 64,
175	.vdisplay	= 1280,
176	.vsync_start	= 1280 + 2,
177	.vsync_end	= 1280 + 2 + 4,
178	.vtotal		= 1280 + 2 + 4 + 12,
179	.width_mm	= 135,
180	.height_mm	= 216,
181	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
182};
183
184static int boe_th101mb31ig002_get_modes(struct drm_panel *panel,
185					struct drm_connector *connector)
186{
187	struct boe_th101mb31ig002 *ctx = container_of(panel,
188						      struct boe_th101mb31ig002,
189						      panel);
190	struct drm_display_mode *mode;
191
192	mode = drm_mode_duplicate(connector->dev,
193				  &boe_th101mb31ig002_default_mode);
194	if (!mode) {
195		dev_err(panel->dev, "Failed to add mode %ux%u@%u\n",
196			boe_th101mb31ig002_default_mode.hdisplay,
197			boe_th101mb31ig002_default_mode.vdisplay,
198			drm_mode_vrefresh(&boe_th101mb31ig002_default_mode));
199		return -ENOMEM;
200	}
201
202	drm_mode_set_name(mode);
203
204	connector->display_info.bpc = 8;
205	connector->display_info.width_mm = mode->width_mm;
206	connector->display_info.height_mm = mode->height_mm;
207
208	/*
209	 * TODO: Remove once all drm drivers call
210	 * drm_connector_set_orientation_from_panel()
211	 */
212	drm_connector_set_panel_orientation(connector, ctx->orientation);
213
214	drm_mode_probed_add(connector, mode);
215
216	return 1;
217}
218
219static enum drm_panel_orientation
220boe_th101mb31ig002_get_orientation(struct drm_panel *panel)
221{
222	struct boe_th101mb31ig002 *ctx = container_of(panel,
223						      struct boe_th101mb31ig002,
224						      panel);
225
226	return ctx->orientation;
227}
228
229static const struct drm_panel_funcs boe_th101mb31ig002_funcs = {
230	.prepare = boe_th101mb31ig002_prepare,
231	.unprepare = boe_th101mb31ig002_unprepare,
232	.disable = boe_th101mb31ig002_disable,
233	.get_modes = boe_th101mb31ig002_get_modes,
234	.get_orientation = boe_th101mb31ig002_get_orientation,
235};
236
237static int boe_th101mb31ig002_dsi_probe(struct mipi_dsi_device *dsi)
238{
239	struct boe_th101mb31ig002 *ctx;
240	int ret;
241
242	ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
243	if (!ctx)
244		return -ENOMEM;
245
246	mipi_dsi_set_drvdata(dsi, ctx);
247	ctx->dsi = dsi;
248
249	dsi->lanes = 4;
250	dsi->format = MIPI_DSI_FMT_RGB888;
251	dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST |
252			  MIPI_DSI_MODE_NO_EOT_PACKET |
253			  MIPI_DSI_MODE_LPM;
254
255	ctx->power = devm_regulator_get(&dsi->dev, "power");
256	if (IS_ERR(ctx->power))
257		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->power),
258				     "Failed to get power regulator\n");
259
260	ctx->enable = devm_gpiod_get(&dsi->dev, "enable", GPIOD_OUT_LOW);
261	if (IS_ERR(ctx->enable))
262		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->enable),
263				     "Failed to get enable GPIO\n");
264
265	ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_HIGH);
266	if (IS_ERR(ctx->reset))
267		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset),
268				     "Failed to get reset GPIO\n");
269
270	ret = of_drm_get_panel_orientation(dsi->dev.of_node,
271					   &ctx->orientation);
272	if (ret)
273		return dev_err_probe(&dsi->dev, ret,
274				     "Failed to get orientation\n");
275
276	drm_panel_init(&ctx->panel, &dsi->dev, &boe_th101mb31ig002_funcs,
277		       DRM_MODE_CONNECTOR_DSI);
278
279	ret = drm_panel_of_backlight(&ctx->panel);
280	if (ret)
281		return ret;
282
283	drm_panel_add(&ctx->panel);
284
285	ret = mipi_dsi_attach(dsi);
286	if (ret < 0) {
287		dev_err_probe(&dsi->dev, ret,
288			      "Failed to attach panel to DSI host\n");
289		drm_panel_remove(&ctx->panel);
290		return ret;
291	}
292
293	return 0;
294}
295
296static void boe_th101mb31ig002_dsi_remove(struct mipi_dsi_device *dsi)
297{
298	struct boe_th101mb31ig002 *ctx = mipi_dsi_get_drvdata(dsi);
299
300	mipi_dsi_detach(dsi);
301	drm_panel_remove(&ctx->panel);
302}
303
304static const struct of_device_id boe_th101mb31ig002_of_match[] = {
305	{ .compatible = "boe,th101mb31ig002-28a", },
306	{ /* sentinel */ }
307};
308MODULE_DEVICE_TABLE(of, boe_th101mb31ig002_of_match);
309
310static struct mipi_dsi_driver boe_th101mb31ig002_driver = {
311	.driver = {
312		.name = "boe-th101mb31ig002-28a",
313		.of_match_table = boe_th101mb31ig002_of_match,
314	},
315	.probe = boe_th101mb31ig002_dsi_probe,
316	.remove = boe_th101mb31ig002_dsi_remove,
317};
318module_mipi_dsi_driver(boe_th101mb31ig002_driver);
319
320MODULE_AUTHOR("Alexander Warnecke <awarnecke002@hotmail.com>");
321MODULE_DESCRIPTION("BOE TH101MB31IG002-28A MIPI-DSI LCD panel");
322MODULE_LICENSE("GPL");