linux/drivers/video/ssd1307fb.c
<<
>>
Prefs
   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
  31struct 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
  39static struct fb_fix_screeninfo ssd1307fb_fix = {
  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
  50static struct fb_var_screeninfo ssd1307fb_var = {
  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
  58static 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
  78error:
  79        kfree(buf);
  80        return ret;
  81}
  82
  83static 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
  88static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd)
  89{
  90        return ssd1307fb_write_cmd_array(client, &cmd, 1);
  91}
  92
  93static 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
  98static inline int ssd1307fb_write_data(struct i2c_client *client, u8 data)
  99{
 100        return ssd1307fb_write_data_array(client, &data, 1);
 101}
 102
 103static 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 << (j % 8));
 149                                bit = bit >> (j % 8);
 150                                buf |= bit << k;
 151                        }
 152                        ssd1307fb_write_data(par->client, buf);
 153                }
 154        }
 155}
 156
 157
 158static 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
 189static 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
 196static 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
 203static 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
 210static 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
 219static void ssd1307fb_deferred_io(struct fb_info *info,
 220                                struct list_head *pagelist)
 221{
 222        ssd1307fb_update_display(info->par);
 223}
 224
 225static struct fb_deferred_io ssd1307fb_defio = {
 226        .delay          = HZ,
 227        .deferred_io    = ssd1307fb_deferred_io,
 228};
 229
 230static int ssd1307fb_probe(struct i2c_client *client,
 231                           const struct i2c_device_id *id)
 232{
 233        struct fb_info *info;
 234        u32 vmem_size = SSD1307FB_WIDTH * SSD1307FB_HEIGHT / 8;
 235        struct ssd1307fb_par *par;
 236        u8 *vmem;
 237        int ret;
 238
 239        if (!client->dev.of_node) {
 240                dev_err(&client->dev, "No device tree data found!\n");
 241                return -EINVAL;
 242        }
 243
 244        info = framebuffer_alloc(sizeof(struct ssd1307fb_par), &client->dev);
 245        if (!info) {
 246                dev_err(&client->dev, "Couldn't allocate framebuffer.\n");
 247                return -ENOMEM;
 248        }
 249
 250        vmem = devm_kzalloc(&client->dev, vmem_size, GFP_KERNEL);
 251        if (!vmem) {
 252                dev_err(&client->dev, "Couldn't allocate graphical memory.\n");
 253                ret = -ENOMEM;
 254                goto fb_alloc_error;
 255        }
 256
 257        info->fbops = &ssd1307fb_ops;
 258        info->fix = ssd1307fb_fix;
 259        info->fbdefio = &ssd1307fb_defio;
 260
 261        info->var = ssd1307fb_var;
 262        info->var.red.length = 1;
 263        info->var.red.offset = 0;
 264        info->var.green.length = 1;
 265        info->var.green.offset = 0;
 266        info->var.blue.length = 1;
 267        info->var.blue.offset = 0;
 268
 269        info->screen_base = (u8 __force __iomem *)vmem;
 270        info->fix.smem_start = (unsigned long)vmem;
 271        info->fix.smem_len = vmem_size;
 272
 273        fb_deferred_io_init(info);
 274
 275        par = info->par;
 276        par->info = info;
 277        par->client = client;
 278
 279        par->reset = of_get_named_gpio(client->dev.of_node,
 280                                         "reset-gpios", 0);
 281        if (!gpio_is_valid(par->reset)) {
 282                ret = -EINVAL;
 283                goto reset_oled_error;
 284        }
 285
 286        ret = devm_gpio_request_one(&client->dev, par->reset,
 287                                    GPIOF_OUT_INIT_HIGH,
 288                                    "oled-reset");
 289        if (ret) {
 290                dev_err(&client->dev,
 291                        "failed to request gpio %d: %d\n",
 292                        par->reset, ret);
 293                goto reset_oled_error;
 294        }
 295
 296        par->pwm = pwm_get(&client->dev, NULL);
 297        if (IS_ERR(par->pwm)) {
 298                dev_err(&client->dev, "Could not get PWM from device tree!\n");
 299                ret = PTR_ERR(par->pwm);
 300                goto pwm_error;
 301        }
 302
 303        par->pwm_period = pwm_get_period(par->pwm);
 304
 305        dev_dbg(&client->dev, "Using PWM%d with a %dns period.\n", par->pwm->pwm, par->pwm_period);
 306
 307        ret = register_framebuffer(info);
 308        if (ret) {
 309                dev_err(&client->dev, "Couldn't register the framebuffer\n");
 310                goto fbreg_error;
 311        }
 312
 313        i2c_set_clientdata(client, info);
 314
 315        /* Reset the screen */
 316        gpio_set_value(par->reset, 0);
 317        udelay(4);
 318        gpio_set_value(par->reset, 1);
 319        udelay(4);
 320
 321        /* Enable the PWM */
 322        pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period);
 323        pwm_enable(par->pwm);
 324
 325        /* Map column 127 of the OLED to segment 0 */
 326        ret = ssd1307fb_write_cmd(client, SSD1307FB_SEG_REMAP_ON);
 327        if (ret < 0) {
 328                dev_err(&client->dev, "Couldn't remap the screen.\n");
 329                goto remap_error;
 330        }
 331
 332        /* Turn on the display */
 333        ret = ssd1307fb_write_cmd(client, SSD1307FB_DISPLAY_ON);
 334        if (ret < 0) {
 335                dev_err(&client->dev, "Couldn't turn the display on.\n");
 336                goto remap_error;
 337        }
 338
 339        dev_info(&client->dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size);
 340
 341        return 0;
 342
 343remap_error:
 344        unregister_framebuffer(info);
 345        pwm_disable(par->pwm);
 346fbreg_error:
 347        pwm_put(par->pwm);
 348pwm_error:
 349reset_oled_error:
 350        fb_deferred_io_cleanup(info);
 351fb_alloc_error:
 352        framebuffer_release(info);
 353        return ret;
 354}
 355
 356static int ssd1307fb_remove(struct i2c_client *client)
 357{
 358        struct fb_info *info = i2c_get_clientdata(client);
 359        struct ssd1307fb_par *par = info->par;
 360
 361        unregister_framebuffer(info);
 362        pwm_disable(par->pwm);
 363        pwm_put(par->pwm);
 364        fb_deferred_io_cleanup(info);
 365        framebuffer_release(info);
 366
 367        return 0;
 368}
 369
 370static const struct i2c_device_id ssd1307fb_i2c_id[] = {
 371        { "ssd1307fb", 0 },
 372        { }
 373};
 374MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id);
 375
 376static const struct of_device_id ssd1307fb_of_match[] = {
 377        { .compatible = "solomon,ssd1307fb-i2c" },
 378        {},
 379};
 380MODULE_DEVICE_TABLE(of, ssd1307fb_of_match);
 381
 382static struct i2c_driver ssd1307fb_driver = {
 383        .probe = ssd1307fb_probe,
 384        .remove = ssd1307fb_remove,
 385        .id_table = ssd1307fb_i2c_id,
 386        .driver = {
 387                .name = "ssd1307fb",
 388                .of_match_table = of_match_ptr(ssd1307fb_of_match),
 389                .owner = THIS_MODULE,
 390        },
 391};
 392
 393module_i2c_driver(ssd1307fb_driver);
 394
 395MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controler");
 396MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
 397MODULE_LICENSE("GPL");
 398
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.