1c9cf4c2aSLaurent Pinchart // SPDX-License-Identifier: GPL-2.0
2c9cf4c2aSLaurent Pinchart /*
3c9cf4c2aSLaurent Pinchart  * Sharp LS037V7DW01 LCD Panel Driver
4c9cf4c2aSLaurent Pinchart  *
5c9cf4c2aSLaurent Pinchart  * Copyright (C) 2019 Texas Instruments Incorporated
6c9cf4c2aSLaurent Pinchart  *
7c9cf4c2aSLaurent Pinchart  * Based on the omapdrm-specific panel-sharp-ls037v7dw01 driver
8c9cf4c2aSLaurent Pinchart  *
9c9cf4c2aSLaurent Pinchart  * Copyright (C) 2013 Texas Instruments Incorporated
10c9cf4c2aSLaurent Pinchart  * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
11c9cf4c2aSLaurent Pinchart  */
12c9cf4c2aSLaurent Pinchart 
13c9cf4c2aSLaurent Pinchart #include <linux/delay.h>
14c9cf4c2aSLaurent Pinchart #include <linux/gpio/consumer.h>
15c9cf4c2aSLaurent Pinchart #include <linux/module.h>
16c9cf4c2aSLaurent Pinchart #include <linux/of.h>
17c9cf4c2aSLaurent Pinchart #include <linux/platform_device.h>
18c9cf4c2aSLaurent Pinchart #include <linux/regulator/consumer.h>
19c9cf4c2aSLaurent Pinchart 
20c9cf4c2aSLaurent Pinchart #include <drm/drm_connector.h>
21c9cf4c2aSLaurent Pinchart #include <drm/drm_modes.h>
22c9cf4c2aSLaurent Pinchart #include <drm/drm_panel.h>
23c9cf4c2aSLaurent Pinchart 
24c9cf4c2aSLaurent Pinchart struct ls037v7dw01_panel {
25c9cf4c2aSLaurent Pinchart 	struct drm_panel panel;
26c9cf4c2aSLaurent Pinchart 	struct platform_device *pdev;
27c9cf4c2aSLaurent Pinchart 
28c9cf4c2aSLaurent Pinchart 	struct regulator *vdd;
29c9cf4c2aSLaurent Pinchart 	struct gpio_desc *resb_gpio;	/* low = reset active min 20 us */
30c9cf4c2aSLaurent Pinchart 	struct gpio_desc *ini_gpio;	/* high = power on */
31c9cf4c2aSLaurent Pinchart 	struct gpio_desc *mo_gpio;	/* low = 480x640, high = 240x320 */
32c9cf4c2aSLaurent Pinchart 	struct gpio_desc *lr_gpio;	/* high = conventional horizontal scanning */
33c9cf4c2aSLaurent Pinchart 	struct gpio_desc *ud_gpio;	/* high = conventional vertical scanning */
34c9cf4c2aSLaurent Pinchart };
35c9cf4c2aSLaurent Pinchart 
36c9cf4c2aSLaurent Pinchart #define to_ls037v7dw01_device(p) \
37c9cf4c2aSLaurent Pinchart 	container_of(p, struct ls037v7dw01_panel, panel)
38c9cf4c2aSLaurent Pinchart 
ls037v7dw01_disable(struct drm_panel * panel)39c9cf4c2aSLaurent Pinchart static int ls037v7dw01_disable(struct drm_panel *panel)
40c9cf4c2aSLaurent Pinchart {
41c9cf4c2aSLaurent Pinchart 	struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel);
42c9cf4c2aSLaurent Pinchart 
43c9cf4c2aSLaurent Pinchart 	gpiod_set_value_cansleep(lcd->ini_gpio, 0);
44c9cf4c2aSLaurent Pinchart 	gpiod_set_value_cansleep(lcd->resb_gpio, 0);
45c9cf4c2aSLaurent Pinchart 
46c9cf4c2aSLaurent Pinchart 	/* Wait at least 5 vsyncs after disabling the LCD. */
47c9cf4c2aSLaurent Pinchart 	msleep(100);
48c9cf4c2aSLaurent Pinchart 
49c9cf4c2aSLaurent Pinchart 	return 0;
50c9cf4c2aSLaurent Pinchart }
51c9cf4c2aSLaurent Pinchart 
ls037v7dw01_unprepare(struct drm_panel * panel)52c9cf4c2aSLaurent Pinchart static int ls037v7dw01_unprepare(struct drm_panel *panel)
53c9cf4c2aSLaurent Pinchart {
54c9cf4c2aSLaurent Pinchart 	struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel);
55c9cf4c2aSLaurent Pinchart 
56c9cf4c2aSLaurent Pinchart 	regulator_disable(lcd->vdd);
57c9cf4c2aSLaurent Pinchart 	return 0;
58c9cf4c2aSLaurent Pinchart }
59c9cf4c2aSLaurent Pinchart 
ls037v7dw01_prepare(struct drm_panel * panel)60c9cf4c2aSLaurent Pinchart static int ls037v7dw01_prepare(struct drm_panel *panel)
61c9cf4c2aSLaurent Pinchart {
62c9cf4c2aSLaurent Pinchart 	struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel);
63c9cf4c2aSLaurent Pinchart 	int ret;
64c9cf4c2aSLaurent Pinchart 
65c9cf4c2aSLaurent Pinchart 	ret = regulator_enable(lcd->vdd);
66c9cf4c2aSLaurent Pinchart 	if (ret < 0)
67c9cf4c2aSLaurent Pinchart 		dev_err(&lcd->pdev->dev, "%s: failed to enable regulator\n",
68c9cf4c2aSLaurent Pinchart 			__func__);
69c9cf4c2aSLaurent Pinchart 
70c9cf4c2aSLaurent Pinchart 	return ret;
71c9cf4c2aSLaurent Pinchart }
72c9cf4c2aSLaurent Pinchart 
ls037v7dw01_enable(struct drm_panel * panel)73c9cf4c2aSLaurent Pinchart static int ls037v7dw01_enable(struct drm_panel *panel)
74c9cf4c2aSLaurent Pinchart {
75c9cf4c2aSLaurent Pinchart 	struct ls037v7dw01_panel *lcd = to_ls037v7dw01_device(panel);
76c9cf4c2aSLaurent Pinchart 
77c9cf4c2aSLaurent Pinchart 	/* Wait couple of vsyncs before enabling the LCD. */
78c9cf4c2aSLaurent Pinchart 	msleep(50);
79c9cf4c2aSLaurent Pinchart 
80c9cf4c2aSLaurent Pinchart 	gpiod_set_value_cansleep(lcd->resb_gpio, 1);
81c9cf4c2aSLaurent Pinchart 	gpiod_set_value_cansleep(lcd->ini_gpio, 1);
82c9cf4c2aSLaurent Pinchart 
83c9cf4c2aSLaurent Pinchart 	return 0;
84c9cf4c2aSLaurent Pinchart }
85c9cf4c2aSLaurent Pinchart 
86c9cf4c2aSLaurent Pinchart static const struct drm_display_mode ls037v7dw01_mode = {
87c9cf4c2aSLaurent Pinchart 	.clock = 19200,
88c9cf4c2aSLaurent Pinchart 	.hdisplay = 480,
89c9cf4c2aSLaurent Pinchart 	.hsync_start = 480 + 1,
90c9cf4c2aSLaurent Pinchart 	.hsync_end = 480 + 1 + 2,
91c9cf4c2aSLaurent Pinchart 	.htotal = 480 + 1 + 2 + 28,
92c9cf4c2aSLaurent Pinchart 	.vdisplay = 640,
93c9cf4c2aSLaurent Pinchart 	.vsync_start = 640 + 1,
94c9cf4c2aSLaurent Pinchart 	.vsync_end = 640 + 1 + 1,
95c9cf4c2aSLaurent Pinchart 	.vtotal = 640 + 1 + 1 + 1,
96c9cf4c2aSLaurent Pinchart 	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
97c9cf4c2aSLaurent Pinchart 	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
98c9cf4c2aSLaurent Pinchart 	.width_mm = 56,
99c9cf4c2aSLaurent Pinchart 	.height_mm = 75,
100c9cf4c2aSLaurent Pinchart };
101c9cf4c2aSLaurent Pinchart 
ls037v7dw01_get_modes(struct drm_panel * panel,struct drm_connector * connector)1020ce8ddd8SSam Ravnborg static int ls037v7dw01_get_modes(struct drm_panel *panel,
1030ce8ddd8SSam Ravnborg 				 struct drm_connector *connector)
104c9cf4c2aSLaurent Pinchart {
105c9cf4c2aSLaurent Pinchart 	struct drm_display_mode *mode;
106c9cf4c2aSLaurent Pinchart 
107aa6c4364SSam Ravnborg 	mode = drm_mode_duplicate(connector->dev, &ls037v7dw01_mode);
108c9cf4c2aSLaurent Pinchart 	if (!mode)
109c9cf4c2aSLaurent Pinchart 		return -ENOMEM;
110c9cf4c2aSLaurent Pinchart 
111c9cf4c2aSLaurent Pinchart 	drm_mode_set_name(mode);
112c9cf4c2aSLaurent Pinchart 	drm_mode_probed_add(connector, mode);
113c9cf4c2aSLaurent Pinchart 
114c9cf4c2aSLaurent Pinchart 	connector->display_info.width_mm = ls037v7dw01_mode.width_mm;
115c9cf4c2aSLaurent Pinchart 	connector->display_info.height_mm = ls037v7dw01_mode.height_mm;
116c9cf4c2aSLaurent Pinchart 	/*
117c9cf4c2aSLaurent Pinchart 	 * FIXME: According to the datasheet pixel data is sampled on the
118c9cf4c2aSLaurent Pinchart 	 * rising edge of the clock, but the code running on the SDP3430
119c9cf4c2aSLaurent Pinchart 	 * indicates sampling on the negative edge. This should be tested on a
120c9cf4c2aSLaurent Pinchart 	 * real device.
121c9cf4c2aSLaurent Pinchart 	 */
122c9cf4c2aSLaurent Pinchart 	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
123c9cf4c2aSLaurent Pinchart 					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
124c9cf4c2aSLaurent Pinchart 					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
125c9cf4c2aSLaurent Pinchart 
126c9cf4c2aSLaurent Pinchart 	return 1;
127c9cf4c2aSLaurent Pinchart }
128c9cf4c2aSLaurent Pinchart 
129c9cf4c2aSLaurent Pinchart static const struct drm_panel_funcs ls037v7dw01_funcs = {
130c9cf4c2aSLaurent Pinchart 	.disable = ls037v7dw01_disable,
131c9cf4c2aSLaurent Pinchart 	.unprepare = ls037v7dw01_unprepare,
132c9cf4c2aSLaurent Pinchart 	.prepare = ls037v7dw01_prepare,
133c9cf4c2aSLaurent Pinchart 	.enable = ls037v7dw01_enable,
134c9cf4c2aSLaurent Pinchart 	.get_modes = ls037v7dw01_get_modes,
135c9cf4c2aSLaurent Pinchart };
136c9cf4c2aSLaurent Pinchart 
ls037v7dw01_probe(struct platform_device * pdev)137c9cf4c2aSLaurent Pinchart static int ls037v7dw01_probe(struct platform_device *pdev)
138c9cf4c2aSLaurent Pinchart {
139c9cf4c2aSLaurent Pinchart 	struct ls037v7dw01_panel *lcd;
140c9cf4c2aSLaurent Pinchart 
141c9cf4c2aSLaurent Pinchart 	lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL);
1421c8fc3f0SLaurent Pinchart 	if (!lcd)
143c9cf4c2aSLaurent Pinchart 		return -ENOMEM;
144c9cf4c2aSLaurent Pinchart 
145c9cf4c2aSLaurent Pinchart 	platform_set_drvdata(pdev, lcd);
146c9cf4c2aSLaurent Pinchart 	lcd->pdev = pdev;
147c9cf4c2aSLaurent Pinchart 
148c9cf4c2aSLaurent Pinchart 	lcd->vdd = devm_regulator_get(&pdev->dev, "envdd");
149ef41af47SCai Huoqing 	if (IS_ERR(lcd->vdd))
150ef41af47SCai Huoqing 		return dev_err_probe(&pdev->dev, PTR_ERR(lcd->vdd),
151ef41af47SCai Huoqing 				     "failed to get regulator\n");
152c9cf4c2aSLaurent Pinchart 
153c9cf4c2aSLaurent Pinchart 	lcd->ini_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW);
154ef41af47SCai Huoqing 	if (IS_ERR(lcd->ini_gpio))
155ef41af47SCai Huoqing 		return dev_err_probe(&pdev->dev, PTR_ERR(lcd->ini_gpio),
156ef41af47SCai Huoqing 				     "failed to get enable gpio\n");
157c9cf4c2aSLaurent Pinchart 
158c9cf4c2aSLaurent Pinchart 	lcd->resb_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
159ef41af47SCai Huoqing 	if (IS_ERR(lcd->resb_gpio))
160ef41af47SCai Huoqing 		return dev_err_probe(&pdev->dev, PTR_ERR(lcd->resb_gpio),
161ef41af47SCai Huoqing 				     "failed to get reset gpio\n");
162c9cf4c2aSLaurent Pinchart 
163c9cf4c2aSLaurent Pinchart 	lcd->mo_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 0,
164c9cf4c2aSLaurent Pinchart 					    GPIOD_OUT_LOW);
165c9cf4c2aSLaurent Pinchart 	if (IS_ERR(lcd->mo_gpio)) {
166c9cf4c2aSLaurent Pinchart 		dev_err(&pdev->dev, "failed to get mode[0] gpio\n");
167c9cf4c2aSLaurent Pinchart 		return PTR_ERR(lcd->mo_gpio);
168c9cf4c2aSLaurent Pinchart 	}
169c9cf4c2aSLaurent Pinchart 
170c9cf4c2aSLaurent Pinchart 	lcd->lr_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 1,
171c9cf4c2aSLaurent Pinchart 					    GPIOD_OUT_LOW);
172c9cf4c2aSLaurent Pinchart 	if (IS_ERR(lcd->lr_gpio)) {
173c9cf4c2aSLaurent Pinchart 		dev_err(&pdev->dev, "failed to get mode[1] gpio\n");
174c9cf4c2aSLaurent Pinchart 		return PTR_ERR(lcd->lr_gpio);
175c9cf4c2aSLaurent Pinchart 	}
176c9cf4c2aSLaurent Pinchart 
177c9cf4c2aSLaurent Pinchart 	lcd->ud_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 2,
178c9cf4c2aSLaurent Pinchart 					    GPIOD_OUT_LOW);
179c9cf4c2aSLaurent Pinchart 	if (IS_ERR(lcd->ud_gpio)) {
180c9cf4c2aSLaurent Pinchart 		dev_err(&pdev->dev, "failed to get mode[2] gpio\n");
181c9cf4c2aSLaurent Pinchart 		return PTR_ERR(lcd->ud_gpio);
182c9cf4c2aSLaurent Pinchart 	}
183c9cf4c2aSLaurent Pinchart 
1849a2654c0SLaurent Pinchart 	drm_panel_init(&lcd->panel, &pdev->dev, &ls037v7dw01_funcs,
1859a2654c0SLaurent Pinchart 		       DRM_MODE_CONNECTOR_DPI);
186c9cf4c2aSLaurent Pinchart 
187c3ee8c65SBernard Zhao 	drm_panel_add(&lcd->panel);
188c3ee8c65SBernard Zhao 
189c3ee8c65SBernard Zhao 	return 0;
190c9cf4c2aSLaurent Pinchart }
191c9cf4c2aSLaurent Pinchart 
ls037v7dw01_remove(struct platform_device * pdev)192*cef3776dSUwe Kleine-König static void ls037v7dw01_remove(struct platform_device *pdev)
193c9cf4c2aSLaurent Pinchart {
194c9cf4c2aSLaurent Pinchart 	struct ls037v7dw01_panel *lcd = platform_get_drvdata(pdev);
195c9cf4c2aSLaurent Pinchart 
196c9cf4c2aSLaurent Pinchart 	drm_panel_remove(&lcd->panel);
197c9cf4c2aSLaurent Pinchart 	drm_panel_disable(&lcd->panel);
198c9cf4c2aSLaurent Pinchart 	drm_panel_unprepare(&lcd->panel);
199c9cf4c2aSLaurent Pinchart }
200c9cf4c2aSLaurent Pinchart 
201c9cf4c2aSLaurent Pinchart static const struct of_device_id ls037v7dw01_of_match[] = {
202c9cf4c2aSLaurent Pinchart 	{ .compatible = "sharp,ls037v7dw01", },
203c9cf4c2aSLaurent Pinchart 	{ /* sentinel */ },
204c9cf4c2aSLaurent Pinchart };
205c9cf4c2aSLaurent Pinchart 
206c9cf4c2aSLaurent Pinchart MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match);
207c9cf4c2aSLaurent Pinchart 
208c9cf4c2aSLaurent Pinchart static struct platform_driver ls037v7dw01_driver = {
209c9cf4c2aSLaurent Pinchart 	.probe		= ls037v7dw01_probe,
210*cef3776dSUwe Kleine-König 	.remove_new	= ls037v7dw01_remove,
211c9cf4c2aSLaurent Pinchart 	.driver		= {
212c9cf4c2aSLaurent Pinchart 		.name = "panel-sharp-ls037v7dw01",
213c9cf4c2aSLaurent Pinchart 		.of_match_table = ls037v7dw01_of_match,
214c9cf4c2aSLaurent Pinchart 	},
215c9cf4c2aSLaurent Pinchart };
216c9cf4c2aSLaurent Pinchart 
217c9cf4c2aSLaurent Pinchart module_platform_driver(ls037v7dw01_driver);
218c9cf4c2aSLaurent Pinchart 
219c9cf4c2aSLaurent Pinchart MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
220c9cf4c2aSLaurent Pinchart MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
221c9cf4c2aSLaurent Pinchart MODULE_LICENSE("GPL");
222