2019-05-27 08:55:01 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-06-08 17:14:35 +02:00
/*
* DRM driver for Pervasive Displays RePaper branded e - ink panels
*
* Copyright 2013 - 2017 Pervasive Displays , Inc .
* Copyright 2017 Noralf Trønnes
*
* The driver supports :
* Material Film : Aurora Mb ( V231 )
* Driver IC : G2 ( eTC )
*
* The controller code was taken from the userspace driver :
* https : //github.com/repaper/gratis
*/
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/module.h>
2020-01-31 22:49:22 +02:00
# include <linux/property.h>
2017-06-08 17:14:35 +02:00
# include <linux/sched/clock.h>
# include <linux/spi/spi.h>
# include <linux/thermal.h>
2019-02-10 14:10:33 +01:00
# include <drm/drm_atomic_helper.h>
2019-07-19 17:59:14 +02:00
# include <drm/drm_connector.h>
2019-01-15 05:36:42 +01:00
# include <drm/drm_damage_helper.h>
2019-01-08 20:29:38 +01:00
# include <drm/drm_drv.h>
2018-11-10 15:56:47 +01:00
# include <drm/drm_fb_cma_helper.h>
2019-02-25 15:42:29 +01:00
# include <drm/drm_fb_helper.h>
2019-04-05 11:52:15 +02:00
# include <drm/drm_format_helper.h>
2021-02-22 15:17:56 +01:00
# include <drm/drm_gem_atomic_helper.h>
2018-11-10 15:56:47 +01:00
# include <drm/drm_gem_cma_helper.h>
2017-09-24 14:26:16 +02:00
# include <drm/drm_gem_framebuffer_helper.h>
2020-03-23 15:49:14 +01:00
# include <drm/drm_managed.h>
2019-07-19 17:59:14 +02:00
# include <drm/drm_modes.h>
2019-01-15 05:36:41 +01:00
# include <drm/drm_rect.h>
2019-07-19 17:59:14 +02:00
# include <drm/drm_probe_helper.h>
2019-02-25 15:42:29 +01:00
# include <drm/drm_simple_kms_helper.h>
2017-06-08 17:14:35 +02:00
# define REPAPER_RID_G2_COG_ID 0x12
enum repaper_model {
2020-01-31 22:49:22 +02:00
/* 0 is reserved to avoid clashing with NULL */
2017-06-08 17:14:35 +02:00
E1144CS021 = 1 ,
E1190CS021 ,
E2200CS021 ,
E2271CS021 ,
} ;
enum repaper_stage { /* Image pixel -> Display pixel */
REPAPER_COMPENSATE , /* B -> W, W -> B (Current Image) */
REPAPER_WHITE , /* B -> N, W -> W (Current Image) */
REPAPER_INVERSE , /* B -> N, W -> B (New Image) */
REPAPER_NORMAL /* B -> B, W -> W (New Image) */
} ;
enum repaper_epd_border_byte {
REPAPER_BORDER_BYTE_NONE ,
REPAPER_BORDER_BYTE_ZERO ,
REPAPER_BORDER_BYTE_SET ,
} ;
struct repaper_epd {
2019-02-25 15:42:29 +01:00
struct drm_device drm ;
struct drm_simple_display_pipe pipe ;
2019-07-19 17:59:14 +02:00
const struct drm_display_mode * mode ;
struct drm_connector connector ;
2017-06-08 17:14:35 +02:00
struct spi_device * spi ;
struct gpio_desc * panel_on ;
struct gpio_desc * border ;
struct gpio_desc * discharge ;
struct gpio_desc * reset ;
struct gpio_desc * busy ;
struct thermal_zone_device * thermal ;
unsigned int height ;
unsigned int width ;
unsigned int bytes_per_scan ;
const u8 * channel_select ;
unsigned int stage_time ;
unsigned int factored_stage_time ;
bool middle_scan ;
bool pre_border_byte ;
enum repaper_epd_border_byte border_byte ;
u8 * line_buffer ;
void * current_frame ;
bool cleared ;
bool partial ;
} ;
2019-02-25 15:42:29 +01:00
static inline struct repaper_epd * drm_to_epd ( struct drm_device * drm )
2017-06-08 17:14:35 +02:00
{
2019-02-25 15:42:29 +01:00
return container_of ( drm , struct repaper_epd , drm ) ;
2017-06-08 17:14:35 +02:00
}
static int repaper_spi_transfer ( struct spi_device * spi , u8 header ,
const void * tx , void * rx , size_t len )
{
void * txbuf = NULL , * rxbuf = NULL ;
struct spi_transfer tr [ 2 ] = { } ;
u8 * headerbuf ;
int ret ;
headerbuf = kmalloc ( 1 , GFP_KERNEL ) ;
if ( ! headerbuf )
return - ENOMEM ;
headerbuf [ 0 ] = header ;
tr [ 0 ] . tx_buf = headerbuf ;
tr [ 0 ] . len = 1 ;
/* Stack allocated tx? */
if ( tx & & len < = 32 ) {
2018-11-29 06:00:33 +00:00
txbuf = kmemdup ( tx , len , GFP_KERNEL ) ;
2017-06-08 17:14:35 +02:00
if ( ! txbuf ) {
ret = - ENOMEM ;
goto out_free ;
}
}
if ( rx ) {
rxbuf = kmalloc ( len , GFP_KERNEL ) ;
if ( ! rxbuf ) {
ret = - ENOMEM ;
goto out_free ;
}
}
tr [ 1 ] . tx_buf = txbuf ? txbuf : tx ;
tr [ 1 ] . rx_buf = rxbuf ;
tr [ 1 ] . len = len ;
ndelay ( 80 ) ;
ret = spi_sync_transfer ( spi , tr , 2 ) ;
if ( rx & & ! ret )
memcpy ( rx , rxbuf , len ) ;
out_free :
kfree ( headerbuf ) ;
kfree ( txbuf ) ;
kfree ( rxbuf ) ;
return ret ;
}
static int repaper_write_buf ( struct spi_device * spi , u8 reg ,
const u8 * buf , size_t len )
{
int ret ;
ret = repaper_spi_transfer ( spi , 0x70 , & reg , NULL , 1 ) ;
if ( ret )
return ret ;
return repaper_spi_transfer ( spi , 0x72 , buf , NULL , len ) ;
}
static int repaper_write_val ( struct spi_device * spi , u8 reg , u8 val )
{
return repaper_write_buf ( spi , reg , & val , 1 ) ;
}
static int repaper_read_val ( struct spi_device * spi , u8 reg )
{
int ret ;
u8 val ;
ret = repaper_spi_transfer ( spi , 0x70 , & reg , NULL , 1 ) ;
if ( ret )
return ret ;
ret = repaper_spi_transfer ( spi , 0x73 , NULL , & val , 1 ) ;
return ret ? ret : val ;
}
static int repaper_read_id ( struct spi_device * spi )
{
int ret ;
u8 id ;
ret = repaper_spi_transfer ( spi , 0x71 , NULL , & id , 1 ) ;
return ret ? ret : id ;
}
static void repaper_spi_mosi_low ( struct spi_device * spi )
{
const u8 buf [ 1 ] = { 0 } ;
spi_write ( spi , buf , 1 ) ;
}
/* pixels on display are numbered from 1 so even is actually bits 1,3,5,... */
static void repaper_even_pixels ( struct repaper_epd * epd , u8 * * pp ,
const u8 * data , u8 fixed_value , const u8 * mask ,
enum repaper_stage stage )
{
unsigned int b ;
for ( b = 0 ; b < ( epd - > width / 8 ) ; b + + ) {
if ( data ) {
u8 pixels = data [ b ] & 0xaa ;
u8 pixel_mask = 0xff ;
u8 p1 , p2 , p3 , p4 ;
if ( mask ) {
pixel_mask = ( mask [ b ] ^ pixels ) & 0xaa ;
pixel_mask | = pixel_mask > > 1 ;
}
switch ( stage ) {
case REPAPER_COMPENSATE : /* B -> W, W -> B (Current) */
pixels = 0xaa | ( ( pixels ^ 0xaa ) > > 1 ) ;
break ;
case REPAPER_WHITE : /* B -> N, W -> W (Current) */
pixels = 0x55 + ( ( pixels ^ 0xaa ) > > 1 ) ;
break ;
case REPAPER_INVERSE : /* B -> N, W -> B (New) */
pixels = 0x55 | ( pixels ^ 0xaa ) ;
break ;
case REPAPER_NORMAL : /* B -> B, W -> W (New) */
pixels = 0xaa | ( pixels > > 1 ) ;
break ;
}
pixels = ( pixels & pixel_mask ) | ( ~ pixel_mask & 0x55 ) ;
p1 = ( pixels > > 6 ) & 0x03 ;
p2 = ( pixels > > 4 ) & 0x03 ;
p3 = ( pixels > > 2 ) & 0x03 ;
p4 = ( pixels > > 0 ) & 0x03 ;
pixels = ( p1 < < 0 ) | ( p2 < < 2 ) | ( p3 < < 4 ) | ( p4 < < 6 ) ;
* ( * pp ) + + = pixels ;
} else {
* ( * pp ) + + = fixed_value ;
}
}
}
/* pixels on display are numbered from 1 so odd is actually bits 0,2,4,... */
static void repaper_odd_pixels ( struct repaper_epd * epd , u8 * * pp ,
const u8 * data , u8 fixed_value , const u8 * mask ,
enum repaper_stage stage )
{
unsigned int b ;
for ( b = epd - > width / 8 ; b > 0 ; b - - ) {
if ( data ) {
u8 pixels = data [ b - 1 ] & 0x55 ;
u8 pixel_mask = 0xff ;
if ( mask ) {
pixel_mask = ( mask [ b - 1 ] ^ pixels ) & 0x55 ;
pixel_mask | = pixel_mask < < 1 ;
}
switch ( stage ) {
case REPAPER_COMPENSATE : /* B -> W, W -> B (Current) */
pixels = 0xaa | ( pixels ^ 0x55 ) ;
break ;
case REPAPER_WHITE : /* B -> N, W -> W (Current) */
pixels = 0x55 + ( pixels ^ 0x55 ) ;
break ;
case REPAPER_INVERSE : /* B -> N, W -> B (New) */
pixels = 0x55 | ( ( pixels ^ 0x55 ) < < 1 ) ;
break ;
case REPAPER_NORMAL : /* B -> B, W -> W (New) */
pixels = 0xaa | pixels ;
break ;
}
pixels = ( pixels & pixel_mask ) | ( ~ pixel_mask & 0x55 ) ;
* ( * pp ) + + = pixels ;
} else {
* ( * pp ) + + = fixed_value ;
}
}
}
/* interleave bits: (byte)76543210 -> (16 bit).7.6.5.4.3.2.1 */
static inline u16 repaper_interleave_bits ( u16 value )
{
value = ( value | ( value < < 4 ) ) & 0x0f0f ;
value = ( value | ( value < < 2 ) ) & 0x3333 ;
value = ( value | ( value < < 1 ) ) & 0x5555 ;
return value ;
}
/* pixels on display are numbered from 1 */
static void repaper_all_pixels ( struct repaper_epd * epd , u8 * * pp ,
const u8 * data , u8 fixed_value , const u8 * mask ,
enum repaper_stage stage )
{
unsigned int b ;
for ( b = epd - > width / 8 ; b > 0 ; b - - ) {
if ( data ) {
u16 pixels = repaper_interleave_bits ( data [ b - 1 ] ) ;
u16 pixel_mask = 0xffff ;
if ( mask ) {
pixel_mask = repaper_interleave_bits ( mask [ b - 1 ] ) ;
pixel_mask = ( pixel_mask ^ pixels ) & 0x5555 ;
pixel_mask | = pixel_mask < < 1 ;
}
switch ( stage ) {
case REPAPER_COMPENSATE : /* B -> W, W -> B (Current) */
pixels = 0xaaaa | ( pixels ^ 0x5555 ) ;
break ;
case REPAPER_WHITE : /* B -> N, W -> W (Current) */
pixels = 0x5555 + ( pixels ^ 0x5555 ) ;
break ;
case REPAPER_INVERSE : /* B -> N, W -> B (New) */
pixels = 0x5555 | ( ( pixels ^ 0x5555 ) < < 1 ) ;
break ;
case REPAPER_NORMAL : /* B -> B, W -> W (New) */
pixels = 0xaaaa | pixels ;
break ;
}
pixels = ( pixels & pixel_mask ) | ( ~ pixel_mask & 0x5555 ) ;
* ( * pp ) + + = pixels > > 8 ;
* ( * pp ) + + = pixels ;
} else {
* ( * pp ) + + = fixed_value ;
* ( * pp ) + + = fixed_value ;
}
}
}
/* output one line of scan and data bytes to the display */
static void repaper_one_line ( struct repaper_epd * epd , unsigned int line ,
const u8 * data , u8 fixed_value , const u8 * mask ,
enum repaper_stage stage )
{
u8 * p = epd - > line_buffer ;
unsigned int b ;
repaper_spi_mosi_low ( epd - > spi ) ;
if ( epd - > pre_border_byte )
* p + + = 0x00 ;
if ( epd - > middle_scan ) {
/* data bytes */
repaper_odd_pixels ( epd , & p , data , fixed_value , mask , stage ) ;
/* scan line */
for ( b = epd - > bytes_per_scan ; b > 0 ; b - - ) {
if ( line / 4 = = b - 1 )
* p + + = 0x03 < < ( 2 * ( line & 0x03 ) ) ;
else
* p + + = 0x00 ;
}
/* data bytes */
repaper_even_pixels ( epd , & p , data , fixed_value , mask , stage ) ;
} else {
/*
* even scan line , but as lines on display are numbered from 1 ,
* line : 1 , 3 , 5 , . . .
*/
for ( b = 0 ; b < epd - > bytes_per_scan ; b + + ) {
if ( 0 ! = ( line & 0x01 ) & & line / 8 = = b )
* p + + = 0xc0 > > ( line & 0x06 ) ;
else
* p + + = 0x00 ;
}
/* data bytes */
repaper_all_pixels ( epd , & p , data , fixed_value , mask , stage ) ;
/*
* odd scan line , but as lines on display are numbered from 1 ,
* line : 0 , 2 , 4 , 6 , . . .
*/
for ( b = epd - > bytes_per_scan ; b > 0 ; b - - ) {
if ( 0 = = ( line & 0x01 ) & & line / 8 = = b - 1 )
* p + + = 0x03 < < ( line & 0x06 ) ;
else
* p + + = 0x00 ;
}
}
switch ( epd - > border_byte ) {
case REPAPER_BORDER_BYTE_NONE :
break ;
case REPAPER_BORDER_BYTE_ZERO :
* p + + = 0x00 ;
break ;
case REPAPER_BORDER_BYTE_SET :
switch ( stage ) {
case REPAPER_COMPENSATE :
case REPAPER_WHITE :
case REPAPER_INVERSE :
* p + + = 0x00 ;
break ;
case REPAPER_NORMAL :
* p + + = 0xaa ;
break ;
}
break ;
}
repaper_write_buf ( epd - > spi , 0x0a , epd - > line_buffer ,
p - epd - > line_buffer ) ;
/* Output data to panel */
repaper_write_val ( epd - > spi , 0x02 , 0x07 ) ;
repaper_spi_mosi_low ( epd - > spi ) ;
}
static void repaper_frame_fixed ( struct repaper_epd * epd , u8 fixed_value ,
enum repaper_stage stage )
{
unsigned int line ;
for ( line = 0 ; line < epd - > height ; line + + )
repaper_one_line ( epd , line , NULL , fixed_value , NULL , stage ) ;
}
static void repaper_frame_data ( struct repaper_epd * epd , const u8 * image ,
const u8 * mask , enum repaper_stage stage )
{
unsigned int line ;
if ( ! mask ) {
for ( line = 0 ; line < epd - > height ; line + + ) {
repaper_one_line ( epd , line ,
& image [ line * ( epd - > width / 8 ) ] ,
0 , NULL , stage ) ;
}
} else {
for ( line = 0 ; line < epd - > height ; line + + ) {
size_t n = line * epd - > width / 8 ;
repaper_one_line ( epd , line , & image [ n ] , 0 , & mask [ n ] ,
stage ) ;
}
}
}
static void repaper_frame_fixed_repeat ( struct repaper_epd * epd , u8 fixed_value ,
enum repaper_stage stage )
{
u64 start = local_clock ( ) ;
u64 end = start + ( epd - > factored_stage_time * 1000 * 1000 ) ;
do {
repaper_frame_fixed ( epd , fixed_value , stage ) ;
} while ( local_clock ( ) < end ) ;
}
static void repaper_frame_data_repeat ( struct repaper_epd * epd , const u8 * image ,
const u8 * mask , enum repaper_stage stage )
{
u64 start = local_clock ( ) ;
u64 end = start + ( epd - > factored_stage_time * 1000 * 1000 ) ;
do {
repaper_frame_data ( epd , image , mask , stage ) ;
} while ( local_clock ( ) < end ) ;
}
static void repaper_get_temperature ( struct repaper_epd * epd )
{
int ret , temperature = 0 ;
unsigned int factor10x ;
if ( ! epd - > thermal )
return ;
ret = thermal_zone_get_temp ( epd - > thermal , & temperature ) ;
if ( ret ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( & epd - > spi - > dev , " Failed to get temperature (%d) \n " , ret ) ;
2017-06-08 17:14:35 +02:00
return ;
}
temperature / = 1000 ;
if ( temperature < = - 10 )
factor10x = 170 ;
else if ( temperature < = - 5 )
factor10x = 120 ;
else if ( temperature < = 5 )
factor10x = 80 ;
else if ( temperature < = 10 )
factor10x = 40 ;
else if ( temperature < = 15 )
factor10x = 30 ;
else if ( temperature < = 20 )
factor10x = 20 ;
else if ( temperature < = 40 )
factor10x = 10 ;
else
factor10x = 7 ;
epd - > factored_stage_time = epd - > stage_time * factor10x / 10 ;
}
2019-01-15 05:36:42 +01:00
static int repaper_fb_dirty ( struct drm_framebuffer * fb )
2017-06-08 17:14:35 +02:00
{
2017-08-07 12:39:37 -05:00
struct drm_gem_cma_object * cma_obj = drm_fb_cma_get_gem_obj ( fb , 0 ) ;
2019-02-25 15:42:29 +01:00
struct repaper_epd * epd = drm_to_epd ( fb - > dev ) ;
2019-01-15 05:36:41 +01:00
struct drm_rect clip ;
2019-02-25 15:42:32 +01:00
int idx , ret = 0 ;
2017-06-08 17:14:35 +02:00
u8 * buf = NULL ;
2019-02-25 15:42:32 +01:00
if ( ! drm_dev_enter ( fb - > dev , & idx ) )
return - ENODEV ;
2017-06-08 17:14:35 +02:00
2017-08-07 12:39:37 -05:00
/* repaper can't do partial updates */
clip . x1 = 0 ;
clip . x2 = fb - > width ;
clip . y1 = 0 ;
clip . y2 = fb - > height ;
2017-06-08 17:14:35 +02:00
repaper_get_temperature ( epd ) ;
DRM_DEBUG ( " Flushing [FB:%d] st=%ums \n " , fb - > base . id ,
epd - > factored_stage_time ) ;
treewide: kmalloc() -> kmalloc_array()
The kmalloc() function has a 2-factor argument form, kmalloc_array(). This
patch replaces cases of:
kmalloc(a * b, gfp)
with:
kmalloc_array(a * b, gfp)
as well as handling cases of:
kmalloc(a * b * c, gfp)
with:
kmalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kmalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kmalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The tools/ directory was manually excluded, since it has its own
implementation of kmalloc().
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kmalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kmalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kmalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kmalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kmalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kmalloc
+ kmalloc_array
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kmalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kmalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kmalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kmalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kmalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kmalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kmalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kmalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kmalloc(C1 * C2 * C3, ...)
|
kmalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kmalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kmalloc(sizeof(THING) * C2, ...)
|
kmalloc(sizeof(TYPE) * C2, ...)
|
kmalloc(C1 * C2 * C3, ...)
|
kmalloc(C1 * C2, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kmalloc
+ kmalloc_array
(
- (E1) * E2
+ E1, E2
, ...)
|
- kmalloc
+ kmalloc_array
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kmalloc
+ kmalloc_array
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-12 13:55:00 -07:00
buf = kmalloc_array ( fb - > width , fb - > height , GFP_KERNEL ) ;
2019-02-25 15:42:32 +01:00
if ( ! buf ) {
ret = - ENOMEM ;
goto out_exit ;
}
2017-06-08 17:14:35 +02:00
2021-07-16 16:08:00 +02:00
ret = drm_gem_fb_begin_cpu_access ( fb , DMA_FROM_DEVICE ) ;
if ( ret )
goto out_free ;
2017-08-07 12:39:37 -05:00
2022-03-17 09:18:26 +01:00
drm_fb_xrgb8888_to_mono ( buf , 0 , cma_obj - > vaddr , fb , & clip ) ;
2017-08-07 12:39:37 -05:00
2021-07-16 16:08:00 +02:00
drm_gem_fb_end_cpu_access ( fb , DMA_FROM_DEVICE ) ;
2017-06-08 17:14:35 +02:00
if ( epd - > partial ) {
repaper_frame_data_repeat ( epd , buf , epd - > current_frame ,
REPAPER_NORMAL ) ;
} else if ( epd - > cleared ) {
repaper_frame_data_repeat ( epd , epd - > current_frame , NULL ,
REPAPER_COMPENSATE ) ;
repaper_frame_data_repeat ( epd , epd - > current_frame , NULL ,
REPAPER_WHITE ) ;
repaper_frame_data_repeat ( epd , buf , NULL , REPAPER_INVERSE ) ;
repaper_frame_data_repeat ( epd , buf , NULL , REPAPER_NORMAL ) ;
epd - > partial = true ;
} else {
/* Clear display (anything -> white) */
repaper_frame_fixed_repeat ( epd , 0xff , REPAPER_COMPENSATE ) ;
repaper_frame_fixed_repeat ( epd , 0xff , REPAPER_WHITE ) ;
repaper_frame_fixed_repeat ( epd , 0xaa , REPAPER_INVERSE ) ;
repaper_frame_fixed_repeat ( epd , 0xaa , REPAPER_NORMAL ) ;
/* Assuming a clear (white) screen output an image */
repaper_frame_fixed_repeat ( epd , 0xaa , REPAPER_COMPENSATE ) ;
repaper_frame_fixed_repeat ( epd , 0xaa , REPAPER_WHITE ) ;
repaper_frame_data_repeat ( epd , buf , NULL , REPAPER_INVERSE ) ;
repaper_frame_data_repeat ( epd , buf , NULL , REPAPER_NORMAL ) ;
epd - > cleared = true ;
epd - > partial = true ;
}
memcpy ( epd - > current_frame , buf , fb - > width * fb - > height / 8 ) ;
/*
* An extra frame write is needed if pixels are set in the bottom line ,
* or else grey lines rises up from the pixels
*/
if ( epd - > pre_border_byte ) {
unsigned int x ;
for ( x = 0 ; x < ( fb - > width / 8 ) ; x + + )
if ( buf [ x + ( fb - > width * ( fb - > height - 1 ) / 8 ) ] ) {
repaper_frame_data_repeat ( epd , buf ,
epd - > current_frame ,
REPAPER_NORMAL ) ;
break ;
}
}
2018-03-23 17:35:09 +02:00
out_free :
2017-06-08 17:14:35 +02:00
kfree ( buf ) ;
2019-02-25 15:42:32 +01:00
out_exit :
drm_dev_exit ( idx ) ;
2017-06-08 17:14:35 +02:00
return ret ;
}
static void power_off ( struct repaper_epd * epd )
{
/* Turn off power and all signals */
gpiod_set_value_cansleep ( epd - > reset , 0 ) ;
gpiod_set_value_cansleep ( epd - > panel_on , 0 ) ;
if ( epd - > border )
gpiod_set_value_cansleep ( epd - > border , 0 ) ;
/* Ensure SPI MOSI and CLOCK are Low before CS Low */
repaper_spi_mosi_low ( epd - > spi ) ;
/* Discharge pulse */
gpiod_set_value_cansleep ( epd - > discharge , 1 ) ;
msleep ( 150 ) ;
gpiod_set_value_cansleep ( epd - > discharge , 0 ) ;
}
static void repaper_pipe_enable ( struct drm_simple_display_pipe * pipe ,
2018-03-22 22:27:37 +02:00
struct drm_crtc_state * crtc_state ,
struct drm_plane_state * plane_state )
2017-06-08 17:14:35 +02:00
{
2019-02-25 15:42:29 +01:00
struct repaper_epd * epd = drm_to_epd ( pipe - > crtc . dev ) ;
2017-06-08 17:14:35 +02:00
struct spi_device * spi = epd - > spi ;
struct device * dev = & spi - > dev ;
bool dc_ok = false ;
2019-02-25 15:42:32 +01:00
int i , ret , idx ;
if ( ! drm_dev_enter ( pipe - > crtc . dev , & idx ) )
return ;
2017-06-08 17:14:35 +02:00
DRM_DEBUG_DRIVER ( " \n " ) ;
/* Power up sequence */
gpiod_set_value_cansleep ( epd - > reset , 0 ) ;
gpiod_set_value_cansleep ( epd - > panel_on , 0 ) ;
gpiod_set_value_cansleep ( epd - > discharge , 0 ) ;
if ( epd - > border )
gpiod_set_value_cansleep ( epd - > border , 0 ) ;
repaper_spi_mosi_low ( spi ) ;
usleep_range ( 5000 , 10000 ) ;
gpiod_set_value_cansleep ( epd - > panel_on , 1 ) ;
/*
* This delay comes from the repaper . org userspace driver , it ' s not
* mentioned in the datasheet .
*/
usleep_range ( 10000 , 15000 ) ;
gpiod_set_value_cansleep ( epd - > reset , 1 ) ;
if ( epd - > border )
gpiod_set_value_cansleep ( epd - > border , 1 ) ;
usleep_range ( 5000 , 10000 ) ;
gpiod_set_value_cansleep ( epd - > reset , 0 ) ;
usleep_range ( 5000 , 10000 ) ;
gpiod_set_value_cansleep ( epd - > reset , 1 ) ;
usleep_range ( 5000 , 10000 ) ;
/* Wait for COG to become ready */
for ( i = 100 ; i > 0 ; i - - ) {
if ( ! gpiod_get_value_cansleep ( epd - > busy ) )
break ;
usleep_range ( 10 , 100 ) ;
}
if ( ! i ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " timeout waiting for panel to become ready. \n " ) ;
2017-06-08 17:14:35 +02:00
power_off ( epd ) ;
2019-02-25 15:42:32 +01:00
goto out_exit ;
2017-06-08 17:14:35 +02:00
}
repaper_read_id ( spi ) ;
ret = repaper_read_id ( spi ) ;
if ( ret ! = REPAPER_RID_G2_COG_ID ) {
if ( ret < 0 )
dev_err ( dev , " failed to read chip (%d) \n " , ret ) ;
else
dev_err ( dev , " wrong COG ID 0x%02x \n " , ret ) ;
power_off ( epd ) ;
2019-02-25 15:42:32 +01:00
goto out_exit ;
2017-06-08 17:14:35 +02:00
}
/* Disable OE */
repaper_write_val ( spi , 0x02 , 0x40 ) ;
ret = repaper_read_val ( spi , 0x0f ) ;
if ( ret < 0 | | ! ( ret & 0x80 ) ) {
if ( ret < 0 )
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " failed to read chip (%d) \n " , ret ) ;
2017-06-08 17:14:35 +02:00
else
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " panel is reported broken \n " ) ;
2017-06-08 17:14:35 +02:00
power_off ( epd ) ;
2019-02-25 15:42:32 +01:00
goto out_exit ;
2017-06-08 17:14:35 +02:00
}
/* Power saving mode */
repaper_write_val ( spi , 0x0b , 0x02 ) ;
/* Channel select */
repaper_write_buf ( spi , 0x01 , epd - > channel_select , 8 ) ;
/* High power mode osc */
repaper_write_val ( spi , 0x07 , 0xd1 ) ;
/* Power setting */
repaper_write_val ( spi , 0x08 , 0x02 ) ;
/* Vcom level */
repaper_write_val ( spi , 0x09 , 0xc2 ) ;
/* Power setting */
repaper_write_val ( spi , 0x04 , 0x03 ) ;
/* Driver latch on */
repaper_write_val ( spi , 0x03 , 0x01 ) ;
/* Driver latch off */
repaper_write_val ( spi , 0x03 , 0x00 ) ;
usleep_range ( 5000 , 10000 ) ;
/* Start chargepump */
for ( i = 0 ; i < 4 ; + + i ) {
/* Charge pump positive voltage on - VGH/VDL on */
repaper_write_val ( spi , 0x05 , 0x01 ) ;
msleep ( 240 ) ;
/* Charge pump negative voltage on - VGL/VDL on */
repaper_write_val ( spi , 0x05 , 0x03 ) ;
msleep ( 40 ) ;
/* Charge pump Vcom on - Vcom driver on */
repaper_write_val ( spi , 0x05 , 0x0f ) ;
msleep ( 40 ) ;
/* check DC/DC */
ret = repaper_read_val ( spi , 0x0f ) ;
if ( ret < 0 ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " failed to read chip (%d) \n " , ret ) ;
2017-06-08 17:14:35 +02:00
power_off ( epd ) ;
2019-02-25 15:42:32 +01:00
goto out_exit ;
2017-06-08 17:14:35 +02:00
}
if ( ret & 0x40 ) {
dc_ok = true ;
break ;
}
}
if ( ! dc_ok ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " dc/dc failed \n " ) ;
2017-06-08 17:14:35 +02:00
power_off ( epd ) ;
2019-02-25 15:42:32 +01:00
goto out_exit ;
2017-06-08 17:14:35 +02:00
}
/*
* Output enable to disable
* The userspace driver sets this to 0x04 , but the datasheet says 0x06
*/
repaper_write_val ( spi , 0x02 , 0x04 ) ;
epd - > partial = false ;
2019-02-25 15:42:32 +01:00
out_exit :
drm_dev_exit ( idx ) ;
2017-06-08 17:14:35 +02:00
}
static void repaper_pipe_disable ( struct drm_simple_display_pipe * pipe )
{
2019-02-25 15:42:29 +01:00
struct repaper_epd * epd = drm_to_epd ( pipe - > crtc . dev ) ;
2017-06-08 17:14:35 +02:00
struct spi_device * spi = epd - > spi ;
unsigned int line ;
2019-02-25 15:42:32 +01:00
/*
* This callback is not protected by drm_dev_enter / exit since we want to
* turn off the display on regular driver unload . It ' s highly unlikely
* that the underlying SPI controller is gone should this be called after
* unplug .
*/
2017-06-08 17:14:35 +02:00
DRM_DEBUG_DRIVER ( " \n " ) ;
/* Nothing frame */
for ( line = 0 ; line < epd - > height ; line + + )
repaper_one_line ( epd , 0x7fffu , NULL , 0x00 , NULL ,
REPAPER_COMPENSATE ) ;
/* 2.7" */
if ( epd - > border ) {
/* Dummy line */
repaper_one_line ( epd , 0x7fffu , NULL , 0x00 , NULL ,
REPAPER_COMPENSATE ) ;
msleep ( 25 ) ;
gpiod_set_value_cansleep ( epd - > border , 0 ) ;
msleep ( 200 ) ;
gpiod_set_value_cansleep ( epd - > border , 1 ) ;
} else {
/* Border dummy line */
repaper_one_line ( epd , 0x7fffu , NULL , 0x00 , NULL ,
REPAPER_NORMAL ) ;
msleep ( 200 ) ;
}
/* not described in datasheet */
repaper_write_val ( spi , 0x0b , 0x00 ) ;
/* Latch reset turn on */
repaper_write_val ( spi , 0x03 , 0x01 ) ;
/* Power off charge pump Vcom */
repaper_write_val ( spi , 0x05 , 0x03 ) ;
/* Power off charge pump neg voltage */
repaper_write_val ( spi , 0x05 , 0x01 ) ;
msleep ( 120 ) ;
/* Discharge internal */
repaper_write_val ( spi , 0x04 , 0x80 ) ;
/* turn off all charge pumps */
repaper_write_val ( spi , 0x05 , 0x00 ) ;
/* Turn off osc */
repaper_write_val ( spi , 0x07 , 0x01 ) ;
msleep ( 50 ) ;
power_off ( epd ) ;
}
2019-01-15 05:36:42 +01:00
static void repaper_pipe_update ( struct drm_simple_display_pipe * pipe ,
struct drm_plane_state * old_state )
{
struct drm_plane_state * state = pipe - > plane . state ;
struct drm_rect rect ;
2020-06-12 18:00:56 +02:00
if ( ! pipe - > crtc . state - > active )
return ;
2019-01-15 05:36:42 +01:00
if ( drm_atomic_helper_damage_merged ( old_state , state , & rect ) )
repaper_fb_dirty ( state - > fb ) ;
}
2017-06-08 17:14:35 +02:00
static const struct drm_simple_display_pipe_funcs repaper_pipe_funcs = {
. enable = repaper_pipe_enable ,
. disable = repaper_pipe_disable ,
2019-01-15 05:36:42 +01:00
. update = repaper_pipe_update ,
2017-06-08 17:14:35 +02:00
} ;
2019-07-19 17:59:14 +02:00
static int repaper_connector_get_modes ( struct drm_connector * connector )
{
struct repaper_epd * epd = drm_to_epd ( connector - > dev ) ;
struct drm_display_mode * mode ;
mode = drm_mode_duplicate ( connector - > dev , epd - > mode ) ;
if ( ! mode ) {
DRM_ERROR ( " Failed to duplicate mode \n " ) ;
return 0 ;
}
drm_mode_set_name ( mode ) ;
mode - > type | = DRM_MODE_TYPE_PREFERRED ;
drm_mode_probed_add ( connector , mode ) ;
connector - > display_info . width_mm = mode - > width_mm ;
connector - > display_info . height_mm = mode - > height_mm ;
return 1 ;
}
static const struct drm_connector_helper_funcs repaper_connector_hfuncs = {
. get_modes = repaper_connector_get_modes ,
} ;
static const struct drm_connector_funcs repaper_connector_funcs = {
. reset = drm_atomic_helper_connector_reset ,
. fill_modes = drm_helper_probe_single_connector_modes ,
. destroy = drm_connector_cleanup ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
} ;
2019-02-25 15:42:29 +01:00
static const struct drm_mode_config_funcs repaper_mode_config_funcs = {
. fb_create = drm_gem_fb_create_with_dirty ,
. atomic_check = drm_atomic_helper_check ,
. atomic_commit = drm_atomic_helper_commit ,
} ;
2017-06-08 17:14:35 +02:00
static const uint32_t repaper_formats [ ] = {
DRM_FORMAT_XRGB8888 ,
} ;
static const struct drm_display_mode repaper_e1144cs021_mode = {
2019-02-10 14:10:31 +01:00
DRM_SIMPLE_MODE ( 128 , 96 , 29 , 22 ) ,
2017-06-08 17:14:35 +02:00
} ;
static const u8 repaper_e1144cs021_cs [ ] = { 0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x0f , 0xff , 0x00 } ;
static const struct drm_display_mode repaper_e1190cs021_mode = {
2019-02-10 14:10:31 +01:00
DRM_SIMPLE_MODE ( 144 , 128 , 36 , 32 ) ,
2017-06-08 17:14:35 +02:00
} ;
static const u8 repaper_e1190cs021_cs [ ] = { 0x00 , 0x00 , 0x00 , 0x03 ,
0xfc , 0x00 , 0x00 , 0xff } ;
static const struct drm_display_mode repaper_e2200cs021_mode = {
2019-02-10 14:10:31 +01:00
DRM_SIMPLE_MODE ( 200 , 96 , 46 , 22 ) ,
2017-06-08 17:14:35 +02:00
} ;
static const u8 repaper_e2200cs021_cs [ ] = { 0x00 , 0x00 , 0x00 , 0x00 ,
0x01 , 0xff , 0xe0 , 0x00 } ;
static const struct drm_display_mode repaper_e2271cs021_mode = {
2019-02-10 14:10:31 +01:00
DRM_SIMPLE_MODE ( 264 , 176 , 57 , 38 ) ,
2017-06-08 17:14:35 +02:00
} ;
static const u8 repaper_e2271cs021_cs [ ] = { 0x00 , 0x00 , 0x00 , 0x7f ,
0xff , 0xfe , 0x00 , 0x00 } ;
DEFINE_DRM_GEM_CMA_FOPS ( repaper_fops ) ;
2020-11-04 11:04:24 +01:00
static const struct drm_driver repaper_driver = {
2019-06-17 17:39:24 +02:00
. driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC ,
2017-06-08 17:14:35 +02:00
. fops = & repaper_fops ,
2020-06-05 09:32:06 +02:00
DRM_GEM_CMA_DRIVER_OPS_VMAP ,
2017-06-08 17:14:35 +02:00
. name = " repaper " ,
. desc = " Pervasive Displays RePaper e-ink panels " ,
. date = " 20170405 " ,
. major = 1 ,
. minor = 0 ,
} ;
static const struct of_device_id repaper_of_match [ ] = {
{ . compatible = " pervasive,e1144cs021 " , . data = ( void * ) E1144CS021 } ,
{ . compatible = " pervasive,e1190cs021 " , . data = ( void * ) E1190CS021 } ,
{ . compatible = " pervasive,e2200cs021 " , . data = ( void * ) E2200CS021 } ,
{ . compatible = " pervasive,e2271cs021 " , . data = ( void * ) E2271CS021 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , repaper_of_match ) ;
static const struct spi_device_id repaper_id [ ] = {
{ " e1144cs021 " , E1144CS021 } ,
{ " e1190cs021 " , E1190CS021 } ,
{ " e2200cs021 " , E2200CS021 } ,
{ " e2271cs021 " , E2271CS021 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( spi , repaper_id ) ;
static int repaper_probe ( struct spi_device * spi )
{
const struct drm_display_mode * mode ;
const struct spi_device_id * spi_id ;
struct device * dev = & spi - > dev ;
enum repaper_model model ;
const char * thermal_zone ;
struct repaper_epd * epd ;
size_t line_buffer_size ;
2019-02-25 15:42:29 +01:00
struct drm_device * drm ;
2020-01-31 22:49:22 +02:00
const void * match ;
2017-06-08 17:14:35 +02:00
int ret ;
2020-01-31 22:49:22 +02:00
match = device_get_match_data ( dev ) ;
2017-06-08 17:14:35 +02:00
if ( match ) {
2020-01-31 22:49:22 +02:00
model = ( enum repaper_model ) match ;
2017-06-08 17:14:35 +02:00
} else {
spi_id = spi_get_device_id ( spi ) ;
2020-01-31 22:49:22 +02:00
model = ( enum repaper_model ) spi_id - > driver_data ;
2017-06-08 17:14:35 +02:00
}
/* The SPI device is used to allocate dma memory */
if ( ! dev - > coherent_dma_mask ) {
ret = dma_coerce_mask_and_coherent ( dev , DMA_BIT_MASK ( 32 ) ) ;
if ( ret ) {
dev_warn ( dev , " Failed to set dma mask %d \n " , ret ) ;
return ret ;
}
}
2020-04-15 09:39:50 +02:00
epd = devm_drm_dev_alloc ( dev , & repaper_driver ,
struct repaper_epd , drm ) ;
if ( IS_ERR ( epd ) )
return PTR_ERR ( epd ) ;
2017-06-08 17:14:35 +02:00
2019-02-25 15:42:29 +01:00
drm = & epd - > drm ;
2020-03-23 15:49:45 +01:00
ret = drmm_mode_config_init ( drm ) ;
if ( ret )
return ret ;
2019-02-25 15:42:29 +01:00
drm - > mode_config . funcs = & repaper_mode_config_funcs ;
2017-06-08 17:14:35 +02:00
epd - > spi = spi ;
epd - > panel_on = devm_gpiod_get ( dev , " panel-on " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( epd - > panel_on ) ) {
ret = PTR_ERR ( epd - > panel_on ) ;
if ( ret ! = - EPROBE_DEFER )
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'panel-on' \n " ) ;
2017-06-08 17:14:35 +02:00
return ret ;
}
epd - > discharge = devm_gpiod_get ( dev , " discharge " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( epd - > discharge ) ) {
ret = PTR_ERR ( epd - > discharge ) ;
if ( ret ! = - EPROBE_DEFER )
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'discharge' \n " ) ;
2017-06-08 17:14:35 +02:00
return ret ;
}
epd - > reset = devm_gpiod_get ( dev , " reset " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( epd - > reset ) ) {
ret = PTR_ERR ( epd - > reset ) ;
if ( ret ! = - EPROBE_DEFER )
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'reset' \n " ) ;
2017-06-08 17:14:35 +02:00
return ret ;
}
epd - > busy = devm_gpiod_get ( dev , " busy " , GPIOD_IN ) ;
if ( IS_ERR ( epd - > busy ) ) {
ret = PTR_ERR ( epd - > busy ) ;
if ( ret ! = - EPROBE_DEFER )
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'busy' \n " ) ;
2017-06-08 17:14:35 +02:00
return ret ;
}
if ( ! device_property_read_string ( dev , " pervasive,thermal-zone " ,
& thermal_zone ) ) {
epd - > thermal = thermal_zone_get_zone_by_name ( thermal_zone ) ;
if ( IS_ERR ( epd - > thermal ) ) {
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get thermal zone: %s \n " , thermal_zone ) ;
2017-06-08 17:14:35 +02:00
return PTR_ERR ( epd - > thermal ) ;
}
}
switch ( model ) {
case E1144CS021 :
mode = & repaper_e1144cs021_mode ;
epd - > channel_select = repaper_e1144cs021_cs ;
epd - > stage_time = 480 ;
epd - > bytes_per_scan = 96 / 4 ;
epd - > middle_scan = true ; /* data-scan-data */
epd - > pre_border_byte = false ;
epd - > border_byte = REPAPER_BORDER_BYTE_ZERO ;
break ;
case E1190CS021 :
mode = & repaper_e1190cs021_mode ;
epd - > channel_select = repaper_e1190cs021_cs ;
epd - > stage_time = 480 ;
epd - > bytes_per_scan = 128 / 4 / 2 ;
epd - > middle_scan = false ; /* scan-data-scan */
epd - > pre_border_byte = false ;
epd - > border_byte = REPAPER_BORDER_BYTE_SET ;
break ;
case E2200CS021 :
mode = & repaper_e2200cs021_mode ;
epd - > channel_select = repaper_e2200cs021_cs ;
epd - > stage_time = 480 ;
epd - > bytes_per_scan = 96 / 4 ;
epd - > middle_scan = true ; /* data-scan-data */
epd - > pre_border_byte = true ;
epd - > border_byte = REPAPER_BORDER_BYTE_NONE ;
break ;
case E2271CS021 :
epd - > border = devm_gpiod_get ( dev , " border " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( epd - > border ) ) {
ret = PTR_ERR ( epd - > border ) ;
if ( ret ! = - EPROBE_DEFER )
2017-10-07 03:47:38 +05:30
DRM_DEV_ERROR ( dev , " Failed to get gpio 'border' \n " ) ;
2017-06-08 17:14:35 +02:00
return ret ;
}
mode = & repaper_e2271cs021_mode ;
epd - > channel_select = repaper_e2271cs021_cs ;
epd - > stage_time = 630 ;
epd - > bytes_per_scan = 176 / 4 ;
epd - > middle_scan = true ; /* data-scan-data */
epd - > pre_border_byte = true ;
epd - > border_byte = REPAPER_BORDER_BYTE_NONE ;
break ;
default :
return - ENODEV ;
}
2019-07-19 17:59:14 +02:00
epd - > mode = mode ;
2017-06-08 17:14:35 +02:00
epd - > width = mode - > hdisplay ;
epd - > height = mode - > vdisplay ;
epd - > factored_stage_time = epd - > stage_time ;
line_buffer_size = 2 * epd - > width / 8 + epd - > bytes_per_scan + 2 ;
epd - > line_buffer = devm_kzalloc ( dev , line_buffer_size , GFP_KERNEL ) ;
if ( ! epd - > line_buffer )
return - ENOMEM ;
epd - > current_frame = devm_kzalloc ( dev , epd - > width * epd - > height / 8 ,
GFP_KERNEL ) ;
if ( ! epd - > current_frame )
return - ENOMEM ;
2019-07-19 17:59:14 +02:00
drm - > mode_config . min_width = mode - > hdisplay ;
drm - > mode_config . max_width = mode - > hdisplay ;
drm - > mode_config . min_height = mode - > vdisplay ;
drm - > mode_config . max_height = mode - > vdisplay ;
drm_connector_helper_add ( & epd - > connector , & repaper_connector_hfuncs ) ;
ret = drm_connector_init ( drm , & epd - > connector , & repaper_connector_funcs ,
DRM_MODE_CONNECTOR_SPI ) ;
if ( ret )
return ret ;
ret = drm_simple_display_pipe_init ( drm , & epd - > pipe , & repaper_pipe_funcs ,
repaper_formats , ARRAY_SIZE ( repaper_formats ) ,
NULL , & epd - > connector ) ;
2017-06-08 17:14:35 +02:00
if ( ret )
return ret ;
2019-02-25 15:42:29 +01:00
drm_mode_config_reset ( drm ) ;
ret = drm_dev_register ( drm , 0 ) ;
if ( ret )
return ret ;
2019-02-10 14:10:33 +01:00
2019-02-25 15:42:29 +01:00
spi_set_drvdata ( spi , drm ) ;
2017-06-08 17:14:35 +02:00
2017-09-08 17:07:26 +02:00
DRM_DEBUG_DRIVER ( " SPI speed: %uMHz \n " , spi - > max_speed_hz / 1000000 ) ;
2017-06-08 17:14:35 +02:00
2019-04-10 14:43:45 +02:00
drm_fbdev_generic_setup ( drm , 0 ) ;
2019-02-25 15:42:29 +01:00
return 0 ;
}
2022-01-23 18:52:01 +01:00
static void repaper_remove ( struct spi_device * spi )
2019-02-25 15:42:29 +01:00
{
struct drm_device * drm = spi_get_drvdata ( spi ) ;
drm_dev_unplug ( drm ) ;
drm_atomic_helper_shutdown ( drm ) ;
2017-06-08 17:14:35 +02:00
}
static void repaper_shutdown ( struct spi_device * spi )
{
2019-02-10 14:10:33 +01:00
drm_atomic_helper_shutdown ( spi_get_drvdata ( spi ) ) ;
2017-06-08 17:14:35 +02:00
}
static struct spi_driver repaper_spi_driver = {
. driver = {
. name = " repaper " ,
. of_match_table = repaper_of_match ,
} ,
. id_table = repaper_id ,
. probe = repaper_probe ,
2019-02-25 15:42:29 +01:00
. remove = repaper_remove ,
2017-06-08 17:14:35 +02:00
. shutdown = repaper_shutdown ,
} ;
module_spi_driver ( repaper_spi_driver ) ;
MODULE_DESCRIPTION ( " Pervasive Displays RePaper DRM driver " ) ;
MODULE_AUTHOR ( " Noralf Trønnes " ) ;
MODULE_LICENSE ( " GPL " ) ;