Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drivers/video: add support for the Solomon SSD1307 OLED Controller

Add support for the Solomon SSD1307 OLED controller found on the
Crystalfontz CFA10036 board.

This controller can drive a display with a resolution up to 128x39 and can
operate over I2C or SPI.

The current driver has only been tested on the CFA-10036, that is using
this controller over I2C to driver a 96x16 OLED screen.

[akpm@linux-foundation.org: checkpatch fixes]
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Cc: Brian Lilly <brian@crystalfontz.com>
Cc: Greg KH <gregkh@linux-foundation.org>
Cc: Florian Tobias Schandinat <FlorianSchandinat@gmx.de>
Cc: Thomas Petazzoni <thomas@free-electrons.com>
Cc: Tomi Valkeinen <tomi.valkeinen@ti.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

authored by

Maxime Ripard and committed by
Tomi Valkeinen
a2ed00da 8f22e8ea

+436
+24
Documentation/devicetree/bindings/video/ssd1307fb.txt
··· 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 + };
+15
drivers/video/Kconfig
··· 2442 2442 Up to 4 memory channels can be configured, allowing 4 RGB or 2443 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 2460 endmenu
+1
drivers/video/Makefile
··· 161 161 obj-$(CONFIG_FB_MX3) += mx3fb.o 162 162 obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o 163 163 obj-$(CONFIG_FB_MXS) += mxsfb.o 164 + obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o 164 165 165 166 # the test framebuffer is last 166 167 obj-$(CONFIG_FB_VIRTUAL) += vfb.o
+396
drivers/video/ssd1307fb.c
··· 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");