162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for the Solomon SSD1307 OLED controller 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2012 Free Electrons 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/backlight.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/fb.h> 1162306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1262306a36Sopenharmony_ci#include <linux/i2c.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/property.h> 1662306a36Sopenharmony_ci#include <linux/pwm.h> 1762306a36Sopenharmony_ci#include <linux/uaccess.h> 1862306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define SSD1307FB_DATA 0x40 2162306a36Sopenharmony_ci#define SSD1307FB_COMMAND 0x80 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE 0x20 2462306a36Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00) 2562306a36Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01) 2662306a36Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02) 2762306a36Sopenharmony_ci#define SSD1307FB_SET_COL_RANGE 0x21 2862306a36Sopenharmony_ci#define SSD1307FB_SET_PAGE_RANGE 0x22 2962306a36Sopenharmony_ci#define SSD1307FB_CONTRAST 0x81 3062306a36Sopenharmony_ci#define SSD1307FB_SET_LOOKUP_TABLE 0x91 3162306a36Sopenharmony_ci#define SSD1307FB_CHARGE_PUMP 0x8d 3262306a36Sopenharmony_ci#define SSD1307FB_SEG_REMAP_ON 0xa1 3362306a36Sopenharmony_ci#define SSD1307FB_DISPLAY_OFF 0xae 3462306a36Sopenharmony_ci#define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 3562306a36Sopenharmony_ci#define SSD1307FB_DISPLAY_ON 0xaf 3662306a36Sopenharmony_ci#define SSD1307FB_START_PAGE_ADDRESS 0xb0 3762306a36Sopenharmony_ci#define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 3862306a36Sopenharmony_ci#define SSD1307FB_SET_CLOCK_FREQ 0xd5 3962306a36Sopenharmony_ci#define SSD1307FB_SET_AREA_COLOR_MODE 0xd8 4062306a36Sopenharmony_ci#define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 4162306a36Sopenharmony_ci#define SSD1307FB_SET_COM_PINS_CONFIG 0xda 4262306a36Sopenharmony_ci#define SSD1307FB_SET_VCOMH 0xdb 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#define MAX_CONTRAST 255 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define REFRESHRATE 1 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic u_int refreshrate = REFRESHRATE; 4962306a36Sopenharmony_cimodule_param(refreshrate, uint, 0); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistruct ssd1307fb_deviceinfo { 5262306a36Sopenharmony_ci u32 default_vcomh; 5362306a36Sopenharmony_ci u32 default_dclk_div; 5462306a36Sopenharmony_ci u32 default_dclk_frq; 5562306a36Sopenharmony_ci bool need_pwm; 5662306a36Sopenharmony_ci bool need_chargepump; 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistruct ssd1307fb_par { 6062306a36Sopenharmony_ci unsigned area_color_enable : 1; 6162306a36Sopenharmony_ci unsigned com_invdir : 1; 6262306a36Sopenharmony_ci unsigned com_lrremap : 1; 6362306a36Sopenharmony_ci unsigned com_seq : 1; 6462306a36Sopenharmony_ci unsigned lookup_table_set : 1; 6562306a36Sopenharmony_ci unsigned low_power : 1; 6662306a36Sopenharmony_ci unsigned seg_remap : 1; 6762306a36Sopenharmony_ci u32 com_offset; 6862306a36Sopenharmony_ci u32 contrast; 6962306a36Sopenharmony_ci u32 dclk_div; 7062306a36Sopenharmony_ci u32 dclk_frq; 7162306a36Sopenharmony_ci const struct ssd1307fb_deviceinfo *device_info; 7262306a36Sopenharmony_ci struct i2c_client *client; 7362306a36Sopenharmony_ci u32 height; 7462306a36Sopenharmony_ci struct fb_info *info; 7562306a36Sopenharmony_ci u8 lookup_table[4]; 7662306a36Sopenharmony_ci u32 page_offset; 7762306a36Sopenharmony_ci u32 col_offset; 7862306a36Sopenharmony_ci u32 prechargep1; 7962306a36Sopenharmony_ci u32 prechargep2; 8062306a36Sopenharmony_ci struct pwm_device *pwm; 8162306a36Sopenharmony_ci struct gpio_desc *reset; 8262306a36Sopenharmony_ci struct regulator *vbat_reg; 8362306a36Sopenharmony_ci u32 vcomh; 8462306a36Sopenharmony_ci u32 width; 8562306a36Sopenharmony_ci /* Cached address ranges */ 8662306a36Sopenharmony_ci u8 col_start; 8762306a36Sopenharmony_ci u8 col_end; 8862306a36Sopenharmony_ci u8 page_start; 8962306a36Sopenharmony_ci u8 page_end; 9062306a36Sopenharmony_ci}; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistruct ssd1307fb_array { 9362306a36Sopenharmony_ci u8 type; 9462306a36Sopenharmony_ci u8 data[]; 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic const struct fb_fix_screeninfo ssd1307fb_fix = { 9862306a36Sopenharmony_ci .id = "Solomon SSD1307", 9962306a36Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 10062306a36Sopenharmony_ci .visual = FB_VISUAL_MONO10, 10162306a36Sopenharmony_ci .xpanstep = 0, 10262306a36Sopenharmony_ci .ypanstep = 0, 10362306a36Sopenharmony_ci .ywrapstep = 0, 10462306a36Sopenharmony_ci .accel = FB_ACCEL_NONE, 10562306a36Sopenharmony_ci}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic const struct fb_var_screeninfo ssd1307fb_var = { 10862306a36Sopenharmony_ci .bits_per_pixel = 1, 10962306a36Sopenharmony_ci .red = { .length = 1 }, 11062306a36Sopenharmony_ci .green = { .length = 1 }, 11162306a36Sopenharmony_ci .blue = { .length = 1 }, 11262306a36Sopenharmony_ci}; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci struct ssd1307fb_array *array; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL); 11962306a36Sopenharmony_ci if (!array) 12062306a36Sopenharmony_ci return NULL; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci array->type = type; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci return array; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic int ssd1307fb_write_array(struct i2c_client *client, 12862306a36Sopenharmony_ci struct ssd1307fb_array *array, u32 len) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci int ret; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci len += sizeof(struct ssd1307fb_array); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ret = i2c_master_send(client, (u8 *)array, len); 13562306a36Sopenharmony_ci if (ret != len) { 13662306a36Sopenharmony_ci dev_err(&client->dev, "Couldn't send I2C command.\n"); 13762306a36Sopenharmony_ci return ret; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct ssd1307fb_array *array; 14662306a36Sopenharmony_ci int ret; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND); 14962306a36Sopenharmony_ci if (!array) 15062306a36Sopenharmony_ci return -ENOMEM; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci array->data[0] = cmd; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci ret = ssd1307fb_write_array(client, array, 1); 15562306a36Sopenharmony_ci kfree(array); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return ret; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic int ssd1307fb_set_col_range(struct ssd1307fb_par *par, u8 col_start, 16162306a36Sopenharmony_ci u8 cols) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci u8 col_end = col_start + cols - 1; 16462306a36Sopenharmony_ci int ret; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (col_start == par->col_start && col_end == par->col_end) 16762306a36Sopenharmony_ci return 0; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); 17062306a36Sopenharmony_ci if (ret < 0) 17162306a36Sopenharmony_ci return ret; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, col_start); 17462306a36Sopenharmony_ci if (ret < 0) 17562306a36Sopenharmony_ci return ret; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, col_end); 17862306a36Sopenharmony_ci if (ret < 0) 17962306a36Sopenharmony_ci return ret; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci par->col_start = col_start; 18262306a36Sopenharmony_ci par->col_end = col_end; 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic int ssd1307fb_set_page_range(struct ssd1307fb_par *par, u8 page_start, 18762306a36Sopenharmony_ci u8 pages) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci u8 page_end = page_start + pages - 1; 19062306a36Sopenharmony_ci int ret; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (page_start == par->page_start && page_end == par->page_end) 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); 19662306a36Sopenharmony_ci if (ret < 0) 19762306a36Sopenharmony_ci return ret; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, page_start); 20062306a36Sopenharmony_ci if (ret < 0) 20162306a36Sopenharmony_ci return ret; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, page_end); 20462306a36Sopenharmony_ci if (ret < 0) 20562306a36Sopenharmony_ci return ret; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci par->page_start = page_start; 20862306a36Sopenharmony_ci par->page_end = page_end; 20962306a36Sopenharmony_ci return 0; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic int ssd1307fb_update_rect(struct ssd1307fb_par *par, unsigned int x, 21362306a36Sopenharmony_ci unsigned int y, unsigned int width, 21462306a36Sopenharmony_ci unsigned int height) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci struct ssd1307fb_array *array; 21762306a36Sopenharmony_ci u8 *vmem = par->info->screen_buffer; 21862306a36Sopenharmony_ci unsigned int line_length = par->info->fix.line_length; 21962306a36Sopenharmony_ci unsigned int pages = DIV_ROUND_UP(y % 8 + height, 8); 22062306a36Sopenharmony_ci u32 array_idx = 0; 22162306a36Sopenharmony_ci int ret, i, j, k; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci array = ssd1307fb_alloc_array(width * pages, SSD1307FB_DATA); 22462306a36Sopenharmony_ci if (!array) 22562306a36Sopenharmony_ci return -ENOMEM; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* 22862306a36Sopenharmony_ci * The screen is divided in pages, each having a height of 8 22962306a36Sopenharmony_ci * pixels, and the width of the screen. When sending a byte of 23062306a36Sopenharmony_ci * data to the controller, it gives the 8 bits for the current 23162306a36Sopenharmony_ci * column. I.e, the first byte are the 8 bits of the first 23262306a36Sopenharmony_ci * column, then the 8 bits for the second column, etc. 23362306a36Sopenharmony_ci * 23462306a36Sopenharmony_ci * 23562306a36Sopenharmony_ci * Representation of the screen, assuming it is 5 bits 23662306a36Sopenharmony_ci * wide. Each letter-number combination is a bit that controls 23762306a36Sopenharmony_ci * one pixel. 23862306a36Sopenharmony_ci * 23962306a36Sopenharmony_ci * A0 A1 A2 A3 A4 24062306a36Sopenharmony_ci * B0 B1 B2 B3 B4 24162306a36Sopenharmony_ci * C0 C1 C2 C3 C4 24262306a36Sopenharmony_ci * D0 D1 D2 D3 D4 24362306a36Sopenharmony_ci * E0 E1 E2 E3 E4 24462306a36Sopenharmony_ci * F0 F1 F2 F3 F4 24562306a36Sopenharmony_ci * G0 G1 G2 G3 G4 24662306a36Sopenharmony_ci * H0 H1 H2 H3 H4 24762306a36Sopenharmony_ci * 24862306a36Sopenharmony_ci * If you want to update this screen, you need to send 5 bytes: 24962306a36Sopenharmony_ci * (1) A0 B0 C0 D0 E0 F0 G0 H0 25062306a36Sopenharmony_ci * (2) A1 B1 C1 D1 E1 F1 G1 H1 25162306a36Sopenharmony_ci * (3) A2 B2 C2 D2 E2 F2 G2 H2 25262306a36Sopenharmony_ci * (4) A3 B3 C3 D3 E3 F3 G3 H3 25362306a36Sopenharmony_ci * (5) A4 B4 C4 D4 E4 F4 G4 H4 25462306a36Sopenharmony_ci */ 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci ret = ssd1307fb_set_col_range(par, par->col_offset + x, width); 25762306a36Sopenharmony_ci if (ret < 0) 25862306a36Sopenharmony_ci goto out_free; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci ret = ssd1307fb_set_page_range(par, par->page_offset + y / 8, pages); 26162306a36Sopenharmony_ci if (ret < 0) 26262306a36Sopenharmony_ci goto out_free; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci for (i = y / 8; i < y / 8 + pages; i++) { 26562306a36Sopenharmony_ci int m = 8; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* Last page may be partial */ 26862306a36Sopenharmony_ci if (8 * (i + 1) > par->height) 26962306a36Sopenharmony_ci m = par->height % 8; 27062306a36Sopenharmony_ci for (j = x; j < x + width; j++) { 27162306a36Sopenharmony_ci u8 data = 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci for (k = 0; k < m; k++) { 27462306a36Sopenharmony_ci u8 byte = vmem[(8 * i + k) * line_length + 27562306a36Sopenharmony_ci j / 8]; 27662306a36Sopenharmony_ci u8 bit = (byte >> (j % 8)) & 1; 27762306a36Sopenharmony_ci data |= bit << k; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci array->data[array_idx++] = data; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci ret = ssd1307fb_write_array(par->client, array, width * pages); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ciout_free: 28662306a36Sopenharmony_ci kfree(array); 28762306a36Sopenharmony_ci return ret; 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic int ssd1307fb_update_display(struct ssd1307fb_par *par) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci return ssd1307fb_update_rect(par, 0, 0, par->width, par->height); 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic int ssd1307fb_blank(int blank_mode, struct fb_info *info) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct ssd1307fb_par *par = info->par; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci if (blank_mode != FB_BLANK_UNBLANK) 30062306a36Sopenharmony_ci return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF); 30162306a36Sopenharmony_ci else 30262306a36Sopenharmony_ci return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic void ssd1307fb_defio_damage_range(struct fb_info *info, off_t off, size_t len) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci struct ssd1307fb_par *par = info->par; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci ssd1307fb_update_display(par); 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_cistatic void ssd1307fb_defio_damage_area(struct fb_info *info, u32 x, u32 y, 31362306a36Sopenharmony_ci u32 width, u32 height) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci struct ssd1307fb_par *par = info->par; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci ssd1307fb_update_rect(par, x, y, width, height); 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ciFB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(ssd1307fb, 32162306a36Sopenharmony_ci ssd1307fb_defio_damage_range, 32262306a36Sopenharmony_ci ssd1307fb_defio_damage_area) 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cistatic const struct fb_ops ssd1307fb_ops = { 32562306a36Sopenharmony_ci .owner = THIS_MODULE, 32662306a36Sopenharmony_ci FB_DEFAULT_DEFERRED_OPS(ssd1307fb), 32762306a36Sopenharmony_ci .fb_blank = ssd1307fb_blank, 32862306a36Sopenharmony_ci}; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic void ssd1307fb_deferred_io(struct fb_info *info, struct list_head *pagereflist) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci ssd1307fb_update_display(info->par); 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic int ssd1307fb_init(struct ssd1307fb_par *par) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci struct pwm_state pwmstate; 33862306a36Sopenharmony_ci int ret; 33962306a36Sopenharmony_ci u32 precharge, dclk, com_invdir, compins; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci if (par->device_info->need_pwm) { 34262306a36Sopenharmony_ci par->pwm = pwm_get(&par->client->dev, NULL); 34362306a36Sopenharmony_ci if (IS_ERR(par->pwm)) { 34462306a36Sopenharmony_ci dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); 34562306a36Sopenharmony_ci return PTR_ERR(par->pwm); 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci pwm_init_state(par->pwm, &pwmstate); 34962306a36Sopenharmony_ci pwm_set_relative_duty_cycle(&pwmstate, 50, 100); 35062306a36Sopenharmony_ci pwm_apply_state(par->pwm, &pwmstate); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci /* Enable the PWM */ 35362306a36Sopenharmony_ci pwm_enable(par->pwm); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci dev_dbg(&par->client->dev, "Using PWM %s with a %lluns period.\n", 35662306a36Sopenharmony_ci par->pwm->label, pwm_get_period(par->pwm)); 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* Set initial contrast */ 36062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); 36162306a36Sopenharmony_ci if (ret < 0) 36262306a36Sopenharmony_ci return ret; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->contrast); 36562306a36Sopenharmony_ci if (ret < 0) 36662306a36Sopenharmony_ci return ret; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci /* Set segment re-map */ 36962306a36Sopenharmony_ci if (par->seg_remap) { 37062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); 37162306a36Sopenharmony_ci if (ret < 0) 37262306a36Sopenharmony_ci return ret; 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci /* Set COM direction */ 37662306a36Sopenharmony_ci com_invdir = 0xc0 | par->com_invdir << 3; 37762306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, com_invdir); 37862306a36Sopenharmony_ci if (ret < 0) 37962306a36Sopenharmony_ci return ret; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci /* Set multiplex ratio value */ 38262306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); 38362306a36Sopenharmony_ci if (ret < 0) 38462306a36Sopenharmony_ci return ret; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->height - 1); 38762306a36Sopenharmony_ci if (ret < 0) 38862306a36Sopenharmony_ci return ret; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci /* set display offset value */ 39162306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET); 39262306a36Sopenharmony_ci if (ret < 0) 39362306a36Sopenharmony_ci return ret; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->com_offset); 39662306a36Sopenharmony_ci if (ret < 0) 39762306a36Sopenharmony_ci return ret; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci /* Set clock frequency */ 40062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ); 40162306a36Sopenharmony_ci if (ret < 0) 40262306a36Sopenharmony_ci return ret; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4; 40562306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, dclk); 40662306a36Sopenharmony_ci if (ret < 0) 40762306a36Sopenharmony_ci return ret; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci /* Set Area Color Mode ON/OFF & Low Power Display Mode */ 41062306a36Sopenharmony_ci if (par->area_color_enable || par->low_power) { 41162306a36Sopenharmony_ci u32 mode; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 41462306a36Sopenharmony_ci SSD1307FB_SET_AREA_COLOR_MODE); 41562306a36Sopenharmony_ci if (ret < 0) 41662306a36Sopenharmony_ci return ret; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci mode = (par->area_color_enable ? 0x30 : 0) | 41962306a36Sopenharmony_ci (par->low_power ? 5 : 0); 42062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, mode); 42162306a36Sopenharmony_ci if (ret < 0) 42262306a36Sopenharmony_ci return ret; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci /* Set precharge period in number of ticks from the internal clock */ 42662306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD); 42762306a36Sopenharmony_ci if (ret < 0) 42862306a36Sopenharmony_ci return ret; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4; 43162306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, precharge); 43262306a36Sopenharmony_ci if (ret < 0) 43362306a36Sopenharmony_ci return ret; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci /* Set COM pins configuration */ 43662306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG); 43762306a36Sopenharmony_ci if (ret < 0) 43862306a36Sopenharmony_ci return ret; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci compins = 0x02 | !par->com_seq << 4 | par->com_lrremap << 5; 44162306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, compins); 44262306a36Sopenharmony_ci if (ret < 0) 44362306a36Sopenharmony_ci return ret; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci /* Set VCOMH */ 44662306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH); 44762306a36Sopenharmony_ci if (ret < 0) 44862306a36Sopenharmony_ci return ret; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->vcomh); 45162306a36Sopenharmony_ci if (ret < 0) 45262306a36Sopenharmony_ci return ret; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci /* Turn on the DC-DC Charge Pump */ 45562306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP); 45662306a36Sopenharmony_ci if (ret < 0) 45762306a36Sopenharmony_ci return ret; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 46062306a36Sopenharmony_ci BIT(4) | (par->device_info->need_chargepump ? BIT(2) : 0)); 46162306a36Sopenharmony_ci if (ret < 0) 46262306a36Sopenharmony_ci return ret; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci /* Set lookup table */ 46562306a36Sopenharmony_ci if (par->lookup_table_set) { 46662306a36Sopenharmony_ci int i; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 46962306a36Sopenharmony_ci SSD1307FB_SET_LOOKUP_TABLE); 47062306a36Sopenharmony_ci if (ret < 0) 47162306a36Sopenharmony_ci return ret; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(par->lookup_table); ++i) { 47462306a36Sopenharmony_ci u8 val = par->lookup_table[i]; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci if (val < 31 || val > 63) 47762306a36Sopenharmony_ci dev_warn(&par->client->dev, 47862306a36Sopenharmony_ci "lookup table index %d value out of range 31 <= %d <= 63\n", 47962306a36Sopenharmony_ci i, val); 48062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, val); 48162306a36Sopenharmony_ci if (ret < 0) 48262306a36Sopenharmony_ci return ret; 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci } 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci /* Switch to horizontal addressing mode */ 48762306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); 48862306a36Sopenharmony_ci if (ret < 0) 48962306a36Sopenharmony_ci return ret; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 49262306a36Sopenharmony_ci SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); 49362306a36Sopenharmony_ci if (ret < 0) 49462306a36Sopenharmony_ci return ret; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci /* Clear the screen */ 49762306a36Sopenharmony_ci ret = ssd1307fb_update_display(par); 49862306a36Sopenharmony_ci if (ret < 0) 49962306a36Sopenharmony_ci return ret; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci /* Turn on the display */ 50262306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); 50362306a36Sopenharmony_ci if (ret < 0) 50462306a36Sopenharmony_ci return ret; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci return 0; 50762306a36Sopenharmony_ci} 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cistatic int ssd1307fb_update_bl(struct backlight_device *bdev) 51062306a36Sopenharmony_ci{ 51162306a36Sopenharmony_ci struct ssd1307fb_par *par = bl_get_data(bdev); 51262306a36Sopenharmony_ci int ret; 51362306a36Sopenharmony_ci int brightness = bdev->props.brightness; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci par->contrast = brightness; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); 51862306a36Sopenharmony_ci if (ret < 0) 51962306a36Sopenharmony_ci return ret; 52062306a36Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->contrast); 52162306a36Sopenharmony_ci if (ret < 0) 52262306a36Sopenharmony_ci return ret; 52362306a36Sopenharmony_ci return 0; 52462306a36Sopenharmony_ci} 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_cistatic int ssd1307fb_get_brightness(struct backlight_device *bdev) 52762306a36Sopenharmony_ci{ 52862306a36Sopenharmony_ci struct ssd1307fb_par *par = bl_get_data(bdev); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci return par->contrast; 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic int ssd1307fb_check_fb(struct backlight_device *bdev, 53462306a36Sopenharmony_ci struct fb_info *info) 53562306a36Sopenharmony_ci{ 53662306a36Sopenharmony_ci return (info->bl_dev == bdev); 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_cistatic const struct backlight_ops ssd1307fb_bl_ops = { 54062306a36Sopenharmony_ci .options = BL_CORE_SUSPENDRESUME, 54162306a36Sopenharmony_ci .update_status = ssd1307fb_update_bl, 54262306a36Sopenharmony_ci .get_brightness = ssd1307fb_get_brightness, 54362306a36Sopenharmony_ci .check_fb = ssd1307fb_check_fb, 54462306a36Sopenharmony_ci}; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = { 54762306a36Sopenharmony_ci .default_vcomh = 0x34, 54862306a36Sopenharmony_ci .default_dclk_div = 1, 54962306a36Sopenharmony_ci .default_dclk_frq = 7, 55062306a36Sopenharmony_ci}; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = { 55362306a36Sopenharmony_ci .default_vcomh = 0x20, 55462306a36Sopenharmony_ci .default_dclk_div = 1, 55562306a36Sopenharmony_ci .default_dclk_frq = 8, 55662306a36Sopenharmony_ci .need_chargepump = 1, 55762306a36Sopenharmony_ci}; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = { 56062306a36Sopenharmony_ci .default_vcomh = 0x20, 56162306a36Sopenharmony_ci .default_dclk_div = 2, 56262306a36Sopenharmony_ci .default_dclk_frq = 12, 56362306a36Sopenharmony_ci .need_pwm = 1, 56462306a36Sopenharmony_ci}; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = { 56762306a36Sopenharmony_ci .default_vcomh = 0x34, 56862306a36Sopenharmony_ci .default_dclk_div = 1, 56962306a36Sopenharmony_ci .default_dclk_frq = 10, 57062306a36Sopenharmony_ci}; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cistatic const struct of_device_id ssd1307fb_of_match[] = { 57362306a36Sopenharmony_ci { 57462306a36Sopenharmony_ci .compatible = "solomon,ssd1305fb-i2c", 57562306a36Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1305_deviceinfo, 57662306a36Sopenharmony_ci }, 57762306a36Sopenharmony_ci { 57862306a36Sopenharmony_ci .compatible = "solomon,ssd1306fb-i2c", 57962306a36Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1306_deviceinfo, 58062306a36Sopenharmony_ci }, 58162306a36Sopenharmony_ci { 58262306a36Sopenharmony_ci .compatible = "solomon,ssd1307fb-i2c", 58362306a36Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1307_deviceinfo, 58462306a36Sopenharmony_ci }, 58562306a36Sopenharmony_ci { 58662306a36Sopenharmony_ci .compatible = "solomon,ssd1309fb-i2c", 58762306a36Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1309_deviceinfo, 58862306a36Sopenharmony_ci }, 58962306a36Sopenharmony_ci {}, 59062306a36Sopenharmony_ci}; 59162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ssd1307fb_of_match); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_cistatic int ssd1307fb_probe(struct i2c_client *client) 59462306a36Sopenharmony_ci{ 59562306a36Sopenharmony_ci struct device *dev = &client->dev; 59662306a36Sopenharmony_ci struct backlight_device *bl; 59762306a36Sopenharmony_ci char bl_name[12]; 59862306a36Sopenharmony_ci struct fb_info *info; 59962306a36Sopenharmony_ci struct fb_deferred_io *ssd1307fb_defio; 60062306a36Sopenharmony_ci u32 vmem_size; 60162306a36Sopenharmony_ci struct ssd1307fb_par *par; 60262306a36Sopenharmony_ci void *vmem; 60362306a36Sopenharmony_ci int ret; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci info = framebuffer_alloc(sizeof(struct ssd1307fb_par), dev); 60662306a36Sopenharmony_ci if (!info) 60762306a36Sopenharmony_ci return -ENOMEM; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci par = info->par; 61062306a36Sopenharmony_ci par->info = info; 61162306a36Sopenharmony_ci par->client = client; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci par->device_info = device_get_match_data(dev); 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci par->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 61662306a36Sopenharmony_ci if (IS_ERR(par->reset)) { 61762306a36Sopenharmony_ci ret = dev_err_probe(dev, PTR_ERR(par->reset), 61862306a36Sopenharmony_ci "failed to get reset gpio\n"); 61962306a36Sopenharmony_ci goto fb_alloc_error; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci par->vbat_reg = devm_regulator_get_optional(dev, "vbat"); 62362306a36Sopenharmony_ci if (IS_ERR(par->vbat_reg)) { 62462306a36Sopenharmony_ci ret = PTR_ERR(par->vbat_reg); 62562306a36Sopenharmony_ci if (ret == -ENODEV) { 62662306a36Sopenharmony_ci par->vbat_reg = NULL; 62762306a36Sopenharmony_ci } else { 62862306a36Sopenharmony_ci dev_err_probe(dev, ret, "failed to get VBAT regulator\n"); 62962306a36Sopenharmony_ci goto fb_alloc_error; 63062306a36Sopenharmony_ci } 63162306a36Sopenharmony_ci } 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,width", &par->width)) 63462306a36Sopenharmony_ci par->width = 96; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,height", &par->height)) 63762306a36Sopenharmony_ci par->height = 16; 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,page-offset", &par->page_offset)) 64062306a36Sopenharmony_ci par->page_offset = 1; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,col-offset", &par->col_offset)) 64362306a36Sopenharmony_ci par->col_offset = 0; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,com-offset", &par->com_offset)) 64662306a36Sopenharmony_ci par->com_offset = 0; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,prechargep1", &par->prechargep1)) 64962306a36Sopenharmony_ci par->prechargep1 = 2; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,prechargep2", &par->prechargep2)) 65262306a36Sopenharmony_ci par->prechargep2 = 2; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci if (!device_property_read_u8_array(dev, "solomon,lookup-table", 65562306a36Sopenharmony_ci par->lookup_table, 65662306a36Sopenharmony_ci ARRAY_SIZE(par->lookup_table))) 65762306a36Sopenharmony_ci par->lookup_table_set = 1; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci par->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap"); 66062306a36Sopenharmony_ci par->com_seq = device_property_read_bool(dev, "solomon,com-seq"); 66162306a36Sopenharmony_ci par->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap"); 66262306a36Sopenharmony_ci par->com_invdir = device_property_read_bool(dev, "solomon,com-invdir"); 66362306a36Sopenharmony_ci par->area_color_enable = 66462306a36Sopenharmony_ci device_property_read_bool(dev, "solomon,area-color-enable"); 66562306a36Sopenharmony_ci par->low_power = device_property_read_bool(dev, "solomon,low-power"); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci par->contrast = 127; 66862306a36Sopenharmony_ci par->vcomh = par->device_info->default_vcomh; 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci /* Setup display timing */ 67162306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,dclk-div", &par->dclk_div)) 67262306a36Sopenharmony_ci par->dclk_div = par->device_info->default_dclk_div; 67362306a36Sopenharmony_ci if (device_property_read_u32(dev, "solomon,dclk-frq", &par->dclk_frq)) 67462306a36Sopenharmony_ci par->dclk_frq = par->device_info->default_dclk_frq; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci vmem_size = DIV_ROUND_UP(par->width, 8) * par->height; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci vmem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 67962306a36Sopenharmony_ci get_order(vmem_size)); 68062306a36Sopenharmony_ci if (!vmem) { 68162306a36Sopenharmony_ci dev_err(dev, "Couldn't allocate graphical memory.\n"); 68262306a36Sopenharmony_ci ret = -ENOMEM; 68362306a36Sopenharmony_ci goto fb_alloc_error; 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci ssd1307fb_defio = devm_kzalloc(dev, sizeof(*ssd1307fb_defio), 68762306a36Sopenharmony_ci GFP_KERNEL); 68862306a36Sopenharmony_ci if (!ssd1307fb_defio) { 68962306a36Sopenharmony_ci dev_err(dev, "Couldn't allocate deferred io.\n"); 69062306a36Sopenharmony_ci ret = -ENOMEM; 69162306a36Sopenharmony_ci goto fb_alloc_error; 69262306a36Sopenharmony_ci } 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci ssd1307fb_defio->delay = HZ / refreshrate; 69562306a36Sopenharmony_ci ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci info->fbops = &ssd1307fb_ops; 69862306a36Sopenharmony_ci info->fix = ssd1307fb_fix; 69962306a36Sopenharmony_ci info->fix.line_length = DIV_ROUND_UP(par->width, 8); 70062306a36Sopenharmony_ci info->fbdefio = ssd1307fb_defio; 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci info->var = ssd1307fb_var; 70362306a36Sopenharmony_ci info->var.xres = par->width; 70462306a36Sopenharmony_ci info->var.xres_virtual = par->width; 70562306a36Sopenharmony_ci info->var.yres = par->height; 70662306a36Sopenharmony_ci info->var.yres_virtual = par->height; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci info->screen_buffer = vmem; 70962306a36Sopenharmony_ci info->fix.smem_start = __pa(vmem); 71062306a36Sopenharmony_ci info->fix.smem_len = vmem_size; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci fb_deferred_io_init(info); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci i2c_set_clientdata(client, info); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci if (par->reset) { 71762306a36Sopenharmony_ci /* Reset the screen */ 71862306a36Sopenharmony_ci gpiod_set_value_cansleep(par->reset, 1); 71962306a36Sopenharmony_ci udelay(4); 72062306a36Sopenharmony_ci gpiod_set_value_cansleep(par->reset, 0); 72162306a36Sopenharmony_ci udelay(4); 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci if (par->vbat_reg) { 72562306a36Sopenharmony_ci ret = regulator_enable(par->vbat_reg); 72662306a36Sopenharmony_ci if (ret) { 72762306a36Sopenharmony_ci dev_err(dev, "failed to enable VBAT: %d\n", ret); 72862306a36Sopenharmony_ci goto reset_oled_error; 72962306a36Sopenharmony_ci } 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci ret = ssd1307fb_init(par); 73362306a36Sopenharmony_ci if (ret) 73462306a36Sopenharmony_ci goto regulator_enable_error; 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci ret = register_framebuffer(info); 73762306a36Sopenharmony_ci if (ret) { 73862306a36Sopenharmony_ci dev_err(dev, "Couldn't register the framebuffer\n"); 73962306a36Sopenharmony_ci goto panel_init_error; 74062306a36Sopenharmony_ci } 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci snprintf(bl_name, sizeof(bl_name), "ssd1307fb%d", info->node); 74362306a36Sopenharmony_ci bl = backlight_device_register(bl_name, dev, par, &ssd1307fb_bl_ops, 74462306a36Sopenharmony_ci NULL); 74562306a36Sopenharmony_ci if (IS_ERR(bl)) { 74662306a36Sopenharmony_ci ret = PTR_ERR(bl); 74762306a36Sopenharmony_ci dev_err(dev, "unable to register backlight device: %d\n", ret); 74862306a36Sopenharmony_ci goto bl_init_error; 74962306a36Sopenharmony_ci } 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci bl->props.brightness = par->contrast; 75262306a36Sopenharmony_ci bl->props.max_brightness = MAX_CONTRAST; 75362306a36Sopenharmony_ci info->bl_dev = bl; 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci dev_info(dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size); 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci return 0; 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_cibl_init_error: 76062306a36Sopenharmony_ci unregister_framebuffer(info); 76162306a36Sopenharmony_cipanel_init_error: 76262306a36Sopenharmony_ci pwm_disable(par->pwm); 76362306a36Sopenharmony_ci pwm_put(par->pwm); 76462306a36Sopenharmony_ciregulator_enable_error: 76562306a36Sopenharmony_ci if (par->vbat_reg) 76662306a36Sopenharmony_ci regulator_disable(par->vbat_reg); 76762306a36Sopenharmony_cireset_oled_error: 76862306a36Sopenharmony_ci fb_deferred_io_cleanup(info); 76962306a36Sopenharmony_cifb_alloc_error: 77062306a36Sopenharmony_ci framebuffer_release(info); 77162306a36Sopenharmony_ci return ret; 77262306a36Sopenharmony_ci} 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_cistatic void ssd1307fb_remove(struct i2c_client *client) 77562306a36Sopenharmony_ci{ 77662306a36Sopenharmony_ci struct fb_info *info = i2c_get_clientdata(client); 77762306a36Sopenharmony_ci struct ssd1307fb_par *par = info->par; 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF); 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci backlight_device_unregister(info->bl_dev); 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci unregister_framebuffer(info); 78462306a36Sopenharmony_ci pwm_disable(par->pwm); 78562306a36Sopenharmony_ci pwm_put(par->pwm); 78662306a36Sopenharmony_ci if (par->vbat_reg) 78762306a36Sopenharmony_ci regulator_disable(par->vbat_reg); 78862306a36Sopenharmony_ci fb_deferred_io_cleanup(info); 78962306a36Sopenharmony_ci __free_pages(__va(info->fix.smem_start), get_order(info->fix.smem_len)); 79062306a36Sopenharmony_ci framebuffer_release(info); 79162306a36Sopenharmony_ci} 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_cistatic const struct i2c_device_id ssd1307fb_i2c_id[] = { 79462306a36Sopenharmony_ci { "ssd1305fb", 0 }, 79562306a36Sopenharmony_ci { "ssd1306fb", 0 }, 79662306a36Sopenharmony_ci { "ssd1307fb", 0 }, 79762306a36Sopenharmony_ci { "ssd1309fb", 0 }, 79862306a36Sopenharmony_ci { } 79962306a36Sopenharmony_ci}; 80062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_cistatic struct i2c_driver ssd1307fb_driver = { 80362306a36Sopenharmony_ci .probe = ssd1307fb_probe, 80462306a36Sopenharmony_ci .remove = ssd1307fb_remove, 80562306a36Sopenharmony_ci .id_table = ssd1307fb_i2c_id, 80662306a36Sopenharmony_ci .driver = { 80762306a36Sopenharmony_ci .name = "ssd1307fb", 80862306a36Sopenharmony_ci .of_match_table = ssd1307fb_of_match, 80962306a36Sopenharmony_ci }, 81062306a36Sopenharmony_ci}; 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_cimodule_i2c_driver(ssd1307fb_driver); 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ciMODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller"); 81562306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 81662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 817