2006-06-27 03:16:16 +04:00
/*
* GPMC support functions
*
* Copyright ( C ) 2005 - 2006 Nokia Corporation
*
* Author : Juha Yrjola
*
2009-05-29 01:16:04 +04:00
* Copyright ( C ) 2009 Texas Instruments
* Added OMAP4 support - Santosh Shilimkar < santosh . shilimkar @ ti . com >
*
2006-06-27 03:16:16 +04:00
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
2008-10-06 16:49:17 +04:00
# undef DEBUG
2006-06-27 03:16:16 +04:00
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/err.h>
# include <linux/clk.h>
2006-09-25 13:41:33 +04:00
# include <linux/ioport.h>
# include <linux/spinlock.h>
2008-09-06 15:10:45 +04:00
# include <linux/io.h>
2008-10-06 16:49:17 +04:00
# include <linux/module.h>
2006-06-27 03:16:16 +04:00
2006-12-30 03:48:51 +03:00
# include <asm/mach-types.h>
2009-10-20 20:40:47 +04:00
# include <plat/gpmc.h>
2006-06-27 03:16:16 +04:00
2009-10-20 20:40:47 +04:00
# include <plat/sdrc.h>
2006-12-07 04:14:05 +03:00
2008-10-06 16:49:17 +04:00
/* GPMC register offsets */
2006-06-27 03:16:16 +04:00
# define GPMC_REVISION 0x00
# define GPMC_SYSCONFIG 0x10
# define GPMC_SYSSTATUS 0x14
# define GPMC_IRQSTATUS 0x18
# define GPMC_IRQENABLE 0x1c
# define GPMC_TIMEOUT_CONTROL 0x40
# define GPMC_ERR_ADDRESS 0x44
# define GPMC_ERR_TYPE 0x48
# define GPMC_CONFIG 0x50
# define GPMC_STATUS 0x54
# define GPMC_PREFETCH_CONFIG1 0x1e0
# define GPMC_PREFETCH_CONFIG2 0x1e4
2008-04-28 15:25:01 +04:00
# define GPMC_PREFETCH_CONTROL 0x1ec
2006-06-27 03:16:16 +04:00
# define GPMC_PREFETCH_STATUS 0x1f0
# define GPMC_ECC_CONFIG 0x1f4
# define GPMC_ECC_CONTROL 0x1f8
# define GPMC_ECC_SIZE_CONFIG 0x1fc
# define GPMC_CS0 0x60
# define GPMC_CS_SIZE 0x30
2006-09-25 13:41:33 +04:00
# define GPMC_MEM_START 0x00000000
# define GPMC_MEM_END 0x3FFFFFFF
# define BOOT_ROM_SPACE 0x100000 /* 1MB */
# define GPMC_CHUNK_SHIFT 24 /* 16 MB */
# define GPMC_SECTION_SHIFT 28 /* 128 MB */
2009-07-13 14:56:24 +04:00
# define PREFETCH_FIFOTHRESHOLD (0x40 << 8)
# define CS_NUM_SHIFT 24
# define ENABLE_PREFETCH (0x1 << 7)
# define DMA_MPU_MODE 2
2006-09-25 13:41:33 +04:00
static struct resource gpmc_mem_root ;
static struct resource gpmc_cs_mem [ GPMC_CS_NUM ] ;
2007-05-11 09:33:04 +04:00
static DEFINE_SPINLOCK ( gpmc_mem_lock ) ;
2006-09-25 13:41:33 +04:00
static unsigned gpmc_cs_map ;
2008-10-06 16:49:17 +04:00
static void __iomem * gpmc_base ;
2006-06-27 03:16:16 +04:00
2008-10-06 16:49:17 +04:00
static struct clk * gpmc_l3_clk ;
2006-06-27 03:16:16 +04:00
static void gpmc_write_reg ( int idx , u32 val )
{
__raw_writel ( val , gpmc_base + idx ) ;
}
static u32 gpmc_read_reg ( int idx )
{
return __raw_readl ( gpmc_base + idx ) ;
}
void gpmc_cs_write_reg ( int cs , int idx , u32 val )
{
void __iomem * reg_addr ;
2008-10-06 16:49:17 +04:00
reg_addr = gpmc_base + GPMC_CS0 + ( cs * GPMC_CS_SIZE ) + idx ;
2006-06-27 03:16:16 +04:00
__raw_writel ( val , reg_addr ) ;
}
u32 gpmc_cs_read_reg ( int cs , int idx )
{
2008-10-06 16:49:17 +04:00
void __iomem * reg_addr ;
reg_addr = gpmc_base + GPMC_CS0 + ( cs * GPMC_CS_SIZE ) + idx ;
return __raw_readl ( reg_addr ) ;
2006-06-27 03:16:16 +04:00
}
2008-10-06 16:49:17 +04:00
/* TODO: Add support for gpmc_fck to clock framework and use it */
2006-12-07 04:13:55 +03:00
unsigned long gpmc_get_fclk_period ( void )
2006-06-27 03:16:16 +04:00
{
2008-10-06 16:49:17 +04:00
unsigned long rate = clk_get_rate ( gpmc_l3_clk ) ;
if ( rate = = 0 ) {
printk ( KERN_WARNING " gpmc_l3_clk not enabled \n " ) ;
return 0 ;
}
rate / = 1000 ;
rate = 1000000000 / rate ; /* In picoseconds */
return rate ;
2006-06-27 03:16:16 +04:00
}
unsigned int gpmc_ns_to_ticks ( unsigned int time_ns )
{
unsigned long tick_ps ;
/* Calculate in picosecs to yield more exact results */
tick_ps = gpmc_get_fclk_period ( ) ;
return ( time_ns * 1000 + tick_ps - 1 ) / tick_ps ;
}
2008-10-06 16:49:17 +04:00
unsigned int gpmc_ticks_to_ns ( unsigned int ticks )
{
return ticks * gpmc_get_fclk_period ( ) / 1000 ;
}
2007-01-26 23:29:40 +03:00
unsigned int gpmc_round_ns_to_ticks ( unsigned int time_ns )
{
unsigned long ticks = gpmc_ns_to_ticks ( time_ns ) ;
return ticks * gpmc_get_fclk_period ( ) / 1000 ;
}
2006-06-27 03:16:16 +04:00
# ifdef DEBUG
static int set_gpmc_timing_reg ( int cs , int reg , int st_bit , int end_bit ,
2006-06-27 03:16:21 +04:00
int time , const char * name )
2006-06-27 03:16:16 +04:00
# else
static int set_gpmc_timing_reg ( int cs , int reg , int st_bit , int end_bit ,
int time )
# endif
{
u32 l ;
int ticks , mask , nr_bits ;
if ( time = = 0 )
ticks = 0 ;
else
ticks = gpmc_ns_to_ticks ( time ) ;
nr_bits = end_bit - st_bit + 1 ;
2006-12-07 04:13:55 +03:00
if ( ticks > = 1 < < nr_bits ) {
# ifdef DEBUG
printk ( KERN_INFO " GPMC CS%d: %-10s* %3d ns, %3d ticks >= %d \n " ,
cs , name , time , ticks , 1 < < nr_bits ) ;
# endif
2006-06-27 03:16:16 +04:00
return - 1 ;
2006-12-07 04:13:55 +03:00
}
2006-06-27 03:16:16 +04:00
mask = ( 1 < < nr_bits ) - 1 ;
l = gpmc_cs_read_reg ( cs , reg ) ;
# ifdef DEBUG
2006-12-07 04:13:55 +03:00
printk ( KERN_INFO
" GPMC CS%d: %-10s: %3d ticks, %3lu ns (was %3i ticks) %3d ns \n " ,
2006-06-27 03:16:21 +04:00
cs , name , ticks , gpmc_get_fclk_period ( ) * ticks / 1000 ,
2006-12-07 04:13:55 +03:00
( l > > st_bit ) & mask , time ) ;
2006-06-27 03:16:16 +04:00
# endif
l & = ~ ( mask < < st_bit ) ;
l | = ticks < < st_bit ;
gpmc_cs_write_reg ( cs , reg , l ) ;
return 0 ;
}
# ifdef DEBUG
# define GPMC_SET_ONE(reg, st, end, field) \
if ( set_gpmc_timing_reg ( cs , ( reg ) , ( st ) , ( end ) , \
t - > field , # field ) < 0 ) \
return - 1
# else
# define GPMC_SET_ONE(reg, st, end, field) \
if ( set_gpmc_timing_reg ( cs , ( reg ) , ( st ) , ( end ) , t - > field ) < 0 ) \
return - 1
# endif
int gpmc_cs_calc_divider ( int cs , unsigned int sync_clk )
{
int div ;
u32 l ;
l = sync_clk * 1000 + ( gpmc_get_fclk_period ( ) - 1 ) ;
div = l / gpmc_get_fclk_period ( ) ;
if ( div > 4 )
return - 1 ;
2006-12-07 04:13:55 +03:00
if ( div < = 0 )
2006-06-27 03:16:16 +04:00
div = 1 ;
return div ;
}
int gpmc_cs_set_timings ( int cs , const struct gpmc_timings * t )
{
int div ;
u32 l ;
div = gpmc_cs_calc_divider ( cs , t - > sync_clk ) ;
if ( div < 0 )
return - 1 ;
GPMC_SET_ONE ( GPMC_CS_CONFIG2 , 0 , 3 , cs_on ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG2 , 8 , 12 , cs_rd_off ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG2 , 16 , 20 , cs_wr_off ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG3 , 0 , 3 , adv_on ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG3 , 8 , 12 , adv_rd_off ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG3 , 16 , 20 , adv_wr_off ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG4 , 0 , 3 , oe_on ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG4 , 8 , 12 , oe_off ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG4 , 16 , 19 , we_on ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG4 , 24 , 28 , we_off ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG5 , 0 , 4 , rd_cycle ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG5 , 8 , 12 , wr_cycle ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG5 , 16 , 20 , access ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG5 , 24 , 27 , page_burst_access ) ;
2008-10-09 18:51:41 +04:00
if ( cpu_is_omap34xx ( ) ) {
GPMC_SET_ONE ( GPMC_CS_CONFIG6 , 16 , 19 , wr_data_mux_bus ) ;
GPMC_SET_ONE ( GPMC_CS_CONFIG6 , 24 , 28 , wr_access ) ;
}
2006-12-07 04:13:55 +03:00
/* caller is expected to have initialized CONFIG1 to cover
* at least sync vs async
*/
l = gpmc_cs_read_reg ( cs , GPMC_CS_CONFIG1 ) ;
if ( l & ( GPMC_CONFIG1_READTYPE_SYNC | GPMC_CONFIG1_WRITETYPE_SYNC ) ) {
2006-06-27 03:16:16 +04:00
# ifdef DEBUG
2006-12-07 04:13:55 +03:00
printk ( KERN_INFO " GPMC CS%d CLK period is %lu ns (div %d) \n " ,
cs , ( div * gpmc_get_fclk_period ( ) ) / 1000 , div ) ;
2006-06-27 03:16:16 +04:00
# endif
2006-12-07 04:13:55 +03:00
l & = ~ 0x03 ;
l | = ( div - 1 ) ;
gpmc_cs_write_reg ( cs , GPMC_CS_CONFIG1 , l ) ;
}
2006-06-27 03:16:16 +04:00
return 0 ;
}
2006-09-25 13:41:33 +04:00
static void gpmc_cs_enable_mem ( int cs , u32 base , u32 size )
{
u32 l ;
u32 mask ;
mask = ( 1 < < GPMC_SECTION_SHIFT ) - size ;
l = gpmc_cs_read_reg ( cs , GPMC_CS_CONFIG7 ) ;
l & = ~ 0x3f ;
l = ( base > > GPMC_CHUNK_SHIFT ) & 0x3f ;
l & = ~ ( 0x0f < < 8 ) ;
l | = ( ( mask > > GPMC_CHUNK_SHIFT ) & 0x0f ) < < 8 ;
l | = 1 < < 6 ; /* CSVALID */
gpmc_cs_write_reg ( cs , GPMC_CS_CONFIG7 , l ) ;
}
static void gpmc_cs_disable_mem ( int cs )
{
u32 l ;
l = gpmc_cs_read_reg ( cs , GPMC_CS_CONFIG7 ) ;
l & = ~ ( 1 < < 6 ) ; /* CSVALID */
gpmc_cs_write_reg ( cs , GPMC_CS_CONFIG7 , l ) ;
}
static void gpmc_cs_get_memconf ( int cs , u32 * base , u32 * size )
{
u32 l ;
u32 mask ;
l = gpmc_cs_read_reg ( cs , GPMC_CS_CONFIG7 ) ;
* base = ( l & 0x3f ) < < GPMC_CHUNK_SHIFT ;
mask = ( l > > 8 ) & 0x0f ;
* size = ( 1 < < GPMC_SECTION_SHIFT ) - ( mask < < GPMC_CHUNK_SHIFT ) ;
}
static int gpmc_cs_mem_enabled ( int cs )
{
u32 l ;
l = gpmc_cs_read_reg ( cs , GPMC_CS_CONFIG7 ) ;
return l & ( 1 < < 6 ) ;
}
2006-12-08 00:58:10 +03:00
int gpmc_cs_set_reserved ( int cs , int reserved )
2006-06-27 03:16:16 +04:00
{
2006-12-08 00:58:10 +03:00
if ( cs > GPMC_CS_NUM )
return - ENODEV ;
2006-09-25 13:41:33 +04:00
gpmc_cs_map & = ~ ( 1 < < cs ) ;
gpmc_cs_map | = ( reserved ? 1 : 0 ) < < cs ;
2006-12-08 00:58:10 +03:00
return 0 ;
2006-09-25 13:41:33 +04:00
}
2006-12-08 00:58:10 +03:00
int gpmc_cs_reserved ( int cs )
2006-09-25 13:41:33 +04:00
{
2006-12-08 00:58:10 +03:00
if ( cs > GPMC_CS_NUM )
return - ENODEV ;
2006-09-25 13:41:33 +04:00
return gpmc_cs_map & ( 1 < < cs ) ;
}
static unsigned long gpmc_mem_align ( unsigned long size )
{
int order ;
size = ( size - 1 ) > > ( GPMC_CHUNK_SHIFT - 1 ) ;
order = GPMC_CHUNK_SHIFT - 1 ;
do {
size > > = 1 ;
order + + ;
} while ( size ) ;
size = 1 < < order ;
return size ;
}
static int gpmc_cs_insert_mem ( int cs , unsigned long base , unsigned long size )
{
struct resource * res = & gpmc_cs_mem [ cs ] ;
int r ;
size = gpmc_mem_align ( size ) ;
spin_lock ( & gpmc_mem_lock ) ;
res - > start = base ;
res - > end = base + size - 1 ;
r = request_resource ( & gpmc_mem_root , res ) ;
spin_unlock ( & gpmc_mem_lock ) ;
return r ;
}
int gpmc_cs_request ( int cs , unsigned long size , unsigned long * base )
{
struct resource * res = & gpmc_cs_mem [ cs ] ;
int r = - 1 ;
if ( cs > GPMC_CS_NUM )
return - ENODEV ;
size = gpmc_mem_align ( size ) ;
if ( size > ( 1 < < GPMC_SECTION_SHIFT ) )
return - ENOMEM ;
spin_lock ( & gpmc_mem_lock ) ;
if ( gpmc_cs_reserved ( cs ) ) {
r = - EBUSY ;
goto out ;
}
if ( gpmc_cs_mem_enabled ( cs ) )
r = adjust_resource ( res , res - > start & ~ ( size - 1 ) , size ) ;
if ( r < 0 )
r = allocate_resource ( & gpmc_mem_root , res , size , 0 , ~ 0 ,
size , NULL , NULL ) ;
if ( r < 0 )
goto out ;
gpmc_cs_enable_mem ( cs , res - > start , res - > end - res - > start + 1 ) ;
* base = res - > start ;
gpmc_cs_set_reserved ( cs , 1 ) ;
out :
spin_unlock ( & gpmc_mem_lock ) ;
return r ;
}
2008-10-06 16:49:17 +04:00
EXPORT_SYMBOL ( gpmc_cs_request ) ;
2006-09-25 13:41:33 +04:00
void gpmc_cs_free ( int cs )
{
spin_lock ( & gpmc_mem_lock ) ;
if ( cs > = GPMC_CS_NUM | | ! gpmc_cs_reserved ( cs ) ) {
printk ( KERN_ERR " Trying to free non-reserved GPMC CS%d \n " , cs ) ;
BUG ( ) ;
spin_unlock ( & gpmc_mem_lock ) ;
return ;
}
gpmc_cs_disable_mem ( cs ) ;
release_resource ( & gpmc_cs_mem [ cs ] ) ;
gpmc_cs_set_reserved ( cs , 0 ) ;
spin_unlock ( & gpmc_mem_lock ) ;
}
2008-10-06 16:49:17 +04:00
EXPORT_SYMBOL ( gpmc_cs_free ) ;
2006-09-25 13:41:33 +04:00
2009-07-13 14:56:24 +04:00
/**
* gpmc_prefetch_enable - configures and starts prefetch transfer
* @ cs : nand cs ( chip select ) number
* @ dma_mode : dma mode enable ( 1 ) or disable ( 0 )
* @ u32_count : number of bytes to be transferred
* @ is_write : prefetch read ( 0 ) or write post ( 1 ) mode
*/
int gpmc_prefetch_enable ( int cs , int dma_mode ,
unsigned int u32_count , int is_write )
{
uint32_t prefetch_config1 ;
if ( ! ( gpmc_read_reg ( GPMC_PREFETCH_CONTROL ) ) ) {
/* Set the amount of bytes to be prefetched */
gpmc_write_reg ( GPMC_PREFETCH_CONFIG2 , u32_count ) ;
/* Set dma/mpu mode, the prefetch read / post write and
* enable the engine . Set which cs is has requested for .
*/
prefetch_config1 = ( ( cs < < CS_NUM_SHIFT ) |
PREFETCH_FIFOTHRESHOLD |
ENABLE_PREFETCH |
( dma_mode < < DMA_MPU_MODE ) |
( 0x1 & is_write ) ) ;
gpmc_write_reg ( GPMC_PREFETCH_CONFIG1 , prefetch_config1 ) ;
} else {
return - EBUSY ;
}
/* Start the prefetch engine */
gpmc_write_reg ( GPMC_PREFETCH_CONTROL , 0x1 ) ;
return 0 ;
}
EXPORT_SYMBOL ( gpmc_prefetch_enable ) ;
/**
* gpmc_prefetch_reset - disables and stops the prefetch engine
*/
void gpmc_prefetch_reset ( void )
{
/* Stop the PFPW engine */
gpmc_write_reg ( GPMC_PREFETCH_CONTROL , 0x0 ) ;
/* Reset/disable the PFPW engine */
gpmc_write_reg ( GPMC_PREFETCH_CONFIG1 , 0x0 ) ;
}
EXPORT_SYMBOL ( gpmc_prefetch_reset ) ;
/**
* gpmc_prefetch_status - reads prefetch status of engine
*/
int gpmc_prefetch_status ( void )
{
return gpmc_read_reg ( GPMC_PREFETCH_STATUS ) ;
}
EXPORT_SYMBOL ( gpmc_prefetch_status ) ;
2008-10-06 16:49:17 +04:00
static void __init gpmc_mem_init ( void )
2006-09-25 13:41:33 +04:00
{
int cs ;
unsigned long boot_rom_space = 0 ;
2006-12-30 03:48:51 +03:00
/* never allocate the first page, to facilitate bug detection;
* even if we didn ' t boot from ROM .
*/
boot_rom_space = BOOT_ROM_SPACE ;
/* In apollon the CS0 is mapped as 0x0000 0000 */
if ( machine_is_omap_apollon ( ) )
boot_rom_space = 0 ;
2006-09-25 13:41:33 +04:00
gpmc_mem_root . start = GPMC_MEM_START + boot_rom_space ;
gpmc_mem_root . end = GPMC_MEM_END ;
/* Reserve all regions that has been set up by bootloader */
for ( cs = 0 ; cs < GPMC_CS_NUM ; cs + + ) {
u32 base , size ;
if ( ! gpmc_cs_mem_enabled ( cs ) )
continue ;
gpmc_cs_get_memconf ( cs , & base , & size ) ;
if ( gpmc_cs_insert_mem ( cs , base , size ) < 0 )
BUG ( ) ;
}
2006-06-27 03:16:16 +04:00
}
void __init gpmc_init ( void )
{
u32 l ;
2008-10-06 16:49:17 +04:00
char * ck ;
if ( cpu_is_omap24xx ( ) ) {
ck = " core_l3_ck " ;
if ( cpu_is_omap2420 ( ) )
l = OMAP2420_GPMC_BASE ;
else
l = OMAP34XX_GPMC_BASE ;
} else if ( cpu_is_omap34xx ( ) ) {
ck = " gpmc_fck " ;
l = OMAP34XX_GPMC_BASE ;
2009-05-29 01:16:04 +04:00
} else if ( cpu_is_omap44xx ( ) ) {
ck = " gpmc_fck " ;
l = OMAP44XX_GPMC_BASE ;
2008-10-06 16:49:17 +04:00
}
2006-06-27 03:16:16 +04:00
2008-10-06 16:49:17 +04:00
gpmc_l3_clk = clk_get ( NULL , ck ) ;
if ( IS_ERR ( gpmc_l3_clk ) ) {
printk ( KERN_ERR " Could not get GPMC clock %s \n " , ck ) ;
2008-11-05 00:35:06 +03:00
BUG ( ) ;
2008-10-06 16:49:17 +04:00
}
gpmc_base = ioremap ( l , SZ_4K ) ;
if ( ! gpmc_base ) {
clk_put ( gpmc_l3_clk ) ;
printk ( KERN_ERR " Could not get GPMC register memory \n " ) ;
2008-11-05 00:35:06 +03:00
BUG ( ) ;
2008-10-06 16:49:17 +04:00
}
2006-06-27 03:16:16 +04:00
l = gpmc_read_reg ( GPMC_REVISION ) ;
printk ( KERN_INFO " GPMC revision %d.%d \n " , ( l > > 4 ) & 0x0f , l & 0x0f ) ;
/* Set smart idle mode and automatic L3 clock gating */
l = gpmc_read_reg ( GPMC_SYSCONFIG ) ;
l & = 0x03 < < 3 ;
l | = ( 0x02 < < 3 ) | ( 1 < < 0 ) ;
gpmc_write_reg ( GPMC_SYSCONFIG , l ) ;
2006-09-25 13:41:33 +04:00
gpmc_mem_init ( ) ;
2006-06-27 03:16:16 +04:00
}