2008-08-17 08:59:32 +04:00
/*
* am200epd . c - - Platform device for AM200 EPD kit
*
* Copyright ( C ) 2008 , Jaya Kumar
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of this archive for
* more details .
*
* Layout is based on skeletonfb . c by James Simmons and Geert Uytterhoeven .
*
* This work was made possible by help and equipment support from E - Ink
* Corporation . http : //support.eink.com/community
*
* This driver is written to be used with the Metronome display controller .
* on the AM200 EPD prototype kit / development kit with an E - Ink 800 x600
* Vizplex EPD on a Gumstix board using the Lyre interface board .
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/fb.h>
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/irq.h>
# include <linux/gpio.h>
2015-01-30 12:45:33 +03:00
# include "pxa25x.h"
# include "gumstix.h"
2012-08-24 17:16:48 +04:00
# include <linux/platform_data/video-pxafb.h>
2008-08-17 08:59:32 +04:00
2008-11-30 00:23:13 +03:00
# include "generic.h"
2008-08-17 08:59:32 +04:00
# include <video/metronomefb.h>
static unsigned int panel_type = 6 ;
static struct platform_device * am200_device ;
static struct metronome_board am200_board ;
static struct pxafb_mode_info am200_fb_mode_9inch7 = {
. pixclock = 40000 ,
. xres = 1200 ,
. yres = 842 ,
. bpp = 16 ,
. hsync_len = 2 ,
. left_margin = 2 ,
. right_margin = 2 ,
. vsync_len = 1 ,
. upper_margin = 2 ,
. lower_margin = 25 ,
. sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT ,
} ;
static struct pxafb_mode_info am200_fb_mode_8inch = {
. pixclock = 40000 ,
. xres = 1088 ,
. yres = 791 ,
. bpp = 16 ,
. hsync_len = 28 ,
. left_margin = 8 ,
. right_margin = 30 ,
. vsync_len = 8 ,
. upper_margin = 10 ,
. lower_margin = 8 ,
. sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT ,
} ;
static struct pxafb_mode_info am200_fb_mode_6inch = {
. pixclock = 40189 ,
. xres = 832 ,
. yres = 622 ,
. bpp = 16 ,
. hsync_len = 28 ,
. left_margin = 34 ,
. right_margin = 34 ,
. vsync_len = 25 ,
. upper_margin = 0 ,
. lower_margin = 2 ,
. sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT ,
} ;
static struct pxafb_mach_info am200_fb_info = {
. modes = & am200_fb_mode_6inch ,
. num_modes = 1 ,
. lcd_conn = LCD_TYPE_COLOR_TFT | LCD_PCLK_EDGE_FALL |
LCD_AC_BIAS_FREQ ( 24 ) ,
} ;
/* register offsets for gpio control */
# define LED_GPIO_PIN 51
# define STDBY_GPIO_PIN 48
# define RST_GPIO_PIN 49
# define RDY_GPIO_PIN 32
# define ERR_GPIO_PIN 17
# define PCBPWR_GPIO_PIN 16
static int gpios [ ] = { LED_GPIO_PIN , STDBY_GPIO_PIN , RST_GPIO_PIN ,
RDY_GPIO_PIN , ERR_GPIO_PIN , PCBPWR_GPIO_PIN } ;
static char * gpio_names [ ] = { " LED " , " STDBY " , " RST " , " RDY " , " ERR " , " PCBPWR " } ;
static int am200_init_gpio_regs ( struct metronomefb_par * par )
{
int i ;
int err ;
for ( i = 0 ; i < ARRAY_SIZE ( gpios ) ; i + + ) {
err = gpio_request ( gpios [ i ] , gpio_names [ i ] ) ;
if ( err ) {
dev_err ( & am200_device - > dev , " failed requesting "
" gpio %s, err=%d \n " , gpio_names [ i ] , err ) ;
goto err_req_gpio ;
}
}
gpio_direction_output ( LED_GPIO_PIN , 0 ) ;
gpio_direction_output ( STDBY_GPIO_PIN , 0 ) ;
gpio_direction_output ( RST_GPIO_PIN , 0 ) ;
gpio_direction_input ( RDY_GPIO_PIN ) ;
gpio_direction_input ( ERR_GPIO_PIN ) ;
gpio_direction_output ( PCBPWR_GPIO_PIN , 0 ) ;
return 0 ;
err_req_gpio :
2011-03-07 08:55:15 +03:00
while ( - - i > = 0 )
gpio_free ( gpios [ i ] ) ;
2008-08-17 08:59:32 +04:00
return err ;
}
static void am200_cleanup ( struct metronomefb_par * par )
{
int i ;
2011-10-10 10:21:08 +04:00
free_irq ( PXA_GPIO_TO_IRQ ( RDY_GPIO_PIN ) , par ) ;
2008-08-17 08:59:32 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( gpios ) ; i + + )
gpio_free ( gpios [ i ] ) ;
}
static int am200_share_video_mem ( struct fb_info * info )
{
/* rough check if this is our desired fb and not something else */
if ( ( info - > var . xres ! = am200_fb_info . modes - > xres )
| | ( info - > var . yres ! = am200_fb_info . modes - > yres ) )
return 0 ;
/* we've now been notified that we have our new fb */
am200_board . metromem = info - > screen_base ;
am200_board . host_fbinfo = info ;
/* try to refcount host drv since we are the consumer after this */
if ( ! try_module_get ( info - > fbops - > owner ) )
return - ENODEV ;
return 0 ;
}
static int am200_unshare_video_mem ( struct fb_info * info )
{
dev_dbg ( & am200_device - > dev , " ENTER %s \n " , __func__ ) ;
if ( info ! = am200_board . host_fbinfo )
return 0 ;
module_put ( am200_board . host_fbinfo - > fbops - > owner ) ;
return 0 ;
}
static int am200_fb_notifier_callback ( struct notifier_block * self ,
unsigned long event , void * data )
{
struct fb_event * evdata = data ;
struct fb_info * info = evdata - > info ;
dev_dbg ( & am200_device - > dev , " ENTER %s \n " , __func__ ) ;
if ( event = = FB_EVENT_FB_REGISTERED )
return am200_share_video_mem ( info ) ;
else if ( event = = FB_EVENT_FB_UNREGISTERED )
return am200_unshare_video_mem ( info ) ;
return 0 ;
}
static struct notifier_block am200_fb_notif = {
. notifier_call = am200_fb_notifier_callback ,
} ;
/* this gets called as part of our init. these steps must be done now so
2011-02-15 10:37:30 +03:00
* that we can use pxa_set_fb_info */
2008-08-17 08:59:32 +04:00
static void __init am200_presetup_fb ( void )
{
int fw ;
int fh ;
int padding_size ;
int totalsize ;
switch ( panel_type ) {
case 6 :
am200_fb_info . modes = & am200_fb_mode_6inch ;
break ;
case 8 :
am200_fb_info . modes = & am200_fb_mode_8inch ;
break ;
case 97 :
am200_fb_info . modes = & am200_fb_mode_9inch7 ;
break ;
default :
dev_err ( & am200_device - > dev , " invalid panel_type selection, "
" setting to 6 \n " ) ;
am200_fb_info . modes = & am200_fb_mode_6inch ;
break ;
}
/* the frame buffer is divided as follows:
command | CRC | padding
16 kb waveform data | CRC | padding
image data | CRC
*/
fw = am200_fb_info . modes - > xres ;
fh = am200_fb_info . modes - > yres ;
/* waveform must be 16k + 2 for checksum */
am200_board . wfm_size = roundup ( 16 * 1024 + 2 , fw ) ;
padding_size = PAGE_SIZE + ( 4 * fw ) ;
/* total is 1 cmd , 1 wfm, padding and image */
totalsize = fw + am200_board . wfm_size + padding_size + ( fw * fh ) ;
/* save this off because we're manipulating fw after this and
* we ' ll need it when we ' re ready to setup the framebuffer */
am200_board . fw = fw ;
am200_board . fh = fh ;
/* the reason we do this adjustment is because we want to acquire
* more framebuffer memory without imposing custom awareness on the
* underlying pxafb driver */
am200_fb_info . modes - > yres = DIV_ROUND_UP ( totalsize , fw ) ;
/* we divide since we told the LCD controller we're 16bpp */
am200_fb_info . modes - > xres / = 2 ;
2011-02-15 10:37:30 +03:00
pxa_set_fb_info ( NULL , & am200_fb_info ) ;
2008-08-17 08:59:32 +04:00
}
/* this gets called by metronomefb as part of its init, in our case, we
* have already completed initial framebuffer init in presetup_fb so we
* can just setup the fb access pointers */
static int am200_setup_fb ( struct metronomefb_par * par )
{
int fw ;
int fh ;
fw = am200_board . fw ;
fh = am200_board . fh ;
/* metromem was set up by the notifier in share_video_mem so now
* we can use its value to calculate the other entries */
par - > metromem_cmd = ( struct metromem_cmd * ) am200_board . metromem ;
par - > metromem_wfm = am200_board . metromem + fw ;
par - > metromem_img = par - > metromem_wfm + am200_board . wfm_size ;
par - > metromem_img_csum = ( u16 * ) ( par - > metromem_img + ( fw * fh ) ) ;
par - > metromem_dma = am200_board . host_fbinfo - > fix . smem_start ;
return 0 ;
}
static int am200_get_panel_type ( void )
{
return panel_type ;
}
static irqreturn_t am200_handle_irq ( int irq , void * dev_id )
{
struct metronomefb_par * par = dev_id ;
wake_up_interruptible ( & par - > waitq ) ;
return IRQ_HANDLED ;
}
static int am200_setup_irq ( struct fb_info * info )
{
int ret ;
2011-10-10 10:21:08 +04:00
ret = request_irq ( PXA_GPIO_TO_IRQ ( RDY_GPIO_PIN ) , am200_handle_irq ,
2013-12-09 14:22:22 +04:00
IRQF_TRIGGER_FALLING , " AM200 " , info - > par ) ;
2008-08-17 08:59:32 +04:00
if ( ret )
dev_err ( & am200_device - > dev , " request_irq failed: %d \n " , ret ) ;
return ret ;
}
static void am200_set_rst ( struct metronomefb_par * par , int state )
{
gpio_set_value ( RST_GPIO_PIN , state ) ;
}
static void am200_set_stdby ( struct metronomefb_par * par , int state )
{
gpio_set_value ( STDBY_GPIO_PIN , state ) ;
}
static int am200_wait_event ( struct metronomefb_par * par )
{
return wait_event_timeout ( par - > waitq , gpio_get_value ( RDY_GPIO_PIN ) , HZ ) ;
}
static int am200_wait_event_intr ( struct metronomefb_par * par )
{
return wait_event_interruptible_timeout ( par - > waitq ,
gpio_get_value ( RDY_GPIO_PIN ) , HZ ) ;
}
static struct metronome_board am200_board = {
. owner = THIS_MODULE ,
. setup_irq = am200_setup_irq ,
. setup_io = am200_init_gpio_regs ,
. setup_fb = am200_setup_fb ,
. set_rst = am200_set_rst ,
. set_stdby = am200_set_stdby ,
. met_wait_event = am200_wait_event ,
. met_wait_event_intr = am200_wait_event_intr ,
. get_panel_type = am200_get_panel_type ,
. cleanup = am200_cleanup ,
} ;
2008-11-30 00:23:13 +03:00
static unsigned long am200_pin_config [ ] __initdata = {
GPIO51_GPIO ,
GPIO49_GPIO ,
GPIO48_GPIO ,
GPIO32_GPIO ,
GPIO17_GPIO ,
GPIO16_GPIO ,
} ;
2008-12-09 17:14:29 +03:00
int __init am200_init ( void )
2008-08-17 08:59:32 +04:00
{
int ret ;
2019-05-28 12:02:41 +03:00
/*
* Before anything else , we request notification for any fb
* creation events .
*
* FIXME : This is terrible and needs to be nuked . The notifier is used
* to get at the fb base address from the boot splash fb driver , which
* is then passed to metronomefb . Instaed of metronomfb or this board
* support file here figuring this out on their own .
*
* See also the # ifdef in fbmem . c .
*/
2008-08-17 08:59:32 +04:00
fb_register_client ( & am200_fb_notif ) ;
2008-11-30 00:23:13 +03:00
pxa2xx_mfp_config ( ARRAY_AND_SIZE ( am200_pin_config ) ) ;
2008-08-17 08:59:32 +04:00
/* request our platform independent driver */
request_module ( " metronomefb " ) ;
am200_device = platform_device_alloc ( " metronomefb " , - 1 ) ;
if ( ! am200_device )
return - ENOMEM ;
/* the am200_board that will be seen by metronomefb is a copy */
platform_device_add_data ( am200_device , & am200_board ,
sizeof ( am200_board ) ) ;
/* this _add binds metronomefb to am200. metronomefb refcounts am200 */
ret = platform_device_add ( am200_device ) ;
if ( ret ) {
platform_device_put ( am200_device ) ;
fb_unregister_client ( & am200_fb_notif ) ;
return ret ;
}
am200_presetup_fb ( ) ;
return 0 ;
}
module_param ( panel_type , uint , 0 ) ;
MODULE_PARM_DESC ( panel_type , " Select the panel type: 6, 8, 97 " ) ;
MODULE_DESCRIPTION ( " board driver for am200 metronome epd kit " ) ;
MODULE_AUTHOR ( " Jaya Kumar " ) ;
MODULE_LICENSE ( " GPL " ) ;