Commit | Line | Data |
---|---|---|
a61732e8 JMC |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * DRM driver for Solomon SSD130x OLED displays | |
4 | * | |
5 | * Copyright 2022 Red Hat Inc. | |
6 | * Author: Javier Martinez Canillas <javierm@redhat.com> | |
7 | * | |
8 | * Based on drivers/video/fbdev/ssd1307fb.c | |
9 | * Copyright 2012 Free Electrons | |
10 | */ | |
11 | ||
12 | #include <linux/backlight.h> | |
13 | #include <linux/bitfield.h> | |
14 | #include <linux/bits.h> | |
15 | #include <linux/delay.h> | |
16 | #include <linux/gpio/consumer.h> | |
17 | #include <linux/property.h> | |
18 | #include <linux/pwm.h> | |
19 | #include <linux/regulator/consumer.h> | |
20 | ||
622113b9 | 21 | #include <drm/drm_atomic.h> |
a61732e8 | 22 | #include <drm/drm_atomic_helper.h> |
7fed7fa3 | 23 | #include <drm/drm_crtc_helper.h> |
a61732e8 | 24 | #include <drm/drm_damage_helper.h> |
255490f9 | 25 | #include <drm/drm_edid.h> |
8ab59da2 | 26 | #include <drm/drm_fbdev_generic.h> |
a61732e8 | 27 | #include <drm/drm_format_helper.h> |
720cf96d | 28 | #include <drm/drm_framebuffer.h> |
a61732e8 JMC |
29 | #include <drm/drm_gem_atomic_helper.h> |
30 | #include <drm/drm_gem_framebuffer_helper.h> | |
31 | #include <drm/drm_gem_shmem_helper.h> | |
32 | #include <drm/drm_managed.h> | |
33 | #include <drm/drm_modes.h> | |
34 | #include <drm/drm_rect.h> | |
35 | #include <drm/drm_probe_helper.h> | |
36 | ||
37 | #include "ssd130x.h" | |
38 | ||
39 | #define DRIVER_NAME "ssd130x" | |
40 | #define DRIVER_DESC "DRM driver for Solomon SSD130x OLED displays" | |
41 | #define DRIVER_DATE "20220131" | |
42 | #define DRIVER_MAJOR 1 | |
43 | #define DRIVER_MINOR 0 | |
44 | ||
b0daaa5c CYT |
45 | #define SSD130X_PAGE_COL_START_LOW 0x00 |
46 | #define SSD130X_PAGE_COL_START_HIGH 0x10 | |
a61732e8 JMC |
47 | #define SSD130X_SET_ADDRESS_MODE 0x20 |
48 | #define SSD130X_SET_COL_RANGE 0x21 | |
49 | #define SSD130X_SET_PAGE_RANGE 0x22 | |
50 | #define SSD130X_CONTRAST 0x81 | |
51 | #define SSD130X_SET_LOOKUP_TABLE 0x91 | |
52 | #define SSD130X_CHARGE_PUMP 0x8d | |
a134109c | 53 | #define SSD130X_SET_SEG_REMAP 0xa0 |
a61732e8 JMC |
54 | #define SSD130X_DISPLAY_OFF 0xae |
55 | #define SSD130X_SET_MULTIPLEX_RATIO 0xa8 | |
56 | #define SSD130X_DISPLAY_ON 0xaf | |
57 | #define SSD130X_START_PAGE_ADDRESS 0xb0 | |
58 | #define SSD130X_SET_COM_SCAN_DIR 0xc0 | |
59 | #define SSD130X_SET_DISPLAY_OFFSET 0xd3 | |
60 | #define SSD130X_SET_CLOCK_FREQ 0xd5 | |
61 | #define SSD130X_SET_AREA_COLOR_MODE 0xd8 | |
62 | #define SSD130X_SET_PRECHARGE_PERIOD 0xd9 | |
63 | #define SSD130X_SET_COM_PINS_CONFIG 0xda | |
64 | #define SSD130X_SET_VCOMH 0xdb | |
65 | ||
b0daaa5c CYT |
66 | #define SSD130X_PAGE_COL_START_MASK GENMASK(3, 0) |
67 | #define SSD130X_PAGE_COL_START_HIGH_SET(val) FIELD_PREP(SSD130X_PAGE_COL_START_MASK, (val) >> 4) | |
68 | #define SSD130X_PAGE_COL_START_LOW_SET(val) FIELD_PREP(SSD130X_PAGE_COL_START_MASK, (val)) | |
69 | #define SSD130X_START_PAGE_ADDRESS_MASK GENMASK(2, 0) | |
70 | #define SSD130X_START_PAGE_ADDRESS_SET(val) FIELD_PREP(SSD130X_START_PAGE_ADDRESS_MASK, (val)) | |
a134109c CYT |
71 | #define SSD130X_SET_SEG_REMAP_MASK GENMASK(0, 0) |
72 | #define SSD130X_SET_SEG_REMAP_SET(val) FIELD_PREP(SSD130X_SET_SEG_REMAP_MASK, (val)) | |
efb37e66 | 73 | #define SSD130X_SET_COM_SCAN_DIR_MASK GENMASK(3, 3) |
a61732e8 JMC |
74 | #define SSD130X_SET_COM_SCAN_DIR_SET(val) FIELD_PREP(SSD130X_SET_COM_SCAN_DIR_MASK, (val)) |
75 | #define SSD130X_SET_CLOCK_DIV_MASK GENMASK(3, 0) | |
76 | #define SSD130X_SET_CLOCK_DIV_SET(val) FIELD_PREP(SSD130X_SET_CLOCK_DIV_MASK, (val)) | |
77 | #define SSD130X_SET_CLOCK_FREQ_MASK GENMASK(7, 4) | |
78 | #define SSD130X_SET_CLOCK_FREQ_SET(val) FIELD_PREP(SSD130X_SET_CLOCK_FREQ_MASK, (val)) | |
79 | #define SSD130X_SET_PRECHARGE_PERIOD1_MASK GENMASK(3, 0) | |
80 | #define SSD130X_SET_PRECHARGE_PERIOD1_SET(val) FIELD_PREP(SSD130X_SET_PRECHARGE_PERIOD1_MASK, (val)) | |
81 | #define SSD130X_SET_PRECHARGE_PERIOD2_MASK GENMASK(7, 4) | |
82 | #define SSD130X_SET_PRECHARGE_PERIOD2_SET(val) FIELD_PREP(SSD130X_SET_PRECHARGE_PERIOD2_MASK, (val)) | |
83 | #define SSD130X_SET_COM_PINS_CONFIG1_MASK GENMASK(4, 4) | |
84 | #define SSD130X_SET_COM_PINS_CONFIG1_SET(val) FIELD_PREP(SSD130X_SET_COM_PINS_CONFIG1_MASK, !(val)) | |
85 | #define SSD130X_SET_COM_PINS_CONFIG2_MASK GENMASK(5, 5) | |
86 | #define SSD130X_SET_COM_PINS_CONFIG2_SET(val) FIELD_PREP(SSD130X_SET_COM_PINS_CONFIG2_MASK, (val)) | |
87 | ||
88 | #define SSD130X_SET_ADDRESS_MODE_HORIZONTAL 0x00 | |
89 | #define SSD130X_SET_ADDRESS_MODE_VERTICAL 0x01 | |
90 | #define SSD130X_SET_ADDRESS_MODE_PAGE 0x02 | |
91 | ||
92 | #define SSD130X_SET_AREA_COLOR_MODE_ENABLE 0x1e | |
93 | #define SSD130X_SET_AREA_COLOR_MODE_LOW_POWER 0x05 | |
94 | ||
95 | #define MAX_CONTRAST 255 | |
96 | ||
4203e88b JMC |
97 | const struct ssd130x_deviceinfo ssd130x_variants[] = { |
98 | [SH1106_ID] = { | |
99 | .default_vcomh = 0x40, | |
100 | .default_dclk_div = 1, | |
101 | .default_dclk_frq = 5, | |
102 | .page_mode_only = 1, | |
103 | }, | |
104 | [SSD1305_ID] = { | |
105 | .default_vcomh = 0x34, | |
106 | .default_dclk_div = 1, | |
107 | .default_dclk_frq = 7, | |
108 | }, | |
109 | [SSD1306_ID] = { | |
110 | .default_vcomh = 0x20, | |
111 | .default_dclk_div = 1, | |
112 | .default_dclk_frq = 8, | |
113 | .need_chargepump = 1, | |
114 | }, | |
115 | [SSD1307_ID] = { | |
116 | .default_vcomh = 0x20, | |
117 | .default_dclk_div = 2, | |
118 | .default_dclk_frq = 12, | |
119 | .need_pwm = 1, | |
120 | }, | |
121 | [SSD1309_ID] = { | |
122 | .default_vcomh = 0x34, | |
123 | .default_dclk_div = 1, | |
124 | .default_dclk_frq = 10, | |
125 | } | |
126 | }; | |
127 | EXPORT_SYMBOL_NS_GPL(ssd130x_variants, DRM_SSD130X); | |
128 | ||
a61732e8 JMC |
129 | static inline struct ssd130x_device *drm_to_ssd130x(struct drm_device *drm) |
130 | { | |
131 | return container_of(drm, struct ssd130x_device, drm); | |
132 | } | |
133 | ||
134 | /* | |
135 | * Helper to write data (SSD130X_DATA) to the device. | |
136 | */ | |
137 | static int ssd130x_write_data(struct ssd130x_device *ssd130x, u8 *values, int count) | |
138 | { | |
139 | return regmap_bulk_write(ssd130x->regmap, SSD130X_DATA, values, count); | |
140 | } | |
141 | ||
142 | /* | |
143 | * Helper to write command (SSD130X_COMMAND). The fist variadic argument | |
144 | * is the command to write and the following are the command options. | |
145 | * | |
146 | * Note that the ssd130x protocol requires each command and option to be | |
147 | * written as a SSD130X_COMMAND device register value. That is why a call | |
148 | * to regmap_write(..., SSD130X_COMMAND, ...) is done for each argument. | |
149 | */ | |
150 | static int ssd130x_write_cmd(struct ssd130x_device *ssd130x, int count, | |
151 | /* u8 cmd, u8 option, ... */...) | |
152 | { | |
153 | va_list ap; | |
154 | u8 value; | |
155 | int ret; | |
156 | ||
157 | va_start(ap, count); | |
158 | ||
159 | do { | |
160 | value = va_arg(ap, int); | |
161 | ret = regmap_write(ssd130x->regmap, SSD130X_COMMAND, value); | |
162 | if (ret) | |
163 | goto out_end; | |
164 | } while (--count); | |
165 | ||
166 | out_end: | |
167 | va_end(ap); | |
168 | ||
169 | return ret; | |
170 | } | |
171 | ||
b0daaa5c | 172 | /* Set address range for horizontal/vertical addressing modes */ |
a61732e8 JMC |
173 | static int ssd130x_set_col_range(struct ssd130x_device *ssd130x, |
174 | u8 col_start, u8 cols) | |
175 | { | |
176 | u8 col_end = col_start + cols - 1; | |
177 | int ret; | |
178 | ||
179 | if (col_start == ssd130x->col_start && col_end == ssd130x->col_end) | |
180 | return 0; | |
181 | ||
182 | ret = ssd130x_write_cmd(ssd130x, 3, SSD130X_SET_COL_RANGE, col_start, col_end); | |
183 | if (ret < 0) | |
184 | return ret; | |
185 | ||
186 | ssd130x->col_start = col_start; | |
187 | ssd130x->col_end = col_end; | |
188 | return 0; | |
189 | } | |
190 | ||
191 | static int ssd130x_set_page_range(struct ssd130x_device *ssd130x, | |
192 | u8 page_start, u8 pages) | |
193 | { | |
194 | u8 page_end = page_start + pages - 1; | |
195 | int ret; | |
196 | ||
197 | if (page_start == ssd130x->page_start && page_end == ssd130x->page_end) | |
198 | return 0; | |
199 | ||
200 | ret = ssd130x_write_cmd(ssd130x, 3, SSD130X_SET_PAGE_RANGE, page_start, page_end); | |
201 | if (ret < 0) | |
202 | return ret; | |
203 | ||
204 | ssd130x->page_start = page_start; | |
205 | ssd130x->page_end = page_end; | |
206 | return 0; | |
207 | } | |
208 | ||
b0daaa5c CYT |
209 | /* Set page and column start address for page addressing mode */ |
210 | static int ssd130x_set_page_pos(struct ssd130x_device *ssd130x, | |
211 | u8 page_start, u8 col_start) | |
212 | { | |
213 | int ret; | |
214 | u32 page, col_low, col_high; | |
215 | ||
216 | page = SSD130X_START_PAGE_ADDRESS | | |
217 | SSD130X_START_PAGE_ADDRESS_SET(page_start); | |
218 | col_low = SSD130X_PAGE_COL_START_LOW | | |
219 | SSD130X_PAGE_COL_START_LOW_SET(col_start); | |
220 | col_high = SSD130X_PAGE_COL_START_HIGH | | |
221 | SSD130X_PAGE_COL_START_HIGH_SET(col_start); | |
222 | ret = ssd130x_write_cmd(ssd130x, 3, page, col_low, col_high); | |
223 | if (ret < 0) | |
224 | return ret; | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
a61732e8 JMC |
229 | static int ssd130x_pwm_enable(struct ssd130x_device *ssd130x) |
230 | { | |
231 | struct device *dev = ssd130x->dev; | |
232 | struct pwm_state pwmstate; | |
233 | ||
234 | ssd130x->pwm = pwm_get(dev, NULL); | |
235 | if (IS_ERR(ssd130x->pwm)) { | |
236 | dev_err(dev, "Could not get PWM from firmware description!\n"); | |
237 | return PTR_ERR(ssd130x->pwm); | |
238 | } | |
239 | ||
240 | pwm_init_state(ssd130x->pwm, &pwmstate); | |
241 | pwm_set_relative_duty_cycle(&pwmstate, 50, 100); | |
242 | pwm_apply_state(ssd130x->pwm, &pwmstate); | |
243 | ||
244 | /* Enable the PWM */ | |
245 | pwm_enable(ssd130x->pwm); | |
246 | ||
247 | dev_dbg(dev, "Using PWM%d with a %lluns period.\n", | |
248 | ssd130x->pwm->pwm, pwm_get_period(ssd130x->pwm)); | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | static void ssd130x_reset(struct ssd130x_device *ssd130x) | |
254 | { | |
255 | if (!ssd130x->reset) | |
256 | return; | |
257 | ||
258 | /* Reset the screen */ | |
259 | gpiod_set_value_cansleep(ssd130x->reset, 1); | |
260 | udelay(4); | |
261 | gpiod_set_value_cansleep(ssd130x->reset, 0); | |
262 | udelay(4); | |
263 | } | |
264 | ||
265 | static int ssd130x_power_on(struct ssd130x_device *ssd130x) | |
266 | { | |
267 | struct device *dev = ssd130x->dev; | |
268 | int ret; | |
269 | ||
270 | ssd130x_reset(ssd130x); | |
271 | ||
272 | ret = regulator_enable(ssd130x->vcc_reg); | |
273 | if (ret) { | |
274 | dev_err(dev, "Failed to enable VCC: %d\n", ret); | |
275 | return ret; | |
276 | } | |
277 | ||
278 | if (ssd130x->device_info->need_pwm) { | |
279 | ret = ssd130x_pwm_enable(ssd130x); | |
280 | if (ret) { | |
281 | dev_err(dev, "Failed to enable PWM: %d\n", ret); | |
282 | regulator_disable(ssd130x->vcc_reg); | |
283 | return ret; | |
284 | } | |
285 | } | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
290 | static void ssd130x_power_off(struct ssd130x_device *ssd130x) | |
291 | { | |
292 | pwm_disable(ssd130x->pwm); | |
293 | pwm_put(ssd130x->pwm); | |
294 | ||
295 | regulator_disable(ssd130x->vcc_reg); | |
296 | } | |
297 | ||
298 | static int ssd130x_init(struct ssd130x_device *ssd130x) | |
299 | { | |
a134109c | 300 | u32 precharge, dclk, com_invdir, compins, chargepump, seg_remap; |
a61732e8 JMC |
301 | int ret; |
302 | ||
303 | /* Set initial contrast */ | |
304 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_CONTRAST, ssd130x->contrast); | |
305 | if (ret < 0) | |
306 | return ret; | |
307 | ||
308 | /* Set segment re-map */ | |
a134109c CYT |
309 | seg_remap = (SSD130X_SET_SEG_REMAP | |
310 | SSD130X_SET_SEG_REMAP_SET(ssd130x->seg_remap)); | |
311 | ret = ssd130x_write_cmd(ssd130x, 1, seg_remap); | |
312 | if (ret < 0) | |
313 | return ret; | |
a61732e8 JMC |
314 | |
315 | /* Set COM direction */ | |
316 | com_invdir = (SSD130X_SET_COM_SCAN_DIR | | |
317 | SSD130X_SET_COM_SCAN_DIR_SET(ssd130x->com_invdir)); | |
318 | ret = ssd130x_write_cmd(ssd130x, 1, com_invdir); | |
319 | if (ret < 0) | |
320 | return ret; | |
321 | ||
322 | /* Set multiplex ratio value */ | |
323 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_MULTIPLEX_RATIO, ssd130x->height - 1); | |
324 | if (ret < 0) | |
325 | return ret; | |
326 | ||
327 | /* set display offset value */ | |
328 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_DISPLAY_OFFSET, ssd130x->com_offset); | |
329 | if (ret < 0) | |
330 | return ret; | |
331 | ||
332 | /* Set clock frequency */ | |
333 | dclk = (SSD130X_SET_CLOCK_DIV_SET(ssd130x->dclk_div - 1) | | |
334 | SSD130X_SET_CLOCK_FREQ_SET(ssd130x->dclk_frq)); | |
335 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_CLOCK_FREQ, dclk); | |
336 | if (ret < 0) | |
337 | return ret; | |
338 | ||
339 | /* Set Area Color Mode ON/OFF & Low Power Display Mode */ | |
340 | if (ssd130x->area_color_enable || ssd130x->low_power) { | |
341 | u32 mode = 0; | |
342 | ||
343 | if (ssd130x->area_color_enable) | |
344 | mode |= SSD130X_SET_AREA_COLOR_MODE_ENABLE; | |
345 | ||
346 | if (ssd130x->low_power) | |
347 | mode |= SSD130X_SET_AREA_COLOR_MODE_LOW_POWER; | |
348 | ||
349 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_AREA_COLOR_MODE, mode); | |
350 | if (ret < 0) | |
351 | return ret; | |
352 | } | |
353 | ||
354 | /* Set precharge period in number of ticks from the internal clock */ | |
355 | precharge = (SSD130X_SET_PRECHARGE_PERIOD1_SET(ssd130x->prechargep1) | | |
b68277f1 | 356 | SSD130X_SET_PRECHARGE_PERIOD2_SET(ssd130x->prechargep2)); |
a61732e8 JMC |
357 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_PRECHARGE_PERIOD, precharge); |
358 | if (ret < 0) | |
359 | return ret; | |
360 | ||
361 | /* Set COM pins configuration */ | |
362 | compins = BIT(1); | |
363 | compins |= (SSD130X_SET_COM_PINS_CONFIG1_SET(ssd130x->com_seq) | | |
364 | SSD130X_SET_COM_PINS_CONFIG2_SET(ssd130x->com_lrremap)); | |
365 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_COM_PINS_CONFIG, compins); | |
366 | if (ret < 0) | |
367 | return ret; | |
368 | ||
369 | /* Set VCOMH */ | |
370 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_VCOMH, ssd130x->vcomh); | |
371 | if (ret < 0) | |
372 | return ret; | |
373 | ||
374 | /* Turn on the DC-DC Charge Pump */ | |
375 | chargepump = BIT(4); | |
376 | ||
377 | if (ssd130x->device_info->need_chargepump) | |
378 | chargepump |= BIT(2); | |
379 | ||
380 | ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_CHARGE_PUMP, chargepump); | |
381 | if (ret < 0) | |
382 | return ret; | |
383 | ||
384 | /* Set lookup table */ | |
385 | if (ssd130x->lookup_table_set) { | |
386 | int i; | |
387 | ||
388 | ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_SET_LOOKUP_TABLE); | |
389 | if (ret < 0) | |
390 | return ret; | |
391 | ||
392 | for (i = 0; i < ARRAY_SIZE(ssd130x->lookup_table); i++) { | |
393 | u8 val = ssd130x->lookup_table[i]; | |
394 | ||
395 | if (val < 31 || val > 63) | |
396 | dev_warn(ssd130x->dev, | |
397 | "lookup table index %d value out of range 31 <= %d <= 63\n", | |
398 | i, val); | |
399 | ret = ssd130x_write_cmd(ssd130x, 1, val); | |
400 | if (ret < 0) | |
401 | return ret; | |
402 | } | |
403 | } | |
404 | ||
b0daaa5c CYT |
405 | /* Switch to page addressing mode */ |
406 | if (ssd130x->page_address_mode) | |
407 | return ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_ADDRESS_MODE, | |
408 | SSD130X_SET_ADDRESS_MODE_PAGE); | |
409 | ||
a61732e8 JMC |
410 | /* Switch to horizontal addressing mode */ |
411 | return ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_ADDRESS_MODE, | |
412 | SSD130X_SET_ADDRESS_MODE_HORIZONTAL); | |
413 | } | |
414 | ||
415 | static int ssd130x_update_rect(struct ssd130x_device *ssd130x, u8 *buf, | |
416 | struct drm_rect *rect) | |
417 | { | |
418 | unsigned int x = rect->x1; | |
419 | unsigned int y = rect->y1; | |
420 | unsigned int width = drm_rect_width(rect); | |
421 | unsigned int height = drm_rect_height(rect); | |
422 | unsigned int line_length = DIV_ROUND_UP(width, 8); | |
a97e753f GU |
423 | unsigned int pages = DIV_ROUND_UP(height, 8); |
424 | struct drm_device *drm = &ssd130x->drm; | |
a61732e8 JMC |
425 | u32 array_idx = 0; |
426 | int ret, i, j, k; | |
427 | u8 *data_array = NULL; | |
428 | ||
a97e753f GU |
429 | drm_WARN_ONCE(drm, y % 8 != 0, "y must be aligned to screen page\n"); |
430 | ||
a61732e8 JMC |
431 | data_array = kcalloc(width, pages, GFP_KERNEL); |
432 | if (!data_array) | |
433 | return -ENOMEM; | |
434 | ||
435 | /* | |
436 | * The screen is divided in pages, each having a height of 8 | |
437 | * pixels, and the width of the screen. When sending a byte of | |
438 | * data to the controller, it gives the 8 bits for the current | |
439 | * column. I.e, the first byte are the 8 bits of the first | |
440 | * column, then the 8 bits for the second column, etc. | |
441 | * | |
442 | * | |
443 | * Representation of the screen, assuming it is 5 bits | |
444 | * wide. Each letter-number combination is a bit that controls | |
445 | * one pixel. | |
446 | * | |
447 | * A0 A1 A2 A3 A4 | |
448 | * B0 B1 B2 B3 B4 | |
449 | * C0 C1 C2 C3 C4 | |
450 | * D0 D1 D2 D3 D4 | |
451 | * E0 E1 E2 E3 E4 | |
452 | * F0 F1 F2 F3 F4 | |
453 | * G0 G1 G2 G3 G4 | |
454 | * H0 H1 H2 H3 H4 | |
455 | * | |
456 | * If you want to update this screen, you need to send 5 bytes: | |
457 | * (1) A0 B0 C0 D0 E0 F0 G0 H0 | |
458 | * (2) A1 B1 C1 D1 E1 F1 G1 H1 | |
459 | * (3) A2 B2 C2 D2 E2 F2 G2 H2 | |
460 | * (4) A3 B3 C3 D3 E3 F3 G3 H3 | |
461 | * (5) A4 B4 C4 D4 E4 F4 G4 H4 | |
462 | */ | |
463 | ||
b0daaa5c CYT |
464 | if (!ssd130x->page_address_mode) { |
465 | /* Set address range for horizontal addressing mode */ | |
466 | ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset + x, width); | |
467 | if (ret < 0) | |
468 | goto out_free; | |
a61732e8 | 469 | |
b0daaa5c CYT |
470 | ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset + y / 8, pages); |
471 | if (ret < 0) | |
472 | goto out_free; | |
473 | } | |
a61732e8 | 474 | |
a97e753f | 475 | for (i = 0; i < pages; i++) { |
a61732e8 JMC |
476 | int m = 8; |
477 | ||
478 | /* Last page may be partial */ | |
a97e753f | 479 | if (8 * (y / 8 + i + 1) > ssd130x->height) |
a61732e8 | 480 | m = ssd130x->height % 8; |
a97e753f | 481 | for (j = 0; j < width; j++) { |
a61732e8 JMC |
482 | u8 data = 0; |
483 | ||
484 | for (k = 0; k < m; k++) { | |
485 | u8 byte = buf[(8 * i + k) * line_length + j / 8]; | |
486 | u8 bit = (byte >> (j % 8)) & 1; | |
487 | ||
488 | data |= bit << k; | |
489 | } | |
490 | data_array[array_idx++] = data; | |
491 | } | |
b0daaa5c CYT |
492 | |
493 | /* | |
494 | * In page addressing mode, the start address needs to be reset, | |
495 | * and each page then needs to be written out separately. | |
496 | */ | |
497 | if (ssd130x->page_address_mode) { | |
498 | ret = ssd130x_set_page_pos(ssd130x, | |
499 | ssd130x->page_offset + i, | |
500 | ssd130x->col_offset + x); | |
501 | if (ret < 0) | |
502 | goto out_free; | |
503 | ||
504 | ret = ssd130x_write_data(ssd130x, data_array, width); | |
505 | if (ret < 0) | |
506 | goto out_free; | |
507 | ||
508 | array_idx = 0; | |
509 | } | |
a61732e8 JMC |
510 | } |
511 | ||
b0daaa5c CYT |
512 | /* Write out update in one go if we aren't using page addressing mode */ |
513 | if (!ssd130x->page_address_mode) | |
514 | ret = ssd130x_write_data(ssd130x, data_array, width * pages); | |
a61732e8 JMC |
515 | |
516 | out_free: | |
517 | kfree(data_array); | |
518 | return ret; | |
519 | } | |
520 | ||
521 | static void ssd130x_clear_screen(struct ssd130x_device *ssd130x) | |
522 | { | |
523 | u8 *buf = NULL; | |
524 | struct drm_rect fullscreen = { | |
525 | .x1 = 0, | |
526 | .x2 = ssd130x->width, | |
527 | .y1 = 0, | |
528 | .y2 = ssd130x->height, | |
529 | }; | |
530 | ||
4442ac1a GU |
531 | buf = kcalloc(DIV_ROUND_UP(ssd130x->width, 8), ssd130x->height, |
532 | GFP_KERNEL); | |
a61732e8 JMC |
533 | if (!buf) |
534 | return; | |
535 | ||
536 | ssd130x_update_rect(ssd130x, buf, &fullscreen); | |
537 | ||
538 | kfree(buf); | |
539 | } | |
540 | ||
b3aca563 | 541 | static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct iosys_map *vmap, |
a61732e8 JMC |
542 | struct drm_rect *rect) |
543 | { | |
544 | struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); | |
b3aca563 | 545 | struct iosys_map dst; |
4442ac1a | 546 | unsigned int dst_pitch; |
a61732e8 JMC |
547 | int ret = 0; |
548 | u8 *buf = NULL; | |
549 | ||
a97e753f GU |
550 | /* Align y to display page boundaries */ |
551 | rect->y1 = round_down(rect->y1, 8); | |
552 | rect->y2 = min_t(unsigned int, round_up(rect->y2, 8), ssd130x->height); | |
553 | ||
4442ac1a GU |
554 | dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 8); |
555 | buf = kcalloc(dst_pitch, drm_rect_height(rect), GFP_KERNEL); | |
a61732e8 JMC |
556 | if (!buf) |
557 | return -ENOMEM; | |
558 | ||
09d6838f JMC |
559 | ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); |
560 | if (ret) | |
561 | goto out_free; | |
562 | ||
b3aca563 TZ |
563 | iosys_map_set_vaddr(&dst, buf); |
564 | drm_fb_xrgb8888_to_mono(&dst, &dst_pitch, vmap, fb, rect); | |
a61732e8 | 565 | |
09d6838f JMC |
566 | drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); |
567 | ||
a61732e8 JMC |
568 | ssd130x_update_rect(ssd130x, buf, rect); |
569 | ||
09d6838f | 570 | out_free: |
a61732e8 JMC |
571 | kfree(buf); |
572 | ||
573 | return ret; | |
574 | } | |
575 | ||
622113b9 | 576 | static void ssd130x_primary_plane_helper_atomic_update(struct drm_plane *plane, |
30b1a079 | 577 | struct drm_atomic_state *state) |
a61732e8 | 578 | { |
30b1a079 JMC |
579 | struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); |
580 | struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); | |
a61732e8 | 581 | struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); |
fdd0640b | 582 | struct drm_atomic_helper_damage_iter iter; |
622113b9 | 583 | struct drm_device *drm = plane->dev; |
fdd0640b JMC |
584 | struct drm_rect dst_clip; |
585 | struct drm_rect damage; | |
622113b9 | 586 | int idx; |
a61732e8 | 587 | |
fdd0640b | 588 | if (!drm_dev_enter(drm, &idx)) |
a61732e8 JMC |
589 | return; |
590 | ||
fdd0640b JMC |
591 | drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); |
592 | drm_atomic_for_each_plane_damage(&iter, &damage) { | |
593 | dst_clip = plane_state->dst; | |
a61732e8 | 594 | |
fdd0640b JMC |
595 | if (!drm_rect_intersect(&dst_clip, &damage)) |
596 | continue; | |
a61732e8 | 597 | |
fdd0640b JMC |
598 | ssd130x_fb_blit_rect(plane_state->fb, &shadow_plane_state->data[0], &dst_clip); |
599 | } | |
a61732e8 JMC |
600 | |
601 | drm_dev_exit(idx); | |
a61732e8 JMC |
602 | } |
603 | ||
622113b9 | 604 | static void ssd130x_primary_plane_helper_atomic_disable(struct drm_plane *plane, |
30b1a079 | 605 | struct drm_atomic_state *state) |
a61732e8 | 606 | { |
622113b9 JMC |
607 | struct drm_device *drm = plane->dev; |
608 | struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); | |
a61732e8 JMC |
609 | int idx; |
610 | ||
611 | if (!drm_dev_enter(drm, &idx)) | |
612 | return; | |
613 | ||
614 | ssd130x_clear_screen(ssd130x); | |
615 | ||
622113b9 JMC |
616 | drm_dev_exit(idx); |
617 | } | |
a61732e8 | 618 | |
622113b9 JMC |
619 | static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs = { |
620 | DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, | |
8401bd36 | 621 | .atomic_check = drm_plane_helper_atomic_check, |
622113b9 JMC |
622 | .atomic_update = ssd130x_primary_plane_helper_atomic_update, |
623 | .atomic_disable = ssd130x_primary_plane_helper_atomic_disable, | |
624 | }; | |
a61732e8 | 625 | |
622113b9 JMC |
626 | static const struct drm_plane_funcs ssd130x_primary_plane_funcs = { |
627 | .update_plane = drm_atomic_helper_update_plane, | |
628 | .disable_plane = drm_atomic_helper_disable_plane, | |
629 | .destroy = drm_plane_cleanup, | |
630 | DRM_GEM_SHADOW_PLANE_FUNCS, | |
631 | }; | |
a61732e8 | 632 | |
622113b9 JMC |
633 | static enum drm_mode_status ssd130x_crtc_helper_mode_valid(struct drm_crtc *crtc, |
634 | const struct drm_display_mode *mode) | |
635 | { | |
636 | struct ssd130x_device *ssd130x = drm_to_ssd130x(crtc->dev); | |
637 | ||
638 | if (mode->hdisplay != ssd130x->mode.hdisplay && | |
639 | mode->vdisplay != ssd130x->mode.vdisplay) | |
640 | return MODE_ONE_SIZE; | |
641 | else if (mode->hdisplay != ssd130x->mode.hdisplay) | |
642 | return MODE_ONE_WIDTH; | |
643 | else if (mode->vdisplay != ssd130x->mode.vdisplay) | |
644 | return MODE_ONE_HEIGHT; | |
645 | ||
646 | return MODE_OK; | |
a61732e8 JMC |
647 | } |
648 | ||
622113b9 JMC |
649 | /* |
650 | * The CRTC is always enabled. Screen updates are performed by | |
651 | * the primary plane's atomic_update function. Disabling clears | |
652 | * the screen in the primary plane's atomic_disable function. | |
653 | */ | |
654 | static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs = { | |
655 | .mode_valid = ssd130x_crtc_helper_mode_valid, | |
7fed7fa3 | 656 | .atomic_check = drm_crtc_helper_atomic_check, |
622113b9 | 657 | }; |
a61732e8 | 658 | |
622113b9 JMC |
659 | static void ssd130x_crtc_reset(struct drm_crtc *crtc) |
660 | { | |
661 | struct drm_device *drm = crtc->dev; | |
662 | struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); | |
a61732e8 | 663 | |
622113b9 JMC |
664 | ssd130x_init(ssd130x); |
665 | ||
666 | drm_atomic_helper_crtc_reset(crtc); | |
667 | } | |
668 | ||
669 | static const struct drm_crtc_funcs ssd130x_crtc_funcs = { | |
670 | .reset = ssd130x_crtc_reset, | |
671 | .destroy = drm_crtc_cleanup, | |
672 | .set_config = drm_atomic_helper_set_config, | |
673 | .page_flip = drm_atomic_helper_page_flip, | |
674 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, | |
675 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, | |
676 | }; | |
677 | ||
678 | static void ssd130x_encoder_helper_atomic_enable(struct drm_encoder *encoder, | |
679 | struct drm_atomic_state *state) | |
680 | { | |
681 | struct drm_device *drm = encoder->dev; | |
682 | struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); | |
683 | int ret; | |
684 | ||
685 | ret = ssd130x_power_on(ssd130x); | |
686 | if (ret) | |
a61732e8 JMC |
687 | return; |
688 | ||
622113b9 | 689 | ssd130x_write_cmd(ssd130x, 1, SSD130X_DISPLAY_ON); |
a61732e8 | 690 | |
622113b9 | 691 | backlight_enable(ssd130x->bl_dev); |
a61732e8 JMC |
692 | } |
693 | ||
622113b9 JMC |
694 | static void ssd130x_encoder_helper_atomic_disable(struct drm_encoder *encoder, |
695 | struct drm_atomic_state *state) | |
696 | { | |
697 | struct drm_device *drm = encoder->dev; | |
698 | struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); | |
699 | ||
700 | backlight_disable(ssd130x->bl_dev); | |
701 | ||
702 | ssd130x_write_cmd(ssd130x, 1, SSD130X_DISPLAY_OFF); | |
703 | ||
704 | ssd130x_power_off(ssd130x); | |
705 | } | |
706 | ||
707 | static const struct drm_encoder_helper_funcs ssd130x_encoder_helper_funcs = { | |
708 | .atomic_enable = ssd130x_encoder_helper_atomic_enable, | |
709 | .atomic_disable = ssd130x_encoder_helper_atomic_disable, | |
710 | }; | |
711 | ||
712 | static const struct drm_encoder_funcs ssd130x_encoder_funcs = { | |
713 | .destroy = drm_encoder_cleanup, | |
a61732e8 JMC |
714 | }; |
715 | ||
622113b9 | 716 | static int ssd130x_connector_helper_get_modes(struct drm_connector *connector) |
a61732e8 JMC |
717 | { |
718 | struct ssd130x_device *ssd130x = drm_to_ssd130x(connector->dev); | |
701920ca | 719 | struct drm_display_mode *mode; |
a61732e8 JMC |
720 | struct device *dev = ssd130x->dev; |
721 | ||
722 | mode = drm_mode_duplicate(connector->dev, &ssd130x->mode); | |
723 | if (!mode) { | |
724 | dev_err(dev, "Failed to duplicated mode\n"); | |
725 | return 0; | |
726 | } | |
727 | ||
728 | drm_mode_probed_add(connector, mode); | |
729 | drm_set_preferred_mode(connector, mode->hdisplay, mode->vdisplay); | |
730 | ||
731 | /* There is only a single mode */ | |
732 | return 1; | |
733 | } | |
734 | ||
735 | static const struct drm_connector_helper_funcs ssd130x_connector_helper_funcs = { | |
622113b9 | 736 | .get_modes = ssd130x_connector_helper_get_modes, |
a61732e8 JMC |
737 | }; |
738 | ||
739 | static const struct drm_connector_funcs ssd130x_connector_funcs = { | |
740 | .reset = drm_atomic_helper_connector_reset, | |
741 | .fill_modes = drm_helper_probe_single_connector_modes, | |
742 | .destroy = drm_connector_cleanup, | |
743 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
744 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
745 | }; | |
746 | ||
747 | static const struct drm_mode_config_funcs ssd130x_mode_config_funcs = { | |
748 | .fb_create = drm_gem_fb_create_with_dirty, | |
749 | .atomic_check = drm_atomic_helper_check, | |
750 | .atomic_commit = drm_atomic_helper_commit, | |
751 | }; | |
752 | ||
753 | static const uint32_t ssd130x_formats[] = { | |
754 | DRM_FORMAT_XRGB8888, | |
755 | }; | |
756 | ||
757 | DEFINE_DRM_GEM_FOPS(ssd130x_fops); | |
758 | ||
759 | static const struct drm_driver ssd130x_drm_driver = { | |
760 | DRM_GEM_SHMEM_DRIVER_OPS, | |
761 | .name = DRIVER_NAME, | |
762 | .desc = DRIVER_DESC, | |
763 | .date = DRIVER_DATE, | |
764 | .major = DRIVER_MAJOR, | |
765 | .minor = DRIVER_MINOR, | |
766 | .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, | |
767 | .fops = &ssd130x_fops, | |
768 | }; | |
769 | ||
770 | static int ssd130x_update_bl(struct backlight_device *bdev) | |
771 | { | |
772 | struct ssd130x_device *ssd130x = bl_get_data(bdev); | |
773 | int brightness = backlight_get_brightness(bdev); | |
774 | int ret; | |
775 | ||
776 | ssd130x->contrast = brightness; | |
777 | ||
778 | ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_CONTRAST); | |
779 | if (ret < 0) | |
780 | return ret; | |
781 | ||
782 | ret = ssd130x_write_cmd(ssd130x, 1, ssd130x->contrast); | |
783 | if (ret < 0) | |
784 | return ret; | |
785 | ||
786 | return 0; | |
787 | } | |
788 | ||
789 | static const struct backlight_ops ssd130xfb_bl_ops = { | |
790 | .update_status = ssd130x_update_bl, | |
791 | }; | |
792 | ||
793 | static void ssd130x_parse_properties(struct ssd130x_device *ssd130x) | |
794 | { | |
795 | struct device *dev = ssd130x->dev; | |
796 | ||
797 | if (device_property_read_u32(dev, "solomon,width", &ssd130x->width)) | |
798 | ssd130x->width = 96; | |
799 | ||
800 | if (device_property_read_u32(dev, "solomon,height", &ssd130x->height)) | |
801 | ssd130x->height = 16; | |
802 | ||
803 | if (device_property_read_u32(dev, "solomon,page-offset", &ssd130x->page_offset)) | |
804 | ssd130x->page_offset = 1; | |
805 | ||
806 | if (device_property_read_u32(dev, "solomon,col-offset", &ssd130x->col_offset)) | |
807 | ssd130x->col_offset = 0; | |
808 | ||
809 | if (device_property_read_u32(dev, "solomon,com-offset", &ssd130x->com_offset)) | |
810 | ssd130x->com_offset = 0; | |
811 | ||
812 | if (device_property_read_u32(dev, "solomon,prechargep1", &ssd130x->prechargep1)) | |
813 | ssd130x->prechargep1 = 2; | |
814 | ||
815 | if (device_property_read_u32(dev, "solomon,prechargep2", &ssd130x->prechargep2)) | |
816 | ssd130x->prechargep2 = 2; | |
817 | ||
818 | if (!device_property_read_u8_array(dev, "solomon,lookup-table", | |
819 | ssd130x->lookup_table, | |
820 | ARRAY_SIZE(ssd130x->lookup_table))) | |
821 | ssd130x->lookup_table_set = 1; | |
822 | ||
823 | ssd130x->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap"); | |
824 | ssd130x->com_seq = device_property_read_bool(dev, "solomon,com-seq"); | |
825 | ssd130x->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap"); | |
826 | ssd130x->com_invdir = device_property_read_bool(dev, "solomon,com-invdir"); | |
827 | ssd130x->area_color_enable = | |
828 | device_property_read_bool(dev, "solomon,area-color-enable"); | |
829 | ssd130x->low_power = device_property_read_bool(dev, "solomon,low-power"); | |
830 | ||
831 | ssd130x->contrast = 127; | |
832 | ssd130x->vcomh = ssd130x->device_info->default_vcomh; | |
833 | ||
834 | /* Setup display timing */ | |
835 | if (device_property_read_u32(dev, "solomon,dclk-div", &ssd130x->dclk_div)) | |
836 | ssd130x->dclk_div = ssd130x->device_info->default_dclk_div; | |
837 | if (device_property_read_u32(dev, "solomon,dclk-frq", &ssd130x->dclk_frq)) | |
838 | ssd130x->dclk_frq = ssd130x->device_info->default_dclk_frq; | |
839 | } | |
840 | ||
841 | static int ssd130x_init_modeset(struct ssd130x_device *ssd130x) | |
842 | { | |
843 | struct drm_display_mode *mode = &ssd130x->mode; | |
844 | struct device *dev = ssd130x->dev; | |
845 | struct drm_device *drm = &ssd130x->drm; | |
846 | unsigned long max_width, max_height; | |
622113b9 JMC |
847 | struct drm_plane *primary_plane; |
848 | struct drm_crtc *crtc; | |
849 | struct drm_encoder *encoder; | |
850 | struct drm_connector *connector; | |
a61732e8 JMC |
851 | int ret; |
852 | ||
622113b9 JMC |
853 | /* |
854 | * Modesetting | |
855 | */ | |
856 | ||
a61732e8 JMC |
857 | ret = drmm_mode_config_init(drm); |
858 | if (ret) { | |
859 | dev_err(dev, "DRM mode config init failed: %d\n", ret); | |
860 | return ret; | |
861 | } | |
862 | ||
863 | mode->type = DRM_MODE_TYPE_DRIVER; | |
864 | mode->clock = 1; | |
865 | mode->hdisplay = mode->htotal = ssd130x->width; | |
866 | mode->hsync_start = mode->hsync_end = ssd130x->width; | |
867 | mode->vdisplay = mode->vtotal = ssd130x->height; | |
868 | mode->vsync_start = mode->vsync_end = ssd130x->height; | |
869 | mode->width_mm = 27; | |
870 | mode->height_mm = 27; | |
871 | ||
872 | max_width = max_t(unsigned long, mode->hdisplay, DRM_SHADOW_PLANE_MAX_WIDTH); | |
873 | max_height = max_t(unsigned long, mode->vdisplay, DRM_SHADOW_PLANE_MAX_HEIGHT); | |
874 | ||
875 | drm->mode_config.min_width = mode->hdisplay; | |
876 | drm->mode_config.max_width = max_width; | |
877 | drm->mode_config.min_height = mode->vdisplay; | |
878 | drm->mode_config.max_height = max_height; | |
879 | drm->mode_config.preferred_depth = 32; | |
880 | drm->mode_config.funcs = &ssd130x_mode_config_funcs; | |
881 | ||
622113b9 JMC |
882 | /* Primary plane */ |
883 | ||
884 | primary_plane = &ssd130x->primary_plane; | |
885 | ret = drm_universal_plane_init(drm, primary_plane, 0, &ssd130x_primary_plane_funcs, | |
886 | ssd130x_formats, ARRAY_SIZE(ssd130x_formats), | |
887 | NULL, DRM_PLANE_TYPE_PRIMARY, NULL); | |
888 | if (ret) { | |
889 | dev_err(dev, "DRM primary plane init failed: %d\n", ret); | |
890 | return ret; | |
891 | } | |
892 | ||
893 | drm_plane_helper_add(primary_plane, &ssd130x_primary_plane_helper_funcs); | |
894 | ||
895 | drm_plane_enable_fb_damage_clips(primary_plane); | |
896 | ||
897 | /* CRTC */ | |
898 | ||
899 | crtc = &ssd130x->crtc; | |
900 | ret = drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, | |
901 | &ssd130x_crtc_funcs, NULL); | |
902 | if (ret) { | |
903 | dev_err(dev, "DRM crtc init failed: %d\n", ret); | |
904 | return ret; | |
905 | } | |
906 | ||
907 | drm_crtc_helper_add(crtc, &ssd130x_crtc_helper_funcs); | |
908 | ||
909 | /* Encoder */ | |
910 | ||
911 | encoder = &ssd130x->encoder; | |
912 | ret = drm_encoder_init(drm, encoder, &ssd130x_encoder_funcs, | |
913 | DRM_MODE_ENCODER_NONE, NULL); | |
914 | if (ret) { | |
915 | dev_err(dev, "DRM encoder init failed: %d\n", ret); | |
916 | return ret; | |
917 | } | |
918 | ||
919 | drm_encoder_helper_add(encoder, &ssd130x_encoder_helper_funcs); | |
920 | ||
921 | encoder->possible_crtcs = drm_crtc_mask(crtc); | |
922 | ||
923 | /* Connector */ | |
924 | ||
925 | connector = &ssd130x->connector; | |
926 | ret = drm_connector_init(drm, connector, &ssd130x_connector_funcs, | |
a61732e8 JMC |
927 | DRM_MODE_CONNECTOR_Unknown); |
928 | if (ret) { | |
929 | dev_err(dev, "DRM connector init failed: %d\n", ret); | |
930 | return ret; | |
931 | } | |
932 | ||
622113b9 | 933 | drm_connector_helper_add(connector, &ssd130x_connector_helper_funcs); |
a61732e8 | 934 | |
622113b9 | 935 | ret = drm_connector_attach_encoder(connector, encoder); |
a61732e8 | 936 | if (ret) { |
622113b9 | 937 | dev_err(dev, "DRM attach connector to encoder failed: %d\n", ret); |
a61732e8 JMC |
938 | return ret; |
939 | } | |
940 | ||
a61732e8 JMC |
941 | drm_mode_config_reset(drm); |
942 | ||
943 | return 0; | |
944 | } | |
945 | ||
946 | static int ssd130x_get_resources(struct ssd130x_device *ssd130x) | |
947 | { | |
948 | struct device *dev = ssd130x->dev; | |
949 | ||
950 | ssd130x->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); | |
951 | if (IS_ERR(ssd130x->reset)) | |
952 | return dev_err_probe(dev, PTR_ERR(ssd130x->reset), | |
953 | "Failed to get reset gpio\n"); | |
954 | ||
955 | ssd130x->vcc_reg = devm_regulator_get(dev, "vcc"); | |
956 | if (IS_ERR(ssd130x->vcc_reg)) | |
957 | return dev_err_probe(dev, PTR_ERR(ssd130x->vcc_reg), | |
958 | "Failed to get VCC regulator\n"); | |
959 | ||
960 | return 0; | |
961 | } | |
962 | ||
963 | struct ssd130x_device *ssd130x_probe(struct device *dev, struct regmap *regmap) | |
964 | { | |
965 | struct ssd130x_device *ssd130x; | |
966 | struct backlight_device *bl; | |
967 | struct drm_device *drm; | |
968 | int ret; | |
969 | ||
970 | ssd130x = devm_drm_dev_alloc(dev, &ssd130x_drm_driver, | |
971 | struct ssd130x_device, drm); | |
972 | if (IS_ERR(ssd130x)) | |
973 | return ERR_PTR(dev_err_probe(dev, PTR_ERR(ssd130x), | |
974 | "Failed to allocate DRM device\n")); | |
975 | ||
976 | drm = &ssd130x->drm; | |
977 | ||
978 | ssd130x->dev = dev; | |
979 | ssd130x->regmap = regmap; | |
980 | ssd130x->device_info = device_get_match_data(dev); | |
981 | ||
b0daaa5c CYT |
982 | if (ssd130x->device_info->page_mode_only) |
983 | ssd130x->page_address_mode = 1; | |
984 | ||
a61732e8 JMC |
985 | ssd130x_parse_properties(ssd130x); |
986 | ||
987 | ret = ssd130x_get_resources(ssd130x); | |
988 | if (ret) | |
989 | return ERR_PTR(ret); | |
990 | ||
991 | bl = devm_backlight_device_register(dev, dev_name(dev), dev, ssd130x, | |
992 | &ssd130xfb_bl_ops, NULL); | |
993 | if (IS_ERR(bl)) | |
994 | return ERR_PTR(dev_err_probe(dev, PTR_ERR(bl), | |
995 | "Unable to register backlight device\n")); | |
996 | ||
997 | bl->props.brightness = ssd130x->contrast; | |
998 | bl->props.max_brightness = MAX_CONTRAST; | |
999 | ssd130x->bl_dev = bl; | |
1000 | ||
1001 | ret = ssd130x_init_modeset(ssd130x); | |
1002 | if (ret) | |
1003 | return ERR_PTR(ret); | |
1004 | ||
1005 | ret = drm_dev_register(drm, 0); | |
1006 | if (ret) | |
1007 | return ERR_PTR(dev_err_probe(dev, ret, "DRM device register failed\n")); | |
1008 | ||
1009 | drm_fbdev_generic_setup(drm, 0); | |
1010 | ||
1011 | return ssd130x; | |
1012 | } | |
1013 | EXPORT_SYMBOL_GPL(ssd130x_probe); | |
1014 | ||
a4359b4e | 1015 | void ssd130x_remove(struct ssd130x_device *ssd130x) |
a61732e8 JMC |
1016 | { |
1017 | drm_dev_unplug(&ssd130x->drm); | |
a61732e8 JMC |
1018 | } |
1019 | EXPORT_SYMBOL_GPL(ssd130x_remove); | |
1020 | ||
1021 | void ssd130x_shutdown(struct ssd130x_device *ssd130x) | |
1022 | { | |
1023 | drm_atomic_helper_shutdown(&ssd130x->drm); | |
1024 | } | |
1025 | EXPORT_SYMBOL_GPL(ssd130x_shutdown); | |
1026 | ||
1027 | MODULE_DESCRIPTION(DRIVER_DESC); | |
1028 | MODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>"); | |
1029 | MODULE_LICENSE("GPL v2"); |