1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * BOE BF060Y8M-AJ0 5.99" MIPI-DSI OLED Panel on SW43404 DriverIC |
4 | * |
5 | * Copyright (c) 2020 AngeloGioacchino Del Regno |
6 | * <angelogioacchino.delregno@somainline.org> |
7 | */ |
8 | |
9 | #include <linux/backlight.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/regulator/consumer.h> |
15 | #include <video/mipi_display.h> |
16 | #include <drm/drm_mipi_dsi.h> |
17 | #include <drm/drm_modes.h> |
18 | #include <drm/drm_panel.h> |
19 | |
20 | #define DCS_ALLOW_HBM_RANGE 0x0c |
21 | #define DCS_DISALLOW_HBM_RANGE 0x08 |
22 | |
23 | enum boe_bf060y8m_aj0_supplies { |
24 | BF060Y8M_VREG_VCC, |
25 | BF060Y8M_VREG_VDDIO, |
26 | BF060Y8M_VREG_VCI, |
27 | BF060Y8M_VREG_EL_VDD, |
28 | BF060Y8M_VREG_EL_VSS, |
29 | BF060Y8M_VREG_MAX |
30 | }; |
31 | |
32 | struct boe_bf060y8m_aj0 { |
33 | struct drm_panel panel; |
34 | struct mipi_dsi_device *dsi; |
35 | struct regulator_bulk_data vregs[BF060Y8M_VREG_MAX]; |
36 | struct gpio_desc *reset_gpio; |
37 | }; |
38 | |
39 | static inline |
40 | struct boe_bf060y8m_aj0 *to_boe_bf060y8m_aj0(struct drm_panel *panel) |
41 | { |
42 | return container_of(panel, struct boe_bf060y8m_aj0, panel); |
43 | } |
44 | |
45 | static void boe_bf060y8m_aj0_reset(struct boe_bf060y8m_aj0 *boe) |
46 | { |
47 | gpiod_set_value_cansleep(desc: boe->reset_gpio, value: 0); |
48 | usleep_range(min: 2000, max: 3000); |
49 | gpiod_set_value_cansleep(desc: boe->reset_gpio, value: 1); |
50 | usleep_range(min: 15000, max: 16000); |
51 | gpiod_set_value_cansleep(desc: boe->reset_gpio, value: 0); |
52 | usleep_range(min: 5000, max: 6000); |
53 | } |
54 | |
55 | static int boe_bf060y8m_aj0_on(struct boe_bf060y8m_aj0 *boe) |
56 | { |
57 | struct mipi_dsi_device *dsi = boe->dsi; |
58 | struct device *dev = &dsi->dev; |
59 | int ret; |
60 | |
61 | mipi_dsi_dcs_write_seq(dsi, 0xb0, 0xa5, 0x00); |
62 | mipi_dsi_dcs_write_seq(dsi, 0xb2, 0x00, 0x4c); |
63 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_3D_CONTROL, 0x10); |
64 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, DCS_ALLOW_HBM_RANGE); |
65 | mipi_dsi_dcs_write_seq(dsi, 0xf8, |
66 | 0x00, 0x08, 0x10, 0x00, 0x22, 0x00, 0x00, 0x2d); |
67 | |
68 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
69 | if (ret < 0) { |
70 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
71 | return ret; |
72 | } |
73 | msleep(msecs: 30); |
74 | |
75 | mipi_dsi_dcs_write_seq(dsi, 0xb0, 0xa5, 0x00); |
76 | mipi_dsi_dcs_write_seq(dsi, 0xc0, |
77 | 0x08, 0x48, 0x65, 0x33, 0x33, 0x33, |
78 | 0x2a, 0x31, 0x39, 0x20, 0x09); |
79 | mipi_dsi_dcs_write_seq(dsi, 0xc1, 0x00, 0x00, 0x00, 0x1f, 0x1f, |
80 | 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, |
81 | 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f); |
82 | mipi_dsi_dcs_write_seq(dsi, 0xe2, 0x20, 0x04, 0x10, 0x12, 0x92, |
83 | 0x4f, 0x8f, 0x44, 0x84, 0x83, 0x83, 0x83, |
84 | 0x5c, 0x5c, 0x5c); |
85 | mipi_dsi_dcs_write_seq(dsi, 0xde, 0x01, 0x2c, 0x00, 0x77, 0x3e); |
86 | |
87 | msleep(msecs: 30); |
88 | |
89 | ret = mipi_dsi_dcs_set_display_on(dsi); |
90 | if (ret < 0) { |
91 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
92 | return ret; |
93 | } |
94 | msleep(msecs: 50); |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static int boe_bf060y8m_aj0_off(struct boe_bf060y8m_aj0 *boe) |
100 | { |
101 | struct mipi_dsi_device *dsi = boe->dsi; |
102 | struct device *dev = &dsi->dev; |
103 | int ret; |
104 | |
105 | /* OFF commands sent in HS mode */ |
106 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
107 | ret = mipi_dsi_dcs_set_display_off(dsi); |
108 | if (ret < 0) { |
109 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
110 | return ret; |
111 | } |
112 | msleep(msecs: 20); |
113 | |
114 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
115 | if (ret < 0) { |
116 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
117 | return ret; |
118 | } |
119 | usleep_range(min: 1000, max: 2000); |
120 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static int boe_bf060y8m_aj0_prepare(struct drm_panel *panel) |
126 | { |
127 | struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel); |
128 | struct device *dev = &boe->dsi->dev; |
129 | int ret; |
130 | |
131 | /* |
132 | * Enable EL Driving Voltage first - doing that at the beginning |
133 | * or at the end of the power sequence doesn't matter, so enable |
134 | * it here to avoid yet another usleep at the end. |
135 | */ |
136 | ret = regulator_enable(regulator: boe->vregs[BF060Y8M_VREG_EL_VDD].consumer); |
137 | if (ret) |
138 | return ret; |
139 | ret = regulator_enable(regulator: boe->vregs[BF060Y8M_VREG_EL_VSS].consumer); |
140 | if (ret) |
141 | goto err_elvss; |
142 | |
143 | ret = regulator_enable(regulator: boe->vregs[BF060Y8M_VREG_VCC].consumer); |
144 | if (ret) |
145 | goto err_vcc; |
146 | usleep_range(min: 1000, max: 2000); |
147 | ret = regulator_enable(regulator: boe->vregs[BF060Y8M_VREG_VDDIO].consumer); |
148 | if (ret) |
149 | goto err_vddio; |
150 | usleep_range(min: 500, max: 1000); |
151 | ret = regulator_enable(regulator: boe->vregs[BF060Y8M_VREG_VCI].consumer); |
152 | if (ret) |
153 | goto err_vci; |
154 | usleep_range(min: 2000, max: 3000); |
155 | |
156 | boe_bf060y8m_aj0_reset(boe); |
157 | |
158 | ret = boe_bf060y8m_aj0_on(boe); |
159 | if (ret < 0) { |
160 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
161 | gpiod_set_value_cansleep(desc: boe->reset_gpio, value: 1); |
162 | return ret; |
163 | } |
164 | |
165 | return 0; |
166 | |
167 | err_vci: |
168 | regulator_disable(regulator: boe->vregs[BF060Y8M_VREG_VDDIO].consumer); |
169 | err_vddio: |
170 | regulator_disable(regulator: boe->vregs[BF060Y8M_VREG_VCC].consumer); |
171 | err_vcc: |
172 | regulator_disable(regulator: boe->vregs[BF060Y8M_VREG_EL_VSS].consumer); |
173 | err_elvss: |
174 | regulator_disable(regulator: boe->vregs[BF060Y8M_VREG_EL_VDD].consumer); |
175 | return ret; |
176 | } |
177 | |
178 | static int boe_bf060y8m_aj0_unprepare(struct drm_panel *panel) |
179 | { |
180 | struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel); |
181 | struct device *dev = &boe->dsi->dev; |
182 | int ret; |
183 | |
184 | ret = boe_bf060y8m_aj0_off(boe); |
185 | if (ret < 0) |
186 | dev_err(dev, "Failed to un-initialize panel: %d\n" , ret); |
187 | |
188 | gpiod_set_value_cansleep(desc: boe->reset_gpio, value: 1); |
189 | ret = regulator_bulk_disable(ARRAY_SIZE(boe->vregs), consumers: boe->vregs); |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static const struct drm_display_mode boe_bf060y8m_aj0_mode = { |
195 | .clock = 165268, |
196 | .hdisplay = 1080, |
197 | .hsync_start = 1080 + 36, |
198 | .hsync_end = 1080 + 36 + 24, |
199 | .htotal = 1080 + 36 + 24 + 96, |
200 | .vdisplay = 2160, |
201 | .vsync_start = 2160 + 16, |
202 | .vsync_end = 2160 + 16 + 1, |
203 | .vtotal = 2160 + 16 + 1 + 15, |
204 | .width_mm = 68, /* 68.04 mm */ |
205 | .height_mm = 136, /* 136.08 mm */ |
206 | }; |
207 | |
208 | static int boe_bf060y8m_aj0_get_modes(struct drm_panel *panel, |
209 | struct drm_connector *connector) |
210 | { |
211 | struct drm_display_mode *mode; |
212 | |
213 | mode = drm_mode_duplicate(dev: connector->dev, mode: &boe_bf060y8m_aj0_mode); |
214 | if (!mode) |
215 | return -ENOMEM; |
216 | |
217 | drm_mode_set_name(mode); |
218 | |
219 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
220 | connector->display_info.width_mm = mode->width_mm; |
221 | connector->display_info.height_mm = mode->height_mm; |
222 | drm_mode_probed_add(connector, mode); |
223 | |
224 | return 1; |
225 | } |
226 | |
227 | static const struct drm_panel_funcs boe_bf060y8m_aj0_panel_funcs = { |
228 | .prepare = boe_bf060y8m_aj0_prepare, |
229 | .unprepare = boe_bf060y8m_aj0_unprepare, |
230 | .get_modes = boe_bf060y8m_aj0_get_modes, |
231 | }; |
232 | |
233 | static int boe_bf060y8m_aj0_bl_update_status(struct backlight_device *bl) |
234 | { |
235 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
236 | u16 brightness = backlight_get_brightness(bd: bl); |
237 | int ret; |
238 | |
239 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); |
240 | if (ret < 0) |
241 | return ret; |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static int boe_bf060y8m_aj0_bl_get_brightness(struct backlight_device *bl) |
247 | { |
248 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
249 | u16 brightness; |
250 | int ret; |
251 | |
252 | ret = mipi_dsi_dcs_get_display_brightness(dsi, brightness: &brightness); |
253 | if (ret < 0) |
254 | return ret; |
255 | |
256 | return brightness & 0xff; |
257 | } |
258 | |
259 | static const struct backlight_ops boe_bf060y8m_aj0_bl_ops = { |
260 | .update_status = boe_bf060y8m_aj0_bl_update_status, |
261 | .get_brightness = boe_bf060y8m_aj0_bl_get_brightness, |
262 | }; |
263 | |
264 | static struct backlight_device * |
265 | boe_bf060y8m_aj0_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 = 127, |
271 | .max_brightness = 255, |
272 | .scale = BACKLIGHT_SCALE_NON_LINEAR, |
273 | }; |
274 | |
275 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
276 | ops: &boe_bf060y8m_aj0_bl_ops, props: &props); |
277 | } |
278 | |
279 | static int boe_bf060y8m_aj0_init_vregs(struct boe_bf060y8m_aj0 *boe, |
280 | struct device *dev) |
281 | { |
282 | struct regulator *vreg; |
283 | int ret; |
284 | |
285 | boe->vregs[BF060Y8M_VREG_VCC].supply = "vcc" ; |
286 | boe->vregs[BF060Y8M_VREG_VDDIO].supply = "vddio" ; |
287 | boe->vregs[BF060Y8M_VREG_VCI].supply = "vci" ; |
288 | boe->vregs[BF060Y8M_VREG_EL_VDD].supply = "elvdd" ; |
289 | boe->vregs[BF060Y8M_VREG_EL_VSS].supply = "elvss" ; |
290 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(boe->vregs), |
291 | consumers: boe->vregs); |
292 | if (ret < 0) { |
293 | dev_err(dev, "Failed to get regulators: %d\n" , ret); |
294 | return ret; |
295 | } |
296 | |
297 | vreg = boe->vregs[BF060Y8M_VREG_VCC].consumer; |
298 | ret = regulator_is_supported_voltage(regulator: vreg, min_uV: 2700000, max_uV: 3600000); |
299 | if (!ret) |
300 | return ret; |
301 | |
302 | vreg = boe->vregs[BF060Y8M_VREG_VDDIO].consumer; |
303 | ret = regulator_is_supported_voltage(regulator: vreg, min_uV: 1620000, max_uV: 1980000); |
304 | if (!ret) |
305 | return ret; |
306 | |
307 | vreg = boe->vregs[BF060Y8M_VREG_VCI].consumer; |
308 | ret = regulator_is_supported_voltage(regulator: vreg, min_uV: 2600000, max_uV: 3600000); |
309 | if (!ret) |
310 | return ret; |
311 | |
312 | vreg = boe->vregs[BF060Y8M_VREG_EL_VDD].consumer; |
313 | ret = regulator_is_supported_voltage(regulator: vreg, min_uV: 4400000, max_uV: 4800000); |
314 | if (!ret) |
315 | return ret; |
316 | |
317 | /* ELVSS is negative: -5.00V to -1.40V */ |
318 | vreg = boe->vregs[BF060Y8M_VREG_EL_VSS].consumer; |
319 | ret = regulator_is_supported_voltage(regulator: vreg, min_uV: 1400000, max_uV: 5000000); |
320 | if (!ret) |
321 | return ret; |
322 | |
323 | /* |
324 | * Set min/max rated current, known only for VCI and VDDIO and, |
325 | * in case of failure, just go on gracefully, as this step is not |
326 | * guaranteed to succeed on all regulator HW but do a debug print |
327 | * to inform the developer during debugging. |
328 | * In any case, these two supplies are also optional, so they may |
329 | * be fixed-regulator which, at the time of writing, does not |
330 | * support fake current limiting. |
331 | */ |
332 | vreg = boe->vregs[BF060Y8M_VREG_VDDIO].consumer; |
333 | ret = regulator_set_current_limit(regulator: vreg, min_uA: 1500, max_uA: 2500); |
334 | if (ret) |
335 | dev_dbg(dev, "Current limit cannot be set on %s: %d\n" , |
336 | boe->vregs[1].supply, ret); |
337 | |
338 | vreg = boe->vregs[BF060Y8M_VREG_VCI].consumer; |
339 | ret = regulator_set_current_limit(regulator: vreg, min_uA: 20000, max_uA: 40000); |
340 | if (ret) |
341 | dev_dbg(dev, "Current limit cannot be set on %s: %d\n" , |
342 | boe->vregs[2].supply, ret); |
343 | |
344 | return 0; |
345 | } |
346 | |
347 | static int boe_bf060y8m_aj0_probe(struct mipi_dsi_device *dsi) |
348 | { |
349 | struct device *dev = &dsi->dev; |
350 | struct boe_bf060y8m_aj0 *boe; |
351 | int ret; |
352 | |
353 | boe = devm_kzalloc(dev, size: sizeof(*boe), GFP_KERNEL); |
354 | if (!boe) |
355 | return -ENOMEM; |
356 | |
357 | ret = boe_bf060y8m_aj0_init_vregs(boe, dev); |
358 | if (ret) |
359 | return dev_err_probe(dev, err: ret, |
360 | fmt: "Failed to initialize supplies.\n" ); |
361 | |
362 | boe->reset_gpio = devm_gpiod_get_optional(dev, con_id: "reset" , flags: GPIOD_ASIS); |
363 | if (IS_ERR(ptr: boe->reset_gpio)) |
364 | return dev_err_probe(dev, err: PTR_ERR(ptr: boe->reset_gpio), |
365 | fmt: "Failed to get reset-gpios\n" ); |
366 | |
367 | boe->dsi = dsi; |
368 | mipi_dsi_set_drvdata(dsi, data: boe); |
369 | |
370 | dsi->lanes = 4; |
371 | dsi->format = MIPI_DSI_FMT_RGB888; |
372 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_NO_EOT_PACKET | |
373 | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | |
374 | MIPI_DSI_CLOCK_NON_CONTINUOUS | |
375 | MIPI_DSI_MODE_LPM; |
376 | |
377 | drm_panel_init(panel: &boe->panel, dev, funcs: &boe_bf060y8m_aj0_panel_funcs, |
378 | DRM_MODE_CONNECTOR_DSI); |
379 | |
380 | boe->panel.backlight = boe_bf060y8m_aj0_create_backlight(dsi); |
381 | if (IS_ERR(ptr: boe->panel.backlight)) |
382 | return dev_err_probe(dev, err: PTR_ERR(ptr: boe->panel.backlight), |
383 | fmt: "Failed to create backlight\n" ); |
384 | |
385 | drm_panel_add(panel: &boe->panel); |
386 | |
387 | ret = mipi_dsi_attach(dsi); |
388 | if (ret < 0) { |
389 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
390 | return ret; |
391 | } |
392 | |
393 | return 0; |
394 | } |
395 | |
396 | static void boe_bf060y8m_aj0_remove(struct mipi_dsi_device *dsi) |
397 | { |
398 | struct boe_bf060y8m_aj0 *boe = mipi_dsi_get_drvdata(dsi); |
399 | int ret; |
400 | |
401 | ret = mipi_dsi_detach(dsi); |
402 | if (ret < 0) |
403 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
404 | |
405 | drm_panel_remove(panel: &boe->panel); |
406 | } |
407 | |
408 | static const struct of_device_id boe_bf060y8m_aj0_of_match[] = { |
409 | { .compatible = "boe,bf060y8m-aj0" }, |
410 | { /* sentinel */ } |
411 | }; |
412 | MODULE_DEVICE_TABLE(of, boe_bf060y8m_aj0_of_match); |
413 | |
414 | static struct mipi_dsi_driver boe_bf060y8m_aj0_driver = { |
415 | .probe = boe_bf060y8m_aj0_probe, |
416 | .remove = boe_bf060y8m_aj0_remove, |
417 | .driver = { |
418 | .name = "panel-sw43404-boe-fhd-amoled" , |
419 | .of_match_table = boe_bf060y8m_aj0_of_match, |
420 | }, |
421 | }; |
422 | module_mipi_dsi_driver(boe_bf060y8m_aj0_driver); |
423 | |
424 | MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>" ); |
425 | MODULE_DESCRIPTION("BOE BF060Y8M-AJ0 MIPI-DSI OLED panel" ); |
426 | MODULE_LICENSE("GPL v2" ); |
427 | |