| 1 | |
| 2 | |
| 3 | * Driver for the Solomon SSD1307 OLED controller
|
| 4 | *
|
| 5 | * Copyright 2012 Free Electrons
|
| 6 |
|
| 7 |
|
| 8 | #include <linux/backlight.h>
|
| 9 | #include <linux/delay.h>
|
| 10 | #include <linux/fb.h>
|
| 11 | #include <linux/gpio/consumer.h>
|
| 12 | #include <linux/i2c.h>
|
| 13 | #include <linux/kernel.h>
|
| 14 | #include <linux/module.h>
|
| 15 | #include <linux/property.h>
|
| 16 | #include <linux/pwm.h>
|
| 17 | #include <linux/spinlock.h>
|
| 18 | #include <linux/uaccess.h>
|
| 19 | #include <linux/regulator/consumer.h>
|
| 20 |
|
| 21 | #define SSD1307FB_DATA 0x40
|
| 22 | #define SSD1307FB_COMMAND 0x80
|
| 23 |
|
| 24 | #define SSD1307FB_SET_ADDRESS_MODE 0x20
|
| 25 | #define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00)
|
| 26 | #define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01)
|
| 27 | #define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02)
|
| 28 | #define SSD1307FB_SET_COL_RANGE 0x21
|
| 29 | #define SSD1307FB_SET_PAGE_RANGE 0x22
|
| 30 | #define SSD1307FB_CONTRAST 0x81
|
| 31 | #define SSD1307FB_SET_LOOKUP_TABLE 0x91
|
| 32 | #define SSD1307FB_CHARGE_PUMP 0x8d
|
| 33 | #define SSD1307FB_SEG_REMAP_ON 0xa1
|
| 34 | #define SSD1307FB_DISPLAY_OFF 0xae
|
| 35 | #define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8
|
| 36 | #define SSD1307FB_DISPLAY_ON 0xaf
|
| 37 | #define SSD1307FB_START_PAGE_ADDRESS 0xb0
|
| 38 | #define SSD1307FB_SET_DISPLAY_OFFSET 0xd3
|
| 39 | #define SSD1307FB_SET_CLOCK_FREQ 0xd5
|
| 40 | #define SSD1307FB_SET_AREA_COLOR_MODE 0xd8
|
| 41 | #define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9
|
| 42 | #define SSD1307FB_SET_COM_PINS_CONFIG 0xda
|
| 43 | #define SSD1307FB_SET_VCOMH 0xdb
|
| 44 |
|
| 45 | #define MAX_CONTRAST 255
|
| 46 |
|
| 47 | #define REFRESHRATE 1
|
| 48 |
|
| 49 | static u_int refreshrate = REFRESHRATE;
|
| 50 | module_param(refreshrate, uint, 0);
|
| 51 |
|
| 52 | struct ssd1307fb_deviceinfo {
|
| 53 | u32 default_vcomh;
|
| 54 | u32 default_dclk_div;
|
| 55 | u32 default_dclk_frq;
|
| 56 | bool need_pwm;
|
| 57 | bool need_chargepump;
|
| 58 | };
|
| 59 |
|
| 60 | struct ssd1307fb_par {
|
| 61 | unsigned area_color_enable : 1;
|
| 62 | unsigned com_invdir : 1;
|
| 63 | unsigned com_lrremap : 1;
|
| 64 | unsigned com_seq : 1;
|
| 65 | unsigned lookup_table_set : 1;
|
| 66 | unsigned low_power : 1;
|
| 67 | unsigned seg_remap : 1;
|
| 68 | u32 com_offset;
|
| 69 | u32 contrast;
|
| 70 | u32 dclk_div;
|
| 71 | u32 dclk_frq;
|
| 72 | const struct ssd1307fb_deviceinfo *device_info;
|
| 73 | struct i2c_client *client;
|
| 74 | u32 height;
|
| 75 | struct fb_info *info;
|
| 76 |
|
| 77 | spinlock_t damage_lock;
|
| 78 | bool damage_pending;
|
| 79 | u32 damage_x1;
|
| 80 | u32 damage_x2;
|
| 81 | u32 damage_y1;
|
| 82 | u32 damage_y2;
|
| 83 | u8 lookup_table[4];
|
| 84 | u32 page_offset;
|
| 85 | u32 col_offset;
|
| 86 | u32 prechargep1;
|
| 87 | u32 prechargep2;
|
| 88 | struct pwm_device *pwm;
|
| 89 | struct gpio_desc *reset;
|
| 90 | struct regulator *vbat_reg;
|
| 91 | u32 vcomh;
|
| 92 | u32 width;
|
| 93 |
|
| 94 | u8 col_start;
|
| 95 | u8 col_end;
|
| 96 | u8 page_start;
|
| 97 | u8 page_end;
|
| 98 | };
|
| 99 |
|
| 100 | struct ssd1307fb_array {
|
| 101 | u8 type;
|
| 102 | u8 data[];
|
| 103 | };
|
| 104 |
|
| 105 | static const struct fb_fix_screeninfo ssd1307fb_fix = {
|
| 106 | .id = "Solomon SSD1307",
|
| 107 | .type = FB_TYPE_PACKED_PIXELS,
|
| 108 | .visual = FB_VISUAL_MONO10,
|
| 109 | .xpanstep = 0,
|
| 110 | .ypanstep = 0,
|
| 111 | .ywrapstep = 0,
|
| 112 | .accel = FB_ACCEL_NONE,
|
| 113 | };
|
| 114 |
|
| 115 | static const struct fb_var_screeninfo ssd1307fb_var = {
|
| 116 | .bits_per_pixel = 1,
|
| 117 | .red = { .length = 1 },
|
| 118 | .green = { .length = 1 },
|
| 119 | .blue = { .length = 1 },
|
| 120 | };
|
| 121 |
|
| 122 | static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type)
|
| 123 | {
|
| 124 | struct ssd1307fb_array *array;
|
| 125 |
|
| 126 | array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL);
|
| 127 | if (!array)
|
| 128 | return NULL;
|
| 129 |
|
| 130 | array->type = type;
|
| 131 |
|
| 132 | return array;
|
| 133 | }
|
| 134 |
|
| 135 | static int ssd1307fb_write_array(struct i2c_client *client,
|
| 136 | struct ssd1307fb_array *array, u32 len)
|
| 137 | {
|
| 138 | int ret;
|
| 139 |
|
| 140 | len += sizeof(struct ssd1307fb_array);
|
| 141 |
|
| 142 | ret = i2c_master_send(client, (u8 *)array, len);
|
| 143 | if (ret != len) {
|
| 144 | dev_err(&client->dev, "Couldn't send I2C command.\n");
|
| 145 | return ret;
|
| 146 | }
|
| 147 |
|
| 148 | return 0;
|
| 149 | }
|
| 150 |
|
| 151 | static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd)
|
| 152 | {
|
| 153 | struct ssd1307fb_array *array;
|
| 154 | int ret;
|
| 155 |
|
| 156 | array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND);
|
| 157 | if (!array)
|
| 158 | return -ENOMEM;
|
| 159 |
|
| 160 | array->data[0] = cmd;
|
| 161 |
|
| 162 | ret = ssd1307fb_write_array(client, array, 1);
|
| 163 | kfree(array);
|
| 164 |
|
| 165 | return ret;
|
| 166 | }
|
| 167 |
|
| 168 | static int ssd1307fb_set_col_range(struct ssd1307fb_par *par, u8 col_start,
|
| 169 | u8 cols)
|
| 170 | {
|
| 171 | u8 col_end = col_start + cols - 1;
|
| 172 | int ret;
|
| 173 |
|
| 174 | if (col_start == par->col_start && col_end == par->col_end)
|
| 175 | return 0;
|
| 176 |
|
| 177 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE);
|
| 178 | if (ret < 0)
|
| 179 | return ret;
|
| 180 |
|
| 181 | ret = ssd1307fb_write_cmd(par->client, col_start);
|
| 182 | if (ret < 0)
|
| 183 | return ret;
|
| 184 |
|
| 185 | ret = ssd1307fb_write_cmd(par->client, col_end);
|
| 186 | if (ret < 0)
|
| 187 | return ret;
|
| 188 |
|
| 189 | par->col_start = col_start;
|
| 190 | par->col_end = col_end;
|
| 191 | return 0;
|
| 192 | }
|
| 193 |
|
| 194 | static int ssd1307fb_set_page_range(struct ssd1307fb_par *par, u8 page_start,
|
| 195 | u8 pages)
|
| 196 | {
|
| 197 | u8 page_end = page_start + pages - 1;
|
| 198 | int ret;
|
| 199 |
|
| 200 | if (page_start == par->page_start && page_end == par->page_end)
|
| 201 | return 0;
|
| 202 |
|
| 203 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE);
|
| 204 | if (ret < 0)
|
| 205 | return ret;
|
| 206 |
|
| 207 | ret = ssd1307fb_write_cmd(par->client, page_start);
|
| 208 | if (ret < 0)
|
| 209 | return ret;
|
| 210 |
|
| 211 | ret = ssd1307fb_write_cmd(par->client, page_end);
|
| 212 | if (ret < 0)
|
| 213 | return ret;
|
| 214 |
|
| 215 | par->page_start = page_start;
|
| 216 | par->page_end = page_end;
|
| 217 | return 0;
|
| 218 | }
|
| 219 |
|
| 220 | static int ssd1307fb_update_rect(struct ssd1307fb_par *par, unsigned int x,
|
| 221 | unsigned int y, unsigned int width,
|
| 222 | unsigned int height)
|
| 223 | {
|
| 224 | struct ssd1307fb_array *array;
|
| 225 | u8 *vmem = par->info->screen_buffer;
|
| 226 | unsigned int line_length = par->info->fix.line_length;
|
| 227 | unsigned int pages = DIV_ROUND_UP(y % 8 + height, 8);
|
| 228 | u32 array_idx = 0;
|
| 229 | int ret, i, j, k;
|
| 230 |
|
| 231 | array = ssd1307fb_alloc_array(width * pages, SSD1307FB_DATA);
|
| 232 | if (!array)
|
| 233 | return -ENOMEM;
|
| 234 |
|
| 235 | |
| 236 | * The screen is divided in pages, each having a height of 8
|
| 237 | * pixels, and the width of the screen. When sending a byte of
|
| 238 | * data to the controller, it gives the 8 bits for the current
|
| 239 | * column. I.e, the first byte are the 8 bits of the first
|
| 240 | * column, then the 8 bits for the second column, etc.
|
| 241 | *
|
| 242 | *
|
| 243 | * Representation of the screen, assuming it is 5 bits
|
| 244 | * wide. Each letter-number combination is a bit that controls
|
| 245 | * one pixel.
|
| 246 | *
|
| 247 | * A0 A1 A2 A3 A4
|
| 248 | * B0 B1 B2 B3 B4
|
| 249 | * C0 C1 C2 C3 C4
|
| 250 | * D0 D1 D2 D3 D4
|
| 251 | * E0 E1 E2 E3 E4
|
| 252 | * F0 F1 F2 F3 F4
|
| 253 | * G0 G1 G2 G3 G4
|
| 254 | * H0 H1 H2 H3 H4
|
| 255 | *
|
| 256 | * If you want to update this screen, you need to send 5 bytes:
|
| 257 | * (1) A0 B0 C0 D0 E0 F0 G0 H0
|
| 258 | * (2) A1 B1 C1 D1 E1 F1 G1 H1
|
| 259 | * (3) A2 B2 C2 D2 E2 F2 G2 H2
|
| 260 | * (4) A3 B3 C3 D3 E3 F3 G3 H3
|
| 261 | * (5) A4 B4 C4 D4 E4 F4 G4 H4
|
| 262 |
|
| 263 |
|
| 264 | ret = ssd1307fb_set_col_range(par, par->col_offset + x, width);
|
| 265 | if (ret < 0)
|
| 266 | goto out_free;
|
| 267 |
|
| 268 | ret = ssd1307fb_set_page_range(par, par->page_offset + y / 8, pages);
|
| 269 | if (ret < 0)
|
| 270 | goto out_free;
|
| 271 |
|
| 272 | for (i = y / 8; i < y / 8 + pages; i++) {
|
| 273 | int m = 8;
|
| 274 |
|
| 275 |
|
| 276 | if (8 * (i + 1) > par->height)
|
| 277 | m = par->height % 8;
|
| 278 | for (j = x; j < x + width; j++) {
|
| 279 | u8 data = 0;
|
| 280 |
|
| 281 | for (k = 0; k < m; k++) {
|
| 282 | u8 byte = vmem[(8 * i + k) * line_length +
|
| 283 | j / 8];
|
| 284 | u8 bit = (byte >> (j % 8)) & 1;
|
| 285 | data |= bit << k;
|
| 286 | }
|
| 287 | array->data[array_idx++] = data;
|
| 288 | }
|
| 289 | }
|
| 290 |
|
| 291 | ret = ssd1307fb_write_array(par->client, array, width * pages);
|
| 292 |
|
| 293 | out_free:
|
| 294 | kfree(array);
|
| 295 | return ret;
|
| 296 | }
|
| 297 |
|
| 298 | static int ssd1307fb_update_display(struct ssd1307fb_par *par)
|
| 299 | {
|
| 300 | return ssd1307fb_update_rect(par, 0, 0, par->width, par->height);
|
| 301 | }
|
| 302 |
|
| 303 | static int ssd1307fb_blank(int blank_mode, struct fb_info *info)
|
| 304 | {
|
| 305 | struct ssd1307fb_par *par = info->par;
|
| 306 |
|
| 307 | if (blank_mode != FB_BLANK_UNBLANK)
|
| 308 | return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF);
|
| 309 | else
|
| 310 | return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON);
|
| 311 | }
|
| 312 |
|
| 313 | static void ssd1307fb_schedule_damage(struct fb_info *info, u32 x, u32 y,
|
| 314 | u32 width, u32 height)
|
| 315 | {
|
| 316 | struct ssd1307fb_par *par = info->par;
|
| 317 | unsigned long flags;
|
| 318 | u32 x2, y2;
|
| 319 |
|
| 320 | if (!width || !height || x >= par->width || y >= par->height)
|
| 321 | return;
|
| 322 |
|
| 323 | x2 = x + min(width, par->width - x);
|
| 324 | y2 = y + min(height, par->height - y);
|
| 325 |
|
| 326 | spin_lock_irqsave(&par->damage_lock, flags);
|
| 327 | if (par->damage_pending) {
|
| 328 | par->damage_x1 = min(par->damage_x1, x);
|
| 329 | par->damage_y1 = min(par->damage_y1, y);
|
| 330 | par->damage_x2 = max(par->damage_x2, x2);
|
| 331 | par->damage_y2 = max(par->damage_y2, y2);
|
| 332 | } else {
|
| 333 | par->damage_x1 = x;
|
| 334 | par->damage_y1 = y;
|
| 335 | par->damage_x2 = x2;
|
| 336 | par->damage_y2 = y2;
|
| 337 | par->damage_pending = true;
|
| 338 | }
|
| 339 | spin_unlock_irqrestore(&par->damage_lock, flags);
|
| 340 |
|
| 341 |
|
| 342 | mod_delayed_work(system_wq, &info->deferred_work, 0);
|
| 343 | }
|
| 344 |
|
| 345 | static void ssd1307fb_defio_damage_range(struct fb_info *info, off_t off, size_t len)
|
| 346 | {
|
| 347 | struct ssd1307fb_par *par = info->par;
|
| 348 |
|
| 349 | ssd1307fb_schedule_damage(info, 0, 0, par->width, par->height);
|
| 350 | }
|
| 351 |
|
| 352 | static void ssd1307fb_defio_damage_area(struct fb_info *info, u32 x, u32 y,
|
| 353 | u32 width, u32 height)
|
| 354 | {
|
| 355 | ssd1307fb_schedule_damage(info, x, y, width, height);
|
| 356 | }
|
| 357 |
|
| 358 | FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(ssd1307fb,
|
| 359 | ssd1307fb_defio_damage_range,
|
| 360 | ssd1307fb_defio_damage_area)
|
| 361 |
|
| 362 | static const struct fb_ops ssd1307fb_ops = {
|
| 363 | .owner = THIS_MODULE,
|
| 364 | FB_DEFAULT_DEFERRED_OPS(ssd1307fb),
|
| 365 | .fb_blank = ssd1307fb_blank,
|
| 366 | };
|
| 367 |
|
| 368 | static void ssd1307fb_deferred_io(struct fb_info *info, struct list_head *pagereflist)
|
| 369 | {
|
| 370 | struct ssd1307fb_par *par = info->par;
|
| 371 | unsigned long flags;
|
| 372 | u32 x, y, width, height;
|
| 373 |
|
| 374 | spin_lock_irqsave(&par->damage_lock, flags);
|
| 375 | if (!list_empty(pagereflist)) {
|
| 376 | x = 0;
|
| 377 | y = 0;
|
| 378 | width = par->width;
|
| 379 | height = par->height;
|
| 380 | par->damage_pending = false;
|
| 381 | } else if (par->damage_pending) {
|
| 382 | x = par->damage_x1;
|
| 383 | y = par->damage_y1;
|
| 384 | width = par->damage_x2 - par->damage_x1;
|
| 385 | height = par->damage_y2 - par->damage_y1;
|
| 386 | par->damage_pending = false;
|
| 387 | } else {
|
| 388 | spin_unlock_irqrestore(&par->damage_lock, flags);
|
| 389 | return;
|
| 390 | }
|
| 391 | spin_unlock_irqrestore(&par->damage_lock, flags);
|
| 392 |
|
| 393 | ssd1307fb_update_rect(par, x, y, width, height);
|
| 394 | }
|
| 395 |
|
| 396 | static int ssd1307fb_init(struct ssd1307fb_par *par)
|
| 397 | {
|
| 398 | struct pwm_state pwmstate;
|
| 399 | int ret;
|
| 400 | u32 precharge, dclk, com_invdir, compins;
|
| 401 |
|
| 402 | if (par->device_info->need_pwm) {
|
| 403 | par->pwm = pwm_get(&par->client->dev, NULL);
|
| 404 | if (IS_ERR(par->pwm)) {
|
| 405 | dev_err(&par->client->dev, "Could not get PWM from device tree!\n");
|
| 406 | return PTR_ERR(par->pwm);
|
| 407 | }
|
| 408 |
|
| 409 | pwm_init_state(par->pwm, &pwmstate);
|
| 410 | pwm_set_relative_duty_cycle(&pwmstate, 50, 100);
|
| 411 | pwm_apply_might_sleep(par->pwm, &pwmstate);
|
| 412 |
|
| 413 |
|
| 414 | pwm_enable(par->pwm);
|
| 415 |
|
| 416 | dev_dbg(&par->client->dev, "Using PWM %s with a %lluns period.\n",
|
| 417 | par->pwm->label, pwm_get_period(par->pwm));
|
| 418 | }
|
| 419 |
|
| 420 |
|
| 421 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST);
|
| 422 | if (ret < 0)
|
| 423 | return ret;
|
| 424 |
|
| 425 | ret = ssd1307fb_write_cmd(par->client, par->contrast);
|
| 426 | if (ret < 0)
|
| 427 | return ret;
|
| 428 |
|
| 429 |
|
| 430 | if (par->seg_remap) {
|
| 431 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON);
|
| 432 | if (ret < 0)
|
| 433 | return ret;
|
| 434 | }
|
| 435 |
|
| 436 |
|
| 437 | com_invdir = 0xc0 | par->com_invdir << 3;
|
| 438 | ret = ssd1307fb_write_cmd(par->client, com_invdir);
|
| 439 | if (ret < 0)
|
| 440 | return ret;
|
| 441 |
|
| 442 |
|
| 443 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO);
|
| 444 | if (ret < 0)
|
| 445 | return ret;
|
| 446 |
|
| 447 | ret = ssd1307fb_write_cmd(par->client, par->height - 1);
|
| 448 | if (ret < 0)
|
| 449 | return ret;
|
| 450 |
|
| 451 |
|
| 452 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET);
|
| 453 | if (ret < 0)
|
| 454 | return ret;
|
| 455 |
|
| 456 | ret = ssd1307fb_write_cmd(par->client, par->com_offset);
|
| 457 | if (ret < 0)
|
| 458 | return ret;
|
| 459 |
|
| 460 |
|
| 461 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ);
|
| 462 | if (ret < 0)
|
| 463 | return ret;
|
| 464 |
|
| 465 | dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4;
|
| 466 | ret = ssd1307fb_write_cmd(par->client, dclk);
|
| 467 | if (ret < 0)
|
| 468 | return ret;
|
| 469 |
|
| 470 |
|
| 471 | if (par->area_color_enable || par->low_power) {
|
| 472 | u32 mode;
|
| 473 |
|
| 474 | ret = ssd1307fb_write_cmd(par->client,
|
| 475 | SSD1307FB_SET_AREA_COLOR_MODE);
|
| 476 | if (ret < 0)
|
| 477 | return ret;
|
| 478 |
|
| 479 | mode = (par->area_color_enable ? 0x30 : 0) |
|
| 480 | (par->low_power ? 5 : 0);
|
| 481 | ret = ssd1307fb_write_cmd(par->client, mode);
|
| 482 | if (ret < 0)
|
| 483 | return ret;
|
| 484 | }
|
| 485 |
|
| 486 |
|
| 487 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD);
|
| 488 | if (ret < 0)
|
| 489 | return ret;
|
| 490 |
|
| 491 | precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4;
|
| 492 | ret = ssd1307fb_write_cmd(par->client, precharge);
|
| 493 | if (ret < 0)
|
| 494 | return ret;
|
| 495 |
|
| 496 |
|
| 497 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG);
|
| 498 | if (ret < 0)
|
| 499 | return ret;
|
| 500 |
|
| 501 | compins = 0x02 | !par->com_seq << 4 | par->com_lrremap << 5;
|
| 502 | ret = ssd1307fb_write_cmd(par->client, compins);
|
| 503 | if (ret < 0)
|
| 504 | return ret;
|
| 505 |
|
| 506 |
|
| 507 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH);
|
| 508 | if (ret < 0)
|
| 509 | return ret;
|
| 510 |
|
| 511 | ret = ssd1307fb_write_cmd(par->client, par->vcomh);
|
| 512 | if (ret < 0)
|
| 513 | return ret;
|
| 514 |
|
| 515 |
|
| 516 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP);
|
| 517 | if (ret < 0)
|
| 518 | return ret;
|
| 519 |
|
| 520 | ret = ssd1307fb_write_cmd(par->client,
|
| 521 | BIT(4) | (par->device_info->need_chargepump ? BIT(2) : 0));
|
| 522 | if (ret < 0)
|
| 523 | return ret;
|
| 524 |
|
| 525 |
|
| 526 | if (par->lookup_table_set) {
|
| 527 | int i;
|
| 528 |
|
| 529 | ret = ssd1307fb_write_cmd(par->client,
|
| 530 | SSD1307FB_SET_LOOKUP_TABLE);
|
| 531 | if (ret < 0)
|
| 532 | return ret;
|
| 533 |
|
| 534 | for (i = 0; i < ARRAY_SIZE(par->lookup_table); ++i) {
|
| 535 | u8 val = par->lookup_table[i];
|
| 536 |
|
| 537 | if (val < 31 || val > 63)
|
| 538 | dev_warn(&par->client->dev,
|
| 539 | "lookup table index %d value out of range 31 <= %d <= 63\n",
|
| 540 | i, val);
|
| 541 | ret = ssd1307fb_write_cmd(par->client, val);
|
| 542 | if (ret < 0)
|
| 543 | return ret;
|
| 544 | }
|
| 545 | }
|
| 546 |
|
| 547 |
|
| 548 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE);
|
| 549 | if (ret < 0)
|
| 550 | return ret;
|
| 551 |
|
| 552 | ret = ssd1307fb_write_cmd(par->client,
|
| 553 | SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL);
|
| 554 | if (ret < 0)
|
| 555 | return ret;
|
| 556 |
|
| 557 |
|
| 558 | ret = ssd1307fb_update_display(par);
|
| 559 | if (ret < 0)
|
| 560 | return ret;
|
| 561 |
|
| 562 |
|
| 563 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON);
|
| 564 | if (ret < 0)
|
| 565 | return ret;
|
| 566 |
|
| 567 | return 0;
|
| 568 | }
|
| 569 |
|
| 570 | static int ssd1307fb_update_bl(struct backlight_device *bdev)
|
| 571 | {
|
| 572 | struct ssd1307fb_par *par = bl_get_data(bdev);
|
| 573 | int ret;
|
| 574 | int brightness = bdev->props.brightness;
|
| 575 |
|
| 576 | par->contrast = brightness;
|
| 577 |
|
| 578 | ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST);
|
| 579 | if (ret < 0)
|
| 580 | return ret;
|
| 581 | ret = ssd1307fb_write_cmd(par->client, par->contrast);
|
| 582 | if (ret < 0)
|
| 583 | return ret;
|
| 584 | return 0;
|
| 585 | }
|
| 586 |
|
| 587 | static int ssd1307fb_get_brightness(struct backlight_device *bdev)
|
| 588 | {
|
| 589 | struct ssd1307fb_par *par = bl_get_data(bdev);
|
| 590 |
|
| 591 | return par->contrast;
|
| 592 | }
|
| 593 |
|
| 594 | static const struct backlight_ops ssd1307fb_bl_ops = {
|
| 595 | .options = BL_CORE_SUSPENDRESUME,
|
| 596 | .update_status = ssd1307fb_update_bl,
|
| 597 | .get_brightness = ssd1307fb_get_brightness,
|
| 598 | };
|
| 599 |
|
| 600 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = {
|
| 601 | .default_vcomh = 0x34,
|
| 602 | .default_dclk_div = 1,
|
| 603 | .default_dclk_frq = 7,
|
| 604 | };
|
| 605 |
|
| 606 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = {
|
| 607 | .default_vcomh = 0x20,
|
| 608 | .default_dclk_div = 1,
|
| 609 | .default_dclk_frq = 8,
|
| 610 | .need_chargepump = 1,
|
| 611 | };
|
| 612 |
|
| 613 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = {
|
| 614 | .default_vcomh = 0x20,
|
| 615 | .default_dclk_div = 2,
|
| 616 | .default_dclk_frq = 12,
|
| 617 | .need_pwm = 1,
|
| 618 | };
|
| 619 |
|
| 620 | static struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = {
|
| 621 | .default_vcomh = 0x34,
|
| 622 | .default_dclk_div = 1,
|
| 623 | .default_dclk_frq = 10,
|
| 624 | };
|
| 625 |
|
| 626 | static const struct of_device_id ssd1307fb_of_match[] = {
|
| 627 | {
|
| 628 | .compatible = "solomon,ssd1305fb-i2c",
|
| 629 | .data = (void *)&ssd1307fb_ssd1305_deviceinfo,
|
| 630 | },
|
| 631 | {
|
| 632 | .compatible = "solomon,ssd1306fb-i2c",
|
| 633 | .data = (void *)&ssd1307fb_ssd1306_deviceinfo,
|
| 634 | },
|
| 635 | {
|
| 636 | .compatible = "solomon,ssd1307fb-i2c",
|
| 637 | .data = (void *)&ssd1307fb_ssd1307_deviceinfo,
|
| 638 | },
|
| 639 | {
|
| 640 | .compatible = "solomon,ssd1309fb-i2c",
|
| 641 | .data = (void *)&ssd1307fb_ssd1309_deviceinfo,
|
| 642 | },
|
| 643 | {},
|
| 644 | };
|
| 645 | MODULE_DEVICE_TABLE(of, ssd1307fb_of_match);
|
| 646 |
|
| 647 | static int ssd1307fb_probe(struct i2c_client *client)
|
| 648 | {
|
| 649 | struct device *dev = &client->dev;
|
| 650 | struct backlight_device *bl;
|
| 651 | struct fb_info *info;
|
| 652 | struct fb_deferred_io *ssd1307fb_defio;
|
| 653 | u32 vmem_size;
|
| 654 | struct ssd1307fb_par *par;
|
| 655 | void *vmem;
|
| 656 | int ret;
|
| 657 |
|
| 658 | info = framebuffer_alloc(sizeof(struct ssd1307fb_par), dev);
|
| 659 | if (!info)
|
| 660 | return -ENOMEM;
|
| 661 |
|
| 662 | par = info->par;
|
| 663 | par->info = info;
|
| 664 | par->client = client;
|
| 665 | spin_lock_init(&par->damage_lock);
|
| 666 |
|
| 667 | par->device_info = device_get_match_data(dev);
|
| 668 | if (!par->device_info) {
|
| 669 | ret = -ENODEV;
|
| 670 | goto fb_alloc_error;
|
| 671 | }
|
| 672 |
|
| 673 | par->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
|
| 674 | if (IS_ERR(par->reset)) {
|
| 675 | ret = dev_err_probe(dev, PTR_ERR(par->reset),
|
| 676 | "failed to get reset gpio\n");
|
| 677 | goto fb_alloc_error;
|
| 678 | }
|
| 679 |
|
| 680 | par->vbat_reg = devm_regulator_get_optional(dev, "vbat");
|
| 681 | if (IS_ERR(par->vbat_reg)) {
|
| 682 | ret = PTR_ERR(par->vbat_reg);
|
| 683 | if (ret == -ENODEV) {
|
| 684 | par->vbat_reg = NULL;
|
| 685 | } else {
|
| 686 | dev_err_probe(dev, ret, "failed to get VBAT regulator\n");
|
| 687 | goto fb_alloc_error;
|
| 688 | }
|
| 689 | }
|
| 690 |
|
| 691 | if (device_property_read_u32(dev, "solomon,width", &par->width))
|
| 692 | par->width = 96;
|
| 693 |
|
| 694 | if (device_property_read_u32(dev, "solomon,height", &par->height))
|
| 695 | par->height = 16;
|
| 696 |
|
| 697 | if (device_property_read_u32(dev, "solomon,page-offset", &par->page_offset))
|
| 698 | par->page_offset = 1;
|
| 699 |
|
| 700 | if (device_property_read_u32(dev, "solomon,col-offset", &par->col_offset))
|
| 701 | par->col_offset = 0;
|
| 702 |
|
| 703 | if (device_property_read_u32(dev, "solomon,com-offset", &par->com_offset))
|
| 704 | par->com_offset = 0;
|
| 705 |
|
| 706 | if (device_property_read_u32(dev, "solomon,prechargep1", &par->prechargep1))
|
| 707 | par->prechargep1 = 2;
|
| 708 |
|
| 709 | if (device_property_read_u32(dev, "solomon,prechargep2", &par->prechargep2))
|
| 710 | par->prechargep2 = 2;
|
| 711 |
|
| 712 | if (!device_property_read_u8_array(dev, "solomon,lookup-table",
|
| 713 | par->lookup_table,
|
| 714 | ARRAY_SIZE(par->lookup_table)))
|
| 715 | par->lookup_table_set = 1;
|
| 716 |
|
| 717 | par->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap");
|
| 718 | par->com_seq = device_property_read_bool(dev, "solomon,com-seq");
|
| 719 | par->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap");
|
| 720 | par->com_invdir = device_property_read_bool(dev, "solomon,com-invdir");
|
| 721 | par->area_color_enable =
|
| 722 | device_property_read_bool(dev, "solomon,area-color-enable");
|
| 723 | par->low_power = device_property_read_bool(dev, "solomon,low-power");
|
| 724 |
|
| 725 | par->contrast = 127;
|
| 726 | par->vcomh = par->device_info->default_vcomh;
|
| 727 |
|
| 728 |
|
| 729 | if (device_property_read_u32(dev, "solomon,dclk-div", &par->dclk_div))
|
| 730 | par->dclk_div = par->device_info->default_dclk_div;
|
| 731 | if (device_property_read_u32(dev, "solomon,dclk-frq", &par->dclk_frq))
|
| 732 | par->dclk_frq = par->device_info->default_dclk_frq;
|
| 733 |
|
| 734 | vmem_size = DIV_ROUND_UP(par->width, 8) * par->height;
|
| 735 |
|
| 736 | vmem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
|
| 737 | get_order(vmem_size));
|
| 738 | if (!vmem) {
|
| 739 | dev_err(dev, "Couldn't allocate graphical memory.\n");
|
| 740 | ret = -ENOMEM;
|
| 741 | goto fb_alloc_error;
|
| 742 | }
|
| 743 |
|
| 744 | ssd1307fb_defio = devm_kzalloc(dev, sizeof(*ssd1307fb_defio),
|
| 745 | GFP_KERNEL);
|
| 746 | if (!ssd1307fb_defio) {
|
| 747 | dev_err(dev, "Couldn't allocate deferred io.\n");
|
| 748 | ret = -ENOMEM;
|
| 749 | goto fb_defio_error;
|
| 750 | }
|
| 751 |
|
| 752 | ssd1307fb_defio->delay = HZ / refreshrate;
|
| 753 | ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io;
|
| 754 |
|
| 755 | info->fbops = &ssd1307fb_ops;
|
| 756 | info->fix = ssd1307fb_fix;
|
| 757 | info->fix.line_length = DIV_ROUND_UP(par->width, 8);
|
| 758 | info->fbdefio = ssd1307fb_defio;
|
| 759 |
|
| 760 | info->var = ssd1307fb_var;
|
| 761 | info->var.xres = par->width;
|
| 762 | info->var.xres_virtual = par->width;
|
| 763 | info->var.yres = par->height;
|
| 764 | info->var.yres_virtual = par->height;
|
| 765 |
|
| 766 | info->screen_buffer = vmem;
|
| 767 | info->fix.smem_start = __pa(vmem);
|
| 768 | info->fix.smem_len = vmem_size;
|
| 769 |
|
| 770 | fb_deferred_io_init(info);
|
| 771 |
|
| 772 | i2c_set_clientdata(client, info);
|
| 773 |
|
| 774 | if (par->reset) {
|
| 775 |
|
| 776 | gpiod_set_value_cansleep(par->reset, 1);
|
| 777 | udelay(4);
|
| 778 | gpiod_set_value_cansleep(par->reset, 0);
|
| 779 | udelay(4);
|
| 780 | }
|
| 781 |
|
| 782 | if (par->vbat_reg) {
|
| 783 | ret = regulator_enable(par->vbat_reg);
|
| 784 | if (ret) {
|
| 785 | dev_err(dev, "failed to enable VBAT: %d\n", ret);
|
| 786 | goto reset_oled_error;
|
| 787 | }
|
| 788 | }
|
| 789 |
|
| 790 | ret = ssd1307fb_init(par);
|
| 791 | if (ret)
|
| 792 | goto regulator_enable_error;
|
| 793 |
|
| 794 | bl = backlight_device_register("ssd1307fb-bl", dev, par, &ssd1307fb_bl_ops,
|
| 795 | NULL);
|
| 796 | if (IS_ERR(bl)) {
|
| 797 | ret = PTR_ERR(bl);
|
| 798 | dev_err(dev, "unable to register backlight device: %d\n", ret);
|
| 799 | goto panel_init_error;
|
| 800 | }
|
| 801 | info->bl_dev = bl;
|
| 802 |
|
| 803 | ret = register_framebuffer(info);
|
| 804 | if (ret) {
|
| 805 | dev_err(dev, "Couldn't register the framebuffer\n");
|
| 806 | goto fb_init_error;
|
| 807 | }
|
| 808 |
|
| 809 | bl->props.brightness = par->contrast;
|
| 810 | bl->props.max_brightness = MAX_CONTRAST;
|
| 811 |
|
| 812 | dev_info(dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size);
|
| 813 |
|
| 814 | return 0;
|
| 815 |
|
| 816 | fb_init_error:
|
| 817 | backlight_device_unregister(bl);
|
| 818 | panel_init_error:
|
| 819 | pwm_disable(par->pwm);
|
| 820 | pwm_put(par->pwm);
|
| 821 | regulator_enable_error:
|
| 822 | if (par->vbat_reg)
|
| 823 | regulator_disable(par->vbat_reg);
|
| 824 | reset_oled_error:
|
| 825 | fb_deferred_io_cleanup(info);
|
| 826 | fb_defio_error:
|
| 827 | __free_pages(vmem, get_order(vmem_size));
|
| 828 | fb_alloc_error:
|
| 829 | framebuffer_release(info);
|
| 830 | return ret;
|
| 831 | }
|
| 832 |
|
| 833 | static void ssd1307fb_remove(struct i2c_client *client)
|
| 834 | {
|
| 835 | struct fb_info *info = i2c_get_clientdata(client);
|
| 836 | struct ssd1307fb_par *par = info->par;
|
| 837 |
|
| 838 | ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF);
|
| 839 |
|
| 840 | backlight_device_unregister(info->bl_dev);
|
| 841 |
|
| 842 | unregister_framebuffer(info);
|
| 843 | pwm_disable(par->pwm);
|
| 844 | pwm_put(par->pwm);
|
| 845 | if (par->vbat_reg)
|
| 846 | regulator_disable(par->vbat_reg);
|
| 847 | fb_deferred_io_cleanup(info);
|
| 848 | __free_pages(__va(info->fix.smem_start), get_order(info->fix.smem_len));
|
| 849 | framebuffer_release(info);
|
| 850 | }
|
| 851 |
|
| 852 | static const struct i2c_device_id ssd1307fb_i2c_id[] = {
|
| 853 | { .name = "ssd1305fb" },
|
| 854 | { .name = "ssd1306fb" },
|
| 855 | { .name = "ssd1307fb" },
|
| 856 | { .name = "ssd1309fb" },
|
| 857 | { }
|
| 858 | };
|
| 859 | MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id);
|
| 860 |
|
| 861 | static struct i2c_driver ssd1307fb_driver = {
|
| 862 | .probe = ssd1307fb_probe,
|
| 863 | .remove = ssd1307fb_remove,
|
| 864 | .id_table = ssd1307fb_i2c_id,
|
| 865 | .driver = {
|
| 866 | .name = "ssd1307fb",
|
| 867 | .of_match_table = ssd1307fb_of_match,
|
| 868 | },
|
| 869 | };
|
| 870 |
|
| 871 | module_i2c_driver(ssd1307fb_driver);
|
| 872 |
|
| 873 | MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller");
|
| 874 | MODULE_AUTHOR("Maxime Ripard <[email protected]>");
|
| 875 | MODULE_LICENSE("GPL");
|