diff options
-rw-r--r-- | Documentation/devicetree/bindings/video/ssd1307fb.txt | 24 | ||||
-rw-r--r-- | drivers/video/Kconfig | 15 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/ssd1307fb.c | 396 |
4 files changed, 436 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/video/ssd1307fb.txt b/Documentation/devicetree/bindings/video/ssd1307fb.txt new file mode 100644 index 000000000000..3d0060cff062 --- /dev/null +++ b/Documentation/devicetree/bindings/video/ssd1307fb.txt | |||
@@ -0,0 +1,24 @@ | |||
1 | * Solomon SSD1307 Framebuffer Driver | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: Should be "solomon,ssd1307fb-<bus>". The only supported bus for | ||
5 | now is i2c. | ||
6 | - reg: Should contain address of the controller on the I2C bus. Most likely | ||
7 | 0x3c or 0x3d | ||
8 | - pwm: Should contain the pwm to use according to the OF device tree PWM | ||
9 | specification [0] | ||
10 | - reset-gpios: Should contain the GPIO used to reset the OLED display | ||
11 | |||
12 | Optional properties: | ||
13 | - reset-active-low: Is the reset gpio is active on physical low? | ||
14 | |||
15 | [0]: Documentation/devicetree/bindings/pwm/pwm.txt | ||
16 | |||
17 | Examples: | ||
18 | ssd1307: oled@3c { | ||
19 | compatible = "solomon,ssd1307fb-i2c"; | ||
20 | reg = <0x3c>; | ||
21 | pwms = <&pwm 4 3000>; | ||
22 | reset-gpios = <&gpio2 7>; | ||
23 | reset-active-low; | ||
24 | }; | ||
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 9018a90b4588..9c31277b3a81 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig | |||
@@ -2442,4 +2442,19 @@ config FB_SH_MOBILE_MERAM | |||
2442 | Up to 4 memory channels can be configured, allowing 4 RGB or | 2442 | Up to 4 memory channels can be configured, allowing 4 RGB or |
2443 | 2 YCbCr framebuffers to be configured. | 2443 | 2 YCbCr framebuffers to be configured. |
2444 | 2444 | ||
2445 | config FB_SSD1307 | ||
2446 | tristate "Solomon SSD1307 framebuffer support" | ||
2447 | depends on FB && I2C | ||
2448 | depends on OF | ||
2449 | depends on GENERIC_GPIO | ||
2450 | select FB_SYS_FOPS | ||
2451 | select FB_SYS_FILLRECT | ||
2452 | select FB_SYS_COPYAREA | ||
2453 | select FB_SYS_IMAGEBLIT | ||
2454 | select FB_DEFERRED_IO | ||
2455 | select PWM | ||
2456 | help | ||
2457 | This driver implements support for the Solomon SSD1307 | ||
2458 | OLED controller over I2C. | ||
2459 | |||
2445 | endmenu | 2460 | endmenu |
diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 23e948ebfab8..768a137a1bac 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile | |||
@@ -161,6 +161,7 @@ obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o | |||
161 | obj-$(CONFIG_FB_MX3) += mx3fb.o | 161 | obj-$(CONFIG_FB_MX3) += mx3fb.o |
162 | obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o | 162 | obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o |
163 | obj-$(CONFIG_FB_MXS) += mxsfb.o | 163 | obj-$(CONFIG_FB_MXS) += mxsfb.o |
164 | obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o | ||
164 | 165 | ||
165 | # the test framebuffer is last | 166 | # the test framebuffer is last |
166 | obj-$(CONFIG_FB_VIRTUAL) += vfb.o | 167 | obj-$(CONFIG_FB_VIRTUAL) += vfb.o |
diff --git a/drivers/video/ssd1307fb.c b/drivers/video/ssd1307fb.c new file mode 100644 index 000000000000..6101f5c2f62f --- /dev/null +++ b/drivers/video/ssd1307fb.c | |||
@@ -0,0 +1,396 @@ | |||
1 | /* | ||
2 | * Driver for the Solomon SSD1307 OLED controler | ||
3 | * | ||
4 | * Copyright 2012 Free Electrons | ||
5 | * | ||
6 | * Licensed under the GPLv2 or later. | ||
7 | */ | ||
8 | |||
9 | #include <linux/module.h> | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/i2c.h> | ||
12 | #include <linux/fb.h> | ||
13 | #include <linux/uaccess.h> | ||
14 | #include <linux/of_device.h> | ||
15 | #include <linux/of_gpio.h> | ||
16 | #include <linux/pwm.h> | ||
17 | #include <linux/delay.h> | ||
18 | |||
19 | #define SSD1307FB_WIDTH 96 | ||
20 | #define SSD1307FB_HEIGHT 16 | ||
21 | |||
22 | #define SSD1307FB_DATA 0x40 | ||
23 | #define SSD1307FB_COMMAND 0x80 | ||
24 | |||
25 | #define SSD1307FB_CONTRAST 0x81 | ||
26 | #define SSD1307FB_SEG_REMAP_ON 0xa1 | ||
27 | #define SSD1307FB_DISPLAY_OFF 0xae | ||
28 | #define SSD1307FB_DISPLAY_ON 0xaf | ||
29 | #define SSD1307FB_START_PAGE_ADDRESS 0xb0 | ||
30 | |||
31 | struct ssd1307fb_par { | ||
32 | struct i2c_client *client; | ||
33 | struct fb_info *info; | ||
34 | struct pwm_device *pwm; | ||
35 | u32 pwm_period; | ||
36 | int reset; | ||
37 | }; | ||
38 | |||
39 | static struct fb_fix_screeninfo ssd1307fb_fix __devinitdata = { | ||
40 | .id = "Solomon SSD1307", | ||
41 | .type = FB_TYPE_PACKED_PIXELS, | ||
42 | .visual = FB_VISUAL_MONO10, | ||
43 | .xpanstep = 0, | ||
44 | .ypanstep = 0, | ||
45 | .ywrapstep = 0, | ||
46 | .line_length = SSD1307FB_WIDTH / 8, | ||
47 | .accel = FB_ACCEL_NONE, | ||
48 | }; | ||
49 | |||
50 | static struct fb_var_screeninfo ssd1307fb_var __devinitdata = { | ||
51 | .xres = SSD1307FB_WIDTH, | ||
52 | .yres = SSD1307FB_HEIGHT, | ||
53 | .xres_virtual = SSD1307FB_WIDTH, | ||
54 | .yres_virtual = SSD1307FB_HEIGHT, | ||
55 | .bits_per_pixel = 1, | ||
56 | }; | ||
57 | |||
58 | static int ssd1307fb_write_array(struct i2c_client *client, u8 type, u8 *cmd, u32 len) | ||
59 | { | ||
60 | u8 *buf; | ||
61 | int ret = 0; | ||
62 | |||
63 | buf = kzalloc(len + 1, GFP_KERNEL); | ||
64 | if (!buf) { | ||
65 | dev_err(&client->dev, "Couldn't allocate sending buffer.\n"); | ||
66 | return -ENOMEM; | ||
67 | } | ||
68 | |||
69 | buf[0] = type; | ||
70 | memcpy(buf + 1, cmd, len); | ||
71 | |||
72 | ret = i2c_master_send(client, buf, len + 1); | ||
73 | if (ret != len + 1) { | ||
74 | dev_err(&client->dev, "Couldn't send I2C command.\n"); | ||
75 | goto error; | ||
76 | } | ||
77 | |||
78 | error: | ||
79 | kfree(buf); | ||
80 | return ret; | ||
81 | } | ||
82 | |||
83 | static inline int ssd1307fb_write_cmd_array(struct i2c_client *client, u8 *cmd, u32 len) | ||
84 | { | ||
85 | return ssd1307fb_write_array(client, SSD1307FB_COMMAND, cmd, len); | ||
86 | } | ||
87 | |||
88 | static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) | ||
89 | { | ||
90 | return ssd1307fb_write_cmd_array(client, &cmd, 1); | ||
91 | } | ||
92 | |||
93 | static inline int ssd1307fb_write_data_array(struct i2c_client *client, u8 *cmd, u32 len) | ||
94 | { | ||
95 | return ssd1307fb_write_array(client, SSD1307FB_DATA, cmd, len); | ||
96 | } | ||
97 | |||
98 | static inline int ssd1307fb_write_data(struct i2c_client *client, u8 data) | ||
99 | { | ||
100 | return ssd1307fb_write_data_array(client, &data, 1); | ||
101 | } | ||
102 | |||
103 | static void ssd1307fb_update_display(struct ssd1307fb_par *par) | ||
104 | { | ||
105 | u8 *vmem = par->info->screen_base; | ||
106 | int i, j, k; | ||
107 | |||
108 | /* | ||
109 | * The screen is divided in pages, each having a height of 8 | ||
110 | * pixels, and the width of the screen. When sending a byte of | ||
111 | * data to the controller, it gives the 8 bits for the current | ||
112 | * column. I.e, the first byte are the 8 bits of the first | ||
113 | * column, then the 8 bits for the second column, etc. | ||
114 | * | ||
115 | * | ||
116 | * Representation of the screen, assuming it is 5 bits | ||
117 | * wide. Each letter-number combination is a bit that controls | ||
118 | * one pixel. | ||
119 | * | ||
120 | * A0 A1 A2 A3 A4 | ||
121 | * B0 B1 B2 B3 B4 | ||
122 | * C0 C1 C2 C3 C4 | ||
123 | * D0 D1 D2 D3 D4 | ||
124 | * E0 E1 E2 E3 E4 | ||
125 | * F0 F1 F2 F3 F4 | ||
126 | * G0 G1 G2 G3 G4 | ||
127 | * H0 H1 H2 H3 H4 | ||
128 | * | ||
129 | * If you want to update this screen, you need to send 5 bytes: | ||
130 | * (1) A0 B0 C0 D0 E0 F0 G0 H0 | ||
131 | * (2) A1 B1 C1 D1 E1 F1 G1 H1 | ||
132 | * (3) A2 B2 C2 D2 E2 F2 G2 H2 | ||
133 | * (4) A3 B3 C3 D3 E3 F3 G3 H3 | ||
134 | * (5) A4 B4 C4 D4 E4 F4 G4 H4 | ||
135 | */ | ||
136 | |||
137 | for (i = 0; i < (SSD1307FB_HEIGHT / 8); i++) { | ||
138 | ssd1307fb_write_cmd(par->client, SSD1307FB_START_PAGE_ADDRESS + (i + 1)); | ||
139 | ssd1307fb_write_cmd(par->client, 0x00); | ||
140 | ssd1307fb_write_cmd(par->client, 0x10); | ||
141 | |||
142 | for (j = 0; j < SSD1307FB_WIDTH; j++) { | ||
143 | u8 buf = 0; | ||
144 | for (k = 0; k < 8; k++) { | ||
145 | u32 page_length = SSD1307FB_WIDTH * i; | ||
146 | u32 index = page_length + (SSD1307FB_WIDTH * k + j) / 8; | ||
147 | u8 byte = *(vmem + index); | ||
148 | u8 bit = byte & (1 << (7 - (j % 8))); | ||
149 | bit = bit >> (7 - (j % 8)); | ||
150 | buf |= bit << k; | ||
151 | } | ||
152 | ssd1307fb_write_data(par->client, buf); | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | |||
157 | |||
158 | static ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf, | ||
159 | size_t count, loff_t *ppos) | ||
160 | { | ||
161 | struct ssd1307fb_par *par = info->par; | ||
162 | unsigned long total_size; | ||
163 | unsigned long p = *ppos; | ||
164 | u8 __iomem *dst; | ||
165 | |||
166 | total_size = info->fix.smem_len; | ||
167 | |||
168 | if (p > total_size) | ||
169 | return -EINVAL; | ||
170 | |||
171 | if (count + p > total_size) | ||
172 | count = total_size - p; | ||
173 | |||
174 | if (!count) | ||
175 | return -EINVAL; | ||
176 | |||
177 | dst = (void __force *) (info->screen_base + p); | ||
178 | |||
179 | if (copy_from_user(dst, buf, count)) | ||
180 | return -EFAULT; | ||
181 | |||
182 | ssd1307fb_update_display(par); | ||
183 | |||
184 | *ppos += count; | ||
185 | |||
186 | return count; | ||
187 | } | ||
188 | |||
189 | static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) | ||
190 | { | ||
191 | struct ssd1307fb_par *par = info->par; | ||
192 | sys_fillrect(info, rect); | ||
193 | ssd1307fb_update_display(par); | ||
194 | } | ||
195 | |||
196 | static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) | ||
197 | { | ||
198 | struct ssd1307fb_par *par = info->par; | ||
199 | sys_copyarea(info, area); | ||
200 | ssd1307fb_update_display(par); | ||
201 | } | ||
202 | |||
203 | static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) | ||
204 | { | ||
205 | struct ssd1307fb_par *par = info->par; | ||
206 | sys_imageblit(info, image); | ||
207 | ssd1307fb_update_display(par); | ||
208 | } | ||
209 | |||
210 | static struct fb_ops ssd1307fb_ops = { | ||
211 | .owner = THIS_MODULE, | ||
212 | .fb_read = fb_sys_read, | ||
213 | .fb_write = ssd1307fb_write, | ||
214 | .fb_fillrect = ssd1307fb_fillrect, | ||
215 | .fb_copyarea = ssd1307fb_copyarea, | ||
216 | .fb_imageblit = ssd1307fb_imageblit, | ||
217 | }; | ||
218 | |||
219 | static void ssd1307fb_deferred_io(struct fb_info *info, | ||
220 | struct list_head *pagelist) | ||
221 | { | ||
222 | ssd1307fb_update_display(info->par); | ||
223 | } | ||
224 | |||
225 | static struct fb_deferred_io ssd1307fb_defio = { | ||
226 | .delay = HZ, | ||
227 | .deferred_io = ssd1307fb_deferred_io, | ||
228 | }; | ||
229 | |||
230 | static int __devinit ssd1307fb_probe(struct i2c_client *client, const struct i2c_device_id *id) | ||
231 | { | ||
232 | struct fb_info *info; | ||
233 | u32 vmem_size = SSD1307FB_WIDTH * SSD1307FB_HEIGHT / 8; | ||
234 | struct ssd1307fb_par *par; | ||
235 | u8 *vmem; | ||
236 | int ret; | ||
237 | |||
238 | if (!client->dev.of_node) { | ||
239 | dev_err(&client->dev, "No device tree data found!\n"); | ||
240 | return -EINVAL; | ||
241 | } | ||
242 | |||
243 | info = framebuffer_alloc(sizeof(struct ssd1307fb_par), &client->dev); | ||
244 | if (!info) { | ||
245 | dev_err(&client->dev, "Couldn't allocate framebuffer.\n"); | ||
246 | return -ENOMEM; | ||
247 | } | ||
248 | |||
249 | vmem = devm_kzalloc(&client->dev, vmem_size, GFP_KERNEL); | ||
250 | if (!vmem) { | ||
251 | dev_err(&client->dev, "Couldn't allocate graphical memory.\n"); | ||
252 | ret = -ENOMEM; | ||
253 | goto fb_alloc_error; | ||
254 | } | ||
255 | |||
256 | info->fbops = &ssd1307fb_ops; | ||
257 | info->fix = ssd1307fb_fix; | ||
258 | info->fbdefio = &ssd1307fb_defio; | ||
259 | |||
260 | info->var = ssd1307fb_var; | ||
261 | info->var.red.length = 1; | ||
262 | info->var.red.offset = 0; | ||
263 | info->var.green.length = 1; | ||
264 | info->var.green.offset = 0; | ||
265 | info->var.blue.length = 1; | ||
266 | info->var.blue.offset = 0; | ||
267 | |||
268 | info->screen_base = (u8 __force __iomem *)vmem; | ||
269 | info->fix.smem_start = (unsigned long)vmem; | ||
270 | info->fix.smem_len = vmem_size; | ||
271 | |||
272 | fb_deferred_io_init(info); | ||
273 | |||
274 | par = info->par; | ||
275 | par->info = info; | ||
276 | par->client = client; | ||
277 | |||
278 | par->reset = of_get_named_gpio(client->dev.of_node, | ||
279 | "reset-gpios", 0); | ||
280 | if (!gpio_is_valid(par->reset)) { | ||
281 | ret = -EINVAL; | ||
282 | goto reset_oled_error; | ||
283 | } | ||
284 | |||
285 | ret = devm_gpio_request_one(&client->dev, par->reset, | ||
286 | GPIOF_OUT_INIT_HIGH, | ||
287 | "oled-reset"); | ||
288 | if (ret) { | ||
289 | dev_err(&client->dev, | ||
290 | "failed to request gpio %d: %d\n", | ||
291 | par->reset, ret); | ||
292 | goto reset_oled_error; | ||
293 | } | ||
294 | |||
295 | par->pwm = pwm_get(&client->dev, NULL); | ||
296 | if (IS_ERR(par->pwm)) { | ||
297 | dev_err(&client->dev, "Could not get PWM from device tree!\n"); | ||
298 | ret = PTR_ERR(par->pwm); | ||
299 | goto pwm_error; | ||
300 | } | ||
301 | |||
302 | par->pwm_period = pwm_get_period(par->pwm); | ||
303 | |||
304 | dev_dbg(&client->dev, "Using PWM%d with a %dns period.\n", par->pwm->pwm, par->pwm_period); | ||
305 | |||
306 | ret = register_framebuffer(info); | ||
307 | if (ret) { | ||
308 | dev_err(&client->dev, "Couldn't register the framebuffer\n"); | ||
309 | goto fbreg_error; | ||
310 | } | ||
311 | |||
312 | i2c_set_clientdata(client, info); | ||
313 | |||
314 | /* Reset the screen */ | ||
315 | gpio_set_value(par->reset, 0); | ||
316 | udelay(4); | ||
317 | gpio_set_value(par->reset, 1); | ||
318 | udelay(4); | ||
319 | |||
320 | /* Enable the PWM */ | ||
321 | pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period); | ||
322 | pwm_enable(par->pwm); | ||
323 | |||
324 | /* Map column 127 of the OLED to segment 0 */ | ||
325 | ret = ssd1307fb_write_cmd(client, SSD1307FB_SEG_REMAP_ON); | ||
326 | if (ret < 0) { | ||
327 | dev_err(&client->dev, "Couldn't remap the screen.\n"); | ||
328 | goto remap_error; | ||
329 | } | ||
330 | |||
331 | /* Turn on the display */ | ||
332 | ret = ssd1307fb_write_cmd(client, SSD1307FB_DISPLAY_ON); | ||
333 | if (ret < 0) { | ||
334 | dev_err(&client->dev, "Couldn't turn the display on.\n"); | ||
335 | goto remap_error; | ||
336 | } | ||
337 | |||
338 | dev_info(&client->dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size); | ||
339 | |||
340 | return 0; | ||
341 | |||
342 | remap_error: | ||
343 | unregister_framebuffer(info); | ||
344 | pwm_disable(par->pwm); | ||
345 | fbreg_error: | ||
346 | pwm_put(par->pwm); | ||
347 | pwm_error: | ||
348 | reset_oled_error: | ||
349 | fb_deferred_io_cleanup(info); | ||
350 | fb_alloc_error: | ||
351 | framebuffer_release(info); | ||
352 | return ret; | ||
353 | } | ||
354 | |||
355 | static int __devexit ssd1307fb_remove(struct i2c_client *client) | ||
356 | { | ||
357 | struct fb_info *info = i2c_get_clientdata(client); | ||
358 | struct ssd1307fb_par *par = info->par; | ||
359 | |||
360 | unregister_framebuffer(info); | ||
361 | pwm_disable(par->pwm); | ||
362 | pwm_put(par->pwm); | ||
363 | fb_deferred_io_cleanup(info); | ||
364 | framebuffer_release(info); | ||
365 | |||
366 | return 0; | ||
367 | } | ||
368 | |||
369 | static const struct i2c_device_id ssd1307fb_i2c_id[] = { | ||
370 | { "ssd1307fb", 0 }, | ||
371 | { } | ||
372 | }; | ||
373 | MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); | ||
374 | |||
375 | static const struct of_device_id ssd1307fb_of_match[] = { | ||
376 | { .compatible = "solomon,ssd1307fb-i2c" }, | ||
377 | {}, | ||
378 | }; | ||
379 | MODULE_DEVICE_TABLE(of, ssd1307fb_of_match); | ||
380 | |||
381 | static struct i2c_driver ssd1307fb_driver = { | ||
382 | .probe = ssd1307fb_probe, | ||
383 | .remove = __devexit_p(ssd1307fb_remove), | ||
384 | .id_table = ssd1307fb_i2c_id, | ||
385 | .driver = { | ||
386 | .name = "ssd1307fb", | ||
387 | .of_match_table = of_match_ptr(ssd1307fb_of_match), | ||
388 | .owner = THIS_MODULE, | ||
389 | }, | ||
390 | }; | ||
391 | |||
392 | module_i2c_driver(ssd1307fb_driver); | ||
393 | |||
394 | MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controler"); | ||
395 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | ||
396 | MODULE_LICENSE("GPL"); | ||