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
*/
2012-07-16 16:02:23 +05:30
# define pr_fmt(fmt) "nand-s3c2410: " fmt
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
# define DEBUG
# endif
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/string.h>
2012-07-16 16:02:24 +05:30
# include <linux/io.h>
2005-04-16 15:20:36 -07:00
# 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>
2016-10-26 21:59:55 -02:00
# include <linux/of.h>
# include <linux/of_device.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>
2012-08-24 15:22:12 +02:00
# include <linux/platform_data/mtd-nand-s3c2410.h>
2005-04-16 15:20:36 -07:00
2014-01-10 11:24:13 +05:30
# define S3C2410_NFREG(x) (x)
# define S3C2410_NFCONF S3C2410_NFREG(0x00)
# define S3C2410_NFCMD S3C2410_NFREG(0x04)
# define S3C2410_NFADDR S3C2410_NFREG(0x08)
# define S3C2410_NFDATA S3C2410_NFREG(0x0C)
# define S3C2410_NFSTAT S3C2410_NFREG(0x10)
# define S3C2410_NFECC S3C2410_NFREG(0x14)
# define S3C2440_NFCONT S3C2410_NFREG(0x04)
# define S3C2440_NFCMD S3C2410_NFREG(0x08)
# define S3C2440_NFADDR S3C2410_NFREG(0x0C)
# define S3C2440_NFDATA S3C2410_NFREG(0x10)
# define S3C2440_NFSTAT S3C2410_NFREG(0x20)
# define S3C2440_NFMECC0 S3C2410_NFREG(0x2C)
# define S3C2412_NFSTAT S3C2410_NFREG(0x28)
# define S3C2412_NFMECC0 S3C2410_NFREG(0x34)
# define S3C2410_NFCONF_EN (1<<15)
# define S3C2410_NFCONF_INITECC (1<<12)
# define S3C2410_NFCONF_nFCE (1<<11)
# define S3C2410_NFCONF_TACLS(x) ((x)<<8)
# define S3C2410_NFCONF_TWRPH0(x) ((x)<<4)
# define S3C2410_NFCONF_TWRPH1(x) ((x)<<0)
# define S3C2410_NFSTAT_BUSY (1<<0)
# define S3C2440_NFCONF_TACLS(x) ((x)<<12)
# define S3C2440_NFCONF_TWRPH0(x) ((x)<<8)
# define S3C2440_NFCONF_TWRPH1(x) ((x)<<4)
# define S3C2440_NFCONT_INITECC (1<<4)
# define S3C2440_NFCONT_nFCE (1<<1)
# define S3C2440_NFCONT_ENABLE (1<<0)
# define S3C2440_NFSTAT_READY (1<<0)
# define S3C2412_NFCONF_NANDBOOT (1<<31)
# define S3C2412_NFCONT_INIT_MAIN_ECC (1<<5)
# define S3C2412_NFCONT_nFCE0 (1<<1)
# define S3C2412_NFSTAT_READY (1<<0)
2005-04-16 15:20:36 -07:00
/* new oob placement block for use with hardware ecc generation
*/
2016-02-03 20:03:30 +01:00
static int s3c2410_ooblayout_ecc ( struct mtd_info * mtd , int section ,
struct mtd_oob_region * oobregion )
{
if ( section )
return - ERANGE ;
oobregion - > offset = 0 ;
oobregion - > length = 3 ;
return 0 ;
}
static int s3c2410_ooblayout_free ( struct mtd_info * mtd , int section ,
struct mtd_oob_region * oobregion )
{
if ( section )
return - ERANGE ;
oobregion - > offset = 8 ;
oobregion - > length = 8 ;
return 0 ;
}
2005-04-16 15:20:36 -07:00
2016-02-03 20:03:30 +01:00
static const struct mtd_ooblayout_ops s3c2410_ooblayout_ops = {
. ecc = s3c2410_ooblayout_ecc ,
. free = s3c2410_ooblayout_free ,
2005-04-16 15:20:36 -07:00
} ;
/* controller and mtd information */
struct s3c2410_nand_info ;
2009-05-30 17:18:15 +01:00
/**
* struct s3c2410_nand_mtd - driver MTD structure
* @ mtd : The MTD instance to pass to the MTD layer .
* @ chip : The NAND chip information .
* @ set : The platform information supplied for this set of NAND chips .
* @ info : Link back to the hardware information .
* @ scan_res : The result from calling nand_scan_ident ( ) .
*/
2005-04-16 15:20:36 -07:00
struct s3c2410_nand_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 ,
} ;
2011-04-13 11:59:30 +02:00
enum s3c_nand_clk_state {
CLOCK_DISABLE = 0 ,
CLOCK_ENABLE ,
CLOCK_SUSPEND ,
} ;
2005-04-16 15:20:36 -07:00
/* overview of the s3c2410 nand state */
2009-05-30 17:18:15 +01:00
/**
* struct s3c2410_nand_info - NAND controller state .
* @ mtds : An array of MTD instances on this controoler .
* @ platform : The platform data for this board .
* @ device : The platform device we bound to .
* @ clk : The clock resource for this controller .
2012-08-21 14:24:09 +05:30
* @ regs : The area mapped for the hardware registers .
2009-05-30 17:18:15 +01:00
* @ sel_reg : Pointer to the register controlling the NAND selection .
* @ sel_bit : The bit in @ sel_reg to select the NAND chip .
* @ mtd_count : The number of MTDs created from this controller .
* @ save_sel : The contents of @ sel_reg to be saved over suspend .
* @ clk_rate : The clock rate from @ clk .
2011-04-13 11:59:30 +02:00
* @ clk_state : The current clock state .
2009-05-30 17:18:15 +01:00
* @ cpu_type : The exact type of this controller .
*/
2005-04-16 15:20:36 -07:00
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 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 ;
2011-04-13 11:59:30 +02:00
enum s3c_nand_clk_state clk_state ;
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
2016-06-27 14:51:38 +02:00
# ifdef CONFIG_ARM_S3C24XX_CPUFREQ
2008-07-15 11:58:31 +01:00
struct notifier_block freq_transition ;
# endif
2005-04-16 15:20:36 -07:00
} ;
2016-10-26 21:59:55 -02:00
struct s3c24XX_nand_devtype_data {
enum s3c_cpu_type type ;
} ;
static const struct s3c24XX_nand_devtype_data s3c2410_nand_devtype_data = {
. type = TYPE_S3C2410 ,
} ;
static const struct s3c24XX_nand_devtype_data s3c2412_nand_devtype_data = {
. type = TYPE_S3C2412 ,
} ;
static const struct s3c24XX_nand_devtype_data s3c2440_nand_devtype_data = {
. type = TYPE_S3C2440 ,
} ;
2005-04-16 15:20:36 -07:00
/* conversion functions */
static struct s3c2410_nand_mtd * s3c2410_nand_mtd_toours ( struct mtd_info * mtd )
{
2015-12-10 09:00:22 +01:00
return container_of ( mtd_to_nand ( mtd ) , struct s3c2410_nand_mtd ,
chip ) ;
2005-04-16 15:20:36 -07:00
}
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
{
2013-07-30 17:18:33 +09:00
return dev_get_platdata ( & dev - > dev ) ;
2005-04-16 15:20:36 -07:00
}
2011-04-13 11:59:30 +02:00
static inline int allow_clk_suspend ( struct s3c2410_nand_info * info )
2006-06-19 09:29:38 +01:00
{
2012-07-16 16:02:25 +05:30
# ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
return 1 ;
# else
return 0 ;
# endif
2006-06-19 09:29:38 +01:00
}
2011-04-13 11:59:30 +02:00
/**
* s3c2410_nand_clk_set_state - Enable , disable or suspend NAND clock .
* @ info : The controller instance .
* @ new_state : State to which clock should be set .
*/
static void s3c2410_nand_clk_set_state ( struct s3c2410_nand_info * info ,
enum s3c_nand_clk_state new_state )
{
if ( ! allow_clk_suspend ( info ) & & new_state = = CLOCK_SUSPEND )
return ;
if ( info - > clk_state = = CLOCK_ENABLE ) {
if ( new_state ! = CLOCK_ENABLE )
2014-06-30 22:12:16 +03:00
clk_disable_unprepare ( info - > clk ) ;
2011-04-13 11:59:30 +02:00
} else {
if ( new_state = = CLOCK_ENABLE )
2014-06-30 22:12:16 +03:00
clk_prepare_enable ( info - > clk ) ;
2011-04-13 11:59:30 +02:00
}
info - > clk_state = new_state ;
}
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
2009-05-30 17:18:15 +01:00
/**
* s3c_nand_calc_rate - calculate timing data .
* @ wanted : The cycle time in nanoseconds .
* @ clk : The clock rate in kHz .
* @ max : The maximum divider value .
*
* Calculate the timing value from the given parameters .
*/
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 ;
2009-05-30 18:34:16 +01:00
result = DIV_ROUND_UP ( ( wanted * clk ) , NS_IN_KHZ ) ;
2005-04-16 15:20:36 -07:00
pr_debug ( " result %d from %ld, %d \n " , result , clk , wanted ) ;
if ( result > max ) {
2012-07-16 16:02:23 +05:30
pr_err ( " %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 ;
}
2012-07-16 16:02:26 +05:30
# define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
2005-04-16 15:20:36 -07:00
/* controller setup */
2009-05-30 17:18:15 +01:00
/**
* s3c2410_nand_setrate - setup controller timing information .
* @ info : The controller instance .
*
* Given the information supplied by the platform , calculate and set
* the necessary timing registers in the hardware to generate the
* necessary timing cycles to the hardware .
*/
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 ) ;
2009-05-10 15:41:54 -05:00
unsigned long uninitialized_var ( set ) , cfg , uninitialized_var ( mask ) ;
2008-07-15 11:58:31 +01:00
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 " ,
2012-07-16 16:02:26 +05:30
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 :
2009-06-03 13:46:54 +02:00
mask = ( S3C2440_NFCONF_TACLS ( tacls_max - 1 ) |
S3C2440_NFCONF_TWRPH0 ( 7 ) |
S3C2440_NFCONF_TWRPH1 ( 7 ) ) ;
2008-07-15 11:58:31 +01:00
set = S3C2440_NFCONF_TACLS ( tacls - 1 ) ;
set | = S3C2440_NFCONF_TWRPH0 ( twrph0 - 1 ) ;
set | = S3C2440_NFCONF_TWRPH1 ( twrph1 - 1 ) ;
break ;
default :
BUG ( ) ;
}
local_irq_save ( flags ) ;
cfg = readl ( info - > regs + S3C2410_NFCONF ) ;
cfg & = ~ mask ;
cfg | = set ;
writel ( cfg , info - > regs + S3C2410_NFCONF ) ;
local_irq_restore ( flags ) ;
2009-05-10 15:42:02 -05:00
dev_dbg ( info - > device , " NF_CONF is 0x%lx \n " , cfg ) ;
2008-07-15 11:58:31 +01:00
return 0 ;
}
2009-05-30 17:18:15 +01:00
/**
* s3c2410_nand_inithw - basic hardware initialisation
* @ info : The hardware state .
*
* Do the basic initialisation of the hardware , using s3c2410_nand_setrate ( )
* to setup the hardware access speeds and set the controller to be enabled .
*/
2008-07-15 11:58:31 +01:00
static int s3c2410_nand_inithw ( struct s3c2410_nand_info * info )
{
int ret ;
ret = s3c2410_nand_setrate ( info ) ;
if ( ret < 0 )
return ret ;
2012-07-16 16:02:26 +05:30
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 ;
2012-07-16 16:02:26 +05:30
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 ;
}
2009-05-30 17:18:15 +01:00
/**
* s3c2410_nand_select_chip - select the given nand chip
* @ mtd : The MTD instance for this chip .
* @ chip : The chip number .
*
* This is called by the MTD layer to either select a given chip for the
* @ mtd instance , or to indicate that the access has finished and the
* chip can be de - selected .
*
* The routine ensures that the nFCE line is correctly setup , and any
* platform specific selection code is called to route nFCE to the specific
* chip .
*/
2005-04-16 15:20:36 -07:00
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 ;
2015-12-01 12:03:04 +01:00
struct nand_chip * this = mtd_to_nand ( mtd ) ;
2005-04-16 15:20:36 -07:00
unsigned long cur ;
2015-12-10 09:00:41 +01:00
nmtd = nand_get_controller_data ( this ) ;
2005-04-16 15:20:36 -07:00
info = nmtd - > info ;
2011-04-13 11:59:30 +02:00
if ( chip ! = - 1 )
s3c2410_nand_clk_set_state ( info , CLOCK_ENABLE ) ;
2006-06-19 09:29:38 +01:00
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
2011-04-13 11:59:30 +02:00
if ( chip = = - 1 )
s3c2410_nand_clk_set_state ( info , CLOCK_SUSPEND ) ;
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 ] ;
2012-08-02 16:06:47 +03:00
pr_debug ( " %s: rd %*phN calc %*phN diff %02x%02x%02x \n " ,
__func__ , 3 , read_ecc , 3 , calc_ecc ,
2007-02-02 16:59:33 +00:00
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 ) ;
2016-04-12 15:30:35 +08:00
/* equal to "(diff0 & ~(1 << __ffs(diff0)))" */
if ( ( diff0 & ( diff0 - 1 ) ) = = 0 )
2007-02-02 16:59:33 +00:00
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 ) ;
2012-08-21 10:21:15 +05:30
writel ( ctrl | S3C2412_NFCONT_INIT_MAIN_ECC ,
info - > regs + S3C2440_NFCONT ) ;
2007-02-13 12:30:38 +01:00
}
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 ) ;
}
2012-08-21 10:21:15 +05:30
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 ) ;
2012-08-02 16:06:47 +03:00
pr_debug ( " %s: returning ecc %*phN \n " , __func__ , 3 , ecc_code ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
2012-08-21 10:21:15 +05:30
static int s3c2412_nand_calculate_ecc ( struct mtd_info * mtd , const u_char * dat ,
u_char * ecc_code )
2007-02-13 12:30:38 +01:00
{
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 ;
2012-08-02 16:06:47 +03:00
pr_debug ( " %s: returning ecc %*phN \n " , __func__ , 3 , ecc_code ) ;
2007-02-13 12:30:38 +01:00
return 0 ;
}
2012-08-21 10:21:15 +05:30
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 )
{
2015-12-01 12:03:04 +01:00
struct nand_chip * this = mtd_to_nand ( mtd ) ;
2005-04-16 15:20:36 -07:00
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 ) ;
2009-05-30 18:30:18 +01:00
readsl ( info - > regs + S3C2440_NFDATA , buf , len > > 2 ) ;
/* cleanup if we've got less than a word to do */
if ( len & 3 ) {
buf + = len & ~ 3 ;
for ( ; len & 3 ; len - - )
* buf + + = readb ( info - > regs + S3C2440_NFDATA ) ;
}
2007-10-18 17:43:07 -07:00
}
2012-08-21 10:21:15 +05:30
static void s3c2410_nand_write_buf ( struct mtd_info * mtd , const u_char * buf ,
int len )
2005-04-16 15:20:36 -07:00
{
2015-12-01 12:03:04 +01:00
struct nand_chip * this = mtd_to_nand ( mtd ) ;
2005-04-16 15:20:36 -07:00
writesb ( this - > IO_ADDR_W , buf , len ) ;
}
2012-08-21 10:21:15 +05:30
static void s3c2440_nand_write_buf ( struct mtd_info * mtd , const u_char * buf ,
int len )
2007-10-18 17:43:07 -07:00
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
2009-05-30 18:30:18 +01:00
writesl ( info - > regs + S3C2440_NFDATA , buf , len > > 2 ) ;
/* cleanup any fractional write */
if ( len & 3 ) {
buf + = len & ~ 3 ;
for ( ; len & 3 ; len - - , buf + + )
writeb ( * buf , info - > regs + S3C2440_NFDATA ) ;
}
2007-10-18 17:43:07 -07:00
}
2008-07-15 11:58:31 +01:00
/* cpufreq driver support */
2016-06-27 14:51:38 +02:00
# ifdef CONFIG_ARM_S3C24XX_CPUFREQ
2008-07-15 11:58:31 +01:00
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 ) ;
}
2012-08-21 10:21:15 +05:30
static inline void
s3c2410_nand_cpufreq_deregister ( struct s3c2410_nand_info * info )
2008-07-15 11:58:31 +01:00
{
cpufreq_unregister_notifier ( & info - > freq_transition ,
CPUFREQ_TRANSITION_NOTIFIER ) ;
}
# else
static inline int s3c2410_nand_cpufreq_register ( struct s3c2410_nand_info * info )
{
return 0 ;
}
2012-08-21 10:21:15 +05:30
static inline void
s3c2410_nand_cpufreq_deregister ( struct s3c2410_nand_info * info )
2008-07-15 11:58:31 +01:00
{
}
# endif
2005-04-16 15:20:36 -07:00
/* device management functions */
2009-05-30 16:55:29 +01:00
static int s3c24xx_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-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 ) ;
2015-12-10 09:00:22 +01:00
nand_release ( nand_to_mtd ( & ptr - > chip ) ) ;
2005-04-16 15:20:36 -07:00
}
}
/* free the common resources */
2012-08-21 14:24:09 +05:30
if ( ! IS_ERR ( info - > clk ) )
2011-04-13 11:59:30 +02:00
s3c2410_nand_clk_set_state ( info , CLOCK_DISABLE ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static int s3c2410_nand_add_partition ( struct s3c2410_nand_info * info ,
struct s3c2410_nand_mtd * mtd ,
struct s3c2410_nand_set * set )
{
2012-11-16 16:08:22 +05:30
if ( set ) {
2015-12-10 09:00:22 +01:00
struct mtd_info * mtdinfo = nand_to_mtd ( & mtd - > chip ) ;
2009-05-10 15:42:09 -05:00
2015-12-10 09:00:22 +01:00
mtdinfo - > name = set - > name ;
return mtd_device_parse_register ( mtdinfo , NULL , NULL ,
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 19:24:26 +02:00
set - > partitions , set - > nr_partitions ) ;
2012-11-16 16:08:22 +05:30
}
return - ENODEV ;
2005-04-16 15:20:36 -07:00
}
2016-10-26 21:59:55 -02:00
static int s3c2410_nand_setup_data_interface ( struct mtd_info * mtd ,
const struct nand_data_interface * conf ,
bool check_only )
{
struct s3c2410_nand_info * info = s3c2410_nand_mtd_toinfo ( mtd ) ;
struct s3c2410_platform_nand * pdata = info - > platform ;
const struct nand_sdr_timings * timings ;
int tacls ;
timings = nand_get_sdr_timings ( conf ) ;
if ( IS_ERR ( timings ) )
return - ENOTSUPP ;
tacls = timings - > tCLS_min - timings - > tWP_min ;
if ( tacls < 0 )
tacls = 0 ;
pdata - > tacls = DIV_ROUND_UP ( tacls , 1000 ) ;
pdata - > twrph0 = DIV_ROUND_UP ( timings - > tWP_min , 1000 ) ;
pdata - > twrph1 = DIV_ROUND_UP ( timings - > tCLH_min , 1000 ) ;
return s3c2410_nand_setrate ( info ) ;
}
2009-05-30 17:18:15 +01:00
/**
* s3c2410_nand_init_chip - initialise a single instance of an chip
* @ info : The base NAND controller the chip is on .
* @ nmtd : The new controller MTD instance to fill in .
* @ set : The information passed from the board specific platform data .
2005-04-16 15:20:36 -07:00
*
2009-05-30 17:18:15 +01:00
* Initialise the given @ nmtd from the information in @ info and @ set . This
* readies the structure for use with the MTD layer functions by ensuring
* all pointers are setup and the necessary control routines selected .
*/
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 )
{
2016-10-26 21:59:55 -02:00
struct device_node * np = info - > device - > of_node ;
2005-04-16 15:20:36 -07:00
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
2016-10-26 21:59:55 -02:00
nand_set_flash_node ( chip , set - > of_node ) ;
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 ;
2015-12-10 09:00:41 +01:00
nand_set_controller_data ( chip , nmtd ) ;
2009-11-02 18:12:51 +00:00
chip - > options = set - > options ;
2005-04-16 15:20:36 -07:00
chip - > controller = & info - > controller ;
2016-10-26 21:59:55 -02:00
/*
* let ' s keep behavior unchanged for legacy boards booting via pdata and
* auto - detect timings only when booting with a device tree .
*/
if ( np )
chip - > setup_data_interface = s3c2410_nand_setup_data_interface ;
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 ;
2012-07-16 16:02:26 +05:30
}
2006-06-27 14:35:46 +01:00
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 - > set = set ;
2016-10-20 19:42:44 -02:00
chip - > ecc . mode = info - > platform - > ecc_mode ;
2009-05-13 16:54:14 +01:00
2016-10-26 21:59:55 -02:00
/*
* If you use u - boot BBT creation code , specifying this flag will
* let the kernel fish out the BBT from the NAND .
*/
if ( set - > flash_bbt )
2011-05-31 16:31:23 -07:00
chip - > bbt_options | = NAND_BBT_USE_FLASH ;
2005-04-16 15:20:36 -07:00
}
2009-05-30 17:18:15 +01:00
/**
* s3c2410_nand_update_chip - post probe update
* @ info : The controller instance .
* @ nmtd : The driver version of the MTD instance .
2008-04-15 11:36:19 +01:00
*
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 13:09:05 -02:00
* This routine is called after the chip probe has successfully completed
2009-05-30 17:18:15 +01:00
* and the relevant per - chip information updated . This call ensure that
* we update the internal state accordingly .
*
* The internal state is currently limited to the ECC state information .
*/
2016-10-20 19:42:44 -02:00
static int s3c2410_nand_update_chip ( struct s3c2410_nand_info * info ,
struct s3c2410_nand_mtd * nmtd )
2008-04-15 11:36:19 +01:00
{
struct nand_chip * chip = & nmtd - > chip ;
2016-10-20 19:42:44 -02:00
switch ( chip - > ecc . mode ) {
2008-04-15 11:36:19 +01:00
2016-10-20 19:42:44 -02:00
case NAND_ECC_NONE :
dev_info ( info - > device , " ECC disabled \n " ) ;
break ;
case NAND_ECC_SOFT :
/*
* This driver expects Hamming based ECC when ecc_mode is set
* to NAND_ECC_SOFT . Force ecc . algo to NAND_ECC_HAMMING to
* avoid adding an extra ecc_algo field to
* s3c2410_platform_nand .
*/
chip - > ecc . algo = NAND_ECC_HAMMING ;
dev_info ( info - > device , " soft ECC \n " ) ;
break ;
case NAND_ECC_HW :
chip - > ecc . calculate = s3c2410_nand_calculate_ecc ;
chip - > ecc . correct = s3c2410_nand_correct_data ;
chip - > ecc . strength = 1 ;
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 :
chip - > ecc . hwctl = s3c2412_nand_enable_hwecc ;
chip - > ecc . calculate = s3c2412_nand_calculate_ecc ;
break ;
case TYPE_S3C2440 :
chip - > ecc . hwctl = s3c2440_nand_enable_hwecc ;
chip - > ecc . calculate = s3c2440_nand_calculate_ecc ;
break ;
}
dev_dbg ( info - > device , " chip %p => page shift %d \n " ,
chip , chip - > page_shift ) ;
2009-05-10 15:41:25 -05:00
2012-09-19 21:48:00 -04:00
/* change the behaviour depending on whether we are using
2008-04-15 11:36:19 +01:00
* the large or small page nand device */
2016-10-20 19:42:44 -02:00
if ( chip - > page_shift > 10 ) {
chip - > ecc . size = 256 ;
chip - > ecc . bytes = 3 ;
} else {
chip - > ecc . size = 512 ;
chip - > ecc . bytes = 3 ;
mtd_set_ooblayout ( nand_to_mtd ( chip ) ,
& s3c2410_ooblayout_ops ) ;
}
2008-04-15 11:36:19 +01:00
2016-10-20 19:42:44 -02:00
dev_info ( info - > device , " hardware ECC \n " ) ;
break ;
default :
dev_err ( info - > device , " invalid ECC mode! \n " ) ;
return - EINVAL ;
2008-04-15 11:36:19 +01:00
}
2016-10-20 19:42:44 -02:00
2016-10-26 21:59:55 -02:00
if ( chip - > bbt_options & NAND_BBT_USE_FLASH )
chip - > options | = NAND_SKIP_BBTSCAN ;
return 0 ;
}
static const struct of_device_id s3c24xx_nand_dt_ids [ ] = {
{
. compatible = " samsung,s3c2410-nand " ,
. data = & s3c2410_nand_devtype_data ,
} , {
/* also compatible with s3c6400 */
. compatible = " samsung,s3c2412-nand " ,
. data = & s3c2412_nand_devtype_data ,
} , {
. compatible = " samsung,s3c2440-nand " ,
. data = & s3c2440_nand_devtype_data ,
} ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , s3c24xx_nand_dt_ids ) ;
static int s3c24xx_nand_probe_dt ( struct platform_device * pdev )
{
const struct s3c24XX_nand_devtype_data * devtype_data ;
struct s3c2410_platform_nand * pdata ;
struct s3c2410_nand_info * info = platform_get_drvdata ( pdev ) ;
struct device_node * np = pdev - > dev . of_node , * child ;
struct s3c2410_nand_set * sets ;
devtype_data = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! devtype_data )
return - ENODEV ;
info - > cpu_type = devtype_data - > type ;
pdata = devm_kzalloc ( & pdev - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
pdev - > dev . platform_data = pdata ;
pdata - > nr_sets = of_get_child_count ( np ) ;
if ( ! pdata - > nr_sets )
return 0 ;
sets = devm_kzalloc ( & pdev - > dev , sizeof ( * sets ) * pdata - > nr_sets ,
GFP_KERNEL ) ;
if ( ! sets )
return - ENOMEM ;
pdata - > sets = sets ;
for_each_available_child_of_node ( np , child ) {
sets - > name = ( char * ) child - > name ;
sets - > of_node = child ;
sets - > nr_chips = 1 ;
of_node_get ( child ) ;
sets + + ;
}
return 0 ;
}
static int s3c24xx_nand_probe_pdata ( struct platform_device * pdev )
{
struct s3c2410_nand_info * info = platform_get_drvdata ( pdev ) ;
info - > cpu_type = platform_get_device_id ( pdev ) - > driver_data ;
2016-10-20 19:42:44 -02:00
return 0 ;
2008-04-15 11:36:19 +01:00
}
2009-05-30 16:55:29 +01:00
/* s3c24xx_nand_probe
2005-04-16 15:20:36 -07:00
*
* 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
*/
2009-05-30 16:55:29 +01:00
static int s3c24xx_nand_probe ( struct platform_device * pdev )
2005-04-16 15:20:36 -07:00
{
2016-10-26 21:59:55 -02:00
struct s3c2410_platform_nand * plat ;
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 ;
2012-08-21 14:24:09 +05:30
info = devm_kzalloc ( & pdev - > dev , sizeof ( * info ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( info = = NULL ) {
err = - ENOMEM ;
goto exit_error ;
}
2005-11-09 22:32:44 +00:00
platform_set_drvdata ( pdev , info ) ;
2005-04-16 15:20:36 -07:00
2016-07-27 11:23:52 +02:00
nand_hw_control_init ( & info - > controller ) ;
2005-04-16 15:20:36 -07:00
/* get the clock source and enable it */
2012-08-21 14:24:09 +05:30
info - > clk = devm_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 ;
}
2011-04-13 11:59:30 +02:00
s3c2410_nand_clk_set_state ( info , CLOCK_ENABLE ) ;
2005-04-16 15:20:36 -07:00
2016-10-26 21:59:55 -02:00
if ( pdev - > dev . of_node )
err = s3c24xx_nand_probe_dt ( pdev ) ;
else
err = s3c24xx_nand_probe_pdata ( pdev ) ;
if ( err )
goto exit_error ;
plat = to_nand_plat ( pdev ) ;
2005-04-16 15:20:36 -07:00
/* allocate and map the resource */
2005-06-20 12:48:25 +01:00
/* currently we assume we have the one resource */
2012-08-21 14:24:09 +05:30
res = pdev - > resource ;
2009-12-14 16:56:22 -05:00
size = resource_size ( res ) ;
2005-04-16 15:20:36 -07:00
2012-08-21 14:24:09 +05:30
info - > device = & pdev - > dev ;
info - > platform = plat ;
2005-04-16 15:20:36 -07:00
2013-01-21 11:09:12 +01:00
info - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( info - > regs ) ) {
err = PTR_ERR ( info - > regs ) ;
2005-04-16 15:20:36 -07:00
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
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 ) ;
2012-08-21 14:24:09 +05:30
info - > mtds = devm_kzalloc ( & pdev - > dev , size , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( info - > mtds = = NULL ) {
err = - ENOMEM ;
goto exit_error ;
}
/* initialise all possible chips */
nmtd = info - > mtds ;
for ( setno = 0 ; setno < nr_sets ; setno + + , nmtd + + ) {
2015-12-10 09:00:22 +01:00
struct mtd_info * mtd = nand_to_mtd ( & nmtd - > chip ) ;
2012-08-21 10:21:15 +05:30
pr_debug ( " initialising set %d (%p, info %p) \n " ,
setno , nmtd , info ) ;
2005-11-07 11:15:49 +00:00
2015-12-10 09:00:22 +01:00
mtd - > dev . parent = & pdev - > dev ;
2005-04-16 15:20:36 -07:00
s3c2410_nand_init_chip ( info , nmtd , sets ) ;
2015-12-10 09:00:22 +01:00
nmtd - > scan_res = nand_scan_ident ( mtd ,
2010-02-26 18:32:56 +00:00
( sets ) ? sets - > nr_chips : 1 ,
NULL ) ;
2005-04-16 15:20:36 -07:00
if ( nmtd - > scan_res = = 0 ) {
2016-10-20 19:42:44 -02:00
err = s3c2410_nand_update_chip ( info , nmtd ) ;
if ( err < 0 )
goto exit_error ;
2015-12-10 09:00:22 +01:00
nand_scan_tail ( 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
2016-10-26 21:59:55 -02:00
/* initialise the hardware */
err = s3c2410_nand_inithw ( info ) ;
if ( err ! = 0 )
goto exit_error ;
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 ;
}
2011-04-13 11:59:30 +02:00
if ( allow_clk_suspend ( info ) ) {
2006-06-19 09:29:38 +01:00
dev_info ( & pdev - > dev , " clock idle support enabled \n " ) ;
2011-04-13 11:59:30 +02:00
s3c2410_nand_clk_set_state ( info , CLOCK_SUSPEND ) ;
2006-06-19 09:29:38 +01:00
}
2005-04-16 15:20:36 -07:00
return 0 ;
exit_error :
2009-05-30 16:55:29 +01:00
s3c24xx_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
2011-04-13 11:59:30 +02:00
s3c2410_nand_clk_set_state ( info , CLOCK_DISABLE ) ;
2006-06-19 09:29:38 +01:00
}
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 ) {
2011-04-13 11:59:30 +02:00
s3c2410_nand_clk_set_state ( info , CLOCK_ENABLE ) ;
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
2011-04-13 11:59:30 +02:00
s3c2410_nand_clk_set_state ( info , CLOCK_SUSPEND ) ;
2006-06-19 09:29:38 +01:00
}
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 */
2015-05-02 00:50:02 +09:00
static const struct platform_device_id s3c24xx_driver_ids [ ] = {
2009-05-30 16:55:29 +01:00
{
. name = " s3c2410-nand " ,
. driver_data = TYPE_S3C2410 ,
} , {
. name = " s3c2440-nand " ,
. driver_data = TYPE_S3C2440 ,
} , {
. name = " s3c2412-nand " ,
. driver_data = TYPE_S3C2412 ,
2009-06-07 06:04:23 -07:00
} , {
. name = " s3c6400-nand " ,
. driver_data = TYPE_S3C2412 , /* compatible with 2412 */
2005-11-09 22:32:44 +00:00
} ,
2009-05-30 16:55:29 +01:00
{ }
2005-04-16 15:20:36 -07:00
} ;
2009-05-30 16:55:29 +01:00
MODULE_DEVICE_TABLE ( platform , s3c24xx_driver_ids ) ;
2005-06-20 12:48:25 +01:00
2009-05-30 16:55:29 +01:00
static struct platform_driver s3c24xx_nand_driver = {
. probe = s3c24xx_nand_probe ,
. remove = s3c24xx_nand_remove ,
2006-06-27 14:35:46 +01:00
. suspend = s3c24xx_nand_suspend ,
. resume = s3c24xx_nand_resume ,
2009-05-30 16:55:29 +01:00
. id_table = s3c24xx_driver_ids ,
2006-06-27 14:35:46 +01:00
. driver = {
2009-05-30 16:55:29 +01:00
. name = " s3c24xx-nand " ,
2016-10-26 21:59:55 -02:00
. of_match_table = s3c24xx_nand_dt_ids ,
2006-06-27 14:35:46 +01:00
} ,
} ;
2012-07-16 16:02:22 +05:30
module_platform_driver ( s3c24xx_nand_driver ) ;
2005-04-16 15:20:36 -07:00
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 " ) ;