// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for the Solomon SSD1307 OLED controller
*
* Copyright 2012 Free Electrons
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/regulator/consumer.h>
#define SSD1307FB_DATA 0x40
#define SSD1307FB_COMMAND 0x80
#define SSD1307FB_SET_ADDRESS_MODE 0x20
#define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00)
#define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01)
#define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02)
#define SSD1307FB_SET_COL_RANGE 0x21
#define SSD1307FB_SET_PAGE_RANGE 0x22
#define SSD1307FB_CONTRAST 0x81
#define SSD1307FB_SET_LOOKUP_TABLE 0x91
#define SSD1307FB_CHARGE_PUMP 0x8d
#define SSD1307FB_SEG_REMAP_ON 0xa1
#define SSD1307FB_DISPLAY_OFF 0xae
#define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8
#define SSD1307FB_DISPLAY_ON 0xaf
#define SSD1307FB_START_PAGE_ADDRESS 0xb0
#define SSD1307FB_SET_DISPLAY_OFFSET 0xd3
#define SSD1307FB_SET_CLOCK_FREQ 0xd5
#define SSD1307FB_SET_AREA_COLOR_MODE 0xd8
#define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9
#define SSD1307FB_SET_COM_PINS_CONFIG 0xda
#define SSD1307FB_SET_VCOMH 0xdb
#define MAX_CONTRAST 255
#define REFRESHRATE 1
static u_int refreshrate = REFRESHRATE;
module_param(refreshrate, uint, 0);
struct ssd1307fb_deviceinfo {
u32 default_vcomh;
u32 default_dclk_div;
u32 default_dclk_frq;
bool need_pwm;
bool need_chargepump;
};
struct ssd1307fb_par {
unsigned area_color_enable : 1;
unsigned com_invdir : 1;
unsigned com_lrremap : 1;
unsigned com_seq : 1;
unsigned lookup_table_set : 1;
unsigned low_power : 1;
unsigned seg_remap : 1;
u32 com_offset;
u32 contrast;
u32 dclk_div;
u32 dclk_frq;
const struct ssd1307fb_deviceinfo *device_info;
struct i2c_client *client;
u32 height;
struct fb_info *info;
/* Pending damage, with exclusive x2/y2, protected by damage_lock. */
spinlock_t damage_lock;
bool damage_pending;
u32 damage_x1;
u32 damage_x2;
u32 damage_y1;
u32 damage_y2;
u8 lookup_table[4];
u32 page_offset;
u32 col_offset;
u32 prechargep1;
u32 prechargep2;
struct pwm_device *pwm;
struct gpio_desc *reset;
struct regulator *vbat_reg;
u32 vcomh;
u32 width;
/* Cached address ranges */
u8 col_start;
u8 col_end;
u8 page_start;
u8 page_end;
};
struct ssd1307fb_array {
u8 type;
u8 data[];
};
static const struct fb_fix_screeninfo ssd1307fb_fix = {
.id = "Solomon SSD1307",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_MONO10,
.xpanstep = 0,
.ypanstep = 0,
.ywrapstep = 0,
.accel = FB_ACCEL_NONE,
};
static const struct fb_var_screeninfo ssd1307fb_var = {
.bits_per_pixel = 1,
.red = { .length = 1 },
.green = { .length = 1 },
.blue = { .length = 1 },
};
static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type)
{
struct ssd1307fb_array *array;
array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL);
if (!array)
return NULL;
array->type = type;
return array;
}
static int ssd1307fb_write_array(struct i2c_client *client,
struct ssd1307fb_array *array, u32 len)
{
int ret;
len += sizeof(struct ssd1307fb_array);
ret = i2c_master_send(client, (u8 *)array, len);
if (ret != len) {
dev_err(&client->dev, "Couldn't send I2C command.\n");
return ret;
}
return 0;
}
static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd)
{
struct ssd1307fb_array *array;
int ret;
array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND);
if (!array)
return -ENOMEM;
array->data[0] = cmd;
ret = ssd1307fb_write_array(client, array, 1);
kfree(array);
return ret;
}
static int ssd1307fb_set_col_range(struct ssd1307fb_par *par, u8 col_start,
u8 cols)
{
u8 col_end = col_start + cols - 1;
int ret;
if (col_start == par->col_start && col_end == par->col_end)
return 0;
ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE);
if (ret < 0)
return ret;
ret = ssd1307fb_write_cmd(par->client, col_start);
if (ret < 0)
return ret;
ret = ssd1307fb_write_cmd(par->client, col_end);
if (ret < 0)
return ret;
par->col_start = col_start;
par->col_end =