2012-12-08 00:30:38 +04:00
/*
2013-04-08 21:06:50 +04:00
* Driver for the Solomon SSD1307 OLED controller
2012-12-08 00:30:38 +04:00
*
* Copyright 2012 Free Electrons
*
* Licensed under the GPLv2 or later .
*/
2015-03-31 21:27:15 +03:00
# include <linux/backlight.h>
2015-09-08 22:19:50 +03:00
# include <linux/delay.h>
2012-12-08 00:30:38 +04:00
# include <linux/fb.h>
2015-09-08 22:19:50 +03:00
# include <linux/i2c.h>
# include <linux/kernel.h>
# include <linux/module.h>
2012-12-08 00:30:38 +04:00
# include <linux/of_device.h>
# include <linux/of_gpio.h>
# include <linux/pwm.h>
2015-09-08 22:19:50 +03:00
# include <linux/uaccess.h>
2017-02-08 18:43:59 +03:00
# include <linux/regulator/consumer.h>
2012-12-08 00:30:38 +04:00
# define SSD1307FB_DATA 0x40
# define SSD1307FB_COMMAND 0x80
2013-04-22 14:02:25 +04: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-08 00:30:38 +04:00
# define SSD1307FB_CONTRAST 0x81
2013-04-22 13:55:54 +04:00
# define SSD1307FB_CHARGE_PUMP 0x8d
2012-12-08 00:30:38 +04:00
# define SSD1307FB_SEG_REMAP_ON 0xa1
# define SSD1307FB_DISPLAY_OFF 0xae
2013-04-22 13:55:54 +04:00
# define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8
2012-12-08 00:30:38 +04:00
# define SSD1307FB_DISPLAY_ON 0xaf
# define SSD1307FB_START_PAGE_ADDRESS 0xb0
2013-04-22 13:55:54 +04:00
# define SSD1307FB_SET_DISPLAY_OFFSET 0xd3
# define SSD1307FB_SET_CLOCK_FREQ 0xd5
# define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9
# define SSD1307FB_SET_COM_PINS_CONFIG 0xda
# define SSD1307FB_SET_VCOMH 0xdb
2015-03-31 21:27:15 +03:00
# define MAX_CONTRAST 255
2015-03-31 21:27:13 +03:00
# define REFRESHRATE 1
static u_int refreshrate = REFRESHRATE ;
module_param ( refreshrate , uint , 0 ) ;
2013-04-22 13:55:54 +04:00
struct ssd1307fb_par ;
2015-03-31 21:27:10 +03:00
struct ssd1307fb_deviceinfo {
u32 default_vcomh ;
u32 default_dclk_div ;
u32 default_dclk_frq ;
int need_pwm ;
int need_chargepump ;
2013-04-22 13:55:54 +04:00
} ;
2012-12-08 00:30:38 +04:00
struct ssd1307fb_par {
2015-03-31 21:27:10 +03:00
u32 com_invdir ;
u32 com_lrremap ;
u32 com_offset ;
u32 com_seq ;
u32 contrast ;
u32 dclk_div ;
u32 dclk_frq ;
2016-08-16 12:27:17 +03:00
const struct ssd1307fb_deviceinfo * device_info ;
2012-12-08 00:30:38 +04:00
struct i2c_client * client ;
2013-04-22 13:55:54 +04:00
u32 height ;
2012-12-08 00:30:38 +04:00
struct fb_info * info ;
2013-04-22 13:55:54 +04:00
u32 page_offset ;
2015-03-31 21:27:10 +03:00
u32 prechargep1 ;
u32 prechargep2 ;
2012-12-08 00:30:38 +04:00
struct pwm_device * pwm ;
u32 pwm_period ;
2017-02-08 18:43:59 +03:00
struct gpio_desc * reset ;
2017-02-08 18:43:59 +03:00
struct regulator * vbat_reg ;
2015-03-31 21:27:10 +03:00
u32 seg_remap ;
u32 vcomh ;
2013-04-22 13:55:54 +04:00
u32 width ;
2012-12-08 00:30:38 +04:00
} ;
2013-04-22 14:02:23 +04:00
struct ssd1307fb_array {
u8 type ;
u8 data [ 0 ] ;
} ;
2016-09-11 18:17:20 +03:00
static const struct fb_fix_screeninfo ssd1307fb_fix = {
2012-12-08 00:30:38 +04: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 18:17:20 +03:00
static const struct fb_var_screeninfo ssd1307fb_var = {
2012-12-08 00:30:38 +04:00
. bits_per_pixel = 1 ,
} ;
2013-04-22 14:02:23 +04:00
static struct ssd1307fb_array * ssd1307fb_alloc_array ( u32 len , u8 type )
2012-12-08 00:30:38 +04:00
{
2013-04-22 14:02:23 +04:00
struct ssd1307fb_array * array ;
2012-12-08 00:30:38 +04:00
2013-04-22 14:02:23 +04:00
array = kzalloc ( sizeof ( struct ssd1307fb_array ) + len , GFP_KERNEL ) ;
if ( ! array )
return NULL ;
2012-12-08 00:30:38 +04:00
2013-04-22 14:02:23 +04:00
array - > type = type ;
2012-12-08 00:30:38 +04:00
2013-04-22 14:02:23 +04:00
return array ;
2012-12-08 00:30:38 +04:00
}
2013-04-22 14:02:23 +04:00
static int ssd1307fb_write_array ( struct i2c_client * client ,
struct ssd1307fb_array * array , u32 len )
2012-12-08 00:30:38 +04:00
{
2013-04-22 14:02:23 +04: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-08 00:30:38 +04:00
}
static inline int ssd1307fb_write_cmd ( struct i2c_client * client , u8 cmd )
{
2013-04-22 14:02:23 +04:00
struct ssd1307fb_array * array ;
int ret ;
2012-12-08 00:30:38 +04:00
2013-04-22 14:02:23 +04: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-08 00:30:38 +04:00
}
static void ssd1307fb_update_display ( struct ssd1307fb_par * par )
{
2013-04-22 14:02:25 +04:00
struct ssd1307fb_array * array ;
2012-12-08 00:30:38 +04:00
u8 * vmem = par - > info - > screen_base ;
int i , j , k ;
2013-04-22 14:02:25 +04:00
array = ssd1307fb_alloc_array ( par - > width * par - > height / 8 ,
SSD1307FB_DATA ) ;
if ( ! array )
return ;
2012-12-08 00:30:38 +04: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
*/
2013-04-22 13:55:54 +04:00
for ( i = 0 ; i < ( par - > height / 8 ) ; i + + ) {
for ( j = 0 ; j < par - > width ; j + + ) {
2013-04-22 14:02:25 +04:00
u32 array_idx = i * par - > width + j ;
array - > data [ array_idx ] = 0 ;
2012-12-08 00:30:38 +04:00
for ( k = 0 ; k < 8 ; k + + ) {
2013-04-22 13:55:54 +04:00
u32 page_length = par - > width * i ;
u32 index = page_length + ( par - > width * k + j ) / 8 ;
2012-12-08 00:30:38 +04:00
u8 byte = * ( vmem + index ) ;
2013-01-12 02:31:43 +04:00
u8 bit = byte & ( 1 < < ( j % 8 ) ) ;
bit = bit > > ( j % 8 ) ;
2013-04-22 14:02:25 +04:00
array - > data [ array_idx ] | = bit < < k ;
2012-12-08 00:30:38 +04:00
}
}
}
2013-04-22 14:02:25 +04:00
ssd1307fb_write_array ( par - > client , array , par - > width * par - > height / 8 ) ;
kfree ( array ) ;
2012-12-08 00:30:38 +04:00
}
static ssize_t ssd1307fb_write ( struct fb_info * info , const char __user * buf ,
size_t count , loff_t * ppos )
{
struct ssd1307fb_par * par = info - > par ;
unsigned long total_size ;
unsigned long p = * ppos ;
u8 __iomem * dst ;
total_size = info - > fix . smem_len ;
if ( p > total_size )
return - EINVAL ;
if ( count + p > total_size )
count = total_size - p ;
if ( ! count )
return - EINVAL ;
dst = ( void __force * ) ( info - > screen_base + p ) ;
if ( copy_from_user ( dst , buf , count ) )
return - EFAULT ;
ssd1307fb_update_display ( par ) ;
* ppos + = count ;
return count ;
}
2015-03-31 21:27:16 +03: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 ) ;
}
2012-12-08 00:30:38 +04:00
static void ssd1307fb_fillrect ( struct fb_info * info , const struct fb_fillrect * rect )
{
struct ssd1307fb_par * par = info - > par ;
sys_fillrect ( info , rect ) ;
ssd1307fb_update_display ( par ) ;
}
static void ssd1307fb_copyarea ( struct fb_info * info , const struct fb_copyarea * area )
{
struct ssd1307fb_par * par = info - > par ;
sys_copyarea ( info , area ) ;
ssd1307fb_update_display ( par ) ;
}
static void ssd1307fb_imageblit ( struct fb_info * info , const struct fb_image * image )
{
struct ssd1307fb_par * par = info - > par ;
sys_imageblit ( info , image ) ;
ssd1307fb_update_display ( par ) ;
}
static struct fb_ops ssd1307fb_ops = {
. owner = THIS_MODULE ,
. fb_read = fb_sys_read ,
. fb_write = ssd1307fb_write ,
2015-03-31 21:27:16 +03:00
. fb_blank = ssd1307fb_blank ,
2012-12-08 00:30:38 +04:00
. fb_fillrect = ssd1307fb_fillrect ,
. fb_copyarea = ssd1307fb_copyarea ,
. fb_imageblit = ssd1307fb_imageblit ,
} ;
static void ssd1307fb_deferred_io ( struct fb_info * info ,
struct list_head * pagelist )
{
ssd1307fb_update_display ( info - > par ) ;
}
2015-03-31 21:27:10 +03:00
static int ssd1307fb_init ( struct ssd1307fb_par * par )
2013-04-22 13:55:54 +04:00
{
int ret ;
2015-03-31 21:27:10 +03:00
u32 precharge , dclk , com_invdir , compins ;
2016-04-14 22:17:28 +03:00
struct pwm_args pargs ;
2013-04-22 13:55:54 +04:00
2015-03-31 21:27:10 +03: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 13:55:54 +04:00
2016-04-14 22:17:28 +03:00
/*
* FIXME : pwm_apply_args ( ) should be removed when switching to
* the atomic PWM API .
*/
pwm_apply_args ( par - > pwm ) ;
pwm_get_args ( par - > pwm , & pargs ) ;
par - > pwm_period = pargs . period ;
2015-03-31 21:27:10 +03:00
/* Enable the PWM */
pwm_config ( par - > pwm , par - > pwm_period / 2 , par - > pwm_period ) ;
pwm_enable ( par - > pwm ) ;
2013-04-22 13:55:54 +04:00
2015-03-31 21:27:10 +03:00
dev_dbg ( & par - > client - > dev , " Using PWM%d with a %dns period. \n " ,
par - > pwm - > pwm , par - > pwm_period ) ;
} ;
2013-04-22 13:55:54 +04:00
/* Set initial contrast */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_CONTRAST ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
ret = ssd1307fb_write_cmd ( par - > client , par - > contrast ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* Set segment re-map */
2015-03-31 21:27:10 +03:00
if ( par - > seg_remap ) {
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SEG_REMAP_ON ) ;
if ( ret < 0 )
return ret ;
} ;
/* Set COM direction */
com_invdir = 0xc0 | ( par - > com_invdir & 0x1 ) < < 3 ;
ret = ssd1307fb_write_cmd ( par - > client , com_invdir ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* Set multiplex ratio value */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_MULTIPLEX_RATIO ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , par - > height - 1 ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* set display offset value */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_DISPLAY_OFFSET ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
ret = ssd1307fb_write_cmd ( par - > client , par - > com_offset ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* Set clock frequency */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_CLOCK_FREQ ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
dclk = ( ( par - > dclk_div - 1 ) & 0xf ) | ( par - > dclk_frq & 0xf ) < < 4 ;
ret = ssd1307fb_write_cmd ( par - > client , dclk ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* Set precharge period in number of ticks from the internal clock */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_PRECHARGE_PERIOD ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
precharge = ( par - > prechargep1 & 0xf ) | ( par - > prechargep2 & 0xf ) < < 4 ;
ret = ssd1307fb_write_cmd ( par - > client , precharge ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* Set COM pins configuration */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_COM_PINS_CONFIG ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-05-25 22:29:21 +03:00
compins = 0x02 | ! ( par - > com_seq & 0x1 ) < < 4
2015-03-31 21:27:10 +03:00
| ( par - > com_lrremap & 0x1 ) < < 5 ;
ret = ssd1307fb_write_cmd ( par - > client , compins ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
/* Set VCOMH */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_VCOMH ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
ret = ssd1307fb_write_cmd ( par - > client , par - > vcomh ) ;
2013-04-22 13:55:54 +04: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 22:05:37 +03:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
ret = ssd1307fb_write_cmd ( par - > client ,
2016-03-25 00:14:23 +03:00
BIT ( 4 ) | ( par - > device_info - > need_chargepump ? BIT ( 2 ) : 0 ) ) ;
2013-04-22 13:55:54 +04:00
if ( ret < 0 )
return ret ;
2013-04-22 14:02:25 +04:00
/* Switch to horizontal addressing mode */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_ADDRESS_MODE ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client ,
SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL ) ;
2013-04-22 14:02:25 +04:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
/* Set column range */
2013-04-22 14:02:25 +04:00
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_COL_RANGE ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , 0x0 ) ;
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , par - > width - 1 ) ;
2013-04-22 14:02:25 +04:00
if ( ret < 0 )
return ret ;
2015-03-31 21:27:10 +03:00
/* Set page range */
2013-04-22 14:02:25 +04:00
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_SET_PAGE_RANGE ) ;
2015-01-15 22:05:37 +03:00
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client , 0x0 ) ;
if ( ret < 0 )
return ret ;
ret = ssd1307fb_write_cmd ( par - > client ,
par - > page_offset + ( par - > height / 8 ) - 1 ) ;
2013-04-22 14:02:25 +04:00
if ( ret < 0 )
return ret ;
2017-02-08 18:44:00 +03:00
/* Clear the screen */
ssd1307fb_update_display ( par ) ;
2017-02-08 18:43:59 +03:00
2013-04-22 13:55:54 +04:00
/* Turn on the display */
ret = ssd1307fb_write_cmd ( par - > client , SSD1307FB_DISPLAY_ON ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
2015-03-31 21:27:15 +03: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 21:27:12 +03:00
static struct ssd1307fb_deviceinfo ssd1307fb_ssd1305_deviceinfo = {
. default_vcomh = 0x34 ,
. default_dclk_div = 1 ,
. default_dclk_frq = 7 ,
} ;
2015-03-31 21:27:10 +03: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 13:55:54 +04:00
} ;
2015-09-08 22:19:51 +03:00
static struct ssd1307fb_deviceinfo ssd1307fb_ssd1309_deviceinfo = {
. default_vcomh = 0x34 ,
. default_dclk_div = 1 ,
. default_dclk_frq = 10 ,
} ;
2013-04-22 13:55:54 +04:00
static const struct of_device_id ssd1307fb_of_match [ ] = {
2015-03-31 21:27:12 +03:00
{
. compatible = " solomon,ssd1305fb-i2c " ,
. data = ( void * ) & ssd1307fb_ssd1305_deviceinfo ,
} ,
2013-04-22 13:55:54 +04:00
{
. compatible = " solomon,ssd1306fb-i2c " ,
2015-03-31 21:27:10 +03:00
. data = ( void * ) & ssd1307fb_ssd1306_deviceinfo ,
2013-04-22 13:55:54 +04:00
} ,
{
. compatible = " solomon,ssd1307fb-i2c " ,
2015-03-31 21:27:10 +03:00
. data = ( void * ) & ssd1307fb_ssd1307_deviceinfo ,
2013-04-22 13:55:54 +04:00
} ,
2015-09-08 22:19:51 +03:00
{
. compatible = " solomon,ssd1309fb-i2c " ,
. data = ( void * ) & ssd1307fb_ssd1309_deviceinfo ,
} ,
2013-04-22 13:55:54 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ssd1307fb_of_match ) ;
2012-12-22 01:07:39 +04:00
static int ssd1307fb_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
2012-12-08 00:30:38 +04:00
{
2015-03-31 21:27:15 +03:00
struct backlight_device * bl ;
char bl_name [ 12 ] ;
2012-12-08 00:30:38 +04:00
struct fb_info * info ;
2013-04-22 13:55:54 +04:00
struct device_node * node = client - > dev . of_node ;
2015-03-31 21:27:13 +03:00
struct fb_deferred_io * ssd1307fb_defio ;
2013-04-22 13:55:54 +04:00
u32 vmem_size ;
2012-12-08 00:30:38 +04:00
struct ssd1307fb_par * par ;
u8 * vmem ;
int ret ;
2013-04-22 13:55:54 +04:00
if ( ! node ) {
2012-12-08 00:30:38 +04:00
dev_err ( & client - > dev , " No device tree data found! \n " ) ;
return - EINVAL ;
}
info = framebuffer_alloc ( sizeof ( struct ssd1307fb_par ) , & client - > dev ) ;
if ( ! info ) {
dev_err ( & client - > dev , " Couldn't allocate framebuffer. \n " ) ;
return - ENOMEM ;
}
2013-04-22 13:55:54 +04:00
par = info - > par ;
par - > info = info ;
par - > client = client ;
2016-08-16 12:27:18 +03:00
par - > device_info = of_device_get_match_data ( & client - > dev ) ;
2013-04-22 13:55:54 +04:00
2017-02-08 18:43:59 +03:00
par - > reset = devm_gpiod_get_optional ( & client - > dev , " reset " ,
GPIOD_OUT_LOW ) ;
2017-02-08 18:43:59 +03:00
if ( IS_ERR ( par - > reset ) ) {
dev_err ( & client - > dev , " failed to get reset gpio: %ld \n " ,
PTR_ERR ( par - > reset ) ) ;
ret = PTR_ERR ( par - > reset ) ;
2013-04-22 13:55:54 +04:00
goto fb_alloc_error ;
}
2017-02-08 18:43:59 +03:00
par - > vbat_reg = devm_regulator_get_optional ( & client - > dev , " vbat " ) ;
if ( IS_ERR ( par - > vbat_reg ) ) {
dev_err ( & client - > dev , " failed to get VBAT regulator: %ld \n " ,
PTR_ERR ( par - > vbat_reg ) ) ;
ret = PTR_ERR ( par - > vbat_reg ) ;
goto fb_alloc_error ;
}
2013-04-22 13:55:54 +04:00
if ( of_property_read_u32 ( node , " solomon,width " , & par - > width ) )
par - > width = 96 ;
if ( of_property_read_u32 ( node , " solomon,height " , & par - > height ) )
2015-01-08 11:17:58 +03:00
par - > height = 16 ;
2013-04-22 13:55:54 +04:00
if ( of_property_read_u32 ( node , " solomon,page-offset " , & par - > page_offset ) )
par - > page_offset = 1 ;
2015-03-31 21:27:10 +03:00
if ( of_property_read_u32 ( node , " solomon,com-offset " , & par - > com_offset ) )
par - > com_offset = 0 ;
if ( of_property_read_u32 ( node , " solomon,prechargep1 " , & par - > prechargep1 ) )
par - > prechargep1 = 2 ;
if ( of_property_read_u32 ( node , " solomon,prechargep2 " , & par - > prechargep2 ) )
par - > prechargep2 = 2 ;
par - > seg_remap = ! of_property_read_bool ( node , " solomon,segment-no-remap " ) ;
par - > com_seq = of_property_read_bool ( node , " solomon,com-seq " ) ;
par - > com_lrremap = of_property_read_bool ( node , " solomon,com-lrremap " ) ;
par - > com_invdir = of_property_read_bool ( node , " solomon,com-invdir " ) ;
par - > contrast = 127 ;
par - > vcomh = par - > device_info - > default_vcomh ;
/* Setup display timing */
par - > dclk_div = par - > device_info - > default_dclk_div ;
par - > dclk_frq = par - > device_info - > default_dclk_frq ;
2013-04-22 13:55:54 +04:00
vmem_size = par - > width * par - > height / 8 ;
2015-03-31 21:27:08 +03:00
vmem = ( void * ) __get_free_pages ( GFP_KERNEL | __GFP_ZERO ,
get_order ( vmem_size ) ) ;
2012-12-08 00:30:38 +04:00
if ( ! vmem ) {
dev_err ( & client - > dev , " Couldn't allocate graphical memory. \n " ) ;
ret = - ENOMEM ;
goto fb_alloc_error ;
}
2015-03-31 21:27:13 +03:00
ssd1307fb_defio = devm_kzalloc ( & client - > dev , sizeof ( struct fb_deferred_io ) , GFP_KERNEL ) ;
if ( ! ssd1307fb_defio ) {
dev_err ( & client - > dev , " Couldn't allocate deferred io. \n " ) ;
ret = - ENOMEM ;
goto fb_alloc_error ;
}
ssd1307fb_defio - > delay = HZ / refreshrate ;
ssd1307fb_defio - > deferred_io = ssd1307fb_deferred_io ;
2012-12-08 00:30:38 +04:00
info - > fbops = & ssd1307fb_ops ;
info - > fix = ssd1307fb_fix ;
2013-04-22 13:55:54 +04:00
info - > fix . line_length = par - > width / 8 ;
2015-03-31 21:27:13 +03:00
info - > fbdefio = ssd1307fb_defio ;
2012-12-08 00:30:38 +04:00
info - > var = ssd1307fb_var ;
2013-04-22 13:55:54 +04:00
info - > var . xres = par - > width ;
info - > var . xres_virtual = par - > width ;
info - > var . yres = par - > height ;
info - > var . yres_virtual = par - > height ;
2012-12-08 00:30:38 +04:00
info - > var . red . length = 1 ;
info - > var . red . offset = 0 ;
info - > var . green . length = 1 ;
info - > var . green . offset = 0 ;
info - > var . blue . length = 1 ;
info - > var . blue . offset = 0 ;
info - > screen_base = ( u8 __force __iomem * ) vmem ;
2015-03-31 21:27:07 +03:00
info - > fix . smem_start = __pa ( vmem ) ;
2012-12-08 00:30:38 +04:00
info - > fix . smem_len = vmem_size ;
fb_deferred_io_init ( info ) ;
i2c_set_clientdata ( client , info ) ;
2017-02-08 18:43:59 +03:00
if ( par - > reset ) {
/* Reset the screen */
gpiod_set_value ( par - > reset , 0 ) ;
udelay ( 4 ) ;
gpiod_set_value ( par - > reset , 1 ) ;
udelay ( 4 ) ;
}
2012-12-08 00:30:38 +04:00
2017-02-08 18:43:59 +03:00
ret = regulator_enable ( par - > vbat_reg ) ;
if ( ret ) {
dev_err ( & client - > dev , " failed to enable VBAT: %d \n " , ret ) ;
goto reset_oled_error ;
}
2015-03-31 21:27:10 +03:00
ret = ssd1307fb_init ( par ) ;
if ( ret )
2017-02-08 18:43:59 +03:00
goto regulator_enable_error ;
2012-12-08 00:30:38 +04:00
2013-04-22 13:55:54 +04:00
ret = register_framebuffer ( info ) ;
if ( ret ) {
dev_err ( & client - > dev , " Couldn't register the framebuffer \n " ) ;
goto panel_init_error ;
2012-12-08 00:30:38 +04:00
}
2015-03-31 21:27:15 +03:00
snprintf ( bl_name , sizeof ( bl_name ) , " ssd1307fb%d " , info - > node ) ;
bl = backlight_device_register ( bl_name , & client - > dev , par ,
& ssd1307fb_bl_ops , NULL ) ;
if ( IS_ERR ( bl ) ) {
2015-08-23 03:11:15 +03:00
ret = PTR_ERR ( bl ) ;
dev_err ( & client - > dev , " unable to register backlight device: %d \n " ,
ret ) ;
2015-03-31 21:27:15 +03: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 ;
2012-12-08 00:30:38 +04:00
dev_info ( & client - > dev , " fb%d: %s framebuffer device registered, using %d bytes of video memory \n " , info - > node , info - > fix . id , vmem_size ) ;
return 0 ;
2015-03-31 21:27:15 +03:00
bl_init_error :
unregister_framebuffer ( info ) ;
2013-04-22 13:55:54 +04:00
panel_init_error :
2015-03-31 21:27:10 +03:00
if ( par - > device_info - > need_pwm ) {
pwm_disable ( par - > pwm ) ;
pwm_put ( par - > pwm ) ;
} ;
2017-02-08 18:43:59 +03:00
regulator_enable_error :
regulator_disable ( par - > vbat_reg ) ;
2012-12-08 00:30:38 +04:00
reset_oled_error :
fb_deferred_io_cleanup ( info ) ;
fb_alloc_error :
framebuffer_release ( info ) ;
return ret ;
}
2012-12-22 01:07:39 +04:00
static int ssd1307fb_remove ( struct i2c_client * client )
2012-12-08 00:30:38 +04:00
{
struct fb_info * info = i2c_get_clientdata ( client ) ;
struct ssd1307fb_par * par = info - > par ;
2015-03-31 21:27:14 +03:00
ssd1307fb_write_cmd ( par - > client , SSD1307FB_DISPLAY_OFF ) ;
2015-03-31 21:27:15 +03:00
backlight_device_unregister ( info - > bl_dev ) ;
2012-12-08 00:30:38 +04:00
unregister_framebuffer ( info ) ;
2015-03-31 21:27:10 +03:00
if ( par - > device_info - > need_pwm ) {
pwm_disable ( par - > pwm ) ;
pwm_put ( par - > pwm ) ;
} ;
2012-12-08 00:30:38 +04:00
fb_deferred_io_cleanup ( info ) ;
2015-03-31 21:27:08 +03:00
__free_pages ( __va ( info - > fix . smem_start ) , get_order ( info - > fix . smem_len ) ) ;
2012-12-08 00:30:38 +04:00
framebuffer_release ( info ) ;
return 0 ;
}
static const struct i2c_device_id ssd1307fb_i2c_id [ ] = {
2015-03-31 21:27:12 +03:00
{ " ssd1305fb " , 0 } ,
2013-04-22 13:55:54 +04:00
{ " ssd1306fb " , 0 } ,
2012-12-08 00:30:38 +04:00
{ " ssd1307fb " , 0 } ,
2015-09-08 22:19:51 +03:00
{ " ssd1309fb " , 0 } ,
2012-12-08 00:30:38 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , ssd1307fb_i2c_id ) ;
static struct i2c_driver ssd1307fb_driver = {
. probe = ssd1307fb_probe ,
2012-12-22 01:07:39 +04:00
. remove = ssd1307fb_remove ,
2012-12-08 00:30:38 +04:00
. id_table = ssd1307fb_i2c_id ,
. driver = {
. name = " ssd1307fb " ,
2013-09-30 12:45:31 +04:00
. of_match_table = ssd1307fb_of_match ,
2012-12-08 00:30:38 +04:00
} ,
} ;
module_i2c_driver ( ssd1307fb_driver ) ;
2013-04-08 21:06:50 +04:00
MODULE_DESCRIPTION ( " FB driver for the Solomon SSD1307 OLED controller " ) ;
2012-12-08 00:30:38 +04:00
MODULE_AUTHOR ( " Maxime Ripard <maxime.ripard@free-electrons.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;