2006-05-12 01:35:28 +04:00
/*
* drivers / mtd / nand / cs553x_nand . c
*
* ( C ) 2005 , 2006 Red Hat Inc .
*
* Author : David Woodhouse < dwmw2 @ infradead . org >
2006-05-13 07:12:40 +04:00
* Tom Sylla < tom . sylla @ amd . com >
2006-05-12 01:35:28 +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 .
*
* Overview :
2006-11-30 11:17:38 +03:00
* This is a device driver for the NAND flash controller found on
2006-05-12 01:35:28 +04:00
* the AMD CS5535 / CS5536 companion chipsets for the Geode processor .
2008-02-09 11:16:36 +03:00
* mtd - id for command line partitioning is cs553x_nand_cs [ 0 - 3 ]
* where 0 - 3 reflects the chip select for NAND .
2006-05-12 01:35:28 +04:00
*
*/
2008-02-09 11:16:36 +03:00
# include <linux/kernel.h>
2006-05-12 01:35:28 +04:00
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
2006-05-13 07:12:40 +04:00
# include <linux/mtd/nand_ecc.h>
2006-05-12 01:35:28 +04:00
# include <linux/mtd/partitions.h>
# include <asm/msr.h>
# include <asm/io.h>
# define NR_CS553X_CONTROLLERS 4
2006-05-26 05:06:27 +04:00
# define MSR_DIVIL_GLD_CAP 0x51400000 /* DIVIL capabilitiies */
# define CAP_CS5535 0x2df000ULL
# define CAP_CS5536 0x5df500ULL
2006-05-12 01:35:28 +04:00
/* NAND Timing MSRs */
# define MSR_NANDF_DATA 0x5140001b /* NAND Flash Data Timing MSR */
# define MSR_NANDF_CTL 0x5140001c /* NAND Flash Control Timing */
# define MSR_NANDF_RSVD 0x5140001d /* Reserved */
/* NAND BAR MSRs */
# define MSR_DIVIL_LBAR_FLSH0 0x51400010 /* Flash Chip Select 0 */
# define MSR_DIVIL_LBAR_FLSH1 0x51400011 /* Flash Chip Select 1 */
# define MSR_DIVIL_LBAR_FLSH2 0x51400012 /* Flash Chip Select 2 */
# define MSR_DIVIL_LBAR_FLSH3 0x51400013 /* Flash Chip Select 3 */
/* Each made up of... */
# define FLSH_LBAR_EN (1ULL<<32)
# define FLSH_NOR_NAND (1ULL<<33) /* 1 for NAND */
# define FLSH_MEM_IO (1ULL<<34) /* 1 for MMIO */
/* I/O BARs have BASE_ADDR in bits 15:4, IO_MASK in 47:36 */
/* MMIO BARs have BASE_ADDR in bits 31:12, MEM_MASK in 63:44 */
/* Pin function selection MSR (IDE vs. flash on the IDE pins) */
# define MSR_DIVIL_BALL_OPTS 0x51400015
2006-05-13 21:07:53 +04:00
# define PIN_OPT_IDE (1<<0) /* 0 for flash, 1 for IDE */
2006-05-12 01:35:28 +04:00
/* Registers within the NAND flash controller BAR -- memory mapped */
# define MM_NAND_DATA 0x00 /* 0 to 0x7ff, in fact */
# define MM_NAND_CTL 0x800 /* Any even address 0x800-0x80e */
# define MM_NAND_IO 0x801 /* Any odd address 0x801-0x80f */
# define MM_NAND_STS 0x810
# define MM_NAND_ECC_LSB 0x811
# define MM_NAND_ECC_MSB 0x812
# define MM_NAND_ECC_COL 0x813
# define MM_NAND_LAC 0x814
# define MM_NAND_ECC_CTL 0x815
/* Registers within the NAND flash controller BAR -- I/O mapped */
# define IO_NAND_DATA 0x00 /* 0 to 3, in fact */
# define IO_NAND_CTL 0x04
# define IO_NAND_IO 0x05
# define IO_NAND_STS 0x06
# define IO_NAND_ECC_CTL 0x08
# define IO_NAND_ECC_LSB 0x09
# define IO_NAND_ECC_MSB 0x0a
# define IO_NAND_ECC_COL 0x0b
# define IO_NAND_LAC 0x0c
# define CS_NAND_CTL_DIST_EN (1<<4) /* Enable NAND Distract interrupt */
# define CS_NAND_CTL_RDY_INT_MASK (1<<3) /* Enable RDY/BUSY# interrupt */
# define CS_NAND_CTL_ALE (1<<2)
# define CS_NAND_CTL_CLE (1<<1)
# define CS_NAND_CTL_CE (1<<0) /* Keep low; 1 to reset */
# define CS_NAND_STS_FLASH_RDY (1<<3)
# define CS_NAND_CTLR_BUSY (1<<2)
# define CS_NAND_CMD_COMP (1<<1)
# define CS_NAND_DIST_ST (1<<0)
# define CS_NAND_ECC_PARITY (1<<2)
# define CS_NAND_ECC_CLRECC (1<<1)
# define CS_NAND_ECC_ENECC (1<<0)
2006-05-13 07:12:40 +04:00
static void cs553x_read_buf ( struct mtd_info * mtd , u_char * buf , int len )
{
struct nand_chip * this = mtd - > priv ;
while ( unlikely ( len > 0x800 ) ) {
memcpy_fromio ( buf , this - > IO_ADDR_R , 0x800 ) ;
buf + = 0x800 ;
len - = 0x800 ;
}
memcpy_fromio ( buf , this - > IO_ADDR_R , len ) ;
}
static void cs553x_write_buf ( struct mtd_info * mtd , const u_char * buf , int len )
{
struct nand_chip * this = mtd - > priv ;
while ( unlikely ( len > 0x800 ) ) {
memcpy_toio ( this - > IO_ADDR_R , buf , 0x800 ) ;
buf + = 0x800 ;
len - = 0x800 ;
}
memcpy_toio ( this - > IO_ADDR_R , buf , len ) ;
}
2006-05-12 01:35:28 +04:00
static unsigned char cs553x_read_byte ( struct mtd_info * mtd )
{
struct nand_chip * this = mtd - > priv ;
2006-05-13 07:12:40 +04:00
return readb ( this - > IO_ADDR_R ) ;
2006-05-12 01:35:28 +04:00
}
static void cs553x_write_byte ( struct mtd_info * mtd , u_char byte )
{
struct nand_chip * this = mtd - > priv ;
int i = 100000 ;
while ( i & & readb ( this - > IO_ADDR_R + MM_NAND_STS ) & CS_NAND_CTLR_BUSY ) {
udelay ( 1 ) ;
i - - ;
}
2006-05-13 21:07:53 +04:00
writeb ( byte , this - > IO_ADDR_W + 0x801 ) ;
2006-05-12 01:35:28 +04:00
}
2006-05-24 01:25:53 +04:00
static void cs553x_hwcontrol ( struct mtd_info * mtd , int cmd ,
unsigned int ctrl )
2006-05-12 01:35:28 +04:00
{
struct nand_chip * this = mtd - > priv ;
void __iomem * mmio_base = this - > IO_ADDR_R ;
2006-05-24 01:25:53 +04:00
if ( ctrl & NAND_CTRL_CHANGE ) {
unsigned char ctl = ( ctrl & ~ NAND_CTRL_CHANGE ) ^ 0x01 ;
writeb ( ctl , mmio_base + MM_NAND_CTL ) ;
2006-05-12 01:35:28 +04:00
}
2006-05-24 01:25:53 +04:00
if ( cmd ! = NAND_CMD_NONE )
cs553x_write_byte ( mtd , cmd ) ;
2006-05-12 01:35:28 +04:00
}
static int cs553x_device_ready ( struct mtd_info * mtd )
{
struct nand_chip * this = mtd - > priv ;
void __iomem * mmio_base = this - > IO_ADDR_R ;
unsigned char foo = readb ( mmio_base + MM_NAND_STS ) ;
2006-05-13 21:07:53 +04:00
return ( foo & CS_NAND_STS_FLASH_RDY ) & & ! ( foo & CS_NAND_CTLR_BUSY ) ;
2006-05-12 01:35:28 +04:00
}
2006-05-13 07:12:40 +04:00
static void cs_enable_hwecc ( struct mtd_info * mtd , int mode )
{
struct nand_chip * this = mtd - > priv ;
void __iomem * mmio_base = this - > IO_ADDR_R ;
writeb ( 0x07 , mmio_base + MM_NAND_ECC_CTL ) ;
}
static int cs_calculate_ecc ( struct mtd_info * mtd , const u_char * dat , u_char * ecc_code )
{
uint32_t ecc ;
struct nand_chip * this = mtd - > priv ;
void __iomem * mmio_base = this - > IO_ADDR_R ;
ecc = readl ( mmio_base + MM_NAND_STS ) ;
ecc_code [ 1 ] = ecc > > 8 ;
ecc_code [ 0 ] = ecc > > 16 ;
ecc_code [ 2 ] = ecc > > 24 ;
return 0 ;
}
2006-05-12 01:35:28 +04:00
static struct mtd_info * cs553x_mtd [ 4 ] ;
static int __init cs553x_init_one ( int cs , int mmio , unsigned long adr )
{
int err = 0 ;
struct nand_chip * this ;
struct mtd_info * new_mtd ;
printk ( KERN_NOTICE " Probing CS553x NAND controller CS#%d at %sIO 0x%08lx \n " , cs , mmio ? " MM " : " P " , adr ) ;
if ( ! mmio ) {
printk ( KERN_NOTICE " PIO mode not yet implemented for CS553X NAND controller \n " ) ;
return - ENXIO ;
}
/* Allocate memory for MTD device structure and private data */
2013-08-23 13:51:45 +04:00
new_mtd = kzalloc ( sizeof ( struct mtd_info ) + sizeof ( struct nand_chip ) , GFP_KERNEL ) ;
2006-05-12 01:35:28 +04:00
if ( ! new_mtd ) {
printk ( KERN_WARNING " Unable to allocate CS553X NAND MTD device structure. \n " ) ;
err = - ENOMEM ;
goto out ;
}
/* Get pointer to private data */
2006-05-13 21:07:53 +04:00
this = ( struct nand_chip * ) ( & new_mtd [ 1 ] ) ;
2006-05-12 01:35:28 +04:00
/* Link the private data with the MTD structure */
new_mtd - > priv = this ;
2006-05-14 04:20:46 +04:00
new_mtd - > owner = THIS_MODULE ;
2006-05-12 01:35:28 +04:00
/* map physical address */
this - > IO_ADDR_R = this - > IO_ADDR_W = ioremap ( adr , 4096 ) ;
if ( ! this - > IO_ADDR_R ) {
printk ( KERN_WARNING " ioremap cs553x NAND @0x%08lx failed \n " , adr ) ;
err = - EIO ;
goto out_mtd ;
}
2006-05-24 01:25:53 +04:00
this - > cmd_ctrl = cs553x_hwcontrol ;
2006-05-12 01:35:28 +04:00
this - > dev_ready = cs553x_device_ready ;
this - > read_byte = cs553x_read_byte ;
2006-05-13 07:12:40 +04:00
this - > read_buf = cs553x_read_buf ;
this - > write_buf = cs553x_write_buf ;
2006-05-12 01:35:28 +04:00
2006-05-13 07:12:40 +04:00
this - > chip_delay = 0 ;
2006-05-12 01:35:28 +04:00
2006-05-23 14:00:46 +04:00
this - > ecc . mode = NAND_ECC_HW ;
this - > ecc . size = 256 ;
this - > ecc . bytes = 3 ;
this - > ecc . hwctl = cs_enable_hwecc ;
this - > ecc . calculate = cs_calculate_ecc ;
this - > ecc . correct = nand_correct_data ;
2012-11-22 03:42:52 +04:00
this - > ecc . strength = 1 ;
2006-05-23 14:00:46 +04:00
2006-05-12 01:35:28 +04:00
/* Enable the following for a flash based bad block table */
2011-06-01 03:31:23 +04:00
this - > bbt_options = NAND_BBT_USE_FLASH ;
2006-05-12 01:35:28 +04:00
2011-03-31 05:57:33 +04:00
/* Scan to find existence of the device */
2006-05-13 07:12:40 +04:00
if ( nand_scan ( new_mtd , 1 ) ) {
2006-05-12 01:35:28 +04:00
err = - ENXIO ;
goto out_ior ;
}
2008-02-09 11:16:36 +03:00
new_mtd - > name = kasprintf ( GFP_KERNEL , " cs553x_nand_cs%d " , cs ) ;
2006-05-12 01:35:28 +04:00
cs553x_mtd [ cs ] = new_mtd ;
goto out ;
out_ior :
2006-10-11 01:46:37 +04:00
iounmap ( this - > IO_ADDR_R ) ;
2006-05-12 01:35:28 +04:00
out_mtd :
2006-05-13 07:12:40 +04:00
kfree ( new_mtd ) ;
2006-05-12 01:35:28 +04:00
out :
return err ;
}
2006-05-26 05:06:27 +04:00
static int is_geode ( void )
{
/* These are the CPUs which will have a CS553[56] companion chip */
if ( boot_cpu_data . x86_vendor = = X86_VENDOR_AMD & &
boot_cpu_data . x86 = = 5 & &
boot_cpu_data . x86_model = = 10 )
return 1 ; /* Geode LX */
if ( ( boot_cpu_data . x86_vendor = = X86_VENDOR_NSC | |
boot_cpu_data . x86_vendor = = X86_VENDOR_CYRIX ) & &
boot_cpu_data . x86 = = 5 & &
boot_cpu_data . x86_model = = 5 )
return 1 ; /* Geode GX (née GX2) */
return 0 ;
}
2006-05-16 16:54:50 +04:00
static int __init cs553x_init ( void )
2006-05-12 01:35:28 +04:00
{
int err = - ENXIO ;
int i ;
uint64_t val ;
2008-02-09 11:16:36 +03:00
2006-05-26 05:06:27 +04:00
/* If the CPU isn't a Geode GX or LX, abort */
if ( ! is_geode ( ) )
return - ENXIO ;
/* If it doesn't have the CS553[56], abort */
rdmsrl ( MSR_DIVIL_GLD_CAP , val ) ;
val & = ~ 0xFFULL ;
if ( val ! = CAP_CS5535 & & val ! = CAP_CS5536 )
2006-05-12 01:35:28 +04:00
return - ENXIO ;
2006-05-26 05:06:27 +04:00
/* If it doesn't have the NAND controller enabled, abort */
2006-05-12 01:35:28 +04:00
rdmsrl ( MSR_DIVIL_BALL_OPTS , val ) ;
2008-02-09 11:16:36 +03:00
if ( val & PIN_OPT_IDE ) {
2006-05-12 01:35:28 +04:00
printk ( KERN_INFO " CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS. \n " ) ;
return - ENXIO ;
}
2006-05-13 21:07:53 +04:00
for ( i = 0 ; i < NR_CS553X_CONTROLLERS ; i + + ) {
rdmsrl ( MSR_DIVIL_LBAR_FLSH0 + i , val ) ;
2006-05-12 01:35:28 +04:00
if ( ( val & ( FLSH_LBAR_EN | FLSH_NOR_NAND ) ) = = ( FLSH_LBAR_EN | FLSH_NOR_NAND ) )
err = cs553x_init_one ( i , ! ! ( val & FLSH_MEM_IO ) , val & 0xFFFFFFFF ) ;
}
2006-05-13 21:07:53 +04:00
2006-11-30 11:17:38 +03:00
/* Register all devices together here. This means we can easily hack it to
2006-05-12 01:35:28 +04:00
do mtdconcat etc . if we want to . */
2006-05-13 21:07:53 +04:00
for ( i = 0 ; i < NR_CS553X_CONTROLLERS ; i + + ) {
2006-05-12 01:35:28 +04:00
if ( cs553x_mtd [ i ] ) {
/* If any devices registered, return success. Else the last error. */
mtd: do not use plain 0 as NULL
The first 3 arguments of 'mtd_device_parse_register()' are pointers,
but many callers pass '0' instead of 'NULL'. Fix this globally. Thanks
to coccinelle for making it easy to do with the following semantic patch:
@@
expression mtd, types, parser_data, parts, nr_parts;
@@
(
-mtd_device_parse_register(mtd, 0, parser_data, parts, nr_parts)
+mtd_device_parse_register(mtd, NULL, parser_data, parts, nr_parts)
|
-mtd_device_parse_register(mtd, types, 0, parts, nr_parts)
+mtd_device_parse_register(mtd, types, NULL, parts, nr_parts)
|
-mtd_device_parse_register(mtd, types, parser_data, 0, nr_parts)
+mtd_device_parse_register(mtd, types, parser_data, NULL, nr_parts)
)
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
2012-03-09 21:24:26 +04:00
mtd_device_parse_register ( cs553x_mtd [ i ] , NULL , NULL ,
2011-06-02 18:00:31 +04:00
NULL , 0 ) ;
2006-05-12 01:35:28 +04:00
err = 0 ;
}
}
return err ;
}
2006-05-13 21:07:53 +04:00
2006-05-12 01:35:28 +04:00
module_init ( cs553x_init ) ;
2006-05-13 21:07:53 +04:00
static void __exit cs553x_cleanup ( void )
2006-05-12 01:35:28 +04:00
{
int i ;
2006-05-13 21:07:53 +04:00
for ( i = 0 ; i < NR_CS553X_CONTROLLERS ; i + + ) {
2006-05-12 01:35:28 +04:00
struct mtd_info * mtd = cs553x_mtd [ i ] ;
struct nand_chip * this ;
void __iomem * mmio_base ;
if ( ! mtd )
2008-02-09 11:16:36 +03:00
continue ;
2006-05-12 01:35:28 +04:00
this = cs553x_mtd [ i ] - > priv ;
mmio_base = this - > IO_ADDR_R ;
/* Release resources, unregister device */
2006-05-13 21:07:53 +04:00
nand_release ( cs553x_mtd [ i ] ) ;
2008-02-09 11:16:36 +03:00
kfree ( cs553x_mtd [ i ] - > name ) ;
2006-05-12 01:35:28 +04:00
cs553x_mtd [ i ] = NULL ;
2008-02-03 18:22:34 +03:00
/* unmap physical address */
2006-05-12 01:35:28 +04:00
iounmap ( mmio_base ) ;
/* Free the MTD device structure */
2006-05-13 07:12:40 +04:00
kfree ( mtd ) ;
2006-05-12 01:35:28 +04:00
}
}
2006-05-13 21:07:53 +04:00
2006-05-12 01:35:28 +04:00
module_exit ( cs553x_cleanup ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " David Woodhouse <dwmw2@infradead.org> " ) ;
MODULE_DESCRIPTION ( " NAND controller driver for AMD CS5535/CS5536 companion chip " ) ;