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>
2012-05-05 17:41:42 +04:00
# include <linux/of.h>
2011-04-12 20:10:01 +04:00
# include <lantiq_soc.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 ;
} ;
2012-04-13 11:36:46 +04:00
static const char ltq_map_name [ ] = " ltq_nor " ;
static const char * ltq_probe_types [ ] __devinitconst = {
" cmdlinepart " , " ofpart " , NULL } ;
2011-04-12 20:10:01 +04:00
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 ) ;
}
2012-04-13 11:36:46 +04:00
static int __devinit
2011-04-12 20:10:01 +04:00
ltq_mtd_probe ( struct platform_device * pdev )
{
2012-04-13 11:36:46 +04:00
struct mtd_part_parser_data ppdata ;
2011-04-12 20:10:01 +04:00
struct ltq_mtd * ltq_mtd ;
struct cfi_private * cfi ;
int err ;
2012-05-05 17:41:42 +04:00
if ( of_machine_is_compatible ( " lantiq,falcon " ) & &
( ltq_boot_select ( ) ! = BS_FLASH ) ) {
dev_err ( & pdev - > dev , " invalid bootstrap options \n " ) ;
return - ENODEV ;
}
2011-04-12 20:10:01 +04:00
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 ) {
2012-04-13 11:36:46 +04:00
dev_err ( & pdev - > dev , " failed to get memory resource \n " ) ;
2011-04-12 20:10:01 +04:00
err = - ENOENT ;
goto err_out ;
}
ltq_mtd - > map = kzalloc ( sizeof ( struct map_info ) , GFP_KERNEL ) ;
2012-04-13 11:36:46 +04:00
ltq_mtd - > map - > phys = ltq_mtd - > res - > start ;
ltq_mtd - > map - > size = resource_size ( ltq_mtd - > res ) ;
ltq_mtd - > map - > virt = devm_request_and_ioremap ( & pdev - > dev , ltq_mtd - > res ) ;
2011-04-12 20:10:01 +04:00
if ( ! ltq_mtd - > map - > virt ) {
2012-04-13 11:36:46 +04:00
dev_err ( & pdev - > dev , " failed to remap mem resource \n " ) ;
err = - EBUSY ;
goto err_out ;
2011-04-12 20:10:01 +04:00
}
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 ;
2011-12-26 21:38:01 +04:00
goto err_free ;
2011-04-12 20:10:01 +04:00
}
ltq_mtd - > mtd - > owner = THIS_MODULE ;
cfi = ltq_mtd - > map - > fldrv_priv ;
cfi - > addr_unlock1 ^ = 1 ;
cfi - > addr_unlock2 ^ = 1 ;
2012-04-13 11:36:46 +04:00
ppdata . of_node = pdev - > dev . of_node ;
err = mtd_device_parse_register ( ltq_mtd - > mtd , ltq_probe_types ,
& ppdata , NULL , 0 ) ;
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_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 ) ;
}
kfree ( ltq_mtd - > map ) ;
kfree ( ltq_mtd ) ;
}
return 0 ;
}
2012-04-13 11:36:46 +04:00
static const struct of_device_id ltq_mtd_match [ ] = {
{ . compatible = " lantiq,nor " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ltq_mtd_match ) ;
2011-04-12 20:10:01 +04:00
static struct platform_driver ltq_mtd_driver = {
2012-04-13 11:36:46 +04:00
. probe = ltq_mtd_probe ,
2011-04-12 20:10:01 +04:00
. remove = __devexit_p ( ltq_mtd_remove ) ,
. driver = {
2012-04-13 11:36:46 +04:00
. name = " ltq-nor " ,
2011-04-12 20:10:01 +04:00
. owner = THIS_MODULE ,
2012-04-13 11:36:46 +04:00
. of_match_table = ltq_mtd_match ,
2011-04-12 20:10:01 +04:00
} ,
} ;
2012-04-13 11:36:46 +04:00
module_platform_driver ( ltq_mtd_driver ) ;
2011-04-12 20:10:01 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " John Crispin <blogic@openwrt.org> " ) ;
MODULE_DESCRIPTION ( " Lantiq SoC NOR " ) ;