18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for the Solomon SSD1307 OLED controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2012 Free Electrons 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/backlight.h> 98c2ecf20Sopenharmony_ci#include <linux/delay.h> 108c2ecf20Sopenharmony_ci#include <linux/fb.h> 118c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 128c2ecf20Sopenharmony_ci#include <linux/i2c.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/property.h> 168c2ecf20Sopenharmony_ci#include <linux/pwm.h> 178c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 188c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define SSD1307FB_DATA 0x40 218c2ecf20Sopenharmony_ci#define SSD1307FB_COMMAND 0x80 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE 0x20 248c2ecf20Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00) 258c2ecf20Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01) 268c2ecf20Sopenharmony_ci#define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02) 278c2ecf20Sopenharmony_ci#define SSD1307FB_SET_COL_RANGE 0x21 288c2ecf20Sopenharmony_ci#define SSD1307FB_SET_PAGE_RANGE 0x22 298c2ecf20Sopenharmony_ci#define SSD1307FB_CONTRAST 0x81 308c2ecf20Sopenharmony_ci#define SSD1307FB_SET_LOOKUP_TABLE 0x91 318c2ecf20Sopenharmony_ci#define SSD1307FB_CHARGE_PUMP 0x8d 328c2ecf20Sopenharmony_ci#define SSD1307FB_SEG_REMAP_ON 0xa1 338c2ecf20Sopenharmony_ci#define SSD1307FB_DISPLAY_OFF 0xae 348c2ecf20Sopenharmony_ci#define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 358c2ecf20Sopenharmony_ci#define SSD1307FB_DISPLAY_ON 0xaf 368c2ecf20Sopenharmony_ci#define SSD1307FB_START_PAGE_ADDRESS 0xb0 378c2ecf20Sopenharmony_ci#define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 388c2ecf20Sopenharmony_ci#define SSD1307FB_SET_CLOCK_FREQ 0xd5 398c2ecf20Sopenharmony_ci#define SSD1307FB_SET_AREA_COLOR_MODE 0xd8 408c2ecf20Sopenharmony_ci#define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 418c2ecf20Sopenharmony_ci#define SSD1307FB_SET_COM_PINS_CONFIG 0xda 428c2ecf20Sopenharmony_ci#define SSD1307FB_SET_VCOMH 0xdb 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define MAX_CONTRAST 255 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define REFRESHRATE 1 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic u_int refreshrate = REFRESHRATE; 498c2ecf20Sopenharmony_cimodule_param(refreshrate, uint, 0); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistruct ssd1307fb_deviceinfo { 528c2ecf20Sopenharmony_ci u32 default_vcomh; 538c2ecf20Sopenharmony_ci u32 default_dclk_div; 548c2ecf20Sopenharmony_ci u32 default_dclk_frq; 558c2ecf20Sopenharmony_ci int need_pwm; 568c2ecf20Sopenharmony_ci int need_chargepump; 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistruct ssd1307fb_par { 608c2ecf20Sopenharmony_ci unsigned area_color_enable : 1; 618c2ecf20Sopenharmony_ci unsigned com_invdir : 1; 628c2ecf20Sopenharmony_ci unsigned com_lrremap : 1; 638c2ecf20Sopenharmony_ci unsigned com_seq : 1; 648c2ecf20Sopenharmony_ci unsigned lookup_table_set : 1; 658c2ecf20Sopenharmony_ci unsigned low_power : 1; 668c2ecf20Sopenharmony_ci unsigned seg_remap : 1; 678c2ecf20Sopenharmony_ci u32 com_offset; 688c2ecf20Sopenharmony_ci u32 contrast; 698c2ecf20Sopenharmony_ci u32 dclk_div; 708c2ecf20Sopenharmony_ci u32 dclk_frq; 718c2ecf20Sopenharmony_ci const struct ssd1307fb_deviceinfo *device_info; 728c2ecf20Sopenharmony_ci struct i2c_client *client; 738c2ecf20Sopenharmony_ci u32 height; 748c2ecf20Sopenharmony_ci struct fb_info *info; 758c2ecf20Sopenharmony_ci u8 lookup_table[4]; 768c2ecf20Sopenharmony_ci u32 page_offset; 778c2ecf20Sopenharmony_ci u32 col_offset; 788c2ecf20Sopenharmony_ci u32 prechargep1; 798c2ecf20Sopenharmony_ci u32 prechargep2; 808c2ecf20Sopenharmony_ci struct pwm_device *pwm; 818c2ecf20Sopenharmony_ci struct gpio_desc *reset; 828c2ecf20Sopenharmony_ci struct regulator *vbat_reg; 838c2ecf20Sopenharmony_ci u32 vcomh; 848c2ecf20Sopenharmony_ci u32 width; 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistruct ssd1307fb_array { 888c2ecf20Sopenharmony_ci u8 type; 898c2ecf20Sopenharmony_ci u8 data[]; 908c2ecf20Sopenharmony_ci}; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic const struct fb_fix_screeninfo ssd1307fb_fix = { 938c2ecf20Sopenharmony_ci .id = "Solomon SSD1307", 948c2ecf20Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 958c2ecf20Sopenharmony_ci .visual = FB_VISUAL_MONO10, 968c2ecf20Sopenharmony_ci .xpanstep = 0, 978c2ecf20Sopenharmony_ci .ypanstep = 0, 988c2ecf20Sopenharmony_ci .ywrapstep = 0, 998c2ecf20Sopenharmony_ci .accel = FB_ACCEL_NONE, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic const struct fb_var_screeninfo ssd1307fb_var = { 1038c2ecf20Sopenharmony_ci .bits_per_pixel = 1, 1048c2ecf20Sopenharmony_ci .red = { .length = 1 }, 1058c2ecf20Sopenharmony_ci .green = { .length = 1 }, 1068c2ecf20Sopenharmony_ci .blue = { .length = 1 }, 1078c2ecf20Sopenharmony_ci}; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci struct ssd1307fb_array *array; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL); 1148c2ecf20Sopenharmony_ci if (!array) 1158c2ecf20Sopenharmony_ci return NULL; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci array->type = type; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci return array; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic int ssd1307fb_write_array(struct i2c_client *client, 1238c2ecf20Sopenharmony_ci struct ssd1307fb_array *array, u32 len) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci int ret; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci len += sizeof(struct ssd1307fb_array); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci ret = i2c_master_send(client, (u8 *)array, len); 1308c2ecf20Sopenharmony_ci if (ret != len) { 1318c2ecf20Sopenharmony_ci dev_err(&client->dev, "Couldn't send I2C command.\n"); 1328c2ecf20Sopenharmony_ci return ret; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return 0; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci struct ssd1307fb_array *array; 1418c2ecf20Sopenharmony_ci int ret; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND); 1448c2ecf20Sopenharmony_ci if (!array) 1458c2ecf20Sopenharmony_ci return -ENOMEM; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci array->data[0] = cmd; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci ret = ssd1307fb_write_array(client, array, 1); 1508c2ecf20Sopenharmony_ci kfree(array); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci return ret; 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic void ssd1307fb_update_display(struct ssd1307fb_par *par) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci struct ssd1307fb_array *array; 1588c2ecf20Sopenharmony_ci u8 *vmem = par->info->screen_buffer; 1598c2ecf20Sopenharmony_ci unsigned int line_length = par->info->fix.line_length; 1608c2ecf20Sopenharmony_ci unsigned int pages = DIV_ROUND_UP(par->height, 8); 1618c2ecf20Sopenharmony_ci int i, j, k; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci array = ssd1307fb_alloc_array(par->width * pages, SSD1307FB_DATA); 1648c2ecf20Sopenharmony_ci if (!array) 1658c2ecf20Sopenharmony_ci return; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci /* 1688c2ecf20Sopenharmony_ci * The screen is divided in pages, each having a height of 8 1698c2ecf20Sopenharmony_ci * pixels, and the width of the screen. When sending a byte of 1708c2ecf20Sopenharmony_ci * data to the controller, it gives the 8 bits for the current 1718c2ecf20Sopenharmony_ci * column. I.e, the first byte are the 8 bits of the first 1728c2ecf20Sopenharmony_ci * column, then the 8 bits for the second column, etc. 1738c2ecf20Sopenharmony_ci * 1748c2ecf20Sopenharmony_ci * 1758c2ecf20Sopenharmony_ci * Representation of the screen, assuming it is 5 bits 1768c2ecf20Sopenharmony_ci * wide. Each letter-number combination is a bit that controls 1778c2ecf20Sopenharmony_ci * one pixel. 1788c2ecf20Sopenharmony_ci * 1798c2ecf20Sopenharmony_ci * A0 A1 A2 A3 A4 1808c2ecf20Sopenharmony_ci * B0 B1 B2 B3 B4 1818c2ecf20Sopenharmony_ci * C0 C1 C2 C3 C4 1828c2ecf20Sopenharmony_ci * D0 D1 D2 D3 D4 1838c2ecf20Sopenharmony_ci * E0 E1 E2 E3 E4 1848c2ecf20Sopenharmony_ci * F0 F1 F2 F3 F4 1858c2ecf20Sopenharmony_ci * G0 G1 G2 G3 G4 1868c2ecf20Sopenharmony_ci * H0 H1 H2 H3 H4 1878c2ecf20Sopenharmony_ci * 1888c2ecf20Sopenharmony_ci * If you want to update this screen, you need to send 5 bytes: 1898c2ecf20Sopenharmony_ci * (1) A0 B0 C0 D0 E0 F0 G0 H0 1908c2ecf20Sopenharmony_ci * (2) A1 B1 C1 D1 E1 F1 G1 H1 1918c2ecf20Sopenharmony_ci * (3) A2 B2 C2 D2 E2 F2 G2 H2 1928c2ecf20Sopenharmony_ci * (4) A3 B3 C3 D3 E3 F3 G3 H3 1938c2ecf20Sopenharmony_ci * (5) A4 B4 C4 D4 E4 F4 G4 H4 1948c2ecf20Sopenharmony_ci */ 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci for (i = 0; i < pages; i++) { 1978c2ecf20Sopenharmony_ci for (j = 0; j < par->width; j++) { 1988c2ecf20Sopenharmony_ci int m = 8; 1998c2ecf20Sopenharmony_ci u32 array_idx = i * par->width + j; 2008c2ecf20Sopenharmony_ci array->data[array_idx] = 0; 2018c2ecf20Sopenharmony_ci /* Last page may be partial */ 2028c2ecf20Sopenharmony_ci if (i + 1 == pages && par->height % 8) 2038c2ecf20Sopenharmony_ci m = par->height % 8; 2048c2ecf20Sopenharmony_ci for (k = 0; k < m; k++) { 2058c2ecf20Sopenharmony_ci u8 byte = vmem[(8 * i + k) * line_length + 2068c2ecf20Sopenharmony_ci j / 8]; 2078c2ecf20Sopenharmony_ci u8 bit = (byte >> (j % 8)) & 1; 2088c2ecf20Sopenharmony_ci array->data[array_idx] |= bit << k; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci ssd1307fb_write_array(par->client, array, par->width * pages); 2148c2ecf20Sopenharmony_ci kfree(array); 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cistatic ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf, 2198c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = info->par; 2228c2ecf20Sopenharmony_ci unsigned long total_size; 2238c2ecf20Sopenharmony_ci unsigned long p = *ppos; 2248c2ecf20Sopenharmony_ci void *dst; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci total_size = info->fix.smem_len; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (p > total_size) 2298c2ecf20Sopenharmony_ci return -EINVAL; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci if (count + p > total_size) 2328c2ecf20Sopenharmony_ci count = total_size - p; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci if (!count) 2358c2ecf20Sopenharmony_ci return -EINVAL; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci dst = info->screen_buffer + p; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci if (copy_from_user(dst, buf, count)) 2408c2ecf20Sopenharmony_ci return -EFAULT; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci ssd1307fb_update_display(par); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci *ppos += count; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci return count; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic int ssd1307fb_blank(int blank_mode, struct fb_info *info) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = info->par; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci if (blank_mode != FB_BLANK_UNBLANK) 2548c2ecf20Sopenharmony_ci return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF); 2558c2ecf20Sopenharmony_ci else 2568c2ecf20Sopenharmony_ci return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = info->par; 2628c2ecf20Sopenharmony_ci sys_fillrect(info, rect); 2638c2ecf20Sopenharmony_ci ssd1307fb_update_display(par); 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = info->par; 2698c2ecf20Sopenharmony_ci sys_copyarea(info, area); 2708c2ecf20Sopenharmony_ci ssd1307fb_update_display(par); 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) 2748c2ecf20Sopenharmony_ci{ 2758c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = info->par; 2768c2ecf20Sopenharmony_ci sys_imageblit(info, image); 2778c2ecf20Sopenharmony_ci ssd1307fb_update_display(par); 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic const struct fb_ops ssd1307fb_ops = { 2818c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2828c2ecf20Sopenharmony_ci .fb_read = fb_sys_read, 2838c2ecf20Sopenharmony_ci .fb_write = ssd1307fb_write, 2848c2ecf20Sopenharmony_ci .fb_blank = ssd1307fb_blank, 2858c2ecf20Sopenharmony_ci .fb_fillrect = ssd1307fb_fillrect, 2868c2ecf20Sopenharmony_ci .fb_copyarea = ssd1307fb_copyarea, 2878c2ecf20Sopenharmony_ci .fb_imageblit = ssd1307fb_imageblit, 2888c2ecf20Sopenharmony_ci}; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cistatic void ssd1307fb_deferred_io(struct fb_info *info, 2918c2ecf20Sopenharmony_ci struct list_head *pagelist) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci ssd1307fb_update_display(info->par); 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cistatic int ssd1307fb_init(struct ssd1307fb_par *par) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct pwm_state pwmstate; 2998c2ecf20Sopenharmony_ci int ret; 3008c2ecf20Sopenharmony_ci u32 precharge, dclk, com_invdir, compins; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci if (par->device_info->need_pwm) { 3038c2ecf20Sopenharmony_ci par->pwm = pwm_get(&par->client->dev, NULL); 3048c2ecf20Sopenharmony_ci if (IS_ERR(par->pwm)) { 3058c2ecf20Sopenharmony_ci dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); 3068c2ecf20Sopenharmony_ci return PTR_ERR(par->pwm); 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci pwm_init_state(par->pwm, &pwmstate); 3108c2ecf20Sopenharmony_ci pwm_set_relative_duty_cycle(&pwmstate, 50, 100); 3118c2ecf20Sopenharmony_ci pwm_apply_state(par->pwm, &pwmstate); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci /* Enable the PWM */ 3148c2ecf20Sopenharmony_ci pwm_enable(par->pwm); 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci dev_dbg(&par->client->dev, "Using PWM%d with a %lluns period.\n", 3178c2ecf20Sopenharmony_ci par->pwm->pwm, pwm_get_period(par->pwm)); 3188c2ecf20Sopenharmony_ci } 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci /* Set initial contrast */ 3218c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); 3228c2ecf20Sopenharmony_ci if (ret < 0) 3238c2ecf20Sopenharmony_ci return ret; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->contrast); 3268c2ecf20Sopenharmony_ci if (ret < 0) 3278c2ecf20Sopenharmony_ci return ret; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci /* Set segment re-map */ 3308c2ecf20Sopenharmony_ci if (par->seg_remap) { 3318c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); 3328c2ecf20Sopenharmony_ci if (ret < 0) 3338c2ecf20Sopenharmony_ci return ret; 3348c2ecf20Sopenharmony_ci } 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci /* Set COM direction */ 3378c2ecf20Sopenharmony_ci com_invdir = 0xc0 | par->com_invdir << 3; 3388c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, com_invdir); 3398c2ecf20Sopenharmony_ci if (ret < 0) 3408c2ecf20Sopenharmony_ci return ret; 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci /* Set multiplex ratio value */ 3438c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); 3448c2ecf20Sopenharmony_ci if (ret < 0) 3458c2ecf20Sopenharmony_ci return ret; 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->height - 1); 3488c2ecf20Sopenharmony_ci if (ret < 0) 3498c2ecf20Sopenharmony_ci return ret; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci /* set display offset value */ 3528c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET); 3538c2ecf20Sopenharmony_ci if (ret < 0) 3548c2ecf20Sopenharmony_ci return ret; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->com_offset); 3578c2ecf20Sopenharmony_ci if (ret < 0) 3588c2ecf20Sopenharmony_ci return ret; 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci /* Set clock frequency */ 3618c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ); 3628c2ecf20Sopenharmony_ci if (ret < 0) 3638c2ecf20Sopenharmony_ci return ret; 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4; 3668c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, dclk); 3678c2ecf20Sopenharmony_ci if (ret < 0) 3688c2ecf20Sopenharmony_ci return ret; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci /* Set Set Area Color Mode ON/OFF & Low Power Display Mode */ 3718c2ecf20Sopenharmony_ci if (par->area_color_enable || par->low_power) { 3728c2ecf20Sopenharmony_ci u32 mode; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 3758c2ecf20Sopenharmony_ci SSD1307FB_SET_AREA_COLOR_MODE); 3768c2ecf20Sopenharmony_ci if (ret < 0) 3778c2ecf20Sopenharmony_ci return ret; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci mode = (par->area_color_enable ? 0x30 : 0) | 3808c2ecf20Sopenharmony_ci (par->low_power ? 5 : 0); 3818c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, mode); 3828c2ecf20Sopenharmony_ci if (ret < 0) 3838c2ecf20Sopenharmony_ci return ret; 3848c2ecf20Sopenharmony_ci } 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci /* Set precharge period in number of ticks from the internal clock */ 3878c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD); 3888c2ecf20Sopenharmony_ci if (ret < 0) 3898c2ecf20Sopenharmony_ci return ret; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4; 3928c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, precharge); 3938c2ecf20Sopenharmony_ci if (ret < 0) 3948c2ecf20Sopenharmony_ci return ret; 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci /* Set COM pins configuration */ 3978c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG); 3988c2ecf20Sopenharmony_ci if (ret < 0) 3998c2ecf20Sopenharmony_ci return ret; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci compins = 0x02 | !par->com_seq << 4 | par->com_lrremap << 5; 4028c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, compins); 4038c2ecf20Sopenharmony_ci if (ret < 0) 4048c2ecf20Sopenharmony_ci return ret; 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci /* Set VCOMH */ 4078c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH); 4088c2ecf20Sopenharmony_ci if (ret < 0) 4098c2ecf20Sopenharmony_ci return ret; 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->vcomh); 4128c2ecf20Sopenharmony_ci if (ret < 0) 4138c2ecf20Sopenharmony_ci return ret; 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci /* Turn on the DC-DC Charge Pump */ 4168c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP); 4178c2ecf20Sopenharmony_ci if (ret < 0) 4188c2ecf20Sopenharmony_ci return ret; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 4218c2ecf20Sopenharmony_ci BIT(4) | (par->device_info->need_chargepump ? BIT(2) : 0)); 4228c2ecf20Sopenharmony_ci if (ret < 0) 4238c2ecf20Sopenharmony_ci return ret; 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci /* Set lookup table */ 4268c2ecf20Sopenharmony_ci if (par->lookup_table_set) { 4278c2ecf20Sopenharmony_ci int i; 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 4308c2ecf20Sopenharmony_ci SSD1307FB_SET_LOOKUP_TABLE); 4318c2ecf20Sopenharmony_ci if (ret < 0) 4328c2ecf20Sopenharmony_ci return ret; 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(par->lookup_table); ++i) { 4358c2ecf20Sopenharmony_ci u8 val = par->lookup_table[i]; 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci if (val < 31 || val > 63) 4388c2ecf20Sopenharmony_ci dev_warn(&par->client->dev, 4398c2ecf20Sopenharmony_ci "lookup table index %d value out of range 31 <= %d <= 63\n", 4408c2ecf20Sopenharmony_ci i, val); 4418c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, val); 4428c2ecf20Sopenharmony_ci if (ret < 0) 4438c2ecf20Sopenharmony_ci return ret; 4448c2ecf20Sopenharmony_ci } 4458c2ecf20Sopenharmony_ci } 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci /* Switch to horizontal addressing mode */ 4488c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); 4498c2ecf20Sopenharmony_ci if (ret < 0) 4508c2ecf20Sopenharmony_ci return ret; 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 4538c2ecf20Sopenharmony_ci SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); 4548c2ecf20Sopenharmony_ci if (ret < 0) 4558c2ecf20Sopenharmony_ci return ret; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci /* Set column range */ 4588c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); 4598c2ecf20Sopenharmony_ci if (ret < 0) 4608c2ecf20Sopenharmony_ci return ret; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->col_offset); 4638c2ecf20Sopenharmony_ci if (ret < 0) 4648c2ecf20Sopenharmony_ci return ret; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->col_offset + par->width - 1); 4678c2ecf20Sopenharmony_ci if (ret < 0) 4688c2ecf20Sopenharmony_ci return ret; 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci /* Set page range */ 4718c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); 4728c2ecf20Sopenharmony_ci if (ret < 0) 4738c2ecf20Sopenharmony_ci return ret; 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->page_offset); 4768c2ecf20Sopenharmony_ci if (ret < 0) 4778c2ecf20Sopenharmony_ci return ret; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, 4808c2ecf20Sopenharmony_ci par->page_offset + 4818c2ecf20Sopenharmony_ci DIV_ROUND_UP(par->height, 8) - 1); 4828c2ecf20Sopenharmony_ci if (ret < 0) 4838c2ecf20Sopenharmony_ci return ret; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci /* Clear the screen */ 4868c2ecf20Sopenharmony_ci ssd1307fb_update_display(par); 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci /* Turn on the display */ 4898c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); 4908c2ecf20Sopenharmony_ci if (ret < 0) 4918c2ecf20Sopenharmony_ci return ret; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci return 0; 4948c2ecf20Sopenharmony_ci} 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_cistatic int ssd1307fb_update_bl(struct backlight_device *bdev) 4978c2ecf20Sopenharmony_ci{ 4988c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = bl_get_data(bdev); 4998c2ecf20Sopenharmony_ci int ret; 5008c2ecf20Sopenharmony_ci int brightness = bdev->props.brightness; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci par->contrast = brightness; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); 5058c2ecf20Sopenharmony_ci if (ret < 0) 5068c2ecf20Sopenharmony_ci return ret; 5078c2ecf20Sopenharmony_ci ret = ssd1307fb_write_cmd(par->client, par->contrast); 5088c2ecf20Sopenharmony_ci if (ret < 0) 5098c2ecf20Sopenharmony_ci return ret; 5108c2ecf20Sopenharmony_ci return 0; 5118c2ecf20Sopenharmony_ci} 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_cistatic int ssd1307fb_get_brightness(struct backlight_device *bdev) 5148c2ecf20Sopenharmony_ci{ 5158c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = bl_get_data(bdev); 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci return par->contrast; 5188c2ecf20Sopenharmony_ci} 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_cistatic int ssd1307fb_check_fb(struct backlight_device *bdev, 5218c2ecf20Sopenharmony_ci struct fb_info *info) 5228c2ecf20Sopenharmony_ci{ 5238c2ecf20Sopenharmony_ci return (info->bl_dev == bdev); 5248c2ecf20Sopenharmony_ci} 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_cistatic const struct backlight_ops ssd1307fb_bl_ops = { 5278c2ecf20Sopenharmony_ci .options = BL_CORE_SUSPENDRESUME, 5288c2ecf20Sopenharmony_ci .update_status = ssd1307fb_update_bl, 5298c2ecf20Sopenharmony_ci .get_brightness = ssd1307fb_get_brightness, 5308c2ecf20Sopenharmony_ci .check_fb = ssd1307fb_check_fb, 5318c2ecf20Sopenharmony_ci}; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = { 5348c2ecf20Sopenharmony_ci .default_vcomh = 0x34, 5358c2ecf20Sopenharmony_ci .default_dclk_div = 1, 5368c2ecf20Sopenharmony_ci .default_dclk_frq = 7, 5378c2ecf20Sopenharmony_ci}; 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = { 5408c2ecf20Sopenharmony_ci .default_vcomh = 0x20, 5418c2ecf20Sopenharmony_ci .default_dclk_div = 1, 5428c2ecf20Sopenharmony_ci .default_dclk_frq = 8, 5438c2ecf20Sopenharmony_ci .need_chargepump = 1, 5448c2ecf20Sopenharmony_ci}; 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = { 5478c2ecf20Sopenharmony_ci .default_vcomh = 0x20, 5488c2ecf20Sopenharmony_ci .default_dclk_div = 2, 5498c2ecf20Sopenharmony_ci .default_dclk_frq = 12, 5508c2ecf20Sopenharmony_ci .need_pwm = 1, 5518c2ecf20Sopenharmony_ci}; 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_cistatic struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = { 5548c2ecf20Sopenharmony_ci .default_vcomh = 0x34, 5558c2ecf20Sopenharmony_ci .default_dclk_div = 1, 5568c2ecf20Sopenharmony_ci .default_dclk_frq = 10, 5578c2ecf20Sopenharmony_ci}; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_cistatic const struct of_device_id ssd1307fb_of_match[] = { 5608c2ecf20Sopenharmony_ci { 5618c2ecf20Sopenharmony_ci .compatible = "solomon,ssd1305fb-i2c", 5628c2ecf20Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1305_deviceinfo, 5638c2ecf20Sopenharmony_ci }, 5648c2ecf20Sopenharmony_ci { 5658c2ecf20Sopenharmony_ci .compatible = "solomon,ssd1306fb-i2c", 5668c2ecf20Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1306_deviceinfo, 5678c2ecf20Sopenharmony_ci }, 5688c2ecf20Sopenharmony_ci { 5698c2ecf20Sopenharmony_ci .compatible = "solomon,ssd1307fb-i2c", 5708c2ecf20Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1307_deviceinfo, 5718c2ecf20Sopenharmony_ci }, 5728c2ecf20Sopenharmony_ci { 5738c2ecf20Sopenharmony_ci .compatible = "solomon,ssd1309fb-i2c", 5748c2ecf20Sopenharmony_ci .data = (void *)&ssd1307fb_ssd1309_deviceinfo, 5758c2ecf20Sopenharmony_ci }, 5768c2ecf20Sopenharmony_ci {}, 5778c2ecf20Sopenharmony_ci}; 5788c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ssd1307fb_of_match); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_cistatic int ssd1307fb_probe(struct i2c_client *client) 5818c2ecf20Sopenharmony_ci{ 5828c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 5838c2ecf20Sopenharmony_ci struct backlight_device *bl; 5848c2ecf20Sopenharmony_ci char bl_name[12]; 5858c2ecf20Sopenharmony_ci struct fb_info *info; 5868c2ecf20Sopenharmony_ci struct fb_deferred_io *ssd1307fb_defio; 5878c2ecf20Sopenharmony_ci u32 vmem_size; 5888c2ecf20Sopenharmony_ci struct ssd1307fb_par *par; 5898c2ecf20Sopenharmony_ci void *vmem; 5908c2ecf20Sopenharmony_ci int ret; 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci info = framebuffer_alloc(sizeof(struct ssd1307fb_par), dev); 5938c2ecf20Sopenharmony_ci if (!info) 5948c2ecf20Sopenharmony_ci return -ENOMEM; 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci par = info->par; 5978c2ecf20Sopenharmony_ci par->info = info; 5988c2ecf20Sopenharmony_ci par->client = client; 5998c2ecf20Sopenharmony_ci 6008c2ecf20Sopenharmony_ci par->device_info = device_get_match_data(dev); 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci par->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 6038c2ecf20Sopenharmony_ci if (IS_ERR(par->reset)) { 6048c2ecf20Sopenharmony_ci dev_err(dev, "failed to get reset gpio: %ld\n", 6058c2ecf20Sopenharmony_ci PTR_ERR(par->reset)); 6068c2ecf20Sopenharmony_ci ret = PTR_ERR(par->reset); 6078c2ecf20Sopenharmony_ci goto fb_alloc_error; 6088c2ecf20Sopenharmony_ci } 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_ci par->vbat_reg = devm_regulator_get_optional(dev, "vbat"); 6118c2ecf20Sopenharmony_ci if (IS_ERR(par->vbat_reg)) { 6128c2ecf20Sopenharmony_ci ret = PTR_ERR(par->vbat_reg); 6138c2ecf20Sopenharmony_ci if (ret == -ENODEV) { 6148c2ecf20Sopenharmony_ci par->vbat_reg = NULL; 6158c2ecf20Sopenharmony_ci } else { 6168c2ecf20Sopenharmony_ci dev_err(dev, "failed to get VBAT regulator: %d\n", ret); 6178c2ecf20Sopenharmony_ci goto fb_alloc_error; 6188c2ecf20Sopenharmony_ci } 6198c2ecf20Sopenharmony_ci } 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,width", &par->width)) 6228c2ecf20Sopenharmony_ci par->width = 96; 6238c2ecf20Sopenharmony_ci 6248c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,height", &par->height)) 6258c2ecf20Sopenharmony_ci par->height = 16; 6268c2ecf20Sopenharmony_ci 6278c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,page-offset", &par->page_offset)) 6288c2ecf20Sopenharmony_ci par->page_offset = 1; 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,col-offset", &par->col_offset)) 6318c2ecf20Sopenharmony_ci par->col_offset = 0; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,com-offset", &par->com_offset)) 6348c2ecf20Sopenharmony_ci par->com_offset = 0; 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,prechargep1", &par->prechargep1)) 6378c2ecf20Sopenharmony_ci par->prechargep1 = 2; 6388c2ecf20Sopenharmony_ci 6398c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,prechargep2", &par->prechargep2)) 6408c2ecf20Sopenharmony_ci par->prechargep2 = 2; 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci if (!device_property_read_u8_array(dev, "solomon,lookup-table", 6438c2ecf20Sopenharmony_ci par->lookup_table, 6448c2ecf20Sopenharmony_ci ARRAY_SIZE(par->lookup_table))) 6458c2ecf20Sopenharmony_ci par->lookup_table_set = 1; 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci par->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap"); 6488c2ecf20Sopenharmony_ci par->com_seq = device_property_read_bool(dev, "solomon,com-seq"); 6498c2ecf20Sopenharmony_ci par->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap"); 6508c2ecf20Sopenharmony_ci par->com_invdir = device_property_read_bool(dev, "solomon,com-invdir"); 6518c2ecf20Sopenharmony_ci par->area_color_enable = 6528c2ecf20Sopenharmony_ci device_property_read_bool(dev, "solomon,area-color-enable"); 6538c2ecf20Sopenharmony_ci par->low_power = device_property_read_bool(dev, "solomon,low-power"); 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci par->contrast = 127; 6568c2ecf20Sopenharmony_ci par->vcomh = par->device_info->default_vcomh; 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci /* Setup display timing */ 6598c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,dclk-div", &par->dclk_div)) 6608c2ecf20Sopenharmony_ci par->dclk_div = par->device_info->default_dclk_div; 6618c2ecf20Sopenharmony_ci if (device_property_read_u32(dev, "solomon,dclk-frq", &par->dclk_frq)) 6628c2ecf20Sopenharmony_ci par->dclk_frq = par->device_info->default_dclk_frq; 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_ci vmem_size = DIV_ROUND_UP(par->width, 8) * par->height; 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_ci vmem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 6678c2ecf20Sopenharmony_ci get_order(vmem_size)); 6688c2ecf20Sopenharmony_ci if (!vmem) { 6698c2ecf20Sopenharmony_ci dev_err(dev, "Couldn't allocate graphical memory.\n"); 6708c2ecf20Sopenharmony_ci ret = -ENOMEM; 6718c2ecf20Sopenharmony_ci goto fb_alloc_error; 6728c2ecf20Sopenharmony_ci } 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci ssd1307fb_defio = devm_kzalloc(dev, sizeof(*ssd1307fb_defio), 6758c2ecf20Sopenharmony_ci GFP_KERNEL); 6768c2ecf20Sopenharmony_ci if (!ssd1307fb_defio) { 6778c2ecf20Sopenharmony_ci dev_err(dev, "Couldn't allocate deferred io.\n"); 6788c2ecf20Sopenharmony_ci ret = -ENOMEM; 6798c2ecf20Sopenharmony_ci goto fb_alloc_error; 6808c2ecf20Sopenharmony_ci } 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci ssd1307fb_defio->delay = HZ / refreshrate; 6838c2ecf20Sopenharmony_ci ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci info->fbops = &ssd1307fb_ops; 6868c2ecf20Sopenharmony_ci info->fix = ssd1307fb_fix; 6878c2ecf20Sopenharmony_ci info->fix.line_length = DIV_ROUND_UP(par->width, 8); 6888c2ecf20Sopenharmony_ci info->fbdefio = ssd1307fb_defio; 6898c2ecf20Sopenharmony_ci 6908c2ecf20Sopenharmony_ci info->var = ssd1307fb_var; 6918c2ecf20Sopenharmony_ci info->var.xres = par->width; 6928c2ecf20Sopenharmony_ci info->var.xres_virtual = par->width; 6938c2ecf20Sopenharmony_ci info->var.yres = par->height; 6948c2ecf20Sopenharmony_ci info->var.yres_virtual = par->height; 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_ci info->screen_buffer = vmem; 6978c2ecf20Sopenharmony_ci info->fix.smem_start = __pa(vmem); 6988c2ecf20Sopenharmony_ci info->fix.smem_len = vmem_size; 6998c2ecf20Sopenharmony_ci 7008c2ecf20Sopenharmony_ci fb_deferred_io_init(info); 7018c2ecf20Sopenharmony_ci 7028c2ecf20Sopenharmony_ci i2c_set_clientdata(client, info); 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_ci if (par->reset) { 7058c2ecf20Sopenharmony_ci /* Reset the screen */ 7068c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(par->reset, 1); 7078c2ecf20Sopenharmony_ci udelay(4); 7088c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(par->reset, 0); 7098c2ecf20Sopenharmony_ci udelay(4); 7108c2ecf20Sopenharmony_ci } 7118c2ecf20Sopenharmony_ci 7128c2ecf20Sopenharmony_ci if (par->vbat_reg) { 7138c2ecf20Sopenharmony_ci ret = regulator_enable(par->vbat_reg); 7148c2ecf20Sopenharmony_ci if (ret) { 7158c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable VBAT: %d\n", ret); 7168c2ecf20Sopenharmony_ci goto reset_oled_error; 7178c2ecf20Sopenharmony_ci } 7188c2ecf20Sopenharmony_ci } 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_ci ret = ssd1307fb_init(par); 7218c2ecf20Sopenharmony_ci if (ret) 7228c2ecf20Sopenharmony_ci goto regulator_enable_error; 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci ret = register_framebuffer(info); 7258c2ecf20Sopenharmony_ci if (ret) { 7268c2ecf20Sopenharmony_ci dev_err(dev, "Couldn't register the framebuffer\n"); 7278c2ecf20Sopenharmony_ci goto panel_init_error; 7288c2ecf20Sopenharmony_ci } 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci snprintf(bl_name, sizeof(bl_name), "ssd1307fb%d", info->node); 7318c2ecf20Sopenharmony_ci bl = backlight_device_register(bl_name, dev, par, &ssd1307fb_bl_ops, 7328c2ecf20Sopenharmony_ci NULL); 7338c2ecf20Sopenharmony_ci if (IS_ERR(bl)) { 7348c2ecf20Sopenharmony_ci ret = PTR_ERR(bl); 7358c2ecf20Sopenharmony_ci dev_err(dev, "unable to register backlight device: %d\n", ret); 7368c2ecf20Sopenharmony_ci goto bl_init_error; 7378c2ecf20Sopenharmony_ci } 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_ci bl->props.brightness = par->contrast; 7408c2ecf20Sopenharmony_ci bl->props.max_brightness = MAX_CONTRAST; 7418c2ecf20Sopenharmony_ci info->bl_dev = bl; 7428c2ecf20Sopenharmony_ci 7438c2ecf20Sopenharmony_ci dev_info(dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size); 7448c2ecf20Sopenharmony_ci 7458c2ecf20Sopenharmony_ci return 0; 7468c2ecf20Sopenharmony_ci 7478c2ecf20Sopenharmony_cibl_init_error: 7488c2ecf20Sopenharmony_ci unregister_framebuffer(info); 7498c2ecf20Sopenharmony_cipanel_init_error: 7508c2ecf20Sopenharmony_ci if (par->device_info->need_pwm) { 7518c2ecf20Sopenharmony_ci pwm_disable(par->pwm); 7528c2ecf20Sopenharmony_ci pwm_put(par->pwm); 7538c2ecf20Sopenharmony_ci } 7548c2ecf20Sopenharmony_ciregulator_enable_error: 7558c2ecf20Sopenharmony_ci if (par->vbat_reg) 7568c2ecf20Sopenharmony_ci regulator_disable(par->vbat_reg); 7578c2ecf20Sopenharmony_cireset_oled_error: 7588c2ecf20Sopenharmony_ci fb_deferred_io_cleanup(info); 7598c2ecf20Sopenharmony_cifb_alloc_error: 7608c2ecf20Sopenharmony_ci framebuffer_release(info); 7618c2ecf20Sopenharmony_ci return ret; 7628c2ecf20Sopenharmony_ci} 7638c2ecf20Sopenharmony_ci 7648c2ecf20Sopenharmony_cistatic int ssd1307fb_remove(struct i2c_client *client) 7658c2ecf20Sopenharmony_ci{ 7668c2ecf20Sopenharmony_ci struct fb_info *info = i2c_get_clientdata(client); 7678c2ecf20Sopenharmony_ci struct ssd1307fb_par *par = info->par; 7688c2ecf20Sopenharmony_ci 7698c2ecf20Sopenharmony_ci ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_OFF); 7708c2ecf20Sopenharmony_ci 7718c2ecf20Sopenharmony_ci backlight_device_unregister(info->bl_dev); 7728c2ecf20Sopenharmony_ci 7738c2ecf20Sopenharmony_ci unregister_framebuffer(info); 7748c2ecf20Sopenharmony_ci if (par->device_info->need_pwm) { 7758c2ecf20Sopenharmony_ci pwm_disable(par->pwm); 7768c2ecf20Sopenharmony_ci pwm_put(par->pwm); 7778c2ecf20Sopenharmony_ci } 7788c2ecf20Sopenharmony_ci if (par->vbat_reg) 7798c2ecf20Sopenharmony_ci regulator_disable(par->vbat_reg); 7808c2ecf20Sopenharmony_ci fb_deferred_io_cleanup(info); 7818c2ecf20Sopenharmony_ci __free_pages(__va(info->fix.smem_start), get_order(info->fix.smem_len)); 7828c2ecf20Sopenharmony_ci framebuffer_release(info); 7838c2ecf20Sopenharmony_ci 7848c2ecf20Sopenharmony_ci return 0; 7858c2ecf20Sopenharmony_ci} 7868c2ecf20Sopenharmony_ci 7878c2ecf20Sopenharmony_cistatic const struct i2c_device_id ssd1307fb_i2c_id[] = { 7888c2ecf20Sopenharmony_ci { "ssd1305fb", 0 }, 7898c2ecf20Sopenharmony_ci { "ssd1306fb", 0 }, 7908c2ecf20Sopenharmony_ci { "ssd1307fb", 0 }, 7918c2ecf20Sopenharmony_ci { "ssd1309fb", 0 }, 7928c2ecf20Sopenharmony_ci { } 7938c2ecf20Sopenharmony_ci}; 7948c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); 7958c2ecf20Sopenharmony_ci 7968c2ecf20Sopenharmony_cistatic struct i2c_driver ssd1307fb_driver = { 7978c2ecf20Sopenharmony_ci .probe_new = ssd1307fb_probe, 7988c2ecf20Sopenharmony_ci .remove = ssd1307fb_remove, 7998c2ecf20Sopenharmony_ci .id_table = ssd1307fb_i2c_id, 8008c2ecf20Sopenharmony_ci .driver = { 8018c2ecf20Sopenharmony_ci .name = "ssd1307fb", 8028c2ecf20Sopenharmony_ci .of_match_table = ssd1307fb_of_match, 8038c2ecf20Sopenharmony_ci }, 8048c2ecf20Sopenharmony_ci}; 8058c2ecf20Sopenharmony_ci 8068c2ecf20Sopenharmony_cimodule_i2c_driver(ssd1307fb_driver); 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controller"); 8098c2ecf20Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 8108c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 811