1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2022 Joel Selvaraj <jo@jsfamily.in> |
4 | * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: |
5 | * Copyright (c) 2013, The Linux Foundation. All rights reserved. |
6 | */ |
7 | |
8 | #include <linux/delay.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/regulator/consumer.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | |
14 | #include <video/mipi_display.h> |
15 | |
16 | #include <drm/drm_mipi_dsi.h> |
17 | #include <drm/drm_modes.h> |
18 | #include <drm/drm_panel.h> |
19 | |
20 | static const char * const regulator_names[] = { |
21 | "vddio" , |
22 | "vddpos" , |
23 | "vddneg" , |
24 | }; |
25 | |
26 | static const unsigned long regulator_enable_loads[] = { |
27 | 62000, |
28 | 100000, |
29 | 100000 |
30 | }; |
31 | |
32 | struct ebbg_ft8719 { |
33 | struct drm_panel panel; |
34 | struct mipi_dsi_device *dsi; |
35 | |
36 | struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)]; |
37 | |
38 | struct gpio_desc *reset_gpio; |
39 | }; |
40 | |
41 | static inline |
42 | struct ebbg_ft8719 *to_ebbg_ft8719(struct drm_panel *panel) |
43 | { |
44 | return container_of(panel, struct ebbg_ft8719, panel); |
45 | } |
46 | |
47 | static void ebbg_ft8719_reset(struct ebbg_ft8719 *ctx) |
48 | { |
49 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
50 | usleep_range(min: 4000, max: 5000); |
51 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
52 | usleep_range(min: 1000, max: 2000); |
53 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
54 | usleep_range(min: 15000, max: 16000); |
55 | } |
56 | |
57 | static int ebbg_ft8719_on(struct ebbg_ft8719 *ctx) |
58 | { |
59 | struct mipi_dsi_device *dsi = ctx->dsi; |
60 | struct device *dev = &dsi->dev; |
61 | int ret; |
62 | |
63 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
64 | |
65 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness: 0x00ff); |
66 | if (ret < 0) { |
67 | dev_err(dev, "Failed to set display brightness: %d\n" , ret); |
68 | return ret; |
69 | } |
70 | |
71 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); |
72 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); |
73 | |
74 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
75 | if (ret < 0) { |
76 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
77 | return ret; |
78 | } |
79 | msleep(msecs: 90); |
80 | |
81 | ret = mipi_dsi_dcs_set_display_on(dsi); |
82 | if (ret < 0) { |
83 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
84 | return ret; |
85 | } |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static int ebbg_ft8719_off(struct ebbg_ft8719 *ctx) |
91 | { |
92 | struct mipi_dsi_device *dsi = ctx->dsi; |
93 | struct device *dev = &dsi->dev; |
94 | int ret; |
95 | |
96 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
97 | |
98 | ret = mipi_dsi_dcs_set_display_off(dsi); |
99 | if (ret < 0) { |
100 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
101 | return ret; |
102 | } |
103 | usleep_range(min: 10000, max: 11000); |
104 | |
105 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
106 | if (ret < 0) { |
107 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
108 | return ret; |
109 | } |
110 | msleep(msecs: 90); |
111 | |
112 | return 0; |
113 | } |
114 | |
115 | static int ebbg_ft8719_prepare(struct drm_panel *panel) |
116 | { |
117 | struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel); |
118 | struct device *dev = &ctx->dsi->dev; |
119 | int ret; |
120 | |
121 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
122 | if (ret < 0) |
123 | return ret; |
124 | |
125 | ebbg_ft8719_reset(ctx); |
126 | |
127 | ret = ebbg_ft8719_on(ctx); |
128 | if (ret < 0) { |
129 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
130 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
131 | return ret; |
132 | } |
133 | |
134 | return 0; |
135 | } |
136 | |
137 | static int ebbg_ft8719_unprepare(struct drm_panel *panel) |
138 | { |
139 | struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel); |
140 | struct device *dev = &ctx->dsi->dev; |
141 | int ret; |
142 | |
143 | ret = ebbg_ft8719_off(ctx); |
144 | if (ret < 0) |
145 | dev_err(dev, "Failed to un-initialize panel: %d\n" , ret); |
146 | |
147 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
148 | |
149 | ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
150 | if (ret) |
151 | dev_err(panel->dev, "Failed to disable regulators: %d\n" , ret); |
152 | |
153 | return 0; |
154 | } |
155 | |
156 | static const struct drm_display_mode ebbg_ft8719_mode = { |
157 | .clock = (1080 + 28 + 4 + 16) * (2246 + 120 + 4 + 12) * 60 / 1000, |
158 | .hdisplay = 1080, |
159 | .hsync_start = 1080 + 28, |
160 | .hsync_end = 1080 + 28 + 4, |
161 | .htotal = 1080 + 28 + 4 + 16, |
162 | .vdisplay = 2246, |
163 | .vsync_start = 2246 + 120, |
164 | .vsync_end = 2246 + 120 + 4, |
165 | .vtotal = 2246 + 120 + 4 + 12, |
166 | .width_mm = 68, |
167 | .height_mm = 141, |
168 | }; |
169 | |
170 | static int ebbg_ft8719_get_modes(struct drm_panel *panel, |
171 | struct drm_connector *connector) |
172 | { |
173 | struct drm_display_mode *mode; |
174 | |
175 | mode = drm_mode_duplicate(dev: connector->dev, mode: &ebbg_ft8719_mode); |
176 | if (!mode) |
177 | return -ENOMEM; |
178 | |
179 | drm_mode_set_name(mode); |
180 | |
181 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
182 | connector->display_info.width_mm = mode->width_mm; |
183 | connector->display_info.height_mm = mode->height_mm; |
184 | drm_mode_probed_add(connector, mode); |
185 | |
186 | return 1; |
187 | } |
188 | |
189 | static const struct drm_panel_funcs ebbg_ft8719_panel_funcs = { |
190 | .prepare = ebbg_ft8719_prepare, |
191 | .unprepare = ebbg_ft8719_unprepare, |
192 | .get_modes = ebbg_ft8719_get_modes, |
193 | }; |
194 | |
195 | static int ebbg_ft8719_probe(struct mipi_dsi_device *dsi) |
196 | { |
197 | struct device *dev = &dsi->dev; |
198 | struct ebbg_ft8719 *ctx; |
199 | int i, ret; |
200 | |
201 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
202 | if (!ctx) |
203 | return -ENOMEM; |
204 | |
205 | for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) |
206 | ctx->supplies[i].supply = regulator_names[i]; |
207 | |
208 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
209 | consumers: ctx->supplies); |
210 | if (ret < 0) |
211 | return dev_err_probe(dev, err: ret, fmt: "Failed to get regulators\n" ); |
212 | |
213 | for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) { |
214 | ret = regulator_set_load(regulator: ctx->supplies[i].consumer, |
215 | load_uA: regulator_enable_loads[i]); |
216 | if (ret) |
217 | return dev_err_probe(dev, err: ret, |
218 | fmt: "Failed to set regulator load\n" ); |
219 | } |
220 | |
221 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
222 | if (IS_ERR(ptr: ctx->reset_gpio)) |
223 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), |
224 | fmt: "Failed to get reset-gpios\n" ); |
225 | |
226 | ctx->dsi = dsi; |
227 | mipi_dsi_set_drvdata(dsi, data: ctx); |
228 | |
229 | dsi->lanes = 4; |
230 | dsi->format = MIPI_DSI_FMT_RGB888; |
231 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
232 | MIPI_DSI_CLOCK_NON_CONTINUOUS; |
233 | |
234 | drm_panel_init(panel: &ctx->panel, dev, funcs: &ebbg_ft8719_panel_funcs, |
235 | DRM_MODE_CONNECTOR_DSI); |
236 | |
237 | ret = drm_panel_of_backlight(panel: &ctx->panel); |
238 | if (ret) |
239 | return dev_err_probe(dev, err: ret, fmt: "Failed to get backlight\n" ); |
240 | |
241 | drm_panel_add(panel: &ctx->panel); |
242 | |
243 | ret = mipi_dsi_attach(dsi); |
244 | if (ret < 0) { |
245 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
246 | drm_panel_remove(panel: &ctx->panel); |
247 | return ret; |
248 | } |
249 | |
250 | return 0; |
251 | } |
252 | |
253 | static void ebbg_ft8719_remove(struct mipi_dsi_device *dsi) |
254 | { |
255 | struct ebbg_ft8719 *ctx = mipi_dsi_get_drvdata(dsi); |
256 | int ret; |
257 | |
258 | ret = mipi_dsi_detach(dsi); |
259 | if (ret < 0) |
260 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
261 | |
262 | drm_panel_remove(panel: &ctx->panel); |
263 | } |
264 | |
265 | static const struct of_device_id ebbg_ft8719_of_match[] = { |
266 | { .compatible = "ebbg,ft8719" }, |
267 | { /* sentinel */ } |
268 | }; |
269 | MODULE_DEVICE_TABLE(of, ebbg_ft8719_of_match); |
270 | |
271 | static struct mipi_dsi_driver ebbg_ft8719_driver = { |
272 | .probe = ebbg_ft8719_probe, |
273 | .remove = ebbg_ft8719_remove, |
274 | .driver = { |
275 | .name = "panel-ebbg-ft8719" , |
276 | .of_match_table = ebbg_ft8719_of_match, |
277 | }, |
278 | }; |
279 | module_mipi_dsi_driver(ebbg_ft8719_driver); |
280 | |
281 | MODULE_AUTHOR("Joel Selvaraj <jo@jsfamily.in>" ); |
282 | MODULE_DESCRIPTION("DRM driver for EBBG FT8719 video dsi panel" ); |
283 | MODULE_LICENSE("GPL v2" ); |
284 | |