2019-05-20 09:19:12 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2012-12-07 12:30:38 -08:00
/*
2013-04-09 02:06:50 +09:00
* Driver for the Solomon SSD1307 OLED controller
2012-12-07 12:30:38 -08:00
*
* Copyright 2012 Free Electrons
*/
2015-03-31 20:27:15 +02:00
# include <linux/backlight.h>
2015-09-08 21:19:50 +02:00
# include <linux/delay.h>
2012-12-07 12:30:38 -08:00
# include <linux/fb.h>
2017-02-20 13:51:07 +01:00
# include <linux/gpio/consumer.h>
2015-09-08 21:19:50 +02:00
# include <linux/i2c.h>
# include <linux/kernel.h>
# include <linux/module.h>
2020-03-24 19:05:30 +02:00
# include <linux/property.h>
2012-12-07 12:30:38 -08:00
# include <linux/pwm.h>
2015-09-08 21:19:50 +02:00
# include <linux/uaccess.h>
2017-02-08 16:43:59 +01:00
# include <linux/regulator/consumer.h>
2012-12-07 12:30:38 -08:00
# define SSD1307FB_DATA 0x40
# define SSD1307FB_COMMAND 0x80
2013-04-22 12:02:25 +02:00
# 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
2012-12-07 12:30:38 -08:00
# define SSD1307FB_CONTRAST 0x81
2019-06-18 10:41:11 +03:00
# define SSD1307FB_SET_LOOKUP_TABLE 0x91
2013-04-22 11:55:54 +02:00
# define SSD1307FB_CHARGE_PUMP 0x8d
2012-12-07 12:30:38 -08:00
# define SSD1307FB_SEG_REMAP_ON 0xa1
# define SSD1307FB_DISPLAY_OFF 0xae
2013-04-22 11:55:54 +02:00
# define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8
2012-12-07 12:30:38 -08:00
# define SSD1307FB_DISPLAY_ON 0xaf
# define SSD1307FB_START_PAGE_ADDRESS 0xb0
2013-04-22 11:55:54 +02:00
# define SSD1307FB_SET_DISPLAY_OFFSET 0xd3
# define SSD1307FB_SET_CLOCK_FREQ 0xd5
2019-06-18 10:41:11 +03:00
# define SSD1307FB_SET_AREA_COLOR_MODE 0xd8
2013-04-22 11:55:54 +02:00
# define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9
# define SSD1307FB_SET_COM_PINS_CONFIG 0xda
# define SSD1307FB_SET_VCOMH 0xdb
2015-03-31 20:27:15 +02:00
# define MAX_CONTRAST 255
2015-03-31 20:27:13 +02:00
# define REFRESHRATE 1
static u_int refreshrate = REFRESHRATE ;
module_param ( refreshrate , uint , 0 ) ;
2015-03-31 20:27:10 +02:00
struct ssd1307fb_deviceinfo {
u32 default_vcomh ;
u32 default_dclk_div ;
u32 default_dclk_frq ;
2023-08-31 14:05:54 +02:00
bool need_pwm ;
bool need_chargepump ;
2013-04-22 11:55:54 +02:00
} ;
2012-12-07 12:30:38 -08:00
struct ssd1307fb_par {
2019-06-18 10:41:11 +03:00
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 ;
2015-03-31 20:27:10 +02:00
u32 com_offset ;
u32 contrast ;
u32 dclk_div ;
u32 dclk_frq ;
2016-08-16 11:27:17 +02:00
const struct ssd1307fb_deviceinfo * device_info ;
2012-12-07 12:30:38 -08:00
struct i2c_client * client ;
2013-04-22 11:55:54 +02:00
u32 height ;
2012-12-07 12:30:38 -08:00
struct fb_info * info ;
2019-06-18 10:41:11 +03:00
u8 lookup_table [ 4 ] ;
2013-04-22 11:55:54 +02:00
u32 page_offset ;
2020-07-24 17:22:18 -03:00
u32 col_offset ;
2015-03-31 20:27:10 +02:00
u32 prechargep1 ;
u32 prechargep2 ;
2012-12-07 12:30:38 -08:00
struct pwm_device * pwm ;
2017-02-08 16:43:59 +01:00
struct gpio_desc * reset ;
2017-02-08 16:43:59 +01:00
struct regulator * vbat_reg ;
2015-03-31 20:27:10 +02:00
u32 vcomh ;
2013-04-22 11:55:54 +02:00
u32 width ;
2021-07-27 15:47:30 +02:00
/* Cached address ranges */
u8 col_start ;
u8 col_end ;
u8 page_start ;
u8 page_end ;
2012-12-07 12:30:38 -08:00
} ;
2013-04-22 12:02:23 +02:00
struct ssd1307fb_array {
u8 type ;
2020-02-21 10:00:05 -06:00
u8 data [ ] ;
2013-04-22 12:02:23 +02:00
} ;
2016-09-11 17:17:20 +02:00
static const struct fb_fix_screeninfo ssd1307fb_fix = {
2012-12-07 12:30:38 -08:00
. id = " Solomon SSD1307 " ,
. type = FB_TYPE_PACKED_PIXELS ,
. visual = FB_VISUAL_MONO10 ,
. xpanstep = 0 ,
. ypanstep = 0 ,
. ywrapstep = 0 ,
. accel = FB_ACCEL_NONE ,
} ;
2016-09-11 17:17:20 +02:00
static const struct fb_var_screeninfo ssd1307fb_var = {
2012-12-07 12:30:38 -08:00
. bits_per_pixel = 1 ,
2019-06-18 10:41:11 +03:00
. red = { . length = 1 } ,
. green = { . length = 1 } ,
. blue = { . length = 1 } ,
2012-12-07 12:30:38 -08:00
} ;
2013-04-22 12:02:23 +02:00
static struct ssd1307fb_array * ssd1307fb_alloc_array ( u32 len , u8 type )
2012-12-07 12:30:38 -08:00
{
2013-04-22 12:02:23 +02:00
struct ssd1307fb_array * array ;
2012-12-07 12:30:38 -08:00
2013-04-22 12:02:23 +02:00
array = kzalloc ( sizeof ( struct ssd1307fb_array ) + len , GFP_KERNEL ) ;
if ( ! array )
return NULL ;
2012-12-07 12:30:38 -08:00
2013-04-22 12:02:23 +02:00
array - > type = type ;
2012-12-07 12:30:38 -08:00
2013-04-22 12:02:23 +02:00
return array ;
2012-12-07 12:30:38 -08:00
}
2013-04-22 12:02:23 +02:00
static int ssd1307fb_write_array ( struct i2c_client * client ,
struct ssd1307fb_array * array , u32 len )
2012-12-07 12:30:38 -08:00
{
2013-04-22 12:02:23 +02:00
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 ;
2012-12-07 12:30:38 -08:00
}
static inline int ssd1307fb_write_cmd ( struct i2c_client * client , u8 cmd )
{
2013-04-22 12:02:23 +02:00
struct ssd1307fb_array * array ;
int ret ;
2012-12-07 12:30:38 -08:00
2013-04-22 12:02:23 +02:00
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 ;
2012-12-07 12:30:38 -08:00
}
2021-07-27 15:47:28 +02:00
static int ssd1307fb_set_col_range ( struct ssd1307fb_par * par , u8 col_start ,
u8 cols )
{
u8 col_end = col_start + cols - 1 ;
int ret ;
2021-07-27 15:47:30 +02:00
if ( col_start = = par - > col_start & & col_end = = par - > col_end )
return 0 ;
2021-07-27 15:47:28 +02:00
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 ;
2021-07-27 15:47:30 +02:00
ret = ssd1307fb_write_cmd ( par - > client , col_end ) ;
if ( ret < 0 )
return ret ;
par - > col_start = col_start ;
par - > col_end = col_end ;
return 0 ;
2021-07-27 15:47:28 +02:00
}
static int ssd1307fb_set_page_range ( struct ssd1307fb_par * par , u8 page_start ,
u8 pages )
{
u8 page_end = page_start + pages - 1 ;
int ret ;
2021-07-27 15:47:30 +02:00
if ( page_start = = par - > page_start & & page_end = = par - > page_end )
return 0 ;
2021-07-27 15:47:28 +02:00
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_PAGE_RANGE ) ;
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , page_start ) ;
if ( ret < 0 )
return ret ;
2021-07-27 15:47:30 +02:00
ret = ssd1307fb_write_cmd ( par - > client , page_end ) ;
if ( ret < 0 )
return ret ;
par - > page_start = page_start ;
par - > page_end = page_end ;
return 0 ;
2021-07-27 15:47:28 +02:00
}
2021-07-27 15:47:29 +02:00
static int ssd1307fb_update_rect ( struct ssd1307fb_par * par , unsigned int x ,
unsigned int y , unsigned int width ,
unsigned int height )
2012-12-07 12:30:38 -08:00
{
2013-04-22 12:02:25 +02:00
struct ssd1307fb_array * array ;
2019-06-18 10:41:06 +03:00
u8 * vmem = par - > info - > screen_buffer ;
2019-06-18 10:41:09 +03:00
unsigned int line_length = par - > info - > fix . line_length ;
2021-07-27 15:47:29 +02:00
unsigned int pages = DIV_ROUND_UP ( y % 8 + height , 8 ) ;
2021-07-27 15:47:27 +02:00
u32 array_idx = 0 ;
2021-07-27 15:47:26 +02:00
int ret , i , j , k ;
2012-12-07 12:30:38 -08:00
2021-07-27 15:47:29 +02:00
array = ssd1307fb_alloc_array ( width * pages , SSD1307FB_DATA ) ;
2013-04-22 12:02:25 +02:00
if ( ! array )
2021-07-27 15:47:26 +02:00
return - ENOMEM ;
2013-04-22 12:02:25 +02:00
2012-12-07 12:30:38 -08:00
/*
* The screen is divided in pages , each having a height of 8
* pixels , and the width of the screen . When sending a byte of
* data to the controller , it gives the 8 bits for the current
* column . I . e , the first byte are the 8 bits of the first
* column , then the 8 bits for the second column , etc .
*
*
* Representation of the screen , assuming it is 5 bits
* wide . Each letter - number combination is a bit that controls
* one pixel .
*
* A0 A1 A2 A3 A4
* B0 B1 B2 B3 B4
* C0 C1 C2 C3 C4
* D0 D1 D2 D3 D4
* E0 E1 E2 E3 E4
* F0 F1 F2 F3 F4
* G0 G1 G2 G3 G4
* H0 H1 H2 H3 H4
*
* If you want to update this screen , you need to send 5 bytes :
* ( 1 ) A0 B0 C0 D0 E0 F0 G0 H0
* ( 2 ) A1 B1 C1 D1 E1 F1 G1 H1
* ( 3 ) A2 B2 C2 D2 E2 F2 G2 H2
* ( 4 ) A3 B3 C3 D3 E3 F3 G3 H3
* ( 5 ) A4 B4 C4 D4 E4 F4 G4 H4
*/
2021-07-27 15:47:29 +02:00
ret = ssd1307fb_set_col_range ( par , par - > col_offset + x , width ) ;
if ( ret < 0 )
goto out_free ;
ret = ssd1307fb_set_page_range ( par , par - > page_offset + y / 8 , pages ) ;
if ( ret < 0 )
goto out_free ;
for ( i = y / 8 ; i < y / 8 + pages ; i + + ) {
2021-07-27 15:47:27 +02:00
int m = 8 ;
/* Last page may be partial */
2021-07-27 15:47:29 +02:00
if ( 8 * ( i + 1 ) > par - > height )
2021-07-27 15:47:27 +02:00
m = par - > height % 8 ;
2021-07-27 15:47:29 +02:00
for ( j = x ; j < x + width ; j + + ) {
2021-07-27 15:47:27 +02:00
u8 data = 0 ;
2019-06-18 10:41:09 +03:00
for ( k = 0 ; k < m ; k + + ) {
u8 byte = vmem [ ( 8 * i + k ) * line_length +
j / 8 ] ;
u8 bit = ( byte > > ( j % 8 ) ) & 1 ;
2021-07-27 15:47:27 +02:00
data | = bit < < k ;
2012-12-07 12:30:38 -08:00
}
2021-07-27 15:47:27 +02:00
array - > data [ array_idx + + ] = data ;
2012-12-07 12:30:38 -08:00
}
}
2013-04-22 12:02:25 +02:00
2021-07-27 15:47:29 +02:00
ret = ssd1307fb_write_array ( par - > client , array , width * pages ) ;
out_free :
2013-04-22 12:02:25 +02:00
kfree ( array ) ;
2021-07-27 15:47:26 +02:00
return ret ;
2012-12-07 12:30:38 -08:00
}
2021-07-27 15:47:29 +02:00
static int ssd1307fb_update_display ( struct ssd1307fb_par * par )
{
return ssd1307fb_update_rect ( par , 0 , 0 , par - > width , par - > height ) ;
}
2012-12-07 12:30:38 -08:00
2015-03-31 20:27:16 +02:00
static int ssd1307fb_blank ( int blank_mode , struct fb_info * info )
{
struct ssd1307fb_par * par = info - > par ;
if ( blank_mode ! = FB_BLANK_UNBLANK )
return ssd1307fb_write_cmd ( par - > client , SSD1307FB_DISPLAY_OFF ) ;
else
return ssd1307fb_write_cmd ( par - > client , SSD1307FB_DISPLAY_ON ) ;
}
2023-07-06 17:08:51 +02:00
static void ssd1307fb_defio_damage_range ( struct fb_info * info , off_t off , size_t len )
2012-12-07 12:30:38 -08:00
{
struct ssd1307fb_par * par = info - > par ;
2023-07-06 17:08:51 +02:00
ssd1307fb_update_display ( par ) ;
2012-12-07 12:30:38 -08:00
}
2023-07-06 17:08:51 +02:00
static void ssd1307fb_defio_damage_area ( struct fb_info * info , u32 x , u32 y ,
u32 width , u32 height )
2012-12-07 12:30:38 -08:00
{
struct ssd1307fb_par * par = info - > par ;
2023-07-06 17:08:51 +02:00
ssd1307fb_update_rect ( par , x , y , width , height ) ;
2012-12-07 12:30:38 -08:00
}
2023-07-29 21:26:49 +02:00
FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS ( ssd1307fb ,
ssd1307fb_defio_damage_range ,
ssd1307fb_defio_damage_area )
2023-07-06 17:08:51 +02:00
2019-12-03 18:38:50 +02:00
static const struct fb_ops ssd1307fb_ops = {
2012-12-07 12:30:38 -08:00
. owner = THIS_MODULE ,
2023-07-06 17:08:51 +02:00
FB_DEFAULT_DEFERRED_OPS ( ssd1307fb ) ,
2015-03-31 20:27:16 +02:00
. fb_blank = ssd1307fb_blank ,
2012-12-07 12:30:38 -08:00
} ;
2022-04-29 12:08:33 +02:00
static void ssd1307fb_deferred_io ( struct fb_info * info , struct list_head * pagereflist )
2012-12-07 12:30:38 -08:00
{
ssd1307fb_update_display ( info - > par ) ;
}
2015-03-31 20:27:10 +02:00
static int ssd1307fb_init ( struct ssd1307fb_par * par )
2013-04-22 11:55:54 +02:00
{
2020-03-24 19:05:31 +02:00
struct pwm_state pwmstate ;
2013-04-22 11:55:54 +02:00
int ret ;
2015-03-31 20:27:10 +02:00
u32 precharge , dclk , com_invdir , compins ;
2013-04-22 11:55:54 +02:00
2015-03-31 20:27:10 +02:00
if ( par - > device_info - > need_pwm ) {
par - > pwm = pwm_get ( & par - > client - > dev , NULL ) ;
if ( IS_ERR ( par - > pwm ) ) {
dev_err ( & par - > client - > dev , " Could not get PWM from device tree! \n " ) ;
return PTR_ERR ( par - > pwm ) ;
}
2013-04-22 11:55:54 +02:00
2020-03-24 19:05:31 +02:00
pwm_init_state ( par - > pwm , & pwmstate ) ;
pwm_set_relative_duty_cycle ( & pwmstate , 50 , 100 ) ;
pwm_apply_state ( par - > pwm , & pwmstate ) ;
2016-04-14 21:17:28 +02:00
2015-03-31 20:27:10 +02:00
/* Enable the PWM */
pwm_enable ( par - > pwm ) ;
2013-04-22 11:55:54 +02:00
2023-07-28 16:58:23 +02:00
dev_dbg ( & par - > client - > dev , " Using PWM %s with a %lluns period. \n " ,
par - > pwm - > label , pwm_get_period ( par - > pwm ) ) ;
2019-06-18 10:41:07 +03:00
}
2013-04-22 11:55:54 +02:00
/* Set initial contrast */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_CONTRAST ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_write_cmd ( par - > client , par - > contrast ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* Set segment re-map */
2015-03-31 20:27:10 +02:00
if ( par - > seg_remap ) {
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SEG_REMAP_ON ) ;
if ( ret < 0 )
return ret ;
2019-06-18 10:41:07 +03:00
}
2015-03-31 20:27:10 +02:00
/* Set COM direction */
2019-06-18 10:41:11 +03:00
com_invdir = 0xc0 | par - > com_invdir < < 3 ;
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_write_cmd ( par - > client , com_invdir ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* Set multiplex ratio value */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_MULTIPLEX_RATIO ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , par - > height - 1 ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* set display offset value */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_DISPLAY_OFFSET ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_write_cmd ( par - > client , par - > com_offset ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* Set clock frequency */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_CLOCK_FREQ ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2015-03-31 20:27:10 +02:00
dclk = ( ( par - > dclk_div - 1 ) & 0xf ) | ( par - > dclk_frq & 0xf ) < < 4 ;
ret = ssd1307fb_write_cmd ( par - > client , dclk ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
2022-08-16 21:07:13 +08:00
/* Set Area Color Mode ON/OFF & Low Power Display Mode */
2019-06-18 10:41:11 +03:00
if ( par - > area_color_enable | | par - > low_power ) {
u32 mode ;
ret = ssd1307fb_write_cmd ( par - > client ,
SSD1307FB_SET_AREA_COLOR_MODE ) ;
if ( ret < 0 )
return ret ;
mode = ( par - > area_color_enable ? 0x30 : 0 ) |
( par - > low_power ? 5 : 0 ) ;
ret = ssd1307fb_write_cmd ( par - > client , mode ) ;
if ( ret < 0 )
return ret ;
}
2013-04-22 11:55:54 +02:00
/* Set precharge period in number of ticks from the internal clock */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_PRECHARGE_PERIOD ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2015-03-31 20:27:10 +02:00
precharge = ( par - > prechargep1 & 0xf ) | ( par - > prechargep2 & 0xf ) < < 4 ;
ret = ssd1307fb_write_cmd ( par - > client , precharge ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* Set COM pins configuration */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_COM_PINS_CONFIG ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2019-06-18 10:41:11 +03:00
compins = 0x02 | ! par - > com_seq < < 4 | par - > com_lrremap < < 5 ;
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_write_cmd ( par - > client , compins ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* Set VCOMH */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_VCOMH ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_write_cmd ( par - > client , par - > vcomh ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
/* Turn on the DC-DC Charge Pump */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_CHARGE_PUMP ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_write_cmd ( par - > client ,
2016-03-24 22:14:23 +01:00
BIT ( 4 ) | ( par - > device_info - > need_chargepump ? BIT ( 2 ) : 0 ) ) ;
2013-04-22 11:55:54 +02:00
if ( ret < 0 )
return ret ;
2019-06-18 10:41:11 +03:00
/* Set lookup table */
if ( par - > lookup_table_set ) {
int i ;
ret = ssd1307fb_write_cmd ( par - > client ,
SSD1307FB_SET_LOOKUP_TABLE ) ;
if ( ret < 0 )
return ret ;
for ( i = 0 ; i < ARRAY_SIZE ( par - > lookup_table ) ; + + i ) {
u8 val = par - > lookup_table [ i ] ;
if ( val < 31 | | val > 63 )
dev_warn ( & par - > client - > dev ,
" lookup table index %d value out of range 31 <= %d <= 63 \n " ,
i , val ) ;
ret = ssd1307fb_write_cmd ( par - > client , val ) ;
if ( ret < 0 )
return ret ;
}
}
2013-04-22 12:02:25 +02:00
/* Switch to horizontal addressing mode */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_ADDRESS_MODE ) ;
2015-01-15 19:05:37 +00:00
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client ,
SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL ) ;
2013-04-22 12:02:25 +02:00
if ( ret < 0 )
return ret ;
2017-02-08 16:44:00 +01:00
/* Clear the screen */
2021-07-27 15:47:26 +02:00
ret = ssd1307fb_update_display ( par ) ;
if ( ret < 0 )
return ret ;
2017-02-08 16:43:59 +01:00
2013-04-22 11:55:54 +02:00
/* Turn on the display */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_DISPLAY_ON ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
2015-03-31 20:27:15 +02:00
static int ssd1307fb_update_bl ( struct backlight_device * bdev )
{
struct ssd1307fb_par * par = bl_get_data ( bdev ) ;
int ret ;
int brightness = bdev - > props . brightness ;
par - > contrast = brightness ;
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_CONTRAST ) ;
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , par - > contrast ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static int ssd1307fb_get_brightness ( struct backlight_device * bdev )
{
struct ssd1307fb_par * par = bl_get_data ( bdev ) ;
return par - > contrast ;
}
static int ssd1307fb_check_fb ( struct backlight_device * bdev ,
struct fb_info * info )
{
return ( info - > bl_dev = = bdev ) ;
}
static const struct backlight_ops ssd1307fb_bl_ops = {
. options = BL_CORE_SUSPENDRESUME ,
. update_status = ssd1307fb_update_bl ,
. get_brightness = ssd1307fb_get_brightness ,
. check_fb = ssd1307fb_check_fb ,
} ;
2015-03-31 20:27:12 +02:00
static struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = {
. default_vcomh = 0x34 ,
. default_dclk_div = 1 ,
. default_dclk_frq = 7 ,
} ;
2015-03-31 20:27:10 +02:00
static struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = {
. default_vcomh = 0x20 ,
. default_dclk_div = 1 ,
. default_dclk_frq = 8 ,
. need_chargepump = 1 ,
} ;
static struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = {
. default_vcomh = 0x20 ,
. default_dclk_div = 2 ,
. default_dclk_frq = 12 ,
. need_pwm = 1 ,
2013-04-22 11:55:54 +02:00
} ;
2015-09-08 21:19:51 +02:00
static struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = {
. default_vcomh = 0x34 ,
. default_dclk_div = 1 ,
. default_dclk_frq = 10 ,
} ;
2013-04-22 11:55:54 +02:00
static const struct of_device_id ssd1307fb_of_match [ ] = {
2015-03-31 20:27:12 +02:00
{
. compatible = " solomon,ssd1305fb-i2c " ,
. data = ( void * ) & ssd1307fb_ssd1305_deviceinfo ,
} ,
2013-04-22 11:55:54 +02:00
{
. compatible = " solomon,ssd1306fb-i2c " ,
2015-03-31 20:27:10 +02:00
. data = ( void * ) & ssd1307fb_ssd1306_deviceinfo ,
2013-04-22 11:55:54 +02:00
} ,
{
. compatible = " solomon,ssd1307fb-i2c " ,
2015-03-31 20:27:10 +02:00
. data = ( void * ) & ssd1307fb_ssd1307_deviceinfo ,
2013-04-22 11:55:54 +02:00
} ,
2015-09-08 21:19:51 +02:00
{
. compatible = " solomon,ssd1309fb-i2c " ,
. data = ( void * ) & ssd1307fb_ssd1309_deviceinfo ,
} ,
2013-04-22 11:55:54 +02:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ssd1307fb_of_match ) ;
2020-03-24 19:05:28 +02:00
static int ssd1307fb_probe ( struct i2c_client * client )
2012-12-07 12:30:38 -08:00
{
2020-03-24 19:05:29 +02:00
struct device * dev = & client - > dev ;
2015-03-31 20:27:15 +02:00
struct backlight_device * bl ;
char bl_name [ 12 ] ;
2012-12-07 12:30:38 -08:00
struct fb_info * info ;
2015-03-31 20:27:13 +02:00
struct fb_deferred_io * ssd1307fb_defio ;
2013-04-22 11:55:54 +02:00
u32 vmem_size ;
2012-12-07 12:30:38 -08:00
struct ssd1307fb_par * par ;
2019-06-18 10:41:06 +03:00
void * vmem ;
2012-12-07 12:30:38 -08:00
int ret ;
2020-03-24 19:05:29 +02:00
info = framebuffer_alloc ( sizeof ( struct ssd1307fb_par ) , dev ) ;
2019-06-28 12:30:08 +02:00
if ( ! info )
2012-12-07 12:30:38 -08:00
return - ENOMEM ;
2013-04-22 11:55:54 +02:00
par = info - > par ;
par - > info = info ;
par - > client = client ;
2020-03-24 19:05:30 +02:00
par - > device_info = device_get_match_data ( dev ) ;
2013-04-22 11:55:54 +02:00
2020-03-24 19:05:29 +02:00
par - > reset = devm_gpiod_get_optional ( dev , " reset " , GPIOD_OUT_LOW ) ;
2017-02-08 16:43:59 +01:00
if ( IS_ERR ( par - > reset ) ) {
2021-09-17 12:22:10 +08:00
ret = dev_err_probe ( dev , PTR_ERR ( par - > reset ) ,
" failed to get reset gpio \n " ) ;
2013-04-22 11:55:54 +02:00
goto fb_alloc_error ;
}
2020-03-24 19:05:29 +02:00
par - > vbat_reg = devm_regulator_get_optional ( dev , " vbat " ) ;
2017-02-08 16:43:59 +01:00
if ( IS_ERR ( par - > vbat_reg ) ) {
ret = PTR_ERR ( par - > vbat_reg ) ;
2017-04-07 17:28:23 +02:00
if ( ret = = - ENODEV ) {
par - > vbat_reg = NULL ;
} else {
2021-09-17 12:22:10 +08:00
dev_err_probe ( dev , ret , " failed to get VBAT regulator \n " ) ;
2017-04-07 17:28:23 +02:00
goto fb_alloc_error ;
}
2017-02-08 16:43:59 +01:00
}
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,width " , & par - > width ) )
2013-04-22 11:55:54 +02:00
par - > width = 96 ;
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,height " , & par - > height ) )
2015-01-08 08:17:58 +00:00
par - > height = 16 ;
2013-04-22 11:55:54 +02:00
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,page-offset " , & par - > page_offset ) )
2013-04-22 11:55:54 +02:00
par - > page_offset = 1 ;
2020-07-24 17:22:18 -03:00
if ( device_property_read_u32 ( dev , " solomon,col-offset " , & par - > col_offset ) )
par - > col_offset = 0 ;
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,com-offset " , & par - > com_offset ) )
2015-03-31 20:27:10 +02:00
par - > com_offset = 0 ;
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,prechargep1 " , & par - > prechargep1 ) )
2015-03-31 20:27:10 +02:00
par - > prechargep1 = 2 ;
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,prechargep2 " , & par - > prechargep2 ) )
2015-03-31 20:27:10 +02:00
par - > prechargep2 = 2 ;
2020-03-24 19:05:30 +02:00
if ( ! device_property_read_u8_array ( dev , " solomon,lookup-table " ,
par - > lookup_table ,
ARRAY_SIZE ( par - > lookup_table ) ) )
2019-06-18 10:41:11 +03:00
par - > lookup_table_set = 1 ;
2020-03-24 19:05:30 +02:00
par - > seg_remap = ! device_property_read_bool ( dev , " solomon,segment-no-remap " ) ;
par - > com_seq = device_property_read_bool ( dev , " solomon,com-seq " ) ;
par - > com_lrremap = device_property_read_bool ( dev , " solomon,com-lrremap " ) ;
par - > com_invdir = device_property_read_bool ( dev , " solomon,com-invdir " ) ;
2019-06-18 10:41:11 +03:00
par - > area_color_enable =
2020-03-24 19:05:30 +02:00
device_property_read_bool ( dev , " solomon,area-color-enable " ) ;
par - > low_power = device_property_read_bool ( dev , " solomon,low-power " ) ;
2015-03-31 20:27:10 +02:00
par - > contrast = 127 ;
par - > vcomh = par - > device_info - > default_vcomh ;
/* Setup display timing */
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,dclk-div " , & par - > dclk_div ) )
2019-06-18 10:41:11 +03:00
par - > dclk_div = par - > device_info - > default_dclk_div ;
2020-03-24 19:05:30 +02:00
if ( device_property_read_u32 ( dev , " solomon,dclk-frq " , & par - > dclk_frq ) )
2019-06-18 10:41:11 +03:00
par - > dclk_frq = par - > device_info - > default_dclk_frq ;
2015-03-31 20:27:10 +02:00
2019-06-18 10:41:09 +03:00
vmem_size = DIV_ROUND_UP ( par - > width , 8 ) * par - > height ;
2013-04-22 11:55:54 +02:00
2015-03-31 20:27:08 +02:00
vmem = ( void * ) __get_free_pages ( GFP_KERNEL | __GFP_ZERO ,
get_order ( vmem_size ) ) ;
2012-12-07 12:30:38 -08:00
if ( ! vmem ) {
2020-03-24 19:05:29 +02:00
dev_err ( dev , " Couldn't allocate graphical memory. \n " ) ;
2012-12-07 12:30:38 -08:00
ret = - ENOMEM ;
goto fb_alloc_error ;
}
2020-03-24 19:05:29 +02:00
ssd1307fb_defio = devm_kzalloc ( dev , sizeof ( * ssd1307fb_defio ) ,
2018-03-28 16:34:28 +02:00
GFP_KERNEL ) ;
2015-03-31 20:27:13 +02:00
if ( ! ssd1307fb_defio ) {
2020-03-24 19:05:29 +02:00
dev_err ( dev , " Couldn't allocate deferred io. \n " ) ;
2015-03-31 20:27:13 +02:00
ret = - ENOMEM ;
goto fb_alloc_error ;
}
ssd1307fb_defio - > delay = HZ / refreshrate ;
ssd1307fb_defio - > deferred_io = ssd1307fb_deferred_io ;
2012-12-07 12:30:38 -08:00
info - > fbops = & ssd1307fb_ops ;
info - > fix = ssd1307fb_fix ;
2019-06-18 10:41:09 +03:00
info - > fix . line_length = DIV_ROUND_UP ( par - > width , 8 ) ;
2015-03-31 20:27:13 +02:00
info - > fbdefio = ssd1307fb_defio ;
2012-12-07 12:30:38 -08:00
info - > var = ssd1307fb_var ;
2013-04-22 11:55:54 +02:00
info - > var . xres = par - > width ;
info - > var . xres_virtual = par - > width ;
info - > var . yres = par - > height ;
info - > var . yres_virtual = par - > height ;
2019-06-18 10:41:06 +03:00
info - > screen_buffer = vmem ;
2015-03-31 20:27:07 +02:00
info - > fix . smem_start = __pa ( vmem ) ;
2012-12-07 12:30:38 -08:00
info - > fix . smem_len = vmem_size ;
fb_deferred_io_init ( info ) ;
i2c_set_clientdata ( client , info ) ;
2017-02-08 16:43:59 +01:00
if ( par - > reset ) {
/* Reset the screen */
2018-10-09 15:18:42 +02:00
gpiod_set_value_cansleep ( par - > reset , 1 ) ;
udelay ( 4 ) ;
2019-02-08 19:24:48 +01:00
gpiod_set_value_cansleep ( par - > reset , 0 ) ;
udelay ( 4 ) ;
2017-02-08 16:43:59 +01:00
}
2012-12-07 12:30:38 -08:00
2017-04-07 17:28:23 +02:00
if ( par - > vbat_reg ) {
ret = regulator_enable ( par - > vbat_reg ) ;
if ( ret ) {
2020-03-24 19:05:29 +02:00
dev_err ( dev , " failed to enable VBAT: %d \n " , ret ) ;
2017-04-07 17:28:23 +02:00
goto reset_oled_error ;
}
2017-02-08 16:43:59 +01:00
}
2015-03-31 20:27:10 +02:00
ret = ssd1307fb_init ( par ) ;
if ( ret )
2017-02-08 16:43:59 +01:00
goto regulator_enable_error ;
2012-12-07 12:30:38 -08:00
2013-04-22 11:55:54 +02:00
ret = register_framebuffer ( info ) ;
if ( ret ) {
2020-03-24 19:05:29 +02:00
dev_err ( dev , " Couldn't register the framebuffer \n " ) ;
2013-04-22 11:55:54 +02:00
goto panel_init_error ;
2012-12-07 12:30:38 -08:00
}
2015-03-31 20:27:15 +02:00
snprintf ( bl_name , sizeof ( bl_name ) , " ssd1307fb%d " , info - > node ) ;
2020-03-24 19:05:29 +02:00
bl = backlight_device_register ( bl_name , dev , par , & ssd1307fb_bl_ops ,
NULL ) ;
2015-03-31 20:27:15 +02:00
if ( IS_ERR ( bl ) ) {
2015-08-23 02:11:15 +02:00
ret = PTR_ERR ( bl ) ;
2020-03-24 19:05:29 +02:00
dev_err ( dev , " unable to register backlight device: %d \n " , ret ) ;
2015-03-31 20:27:15 +02:00
goto bl_init_error ;
}
2015-05-23 20:32:35 +03:00
bl - > props . brightness = par - > contrast ;
bl - > props . max_brightness = MAX_CONTRAST ;
info - > bl_dev = bl ;
2020-03-24 19:05:29 +02:00
dev_info ( dev , " fb%d: %s framebuffer device registered, using %d bytes of video memory \n " , info - > node , info - > fix . id , vmem_size ) ;
2012-12-07 12:30:38 -08:00
return 0 ;
2015-03-31 20:27:15 +02:00
bl_init_error :
unregister_framebuffer ( info ) ;
2013-04-22 11:55:54 +02:00
panel_init_error :
2022-11-01 17:09:47 +02:00
pwm_disable ( par - > pwm ) ;
pwm_put ( par - > pwm ) ;
2017-02-08 16:43:59 +01:00
regulator_enable_error :
2017-04-07 17:28:23 +02:00
if ( par - > vbat_reg )
regulator_disable ( par - > vbat_reg ) ;
2012-12-07 12:30:38 -08:00
reset_oled_error :
fb_deferred_io_cleanup ( info ) ;
fb_alloc_error :
framebuffer_release ( info ) ;
return ret ;
}
2022-08-15 10:02:30 +02:00
static void ssd1307fb_remove ( struct i2c_client * client )
2012-12-07 12:30:38 -08:00
{
struct fb_info * info = i2c_get_clientdata ( client ) ;
struct ssd1307fb_par * par = info - > par ;
2015-03-31 20:27:14 +02:00
ssd1307fb_write_cmd ( par - > client , SSD1307FB_DISPLAY_OFF ) ;
2015-03-31 20:27:15 +02:00
backlight_device_unregister ( info - > bl_dev ) ;
2012-12-07 12:30:38 -08:00
unregister_framebuffer ( info ) ;
2022-11-01 17:09:47 +02:00
pwm_disable ( par - > pwm ) ;
pwm_put ( par - > pwm ) ;
2019-11-18 19:41:50 +08:00
if ( par - > vbat_reg )
regulator_disable ( par - > vbat_reg ) ;
2012-12-07 12:30:38 -08:00
fb_deferred_io_cleanup ( info ) ;
2015-03-31 20:27:08 +02:00
__free_pages ( __va ( info - > fix . smem_start ) , get_order ( info - > fix . smem_len ) ) ;
2012-12-07 12:30:38 -08:00
framebuffer_release ( info ) ;
}
static const struct i2c_device_id ssd1307fb_i2c_id [ ] = {
2015-03-31 20:27:12 +02:00
{ " ssd1305fb " , 0 } ,
2013-04-22 11:55:54 +02:00
{ " ssd1306fb " , 0 } ,
2012-12-07 12:30:38 -08:00
{ " ssd1307fb " , 0 } ,
2015-09-08 21:19:51 +02:00
{ " ssd1309fb " , 0 } ,
2012-12-07 12:30:38 -08:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , ssd1307fb_i2c_id ) ;
static struct i2c_driver ssd1307fb_driver = {
2023-05-26 07:41:23 +02:00
. probe = ssd1307fb_probe ,
2012-12-21 13:07:39 -08:00
. remove = ssd1307fb_remove ,
2012-12-07 12:30:38 -08:00
. id_table = ssd1307fb_i2c_id ,
. driver = {
. name = " ssd1307fb " ,
2013-09-30 14:15:31 +05:30
. of_match_table = ssd1307fb_of_match ,
2012-12-07 12:30:38 -08:00
} ,
} ;
module_i2c_driver ( ssd1307fb_driver ) ;
2013-04-09 02:06:50 +09:00
MODULE_DESCRIPTION ( " FB driver for the Solomon SSD1307 OLED controller " ) ;
2012-12-07 12:30:38 -08:00
MODULE_AUTHOR ( " Maxime Ripard <maxime.ripard@free-electrons.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;