brintos

brintos / linux-shallow public Read only

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