2005-04-16 15:20:36 -07:00
/* linux/drivers/mtd/nand/s3c2410.c
*
2008-05-20 17:32:27 +01:00
* Copyright © 2004 - 2008 Simtec Electronics
* http : //armlinux.simtec.co.uk/
2005-02-18 14:46:15 +00:00
* Ben Dooks < ben @ simtec . co . uk >
2005-04-16 15:20:36 -07:00
*
2008-05-20 17:32:27 +01:00
* Samsung S3C2410 / S3C2440 / S3C2412 NAND driver
2005-04-16 15:20:36 -07:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
# define DEBUG
# endif
# include <linux/module.h>
# include <linux/types.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/ioport.h>
2005-10-29 19:07:23 +01:00
# include <linux/platform_device.h>
2005-04-16 15:20:36 -07:00
# include <linux/delay.h>
# include <linux/err.h>
2005-10-30 15:03:48 -08:00
# include <linux/slab.h>
2006-01-07 16:15:52 +00:00
# include <linux/clk.h>
2008-07-15 11:58:31 +01:00
# include <linux/cpufreq.h>
2005-04-16 15:20:36 -07:00
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/nand_ecc.h>
# include <linux/mtd/partitions.h>
# include <asm/io.h>
2007-07-24 13:37:27 +01:00
# include <asm/plat-s3c/regs-nand.h>
# include <asm/plat-s3c/nand.h>
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_MTD_NAND_S3C2410_HWECC
static int hardware_ecc = 1 ;
# else
static int hardware_ecc = 0 ;
# endif
2006-06-19 09:29:38 +01:00
# ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
static int clock_stop = 1 ;
# else
static const int clock_stop = 0 ;
# endif
2005-04-16 15:20:36 -07:00
/* new oob placement block for use with hardware ecc generation
*/
2006-05-27 22:16:10 +02:00
static struct nand_ecclayout nand_hw_eccoob = {
2006-05-13 18:07:53 +01:00
. eccbytes = 3 ,
. eccpos = { 0 , 1 , 2 } ,
. oobfree = { { 8 , 8 } }
2005-04-16 15:20:36 -07:00
} ;
/* controller and mtd information */
struct s3c2410_nand_info ;
struct s3c2410_nand_mtd {
struct mtd_info mtd ;
struct nand_chip chip ;
struct s3c2410_nand_set * set ;
struct s3c2410_nand_info * info ;
int scan_res ;
} ;
2006-06-27 14:35:46 +01:00
enum s3c_cpu_type {
TYPE_S3C2410 ,
TYPE_S3C2412 ,
TYPE_S3C2440 ,
} ;
2005-04-16 15:20:36 -07:00
/* overview of the s3c2410 nand state */
struct s3c2410_nand_info {
/* mtd info */
struct nand_hw_control controller ;
struct s3c2410_nand_mtd * mtds ;
struct s3c2410_platform_nand * platform ;
/* device info */
struct device * device ;
struct resource * area ;
struct clk * clk ;
2005-02-18 14:46:15 +00:00
void __iomem * regs ;
2006-06-27 14:35:46 +01:00
void __iomem * sel_reg ;
int sel_bit ;
2005-04-16 15:20:36 -07:00
int mtd_count ;
2008-04-15 11:36:18 +01:00
unsigned long save_sel ;
2008-07-15 11:58:31 +01:00
unsigned long clk_rate ;
2007-11-19 23:28:07 +00:00
2006-06-27 14:35:46 +01:00
enum s3c_cpu_type cpu_type ;
2008-07-15 11:58:31 +01:00
# ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition ;
# endif
2005-04-16 15:20:36 -07:00
} ;
/* conversion functions */
static struct s3c2410_nand_mtd * s3c2410_nand_mtd_toours ( struct mtd_info * mtd )
{
return container_of ( mtd , struct s3c2410_nand_mtd , mtd ) ;
}
static struct s3c2410_nand_info * s3c2410_nand_mtd_toinfo ( struct mtd_info * mtd )
{
return s3c2410_nand_mtd_toours ( mtd ) - > info ;
}
2005-11-09 22:32:44 +00:00
static struct s3c2410_nand_info * to_nand_info ( struct platform_device * dev )
2005-04-16 15:20:36 -07:00
{
2005-11-09 22:32:44 +00:00
return platform_get_drvdata ( dev ) ;
2005-04-16 15:20:36 -07:00
}
2005-11-09 22:32:44 +00:00
static struct s3c2410_platform_nand * to_nand_plat ( struct platform_device * dev )
2005-04-16 15:20:36 -07:00
{
2005-11-09 22:32:44 +00:00
return dev - > dev . platform_data ;
2005-04-16 15:20:36 -07:00
}
2006-06-19 09:29:38 +01:00
static inline int allow_clk_stop ( struct s3c2410_nand_info * info )
{
return clock_stop ;
}
2005-04-16 15:20:36 -07:00
/* timing calculations */
2005-10-20 22:22:58 +01:00
# define NS_IN_KHZ 1000000
2005-04-16 15:20:36 -07:00
2006-06-27 14:35:46 +01:00
static int s3c_nand_calc_rate ( int wanted , unsigned long clk , int max )
2005-04-16 15:20:36 -07:00
{
int result ;
2005-10-20 22:22:58 +01:00
result = ( wanted * clk ) / NS_IN_KHZ ;
2005-04-16 15:20:36 -07:00
result + + ;
pr_debug ( " result %d from %ld, %d \n " , result , clk , wanted ) ;
if ( result > max ) {
2006-05-13 18:07:53 +01:00
printk ( " %d ns is too big for current clock rate %ld \n " , wanted , clk ) ;
2005-04-16 15:20:36 -07:00
return - 1 ;
}
if ( result < 1 )
result = 1 ;
return result ;
}
2005-10-20 22:22:58 +01:00
# define to_ns(ticks,clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
2005-04-16 15:20:36 -07:00
/* controller setup */
2008-07-15 11:58:31 +01:00
static int s3c2410_nand_setrate ( struct s3c2410_nand_info * info )
2005-04-16 15:20:36 -07:00
{
2008-07-15 11:58:31 +01:00
struct s3c2410_platform_nand * plat = info - > platform ;
2006-06-27 14:35:46 +01:00
int tacls_max = ( info - > cpu_type = = TYPE_S3C2412 ) ? 8 : 4 ;
2005-10-20 22:22:58 +01:00
int tacls , twrph0 , twrph1 ;
2008-07-15 11:58:31 +01:00
unsigned long clkrate = clk_get_rate ( info - > clk ) ;
unsigned long set , cfg , mask ;
unsigned long flags ;
2005-04-16 15:20:36 -07:00
/* calculate the timing information for the controller */
2008-07-15 11:58:31 +01:00
info - > clk_rate = clkrate ;
2005-10-20 22:22:58 +01:00
clkrate / = 1000 ; /* turn clock into kHz for ease of use */
2005-04-16 15:20:36 -07:00
if ( plat ! = NULL ) {
2006-06-27 14:35:46 +01:00
tacls = s3c_nand_calc_rate ( plat - > tacls , clkrate , tacls_max ) ;
twrph0 = s3c_nand_calc_rate ( plat - > twrph0 , clkrate , 8 ) ;
twrph1 = s3c_nand_calc_rate ( plat - > twrph1 , clkrate , 8 ) ;
2005-04-16 15:20:36 -07:00
} else {
/* default timings */
2006-06-27 14:35:46 +01:00
tacls = tacls_max ;
2005-04-16 15:20:36 -07:00
twrph0 = 8 ;
twrph1 = 8 ;
}
2005-11-07 11:15:49 +00:00
2005-04-16 15:20:36 -07:00
if ( tacls < 0 | | twrph0 < 0 | | twrph1 < 0 ) {
2006-06-21 15:43:05 +01:00
dev_err ( info - > device , " cannot get suitable timings \n " ) ;
2005-04-16 15:20:36 -07:00
return - EINVAL ;
}
2006-06-21 15:43:05 +01:00
dev_info ( info - > device , " Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns \n " ,
2006-05-13 18:07:53 +01:00
tacls , to_ns ( tacls , clkrate ) , twrph0 , to_ns ( twrph0 , clkrate ) , twrph1 , to_ns ( twrph1 , clkrate ) ) ;
2005-04-16 15:20:36 -07:00
2008-07-15 11:58:31 +01:00
switch ( info - > cpu_type ) {
case TYPE_S3C2410 :
mask = ( S3C2410_NFCONF_TACLS ( 3 ) |
S3C2410_NFCONF_TWRPH0 ( 7 ) |
S3C2410_NFCONF_TWRPH1 ( 7 ) ) ;
set = S3C2410_NFCONF_EN ;
set | = S3C2410_NFCONF_TACLS ( tacls - 1 ) ;
set | = S3C2410_NFCONF_TWRPH0 ( twrph0 - 1 ) ;
set | = S3C2410_NFCONF_TWRPH1 ( twrph1 - 1 ) ;
break ;
case TYPE_S3C2440 :
case TYPE_S3C2412 :
mask = ( S3C2410_NFCONF_TACLS ( tacls_max - 1 ) |
S3C2410_NFCONF_TWRPH0 ( 7 ) |
S3C2410_NFCONF_TWRPH1 ( 7 ) ) ;
set = S3C2440_NFCONF_TACLS ( tacls - 1 ) ;
set | = S3C2440_NFCONF_TWRPH0 ( twrph0 - 1 ) ;
set | = S3C2440_NFCONF_TWRPH1 ( twrph1 - 1 ) ;
break ;
default :
/* keep compiler happy */
mask = 0 ;
set = 0 ;
BUG ( ) ;
}
dev_dbg ( info - > device , " NF_CONF is 0x%lx \n " , cfg ) ;
local_irq_save ( flags ) ;
cfg = readl ( info - > regs + S3C2410_NFCONF ) ;
cfg & = ~ mask ;
cfg | = set ;
writel ( cfg , info - > regs + S3C2410_NFCONF ) ;
local_irq_restore ( flags ) ;
return 0 ;
}
static int s3c2410_nand_inithw ( struct s3c2410_nand_info * info )
{
int ret ;
ret = s3c2410_nand_setrate ( info ) ;
if ( ret < 0 )
return ret ;
2006-06-27 14:35:46 +01:00
switch ( info - > cpu_type ) {
case TYPE_S3C2410 :
2008-07-15 11:58:31 +01:00
default :
2006-06-27 14:35:46 +01:00
break ;
case TYPE_S3C2440 :
case TYPE_S3C2412 :
2006-06-19 09:29:38 +01:00
/* enable the controller and de-assert nFCE */
2006-06-27 14:35:46 +01:00
writel ( S3C2440_NFCONT_ENABLE , info - > regs + S3C2440_NFCONT ) ;
2005-06-20 12:48:25 +01:00
}
2005-04-16 15:20:36 -07:00
return 0 ;
}
/* select chip */
static void s3c2410_nand_select_chip ( struct mtd_info * mtd , int chip )
{
struct s3c2410_nand_info * info ;
2005-11-07 11:15:49 +00:00
struct s3c2410_nand_mtd * nmtd ;
2005-04-16 15:20:36 -07:00
struct nand_chip * this = mtd - > priv ;
unsigned long cur ;
nmtd = this - > priv ;
info = nmtd - > info ;
2006-06-19 09:29:38 +01:00
if ( chip ! = - 1 & & allow_clk_stop ( info ) )
clk_enable ( info - > clk ) ;
2006-06-27 14:35:46 +01:00
cur = readl ( info - > sel_reg ) ;
2005-04-16 15:20:36 -07:00
if ( chip = = - 1 ) {
2006-06-27 14:35:46 +01:00
cur | = info - > sel_bit ;
2005-04-16 15:20:36 -07:00
} else {
2005-07-06 21:05:10 +01:00
if ( nmtd - > set ! = NULL & & chip > nmtd - > set - > nr_chips ) {
2006-06-21 15:43:05 +01:00
dev_err ( info - > device , " invalid chip %d \n " , chip ) ;
2005-04-16 15:20:36 -07:00
return ;
}
if ( info - > platform ! = NULL ) {
if ( info - > platform - > select_chip ! = NULL )
2006-05-13 18:07:53 +01:00
( info - > platform - > select_chip ) ( nmtd - > set , chip ) ;
2005-04-16 15:20:36 -07:00
}
2006-06-27 14:35:46 +01:00
cur & = ~ info - > sel_bit ;
2005-04-16 15:20:36 -07:00
}
2006-06-27 14:35:46 +01:00
writel ( cur , info - > sel_reg ) ;
2006-06-19 09:29:38 +01:00
if ( chip = = - 1 & & allow_clk_stop ( info ) )
clk_disable ( info - > clk ) ;
2005-04-16 15:20:36 -07:00
}
2006-06-19 09:43:23 +01:00
/* s3c2410_nand_hwcontrol
2005-06-20 12:48:25 +01:00
*
2006-06-19 09:43:23 +01:00
* Issue command and address cycles to the chip
2005-06-20 12:48:25 +01:00
*/
2005-04-16 15:20:36 -07:00
2006-05-23 23:25:53 +02:00
static void s3c2410_nand_hwcontrol ( struct mtd_info * mtd , int cmd ,
2006-06-10 00:53:16 +01:00
unsigned int ctrl )
2005-04-16 15:20:36 -07:00
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
2006-11-30 08:17:38 +00:00
2006-05-23 23:25:53 +02:00
if ( cmd = = NAND_CMD_NONE )
return ;
2006-06-10 00:53:16 +01:00
if ( ctrl & NAND_CLE )
2006-05-23 23:25:53 +02:00
writeb ( cmd , info - > regs + S3C2410_NFCMD ) ;
else
writeb ( cmd , info - > regs + S3C2410_NFADDR ) ;
2005-06-20 12:48:25 +01:00
}
/* command and control functions */
2006-06-10 00:53:16 +01:00
static void s3c2440_nand_hwcontrol ( struct mtd_info * mtd , int cmd ,
unsigned int ctrl )
2005-06-20 12:48:25 +01:00
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
2005-04-16 15:20:36 -07:00
2006-05-23 23:25:53 +02:00
if ( cmd = = NAND_CMD_NONE )
return ;
2006-06-10 00:53:16 +01:00
if ( ctrl & NAND_CLE )
2006-05-23 23:25:53 +02:00
writeb ( cmd , info - > regs + S3C2440_NFCMD ) ;
else
writeb ( cmd , info - > regs + S3C2440_NFADDR ) ;
2005-04-16 15:20:36 -07:00
}
/* s3c2410_nand_devready()
*
* returns 0 if the nand is busy , 1 if it is ready
*/
static int s3c2410_nand_devready ( struct mtd_info * mtd )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
return readb ( info - > regs + S3C2410_NFSTAT ) & S3C2410_NFSTAT_BUSY ;
}
2006-06-27 14:35:46 +01:00
static int s3c2440_nand_devready ( struct mtd_info * mtd )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
return readb ( info - > regs + S3C2440_NFSTAT ) & S3C2440_NFSTAT_READY ;
}
static int s3c2412_nand_devready ( struct mtd_info * mtd )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
return readb ( info - > regs + S3C2412_NFSTAT ) & S3C2412_NFSTAT_READY ;
}
2005-04-16 15:20:36 -07:00
/* ECC handling functions */
2006-06-27 14:35:46 +01:00
static int s3c2410_nand_correct_data ( struct mtd_info * mtd , u_char * dat ,
u_char * read_ecc , u_char * calc_ecc )
2005-04-16 15:20:36 -07:00
{
2007-02-02 16:59:33 +00:00
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
unsigned int diff0 , diff1 , diff2 ;
unsigned int bit , byte ;
pr_debug ( " %s(%p,%p,%p,%p) \n " , __func__ , mtd , dat , read_ecc , calc_ecc ) ;
diff0 = read_ecc [ 0 ] ^ calc_ecc [ 0 ] ;
diff1 = read_ecc [ 1 ] ^ calc_ecc [ 1 ] ;
diff2 = read_ecc [ 2 ] ^ calc_ecc [ 2 ] ;
pr_debug ( " %s: rd %02x%02x%02x calc %02x%02x%02x diff %02x%02x%02x \n " ,
__func__ ,
read_ecc [ 0 ] , read_ecc [ 1 ] , read_ecc [ 2 ] ,
calc_ecc [ 0 ] , calc_ecc [ 1 ] , calc_ecc [ 2 ] ,
diff0 , diff1 , diff2 ) ;
if ( diff0 = = 0 & & diff1 = = 0 & & diff2 = = 0 )
return 0 ; /* ECC is ok */
2008-04-15 11:36:20 +01:00
/* sometimes people do not think about using the ECC, so check
* to see if we have an 0xff , 0xff , 0xff read ECC and then ignore
* the error , on the assumption that this is an un - eccd page .
*/
if ( read_ecc [ 0 ] = = 0xff & & read_ecc [ 1 ] = = 0xff & & read_ecc [ 2 ] = = 0xff
& & info - > platform - > ignore_unset_ecc )
return 0 ;
2007-02-02 16:59:33 +00:00
/* Can we correct this ECC (ie, one row and column change).
* Note , this is similar to the 256 error code on smartmedia */
if ( ( ( diff0 ^ ( diff0 > > 1 ) ) & 0x55 ) = = 0x55 & &
( ( diff1 ^ ( diff1 > > 1 ) ) & 0x55 ) = = 0x55 & &
( ( diff2 ^ ( diff2 > > 1 ) ) & 0x55 ) = = 0x55 ) {
/* calculate the bit position of the error */
2007-10-18 18:02:43 -07:00
bit = ( ( diff2 > > 3 ) & 1 ) |
( ( diff2 > > 4 ) & 2 ) |
( ( diff2 > > 5 ) & 4 ) ;
2005-04-16 15:20:36 -07:00
2007-02-02 16:59:33 +00:00
/* calculate the byte position of the error */
2005-04-16 15:20:36 -07:00
2007-10-18 18:02:43 -07:00
byte = ( ( diff2 < < 7 ) & 0x100 ) |
( ( diff1 < < 0 ) & 0x80 ) |
( ( diff1 < < 1 ) & 0x40 ) |
( ( diff1 < < 2 ) & 0x20 ) |
( ( diff1 < < 3 ) & 0x10 ) |
( ( diff0 > > 4 ) & 0x08 ) |
( ( diff0 > > 3 ) & 0x04 ) |
( ( diff0 > > 2 ) & 0x02 ) |
( ( diff0 > > 1 ) & 0x01 ) ;
2007-02-02 16:59:33 +00:00
dev_dbg ( info - > device , " correcting error bit %d, byte %d \n " ,
bit , byte ) ;
dat [ byte ] ^ = ( 1 < < bit ) ;
return 1 ;
}
/* if there is only one bit difference in the ECC, then
* one of only a row or column parity has changed , which
* means the error is most probably in the ECC itself */
diff0 | = ( diff1 < < 8 ) ;
diff0 | = ( diff2 < < 16 ) ;
if ( ( diff0 & ~ ( 1 < < fls ( diff0 ) ) ) = = 0 )
return 1 ;
2007-10-18 18:02:44 -07:00
return - 1 ;
2005-04-16 15:20:36 -07:00
}
2005-06-20 12:48:25 +01:00
/* ECC functions
*
* These allow the s3c2410 and s3c2440 to use the controller ' s ECC
* generator block to ECC the data as it passes through ]
*/
2005-04-16 15:20:36 -07:00
static void s3c2410_nand_enable_hwecc ( struct mtd_info * mtd , int mode )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
unsigned long ctrl ;
ctrl = readl ( info - > regs + S3C2410_NFCONF ) ;
ctrl | = S3C2410_NFCONF_INITECC ;
writel ( ctrl , info - > regs + S3C2410_NFCONF ) ;
}
2007-02-13 12:30:38 +01:00
static void s3c2412_nand_enable_hwecc ( struct mtd_info * mtd , int mode )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
unsigned long ctrl ;
ctrl = readl ( info - > regs + S3C2440_NFCONT ) ;
writel ( ctrl | S3C2412_NFCONT_INIT_MAIN_ECC , info - > regs + S3C2440_NFCONT ) ;
}
2005-06-20 12:48:25 +01:00
static void s3c2440_nand_enable_hwecc ( struct mtd_info * mtd , int mode )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
unsigned long ctrl ;
ctrl = readl ( info - > regs + S3C2440_NFCONT ) ;
writel ( ctrl | S3C2440_NFCONT_INITECC , info - > regs + S3C2440_NFCONT ) ;
}
2006-05-13 18:07:53 +01:00
static int s3c2410_nand_calculate_ecc ( struct mtd_info * mtd , const u_char * dat , u_char * ecc_code )
2005-04-16 15:20:36 -07:00
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
ecc_code [ 0 ] = readb ( info - > regs + S3C2410_NFECC + 0 ) ;
ecc_code [ 1 ] = readb ( info - > regs + S3C2410_NFECC + 1 ) ;
ecc_code [ 2 ] = readb ( info - > regs + S3C2410_NFECC + 2 ) ;
2007-02-02 16:59:33 +00:00
pr_debug ( " %s: returning ecc %02x%02x%02x \n " , __func__ ,
ecc_code [ 0 ] , ecc_code [ 1 ] , ecc_code [ 2 ] ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
2007-02-13 12:30:38 +01:00
static int s3c2412_nand_calculate_ecc ( struct mtd_info * mtd , const u_char * dat , u_char * ecc_code )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
unsigned long ecc = readl ( info - > regs + S3C2412_NFMECC0 ) ;
ecc_code [ 0 ] = ecc ;
ecc_code [ 1 ] = ecc > > 8 ;
ecc_code [ 2 ] = ecc > > 16 ;
pr_debug ( " calculate_ecc: returning ecc %02x,%02x,%02x \n " , ecc_code [ 0 ] , ecc_code [ 1 ] , ecc_code [ 2 ] ) ;
return 0 ;
}
2006-05-13 18:07:53 +01:00
static int s3c2440_nand_calculate_ecc ( struct mtd_info * mtd , const u_char * dat , u_char * ecc_code )
2005-06-20 12:48:25 +01:00
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
unsigned long ecc = readl ( info - > regs + S3C2440_NFMECC0 ) ;
ecc_code [ 0 ] = ecc ;
ecc_code [ 1 ] = ecc > > 8 ;
ecc_code [ 2 ] = ecc > > 16 ;
2008-04-15 11:36:19 +01:00
pr_debug ( " %s: returning ecc %06lx \n " , __func__ , ecc & 0xffffff ) ;
2005-06-20 12:48:25 +01:00
return 0 ;
}
/* over-ride the standard functions for a little more speed. We can
* use read / write block to move the data buffers to / from the controller
*/
2005-04-16 15:20:36 -07:00
static void s3c2410_nand_read_buf ( struct mtd_info * mtd , u_char * buf , int len )
{
struct nand_chip * this = mtd - > priv ;
readsb ( this - > IO_ADDR_R , buf , len ) ;
}
2007-10-18 17:43:07 -07:00
static void s3c2440_nand_read_buf ( struct mtd_info * mtd , u_char * buf , int len )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
readsl ( info - > regs + S3C2440_NFDATA , buf , len / 4 ) ;
}
2006-05-13 18:07:53 +01:00
static void s3c2410_nand_write_buf ( struct mtd_info * mtd , const u_char * buf , int len )
2005-04-16 15:20:36 -07:00
{
struct nand_chip * this = mtd - > priv ;
writesb ( this - > IO_ADDR_W , buf , len ) ;
}
2007-10-18 17:43:07 -07:00
static void s3c2440_nand_write_buf ( struct mtd_info * mtd , const u_char * buf , int len )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
writesl ( info - > regs + S3C2440_NFDATA , buf , len / 4 ) ;
}
2008-07-15 11:58:31 +01:00
/* cpufreq driver support */
# ifdef CONFIG_CPU_FREQ
static int s3c2410_nand_cpufreq_transition ( struct notifier_block * nb ,
unsigned long val , void * data )
{
struct s3c2410_nand_info * info ;
unsigned long newclk ;
info = container_of ( nb , struct s3c2410_nand_info , freq_transition ) ;
newclk = clk_get_rate ( info - > clk ) ;
if ( ( val = = CPUFREQ_POSTCHANGE & & newclk < info - > clk_rate ) | |
( val = = CPUFREQ_PRECHANGE & & newclk > info - > clk_rate ) ) {
s3c2410_nand_setrate ( info ) ;
}
return 0 ;
}
static inline int s3c2410_nand_cpufreq_register ( struct s3c2410_nand_info * info )
{
info - > freq_transition . notifier_call = s3c2410_nand_cpufreq_transition ;
return cpufreq_register_notifier ( & info - > freq_transition ,
CPUFREQ_TRANSITION_NOTIFIER ) ;
}
static inline void s3c2410_nand_cpufreq_deregister ( struct s3c2410_nand_info * info )
{
cpufreq_unregister_notifier ( & info - > freq_transition ,
CPUFREQ_TRANSITION_NOTIFIER ) ;
}
# else
static inline int s3c2410_nand_cpufreq_register ( struct s3c2410_nand_info * info )
{
return 0 ;
}
static inline void s3c2410_nand_cpufreq_deregister ( struct s3c2410_nand_info * info )
{
}
# endif
2005-04-16 15:20:36 -07:00
/* device management functions */
2005-11-09 22:32:44 +00:00
static int s3c2410_nand_remove ( struct platform_device * pdev )
2005-04-16 15:20:36 -07:00
{
2005-11-09 22:32:44 +00:00
struct s3c2410_nand_info * info = to_nand_info ( pdev ) ;
2005-04-16 15:20:36 -07:00
2005-11-09 22:32:44 +00:00
platform_set_drvdata ( pdev , NULL ) ;
2005-04-16 15:20:36 -07:00
2005-11-07 11:15:49 +00:00
if ( info = = NULL )
2005-04-16 15:20:36 -07:00
return 0 ;
2008-07-15 11:58:31 +01:00
s3c2410_nand_cpufreq_deregister ( info ) ;
/* Release all our mtds and their partitions, then go through
* freeing the resources used
2005-04-16 15:20:36 -07:00
*/
2005-11-07 11:15:49 +00:00
2005-04-16 15:20:36 -07:00
if ( info - > mtds ! = NULL ) {
struct s3c2410_nand_mtd * ptr = info - > mtds ;
int mtdno ;
for ( mtdno = 0 ; mtdno < info - > mtd_count ; mtdno + + , ptr + + ) {
pr_debug ( " releasing mtd %d (%p) \n " , mtdno , ptr ) ;
nand_release ( & ptr - > mtd ) ;
}
kfree ( info - > mtds ) ;
}
/* free the common resources */
if ( info - > clk ! = NULL & & ! IS_ERR ( info - > clk ) ) {
2006-06-19 09:29:38 +01:00
if ( ! allow_clk_stop ( info ) )
clk_disable ( info - > clk ) ;
2005-04-16 15:20:36 -07:00
clk_put ( info - > clk ) ;
}
if ( info - > regs ! = NULL ) {
iounmap ( info - > regs ) ;
info - > regs = NULL ;
}
if ( info - > area ! = NULL ) {
release_resource ( info - > area ) ;
kfree ( info - > area ) ;
info - > area = NULL ;
}
kfree ( info ) ;
return 0 ;
}
# ifdef CONFIG_MTD_PARTITIONS
static int s3c2410_nand_add_partition ( struct s3c2410_nand_info * info ,
struct s3c2410_nand_mtd * mtd ,
struct s3c2410_nand_set * set )
{
if ( set = = NULL )
return add_mtd_device ( & mtd - > mtd ) ;
if ( set - > nr_partitions > 0 & & set - > partitions ! = NULL ) {
2006-05-13 18:07:53 +01:00
return add_mtd_partitions ( & mtd - > mtd , set - > partitions , set - > nr_partitions ) ;
2005-04-16 15:20:36 -07:00
}
return add_mtd_device ( & mtd - > mtd ) ;
}
# else
static int s3c2410_nand_add_partition ( struct s3c2410_nand_info * info ,
struct s3c2410_nand_mtd * mtd ,
struct s3c2410_nand_set * set )
{
return add_mtd_device ( & mtd - > mtd ) ;
}
# endif
/* s3c2410_nand_init_chip
*
2005-11-07 11:15:49 +00:00
* init a single instance of an chip
2005-04-16 15:20:36 -07:00
*/
static void s3c2410_nand_init_chip ( struct s3c2410_nand_info * info ,
struct s3c2410_nand_mtd * nmtd ,
struct s3c2410_nand_set * set )
{
struct nand_chip * chip = & nmtd - > chip ;
2006-06-27 14:35:46 +01:00
void __iomem * regs = info - > regs ;
2005-04-16 15:20:36 -07:00
chip - > write_buf = s3c2410_nand_write_buf ;
chip - > read_buf = s3c2410_nand_read_buf ;
chip - > select_chip = s3c2410_nand_select_chip ;
chip - > chip_delay = 50 ;
chip - > priv = nmtd ;
chip - > options = 0 ;
chip - > controller = & info - > controller ;
2006-06-27 14:35:46 +01:00
switch ( info - > cpu_type ) {
case TYPE_S3C2410 :
chip - > IO_ADDR_W = regs + S3C2410_NFDATA ;
info - > sel_reg = regs + S3C2410_NFCONF ;
info - > sel_bit = S3C2410_NFCONF_nFCE ;
chip - > cmd_ctrl = s3c2410_nand_hwcontrol ;
chip - > dev_ready = s3c2410_nand_devready ;
break ;
case TYPE_S3C2440 :
chip - > IO_ADDR_W = regs + S3C2440_NFDATA ;
info - > sel_reg = regs + S3C2440_NFCONT ;
info - > sel_bit = S3C2440_NFCONT_nFCE ;
chip - > cmd_ctrl = s3c2440_nand_hwcontrol ;
chip - > dev_ready = s3c2440_nand_devready ;
2007-10-18 17:43:07 -07:00
chip - > read_buf = s3c2440_nand_read_buf ;
chip - > write_buf = s3c2440_nand_write_buf ;
2006-06-27 14:35:46 +01:00
break ;
case TYPE_S3C2412 :
chip - > IO_ADDR_W = regs + S3C2440_NFDATA ;
info - > sel_reg = regs + S3C2440_NFCONT ;
info - > sel_bit = S3C2412_NFCONT_nFCE0 ;
chip - > cmd_ctrl = s3c2440_nand_hwcontrol ;
chip - > dev_ready = s3c2412_nand_devready ;
if ( readl ( regs + S3C2410_NFCONF ) & S3C2412_NFCONF_NANDBOOT )
dev_info ( info - > device , " System booted from NAND \n " ) ;
break ;
}
chip - > IO_ADDR_R = chip - > IO_ADDR_W ;
2005-06-20 12:48:25 +01:00
2005-04-16 15:20:36 -07:00
nmtd - > info = info ;
nmtd - > mtd . priv = chip ;
2006-05-14 01:20:46 +01:00
nmtd - > mtd . owner = THIS_MODULE ;
2005-04-16 15:20:36 -07:00
nmtd - > set = set ;
if ( hardware_ecc ) {
2006-05-23 12:00:46 +02:00
chip - > ecc . calculate = s3c2410_nand_calculate_ecc ;
2006-06-27 14:35:46 +01:00
chip - > ecc . correct = s3c2410_nand_correct_data ;
2006-05-23 12:00:46 +02:00
chip - > ecc . mode = NAND_ECC_HW ;
2005-06-20 12:48:25 +01:00
2006-06-27 14:35:46 +01:00
switch ( info - > cpu_type ) {
case TYPE_S3C2410 :
chip - > ecc . hwctl = s3c2410_nand_enable_hwecc ;
chip - > ecc . calculate = s3c2410_nand_calculate_ecc ;
break ;
case TYPE_S3C2412 :
2007-02-13 12:30:38 +01:00
chip - > ecc . hwctl = s3c2412_nand_enable_hwecc ;
chip - > ecc . calculate = s3c2412_nand_calculate_ecc ;
break ;
2006-06-27 14:35:46 +01:00
case TYPE_S3C2440 :
chip - > ecc . hwctl = s3c2440_nand_enable_hwecc ;
chip - > ecc . calculate = s3c2440_nand_calculate_ecc ;
break ;
2005-06-20 12:48:25 +01:00
}
2005-04-16 15:20:36 -07:00
} else {
2006-05-23 12:00:46 +02:00
chip - > ecc . mode = NAND_ECC_SOFT ;
2005-04-16 15:20:36 -07:00
}
2008-04-15 11:36:21 +01:00
if ( set - > ecc_layout ! = NULL )
chip - > ecc . layout = set - > ecc_layout ;
2008-04-15 11:36:22 +01:00
if ( set - > disable_ecc )
chip - > ecc . mode = NAND_ECC_NONE ;
2005-04-16 15:20:36 -07:00
}
2008-04-15 11:36:19 +01:00
/* s3c2410_nand_update_chip
*
* post - probe chip update , to change any items , such as the
* layout for large page nand
*/
static void s3c2410_nand_update_chip ( struct s3c2410_nand_info * info ,
struct s3c2410_nand_mtd * nmtd )
{
struct nand_chip * chip = & nmtd - > chip ;
2008-05-20 17:32:14 +01:00
dev_dbg ( info - > device , " chip %p => page shift %d \n " ,
chip , chip - > page_shift ) ;
2008-04-15 11:36:19 +01:00
if ( hardware_ecc ) {
/* change the behaviour depending on wether we are using
* the large or small page nand device */
if ( chip - > page_shift > 10 ) {
chip - > ecc . size = 256 ;
chip - > ecc . bytes = 3 ;
} else {
chip - > ecc . size = 512 ;
chip - > ecc . bytes = 3 ;
chip - > ecc . layout = & nand_hw_eccoob ;
}
}
}
2005-04-16 15:20:36 -07:00
/* s3c2410_nand_probe
*
* called by device layer when it finds a device matching
* one our driver can handled . This code checks to see if
* it can allocate all necessary resources then calls the
* nand layer to look for devices
*/
2006-06-27 14:35:46 +01:00
static int s3c24xx_nand_probe ( struct platform_device * pdev ,
enum s3c_cpu_type cpu_type )
2005-04-16 15:20:36 -07:00
{
2005-11-09 22:32:44 +00:00
struct s3c2410_platform_nand * plat = to_nand_plat ( pdev ) ;
2005-04-16 15:20:36 -07:00
struct s3c2410_nand_info * info ;
struct s3c2410_nand_mtd * nmtd ;
struct s3c2410_nand_set * sets ;
struct resource * res ;
int err = 0 ;
int size ;
int nr_sets ;
int setno ;
2005-11-09 22:32:44 +00:00
pr_debug ( " s3c2410_nand_probe(%p) \n " , pdev ) ;
2005-04-16 15:20:36 -07:00
info = kmalloc ( sizeof ( * info ) , GFP_KERNEL ) ;
if ( info = = NULL ) {
2005-11-09 22:32:44 +00:00
dev_err ( & pdev - > dev , " no memory for flash info \n " ) ;
2005-04-16 15:20:36 -07:00
err = - ENOMEM ;
goto exit_error ;
}
memzero ( info , sizeof ( * info ) ) ;
2005-11-09 22:32:44 +00:00
platform_set_drvdata ( pdev , info ) ;
2005-04-16 15:20:36 -07:00
spin_lock_init ( & info - > controller . lock ) ;
2005-06-20 12:48:25 +01:00
init_waitqueue_head ( & info - > controller . wq ) ;
2005-04-16 15:20:36 -07:00
/* get the clock source and enable it */
2005-11-09 22:32:44 +00:00
info - > clk = clk_get ( & pdev - > dev , " nand " ) ;
2005-04-16 15:20:36 -07:00
if ( IS_ERR ( info - > clk ) ) {
2007-10-18 03:06:30 -07:00
dev_err ( & pdev - > dev , " failed to get clock \n " ) ;
2005-04-16 15:20:36 -07:00
err = - ENOENT ;
goto exit_error ;
}
clk_enable ( info - > clk ) ;
/* allocate and map the resource */
2005-06-20 12:48:25 +01:00
/* currently we assume we have the one resource */
res = pdev - > resource ;
2005-04-16 15:20:36 -07:00
size = res - > end - res - > start + 1 ;
info - > area = request_mem_region ( res - > start , size , pdev - > name ) ;
if ( info - > area = = NULL ) {
2005-11-09 22:32:44 +00:00
dev_err ( & pdev - > dev , " cannot reserve register region \n " ) ;
2005-04-16 15:20:36 -07:00
err = - ENOENT ;
goto exit_error ;
}
2005-11-09 22:32:44 +00:00
info - > device = & pdev - > dev ;
2005-06-20 12:48:25 +01:00
info - > platform = plat ;
info - > regs = ioremap ( res - > start , size ) ;
2006-06-27 14:35:46 +01:00
info - > cpu_type = cpu_type ;
2005-04-16 15:20:36 -07:00
if ( info - > regs = = NULL ) {
2005-11-09 22:32:44 +00:00
dev_err ( & pdev - > dev , " cannot reserve register region \n " ) ;
2005-04-16 15:20:36 -07:00
err = - EIO ;
goto exit_error ;
2005-11-07 11:15:49 +00:00
}
2005-04-16 15:20:36 -07:00
2005-11-09 22:32:44 +00:00
dev_dbg ( & pdev - > dev , " mapped registers at %p \n " , info - > regs ) ;
2005-04-16 15:20:36 -07:00
/* initialise the hardware */
2008-07-15 11:58:31 +01:00
err = s3c2410_nand_inithw ( info ) ;
2005-04-16 15:20:36 -07:00
if ( err ! = 0 )
goto exit_error ;
sets = ( plat ! = NULL ) ? plat - > sets : NULL ;
nr_sets = ( plat ! = NULL ) ? plat - > nr_sets : 1 ;
info - > mtd_count = nr_sets ;
/* allocate our information */
size = nr_sets * sizeof ( * info - > mtds ) ;
info - > mtds = kmalloc ( size , GFP_KERNEL ) ;
if ( info - > mtds = = NULL ) {
2005-11-09 22:32:44 +00:00
dev_err ( & pdev - > dev , " failed to allocate mtd storage \n " ) ;
2005-04-16 15:20:36 -07:00
err = - ENOMEM ;
goto exit_error ;
}
memzero ( info - > mtds , size ) ;
/* initialise all possible chips */
nmtd = info - > mtds ;
for ( setno = 0 ; setno < nr_sets ; setno + + , nmtd + + ) {
2006-05-13 18:07:53 +01:00
pr_debug ( " initialising set %d (%p, info %p) \n " , setno , nmtd , info ) ;
2005-11-07 11:15:49 +00:00
2005-04-16 15:20:36 -07:00
s3c2410_nand_init_chip ( info , nmtd , sets ) ;
2008-04-15 11:36:19 +01:00
nmtd - > scan_res = nand_scan_ident ( & nmtd - > mtd ,
( sets ) ? sets - > nr_chips : 1 ) ;
2005-04-16 15:20:36 -07:00
if ( nmtd - > scan_res = = 0 ) {
2008-04-15 11:36:19 +01:00
s3c2410_nand_update_chip ( info , nmtd ) ;
nand_scan_tail ( & nmtd - > mtd ) ;
2005-04-16 15:20:36 -07:00
s3c2410_nand_add_partition ( info , nmtd , sets ) ;
}
if ( sets ! = NULL )
sets + + ;
}
2005-11-07 11:15:49 +00:00
2008-07-15 11:58:31 +01:00
err = s3c2410_nand_cpufreq_register ( info ) ;
if ( err < 0 ) {
dev_err ( & pdev - > dev , " failed to init cpufreq support \n " ) ;
goto exit_error ;
}
2006-06-19 09:29:38 +01:00
if ( allow_clk_stop ( info ) ) {
dev_info ( & pdev - > dev , " clock idle support enabled \n " ) ;
clk_disable ( info - > clk ) ;
}
2005-04-16 15:20:36 -07:00
pr_debug ( " initialised ok \n " ) ;
return 0 ;
exit_error :
2005-11-09 22:32:44 +00:00
s3c2410_nand_remove ( pdev ) ;
2005-04-16 15:20:36 -07:00
if ( err = = 0 )
err = - EINVAL ;
return err ;
}
2006-06-19 09:29:38 +01:00
/* PM Support */
# ifdef CONFIG_PM
static int s3c24xx_nand_suspend ( struct platform_device * dev , pm_message_t pm )
{
struct s3c2410_nand_info * info = platform_get_drvdata ( dev ) ;
if ( info ) {
2008-04-15 11:36:18 +01:00
info - > save_sel = readl ( info - > sel_reg ) ;
2007-11-19 23:28:07 +00:00
/* For the moment, we must ensure nFCE is high during
* the time we are suspended . This really should be
* handled by suspending the MTDs we are using , but
* that is currently not the case . */
2008-04-15 11:36:18 +01:00
writel ( info - > save_sel | info - > sel_bit , info - > sel_reg ) ;
2007-11-19 23:28:07 +00:00
2006-06-19 09:29:38 +01:00
if ( ! allow_clk_stop ( info ) )
clk_disable ( info - > clk ) ;
}
return 0 ;
}
static int s3c24xx_nand_resume ( struct platform_device * dev )
{
struct s3c2410_nand_info * info = platform_get_drvdata ( dev ) ;
2008-04-15 11:36:18 +01:00
unsigned long sel ;
2006-06-19 09:29:38 +01:00
if ( info ) {
clk_enable ( info - > clk ) ;
2008-07-15 11:58:31 +01:00
s3c2410_nand_inithw ( info ) ;
2006-06-19 09:29:38 +01:00
2007-11-19 23:28:07 +00:00
/* Restore the state of the nFCE line. */
2008-04-15 11:36:18 +01:00
sel = readl ( info - > sel_reg ) ;
sel & = ~ info - > sel_bit ;
sel | = info - > save_sel & info - > sel_bit ;
writel ( sel , info - > sel_reg ) ;
2007-11-19 23:28:07 +00:00
2006-06-19 09:29:38 +01:00
if ( allow_clk_stop ( info ) )
clk_disable ( info - > clk ) ;
}
return 0 ;
}
# else
# define s3c24xx_nand_suspend NULL
# define s3c24xx_nand_resume NULL
# endif
2005-06-20 12:48:25 +01:00
/* driver device registration */
2005-11-09 22:32:44 +00:00
static int s3c2410_nand_probe ( struct platform_device * dev )
2005-06-20 12:48:25 +01:00
{
2006-06-27 14:35:46 +01:00
return s3c24xx_nand_probe ( dev , TYPE_S3C2410 ) ;
2005-06-20 12:48:25 +01:00
}
2005-11-09 22:32:44 +00:00
static int s3c2440_nand_probe ( struct platform_device * dev )
2005-06-20 12:48:25 +01:00
{
2006-06-27 14:35:46 +01:00
return s3c24xx_nand_probe ( dev , TYPE_S3C2440 ) ;
}
static int s3c2412_nand_probe ( struct platform_device * dev )
{
return s3c24xx_nand_probe ( dev , TYPE_S3C2412 ) ;
2005-06-20 12:48:25 +01:00
}
2005-11-09 22:32:44 +00:00
static struct platform_driver s3c2410_nand_driver = {
2005-04-16 15:20:36 -07:00
. probe = s3c2410_nand_probe ,
. remove = s3c2410_nand_remove ,
2006-06-19 09:29:38 +01:00
. suspend = s3c24xx_nand_suspend ,
. resume = s3c24xx_nand_resume ,
2005-11-09 22:32:44 +00:00
. driver = {
. name = " s3c2410-nand " ,
. owner = THIS_MODULE ,
} ,
2005-04-16 15:20:36 -07:00
} ;
2005-11-09 22:32:44 +00:00
static struct platform_driver s3c2440_nand_driver = {
2005-06-20 12:48:25 +01:00
. probe = s3c2440_nand_probe ,
. remove = s3c2410_nand_remove ,
2006-06-19 09:29:38 +01:00
. suspend = s3c24xx_nand_suspend ,
. resume = s3c24xx_nand_resume ,
2005-11-09 22:32:44 +00:00
. driver = {
. name = " s3c2440-nand " ,
. owner = THIS_MODULE ,
} ,
2005-06-20 12:48:25 +01:00
} ;
2006-06-27 14:35:46 +01:00
static struct platform_driver s3c2412_nand_driver = {
. probe = s3c2412_nand_probe ,
. remove = s3c2410_nand_remove ,
. suspend = s3c24xx_nand_suspend ,
. resume = s3c24xx_nand_resume ,
. driver = {
. name = " s3c2412-nand " ,
. owner = THIS_MODULE ,
} ,
} ;
2005-04-16 15:20:36 -07:00
static int __init s3c2410_nand_init ( void )
{
2005-06-20 12:48:25 +01:00
printk ( " S3C24XX NAND Driver, (c) 2004 Simtec Electronics \n " ) ;
2006-06-27 14:35:46 +01:00
platform_driver_register ( & s3c2412_nand_driver ) ;
2005-11-09 22:32:44 +00:00
platform_driver_register ( & s3c2440_nand_driver ) ;
return platform_driver_register ( & s3c2410_nand_driver ) ;
2005-04-16 15:20:36 -07:00
}
static void __exit s3c2410_nand_exit ( void )
{
2006-06-27 14:35:46 +01:00
platform_driver_unregister ( & s3c2412_nand_driver ) ;
2005-11-09 22:32:44 +00:00
platform_driver_unregister ( & s3c2440_nand_driver ) ;
platform_driver_unregister ( & s3c2410_nand_driver ) ;
2005-04-16 15:20:36 -07:00
}
module_init ( s3c2410_nand_init ) ;
module_exit ( s3c2410_nand_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Ben Dooks <ben@simtec.co.uk> " ) ;
2005-06-20 12:48:25 +01:00
MODULE_DESCRIPTION ( " S3C24XX MTD NAND driver " ) ;
2008-04-18 13:44:27 -07:00
MODULE_ALIAS ( " platform:s3c2410-nand " ) ;
MODULE_ALIAS ( " platform:s3c2412-nand " ) ;
MODULE_ALIAS ( " platform:s3c2440-nand " ) ;