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