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