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