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) 2024 David Wronek <david@mainlining.org> |
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/of_device.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/regulator/consumer.h> |
15 | |
16 | #include <video/mipi_display.h> |
17 | |
18 | #include <drm/drm_mipi_dsi.h> |
19 | #include <drm/drm_modes.h> |
20 | #include <drm/drm_panel.h> |
21 | #include <drm/drm_probe_helper.h> |
22 | |
23 | struct rm69380_panel { |
24 | struct drm_panel panel; |
25 | struct mipi_dsi_device *dsi[2]; |
26 | struct regulator_bulk_data supplies[2]; |
27 | struct gpio_desc *reset_gpio; |
28 | }; |
29 | |
30 | static inline |
31 | struct rm69380_panel *to_rm69380_panel(struct drm_panel *panel) |
32 | { |
33 | return container_of(panel, struct rm69380_panel, panel); |
34 | } |
35 | |
36 | static void rm69380_reset(struct rm69380_panel *ctx) |
37 | { |
38 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
39 | usleep_range(min: 15000, max: 16000); |
40 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
41 | usleep_range(min: 10000, max: 11000); |
42 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
43 | msleep(msecs: 30); |
44 | } |
45 | |
46 | static int rm69380_on(struct rm69380_panel *ctx) |
47 | { |
48 | struct mipi_dsi_device *dsi = ctx->dsi[0]; |
49 | struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; |
50 | |
51 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
52 | if (ctx->dsi[1]) |
53 | ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; |
54 | |
55 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd4); |
56 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x80); |
57 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0xd0); |
58 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x00); |
59 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x26); |
60 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x3f); |
61 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1d, 0x1a); |
62 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x00); |
63 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x28); |
64 | mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc2, 0x08); |
65 | |
66 | mipi_dsi_dcs_set_tear_on_multi(ctx: &dsi_ctx, mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
67 | mipi_dsi_dcs_exit_sleep_mode_multi(ctx: &dsi_ctx); |
68 | mipi_dsi_msleep(&dsi_ctx, 20); |
69 | |
70 | mipi_dsi_dcs_set_display_on_multi(ctx: &dsi_ctx); |
71 | mipi_dsi_msleep(&dsi_ctx, 36); |
72 | |
73 | return dsi_ctx.accum_err; |
74 | } |
75 | |
76 | static void rm69380_off(struct rm69380_panel *ctx) |
77 | { |
78 | struct mipi_dsi_device *dsi = ctx->dsi[0]; |
79 | struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; |
80 | |
81 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
82 | if (ctx->dsi[1]) |
83 | ctx->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM; |
84 | |
85 | mipi_dsi_dcs_set_display_off_multi(ctx: &dsi_ctx); |
86 | mipi_dsi_msleep(&dsi_ctx, 35); |
87 | mipi_dsi_dcs_enter_sleep_mode_multi(ctx: &dsi_ctx); |
88 | mipi_dsi_msleep(&dsi_ctx, 20); |
89 | } |
90 | |
91 | static int rm69380_prepare(struct drm_panel *panel) |
92 | { |
93 | struct rm69380_panel *ctx = to_rm69380_panel(panel); |
94 | int ret; |
95 | |
96 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
97 | if (ret < 0) |
98 | return ret; |
99 | |
100 | rm69380_reset(ctx); |
101 | |
102 | ret = rm69380_on(ctx); |
103 | if (ret < 0) { |
104 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
105 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
106 | } |
107 | |
108 | return ret; |
109 | } |
110 | |
111 | static int rm69380_unprepare(struct drm_panel *panel) |
112 | { |
113 | struct rm69380_panel *ctx = to_rm69380_panel(panel); |
114 | |
115 | rm69380_off(ctx); |
116 | |
117 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
118 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static const struct drm_display_mode rm69380_mode = { |
124 | .clock = (2560 + 32 + 12 + 38) * (1600 + 20 + 4 + 8) * 90 / 1000, |
125 | .hdisplay = 2560, |
126 | .hsync_start = 2560 + 32, |
127 | .hsync_end = 2560 + 32 + 12, |
128 | .htotal = 2560 + 32 + 12 + 38, |
129 | .vdisplay = 1600, |
130 | .vsync_start = 1600 + 20, |
131 | .vsync_end = 1600 + 20 + 4, |
132 | .vtotal = 1600 + 20 + 4 + 8, |
133 | .width_mm = 248, |
134 | .height_mm = 155, |
135 | .type = DRM_MODE_TYPE_DRIVER, |
136 | }; |
137 | |
138 | static int rm69380_get_modes(struct drm_panel *panel, |
139 | struct drm_connector *connector) |
140 | { |
141 | return drm_connector_helper_get_modes_fixed(connector, fixed_mode: &rm69380_mode); |
142 | } |
143 | |
144 | static const struct drm_panel_funcs rm69380_panel_funcs = { |
145 | .prepare = rm69380_prepare, |
146 | .unprepare = rm69380_unprepare, |
147 | .get_modes = rm69380_get_modes, |
148 | }; |
149 | |
150 | static int rm69380_bl_update_status(struct backlight_device *bl) |
151 | { |
152 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
153 | u16 brightness = backlight_get_brightness(bd: bl); |
154 | int ret; |
155 | |
156 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
157 | |
158 | ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); |
159 | if (ret < 0) |
160 | return ret; |
161 | |
162 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
163 | |
164 | return 0; |
165 | } |
166 | |
167 | static int rm69380_bl_get_brightness(struct backlight_device *bl) |
168 | { |
169 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
170 | u16 brightness; |
171 | int ret; |
172 | |
173 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
174 | |
175 | ret = mipi_dsi_dcs_get_display_brightness_large(dsi, brightness: &brightness); |
176 | if (ret < 0) |
177 | return ret; |
178 | |
179 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
180 | |
181 | return brightness; |
182 | } |
183 | |
184 | static const struct backlight_ops rm69380_bl_ops = { |
185 | .update_status = rm69380_bl_update_status, |
186 | .get_brightness = rm69380_bl_get_brightness, |
187 | }; |
188 | |
189 | static struct backlight_device * |
190 | rm69380_create_backlight(struct mipi_dsi_device *dsi) |
191 | { |
192 | struct device *dev = &dsi->dev; |
193 | const struct backlight_properties props = { |
194 | .type = BACKLIGHT_RAW, |
195 | .brightness = 511, |
196 | .max_brightness = 2047, |
197 | }; |
198 | |
199 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
200 | ops: &rm69380_bl_ops, props: &props); |
201 | } |
202 | |
203 | static int rm69380_probe(struct mipi_dsi_device *dsi) |
204 | { |
205 | struct mipi_dsi_host *dsi_sec_host; |
206 | struct rm69380_panel *ctx; |
207 | struct device *dev = &dsi->dev; |
208 | struct device_node *dsi_sec; |
209 | int ret, i; |
210 | |
211 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
212 | if (!ctx) |
213 | return -ENOMEM; |
214 | |
215 | ctx->supplies[0].supply = "vddio" ; |
216 | ctx->supplies[1].supply = "avdd" ; |
217 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
218 | consumers: ctx->supplies); |
219 | if (ret < 0) |
220 | return dev_err_probe(dev, err: ret, fmt: "Failed to get regulators\n" ); |
221 | |
222 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
223 | if (IS_ERR(ptr: ctx->reset_gpio)) |
224 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), |
225 | fmt: "Failed to get reset-gpios\n" ); |
226 | |
227 | dsi_sec = of_graph_get_remote_node(node: dsi->dev.of_node, port: 1, endpoint: -1); |
228 | |
229 | if (dsi_sec) { |
230 | const struct mipi_dsi_device_info info = { "RM69380 DSI1" , 0, |
231 | dsi_sec }; |
232 | |
233 | dsi_sec_host = of_find_mipi_dsi_host_by_node(node: dsi_sec); |
234 | of_node_put(node: dsi_sec); |
235 | if (!dsi_sec_host) |
236 | return dev_err_probe(dev, err: -EPROBE_DEFER, |
237 | fmt: "Cannot get secondary DSI host\n" ); |
238 | |
239 | ctx->dsi[1] = |
240 | devm_mipi_dsi_device_register_full(dev, host: dsi_sec_host, info: &info); |
241 | if (IS_ERR(ptr: ctx->dsi[1])) |
242 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->dsi[1]), |
243 | fmt: "Cannot get secondary DSI node\n" ); |
244 | |
245 | mipi_dsi_set_drvdata(dsi: ctx->dsi[1], data: ctx); |
246 | } |
247 | |
248 | ctx->dsi[0] = dsi; |
249 | mipi_dsi_set_drvdata(dsi, data: ctx); |
250 | |
251 | drm_panel_init(panel: &ctx->panel, dev, funcs: &rm69380_panel_funcs, |
252 | DRM_MODE_CONNECTOR_DSI); |
253 | ctx->panel.prepare_prev_first = true; |
254 | |
255 | ctx->panel.backlight = rm69380_create_backlight(dsi); |
256 | if (IS_ERR(ptr: ctx->panel.backlight)) |
257 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->panel.backlight), |
258 | fmt: "Failed to create backlight\n" ); |
259 | |
260 | drm_panel_add(panel: &ctx->panel); |
261 | |
262 | for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { |
263 | if (!ctx->dsi[i]) |
264 | continue; |
265 | |
266 | dev_dbg(&ctx->dsi[i]->dev, "Binding DSI %d\n" , i); |
267 | |
268 | ctx->dsi[i]->lanes = 4; |
269 | ctx->dsi[i]->format = MIPI_DSI_FMT_RGB888; |
270 | ctx->dsi[i]->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | |
271 | MIPI_DSI_CLOCK_NON_CONTINUOUS; |
272 | |
273 | ret = devm_mipi_dsi_attach(dev, dsi: ctx->dsi[i]); |
274 | if (ret < 0) { |
275 | drm_panel_remove(panel: &ctx->panel); |
276 | return dev_err_probe(dev, err: ret, |
277 | fmt: "Failed to attach to DSI%d\n" , i); |
278 | } |
279 | } |
280 | |
281 | return 0; |
282 | } |
283 | |
284 | static void rm69380_remove(struct mipi_dsi_device *dsi) |
285 | { |
286 | struct rm69380_panel *ctx = mipi_dsi_get_drvdata(dsi); |
287 | |
288 | drm_panel_remove(panel: &ctx->panel); |
289 | } |
290 | |
291 | static const struct of_device_id rm69380_of_match[] = { |
292 | { .compatible = "lenovo,j716f-edo-rm69380" }, |
293 | { /* sentinel */ } |
294 | }; |
295 | MODULE_DEVICE_TABLE(of, rm69380_of_match); |
296 | |
297 | static struct mipi_dsi_driver rm69380_panel_driver = { |
298 | .probe = rm69380_probe, |
299 | .remove = rm69380_remove, |
300 | .driver = { |
301 | .name = "panel-raydium-rm69380" , |
302 | .of_match_table = rm69380_of_match, |
303 | }, |
304 | }; |
305 | module_mipi_dsi_driver(rm69380_panel_driver); |
306 | |
307 | MODULE_AUTHOR("David Wronek <david@mainlining.org" ); |
308 | MODULE_DESCRIPTION("DRM driver for Raydium RM69380-equipped DSI panels" ); |
309 | MODULE_LICENSE("GPL" ); |
310 | |