xref: /openbmc/linux/drivers/video/fbdev/ssd1307fb.c (revision d7a3d85e)
1 /*
2  * Driver for the Solomon SSD1307 OLED controller
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_DATA			0x40
20 #define SSD1307FB_COMMAND		0x80
21 
22 #define SSD1307FB_SET_ADDRESS_MODE	0x20
23 #define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL	(0x00)
24 #define SSD1307FB_SET_ADDRESS_MODE_VERTICAL	(0x01)
25 #define SSD1307FB_SET_ADDRESS_MODE_PAGE		(0x02)
26 #define SSD1307FB_SET_COL_RANGE		0x21
27 #define SSD1307FB_SET_PAGE_RANGE	0x22
28 #define SSD1307FB_CONTRAST		0x81
29 #define	SSD1307FB_CHARGE_PUMP		0x8d
30 #define SSD1307FB_SEG_REMAP_ON		0xa1
31 #define SSD1307FB_DISPLAY_OFF		0xae
32 #define SSD1307FB_SET_MULTIPLEX_RATIO	0xa8
33 #define SSD1307FB_DISPLAY_ON		0xaf
34 #define SSD1307FB_START_PAGE_ADDRESS	0xb0
35 #define SSD1307FB_SET_DISPLAY_OFFSET	0xd3
36 #define	SSD1307FB_SET_CLOCK_FREQ	0xd5
37 #define	SSD1307FB_SET_PRECHARGE_PERIOD	0xd9
38 #define	SSD1307FB_SET_COM_PINS_CONFIG	0xda
39 #define	SSD1307FB_SET_VCOMH		0xdb
40 
41 struct ssd1307fb_par;
42 
43 struct ssd1307fb_ops {
44 	int (*init)(struct ssd1307fb_par *);
45 	int (*remove)(struct ssd1307fb_par *);
46 };
47 
48 struct ssd1307fb_par {
49 	struct i2c_client *client;
50 	u32 height;
51 	struct fb_info *info;
52 	struct ssd1307fb_ops *ops;
53 	u32 page_offset;
54 	struct pwm_device *pwm;
55 	u32 pwm_period;
56 	int reset;
57 	u32 width;
58 };
59 
60 struct ssd1307fb_array {
61 	u8	type;
62 	u8	data[0];
63 };
64 
65 static struct fb_fix_screeninfo ssd1307fb_fix = {
66 	.id		= "Solomon SSD1307",
67 	.type		= FB_TYPE_PACKED_PIXELS,
68 	.visual		= FB_VISUAL_MONO10,
69 	.xpanstep	= 0,
70 	.ypanstep	= 0,
71 	.ywrapstep	= 0,
72 	.accel		= FB_ACCEL_NONE,
73 };
74 
75 static struct fb_var_screeninfo ssd1307fb_var = {
76 	.bits_per_pixel	= 1,
77 };
78 
79 static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type)
80 {
81 	struct ssd1307fb_array *array;
82 
83 	array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL);
84 	if (!array)
85 		return NULL;
86 
87 	array->type = type;
88 
89 	return array;
90 }
91 
92 static int ssd1307fb_write_array(struct i2c_client *client,
93 				 struct ssd1307fb_array *array, u32 len)
94 {
95 	int ret;
96 
97 	len += sizeof(struct ssd1307fb_array);
98 
99 	ret = i2c_master_send(client, (u8 *)array, len);
100 	if (ret != len) {
101 		dev_err(&client->dev, "Couldn't send I2C command.\n");
102 		return ret;
103 	}
104 
105 	return 0;
106 }
107 
108 static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd)
109 {
110 	struct ssd1307fb_array *array;
111 	int ret;
112 
113 	array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND);
114 	if (!array)
115 		return -ENOMEM;
116 
117 	array->data[0] = cmd;
118 
119 	ret = ssd1307fb_write_array(client, array, 1);
120 	kfree(array);
121 
122 	return ret;
123 }
124 
125 static void ssd1307fb_update_display(struct ssd1307fb_par *par)
126 {
127 	struct ssd1307fb_array *array;
128 	u8 *vmem = par->info->screen_base;
129 	int i, j, k;
130 
131 	array = ssd1307fb_alloc_array(par->width * par->height / 8,
132 				      SSD1307FB_DATA);
133 	if (!array)
134 		return;
135 
136 	/*
137 	 * The screen is divided in pages, each having a height of 8
138 	 * pixels, and the width of the screen. When sending a byte of
139 	 * data to the controller, it gives the 8 bits for the current
140 	 * column. I.e, the first byte are the 8 bits of the first
141 	 * column, then the 8 bits for the second column, etc.
142 	 *
143 	 *
144 	 * Representation of the screen, assuming it is 5 bits
145 	 * wide. Each letter-number combination is a bit that controls
146 	 * one pixel.
147 	 *
148 	 * A0 A1 A2 A3 A4
149 	 * B0 B1 B2 B3 B4
150 	 * C0 C1 C2 C3 C4
151 	 * D0 D1 D2 D3 D4
152 	 * E0 E1 E2 E3 E4
153 	 * F0 F1 F2 F3 F4
154 	 * G0 G1 G2 G3 G4
155 	 * H0 H1 H2 H3 H4
156 	 *
157 	 * If you want to update this screen, you need to send 5 bytes:
158 	 *  (1) A0 B0 C0 D0 E0 F0 G0 H0
159 	 *  (2) A1 B1 C1 D1 E1 F1 G1 H1
160 	 *  (3) A2 B2 C2 D2 E2 F2 G2 H2
161 	 *  (4) A3 B3 C3 D3 E3 F3 G3 H3
162 	 *  (5) A4 B4 C4 D4 E4 F4 G4 H4
163 	 */
164 
165 	for (i = 0; i < (par->height / 8); i++) {
166 		for (j = 0; j < par->width; j++) {
167 			u32 array_idx = i * par->width + j;
168 			array->data[array_idx] = 0;
169 			for (k = 0; k < 8; k++) {
170 				u32 page_length = par->width * i;
171 				u32 index = page_length + (par->width * k + j) / 8;
172 				u8 byte = *(vmem + index);
173 				u8 bit = byte & (1 << (j % 8));
174 				bit = bit >> (j % 8);
175 				array->data[array_idx] |= bit << k;
176 			}
177 		}
178 	}
179 
180 	ssd1307fb_write_array(par->client, array, par->width * par->height / 8);
181 	kfree(array);
182 }
183 
184 
185 static ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf,
186 		size_t count, loff_t *ppos)
187 {
188 	struct ssd1307fb_par *par = info->par;
189 	unsigned long total_size;
190 	unsigned long p = *ppos;
191 	u8 __iomem *dst;
192 
193 	total_size = info->fix.smem_len;
194 
195 	if (p > total_size)
196 		return -EINVAL;
197 
198 	if (count + p > total_size)
199 		count = total_size - p;
200 
201 	if (!count)
202 		return -EINVAL;
203 
204 	dst = (void __force *) (info->screen_base + p);
205 
206 	if (copy_from_user(dst, buf, count))
207 		return -EFAULT;
208 
209 	ssd1307fb_update_display(par);
210 
211 	*ppos += count;
212 
213 	return count;
214 }
215 
216 static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
217 {
218 	struct ssd1307fb_par *par = info->par;
219 	sys_fillrect(info, rect);
220 	ssd1307fb_update_display(par);
221 }
222 
223 static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
224 {
225 	struct ssd1307fb_par *par = info->par;
226 	sys_copyarea(info, area);
227 	ssd1307fb_update_display(par);
228 }
229 
230 static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image)
231 {
232 	struct ssd1307fb_par *par = info->par;
233 	sys_imageblit(info, image);
234 	ssd1307fb_update_display(par);
235 }
236 
237 static struct fb_ops ssd1307fb_ops = {
238 	.owner		= THIS_MODULE,
239 	.fb_read	= fb_sys_read,
240 	.fb_write	= ssd1307fb_write,
241 	.fb_fillrect	= ssd1307fb_fillrect,
242 	.fb_copyarea	= ssd1307fb_copyarea,
243 	.fb_imageblit	= ssd1307fb_imageblit,
244 };
245 
246 static void ssd1307fb_deferred_io(struct fb_info *info,
247 				struct list_head *pagelist)
248 {
249 	ssd1307fb_update_display(info->par);
250 }
251 
252 static struct fb_deferred_io ssd1307fb_defio = {
253 	.delay		= HZ,
254 	.deferred_io	= ssd1307fb_deferred_io,
255 };
256 
257 static int ssd1307fb_ssd1307_init(struct ssd1307fb_par *par)
258 {
259 	int ret;
260 
261 	par->pwm = pwm_get(&par->client->dev, NULL);
262 	if (IS_ERR(par->pwm)) {
263 		dev_err(&par->client->dev, "Could not get PWM from device tree!\n");
264 		return PTR_ERR(par->pwm);
265 	}
266 
267 	par->pwm_period = pwm_get_period(par->pwm);
268 	/* Enable the PWM */
269 	pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period);
270 	pwm_enable(par->pwm);
271 
272 	dev_dbg(&par->client->dev, "Using PWM%d with a %dns period.\n",
273 		par->pwm->pwm, par->pwm_period);
274 
275 	/* Map column 127 of the OLED to segment 0 */
276 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON);
277 	if (ret < 0)
278 		return ret;
279 
280 	/* Turn on the display */
281 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON);
282 	if (ret < 0)
283 		return ret;
284 
285 	return 0;
286 }
287 
288 static int ssd1307fb_ssd1307_remove(struct ssd1307fb_par *par)
289 {
290 	pwm_disable(par->pwm);
291 	pwm_put(par->pwm);
292 	return 0;
293 }
294 
295 static struct ssd1307fb_ops ssd1307fb_ssd1307_ops = {
296 	.init	= ssd1307fb_ssd1307_init,
297 	.remove	= ssd1307fb_ssd1307_remove,
298 };
299 
300 static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par)
301 {
302 	int ret;
303 
304 	/* Set initial contrast */
305 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST);
306 	if (ret < 0)
307 		return ret;
308 
309 	ret = ssd1307fb_write_cmd(par->client, 0x7f);
310 	if (ret < 0)
311 		return ret;
312 
313 	/* Set COM direction */
314 	ret = ssd1307fb_write_cmd(par->client, 0xc8);
315 	if (ret < 0)
316 		return ret;
317 
318 	/* Set segment re-map */
319 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON);
320 	if (ret < 0)
321 		return ret;
322 
323 	/* Set multiplex ratio value */
324 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO);
325 	if (ret < 0)
326 		return ret;
327 
328 	ret = ssd1307fb_write_cmd(par->client, par->height - 1);
329 	if (ret < 0)
330 		return ret;
331 
332 	/* set display offset value */
333 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET);
334 	if (ret < 0)
335 		return ret;
336 
337 	ret = ssd1307fb_write_cmd(par->client, 0x20);
338 	if (ret < 0)
339 		return ret;
340 
341 	/* Set clock frequency */
342 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ);
343 	if (ret < 0)
344 		return ret;
345 
346 	ret = ssd1307fb_write_cmd(par->client, 0xf0);
347 	if (ret < 0)
348 		return ret;
349 
350 	/* Set precharge period in number of ticks from the internal clock */
351 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD);
352 	if (ret < 0)
353 		return ret;
354 
355 	ret = ssd1307fb_write_cmd(par->client, 0x22);
356 	if (ret < 0)
357 		return ret;
358 
359 	/* Set COM pins configuration */
360 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG);
361 	if (ret < 0)
362 		return ret;
363 
364 	ret = ssd1307fb_write_cmd(par->client, 0x22);
365 	if (ret < 0)
366 		return ret;
367 
368 	/* Set VCOMH */
369 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH);
370 	if (ret < 0)
371 		return ret;
372 
373 	ret = ssd1307fb_write_cmd(par->client, 0x49);
374 	if (ret < 0)
375 		return ret;
376 
377 	/* Turn on the DC-DC Charge Pump */
378 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP);
379 	if (ret < 0)
380 		return ret;
381 
382 	ret = ssd1307fb_write_cmd(par->client, 0x14);
383 	if (ret < 0)
384 		return ret;
385 
386 	/* Switch to horizontal addressing mode */
387 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE);
388 	if (ret < 0)
389 		return ret;
390 
391 	ret = ssd1307fb_write_cmd(par->client,
392 				  SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL);
393 	if (ret < 0)
394 		return ret;
395 
396 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE);
397 	if (ret < 0)
398 		return ret;
399 
400 	ret = ssd1307fb_write_cmd(par->client, 0x0);
401 	if (ret < 0)
402 		return ret;
403 
404 	ret = ssd1307fb_write_cmd(par->client, par->width - 1);
405 	if (ret < 0)
406 		return ret;
407 
408 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE);
409 	if (ret < 0)
410 		return ret;
411 
412 	ret = ssd1307fb_write_cmd(par->client, 0x0);
413 	if (ret < 0)
414 		return ret;
415 
416 	ret = ssd1307fb_write_cmd(par->client,
417 				  par->page_offset + (par->height / 8) - 1);
418 	if (ret < 0)
419 		return ret;
420 
421 	/* Turn on the display */
422 	ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON);
423 	if (ret < 0)
424 		return ret;
425 
426 	return 0;
427 }
428 
429 static struct ssd1307fb_ops ssd1307fb_ssd1306_ops = {
430 	.init	= ssd1307fb_ssd1306_init,
431 };
432 
433 static const struct of_device_id ssd1307fb_of_match[] = {
434 	{
435 		.compatible = "solomon,ssd1306fb-i2c",
436 		.data = (void *)&ssd1307fb_ssd1306_ops,
437 	},
438 	{
439 		.compatible = "solomon,ssd1307fb-i2c",
440 		.data = (void *)&ssd1307fb_ssd1307_ops,
441 	},
442 	{},
443 };
444 MODULE_DEVICE_TABLE(of, ssd1307fb_of_match);
445 
446 static int ssd1307fb_probe(struct i2c_client *client,
447 			   const struct i2c_device_id *id)
448 {
449 	struct fb_info *info;
450 	struct device_node *node = client->dev.of_node;
451 	u32 vmem_size;
452 	struct ssd1307fb_par *par;
453 	u8 *vmem;
454 	int ret;
455 
456 	if (!node) {
457 		dev_err(&client->dev, "No device tree data found!\n");
458 		return -EINVAL;
459 	}
460 
461 	info = framebuffer_alloc(sizeof(struct ssd1307fb_par), &client->dev);
462 	if (!info) {
463 		dev_err(&client->dev, "Couldn't allocate framebuffer.\n");
464 		return -ENOMEM;
465 	}
466 
467 	par = info->par;
468 	par->info = info;
469 	par->client = client;
470 
471 	par->ops = (struct ssd1307fb_ops *)of_match_device(ssd1307fb_of_match,
472 							   &client->dev)->data;
473 
474 	par->reset = of_get_named_gpio(client->dev.of_node,
475 					 "reset-gpios", 0);
476 	if (!gpio_is_valid(par->reset)) {
477 		ret = -EINVAL;
478 		goto fb_alloc_error;
479 	}
480 
481 	if (of_property_read_u32(node, "solomon,width", &par->width))
482 		par->width = 96;
483 
484 	if (of_property_read_u32(node, "solomon,height", &par->height))
485 		par->height = 16;
486 
487 	if (of_property_read_u32(node, "solomon,page-offset", &par->page_offset))
488 		par->page_offset = 1;
489 
490 	vmem_size = par->width * par->height / 8;
491 
492 	vmem = devm_kzalloc(&client->dev, vmem_size, GFP_KERNEL);
493 	if (!vmem) {
494 		dev_err(&client->dev, "Couldn't allocate graphical memory.\n");
495 		ret = -ENOMEM;
496 		goto fb_alloc_error;
497 	}
498 
499 	info->fbops = &ssd1307fb_ops;
500 	info->fix = ssd1307fb_fix;
501 	info->fix.line_length = par->width / 8;
502 	info->fbdefio = &ssd1307fb_defio;
503 
504 	info->var = ssd1307fb_var;
505 	info->var.xres = par->width;
506 	info->var.xres_virtual = par->width;
507 	info->var.yres = par->height;
508 	info->var.yres_virtual = par->height;
509 
510 	info->var.red.length = 1;
511 	info->var.red.offset = 0;
512 	info->var.green.length = 1;
513 	info->var.green.offset = 0;
514 	info->var.blue.length = 1;
515 	info->var.blue.offset = 0;
516 
517 	info->screen_base = (u8 __force __iomem *)vmem;
518 	info->fix.smem_start = (unsigned long)vmem;
519 	info->fix.smem_len = vmem_size;
520 
521 	fb_deferred_io_init(info);
522 
523 	ret = devm_gpio_request_one(&client->dev, par->reset,
524 				    GPIOF_OUT_INIT_HIGH,
525 				    "oled-reset");
526 	if (ret) {
527 		dev_err(&client->dev,
528 			"failed to request gpio %d: %d\n",
529 			par->reset, ret);
530 		goto reset_oled_error;
531 	}
532 
533 	i2c_set_clientdata(client, info);
534 
535 	/* Reset the screen */
536 	gpio_set_value(par->reset, 0);
537 	udelay(4);
538 	gpio_set_value(par->reset, 1);
539 	udelay(4);
540 
541 	if (par->ops->init) {
542 		ret = par->ops->init(par);
543 		if (ret)
544 			goto reset_oled_error;
545 	}
546 
547 	ret = register_framebuffer(info);
548 	if (ret) {
549 		dev_err(&client->dev, "Couldn't register the framebuffer\n");
550 		goto panel_init_error;
551 	}
552 
553 	dev_info(&client->dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size);
554 
555 	return 0;
556 
557 panel_init_error:
558 	if (par->ops->remove)
559 		par->ops->remove(par);
560 reset_oled_error:
561 	fb_deferred_io_cleanup(info);
562 fb_alloc_error:
563 	framebuffer_release(info);
564 	return ret;
565 }
566 
567 static int ssd1307fb_remove(struct i2c_client *client)
568 {
569 	struct fb_info *info = i2c_get_clientdata(client);
570 	struct ssd1307fb_par *par = info->par;
571 
572 	unregister_framebuffer(info);
573 	if (par->ops->remove)
574 		par->ops->remove(par);
575 	fb_deferred_io_cleanup(info);
576 	framebuffer_release(info);
577 
578 	return 0;
579 }
580 
581 static const struct i2c_device_id ssd1307fb_i2c_id[] = {
582 	{ "ssd1306fb", 0 },
583 	{ "ssd1307fb", 0 },
584 	{ }
585 };
586 MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id);
587 
588 static struct i2c_driver ssd1307fb_driver = {
589 	.probe = ssd1307fb_probe,
590 	.remove = ssd1307fb_remove,
591 	.id_table = ssd1307fb_i2c_id,
592 	.driver = {
593 		.name = "ssd1307fb",
594 		.of_match_table = ssd1307fb_of_match,
595 		.owner = THIS_MODULE,
596 	},
597 };
598 
599 module_i2c_driver(ssd1307fb_driver);
600 
601 MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller");
602 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
603 MODULE_LICENSE("GPL");
604