1//SPDX-License-Identifier: GPL-2.0-only
2//Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
3
4#include <linux/backlight.h>
5#include <linux/delay.h>
6#include <linux/gpio/consumer.h>
7#include <linux/module.h>
8#include <linux/of.h>
9#include <linux/regulator/consumer.h>
10
11#include <drm/drm_mipi_dsi.h>
12#include <drm/drm_probe_helper.h>
13#include <drm/drm_modes.h>
14#include <drm/drm_panel.h>
15#include <drm/display/drm_dsc.h>
16#include <drm/display/drm_dsc_helper.h>
17
18#include <video/mipi_display.h>
19
20struct visionox_r66451 {
21 struct drm_panel panel;
22 struct mipi_dsi_device *dsi;
23 struct gpio_desc *reset_gpio;
24 struct regulator_bulk_data supplies[2];
25};
26
27static inline struct visionox_r66451 *to_visionox_r66451(struct drm_panel *panel)
28{
29 return container_of(panel, struct visionox_r66451, panel);
30}
31
32static void visionox_r66451_reset(struct visionox_r66451 *ctx)
33{
34 gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0);
35 usleep_range(min: 10000, max: 10100);
36 gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1);
37 usleep_range(min: 10000, max: 10100);
38 gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0);
39 usleep_range(min: 10000, max: 10100);
40}
41
42static int visionox_r66451_on(struct visionox_r66451 *ctx)
43{
44 struct mipi_dsi_device *dsi = ctx->dsi;
45
46 dsi->mode_flags |= MIPI_DSI_MODE_LPM;
47
48 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
49 mipi_dsi_dcs_write_seq(dsi, 0xc2,
50 0x09, 0x24, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
51 0x09, 0x3c);
52 mipi_dsi_dcs_write_seq(dsi, 0xd7,
53 0x00, 0xb9, 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
54 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
55 0x3c, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
56 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
57 mipi_dsi_dcs_write_seq(dsi, 0xde,
58 0x40, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18,
59 0x10, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x02, 0x00, 0x00);
60 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
61 mipi_dsi_dcs_write_seq(dsi, 0xe8, 0x00, 0x02);
62 mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x00, 0x08);
63 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x00);
64 mipi_dsi_dcs_write_seq(dsi, 0xc4,
65 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x32);
67 mipi_dsi_dcs_write_seq(dsi, 0xcf,
68 0x64, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
69 0x00, 0x0b, 0x77, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
70 0x02, 0x02, 0x02, 0x02, 0x02, 0x03);
71 mipi_dsi_dcs_write_seq(dsi, 0xd3,
72 0x45, 0x00, 0x00, 0x01, 0x13, 0x15, 0x00, 0x15, 0x07,
73 0x0f, 0x77, 0x77, 0x77, 0x37, 0xb2, 0x11, 0x00, 0xa0,
74 0x3c, 0x9c);
75 mipi_dsi_dcs_write_seq(dsi, 0xd7,
76 0x00, 0xb9, 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a,
77 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
78 0x34, 0x00, 0x40, 0x04, 0x00, 0xa0, 0x0a);
79 mipi_dsi_dcs_write_seq(dsi, 0xd8,
80 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
81 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x3a,
82 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
83 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
84 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
85 0x00, 0x32, 0x00, 0x0a, 0x00, 0x22);
86 mipi_dsi_dcs_write_seq(dsi, 0xdf,
87 0x50, 0x42, 0x58, 0x81, 0x2d, 0x00, 0x00, 0x00, 0x00,
88 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
89 0x00, 0x00, 0x01, 0x0f, 0xff, 0xd4, 0x0e, 0x00, 0x00,
90 0x00, 0x00, 0x00, 0x00, 0x0f, 0x53, 0xf1, 0x00, 0x00,
91 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
92 mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x01);
93 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x80);
94 mipi_dsi_dcs_write_seq(dsi, 0xe4, 0x34, 0xb4, 0x00, 0x00, 0x00, 0x39, 0x04, 0x09, 0x34);
95 mipi_dsi_dcs_write_seq(dsi, 0xe6, 0x00);
96 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0x04);
97 mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x40);
98 mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x50, 0x00, 0x00, 0x00, 0x00);
99 mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x11);
100 mipi_dsi_dcs_write_seq(dsi, 0xf3, 0x01, 0x00, 0x00, 0x00, 0x01);
101 mipi_dsi_dcs_write_seq(dsi, 0xf4, 0x00, 0x02);
102 mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x19);
103 mipi_dsi_dcs_write_seq(dsi, 0xdf, 0x50, 0x42);
104 mipi_dsi_dcs_set_tear_on(dsi, mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK);
105 mipi_dsi_dcs_set_column_address(dsi, start: 0, end: 1080 - 1);
106 mipi_dsi_dcs_set_page_address(dsi, start: 0, end: 2340 - 1);
107
108 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
109
110 return 0;
111}
112
113static int visionox_r66451_off(struct visionox_r66451 *ctx)
114{
115 ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
116 return 0;
117}
118
119static int visionox_r66451_prepare(struct drm_panel *panel)
120{
121 struct visionox_r66451 *ctx = to_visionox_r66451(panel);
122 struct mipi_dsi_device *dsi = ctx->dsi;
123 struct device *dev = &dsi->dev;
124 int ret;
125
126 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies),
127 consumers: ctx->supplies);
128 if (ret < 0)
129 return ret;
130
131 visionox_r66451_reset(ctx);
132
133 ret = visionox_r66451_on(ctx);
134 if (ret < 0) {
135 dev_err(dev, "Failed to initialize panel: %d\n", ret);
136 gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1);
137 regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies);
138 return ret;
139 }
140
141 mipi_dsi_compression_mode(dsi: ctx->dsi, enable: true);
142
143 return 0;
144}
145
146static int visionox_r66451_unprepare(struct drm_panel *panel)
147{
148 struct visionox_r66451 *ctx = to_visionox_r66451(panel);
149 struct device *dev = &ctx->dsi->dev;
150 int ret;
151
152 ret = visionox_r66451_off(ctx);
153 if (ret < 0)
154 dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
155
156 gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1);
157 regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies);
158
159 return 0;
160}
161
162static const struct drm_display_mode visionox_r66451_mode = {
163 .clock = 345830,
164 .hdisplay = 1080,
165 .hsync_start = 1175,
166 .hsync_end = 1176,
167 .htotal = 1216,
168 .vdisplay = 2340,
169 .vsync_start = 2365,
170 .vsync_end = 2366,
171 .vtotal = 2370,
172 .width_mm = 0,
173 .height_mm = 0,
174 .type = DRM_MODE_TYPE_DRIVER,
175};
176
177static int visionox_r66451_enable(struct drm_panel *panel)
178{
179 struct visionox_r66451 *ctx = to_visionox_r66451(panel);
180 struct mipi_dsi_device *dsi = ctx->dsi;
181 struct drm_dsc_picture_parameter_set pps;
182 int ret;
183
184 if (!dsi->dsc) {
185 dev_err(&dsi->dev, "DSC not attached to DSI\n");
186 return -ENODEV;
187 }
188
189 drm_dsc_pps_payload_pack(pps_sdp: &pps, dsc_cfg: dsi->dsc);
190 ret = mipi_dsi_picture_parameter_set(dsi, pps: &pps);
191 if (ret) {
192 dev_err(&dsi->dev, "Failed to set PPS\n");
193 return ret;
194 }
195
196 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
197 if (ret < 0) {
198 dev_err(&dsi->dev, "Failed to exit sleep mode: %d\n", ret);
199 return ret;
200 }
201 msleep(msecs: 120);
202
203 ret = mipi_dsi_dcs_set_display_on(dsi);
204 if (ret < 0) {
205 dev_err(&dsi->dev, "Failed on set display on: %d\n", ret);
206 return ret;
207 }
208 msleep(msecs: 20);
209
210 return 0;
211}
212
213static int visionox_r66451_disable(struct drm_panel *panel)
214{
215 struct visionox_r66451 *ctx = to_visionox_r66451(panel);
216 struct mipi_dsi_device *dsi = ctx->dsi;
217 struct device *dev = &dsi->dev;
218 int ret;
219
220 ret = mipi_dsi_dcs_set_display_off(dsi);
221 if (ret < 0) {
222 dev_err(dev, "Failed to set display off: %d\n", ret);
223 return ret;
224 }
225 msleep(msecs: 20);
226
227 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
228 if (ret < 0) {
229 dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
230 return ret;
231 }
232 msleep(msecs: 120);
233
234 return 0;
235}
236
237static int visionox_r66451_get_modes(struct drm_panel *panel,
238 struct drm_connector *connector)
239{
240 drm_connector_helper_get_modes_fixed(connector, fixed_mode: &visionox_r66451_mode);
241 return 1;
242}
243
244static const struct drm_panel_funcs visionox_r66451_funcs = {
245 .prepare = visionox_r66451_prepare,
246 .unprepare = visionox_r66451_unprepare,
247 .get_modes = visionox_r66451_get_modes,
248 .enable = visionox_r66451_enable,
249 .disable = visionox_r66451_disable,
250};
251
252static int visionox_r66451_bl_update_status(struct backlight_device *bl)
253{
254 struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl);
255 u16 brightness = backlight_get_brightness(bd: bl);
256
257 return mipi_dsi_dcs_set_display_brightness(dsi, brightness);
258}
259
260static const struct backlight_ops visionox_r66451_bl_ops = {
261 .update_status = visionox_r66451_bl_update_status,
262};
263
264static struct backlight_device *
265visionox_r66451_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 = 255,
271 .max_brightness = 4095,
272 };
273
274 return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi,
275 ops: &visionox_r66451_bl_ops, props: &props);
276}
277
278static int visionox_r66451_probe(struct mipi_dsi_device *dsi)
279{
280 struct device *dev = &dsi->dev;
281 struct visionox_r66451 *ctx;
282 struct drm_dsc_config *dsc;
283 int ret = 0;
284
285 ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL);
286 if (!ctx)
287 return -ENOMEM;
288
289 dsc = devm_kzalloc(dev, size: sizeof(*dsc), GFP_KERNEL);
290 if (!dsc)
291 return -ENOMEM;
292
293 /* Set DSC params */
294 dsc->dsc_version_major = 0x1;
295 dsc->dsc_version_minor = 0x2;
296
297 dsc->slice_height = 20;
298 dsc->slice_width = 540;
299 dsc->slice_count = 2;
300 dsc->bits_per_component = 8;
301 dsc->bits_per_pixel = 8 << 4;
302 dsc->block_pred_enable = true;
303
304 dsi->dsc = dsc;
305
306 ctx->supplies[0].supply = "vddio";
307 ctx->supplies[1].supply = "vdd";
308
309 ret = devm_regulator_bulk_get(dev: &dsi->dev, ARRAY_SIZE(ctx->supplies),
310 consumers: ctx->supplies);
311
312 if (ret < 0)
313 return ret;
314
315 ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset", flags: GPIOD_OUT_LOW);
316 if (IS_ERR(ptr: ctx->reset_gpio))
317 return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->reset_gpio), fmt: "Failed to get reset-gpios\n");
318
319 ctx->dsi = dsi;
320 mipi_dsi_set_drvdata(dsi, data: ctx);
321
322 dsi->lanes = 4;
323 dsi->format = MIPI_DSI_FMT_RGB888;
324 dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
325 ctx->panel.prepare_prev_first = true;
326
327 drm_panel_init(panel: &ctx->panel, dev, funcs: &visionox_r66451_funcs, DRM_MODE_CONNECTOR_DSI);
328 ctx->panel.backlight = visionox_r66451_create_backlight(dsi);
329 if (IS_ERR(ptr: ctx->panel.backlight))
330 return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->panel.backlight),
331 fmt: "Failed to create backlight\n");
332
333 drm_panel_add(panel: &ctx->panel);
334
335 ret = mipi_dsi_attach(dsi);
336 if (ret < 0) {
337 dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
338 drm_panel_remove(panel: &ctx->panel);
339 }
340
341 return ret;
342}
343
344static void visionox_r66451_remove(struct mipi_dsi_device *dsi)
345{
346 struct visionox_r66451 *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 DSI host: %d\n", ret);
352
353 drm_panel_remove(panel: &ctx->panel);
354}
355
356static const struct of_device_id visionox_r66451_of_match[] = {
357 {.compatible = "visionox,r66451"},
358 { /*sentinel*/ }
359};
360MODULE_DEVICE_TABLE(of, visionox_r66451_of_match);
361
362static struct mipi_dsi_driver visionox_r66451_driver = {
363 .probe = visionox_r66451_probe,
364 .remove = visionox_r66451_remove,
365 .driver = {
366 .name = "panel-visionox-r66451",
367 .of_match_table = visionox_r66451_of_match,
368 },
369};
370
371module_mipi_dsi_driver(visionox_r66451_driver);
372
373MODULE_AUTHOR("Jessica Zhang <quic_jesszhan@quicinc.com>");
374MODULE_DESCRIPTION("Panel driver for the Visionox R66451 AMOLED DSI panel");
375MODULE_LICENSE("GPL");
376

source code of linux/drivers/gpu/drm/panel/panel-visionox-r66451.c