2005-04-17 02:20:36 +04:00
/*======================================================================
Device driver for the PCMCIA control functionality of PXA2xx
microprocessors .
The contents of this file may be used under the
terms of the GNU Public License version 2 ( the " GPL " )
( c ) Ian Molton ( spyro @ f2s . com ) 2003
( c ) Stefan Eletzhofer ( stefan . eletzhofer @ inquant . de ) 2003 , 4
derived from sa11xx_base . c
Portions created by John G . Dorsey are
Copyright ( C ) 1999 John G . Dorsey .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
# include <linux/module.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/ioport.h>
# include <linux/kernel.h>
# include <linux/spinlock.h>
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
2008-08-05 19:14:15 +04:00
# include <mach/hardware.h>
2005-04-17 02:20:36 +04:00
# include <asm/io.h>
# include <asm/irq.h>
# include <asm/system.h>
2008-08-05 19:14:15 +04:00
# include <mach/pxa2xx-regs.h>
2008-08-19 14:15:53 +04:00
# include <asm/mach-types.h>
2005-04-17 02:20:36 +04:00
# include <pcmcia/cs_types.h>
# include <pcmcia/ss.h>
# include <pcmcia/cistpl.h>
# include "soc_common.h"
# include "pxa2xx_base.h"
2009-01-19 12:34:27 +03:00
/*
* Personal Computer Memory Card International Association ( PCMCIA ) sockets
*/
# define PCMCIAPrtSp 0x04000000 /* PCMCIA Partition Space [byte] */
# define PCMCIASp (4*PCMCIAPrtSp) /* PCMCIA Space [byte] */
# define PCMCIAIOSp PCMCIAPrtSp /* PCMCIA I/O Space [byte] */
# define PCMCIAAttrSp PCMCIAPrtSp /* PCMCIA Attribute Space [byte] */
# define PCMCIAMemSp PCMCIAPrtSp /* PCMCIA Memory Space [byte] */
# define PCMCIA0Sp PCMCIASp /* PCMCIA 0 Space [byte] */
# define PCMCIA0IOSp PCMCIAIOSp /* PCMCIA 0 I/O Space [byte] */
# define PCMCIA0AttrSp PCMCIAAttrSp /* PCMCIA 0 Attribute Space [byte] */
# define PCMCIA0MemSp PCMCIAMemSp /* PCMCIA 0 Memory Space [byte] */
# define PCMCIA1Sp PCMCIASp /* PCMCIA 1 Space [byte] */
# define PCMCIA1IOSp PCMCIAIOSp /* PCMCIA 1 I/O Space [byte] */
# define PCMCIA1AttrSp PCMCIAAttrSp /* PCMCIA 1 Attribute Space [byte] */
# define PCMCIA1MemSp PCMCIAMemSp /* PCMCIA 1 Memory Space [byte] */
# define _PCMCIA(Nb) /* PCMCIA [0..1] */ \
( 0x20000000 + ( Nb ) * PCMCIASp )
# define _PCMCIAIO(Nb) _PCMCIA(Nb) /* PCMCIA I/O [0..1] */
# define _PCMCIAAttr(Nb) /* PCMCIA Attribute [0..1] */ \
( _PCMCIA ( Nb ) + 2 * PCMCIAPrtSp )
# define _PCMCIAMem(Nb) /* PCMCIA Memory [0..1] */ \
( _PCMCIA ( Nb ) + 3 * PCMCIAPrtSp )
# define _PCMCIA0 _PCMCIA(0) /* PCMCIA 0 */
# define _PCMCIA0IO _PCMCIAIO(0) /* PCMCIA 0 I/O */
# define _PCMCIA0Attr _PCMCIAAttr(0) /* PCMCIA 0 Attribute */
# define _PCMCIA0Mem _PCMCIAMem(0) /* PCMCIA 0 Memory */
# define _PCMCIA1 _PCMCIA(1) /* PCMCIA 1 */
# define _PCMCIA1IO _PCMCIAIO(1) /* PCMCIA 1 I/O */
# define _PCMCIA1Attr _PCMCIAAttr(1) /* PCMCIA 1 Attribute */
# define _PCMCIA1Mem _PCMCIAMem(1) /* PCMCIA 1 Memory */
2005-04-17 02:20:36 +04:00
# define MCXX_SETUP_MASK (0x7f)
# define MCXX_ASST_MASK (0x1f)
# define MCXX_HOLD_MASK (0x3f)
# define MCXX_SETUP_SHIFT (0)
# define MCXX_ASST_SHIFT (7)
# define MCXX_HOLD_SHIFT (14)
static inline u_int pxa2xx_mcxx_hold ( u_int pcmcia_cycle_ns ,
u_int mem_clk_10khz )
{
u_int code = pcmcia_cycle_ns * mem_clk_10khz ;
return ( code / 300000 ) + ( ( code % 300000 ) ? 1 : 0 ) - 1 ;
}
static inline u_int pxa2xx_mcxx_asst ( u_int pcmcia_cycle_ns ,
u_int mem_clk_10khz )
{
u_int code = pcmcia_cycle_ns * mem_clk_10khz ;
2007-10-16 12:23:49 +04:00
return ( code / 300000 ) + ( ( code % 300000 ) ? 1 : 0 ) + 1 ;
2005-04-17 02:20:36 +04:00
}
static inline u_int pxa2xx_mcxx_setup ( u_int pcmcia_cycle_ns ,
u_int mem_clk_10khz )
{
u_int code = pcmcia_cycle_ns * mem_clk_10khz ;
return ( code / 100000 ) + ( ( code % 100000 ) ? 1 : 0 ) - 1 ;
}
/* This function returns the (approximate) command assertion period, in
* nanoseconds , for a given CPU clock frequency and MCXX_ASST value :
*/
static inline u_int pxa2xx_pcmcia_cmd_time ( u_int mem_clk_10khz ,
u_int pcmcia_mcxx_asst )
{
return ( 300000 * ( pcmcia_mcxx_asst + 1 ) / mem_clk_10khz ) ;
}
static int pxa2xx_pcmcia_set_mcmem ( int sock , int speed , int clock )
{
MCMEM ( sock ) = ( ( pxa2xx_mcxx_setup ( speed , clock )
& MCXX_SETUP_MASK ) < < MCXX_SETUP_SHIFT )
| ( ( pxa2xx_mcxx_asst ( speed , clock )
& MCXX_ASST_MASK ) < < MCXX_ASST_SHIFT )
| ( ( pxa2xx_mcxx_hold ( speed , clock )
& MCXX_HOLD_MASK ) < < MCXX_HOLD_SHIFT ) ;
return 0 ;
}
static int pxa2xx_pcmcia_set_mcio ( int sock , int speed , int clock )
{
MCIO ( sock ) = ( ( pxa2xx_mcxx_setup ( speed , clock )
& MCXX_SETUP_MASK ) < < MCXX_SETUP_SHIFT )
| ( ( pxa2xx_mcxx_asst ( speed , clock )
& MCXX_ASST_MASK ) < < MCXX_ASST_SHIFT )
| ( ( pxa2xx_mcxx_hold ( speed , clock )
& MCXX_HOLD_MASK ) < < MCXX_HOLD_SHIFT ) ;
return 0 ;
}
static int pxa2xx_pcmcia_set_mcatt ( int sock , int speed , int clock )
{
MCATT ( sock ) = ( ( pxa2xx_mcxx_setup ( speed , clock )
& MCXX_SETUP_MASK ) < < MCXX_SETUP_SHIFT )
| ( ( pxa2xx_mcxx_asst ( speed , clock )
& MCXX_ASST_MASK ) < < MCXX_ASST_SHIFT )
| ( ( pxa2xx_mcxx_hold ( speed , clock )
& MCXX_HOLD_MASK ) < < MCXX_HOLD_SHIFT ) ;
return 0 ;
}
static int pxa2xx_pcmcia_set_mcxx ( struct soc_pcmcia_socket * skt , unsigned int clk )
{
struct soc_pcmcia_timing timing ;
int sock = skt - > nr ;
soc_common_pcmcia_get_timing ( skt , & timing ) ;
pxa2xx_pcmcia_set_mcmem ( sock , timing . mem , clk ) ;
pxa2xx_pcmcia_set_mcatt ( sock , timing . attr , clk ) ;
pxa2xx_pcmcia_set_mcio ( sock , timing . io , clk ) ;
return 0 ;
}
static int pxa2xx_pcmcia_set_timing ( struct soc_pcmcia_socket * skt )
{
unsigned int clk = get_memclk_frequency_10khz ( ) ;
return pxa2xx_pcmcia_set_mcxx ( skt , clk ) ;
}
# ifdef CONFIG_CPU_FREQ
static int
pxa2xx_pcmcia_frequency_change ( struct soc_pcmcia_socket * skt ,
unsigned long val ,
struct cpufreq_freqs * freqs )
{
# warning "it's not clear if this is right since the core CPU (N) clock has no effect on the memory (L) clock"
switch ( val ) {
case CPUFREQ_PRECHANGE :
if ( freqs - > new > freqs - > old ) {
debug ( skt , 2 , " new frequency %u.%uMHz > %u.%uMHz, "
" pre-updating \n " ,
freqs - > new / 1000 , ( freqs - > new / 100 ) % 10 ,
freqs - > old / 1000 , ( freqs - > old / 100 ) % 10 ) ;
pxa2xx_pcmcia_set_mcxx ( skt , freqs - > new ) ;
}
break ;
case CPUFREQ_POSTCHANGE :
if ( freqs - > new < freqs - > old ) {
debug ( skt , 2 , " new frequency %u.%uMHz < %u.%uMHz, "
" post-updating \n " ,
freqs - > new / 1000 , ( freqs - > new / 100 ) % 10 ,
freqs - > old / 1000 , ( freqs - > old / 100 ) % 10 ) ;
pxa2xx_pcmcia_set_mcxx ( skt , freqs - > new ) ;
}
break ;
}
return 0 ;
}
# endif
2008-08-19 14:15:53 +04:00
static void pxa2xx_configure_sockets ( struct device * dev )
{
struct pcmcia_low_level * ops = dev - > platform_data ;
/*
* We have at least one socket , so set MECR : CIT
* ( Card Is There )
*/
MECR | = MECR_CIT ;
/* Set MECR:NOS (Number Of Sockets) */
2009-03-28 12:56:28 +03:00
if ( ( ops - > first + ops - > nr ) > 1 | | machine_is_viper ( ) )
2008-08-19 14:15:53 +04:00
MECR | = MECR_NOS ;
else
MECR & = ~ MECR_NOS ;
}
2009-01-19 12:34:27 +03:00
static const char * skt_names [ ] = {
" PCMCIA socket 0 " ,
" PCMCIA socket 1 " ,
} ;
# define SKT_DEV_INFO_SIZE(n) \
( sizeof ( struct skt_dev_info ) + ( n ) * sizeof ( struct soc_pcmcia_socket ) )
2009-03-29 22:23:42 +04:00
static int pxa2xx_drv_pcmcia_add_one ( struct soc_pcmcia_socket * skt )
{
skt - > res_skt . start = _PCMCIA ( skt - > nr ) ;
skt - > res_skt . end = _PCMCIA ( skt - > nr ) + PCMCIASp - 1 ;
skt - > res_skt . name = skt_names [ skt - > nr ] ;
skt - > res_skt . flags = IORESOURCE_MEM ;
skt - > res_io . start = _PCMCIAIO ( skt - > nr ) ;
skt - > res_io . end = _PCMCIAIO ( skt - > nr ) + PCMCIAIOSp - 1 ;
skt - > res_io . name = " io " ;
skt - > res_io . flags = IORESOURCE_MEM | IORESOURCE_BUSY ;
skt - > res_mem . start = _PCMCIAMem ( skt - > nr ) ;
skt - > res_mem . end = _PCMCIAMem ( skt - > nr ) + PCMCIAMemSp - 1 ;
skt - > res_mem . name = " memory " ;
skt - > res_mem . flags = IORESOURCE_MEM ;
skt - > res_attr . start = _PCMCIAAttr ( skt - > nr ) ;
skt - > res_attr . end = _PCMCIAAttr ( skt - > nr ) + PCMCIAAttrSp - 1 ;
skt - > res_attr . name = " attribute " ;
skt - > res_attr . flags = IORESOURCE_MEM ;
return soc_pcmcia_add_one ( skt ) ;
}
2006-10-29 01:42:56 +04:00
int __pxa2xx_drv_pcmcia_probe ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2009-01-19 12:34:27 +03:00
int i , ret ;
2005-04-17 02:20:36 +04:00
struct pcmcia_low_level * ops ;
2009-01-19 12:34:27 +03:00
struct skt_dev_info * sinfo ;
struct soc_pcmcia_socket * skt ;
2005-04-17 02:20:36 +04:00
if ( ! dev | | ! dev - > platform_data )
return - ENODEV ;
ops = ( struct pcmcia_low_level * ) dev - > platform_data ;
2009-03-29 22:23:42 +04:00
/* Provide our PXA2xx specific timing routines. */
ops - > set_timing = pxa2xx_pcmcia_set_timing ;
# ifdef CONFIG_CPU_FREQ
ops - > frequency_change = pxa2xx_pcmcia_frequency_change ;
# endif
2009-01-19 12:34:27 +03:00
sinfo = kzalloc ( SKT_DEV_INFO_SIZE ( ops - > nr ) , GFP_KERNEL ) ;
if ( ! sinfo )
return - ENOMEM ;
sinfo - > nskt = ops - > nr ;
/* Initialize processor specific parameters */
for ( i = 0 ; i < ops - > nr ; i + + ) {
skt = & sinfo - > skt [ i ] ;
2009-03-29 22:23:42 +04:00
skt - > nr = ops - > first + i ;
skt - > irq = NO_IRQ ;
skt - > dev = dev ;
skt - > ops = ops ;
skt - > socket . owner = ops - > owner ;
skt - > socket . dev . parent = dev ;
2009-01-19 12:34:27 +03:00
2009-03-29 22:23:42 +04:00
ret = pxa2xx_drv_pcmcia_add_one ( skt ) ;
if ( ret )
break ;
2009-01-19 12:34:27 +03:00
}
2009-03-29 22:23:42 +04:00
if ( ret ) {
while ( - - i > = 0 )
soc_pcmcia_remove_one ( & sinfo - > skt [ i ] ) ;
kfree ( sinfo ) ;
} else {
2008-08-19 14:15:53 +04:00
pxa2xx_configure_sockets ( dev ) ;
2009-03-29 22:23:42 +04:00
dev_set_drvdata ( dev , sinfo ) ;
}
2005-04-17 02:20:36 +04:00
return ret ;
}
2006-10-29 01:42:56 +04:00
EXPORT_SYMBOL ( __pxa2xx_drv_pcmcia_probe ) ;
2005-04-17 02:20:36 +04:00
2006-10-29 01:42:56 +04:00
static int pxa2xx_drv_pcmcia_probe ( struct platform_device * dev )
{
return __pxa2xx_drv_pcmcia_probe ( & dev - > dev ) ;
}
static int pxa2xx_drv_pcmcia_remove ( struct platform_device * dev )
{
2009-03-27 01:21:18 +03:00
struct skt_dev_info * sinfo = platform_get_drvdata ( dev ) ;
int i ;
platform_set_drvdata ( dev , NULL ) ;
for ( i = 0 ; i < sinfo - > nskt ; i + + )
soc_pcmcia_remove_one ( & sinfo - > skt [ i ] ) ;
kfree ( sinfo ) ;
return 0 ;
2006-10-29 01:42:56 +04:00
}
2009-07-29 12:59:25 +04:00
static int pxa2xx_drv_pcmcia_suspend ( struct device * dev )
2006-10-29 01:42:56 +04:00
{
2009-09-29 02:10:41 +04:00
return pcmcia_socket_dev_suspend ( dev ) ;
2006-10-29 01:42:56 +04:00
}
2009-07-29 12:59:25 +04:00
static int pxa2xx_drv_pcmcia_resume ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2009-07-29 12:59:25 +04:00
pxa2xx_configure_sockets ( dev ) ;
return pcmcia_socket_dev_resume ( dev ) ;
2005-04-17 02:20:36 +04:00
}
2009-07-29 12:59:25 +04:00
static struct dev_pm_ops pxa2xx_drv_pcmcia_pm_ops = {
. suspend = pxa2xx_drv_pcmcia_suspend ,
. resume = pxa2xx_drv_pcmcia_resume ,
} ;
2006-10-29 01:42:56 +04:00
static struct platform_driver pxa2xx_pcmcia_driver = {
2005-04-17 02:20:36 +04:00
. probe = pxa2xx_drv_pcmcia_probe ,
2006-10-29 01:42:56 +04:00
. remove = pxa2xx_drv_pcmcia_remove ,
. driver = {
. name = " pxa2xx-pcmcia " ,
2008-04-16 01:34:34 +04:00
. owner = THIS_MODULE ,
2009-07-29 12:59:25 +04:00
. pm = & pxa2xx_drv_pcmcia_pm_ops ,
2006-10-29 01:42:56 +04:00
} ,
2005-04-17 02:20:36 +04:00
} ;
static int __init pxa2xx_pcmcia_init ( void )
{
2006-10-29 01:42:56 +04:00
return platform_driver_register ( & pxa2xx_pcmcia_driver ) ;
2005-04-17 02:20:36 +04:00
}
static void __exit pxa2xx_pcmcia_exit ( void )
{
2006-10-29 01:42:56 +04:00
platform_driver_unregister ( & pxa2xx_pcmcia_driver ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-03 22:39:25 +04:00
fs_initcall ( pxa2xx_pcmcia_init ) ;
2005-04-17 02:20:36 +04:00
module_exit ( pxa2xx_pcmcia_exit ) ;
MODULE_AUTHOR ( " Stefan Eletzhofer <stefan.eletzhofer@inquant.de> and Ian Molton <spyro@f2s.com> " ) ;
MODULE_DESCRIPTION ( " Linux PCMCIA Card Services: PXA2xx core socket driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2008-04-16 01:34:34 +04:00
MODULE_ALIAS ( " platform:pxa2xx-pcmcia " ) ;