2011-04-12 20:10:01 +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 .
*
* Copyright ( C ) 2004 Liu Peng Infineon IFAP DC COM CPE
* Copyright ( C ) 2010 John Crispin < blogic @ openwrt . org >
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/io.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/map.h>
# include <linux/mtd/partitions.h>
# include <linux/mtd/cfi.h>
# include <linux/platform_device.h>
# include <linux/mtd/physmap.h>
# include <lantiq_soc.h>
# include <lantiq_platform.h>
/*
* The NOR flash is connected to the same external bus unit ( EBU ) as PCI .
* To make PCI work we need to enable the endianness swapping for the address
* written to the EBU . This endianness swapping works for PCI correctly but
* fails for attached NOR devices . To workaround this we need to use a complex
* map . The workaround involves swapping all addresses whilst probing the chip .
* Once probing is complete we stop swapping the addresses but swizzle the
* unlock addresses to ensure that access to the NOR device works correctly .
*/
enum {
LTQ_NOR_PROBING ,
LTQ_NOR_NORMAL
} ;
struct ltq_mtd {
struct resource * res ;
struct mtd_info * mtd ;
struct map_info * map ;
} ;
static char ltq_map_name [ ] = " ltq_nor " ;
static map_word
ltq_read16 ( struct map_info * map , unsigned long adr )
{
unsigned long flags ;
map_word temp ;
if ( map - > map_priv_1 = = LTQ_NOR_PROBING )
adr ^ = 2 ;
spin_lock_irqsave ( & ebu_lock , flags ) ;
temp . x [ 0 ] = * ( u16 * ) ( map - > virt + adr ) ;
spin_unlock_irqrestore ( & ebu_lock , flags ) ;
return temp ;
}
static void
ltq_write16 ( struct map_info * map , map_word d , unsigned long adr )
{
unsigned long flags ;
if ( map - > map_priv_1 = = LTQ_NOR_PROBING )
adr ^ = 2 ;
spin_lock_irqsave ( & ebu_lock , flags ) ;
* ( u16 * ) ( map - > virt + adr ) = d . x [ 0 ] ;
spin_unlock_irqrestore ( & ebu_lock , flags ) ;
}
/*
* The following 2 functions copy data between iomem and a cached memory
* section . As memcpy ( ) makes use of pre - fetching we cannot use it here .
* The normal alternative of using memcpy_ { to , from } io also makes use of
* memcpy ( ) on MIPS so it is not applicable either . We are therefore stuck
* with having to use our own loop .
*/
static void
ltq_copy_from ( struct map_info * map , void * to ,
unsigned long from , ssize_t len )
{
unsigned char * f = ( unsigned char * ) map - > virt + from ;
unsigned char * t = ( unsigned char * ) to ;
unsigned long flags ;
spin_lock_irqsave ( & ebu_lock , flags ) ;
while ( len - - )
* t + + = * f + + ;
spin_unlock_irqrestore ( & ebu_lock , flags ) ;
}
static void
ltq_copy_to ( struct map_info * map , unsigned long to ,
const void * from , ssize_t len )
{
unsigned char * f = ( unsigned char * ) from ;
unsigned char * t = ( unsigned char * ) map - > virt + to ;
unsigned long flags ;
spin_lock_irqsave ( & ebu_lock , flags ) ;
while ( len - - )
* t + + = * f + + ;
spin_unlock_irqrestore ( & ebu_lock , flags ) ;
}
static int __init
ltq_mtd_probe ( struct platform_device * pdev )
{
struct physmap_flash_data * ltq_mtd_data = dev_get_platdata ( & pdev - > dev ) ;
struct ltq_mtd * ltq_mtd ;
struct resource * res ;
struct cfi_private * cfi ;
int err ;
ltq_mtd = kzalloc ( sizeof ( struct ltq_mtd ) , GFP_KERNEL ) ;
platform_set_drvdata ( pdev , ltq_mtd ) ;
ltq_mtd - > res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! ltq_mtd - > res ) {
dev_err ( & pdev - > dev , " failed to get memory resource " ) ;
err = - ENOENT ;
goto err_out ;
}
res = devm_request_mem_region ( & pdev - > dev , ltq_mtd - > res - > start ,
resource_size ( ltq_mtd - > res ) , dev_name ( & pdev - > dev ) ) ;
if ( ! ltq_mtd - > res ) {
dev_err ( & pdev - > dev , " failed to request mem resource " ) ;
err = - EBUSY ;
goto err_out ;
}
ltq_mtd - > map = kzalloc ( sizeof ( struct map_info ) , GFP_KERNEL ) ;
ltq_mtd - > map - > phys = res - > start ;
ltq_mtd - > map - > size = resource_size ( res ) ;
ltq_mtd - > map - > virt = devm_ioremap_nocache ( & pdev - > dev ,
ltq_mtd - > map - > phys , ltq_mtd - > map - > size ) ;
if ( ! ltq_mtd - > map - > virt ) {
dev_err ( & pdev - > dev , " failed to ioremap! \n " ) ;
err = - ENOMEM ;
goto err_free ;
}
ltq_mtd - > map - > name = ltq_map_name ;
ltq_mtd - > map - > bankwidth = 2 ;
ltq_mtd - > map - > read = ltq_read16 ;
ltq_mtd - > map - > write = ltq_write16 ;
ltq_mtd - > map - > copy_from = ltq_copy_from ;
ltq_mtd - > map - > copy_to = ltq_copy_to ;
ltq_mtd - > map - > map_priv_1 = LTQ_NOR_PROBING ;
ltq_mtd - > mtd = do_map_probe ( " cfi_probe " , ltq_mtd - > map ) ;
ltq_mtd - > map - > map_priv_1 = LTQ_NOR_NORMAL ;
if ( ! ltq_mtd - > mtd ) {
dev_err ( & pdev - > dev , " probing failed \n " ) ;
err = - ENXIO ;
goto err_unmap ;
}
ltq_mtd - > mtd - > owner = THIS_MODULE ;
cfi = ltq_mtd - > map - > fldrv_priv ;
cfi - > addr_unlock1 ^ = 1 ;
cfi - > addr_unlock2 ^ = 1 ;
2011-06-02 17:59:49 +04:00
err = mtd_device_parse_register ( ltq_mtd - > mtd , NULL , 0 ,
ltq_mtd_data - > parts , ltq_mtd_data - > nr_parts ) ;
2011-04-12 20:10:01 +04:00
if ( err ) {
dev_err ( & pdev - > dev , " failed to add partitions \n " ) ;
goto err_destroy ;
}
return 0 ;
err_destroy :
map_destroy ( ltq_mtd - > mtd ) ;
err_unmap :
iounmap ( ltq_mtd - > map - > virt ) ;
err_free :
kfree ( ltq_mtd - > map ) ;
err_out :
kfree ( ltq_mtd ) ;
return err ;
}
static int __devexit
ltq_mtd_remove ( struct platform_device * pdev )
{
struct ltq_mtd * ltq_mtd = platform_get_drvdata ( pdev ) ;
if ( ltq_mtd ) {
if ( ltq_mtd - > mtd ) {
2011-06-09 21:57:33 +04:00
mtd_device_unregister ( ltq_mtd - > mtd ) ;
2011-04-12 20:10:01 +04:00
map_destroy ( ltq_mtd - > mtd ) ;
}
if ( ltq_mtd - > map - > virt )
iounmap ( ltq_mtd - > map - > virt ) ;
kfree ( ltq_mtd - > map ) ;
kfree ( ltq_mtd ) ;
}
return 0 ;
}
static struct platform_driver ltq_mtd_driver = {
. remove = __devexit_p ( ltq_mtd_remove ) ,
. driver = {
. name = " ltq_nor " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init
init_ltq_mtd ( void )
{
int ret = platform_driver_probe ( & ltq_mtd_driver , ltq_mtd_probe ) ;
if ( ret )
pr_err ( " ltq_nor: error registering platform driver " ) ;
return ret ;
}
static void __exit
exit_ltq_mtd ( void )
{
platform_driver_unregister ( & ltq_mtd_driver ) ;
}
module_init ( init_ltq_mtd ) ;
module_exit ( exit_ltq_mtd ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " John Crispin <blogic@openwrt.org> " ) ;
MODULE_DESCRIPTION ( " Lantiq SoC NOR " ) ;