1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Panel driver for the Samsung S6D27A1 480x800 DPI RGB panel. |
4 | * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone. |
5 | */ |
6 | |
7 | #include <drm/drm_mipi_dbi.h> |
8 | #include <drm/drm_modes.h> |
9 | #include <drm/drm_panel.h> |
10 | |
11 | #include <linux/delay.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/init.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/media-bus-format.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/regulator/consumer.h> |
19 | #include <linux/spi/spi.h> |
20 | |
21 | #include <video/mipi_display.h> |
22 | |
23 | #define S6D27A1_PASSWD_L2 0xF0 /* Password Command for Level 2 Control */ |
24 | #define S6D27A1_RESCTL 0xB3 /* Resolution Select Control */ |
25 | #define S6D27A1_PANELCTL2 0xB4 /* ASG Signal Control */ |
26 | #define S6D27A1_READID1 0xDA /* Read panel ID 1 */ |
27 | #define S6D27A1_READID2 0xDB /* Read panel ID 2 */ |
28 | #define S6D27A1_READID3 0xDC /* Read panel ID 3 */ |
29 | #define S6D27A1_DISPCTL 0xF2 /* Display Control */ |
30 | #define S6D27A1_MANPWR 0xF3 /* Manual Control */ |
31 | #define S6D27A1_PWRCTL1 0xF4 /* Power Control */ |
32 | #define S6D27A1_SRCCTL 0xF6 /* Source Control */ |
33 | #define S6D27A1_PANELCTL 0xF7 /* Panel Control*/ |
34 | |
35 | static const u8 s6d27a1_dbi_read_commands[] = { |
36 | S6D27A1_READID1, |
37 | S6D27A1_READID2, |
38 | S6D27A1_READID3, |
39 | 0, /* sentinel */ |
40 | }; |
41 | |
42 | struct s6d27a1 { |
43 | struct device *dev; |
44 | struct mipi_dbi dbi; |
45 | struct drm_panel panel; |
46 | struct gpio_desc *reset; |
47 | struct regulator_bulk_data regulators[2]; |
48 | }; |
49 | |
50 | static const struct drm_display_mode s6d27a1_480_800_mode = { |
51 | /* |
52 | * The vendor driver states that the S6D27A1 panel |
53 | * has a pixel clock frequency of 49920000 Hz / 2 = 24960000 Hz. |
54 | */ |
55 | .clock = 24960, |
56 | .hdisplay = 480, |
57 | .hsync_start = 480 + 63, |
58 | .hsync_end = 480 + 63 + 2, |
59 | .htotal = 480 + 63 + 2 + 63, |
60 | .vdisplay = 800, |
61 | .vsync_start = 800 + 11, |
62 | .vsync_end = 800 + 11 + 2, |
63 | .vtotal = 800 + 11 + 2 + 10, |
64 | .width_mm = 50, |
65 | .height_mm = 84, |
66 | .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, |
67 | }; |
68 | |
69 | static inline struct s6d27a1 *to_s6d27a1(struct drm_panel *panel) |
70 | { |
71 | return container_of(panel, struct s6d27a1, panel); |
72 | } |
73 | |
74 | static void s6d27a1_read_mtp_id(struct s6d27a1 *ctx) |
75 | { |
76 | struct mipi_dbi *dbi = &ctx->dbi; |
77 | u8 id1, id2, id3; |
78 | int ret; |
79 | |
80 | ret = mipi_dbi_command_read(dbi, S6D27A1_READID1, val: &id1); |
81 | if (ret) { |
82 | dev_err(ctx->dev, "unable to read MTP ID 1\n" ); |
83 | return; |
84 | } |
85 | ret = mipi_dbi_command_read(dbi, S6D27A1_READID2, val: &id2); |
86 | if (ret) { |
87 | dev_err(ctx->dev, "unable to read MTP ID 2\n" ); |
88 | return; |
89 | } |
90 | ret = mipi_dbi_command_read(dbi, S6D27A1_READID3, val: &id3); |
91 | if (ret) { |
92 | dev_err(ctx->dev, "unable to read MTP ID 3\n" ); |
93 | return; |
94 | } |
95 | dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n" , id1, id2, id3); |
96 | } |
97 | |
98 | static int s6d27a1_power_on(struct s6d27a1 *ctx) |
99 | { |
100 | struct mipi_dbi *dbi = &ctx->dbi; |
101 | int ret; |
102 | |
103 | /* Power up */ |
104 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->regulators), |
105 | consumers: ctx->regulators); |
106 | if (ret) { |
107 | dev_err(ctx->dev, "failed to enable regulators: %d\n" , ret); |
108 | return ret; |
109 | } |
110 | |
111 | msleep(msecs: 20); |
112 | |
113 | /* Assert reset >=1 ms */ |
114 | gpiod_set_value_cansleep(desc: ctx->reset, value: 1); |
115 | usleep_range(min: 1000, max: 5000); |
116 | /* De-assert reset */ |
117 | gpiod_set_value_cansleep(desc: ctx->reset, value: 0); |
118 | /* Wait >= 10 ms */ |
119 | msleep(msecs: 20); |
120 | |
121 | /* |
122 | * Exit sleep mode and initialize display - some hammering is |
123 | * necessary. |
124 | */ |
125 | mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); |
126 | mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); |
127 | msleep(msecs: 120); |
128 | |
129 | /* Magic to unlock level 2 control of the display */ |
130 | mipi_dbi_command(dbi, S6D27A1_PASSWD_L2, 0x5A, 0x5A); |
131 | |
132 | /* Configure resolution to 480RGBx800 */ |
133 | mipi_dbi_command(dbi, S6D27A1_RESCTL, 0x22); |
134 | |
135 | mipi_dbi_command(dbi, S6D27A1_PANELCTL2, 0x00, 0x02, 0x03, 0x04, 0x05, 0x08, 0x00, 0x0c); |
136 | |
137 | mipi_dbi_command(dbi, S6D27A1_MANPWR, 0x01, 0x00, 0x00, 0x08, 0x08, 0x02, 0x00); |
138 | |
139 | mipi_dbi_command(dbi, S6D27A1_DISPCTL, 0x19, 0x00, 0x08, 0x0D, 0x03, 0x41, 0x3F); |
140 | |
141 | mipi_dbi_command(dbi, S6D27A1_PWRCTL1, 0x00, 0x00, 0x00, 0x00, 0x55, |
142 | 0x44, 0x05, 0x88, 0x4B, 0x50); |
143 | |
144 | mipi_dbi_command(dbi, S6D27A1_SRCCTL, 0x03, 0x09, 0x8A, 0x00, 0x01, 0x16); |
145 | |
146 | mipi_dbi_command(dbi, S6D27A1_PANELCTL, 0x00, 0x05, 0x06, 0x07, 0x08, |
147 | 0x01, 0x09, 0x0D, 0x0A, 0x0E, |
148 | 0x0B, 0x0F, 0x0C, 0x10, 0x01, |
149 | 0x11, 0x12, 0x13, 0x14, 0x05, |
150 | 0x06, 0x07, 0x08, 0x01, 0x09, |
151 | 0x0D, 0x0A, 0x0E, 0x0B, 0x0F, |
152 | 0x0C, 0x10, 0x01, 0x11, 0x12, |
153 | 0x13, 0x14); |
154 | |
155 | /* lock the level 2 control */ |
156 | mipi_dbi_command(dbi, S6D27A1_PASSWD_L2, 0xA5, 0xA5); |
157 | |
158 | s6d27a1_read_mtp_id(ctx); |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | static int s6d27a1_power_off(struct s6d27a1 *ctx) |
164 | { |
165 | /* Go into RESET and disable regulators */ |
166 | gpiod_set_value_cansleep(desc: ctx->reset, value: 1); |
167 | return regulator_bulk_disable(ARRAY_SIZE(ctx->regulators), |
168 | consumers: ctx->regulators); |
169 | } |
170 | |
171 | static int s6d27a1_unprepare(struct drm_panel *panel) |
172 | { |
173 | struct s6d27a1 *ctx = to_s6d27a1(panel); |
174 | struct mipi_dbi *dbi = &ctx->dbi; |
175 | |
176 | mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); |
177 | msleep(msecs: 120); |
178 | return s6d27a1_power_off(ctx: to_s6d27a1(panel)); |
179 | } |
180 | |
181 | static int s6d27a1_disable(struct drm_panel *panel) |
182 | { |
183 | struct s6d27a1 *ctx = to_s6d27a1(panel); |
184 | struct mipi_dbi *dbi = &ctx->dbi; |
185 | |
186 | mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); |
187 | msleep(msecs: 25); |
188 | |
189 | return 0; |
190 | } |
191 | |
192 | static int s6d27a1_prepare(struct drm_panel *panel) |
193 | { |
194 | return s6d27a1_power_on(ctx: to_s6d27a1(panel)); |
195 | } |
196 | |
197 | static int s6d27a1_enable(struct drm_panel *panel) |
198 | { |
199 | struct s6d27a1 *ctx = to_s6d27a1(panel); |
200 | struct mipi_dbi *dbi = &ctx->dbi; |
201 | |
202 | mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static int s6d27a1_get_modes(struct drm_panel *panel, |
208 | struct drm_connector *connector) |
209 | { |
210 | struct s6d27a1 *ctx = to_s6d27a1(panel); |
211 | struct drm_display_mode *mode; |
212 | static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; |
213 | |
214 | mode = drm_mode_duplicate(dev: connector->dev, mode: &s6d27a1_480_800_mode); |
215 | if (!mode) { |
216 | dev_err(ctx->dev, "failed to add mode\n" ); |
217 | return -ENOMEM; |
218 | } |
219 | |
220 | connector->display_info.bpc = 8; |
221 | connector->display_info.width_mm = mode->width_mm; |
222 | connector->display_info.height_mm = mode->height_mm; |
223 | connector->display_info.bus_flags = |
224 | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; |
225 | drm_display_info_set_bus_formats(info: &connector->display_info, |
226 | formats: &bus_format, num_formats: 1); |
227 | |
228 | drm_mode_set_name(mode); |
229 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
230 | |
231 | drm_mode_probed_add(connector, mode); |
232 | |
233 | return 1; |
234 | } |
235 | |
236 | static const struct drm_panel_funcs s6d27a1_drm_funcs = { |
237 | .disable = s6d27a1_disable, |
238 | .unprepare = s6d27a1_unprepare, |
239 | .prepare = s6d27a1_prepare, |
240 | .enable = s6d27a1_enable, |
241 | .get_modes = s6d27a1_get_modes, |
242 | }; |
243 | |
244 | static int s6d27a1_probe(struct spi_device *spi) |
245 | { |
246 | struct device *dev = &spi->dev; |
247 | struct s6d27a1 *ctx; |
248 | int ret; |
249 | |
250 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
251 | if (!ctx) |
252 | return -ENOMEM; |
253 | |
254 | ctx->dev = dev; |
255 | |
256 | /* |
257 | * VCI is the analog voltage supply |
258 | * VCCIO is the digital I/O voltage supply |
259 | */ |
260 | ctx->regulators[0].supply = "vci" ; |
261 | ctx->regulators[1].supply = "vccio" ; |
262 | ret = devm_regulator_bulk_get(dev, |
263 | ARRAY_SIZE(ctx->regulators), |
264 | consumers: ctx->regulators); |
265 | if (ret) |
266 | return dev_err_probe(dev, err: ret, fmt: "failed to get regulators\n" ); |
267 | |
268 | ctx->reset = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
269 | if (IS_ERR(ptr: ctx->reset)) { |
270 | ret = PTR_ERR(ptr: ctx->reset); |
271 | return dev_err_probe(dev, err: ret, fmt: "no RESET GPIO\n" ); |
272 | } |
273 | |
274 | ret = mipi_dbi_spi_init(spi, dbi: &ctx->dbi, NULL); |
275 | if (ret) |
276 | return dev_err_probe(dev, err: ret, fmt: "MIPI DBI init failed\n" ); |
277 | |
278 | ctx->dbi.read_commands = s6d27a1_dbi_read_commands; |
279 | |
280 | drm_panel_init(panel: &ctx->panel, dev, funcs: &s6d27a1_drm_funcs, |
281 | DRM_MODE_CONNECTOR_DPI); |
282 | |
283 | ret = drm_panel_of_backlight(panel: &ctx->panel); |
284 | if (ret) |
285 | return dev_err_probe(dev, err: ret, fmt: "failed to add backlight\n" ); |
286 | |
287 | spi_set_drvdata(spi, data: ctx); |
288 | |
289 | drm_panel_add(panel: &ctx->panel); |
290 | |
291 | return 0; |
292 | } |
293 | |
294 | static void s6d27a1_remove(struct spi_device *spi) |
295 | { |
296 | struct s6d27a1 *ctx = spi_get_drvdata(spi); |
297 | |
298 | drm_panel_remove(panel: &ctx->panel); |
299 | } |
300 | |
301 | static const struct of_device_id s6d27a1_match[] = { |
302 | { .compatible = "samsung,s6d27a1" , }, |
303 | { /* sentinel */ }, |
304 | }; |
305 | MODULE_DEVICE_TABLE(of, s6d27a1_match); |
306 | |
307 | static struct spi_driver s6d27a1_driver = { |
308 | .probe = s6d27a1_probe, |
309 | .remove = s6d27a1_remove, |
310 | .driver = { |
311 | .name = "s6d27a1-panel" , |
312 | .of_match_table = s6d27a1_match, |
313 | }, |
314 | }; |
315 | module_spi_driver(s6d27a1_driver); |
316 | |
317 | MODULE_AUTHOR("Markuss Broks <markuss.broks@gmail.com>" ); |
318 | MODULE_DESCRIPTION("Samsung S6D27A1 panel driver" ); |
319 | MODULE_LICENSE("GPL v2" ); |
320 | |