]>
Commit | Line | Data |
---|---|---|
1f47e6cb NT |
1 | /* |
2 | * DRM driver for Multi-Inno MI0283QT panels | |
3 | * | |
4 | * Copyright 2016 Noralf Trønnes | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | ||
d1a2e700 | 12 | #include <linux/backlight.h> |
1f47e6cb NT |
13 | #include <linux/delay.h> |
14 | #include <linux/gpio/consumer.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/property.h> | |
17 | #include <linux/regulator/consumer.h> | |
18 | #include <linux/spi/spi.h> | |
2a678996 | 19 | |
84056e9b | 20 | #include <drm/drm_drv.h> |
3db8d37d | 21 | #include <drm/drm_gem_cma_helper.h> |
ccc3b2b3 | 22 | #include <drm/drm_gem_framebuffer_helper.h> |
3db8d37d | 23 | #include <drm/drm_modeset_helper.h> |
2a678996 NT |
24 | #include <drm/tinydrm/mipi-dbi.h> |
25 | #include <drm/tinydrm/tinydrm-helpers.h> | |
1f47e6cb NT |
26 | #include <video/mipi_display.h> |
27 | ||
24e05e7a NT |
28 | #define ILI9341_FRMCTR1 0xb1 |
29 | #define ILI9341_DISCTRL 0xb6 | |
30 | #define ILI9341_ETMOD 0xb7 | |
31 | ||
32 | #define ILI9341_PWCTRL1 0xc0 | |
33 | #define ILI9341_PWCTRL2 0xc1 | |
34 | #define ILI9341_VMCTRL1 0xc5 | |
35 | #define ILI9341_VMCTRL2 0xc7 | |
36 | #define ILI9341_PWCTRLA 0xcb | |
37 | #define ILI9341_PWCTRLB 0xcf | |
38 | ||
39 | #define ILI9341_PGAMCTRL 0xe0 | |
40 | #define ILI9341_NGAMCTRL 0xe1 | |
41 | #define ILI9341_DTCTRLA 0xe8 | |
42 | #define ILI9341_DTCTRLB 0xea | |
43 | #define ILI9341_PWRSEQ 0xed | |
44 | ||
45 | #define ILI9341_EN3GAM 0xf2 | |
46 | #define ILI9341_PUMPCTRL 0xf7 | |
47 | ||
48 | #define ILI9341_MADCTL_BGR BIT(3) | |
49 | #define ILI9341_MADCTL_MV BIT(5) | |
50 | #define ILI9341_MADCTL_MX BIT(6) | |
51 | #define ILI9341_MADCTL_MY BIT(7) | |
52 | ||
f730eceb | 53 | static void mi0283qt_enable(struct drm_simple_display_pipe *pipe, |
0c9c7fd0 VS |
54 | struct drm_crtc_state *crtc_state, |
55 | struct drm_plane_state *plane_state) | |
1f47e6cb | 56 | { |
f730eceb NT |
57 | struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); |
58 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); | |
1f47e6cb NT |
59 | u8 addr_mode; |
60 | int ret; | |
61 | ||
62 | DRM_DEBUG_KMS("\n"); | |
63 | ||
070ab128 NT |
64 | ret = mipi_dbi_poweron_conditional_reset(mipi); |
65 | if (ret < 0) | |
f730eceb | 66 | return; |
070ab128 | 67 | if (ret == 1) |
f730eceb | 68 | goto out_enable; |
1f47e6cb | 69 | |
1f47e6cb NT |
70 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF); |
71 | ||
72 | mipi_dbi_command(mipi, ILI9341_PWCTRLB, 0x00, 0x83, 0x30); | |
73 | mipi_dbi_command(mipi, ILI9341_PWRSEQ, 0x64, 0x03, 0x12, 0x81); | |
74 | mipi_dbi_command(mipi, ILI9341_DTCTRLA, 0x85, 0x01, 0x79); | |
75 | mipi_dbi_command(mipi, ILI9341_PWCTRLA, 0x39, 0x2c, 0x00, 0x34, 0x02); | |
76 | mipi_dbi_command(mipi, ILI9341_PUMPCTRL, 0x20); | |
77 | mipi_dbi_command(mipi, ILI9341_DTCTRLB, 0x00, 0x00); | |
78 | ||
79 | /* Power Control */ | |
80 | mipi_dbi_command(mipi, ILI9341_PWCTRL1, 0x26); | |
81 | mipi_dbi_command(mipi, ILI9341_PWCTRL2, 0x11); | |
82 | /* VCOM */ | |
83 | mipi_dbi_command(mipi, ILI9341_VMCTRL1, 0x35, 0x3e); | |
84 | mipi_dbi_command(mipi, ILI9341_VMCTRL2, 0xbe); | |
85 | ||
86 | /* Memory Access Control */ | |
24e05e7a | 87 | mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT); |
1f47e6cb | 88 | |
1f47e6cb NT |
89 | /* Frame Rate */ |
90 | mipi_dbi_command(mipi, ILI9341_FRMCTR1, 0x00, 0x1b); | |
91 | ||
92 | /* Gamma */ | |
93 | mipi_dbi_command(mipi, ILI9341_EN3GAM, 0x08); | |
94 | mipi_dbi_command(mipi, MIPI_DCS_SET_GAMMA_CURVE, 0x01); | |
95 | mipi_dbi_command(mipi, ILI9341_PGAMCTRL, | |
96 | 0x1f, 0x1a, 0x18, 0x0a, 0x0f, 0x06, 0x45, 0x87, | |
97 | 0x32, 0x0a, 0x07, 0x02, 0x07, 0x05, 0x00); | |
98 | mipi_dbi_command(mipi, ILI9341_NGAMCTRL, | |
99 | 0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3a, 0x78, | |
100 | 0x4d, 0x05, 0x18, 0x0d, 0x38, 0x3a, 0x1f); | |
101 | ||
102 | /* DDRAM */ | |
103 | mipi_dbi_command(mipi, ILI9341_ETMOD, 0x07); | |
104 | ||
105 | /* Display */ | |
106 | mipi_dbi_command(mipi, ILI9341_DISCTRL, 0x0a, 0x82, 0x27, 0x00); | |
107 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE); | |
108 | msleep(100); | |
109 | ||
110 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON); | |
111 | msleep(100); | |
112 | ||
f730eceb | 113 | out_enable: |
9f99963a TC |
114 | /* The PiTFT (ili9340) has a hardware reset circuit that |
115 | * resets only on power-on and not on each reboot through | |
116 | * a gpio like the rpi-display does. | |
117 | * As a result, we need to always apply the rotation value | |
118 | * regardless of the display "on/off" state. | |
119 | */ | |
120 | switch (mipi->rotation) { | |
121 | default: | |
122 | addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY | | |
123 | ILI9341_MADCTL_MX; | |
124 | break; | |
125 | case 90: | |
126 | addr_mode = ILI9341_MADCTL_MY; | |
127 | break; | |
128 | case 180: | |
129 | addr_mode = ILI9341_MADCTL_MV; | |
130 | break; | |
131 | case 270: | |
132 | addr_mode = ILI9341_MADCTL_MX; | |
133 | break; | |
134 | } | |
135 | addr_mode |= ILI9341_MADCTL_BGR; | |
136 | mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); | |
e85d3006 | 137 | mipi_dbi_enable_flush(mipi, crtc_state, plane_state); |
1f47e6cb NT |
138 | } |
139 | ||
140 | static const struct drm_simple_display_pipe_funcs mi0283qt_pipe_funcs = { | |
f730eceb | 141 | .enable = mi0283qt_enable, |
1f47e6cb | 142 | .disable = mipi_dbi_pipe_disable, |
af741381 | 143 | .update = mipi_dbi_pipe_update, |
ccc3b2b3 | 144 | .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, |
1f47e6cb NT |
145 | }; |
146 | ||
147 | static const struct drm_display_mode mi0283qt_mode = { | |
148 | TINYDRM_MODE(320, 240, 58, 43), | |
149 | }; | |
150 | ||
79b85d2b NT |
151 | DEFINE_DRM_GEM_CMA_FOPS(mi0283qt_fops); |
152 | ||
1f47e6cb NT |
153 | static struct drm_driver mi0283qt_driver = { |
154 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | | |
155 | DRIVER_ATOMIC, | |
79b85d2b | 156 | .fops = &mi0283qt_fops, |
3db8d37d | 157 | DRM_GEM_CMA_VMAP_DRIVER_OPS, |
1f47e6cb NT |
158 | .debugfs_init = mipi_dbi_debugfs_init, |
159 | .name = "mi0283qt", | |
160 | .desc = "Multi-Inno MI0283QT", | |
161 | .date = "20160614", | |
162 | .major = 1, | |
163 | .minor = 0, | |
164 | }; | |
165 | ||
166 | static const struct of_device_id mi0283qt_of_match[] = { | |
167 | { .compatible = "multi-inno,mi0283qt" }, | |
168 | {}, | |
169 | }; | |
170 | MODULE_DEVICE_TABLE(of, mi0283qt_of_match); | |
171 | ||
172 | static const struct spi_device_id mi0283qt_id[] = { | |
173 | { "mi0283qt", 0 }, | |
174 | { }, | |
175 | }; | |
176 | MODULE_DEVICE_TABLE(spi, mi0283qt_id); | |
177 | ||
178 | static int mi0283qt_probe(struct spi_device *spi) | |
179 | { | |
180 | struct device *dev = &spi->dev; | |
1f47e6cb NT |
181 | struct mipi_dbi *mipi; |
182 | struct gpio_desc *dc; | |
183 | u32 rotation = 0; | |
184 | int ret; | |
185 | ||
186 | mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL); | |
187 | if (!mipi) | |
188 | return -ENOMEM; | |
189 | ||
190 | mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); | |
191 | if (IS_ERR(mipi->reset)) { | |
e43e8181 | 192 | DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n"); |
1f47e6cb NT |
193 | return PTR_ERR(mipi->reset); |
194 | } | |
195 | ||
196 | dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW); | |
197 | if (IS_ERR(dc)) { | |
e43e8181 | 198 | DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n"); |
1f47e6cb NT |
199 | return PTR_ERR(dc); |
200 | } | |
201 | ||
202 | mipi->regulator = devm_regulator_get(dev, "power"); | |
203 | if (IS_ERR(mipi->regulator)) | |
204 | return PTR_ERR(mipi->regulator); | |
205 | ||
27f6640c | 206 | mipi->backlight = devm_of_find_backlight(dev); |
1f47e6cb NT |
207 | if (IS_ERR(mipi->backlight)) |
208 | return PTR_ERR(mipi->backlight); | |
209 | ||
210 | device_property_read_u32(dev, "rotation", &rotation); | |
211 | ||
ace98812 DL |
212 | ret = mipi_dbi_spi_init(spi, mipi, dc); |
213 | if (ret) | |
214 | return ret; | |
215 | ||
216 | ret = mipi_dbi_init(&spi->dev, mipi, &mi0283qt_pipe_funcs, | |
217 | &mi0283qt_driver, &mi0283qt_mode, rotation); | |
1f47e6cb NT |
218 | if (ret) |
219 | return ret; | |
220 | ||
1f47e6cb NT |
221 | spi_set_drvdata(spi, mipi); |
222 | ||
95a0cfe9 | 223 | return devm_tinydrm_register(&mipi->tinydrm); |
1f47e6cb NT |
224 | } |
225 | ||
226 | static void mi0283qt_shutdown(struct spi_device *spi) | |
227 | { | |
228 | struct mipi_dbi *mipi = spi_get_drvdata(spi); | |
229 | ||
230 | tinydrm_shutdown(&mipi->tinydrm); | |
231 | } | |
232 | ||
233 | static int __maybe_unused mi0283qt_pm_suspend(struct device *dev) | |
234 | { | |
235 | struct mipi_dbi *mipi = dev_get_drvdata(dev); | |
1f47e6cb | 236 | |
f730eceb | 237 | return drm_mode_config_helper_suspend(mipi->tinydrm.drm); |
1f47e6cb NT |
238 | } |
239 | ||
240 | static int __maybe_unused mi0283qt_pm_resume(struct device *dev) | |
241 | { | |
242 | struct mipi_dbi *mipi = dev_get_drvdata(dev); | |
1f47e6cb | 243 | |
6e8e9a01 NT |
244 | drm_mode_config_helper_resume(mipi->tinydrm.drm); |
245 | ||
246 | return 0; | |
1f47e6cb NT |
247 | } |
248 | ||
249 | static const struct dev_pm_ops mi0283qt_pm_ops = { | |
250 | SET_SYSTEM_SLEEP_PM_OPS(mi0283qt_pm_suspend, mi0283qt_pm_resume) | |
251 | }; | |
252 | ||
253 | static struct spi_driver mi0283qt_spi_driver = { | |
254 | .driver = { | |
255 | .name = "mi0283qt", | |
256 | .owner = THIS_MODULE, | |
257 | .of_match_table = mi0283qt_of_match, | |
258 | .pm = &mi0283qt_pm_ops, | |
259 | }, | |
260 | .id_table = mi0283qt_id, | |
261 | .probe = mi0283qt_probe, | |
262 | .shutdown = mi0283qt_shutdown, | |
263 | }; | |
264 | module_spi_driver(mi0283qt_spi_driver); | |
265 | ||
266 | MODULE_DESCRIPTION("Multi-Inno MI0283QT DRM driver"); | |
267 | MODULE_AUTHOR("Noralf Trønnes"); | |
268 | MODULE_LICENSE("GPL"); |