2011-05-18 15:10:07 +04:00
/*
* SuperH Mobile MERAM Driver for SuperH Mobile LCDC Driver
*
* Copyright ( c ) 2011 Damian Hobson - Garcia < dhobsong @ igel . co . jp >
* Takanari Hayama < taki @ igel . co . jp >
*
* 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 .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/device.h>
2011-07-04 10:06:11 +04:00
# include <linux/pm_runtime.h>
2011-05-18 15:10:07 +04:00
# include <linux/io.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
2011-07-13 14:13:47 +04:00
# include <video/sh_mobile_meram.h>
2011-05-18 15:10:07 +04:00
/* meram registers */
2011-07-13 14:13:47 +04:00
# define MEVCR1 0x4
# define MEVCR1_RST (1 << 31)
# define MEVCR1_WD (1 << 30)
# define MEVCR1_AMD1 (1 << 29)
# define MEVCR1_AMD0 (1 << 28)
# define MEQSEL1 0x40
# define MEQSEL2 0x44
# define MExxCTL 0x400
# define MExxCTL_BV (1 << 31)
# define MExxCTL_BSZ_SHIFT 28
# define MExxCTL_MSAR_MASK (0x7ff << MExxCTL_MSAR_SHIFT)
# define MExxCTL_MSAR_SHIFT 16
# define MExxCTL_NXT_MASK (0x1f << MExxCTL_NXT_SHIFT)
# define MExxCTL_NXT_SHIFT 11
# define MExxCTL_WD1 (1 << 10)
# define MExxCTL_WD0 (1 << 9)
# define MExxCTL_WS (1 << 8)
# define MExxCTL_CB (1 << 7)
# define MExxCTL_WBF (1 << 6)
# define MExxCTL_WF (1 << 5)
# define MExxCTL_RF (1 << 4)
# define MExxCTL_CM (1 << 3)
# define MExxCTL_MD_READ (1 << 0)
# define MExxCTL_MD_WRITE (2 << 0)
# define MExxCTL_MD_ICB_WB (3 << 0)
# define MExxCTL_MD_ICB (4 << 0)
# define MExxCTL_MD_FB (7 << 0)
# define MExxCTL_MD_MASK (7 << 0)
# define MExxBSIZE 0x404
# define MExxBSIZE_RCNT_SHIFT 28
# define MExxBSIZE_YSZM1_SHIFT 16
# define MExxBSIZE_XSZM1_SHIFT 0
# define MExxMNCF 0x408
# define MExxMNCF_KWBNM_SHIFT 28
# define MExxMNCF_KRBNM_SHIFT 24
# define MExxMNCF_BNM_SHIFT 16
# define MExxMNCF_XBV (1 << 15)
# define MExxMNCF_CPL_YCBCR444 (1 << 12)
# define MExxMNCF_CPL_YCBCR420 (2 << 12)
# define MExxMNCF_CPL_YCBCR422 (3 << 12)
# define MExxMNCF_CPL_MSK (3 << 12)
# define MExxMNCF_BL (1 << 2)
# define MExxMNCF_LNM_SHIFT 0
# define MExxSARA 0x410
# define MExxSARB 0x414
# define MExxSBSIZE 0x418
# define MExxSBSIZE_HDV (1 << 31)
# define MExxSBSIZE_HSZ16 (0 << 28)
# define MExxSBSIZE_HSZ32 (1 << 28)
# define MExxSBSIZE_HSZ64 (2 << 28)
# define MExxSBSIZE_HSZ128 (3 << 28)
# define MExxSBSIZE_SBSIZZ_SHIFT 0
# define MERAM_MExxCTL_VAL(next, addr) \
( ( ( ( next ) < < MExxCTL_NXT_SHIFT ) & MExxCTL_NXT_MASK ) | \
( ( ( addr ) < < MExxCTL_MSAR_SHIFT ) & MExxCTL_MSAR_MASK ) )
# define MERAM_MExxBSIZE_VAL(rcnt, yszm1, xszm1) \
( ( ( rcnt ) < < MExxBSIZE_RCNT_SHIFT ) | \
( ( yszm1 ) < < MExxBSIZE_YSZM1_SHIFT ) | \
( ( xszm1 ) < < MExxBSIZE_XSZM1_SHIFT ) )
2011-05-18 15:10:07 +04:00
2011-07-13 14:13:47 +04:00
# define SH_MOBILE_MERAM_ICB_NUM 32
2011-06-22 09:49:51 +04:00
static unsigned long common_regs [ ] = {
MEVCR1 ,
MEQSEL1 ,
MEQSEL2 ,
} ;
# define CMN_REGS_SIZE ARRAY_SIZE(common_regs)
static unsigned long icb_regs [ ] = {
MExxCTL ,
MExxBSIZE ,
MExxMNCF ,
MExxSARA ,
MExxSARB ,
MExxSBSIZE ,
} ;
# define ICB_REGS_SIZE ARRAY_SIZE(icb_regs)
2011-06-22 09:49:50 +04:00
struct sh_mobile_meram_priv {
void __iomem * base ;
struct mutex lock ;
unsigned long used_icb ;
int used_meram_cache_regions ;
unsigned long used_meram_cache [ SH_MOBILE_MERAM_ICB_NUM ] ;
2011-06-22 09:49:51 +04:00
unsigned long cmn_saved_regs [ CMN_REGS_SIZE ] ;
unsigned long icb_saved_regs [ ICB_REGS_SIZE * SH_MOBILE_MERAM_ICB_NUM ] ;
2011-06-22 09:49:50 +04:00
} ;
2011-05-18 15:10:07 +04:00
/* settings */
# define MERAM_SEC_LINE 15
# define MERAM_LINE_WIDTH 2048
/*
* MERAM / ICB access functions
*/
2011-07-13 14:13:47 +04:00
# define MERAM_ICB_OFFSET(base, idx, off) ((base) + (off) + (idx) * 0x20)
2011-05-18 15:10:07 +04:00
static inline void meram_write_icb ( void __iomem * base , int idx , int off ,
unsigned long val )
{
iowrite32 ( val , MERAM_ICB_OFFSET ( base , idx , off ) ) ;
}
static inline unsigned long meram_read_icb ( void __iomem * base , int idx , int off )
{
return ioread32 ( MERAM_ICB_OFFSET ( base , idx , off ) ) ;
}
static inline void meram_write_reg ( void __iomem * base , int off ,
unsigned long val )
{
iowrite32 ( val , base + off ) ;
}
static inline unsigned long meram_read_reg ( void __iomem * base , int off )
{
return ioread32 ( base + off ) ;
}
/*
* register ICB
*/
# define MERAM_CACHE_START(p) ((p) >> 16)
# define MERAM_CACHE_END(p) ((p) & 0xffff)
# define MERAM_CACHE_SET(o, s) ((((o) & 0xffff) << 16) | \
( ( ( o ) + ( s ) - 1 ) & 0xffff ) )
/*
* check if there ' s no overlaps in MERAM allocation .
*/
static inline int meram_check_overlap ( struct sh_mobile_meram_priv * priv ,
struct sh_mobile_meram_icb * new )
{
int i ;
int used_start , used_end , meram_start , meram_end ;
/* valid ICB? */
if ( new - > marker_icb & ~ 0x1f | | new - > cache_icb & ~ 0x1f )
return 1 ;
if ( test_bit ( new - > marker_icb , & priv - > used_icb ) | |
test_bit ( new - > cache_icb , & priv - > used_icb ) )
return 1 ;
for ( i = 0 ; i < priv - > used_meram_cache_regions ; i + + ) {
used_start = MERAM_CACHE_START ( priv - > used_meram_cache [ i ] ) ;
used_end = MERAM_CACHE_END ( priv - > used_meram_cache [ i ] ) ;
meram_start = new - > meram_offset ;
meram_end = new - > meram_offset + new - > meram_size ;
if ( ( meram_start > = used_start & & meram_start < used_end ) | |
( meram_end > used_start & & meram_end < used_end ) )
return 1 ;
}
return 0 ;
}
/*
* mark the specified ICB as used
*/
static inline void meram_mark ( struct sh_mobile_meram_priv * priv ,
struct sh_mobile_meram_icb * new )
{
int n ;
if ( new - > marker_icb < 0 | | new - > cache_icb < 0 )
return ;
__set_bit ( new - > marker_icb , & priv - > used_icb ) ;
__set_bit ( new - > cache_icb , & priv - > used_icb ) ;
n = priv - > used_meram_cache_regions ;
priv - > used_meram_cache [ n ] = MERAM_CACHE_SET ( new - > meram_offset ,
new - > meram_size ) ;
priv - > used_meram_cache_regions + + ;
}
/*
* unmark the specified ICB as used
*/
static inline void meram_unmark ( struct sh_mobile_meram_priv * priv ,
struct sh_mobile_meram_icb * icb )
{
int i ;
unsigned long pattern ;
if ( icb - > marker_icb < 0 | | icb - > cache_icb < 0 )
return ;
__clear_bit ( icb - > marker_icb , & priv - > used_icb ) ;
__clear_bit ( icb - > cache_icb , & priv - > used_icb ) ;
pattern = MERAM_CACHE_SET ( icb - > meram_offset , icb - > meram_size ) ;
for ( i = 0 ; i < priv - > used_meram_cache_regions ; i + + ) {
if ( priv - > used_meram_cache [ i ] = = pattern ) {
while ( i < priv - > used_meram_cache_regions - 1 ) {
priv - > used_meram_cache [ i ] =
priv - > used_meram_cache [ i + 1 ] ;
i + + ;
}
priv - > used_meram_cache [ i ] = 0 ;
priv - > used_meram_cache_regions - - ;
break ;
}
}
}
2011-05-18 15:10:08 +04:00
/*
* is this a YCbCr ( NV12 , NV16 or NV24 ) colorspace
*/
static inline int is_nvcolor ( int cspace )
{
if ( cspace = = SH_MOBILE_MERAM_PF_NV | |
cspace = = SH_MOBILE_MERAM_PF_NV24 )
return 1 ;
return 0 ;
}
2011-05-18 15:10:07 +04:00
/*
* set the next address to fetch
*/
static inline void meram_set_next_addr ( struct sh_mobile_meram_priv * priv ,
struct sh_mobile_meram_cfg * cfg ,
unsigned long base_addr_y ,
unsigned long base_addr_c )
{
unsigned long target ;
target = ( cfg - > current_reg ) ? MExxSARA : MExxSARB ;
cfg - > current_reg ^ = 1 ;
/* set the next address to fetch */
meram_write_icb ( priv - > base , cfg - > icb [ 0 ] . cache_icb , target ,
base_addr_y ) ;
meram_write_icb ( priv - > base , cfg - > icb [ 0 ] . marker_icb , target ,
base_addr_y + cfg - > icb [ 0 ] . cache_unit ) ;
2011-05-18 15:10:08 +04:00
if ( is_nvcolor ( cfg - > pixelformat ) ) {
2011-05-18 15:10:07 +04:00
meram_write_icb ( priv - > base , cfg - > icb [ 1 ] . cache_icb , target ,
base_addr_c ) ;
meram_write_icb ( priv - > base , cfg - > icb [ 1 ] . marker_icb , target ,
base_addr_c + cfg - > icb [ 1 ] . cache_unit ) ;
}
}
/*
* get the next ICB address
*/
static inline void meram_get_next_icb_addr ( struct sh_mobile_meram_info * pdata ,
struct sh_mobile_meram_cfg * cfg ,
unsigned long * icb_addr_y ,
unsigned long * icb_addr_c )
{
unsigned long icb_offset ;
if ( pdata - > addr_mode = = SH_MOBILE_MERAM_MODE0 )
icb_offset = 0x80000000 | ( cfg - > current_reg < < 29 ) ;
else
icb_offset = 0xc0000000 | ( cfg - > current_reg < < 23 ) ;
* icb_addr_y = icb_offset | ( cfg - > icb [ 0 ] . marker_icb < < 24 ) ;
2011-06-22 11:46:25 +04:00
if ( is_nvcolor ( cfg - > pixelformat ) )
2011-05-18 15:10:07 +04:00
* icb_addr_c = icb_offset | ( cfg - > icb [ 1 ] . marker_icb < < 24 ) ;
}
# define MERAM_CALC_BYTECOUNT(x, y) \
( ( ( x ) * ( y ) + ( MERAM_LINE_WIDTH - 1 ) ) & ~ ( MERAM_LINE_WIDTH - 1 ) )
/*
* initialize MERAM
*/
static int meram_init ( struct sh_mobile_meram_priv * priv ,
struct sh_mobile_meram_icb * icb ,
int xres , int yres , int * out_pitch )
{
unsigned long total_byte_count = MERAM_CALC_BYTECOUNT ( xres , yres ) ;
unsigned long bnm ;
int lcdc_pitch , xpitch , line_cnt ;
int save_lines ;
/* adjust pitch to 1024, 2048, 4096 or 8192 */
lcdc_pitch = ( xres - 1 ) | 1023 ;
lcdc_pitch = lcdc_pitch | ( lcdc_pitch > > 1 ) ;
lcdc_pitch = lcdc_pitch | ( lcdc_pitch > > 2 ) ;
lcdc_pitch + = 1 ;
/* derive settings */
if ( lcdc_pitch = = 8192 & & yres > = 1024 ) {
lcdc_pitch = xpitch = MERAM_LINE_WIDTH ;
line_cnt = total_byte_count > > 11 ;
* out_pitch = xres ;
save_lines = ( icb - > meram_size / 16 / MERAM_SEC_LINE ) ;
save_lines * = MERAM_SEC_LINE ;
} else {
xpitch = xres ;
line_cnt = yres ;
* out_pitch = lcdc_pitch ;
save_lines = icb - > meram_size / ( lcdc_pitch > > 10 ) / 2 ;
save_lines & = 0xff ;
}
bnm = ( save_lines - 1 ) < < 16 ;
/* TODO: we better to check if we have enough MERAM buffer size */
/* set up ICB */
meram_write_icb ( priv - > base , icb - > cache_icb , MExxBSIZE ,
MERAM_MExxBSIZE_VAL ( 0x0 , line_cnt - 1 , xpitch - 1 ) ) ;
meram_write_icb ( priv - > base , icb - > marker_icb , MExxBSIZE ,
MERAM_MExxBSIZE_VAL ( 0xf , line_cnt - 1 , xpitch - 1 ) ) ;
meram_write_icb ( priv - > base , icb - > cache_icb , MExxMNCF , bnm ) ;
meram_write_icb ( priv - > base , icb - > marker_icb , MExxMNCF , bnm ) ;
meram_write_icb ( priv - > base , icb - > cache_icb , MExxSBSIZE , xpitch ) ;
meram_write_icb ( priv - > base , icb - > marker_icb , MExxSBSIZE , xpitch ) ;
/* save a cache unit size */
icb - > cache_unit = xres * save_lines ;
/*
* Set MERAM for framebuffer
*
* we also chain the cache_icb and the marker_icb .
* we also split the allocated MERAM buffer between two ICBs .
*/
meram_write_icb ( priv - > base , icb - > cache_icb , MExxCTL ,
2011-07-13 14:13:47 +04:00
MERAM_MExxCTL_VAL ( icb - > marker_icb , icb - > meram_offset ) |
MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
MExxCTL_MD_FB ) ;
2011-05-18 15:10:07 +04:00
meram_write_icb ( priv - > base , icb - > marker_icb , MExxCTL ,
2011-07-13 14:13:47 +04:00
MERAM_MExxCTL_VAL ( icb - > cache_icb , icb - > meram_offset +
icb - > meram_size / 2 ) |
MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
MExxCTL_MD_FB ) ;
2011-05-18 15:10:07 +04:00
return 0 ;
}
static void meram_deinit ( struct sh_mobile_meram_priv * priv ,
struct sh_mobile_meram_icb * icb )
{
/* disable ICB */
meram_write_icb ( priv - > base , icb - > cache_icb , MExxCTL , 0 ) ;
meram_write_icb ( priv - > base , icb - > marker_icb , MExxCTL , 0 ) ;
icb - > cache_unit = 0 ;
}
/*
* register the ICB
*/
static int sh_mobile_meram_register ( struct sh_mobile_meram_info * pdata ,
struct sh_mobile_meram_cfg * cfg ,
int xres , int yres , int pixelformat ,
unsigned long base_addr_y ,
unsigned long base_addr_c ,
unsigned long * icb_addr_y ,
unsigned long * icb_addr_c ,
int * pitch )
{
struct platform_device * pdev ;
struct sh_mobile_meram_priv * priv ;
int n , out_pitch ;
int error = 0 ;
if ( ! pdata | | ! pdata - > priv | | ! pdata - > pdev | | ! cfg )
return - EINVAL ;
if ( pixelformat ! = SH_MOBILE_MERAM_PF_NV & &
2011-05-18 15:10:08 +04:00
pixelformat ! = SH_MOBILE_MERAM_PF_NV24 & &
2011-05-18 15:10:07 +04:00
pixelformat ! = SH_MOBILE_MERAM_PF_RGB )
return - EINVAL ;
priv = pdata - > priv ;
pdev = pdata - > pdev ;
dev_dbg ( & pdev - > dev , " registering %dx%d (%s) (y=%08lx, c=%08lx) " ,
xres , yres , ( ! pixelformat ) ? " yuv " : " rgb " ,
base_addr_y , base_addr_c ) ;
/* we can't handle wider than 8192px */
if ( xres > 8192 ) {
dev_err ( & pdev - > dev , " width exceeding the limit (> 8192). " ) ;
2011-07-13 14:13:47 +04:00
return - EINVAL ;
2011-05-18 15:10:07 +04:00
}
/* do we have at least one ICB config? */
if ( cfg - > icb [ 0 ] . marker_icb < 0 | | cfg - > icb [ 0 ] . cache_icb < 0 ) {
dev_err ( & pdev - > dev , " at least one ICB is required. " ) ;
2011-07-13 14:13:47 +04:00
return - EINVAL ;
}
mutex_lock ( & priv - > lock ) ;
if ( priv - > used_meram_cache_regions + 2 > SH_MOBILE_MERAM_ICB_NUM ) {
dev_err ( & pdev - > dev , " no more ICB available. " ) ;
2011-05-18 15:10:07 +04:00
error = - EINVAL ;
goto err ;
}
/* make sure that there's no overlaps */
if ( meram_check_overlap ( priv , & cfg - > icb [ 0 ] ) ) {
dev_err ( & pdev - > dev , " conflicting config detected. " ) ;
error = - EINVAL ;
goto err ;
}
n = 1 ;
/* do the same if we have the second ICB set */
if ( cfg - > icb [ 1 ] . marker_icb > = 0 & & cfg - > icb [ 1 ] . cache_icb > = 0 ) {
if ( meram_check_overlap ( priv , & cfg - > icb [ 1 ] ) ) {
dev_err ( & pdev - > dev , " conflicting config detected. " ) ;
error = - EINVAL ;
goto err ;
}
n = 2 ;
}
2011-05-18 15:10:08 +04:00
if ( is_nvcolor ( pixelformat ) & & n ! = 2 ) {
2011-05-18 15:10:07 +04:00
dev_err ( & pdev - > dev , " requires two ICB sets for planar Y/C. " ) ;
error = - EINVAL ;
goto err ;
}
/* we now register the ICB */
cfg - > pixelformat = pixelformat ;
meram_mark ( priv , & cfg - > icb [ 0 ] ) ;
2011-05-18 15:10:08 +04:00
if ( is_nvcolor ( pixelformat ) )
2011-05-18 15:10:07 +04:00
meram_mark ( priv , & cfg - > icb [ 1 ] ) ;
/* initialize MERAM */
meram_init ( priv , & cfg - > icb [ 0 ] , xres , yres , & out_pitch ) ;
* pitch = out_pitch ;
if ( pixelformat = = SH_MOBILE_MERAM_PF_NV )
meram_init ( priv , & cfg - > icb [ 1 ] , xres , ( yres + 1 ) / 2 ,
& out_pitch ) ;
2011-05-18 15:10:08 +04:00
else if ( pixelformat = = SH_MOBILE_MERAM_PF_NV24 )
meram_init ( priv , & cfg - > icb [ 1 ] , 2 * xres , ( yres + 1 ) / 2 ,
& out_pitch ) ;
2011-05-18 15:10:07 +04:00
cfg - > current_reg = 1 ;
meram_set_next_addr ( priv , cfg , base_addr_y , base_addr_c ) ;
meram_get_next_icb_addr ( pdata , cfg , icb_addr_y , icb_addr_c ) ;
dev_dbg ( & pdev - > dev , " registered - can access via y=%08lx, c=%08lx " ,
* icb_addr_y , * icb_addr_c ) ;
err :
mutex_unlock ( & priv - > lock ) ;
return error ;
}
static int sh_mobile_meram_unregister ( struct sh_mobile_meram_info * pdata ,
struct sh_mobile_meram_cfg * cfg )
{
struct sh_mobile_meram_priv * priv ;
if ( ! pdata | | ! pdata - > priv | | ! cfg )
return - EINVAL ;
priv = pdata - > priv ;
mutex_lock ( & priv - > lock ) ;
/* deinit & unmark */
2011-05-18 15:10:08 +04:00
if ( is_nvcolor ( cfg - > pixelformat ) ) {
2011-05-18 15:10:07 +04:00
meram_deinit ( priv , & cfg - > icb [ 1 ] ) ;
meram_unmark ( priv , & cfg - > icb [ 1 ] ) ;
}
meram_deinit ( priv , & cfg - > icb [ 0 ] ) ;
meram_unmark ( priv , & cfg - > icb [ 0 ] ) ;
mutex_unlock ( & priv - > lock ) ;
return 0 ;
}
static int sh_mobile_meram_update ( struct sh_mobile_meram_info * pdata ,
struct sh_mobile_meram_cfg * cfg ,
unsigned long base_addr_y ,
unsigned long base_addr_c ,
unsigned long * icb_addr_y ,
unsigned long * icb_addr_c )
{
struct sh_mobile_meram_priv * priv ;
if ( ! pdata | | ! pdata - > priv | | ! cfg )
return - EINVAL ;
priv = pdata - > priv ;
mutex_lock ( & priv - > lock ) ;
meram_set_next_addr ( priv , cfg , base_addr_y , base_addr_c ) ;
meram_get_next_icb_addr ( pdata , cfg , icb_addr_y , icb_addr_c ) ;
mutex_unlock ( & priv - > lock ) ;
return 0 ;
}
2011-06-22 09:49:51 +04:00
static int sh_mobile_meram_runtime_suspend ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct sh_mobile_meram_priv * priv = platform_get_drvdata ( pdev ) ;
int k , j ;
for ( k = 0 ; k < CMN_REGS_SIZE ; k + + )
priv - > cmn_saved_regs [ k ] = meram_read_reg ( priv - > base ,
common_regs [ k ] ) ;
for ( j = 0 ; j < 32 ; j + + ) {
if ( ! test_bit ( j , & priv - > used_icb ) )
continue ;
for ( k = 0 ; k < ICB_REGS_SIZE ; k + + ) {
priv - > icb_saved_regs [ j * ICB_REGS_SIZE + k ] =
meram_read_icb ( priv - > base , j , icb_regs [ k ] ) ;
/* Reset ICB on resume */
if ( icb_regs [ k ] = = MExxCTL )
2011-07-13 14:13:47 +04:00
priv - > icb_saved_regs [ j * ICB_REGS_SIZE + k ] | =
2011-07-13 14:13:47 +04:00
MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF ;
2011-06-22 09:49:51 +04:00
}
}
return 0 ;
}
static int sh_mobile_meram_runtime_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct sh_mobile_meram_priv * priv = platform_get_drvdata ( pdev ) ;
int k , j ;
for ( j = 0 ; j < 32 ; j + + ) {
if ( ! test_bit ( j , & priv - > used_icb ) )
continue ;
for ( k = 0 ; k < ICB_REGS_SIZE ; k + + ) {
meram_write_icb ( priv - > base , j , icb_regs [ k ] ,
priv - > icb_saved_regs [ j * ICB_REGS_SIZE + k ] ) ;
}
}
for ( k = 0 ; k < CMN_REGS_SIZE ; k + + )
meram_write_reg ( priv - > base , common_regs [ k ] ,
priv - > cmn_saved_regs [ k ] ) ;
return 0 ;
}
static const struct dev_pm_ops sh_mobile_meram_dev_pm_ops = {
. runtime_suspend = sh_mobile_meram_runtime_suspend ,
. runtime_resume = sh_mobile_meram_runtime_resume ,
} ;
2011-05-18 15:10:07 +04:00
static struct sh_mobile_meram_ops sh_mobile_meram_ops = {
. module = THIS_MODULE ,
. meram_register = sh_mobile_meram_register ,
. meram_unregister = sh_mobile_meram_unregister ,
. meram_update = sh_mobile_meram_update ,
} ;
/*
* initialize MERAM
*/
static int sh_mobile_meram_remove ( struct platform_device * pdev ) ;
static int __devinit sh_mobile_meram_probe ( struct platform_device * pdev )
{
struct sh_mobile_meram_priv * priv ;
struct sh_mobile_meram_info * pdata = pdev - > dev . platform_data ;
struct resource * res ;
int error ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " no platform data defined \n " ) ;
return - EINVAL ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " cannot get platform resources \n " ) ;
return - ENOENT ;
}
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
dev_err ( & pdev - > dev , " cannot allocate device data \n " ) ;
return - ENOMEM ;
}
platform_set_drvdata ( pdev , priv ) ;
/* initialize private data */
mutex_init ( & priv - > lock ) ;
priv - > base = ioremap_nocache ( res - > start , resource_size ( res ) ) ;
if ( ! priv - > base ) {
dev_err ( & pdev - > dev , " ioremap failed \n " ) ;
error = - EFAULT ;
goto err ;
}
pdata - > ops = & sh_mobile_meram_ops ;
pdata - > priv = priv ;
pdata - > pdev = pdev ;
/* initialize ICB addressing mode */
if ( pdata - > addr_mode = = SH_MOBILE_MERAM_MODE1 )
2011-07-13 14:13:47 +04:00
meram_write_reg ( priv - > base , MEVCR1 , MEVCR1_AMD1 ) ;
2011-05-18 15:10:07 +04:00
2011-07-04 10:06:11 +04:00
pm_runtime_enable ( & pdev - > dev ) ;
2011-05-18 15:10:07 +04:00
dev_info ( & pdev - > dev , " sh_mobile_meram initialized. " ) ;
return 0 ;
err :
sh_mobile_meram_remove ( pdev ) ;
return error ;
}
static int sh_mobile_meram_remove ( struct platform_device * pdev )
{
struct sh_mobile_meram_priv * priv = platform_get_drvdata ( pdev ) ;
2011-07-04 10:06:11 +04:00
pm_runtime_disable ( & pdev - > dev ) ;
2011-05-18 15:10:07 +04:00
if ( priv - > base )
iounmap ( priv - > base ) ;
mutex_destroy ( & priv - > lock ) ;
kfree ( priv ) ;
return 0 ;
}
static struct platform_driver sh_mobile_meram_driver = {
. driver = {
. name = " sh_mobile_meram " ,
. owner = THIS_MODULE ,
2011-06-22 09:49:51 +04:00
. pm = & sh_mobile_meram_dev_pm_ops ,
2011-05-18 15:10:07 +04:00
} ,
. probe = sh_mobile_meram_probe ,
. remove = sh_mobile_meram_remove ,
} ;
static int __init sh_mobile_meram_init ( void )
{
return platform_driver_register ( & sh_mobile_meram_driver ) ;
}
static void __exit sh_mobile_meram_exit ( void )
{
platform_driver_unregister ( & sh_mobile_meram_driver ) ;
}
module_init ( sh_mobile_meram_init ) ;
module_exit ( sh_mobile_meram_exit ) ;
MODULE_DESCRIPTION ( " SuperH Mobile MERAM driver " ) ;
MODULE_AUTHOR ( " Damian Hobson-Garcia / Takanari Hayama " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;