1caab277bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2086ceb6bSWerner Johansson /*
3086ceb6bSWerner Johansson  * Copyright (C) 2015 Red Hat
4086ceb6bSWerner Johansson  * Copyright (C) 2015 Sony Mobile Communications Inc.
5086ceb6bSWerner Johansson  * Author: Werner Johansson <werner.johansson@sonymobile.com>
6086ceb6bSWerner Johansson  *
7086ceb6bSWerner Johansson  * Based on AUO panel driver by Rob Clark <robdclark@gmail.com>
8086ceb6bSWerner Johansson  */
9086ceb6bSWerner Johansson 
10cb23eae3SSam Ravnborg #include <linux/delay.h>
11086ceb6bSWerner Johansson #include <linux/module.h>
12086ceb6bSWerner Johansson #include <linux/of.h>
13086ceb6bSWerner Johansson #include <linux/regulator/consumer.h>
14086ceb6bSWerner Johansson 
15cb23eae3SSam Ravnborg #include <video/mipi_display.h>
16cb23eae3SSam Ravnborg 
17086ceb6bSWerner Johansson #include <drm/drm_crtc.h>
18cb23eae3SSam Ravnborg #include <drm/drm_device.h>
19086ceb6bSWerner Johansson #include <drm/drm_mipi_dsi.h>
20086ceb6bSWerner Johansson #include <drm/drm_panel.h>
21086ceb6bSWerner Johansson 
22086ceb6bSWerner Johansson /*
23086ceb6bSWerner Johansson  * When power is turned off to this panel a minimum off time of 500ms has to be
24086ceb6bSWerner Johansson  * observed before powering back on as there's no external reset pin. Keep
25086ceb6bSWerner Johansson  * track of earliest wakeup time and delay subsequent prepare call accordingly
26086ceb6bSWerner Johansson  */
27086ceb6bSWerner Johansson #define MIN_POFF_MS (500)
28086ceb6bSWerner Johansson 
29086ceb6bSWerner Johansson struct wuxga_nt_panel {
30086ceb6bSWerner Johansson 	struct drm_panel base;
31086ceb6bSWerner Johansson 	struct mipi_dsi_device *dsi;
32086ceb6bSWerner Johansson 
33086ceb6bSWerner Johansson 	struct regulator *supply;
34086ceb6bSWerner Johansson 
35086ceb6bSWerner Johansson 	bool prepared;
36086ceb6bSWerner Johansson 	bool enabled;
37086ceb6bSWerner Johansson 
38086ceb6bSWerner Johansson 	ktime_t earliest_wake;
39086ceb6bSWerner Johansson 
40086ceb6bSWerner Johansson 	const struct drm_display_mode *mode;
41086ceb6bSWerner Johansson };
42086ceb6bSWerner Johansson 
to_wuxga_nt_panel(struct drm_panel * panel)43086ceb6bSWerner Johansson static inline struct wuxga_nt_panel *to_wuxga_nt_panel(struct drm_panel *panel)
44086ceb6bSWerner Johansson {
45086ceb6bSWerner Johansson 	return container_of(panel, struct wuxga_nt_panel, base);
46086ceb6bSWerner Johansson }
47086ceb6bSWerner Johansson 
wuxga_nt_panel_on(struct wuxga_nt_panel * wuxga_nt)48086ceb6bSWerner Johansson static int wuxga_nt_panel_on(struct wuxga_nt_panel *wuxga_nt)
49086ceb6bSWerner Johansson {
504ac51116SSean Paul 	return mipi_dsi_turn_on_peripheral(wuxga_nt->dsi);
51086ceb6bSWerner Johansson }
52086ceb6bSWerner Johansson 
wuxga_nt_panel_disable(struct drm_panel * panel)53086ceb6bSWerner Johansson static int wuxga_nt_panel_disable(struct drm_panel *panel)
54086ceb6bSWerner Johansson {
55086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel);
564ac51116SSean Paul 	int mipi_ret, bl_ret = 0;
57086ceb6bSWerner Johansson 
58086ceb6bSWerner Johansson 	if (!wuxga_nt->enabled)
59086ceb6bSWerner Johansson 		return 0;
60086ceb6bSWerner Johansson 
614ac51116SSean Paul 	mipi_ret = mipi_dsi_shutdown_peripheral(wuxga_nt->dsi);
62086ceb6bSWerner Johansson 
63086ceb6bSWerner Johansson 	wuxga_nt->enabled = false;
64086ceb6bSWerner Johansson 
654ac51116SSean Paul 	return mipi_ret ? mipi_ret : bl_ret;
66086ceb6bSWerner Johansson }
67086ceb6bSWerner Johansson 
wuxga_nt_panel_unprepare(struct drm_panel * panel)68086ceb6bSWerner Johansson static int wuxga_nt_panel_unprepare(struct drm_panel *panel)
69086ceb6bSWerner Johansson {
70086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel);
71086ceb6bSWerner Johansson 
72086ceb6bSWerner Johansson 	if (!wuxga_nt->prepared)
73086ceb6bSWerner Johansson 		return 0;
74086ceb6bSWerner Johansson 
75086ceb6bSWerner Johansson 	regulator_disable(wuxga_nt->supply);
76086ceb6bSWerner Johansson 	wuxga_nt->earliest_wake = ktime_add_ms(ktime_get_real(), MIN_POFF_MS);
77086ceb6bSWerner Johansson 	wuxga_nt->prepared = false;
78086ceb6bSWerner Johansson 
79086ceb6bSWerner Johansson 	return 0;
80086ceb6bSWerner Johansson }
81086ceb6bSWerner Johansson 
wuxga_nt_panel_prepare(struct drm_panel * panel)82086ceb6bSWerner Johansson static int wuxga_nt_panel_prepare(struct drm_panel *panel)
83086ceb6bSWerner Johansson {
84086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel);
85086ceb6bSWerner Johansson 	int ret;
86086ceb6bSWerner Johansson 	s64 enablewait;
87086ceb6bSWerner Johansson 
88086ceb6bSWerner Johansson 	if (wuxga_nt->prepared)
89086ceb6bSWerner Johansson 		return 0;
90086ceb6bSWerner Johansson 
91086ceb6bSWerner Johansson 	/*
92086ceb6bSWerner Johansson 	 * If the user re-enabled the panel before the required off-time then
93086ceb6bSWerner Johansson 	 * we need to wait the remaining period before re-enabling regulator
94086ceb6bSWerner Johansson 	 */
95086ceb6bSWerner Johansson 	enablewait = ktime_ms_delta(wuxga_nt->earliest_wake, ktime_get_real());
96086ceb6bSWerner Johansson 
97086ceb6bSWerner Johansson 	/* Sanity check, this should never happen */
98086ceb6bSWerner Johansson 	if (enablewait > MIN_POFF_MS)
99086ceb6bSWerner Johansson 		enablewait = MIN_POFF_MS;
100086ceb6bSWerner Johansson 
101086ceb6bSWerner Johansson 	if (enablewait > 0)
102086ceb6bSWerner Johansson 		msleep(enablewait);
103086ceb6bSWerner Johansson 
104086ceb6bSWerner Johansson 	ret = regulator_enable(wuxga_nt->supply);
105086ceb6bSWerner Johansson 	if (ret < 0)
106086ceb6bSWerner Johansson 		return ret;
107086ceb6bSWerner Johansson 
108086ceb6bSWerner Johansson 	/*
109086ceb6bSWerner Johansson 	 * A minimum delay of 250ms is required after power-up until commands
110086ceb6bSWerner Johansson 	 * can be sent
111086ceb6bSWerner Johansson 	 */
112086ceb6bSWerner Johansson 	msleep(250);
113086ceb6bSWerner Johansson 
114086ceb6bSWerner Johansson 	ret = wuxga_nt_panel_on(wuxga_nt);
115086ceb6bSWerner Johansson 	if (ret < 0) {
116086ceb6bSWerner Johansson 		dev_err(panel->dev, "failed to set panel on: %d\n", ret);
117086ceb6bSWerner Johansson 		goto poweroff;
118086ceb6bSWerner Johansson 	}
119086ceb6bSWerner Johansson 
120086ceb6bSWerner Johansson 	wuxga_nt->prepared = true;
121086ceb6bSWerner Johansson 
122086ceb6bSWerner Johansson 	return 0;
123086ceb6bSWerner Johansson 
124086ceb6bSWerner Johansson poweroff:
125086ceb6bSWerner Johansson 	regulator_disable(wuxga_nt->supply);
126086ceb6bSWerner Johansson 
127086ceb6bSWerner Johansson 	return ret;
128086ceb6bSWerner Johansson }
129086ceb6bSWerner Johansson 
wuxga_nt_panel_enable(struct drm_panel * panel)130086ceb6bSWerner Johansson static int wuxga_nt_panel_enable(struct drm_panel *panel)
131086ceb6bSWerner Johansson {
132086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel);
133086ceb6bSWerner Johansson 
134086ceb6bSWerner Johansson 	if (wuxga_nt->enabled)
135086ceb6bSWerner Johansson 		return 0;
136086ceb6bSWerner Johansson 
137086ceb6bSWerner Johansson 	wuxga_nt->enabled = true;
138086ceb6bSWerner Johansson 
139086ceb6bSWerner Johansson 	return 0;
140086ceb6bSWerner Johansson }
141086ceb6bSWerner Johansson 
142086ceb6bSWerner Johansson static const struct drm_display_mode default_mode = {
143086ceb6bSWerner Johansson 	.clock = 164402,
144086ceb6bSWerner Johansson 	.hdisplay = 1920,
145086ceb6bSWerner Johansson 	.hsync_start = 1920 + 152,
146086ceb6bSWerner Johansson 	.hsync_end = 1920 + 152 + 52,
147086ceb6bSWerner Johansson 	.htotal = 1920 + 152 + 52 + 20,
148086ceb6bSWerner Johansson 	.vdisplay = 1200,
149086ceb6bSWerner Johansson 	.vsync_start = 1200 + 24,
150086ceb6bSWerner Johansson 	.vsync_end = 1200 + 24 + 6,
151086ceb6bSWerner Johansson 	.vtotal = 1200 + 24 + 6 + 48,
152086ceb6bSWerner Johansson };
153086ceb6bSWerner Johansson 
wuxga_nt_panel_get_modes(struct drm_panel * panel,struct drm_connector * connector)1540ce8ddd8SSam Ravnborg static int wuxga_nt_panel_get_modes(struct drm_panel *panel,
1550ce8ddd8SSam Ravnborg 				    struct drm_connector *connector)
156086ceb6bSWerner Johansson {
157086ceb6bSWerner Johansson 	struct drm_display_mode *mode;
158086ceb6bSWerner Johansson 
159aa6c4364SSam Ravnborg 	mode = drm_mode_duplicate(connector->dev, &default_mode);
160086ceb6bSWerner Johansson 	if (!mode) {
161aa6c4364SSam Ravnborg 		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
162086ceb6bSWerner Johansson 			default_mode.hdisplay, default_mode.vdisplay,
1630425662fSVille Syrjälä 			drm_mode_vrefresh(&default_mode));
164086ceb6bSWerner Johansson 		return -ENOMEM;
165086ceb6bSWerner Johansson 	}
166086ceb6bSWerner Johansson 
167086ceb6bSWerner Johansson 	drm_mode_set_name(mode);
168086ceb6bSWerner Johansson 
1690ce8ddd8SSam Ravnborg 	drm_mode_probed_add(connector, mode);
170086ceb6bSWerner Johansson 
1710ce8ddd8SSam Ravnborg 	connector->display_info.width_mm = 217;
1720ce8ddd8SSam Ravnborg 	connector->display_info.height_mm = 136;
173086ceb6bSWerner Johansson 
174086ceb6bSWerner Johansson 	return 1;
175086ceb6bSWerner Johansson }
176086ceb6bSWerner Johansson 
177086ceb6bSWerner Johansson static const struct drm_panel_funcs wuxga_nt_panel_funcs = {
178086ceb6bSWerner Johansson 	.disable = wuxga_nt_panel_disable,
179086ceb6bSWerner Johansson 	.unprepare = wuxga_nt_panel_unprepare,
180086ceb6bSWerner Johansson 	.prepare = wuxga_nt_panel_prepare,
181086ceb6bSWerner Johansson 	.enable = wuxga_nt_panel_enable,
182086ceb6bSWerner Johansson 	.get_modes = wuxga_nt_panel_get_modes,
183086ceb6bSWerner Johansson };
184086ceb6bSWerner Johansson 
185086ceb6bSWerner Johansson static const struct of_device_id wuxga_nt_of_match[] = {
186086ceb6bSWerner Johansson 	{ .compatible = "panasonic,vvx10f034n00", },
187086ceb6bSWerner Johansson 	{ }
188086ceb6bSWerner Johansson };
189086ceb6bSWerner Johansson MODULE_DEVICE_TABLE(of, wuxga_nt_of_match);
190086ceb6bSWerner Johansson 
wuxga_nt_panel_add(struct wuxga_nt_panel * wuxga_nt)191086ceb6bSWerner Johansson static int wuxga_nt_panel_add(struct wuxga_nt_panel *wuxga_nt)
192086ceb6bSWerner Johansson {
193086ceb6bSWerner Johansson 	struct device *dev = &wuxga_nt->dsi->dev;
194086ceb6bSWerner Johansson 	int ret;
195086ceb6bSWerner Johansson 
196086ceb6bSWerner Johansson 	wuxga_nt->mode = &default_mode;
197086ceb6bSWerner Johansson 
198086ceb6bSWerner Johansson 	wuxga_nt->supply = devm_regulator_get(dev, "power");
199086ceb6bSWerner Johansson 	if (IS_ERR(wuxga_nt->supply))
200086ceb6bSWerner Johansson 		return PTR_ERR(wuxga_nt->supply);
201086ceb6bSWerner Johansson 
2026dbe0c4bSLaurent Pinchart 	drm_panel_init(&wuxga_nt->base, &wuxga_nt->dsi->dev,
2039a2654c0SLaurent Pinchart 		       &wuxga_nt_panel_funcs, DRM_MODE_CONNECTOR_DSI);
204086ceb6bSWerner Johansson 
205581ee32eSSam Ravnborg 	ret = drm_panel_of_backlight(&wuxga_nt->base);
206581ee32eSSam Ravnborg 	if (ret)
207086ceb6bSWerner Johansson 		return ret;
208581ee32eSSam Ravnborg 
209c3ee8c65SBernard Zhao 	drm_panel_add(&wuxga_nt->base);
210c3ee8c65SBernard Zhao 
211c3ee8c65SBernard Zhao 	return 0;
212086ceb6bSWerner Johansson }
213086ceb6bSWerner Johansson 
wuxga_nt_panel_del(struct wuxga_nt_panel * wuxga_nt)214086ceb6bSWerner Johansson static void wuxga_nt_panel_del(struct wuxga_nt_panel *wuxga_nt)
215086ceb6bSWerner Johansson {
216086ceb6bSWerner Johansson 	if (wuxga_nt->base.dev)
217086ceb6bSWerner Johansson 		drm_panel_remove(&wuxga_nt->base);
218086ceb6bSWerner Johansson }
219086ceb6bSWerner Johansson 
wuxga_nt_panel_probe(struct mipi_dsi_device * dsi)220086ceb6bSWerner Johansson static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi)
221086ceb6bSWerner Johansson {
222086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt;
223086ceb6bSWerner Johansson 	int ret;
224086ceb6bSWerner Johansson 
225086ceb6bSWerner Johansson 	dsi->lanes = 4;
226086ceb6bSWerner Johansson 	dsi->format = MIPI_DSI_FMT_RGB888;
227086ceb6bSWerner Johansson 	dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
228086ceb6bSWerner Johansson 			MIPI_DSI_MODE_VIDEO_HSE |
229086ceb6bSWerner Johansson 			MIPI_DSI_CLOCK_NON_CONTINUOUS |
230086ceb6bSWerner Johansson 			MIPI_DSI_MODE_LPM;
231086ceb6bSWerner Johansson 
232086ceb6bSWerner Johansson 	wuxga_nt = devm_kzalloc(&dsi->dev, sizeof(*wuxga_nt), GFP_KERNEL);
233086ceb6bSWerner Johansson 	if (!wuxga_nt)
234086ceb6bSWerner Johansson 		return -ENOMEM;
235086ceb6bSWerner Johansson 
236086ceb6bSWerner Johansson 	mipi_dsi_set_drvdata(dsi, wuxga_nt);
237086ceb6bSWerner Johansson 
238086ceb6bSWerner Johansson 	wuxga_nt->dsi = dsi;
239086ceb6bSWerner Johansson 
240086ceb6bSWerner Johansson 	ret = wuxga_nt_panel_add(wuxga_nt);
241086ceb6bSWerner Johansson 	if (ret < 0)
242086ceb6bSWerner Johansson 		return ret;
243086ceb6bSWerner Johansson 
2449bf7123bSBrian Norris 	ret = mipi_dsi_attach(dsi);
2459bf7123bSBrian Norris 	if (ret < 0) {
2469bf7123bSBrian Norris 		wuxga_nt_panel_del(wuxga_nt);
2479bf7123bSBrian Norris 		return ret;
2489bf7123bSBrian Norris 	}
2499bf7123bSBrian Norris 
2509bf7123bSBrian Norris 	return 0;
251086ceb6bSWerner Johansson }
252086ceb6bSWerner Johansson 
wuxga_nt_panel_remove(struct mipi_dsi_device * dsi)253*79abca2bSUwe Kleine-König static void wuxga_nt_panel_remove(struct mipi_dsi_device *dsi)
254086ceb6bSWerner Johansson {
255086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi);
256086ceb6bSWerner Johansson 	int ret;
257086ceb6bSWerner Johansson 
258581ee32eSSam Ravnborg 	ret = drm_panel_disable(&wuxga_nt->base);
259086ceb6bSWerner Johansson 	if (ret < 0)
260086ceb6bSWerner Johansson 		dev_err(&dsi->dev, "failed to disable panel: %d\n", ret);
261086ceb6bSWerner Johansson 
262086ceb6bSWerner Johansson 	ret = mipi_dsi_detach(dsi);
263086ceb6bSWerner Johansson 	if (ret < 0)
264086ceb6bSWerner Johansson 		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
265086ceb6bSWerner Johansson 
266086ceb6bSWerner Johansson 	wuxga_nt_panel_del(wuxga_nt);
267086ceb6bSWerner Johansson }
268086ceb6bSWerner Johansson 
wuxga_nt_panel_shutdown(struct mipi_dsi_device * dsi)269086ceb6bSWerner Johansson static void wuxga_nt_panel_shutdown(struct mipi_dsi_device *dsi)
270086ceb6bSWerner Johansson {
271086ceb6bSWerner Johansson 	struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi);
272086ceb6bSWerner Johansson 
273581ee32eSSam Ravnborg 	drm_panel_disable(&wuxga_nt->base);
274086ceb6bSWerner Johansson }
275086ceb6bSWerner Johansson 
276086ceb6bSWerner Johansson static struct mipi_dsi_driver wuxga_nt_panel_driver = {
277086ceb6bSWerner Johansson 	.driver = {
278086ceb6bSWerner Johansson 		.name = "panel-panasonic-vvx10f034n00",
279086ceb6bSWerner Johansson 		.of_match_table = wuxga_nt_of_match,
280086ceb6bSWerner Johansson 	},
281086ceb6bSWerner Johansson 	.probe = wuxga_nt_panel_probe,
282086ceb6bSWerner Johansson 	.remove = wuxga_nt_panel_remove,
283086ceb6bSWerner Johansson 	.shutdown = wuxga_nt_panel_shutdown,
284086ceb6bSWerner Johansson };
285086ceb6bSWerner Johansson module_mipi_dsi_driver(wuxga_nt_panel_driver);
286086ceb6bSWerner Johansson 
287086ceb6bSWerner Johansson MODULE_AUTHOR("Werner Johansson <werner.johansson@sonymobile.com>");
288086ceb6bSWerner Johansson MODULE_DESCRIPTION("Panasonic VVX10F034N00 Novatek NT1397-based WUXGA (1920x1200) video mode panel driver");
289086ceb6bSWerner Johansson MODULE_LICENSE("GPL v2");
290