1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Magnachip d53e6ea8966 MIPI-DSI panel driver |
4 | * Copyright (C) 2023 Chris Morgan |
5 | */ |
6 | |
7 | #include <drm/drm_mipi_dbi.h> |
8 | #include <drm/drm_mipi_dsi.h> |
9 | #include <drm/drm_modes.h> |
10 | #include <drm/drm_of.h> |
11 | #include <drm/drm_panel.h> |
12 | |
13 | #include <linux/backlight.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/gpio/consumer.h> |
16 | #include <linux/init.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/media-bus-format.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of.h> |
21 | #include <linux/regulator/consumer.h> |
22 | #include <linux/spi/spi.h> |
23 | |
24 | #include <video/mipi_display.h> |
25 | |
26 | /* Forward declaration for use in backlight function */ |
27 | struct d53e6ea8966; |
28 | |
29 | /* Panel info, unique to each panel */ |
30 | struct d53e6ea8966_panel_info { |
31 | /** @display_modes: the supported display modes */ |
32 | const struct drm_display_mode *display_modes; |
33 | /** @num_modes: the number of supported display modes */ |
34 | unsigned int num_modes; |
35 | /** @width_mm: panel width in mm */ |
36 | u16 width_mm; |
37 | /** @height_mm: panel height in mm */ |
38 | u16 height_mm; |
39 | /** @bus_flags: drm bus flags for panel */ |
40 | u32 bus_flags; |
41 | /** @panel_init_seq: panel specific init sequence */ |
42 | void (*panel_init_seq)(struct d53e6ea8966 *db); |
43 | /** @backlight_register: panel backlight registration or NULL */ |
44 | int (*backlight_register)(struct d53e6ea8966 *db); |
45 | }; |
46 | |
47 | struct d53e6ea8966 { |
48 | /** @dev: the container device */ |
49 | struct device *dev; |
50 | /** @dbi: the DBI bus abstraction handle */ |
51 | struct mipi_dbi dbi; |
52 | /** @panel: the DRM panel instance for this device */ |
53 | struct drm_panel panel; |
54 | /** @reset: reset GPIO line */ |
55 | struct gpio_desc *reset; |
56 | /** @enable: enable GPIO line */ |
57 | struct gpio_desc *enable; |
58 | /** @reg_vdd: VDD supply regulator for panel logic */ |
59 | struct regulator *reg_vdd; |
60 | /** @reg_elvdd: ELVDD supply regulator for panel display */ |
61 | struct regulator *reg_elvdd; |
62 | /** @dsi_dev: DSI child device (panel) */ |
63 | struct mipi_dsi_device *dsi_dev; |
64 | /** @bl_dev: pseudo-backlight device for oled panel */ |
65 | struct backlight_device *bl_dev; |
66 | /** @panel_info: struct containing panel timing and info */ |
67 | const struct d53e6ea8966_panel_info *panel_info; |
68 | }; |
69 | |
70 | #define NUM_GAMMA_LEVELS 16 |
71 | #define GAMMA_TABLE_COUNT 23 |
72 | #define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1) |
73 | |
74 | #define MCS_ELVSS_ON 0xb1 |
75 | #define MCS_TEMP_SWIRE 0xb2 |
76 | #define MCS_PASSWORD_0 0xf0 |
77 | #define MCS_PASSWORD_1 0xf1 |
78 | #define MCS_ANALOG_PWR_CTL_0 0xf4 |
79 | #define MCS_ANALOG_PWR_CTL_1 0xf5 |
80 | #define MCS_GTCON_SET 0xf7 |
81 | #define MCS_GATELESS_SIGNAL_SET 0xf8 |
82 | #define MCS_SET_GAMMA 0xf9 |
83 | |
84 | static inline struct d53e6ea8966 *to_d53e6ea8966(struct drm_panel *panel) |
85 | { |
86 | return container_of(panel, struct d53e6ea8966, panel); |
87 | } |
88 | |
89 | /* Table of gamma values provided in datasheet */ |
90 | static u8 ams495qa01_gamma[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = { |
91 | {0x01, 0x79, 0x78, 0x8d, 0xd9, 0xdf, 0xd5, 0xcb, 0xcf, 0xc5, |
92 | 0xe5, 0xe0, 0xe4, 0xdc, 0xb8, 0xd4, 0xfa, 0xed, 0xe6, 0x2f, |
93 | 0x00, 0x2f}, |
94 | {0x01, 0x7d, 0x7c, 0x92, 0xd7, 0xdd, 0xd2, 0xcb, 0xd0, 0xc6, |
95 | 0xe5, 0xe1, 0xe3, 0xda, 0xbd, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
96 | 0x00, 0x2f}, |
97 | {0x01, 0x7f, 0x7e, 0x95, 0xd7, 0xde, 0xd2, 0xcb, 0xcf, 0xc5, |
98 | 0xe5, 0xe3, 0xe3, 0xda, 0xbf, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
99 | 0x00, 0x2f}, |
100 | {0x01, 0x82, 0x81, 0x99, 0xd6, 0xdd, 0xd1, 0xca, 0xcf, 0xc3, |
101 | 0xe4, 0xe3, 0xe3, 0xda, 0xc2, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
102 | 0x00, 0x2f}, |
103 | {0x01, 0x84, 0x83, 0x9b, 0xd7, 0xde, 0xd2, 0xc8, 0xce, 0xc2, |
104 | 0xe4, 0xe3, 0xe2, 0xd9, 0xc3, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
105 | 0x00, 0x2f}, |
106 | {0x01, 0x87, 0x86, 0x9f, 0xd6, 0xdd, 0xd1, 0xc7, 0xce, 0xc1, |
107 | 0xe4, 0xe3, 0xe2, 0xd9, 0xc6, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
108 | 0x00, 0x2f}, |
109 | {0x01, 0x89, 0x89, 0xa2, 0xd5, 0xdb, 0xcf, 0xc8, 0xcf, 0xc2, |
110 | 0xe3, 0xe3, 0xe1, 0xd9, 0xc7, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
111 | 0x00, 0x2f}, |
112 | {0x01, 0x8b, 0x8b, 0xa5, 0xd5, 0xdb, 0xcf, 0xc7, 0xce, 0xc0, |
113 | 0xe3, 0xe3, 0xe1, 0xd8, 0xc7, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
114 | 0x00, 0x2f}, |
115 | {0x01, 0x8d, 0x8d, 0xa7, 0xd5, 0xdb, 0xcf, 0xc6, 0xce, 0xc0, |
116 | 0xe4, 0xe4, 0xe1, 0xd7, 0xc8, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
117 | 0x00, 0x2f}, |
118 | {0x01, 0x8f, 0x8f, 0xaa, 0xd4, 0xdb, 0xce, 0xc6, 0xcd, 0xbf, |
119 | 0xe3, 0xe3, 0xe1, 0xd7, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
120 | 0x00, 0x2f}, |
121 | {0x01, 0x91, 0x91, 0xac, 0xd3, 0xda, 0xce, 0xc5, 0xcd, 0xbe, |
122 | 0xe3, 0xe3, 0xe0, 0xd7, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
123 | 0x00, 0x2f}, |
124 | {0x01, 0x93, 0x93, 0xaf, 0xd3, 0xda, 0xcd, 0xc5, 0xcd, 0xbe, |
125 | 0xe2, 0xe3, 0xdf, 0xd6, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
126 | 0x00, 0x2f}, |
127 | {0x01, 0x95, 0x95, 0xb1, 0xd2, 0xd9, 0xcc, 0xc4, 0xcd, 0xbe, |
128 | 0xe2, 0xe3, 0xdf, 0xd7, 0xcc, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
129 | 0x00, 0x2f}, |
130 | {0x01, 0x99, 0x99, 0xb6, 0xd1, 0xd9, 0xcc, 0xc3, 0xcb, 0xbc, |
131 | 0xe2, 0xe4, 0xdf, 0xd6, 0xcc, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
132 | 0x00, 0x2f}, |
133 | {0x01, 0x9c, 0x9c, 0xba, 0xd0, 0xd8, 0xcb, 0xc3, 0xcb, 0xbb, |
134 | 0xe2, 0xe4, 0xdf, 0xd6, 0xce, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
135 | 0x00, 0x2f}, |
136 | {0x01, 0x9f, 0x9f, 0xbe, 0xcf, 0xd7, 0xc9, 0xc2, 0xcb, 0xbb, |
137 | 0xe1, 0xe3, 0xde, 0xd6, 0xd0, 0xd3, 0xfa, 0xed, 0xe6, 0x2f, |
138 | 0x00, 0x2f}, |
139 | }; |
140 | |
141 | /* |
142 | * Table of elvss values provided in datasheet and corresponds to |
143 | * gamma values. |
144 | */ |
145 | static u8 ams495qa01_elvss[NUM_GAMMA_LEVELS] = { |
146 | 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, |
147 | 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, |
148 | }; |
149 | |
150 | static int ams495qa01_update_gamma(struct mipi_dbi *dbi, int brightness) |
151 | { |
152 | int tmp = brightness; |
153 | |
154 | mipi_dbi_command_buf(dbi, MCS_SET_GAMMA, data: ams495qa01_gamma[tmp], |
155 | ARRAY_SIZE(ams495qa01_gamma[tmp])); |
156 | mipi_dbi_command(dbi, MCS_SET_GAMMA, 0x00); |
157 | |
158 | /* Undocumented command */ |
159 | mipi_dbi_command(dbi, 0x26, 0x00); |
160 | |
161 | mipi_dbi_command(dbi, MCS_TEMP_SWIRE, ams495qa01_elvss[tmp]); |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static void ams495qa01_panel_init(struct d53e6ea8966 *db) |
167 | { |
168 | struct mipi_dbi *dbi = &db->dbi; |
169 | |
170 | mipi_dbi_command(dbi, MCS_PASSWORD_0, 0x5a, 0x5a); |
171 | mipi_dbi_command(dbi, MCS_PASSWORD_1, 0x5a, 0x5a); |
172 | |
173 | /* Undocumented commands */ |
174 | mipi_dbi_command(dbi, 0xb0, 0x02); |
175 | mipi_dbi_command(dbi, 0xf3, 0x3b); |
176 | |
177 | mipi_dbi_command(dbi, MCS_ANALOG_PWR_CTL_0, 0x33, 0x42, 0x00, 0x08); |
178 | mipi_dbi_command(dbi, MCS_ANALOG_PWR_CTL_1, 0x00, 0x06, 0x26, 0x35, 0x03); |
179 | |
180 | /* Undocumented commands */ |
181 | mipi_dbi_command(dbi, 0xf6, 0x02); |
182 | mipi_dbi_command(dbi, 0xc6, 0x0b, 0x00, 0x00, 0x3c, 0x00, 0x22, |
183 | 0x00, 0x00, 0x00, 0x00); |
184 | |
185 | mipi_dbi_command(dbi, MCS_GTCON_SET, 0x20); |
186 | mipi_dbi_command(dbi, MCS_TEMP_SWIRE, 0x06, 0x06, 0x06, 0x06); |
187 | mipi_dbi_command(dbi, MCS_ELVSS_ON, 0x07, 0x00, 0x10); |
188 | mipi_dbi_command(dbi, MCS_GATELESS_SIGNAL_SET, 0x7f, 0x7a, |
189 | 0x89, 0x67, 0x26, 0x38, 0x00, 0x00, 0x09, |
190 | 0x67, 0x70, 0x88, 0x7a, 0x76, 0x05, 0x09, |
191 | 0x23, 0x23, 0x23); |
192 | |
193 | /* Undocumented commands */ |
194 | mipi_dbi_command(dbi, 0xb5, 0xff, 0xef, 0x35, 0x42, 0x0d, 0xd7, |
195 | 0xff, 0x07, 0xff, 0xff, 0xfd, 0x00, 0x01, |
196 | 0xff, 0x05, 0x12, 0x0f, 0xff, 0xff, 0xff, |
197 | 0xff); |
198 | mipi_dbi_command(dbi, 0xb4, 0x15); |
199 | mipi_dbi_command(dbi, 0xb3, 0x00); |
200 | |
201 | ams495qa01_update_gamma(dbi, MAX_BRIGHTNESS); |
202 | } |
203 | |
204 | static int d53e6ea8966_prepare(struct drm_panel *panel) |
205 | { |
206 | struct d53e6ea8966 *db = to_d53e6ea8966(panel); |
207 | int ret; |
208 | |
209 | /* Power up */ |
210 | ret = regulator_enable(regulator: db->reg_vdd); |
211 | if (ret) { |
212 | dev_err(db->dev, "failed to enable vdd regulator: %d\n" , ret); |
213 | return ret; |
214 | } |
215 | |
216 | if (db->reg_elvdd) { |
217 | ret = regulator_enable(regulator: db->reg_elvdd); |
218 | if (ret) { |
219 | dev_err(db->dev, |
220 | "failed to enable elvdd regulator: %d\n" , ret); |
221 | regulator_disable(regulator: db->reg_vdd); |
222 | return ret; |
223 | } |
224 | } |
225 | |
226 | /* Enable */ |
227 | if (db->enable) |
228 | gpiod_set_value_cansleep(desc: db->enable, value: 1); |
229 | |
230 | msleep(msecs: 50); |
231 | |
232 | /* Reset */ |
233 | gpiod_set_value_cansleep(desc: db->reset, value: 1); |
234 | usleep_range(min: 1000, max: 5000); |
235 | gpiod_set_value_cansleep(desc: db->reset, value: 0); |
236 | msleep(msecs: 20); |
237 | |
238 | db->panel_info->panel_init_seq(db); |
239 | |
240 | return 0; |
241 | } |
242 | |
243 | static int d53e6ea8966_enable(struct drm_panel *panel) |
244 | { |
245 | struct d53e6ea8966 *db = to_d53e6ea8966(panel); |
246 | struct mipi_dbi *dbi = &db->dbi; |
247 | |
248 | mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); |
249 | msleep(msecs: 200); |
250 | mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); |
251 | usleep_range(min: 10000, max: 15000); |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int d53e6ea8966_disable(struct drm_panel *panel) |
257 | { |
258 | struct d53e6ea8966 *db = to_d53e6ea8966(panel); |
259 | struct mipi_dbi *dbi = &db->dbi; |
260 | |
261 | mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); |
262 | msleep(msecs: 20); |
263 | mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); |
264 | msleep(msecs: 100); |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static int d53e6ea8966_unprepare(struct drm_panel *panel) |
270 | { |
271 | struct d53e6ea8966 *db = to_d53e6ea8966(panel); |
272 | |
273 | if (db->enable) |
274 | gpiod_set_value_cansleep(desc: db->enable, value: 0); |
275 | |
276 | gpiod_set_value_cansleep(desc: db->reset, value: 1); |
277 | |
278 | if (db->reg_elvdd) |
279 | regulator_disable(regulator: db->reg_elvdd); |
280 | |
281 | regulator_disable(regulator: db->reg_vdd); |
282 | msleep(msecs: 100); |
283 | |
284 | return 0; |
285 | } |
286 | |
287 | static int d53e6ea8966_get_modes(struct drm_panel *panel, |
288 | struct drm_connector *connector) |
289 | { |
290 | struct d53e6ea8966 *db = to_d53e6ea8966(panel); |
291 | const struct d53e6ea8966_panel_info *panel_info = db->panel_info; |
292 | struct drm_display_mode *mode; |
293 | static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; |
294 | unsigned int i; |
295 | |
296 | for (i = 0; i < panel_info->num_modes; i++) { |
297 | mode = drm_mode_duplicate(dev: connector->dev, |
298 | mode: &panel_info->display_modes[i]); |
299 | if (!mode) |
300 | return -ENOMEM; |
301 | |
302 | drm_mode_set_name(mode); |
303 | drm_mode_probed_add(connector, mode); |
304 | } |
305 | |
306 | connector->display_info.bpc = 8; |
307 | connector->display_info.width_mm = panel_info->width_mm; |
308 | connector->display_info.height_mm = panel_info->height_mm; |
309 | connector->display_info.bus_flags = panel_info->bus_flags; |
310 | |
311 | drm_display_info_set_bus_formats(info: &connector->display_info, |
312 | formats: &bus_format, num_formats: 1); |
313 | |
314 | return 1; |
315 | } |
316 | |
317 | static const struct drm_panel_funcs d53e6ea8966_panel_funcs = { |
318 | .disable = d53e6ea8966_disable, |
319 | .enable = d53e6ea8966_enable, |
320 | .get_modes = d53e6ea8966_get_modes, |
321 | .prepare = d53e6ea8966_prepare, |
322 | .unprepare = d53e6ea8966_unprepare, |
323 | }; |
324 | |
325 | static int ams495qa01_set_brightness(struct backlight_device *bd) |
326 | { |
327 | struct d53e6ea8966 *db = bl_get_data(bl_dev: bd); |
328 | struct mipi_dbi *dbi = &db->dbi; |
329 | int brightness = backlight_get_brightness(bd); |
330 | |
331 | ams495qa01_update_gamma(dbi, brightness); |
332 | |
333 | return 0; |
334 | } |
335 | |
336 | static const struct backlight_ops ams495qa01_backlight_ops = { |
337 | .update_status = ams495qa01_set_brightness, |
338 | }; |
339 | |
340 | static int ams495qa01_backlight_register(struct d53e6ea8966 *db) |
341 | { |
342 | struct backlight_properties props = { |
343 | .type = BACKLIGHT_RAW, |
344 | .brightness = MAX_BRIGHTNESS, |
345 | .max_brightness = MAX_BRIGHTNESS, |
346 | }; |
347 | struct device *dev = db->dev; |
348 | int ret = 0; |
349 | |
350 | db->bl_dev = devm_backlight_device_register(dev, name: "panel" , parent: dev, devdata: db, |
351 | ops: &ams495qa01_backlight_ops, |
352 | props: &props); |
353 | if (IS_ERR(ptr: db->bl_dev)) { |
354 | ret = PTR_ERR(ptr: db->bl_dev); |
355 | dev_err(dev, "error registering backlight device (%d)\n" , ret); |
356 | } |
357 | |
358 | return ret; |
359 | } |
360 | |
361 | static int d53e6ea8966_probe(struct spi_device *spi) |
362 | { |
363 | struct device *dev = &spi->dev; |
364 | struct mipi_dsi_host *dsi_host; |
365 | struct d53e6ea8966 *db; |
366 | int ret; |
367 | struct mipi_dsi_device_info info = { |
368 | .type = "d53e6ea8966" , |
369 | .channel = 0, |
370 | .node = NULL, |
371 | }; |
372 | |
373 | db = devm_kzalloc(dev, size: sizeof(*db), GFP_KERNEL); |
374 | if (!db) |
375 | return -ENOMEM; |
376 | |
377 | spi_set_drvdata(spi, data: db); |
378 | |
379 | db->dev = dev; |
380 | |
381 | db->panel_info = of_device_get_match_data(dev); |
382 | if (!db->panel_info) |
383 | return -EINVAL; |
384 | |
385 | db->reg_vdd = devm_regulator_get(dev, id: "vdd" ); |
386 | if (IS_ERR(ptr: db->reg_vdd)) |
387 | return dev_err_probe(dev, err: PTR_ERR(ptr: db->reg_vdd), |
388 | fmt: "Failed to get vdd supply\n" ); |
389 | |
390 | db->reg_elvdd = devm_regulator_get_optional(dev, id: "elvdd" ); |
391 | if (IS_ERR(ptr: db->reg_elvdd)) |
392 | db->reg_elvdd = NULL; |
393 | |
394 | db->reset = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
395 | if (IS_ERR(ptr: db->reset)) { |
396 | ret = PTR_ERR(ptr: db->reset); |
397 | return dev_err_probe(dev, err: ret, fmt: "no RESET GPIO\n" ); |
398 | } |
399 | |
400 | db->enable = devm_gpiod_get_optional(dev, con_id: "enable" , flags: GPIOD_OUT_LOW); |
401 | if (IS_ERR(ptr: db->enable)) { |
402 | ret = PTR_ERR(ptr: db->enable); |
403 | return dev_err_probe(dev, err: ret, fmt: "cannot get ENABLE GPIO\n" ); |
404 | } |
405 | |
406 | ret = mipi_dbi_spi_init(spi, dbi: &db->dbi, NULL); |
407 | if (ret) |
408 | return dev_err_probe(dev, err: ret, fmt: "MIPI DBI init failed\n" ); |
409 | |
410 | dsi_host = drm_of_get_dsi_bus(dev); |
411 | if (IS_ERR(ptr: dsi_host)) { |
412 | ret = PTR_ERR(ptr: dsi_host); |
413 | return dev_err_probe(dev, err: ret, fmt: "Error attaching DSI bus\n" ); |
414 | } |
415 | |
416 | db->dsi_dev = devm_mipi_dsi_device_register_full(dev, host: dsi_host, info: &info); |
417 | if (IS_ERR(ptr: db->dsi_dev)) { |
418 | dev_err(dev, "failed to register dsi device: %ld\n" , |
419 | PTR_ERR(db->dsi_dev)); |
420 | return PTR_ERR(ptr: db->dsi_dev); |
421 | } |
422 | |
423 | db->dsi_dev->lanes = 2; |
424 | db->dsi_dev->format = MIPI_DSI_FMT_RGB888; |
425 | db->dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
426 | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET; |
427 | |
428 | drm_panel_init(panel: &db->panel, dev, funcs: &d53e6ea8966_panel_funcs, |
429 | DRM_MODE_CONNECTOR_DSI); |
430 | |
431 | if (db->panel_info->backlight_register) { |
432 | ret = db->panel_info->backlight_register(db); |
433 | if (ret < 0) |
434 | return ret; |
435 | db->panel.backlight = db->bl_dev; |
436 | } |
437 | |
438 | drm_panel_add(panel: &db->panel); |
439 | |
440 | ret = devm_mipi_dsi_attach(dev, dsi: db->dsi_dev); |
441 | if (ret < 0) { |
442 | dev_err(dev, "mipi_dsi_attach failed: %d\n" , ret); |
443 | drm_panel_remove(panel: &db->panel); |
444 | return ret; |
445 | } |
446 | |
447 | return 0; |
448 | } |
449 | |
450 | static void d53e6ea8966_remove(struct spi_device *spi) |
451 | { |
452 | struct d53e6ea8966 *db = spi_get_drvdata(spi); |
453 | |
454 | drm_panel_remove(panel: &db->panel); |
455 | } |
456 | |
457 | static const struct drm_display_mode ams495qa01_modes[] = { |
458 | { /* 60hz */ |
459 | .clock = 33500, |
460 | .hdisplay = 960, |
461 | .hsync_start = 960 + 10, |
462 | .hsync_end = 960 + 10 + 2, |
463 | .htotal = 960 + 10 + 2 + 10, |
464 | .vdisplay = 544, |
465 | .vsync_start = 544 + 10, |
466 | .vsync_end = 544 + 10 + 2, |
467 | .vtotal = 544 + 10 + 2 + 10, |
468 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
469 | .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, |
470 | }, |
471 | { /* 50hz */ |
472 | .clock = 27800, |
473 | .hdisplay = 960, |
474 | .hsync_start = 960 + 10, |
475 | .hsync_end = 960 + 10 + 2, |
476 | .htotal = 960 + 10 + 2 + 10, |
477 | .vdisplay = 544, |
478 | .vsync_start = 544 + 10, |
479 | .vsync_end = 544 + 10 + 2, |
480 | .vtotal = 544 + 10 + 2 + 10, |
481 | .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, |
482 | .type = DRM_MODE_TYPE_DRIVER, |
483 | }, |
484 | }; |
485 | |
486 | static const struct d53e6ea8966_panel_info ams495qa01_info = { |
487 | .display_modes = ams495qa01_modes, |
488 | .num_modes = ARRAY_SIZE(ams495qa01_modes), |
489 | .width_mm = 117, |
490 | .height_mm = 74, |
491 | .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, |
492 | .panel_init_seq = ams495qa01_panel_init, |
493 | .backlight_register = ams495qa01_backlight_register, |
494 | }; |
495 | |
496 | static const struct of_device_id d53e6ea8966_match[] = { |
497 | { .compatible = "samsung,ams495qa01" , .data = &ams495qa01_info }, |
498 | { /* sentinel */ }, |
499 | }; |
500 | MODULE_DEVICE_TABLE(of, d53e6ea8966_match); |
501 | |
502 | static const struct spi_device_id d53e6ea8966_ids[] = { |
503 | { "ams495qa01" , 0 }, |
504 | { /* sentinel */ }, |
505 | }; |
506 | MODULE_DEVICE_TABLE(spi, d53e6ea8966_ids); |
507 | |
508 | static struct spi_driver d53e6ea8966_driver = { |
509 | .driver = { |
510 | .name = "d53e6ea8966-panel" , |
511 | .of_match_table = d53e6ea8966_match, |
512 | }, |
513 | .id_table = d53e6ea8966_ids, |
514 | .probe = d53e6ea8966_probe, |
515 | .remove = d53e6ea8966_remove, |
516 | }; |
517 | module_spi_driver(d53e6ea8966_driver); |
518 | |
519 | MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>" ); |
520 | MODULE_DESCRIPTION("Magnachip d53e6ea8966 panel driver" ); |
521 | MODULE_LICENSE("GPL" ); |
522 | |