1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree. |
4 | * Copyright (c) 2023 Linaro Limited |
5 | */ |
6 | |
7 | #include <linux/backlight.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/regulator/consumer.h> |
13 | |
14 | #include <drm/display/drm_dsc.h> |
15 | #include <drm/display/drm_dsc_helper.h> |
16 | #include <drm/drm_mipi_dsi.h> |
17 | #include <drm/drm_modes.h> |
18 | #include <drm/drm_panel.h> |
19 | |
20 | struct rm692e5_panel { |
21 | struct drm_panel panel; |
22 | struct mipi_dsi_device *dsi; |
23 | struct drm_dsc_config dsc; |
24 | struct regulator_bulk_data supplies[3]; |
25 | struct gpio_desc *reset_gpio; |
26 | }; |
27 | |
28 | static inline struct rm692e5_panel *to_rm692e5_panel(struct drm_panel *panel) |
29 | { |
30 | return container_of(panel, struct rm692e5_panel, panel); |
31 | } |
32 | |
33 | static void rm692e5_reset(struct rm692e5_panel *ctx) |
34 | { |
35 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
36 | usleep_range(min: 10000, max: 11000); |
37 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
38 | usleep_range(min: 5000, max: 6000); |
39 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
40 | usleep_range(min: 10000, max: 11000); |
41 | } |
42 | |
43 | static void rm692e5_on(struct mipi_dsi_multi_context *dsi_ctx) |
44 | { |
45 | dsi_ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
46 | |
47 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x41); |
48 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd6, 0x00); |
49 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x16); |
50 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x8a, 0x87); |
51 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x71); |
52 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x82, 0x01); |
53 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc6, 0x00); |
54 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc7, 0x2c); |
55 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc8, 0x64); |
56 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc9, 0x3c); |
57 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xca, 0x80); |
58 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xcb, 0x02); |
59 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xcc, 0x02); |
60 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x38); |
61 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0x13); |
62 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xf4); |
63 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x00, 0xff); |
64 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x01, 0xff); |
65 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x02, 0xcf); |
66 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x03, 0xbc); |
67 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x04, 0xb9); |
68 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x05, 0x99); |
69 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x06, 0x02); |
70 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x07, 0x0a); |
71 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x08, 0xe0); |
72 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x09, 0x4c); |
73 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0a, 0xeb); |
74 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0b, 0xe8); |
75 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0c, 0x32); |
76 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0d, 0x07); |
77 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xf4); |
78 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0d, 0xc0); |
79 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0e, 0xff); |
80 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x0f, 0xff); |
81 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x10, 0x33); |
82 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x11, 0x6f); |
83 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x12, 0x6e); |
84 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x13, 0xa6); |
85 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x14, 0x80); |
86 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x15, 0x02); |
87 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x16, 0x38); |
88 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x17, 0xd3); |
89 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0x3a); |
90 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x19, 0xba); |
91 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1a, 0xcc); |
92 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1b, 0x01); |
93 | |
94 | mipi_dsi_dcs_nop_multi(ctx: dsi_ctx); |
95 | |
96 | mipi_dsi_msleep(dsi_ctx, 32); |
97 | |
98 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x38); |
99 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x18, 0x13); |
100 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xd1); |
101 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd3, 0x00); |
102 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd0, 0x00); |
103 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd2, 0x00); |
104 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xd4, 0x00); |
105 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xb4, 0x01); |
106 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0xf9); |
107 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x00, 0xaf); |
108 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x1d, 0x37); |
109 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x44, 0x0a, 0x7b); |
110 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfe, 0x00); |
111 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xfa, 0x01); |
112 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0xc2, 0x08); |
113 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x35, 0x00); |
114 | mipi_dsi_generic_write_seq_multi(dsi_ctx, 0x51, 0x05, 0x42); |
115 | |
116 | mipi_dsi_dcs_exit_sleep_mode_multi(ctx: dsi_ctx); |
117 | mipi_dsi_msleep(dsi_ctx, 100); |
118 | mipi_dsi_dcs_set_display_on_multi(ctx: dsi_ctx); |
119 | } |
120 | |
121 | static int rm692e5_disable(struct drm_panel *panel) |
122 | { |
123 | struct rm692e5_panel *ctx = to_rm692e5_panel(panel); |
124 | struct mipi_dsi_device *dsi = ctx->dsi; |
125 | struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; |
126 | |
127 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
128 | |
129 | mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xfe, 0x00); |
130 | |
131 | mipi_dsi_dcs_set_display_off_multi(ctx: &dsi_ctx); |
132 | |
133 | mipi_dsi_dcs_enter_sleep_mode_multi(ctx: &dsi_ctx); |
134 | |
135 | mipi_dsi_msleep(&dsi_ctx, 100); |
136 | |
137 | return dsi_ctx.accum_err; |
138 | } |
139 | |
140 | static int rm692e5_prepare(struct drm_panel *panel) |
141 | { |
142 | struct rm692e5_panel *ctx = to_rm692e5_panel(panel); |
143 | struct drm_dsc_picture_parameter_set pps; |
144 | struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; |
145 | |
146 | dsi_ctx.accum_err = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
147 | if (dsi_ctx.accum_err) |
148 | return dsi_ctx.accum_err; |
149 | |
150 | rm692e5_reset(ctx); |
151 | |
152 | rm692e5_on(dsi_ctx: &dsi_ctx); |
153 | |
154 | drm_dsc_pps_payload_pack(pps_sdp: &pps, dsc_cfg: &ctx->dsc); |
155 | |
156 | mipi_dsi_picture_parameter_set_multi(ctx: &dsi_ctx, pps: &pps); |
157 | mipi_dsi_compression_mode_ext_multi(ctx: &dsi_ctx, enable: true, algo: MIPI_DSI_COMPRESSION_DSC, pps_selector: 0); |
158 | mipi_dsi_msleep(&dsi_ctx, 28); |
159 | |
160 | mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xfe, 0x40); |
161 | |
162 | /* 0x05 -> 90Hz, 0x00 -> 60Hz */ |
163 | mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xbd, 0x05); |
164 | |
165 | mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xfe, 0x00); |
166 | |
167 | if (dsi_ctx.accum_err) { |
168 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
169 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
170 | } |
171 | |
172 | return dsi_ctx.accum_err; |
173 | } |
174 | |
175 | static int rm692e5_unprepare(struct drm_panel *panel) |
176 | { |
177 | struct rm692e5_panel *ctx = to_rm692e5_panel(panel); |
178 | |
179 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
180 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | static const struct drm_display_mode rm692e5_mode = { |
186 | .clock = (1224 + 32 + 8 + 8) * (2700 + 8 + 2 + 8) * 90 / 1000, |
187 | .hdisplay = 1224, |
188 | .hsync_start = 1224 + 32, |
189 | .hsync_end = 1224 + 32 + 8, |
190 | .htotal = 1224 + 32 + 8 + 8, |
191 | .vdisplay = 2700, |
192 | .vsync_start = 2700 + 8, |
193 | .vsync_end = 2700 + 8 + 2, |
194 | .vtotal = 2700 + 8 + 2 + 8, |
195 | .width_mm = 68, |
196 | .height_mm = 150, |
197 | }; |
198 | |
199 | static int rm692e5_get_modes(struct drm_panel *panel, |
200 | struct drm_connector *connector) |
201 | { |
202 | struct drm_display_mode *mode; |
203 | |
204 | mode = drm_mode_duplicate(dev: connector->dev, mode: &rm692e5_mode); |
205 | if (!mode) |
206 | return -ENOMEM; |
207 | |
208 | drm_mode_set_name(mode); |
209 | |
210 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
211 | connector->display_info.width_mm = mode->width_mm; |
212 | connector->display_info.height_mm = mode->height_mm; |
213 | drm_mode_probed_add(connector, mode); |
214 | |
215 | return 1; |
216 | } |
217 | |
218 | static const struct drm_panel_funcs rm692e5_panel_funcs = { |
219 | .prepare = rm692e5_prepare, |
220 | .unprepare = rm692e5_unprepare, |
221 | .disable = rm692e5_disable, |
222 | .get_modes = rm692e5_get_modes, |
223 | }; |
224 | |
225 | static int rm692e5_bl_update_status(struct backlight_device *bl) |
226 | { |
227 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
228 | u16 brightness = backlight_get_brightness(bd: bl); |
229 | int ret; |
230 | |
231 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
232 | |
233 | ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); |
234 | if (ret < 0) |
235 | return ret; |
236 | |
237 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static int rm692e5_bl_get_brightness(struct backlight_device *bl) |
243 | { |
244 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
245 | u16 brightness; |
246 | int ret; |
247 | |
248 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
249 | |
250 | ret = mipi_dsi_dcs_get_display_brightness_large(dsi, brightness: &brightness); |
251 | if (ret < 0) |
252 | return ret; |
253 | |
254 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
255 | |
256 | return brightness; |
257 | } |
258 | |
259 | static const struct backlight_ops rm692e5_bl_ops = { |
260 | .update_status = rm692e5_bl_update_status, |
261 | .get_brightness = rm692e5_bl_get_brightness, |
262 | }; |
263 | |
264 | static struct backlight_device * |
265 | rm692e5_create_backlight(struct mipi_dsi_device *dsi) |
266 | { |
267 | struct device *dev = &dsi->dev; |
268 | const struct backlight_properties props = { |
269 | .type = BACKLIGHT_RAW, |
270 | .brightness = 4095, |
271 | .max_brightness = 4095, |
272 | }; |
273 | |
274 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
275 | ops: &rm692e5_bl_ops, props: &props); |
276 | } |
277 | |
278 | static int rm692e5_probe(struct mipi_dsi_device *dsi) |
279 | { |
280 | struct device *dev = &dsi->dev; |
281 | struct rm692e5_panel *ctx; |
282 | int ret; |
283 | |
284 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
285 | if (!ctx) |
286 | return -ENOMEM; |
287 | |
288 | ctx->supplies[0].supply = "vddio" ; |
289 | ctx->supplies[1].supply = "dvdd" ; |
290 | ctx->supplies[2].supply = "vci" ; |
291 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
292 | consumers: ctx->supplies); |
293 | if (ret < 0) |
294 | return dev_err_probe(dev, err: ret, fmt: "Failed to get regulators\n" ); |
295 | |
296 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
297 | if (IS_ERR(ptr: ctx->reset_gpio)) |
298 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), |
299 | fmt: "Failed to get reset-gpios\n" ); |
300 | |
301 | ctx->dsi = dsi; |
302 | mipi_dsi_set_drvdata(dsi, data: ctx); |
303 | |
304 | dsi->lanes = 4; |
305 | dsi->format = MIPI_DSI_FMT_RGB888; |
306 | dsi->mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | |
307 | MIPI_DSI_CLOCK_NON_CONTINUOUS; |
308 | |
309 | drm_panel_init(panel: &ctx->panel, dev, funcs: &rm692e5_panel_funcs, |
310 | DRM_MODE_CONNECTOR_DSI); |
311 | ctx->panel.prepare_prev_first = true; |
312 | |
313 | ctx->panel.backlight = rm692e5_create_backlight(dsi); |
314 | if (IS_ERR(ptr: ctx->panel.backlight)) |
315 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->panel.backlight), |
316 | fmt: "Failed to create backlight\n" ); |
317 | |
318 | drm_panel_add(panel: &ctx->panel); |
319 | |
320 | /* This panel only supports DSC; unconditionally enable it */ |
321 | dsi->dsc = &ctx->dsc; |
322 | |
323 | /* TODO: Pass slice_per_pkt = 2 */ |
324 | ctx->dsc.dsc_version_major = 1; |
325 | ctx->dsc.dsc_version_minor = 1; |
326 | ctx->dsc.slice_height = 60; |
327 | ctx->dsc.slice_width = 1224; |
328 | |
329 | ctx->dsc.slice_count = 1224 / ctx->dsc.slice_width; |
330 | ctx->dsc.bits_per_component = 8; |
331 | ctx->dsc.bits_per_pixel = 8 << 4; /* 4 fractional bits */ |
332 | ctx->dsc.block_pred_enable = true; |
333 | |
334 | ret = mipi_dsi_attach(dsi); |
335 | if (ret < 0) { |
336 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
337 | drm_panel_remove(panel: &ctx->panel); |
338 | return ret; |
339 | } |
340 | |
341 | return 0; |
342 | } |
343 | |
344 | static void rm692e5_remove(struct mipi_dsi_device *dsi) |
345 | { |
346 | struct rm692e5_panel *ctx = mipi_dsi_get_drvdata(dsi); |
347 | int ret; |
348 | |
349 | ret = mipi_dsi_detach(dsi); |
350 | if (ret < 0) |
351 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
352 | |
353 | drm_panel_remove(panel: &ctx->panel); |
354 | } |
355 | |
356 | static const struct of_device_id rm692e5_of_match[] = { |
357 | { .compatible = "fairphone,fp5-rm692e5-boe" }, |
358 | { } |
359 | }; |
360 | MODULE_DEVICE_TABLE(of, rm692e5_of_match); |
361 | |
362 | static struct mipi_dsi_driver rm692e5_driver = { |
363 | .probe = rm692e5_probe, |
364 | .remove = rm692e5_remove, |
365 | .driver = { |
366 | .name = "panel-rm692e5-boe-amoled" , |
367 | .of_match_table = rm692e5_of_match, |
368 | }, |
369 | }; |
370 | module_mipi_dsi_driver(rm692e5_driver); |
371 | |
372 | MODULE_DESCRIPTION("DRM driver for rm692e5-equipped DSI panels" ); |
373 | MODULE_LICENSE("GPL" ); |
374 | |