162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright © 2004-2008 Simtec Electronics 462306a36Sopenharmony_ci * http://armlinux.simtec.co.uk/ 562306a36Sopenharmony_ci * Ben Dooks <ben@simtec.co.uk> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Samsung S3C2410/S3C2440/S3C2412 NAND driver 862306a36Sopenharmony_ci*/ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define pr_fmt(fmt) "nand-s3c2410: " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#ifdef CONFIG_MTD_NAND_S3C2410_DEBUG 1362306a36Sopenharmony_ci#define DEBUG 1462306a36Sopenharmony_ci#endif 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/types.h> 1862306a36Sopenharmony_ci#include <linux/kernel.h> 1962306a36Sopenharmony_ci#include <linux/string.h> 2062306a36Sopenharmony_ci#include <linux/io.h> 2162306a36Sopenharmony_ci#include <linux/ioport.h> 2262306a36Sopenharmony_ci#include <linux/platform_device.h> 2362306a36Sopenharmony_ci#include <linux/delay.h> 2462306a36Sopenharmony_ci#include <linux/err.h> 2562306a36Sopenharmony_ci#include <linux/slab.h> 2662306a36Sopenharmony_ci#include <linux/clk.h> 2762306a36Sopenharmony_ci#include <linux/cpufreq.h> 2862306a36Sopenharmony_ci#include <linux/of.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 3162306a36Sopenharmony_ci#include <linux/mtd/rawnand.h> 3262306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#include <linux/platform_data/mtd-nand-s3c2410.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define S3C2410_NFREG(x) (x) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define S3C2410_NFCONF S3C2410_NFREG(0x00) 3962306a36Sopenharmony_ci#define S3C2410_NFCMD S3C2410_NFREG(0x04) 4062306a36Sopenharmony_ci#define S3C2410_NFADDR S3C2410_NFREG(0x08) 4162306a36Sopenharmony_ci#define S3C2410_NFDATA S3C2410_NFREG(0x0C) 4262306a36Sopenharmony_ci#define S3C2410_NFSTAT S3C2410_NFREG(0x10) 4362306a36Sopenharmony_ci#define S3C2410_NFECC S3C2410_NFREG(0x14) 4462306a36Sopenharmony_ci#define S3C2440_NFCONT S3C2410_NFREG(0x04) 4562306a36Sopenharmony_ci#define S3C2440_NFCMD S3C2410_NFREG(0x08) 4662306a36Sopenharmony_ci#define S3C2440_NFADDR S3C2410_NFREG(0x0C) 4762306a36Sopenharmony_ci#define S3C2440_NFDATA S3C2410_NFREG(0x10) 4862306a36Sopenharmony_ci#define S3C2440_NFSTAT S3C2410_NFREG(0x20) 4962306a36Sopenharmony_ci#define S3C2440_NFMECC0 S3C2410_NFREG(0x2C) 5062306a36Sopenharmony_ci#define S3C2412_NFSTAT S3C2410_NFREG(0x28) 5162306a36Sopenharmony_ci#define S3C2412_NFMECC0 S3C2410_NFREG(0x34) 5262306a36Sopenharmony_ci#define S3C2410_NFCONF_EN (1<<15) 5362306a36Sopenharmony_ci#define S3C2410_NFCONF_INITECC (1<<12) 5462306a36Sopenharmony_ci#define S3C2410_NFCONF_nFCE (1<<11) 5562306a36Sopenharmony_ci#define S3C2410_NFCONF_TACLS(x) ((x)<<8) 5662306a36Sopenharmony_ci#define S3C2410_NFCONF_TWRPH0(x) ((x)<<4) 5762306a36Sopenharmony_ci#define S3C2410_NFCONF_TWRPH1(x) ((x)<<0) 5862306a36Sopenharmony_ci#define S3C2410_NFSTAT_BUSY (1<<0) 5962306a36Sopenharmony_ci#define S3C2440_NFCONF_TACLS(x) ((x)<<12) 6062306a36Sopenharmony_ci#define S3C2440_NFCONF_TWRPH0(x) ((x)<<8) 6162306a36Sopenharmony_ci#define S3C2440_NFCONF_TWRPH1(x) ((x)<<4) 6262306a36Sopenharmony_ci#define S3C2440_NFCONT_INITECC (1<<4) 6362306a36Sopenharmony_ci#define S3C2440_NFCONT_nFCE (1<<1) 6462306a36Sopenharmony_ci#define S3C2440_NFCONT_ENABLE (1<<0) 6562306a36Sopenharmony_ci#define S3C2440_NFSTAT_READY (1<<0) 6662306a36Sopenharmony_ci#define S3C2412_NFCONF_NANDBOOT (1<<31) 6762306a36Sopenharmony_ci#define S3C2412_NFCONT_INIT_MAIN_ECC (1<<5) 6862306a36Sopenharmony_ci#define S3C2412_NFCONT_nFCE0 (1<<1) 6962306a36Sopenharmony_ci#define S3C2412_NFSTAT_READY (1<<0) 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* new oob placement block for use with hardware ecc generation 7262306a36Sopenharmony_ci */ 7362306a36Sopenharmony_cistatic int s3c2410_ooblayout_ecc(struct mtd_info *mtd, int section, 7462306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci if (section) 7762306a36Sopenharmony_ci return -ERANGE; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci oobregion->offset = 0; 8062306a36Sopenharmony_ci oobregion->length = 3; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci return 0; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic int s3c2410_ooblayout_free(struct mtd_info *mtd, int section, 8662306a36Sopenharmony_ci struct mtd_oob_region *oobregion) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci if (section) 8962306a36Sopenharmony_ci return -ERANGE; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci oobregion->offset = 8; 9262306a36Sopenharmony_ci oobregion->length = 8; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic const struct mtd_ooblayout_ops s3c2410_ooblayout_ops = { 9862306a36Sopenharmony_ci .ecc = s3c2410_ooblayout_ecc, 9962306a36Sopenharmony_ci .free = s3c2410_ooblayout_free, 10062306a36Sopenharmony_ci}; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci/* controller and mtd information */ 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistruct s3c2410_nand_info; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/** 10762306a36Sopenharmony_ci * struct s3c2410_nand_mtd - driver MTD structure 10862306a36Sopenharmony_ci * @mtd: The MTD instance to pass to the MTD layer. 10962306a36Sopenharmony_ci * @chip: The NAND chip information. 11062306a36Sopenharmony_ci * @set: The platform information supplied for this set of NAND chips. 11162306a36Sopenharmony_ci * @info: Link back to the hardware information. 11262306a36Sopenharmony_ci*/ 11362306a36Sopenharmony_cistruct s3c2410_nand_mtd { 11462306a36Sopenharmony_ci struct nand_chip chip; 11562306a36Sopenharmony_ci struct s3c2410_nand_set *set; 11662306a36Sopenharmony_ci struct s3c2410_nand_info *info; 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cienum s3c_cpu_type { 12062306a36Sopenharmony_ci TYPE_S3C2410, 12162306a36Sopenharmony_ci TYPE_S3C2412, 12262306a36Sopenharmony_ci TYPE_S3C2440, 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cienum s3c_nand_clk_state { 12662306a36Sopenharmony_ci CLOCK_DISABLE = 0, 12762306a36Sopenharmony_ci CLOCK_ENABLE, 12862306a36Sopenharmony_ci CLOCK_SUSPEND, 12962306a36Sopenharmony_ci}; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/* overview of the s3c2410 nand state */ 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci/** 13462306a36Sopenharmony_ci * struct s3c2410_nand_info - NAND controller state. 13562306a36Sopenharmony_ci * @controller: Base controller structure. 13662306a36Sopenharmony_ci * @mtds: An array of MTD instances on this controller. 13762306a36Sopenharmony_ci * @platform: The platform data for this board. 13862306a36Sopenharmony_ci * @device: The platform device we bound to. 13962306a36Sopenharmony_ci * @clk: The clock resource for this controller. 14062306a36Sopenharmony_ci * @regs: The area mapped for the hardware registers. 14162306a36Sopenharmony_ci * @sel_reg: Pointer to the register controlling the NAND selection. 14262306a36Sopenharmony_ci * @sel_bit: The bit in @sel_reg to select the NAND chip. 14362306a36Sopenharmony_ci * @mtd_count: The number of MTDs created from this controller. 14462306a36Sopenharmony_ci * @save_sel: The contents of @sel_reg to be saved over suspend. 14562306a36Sopenharmony_ci * @clk_rate: The clock rate from @clk. 14662306a36Sopenharmony_ci * @clk_state: The current clock state. 14762306a36Sopenharmony_ci * @cpu_type: The exact type of this controller. 14862306a36Sopenharmony_ci * @freq_transition: CPUFreq notifier block 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_cistruct s3c2410_nand_info { 15162306a36Sopenharmony_ci /* mtd info */ 15262306a36Sopenharmony_ci struct nand_controller controller; 15362306a36Sopenharmony_ci struct s3c2410_nand_mtd *mtds; 15462306a36Sopenharmony_ci struct s3c2410_platform_nand *platform; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* device info */ 15762306a36Sopenharmony_ci struct device *device; 15862306a36Sopenharmony_ci struct clk *clk; 15962306a36Sopenharmony_ci void __iomem *regs; 16062306a36Sopenharmony_ci void __iomem *sel_reg; 16162306a36Sopenharmony_ci int sel_bit; 16262306a36Sopenharmony_ci int mtd_count; 16362306a36Sopenharmony_ci unsigned long save_sel; 16462306a36Sopenharmony_ci unsigned long clk_rate; 16562306a36Sopenharmony_ci enum s3c_nand_clk_state clk_state; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci enum s3c_cpu_type cpu_type; 16862306a36Sopenharmony_ci}; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistruct s3c24XX_nand_devtype_data { 17162306a36Sopenharmony_ci enum s3c_cpu_type type; 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic const struct s3c24XX_nand_devtype_data s3c2410_nand_devtype_data = { 17562306a36Sopenharmony_ci .type = TYPE_S3C2410, 17662306a36Sopenharmony_ci}; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic const struct s3c24XX_nand_devtype_data s3c2412_nand_devtype_data = { 17962306a36Sopenharmony_ci .type = TYPE_S3C2412, 18062306a36Sopenharmony_ci}; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic const struct s3c24XX_nand_devtype_data s3c2440_nand_devtype_data = { 18362306a36Sopenharmony_ci .type = TYPE_S3C2440, 18462306a36Sopenharmony_ci}; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci/* conversion functions */ 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic struct s3c2410_nand_mtd *s3c2410_nand_mtd_toours(struct mtd_info *mtd) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci return container_of(mtd_to_nand(mtd), struct s3c2410_nand_mtd, 19162306a36Sopenharmony_ci chip); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci return s3c2410_nand_mtd_toours(mtd)->info; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic struct s3c2410_nand_info *to_nand_info(struct platform_device *dev) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci return platform_get_drvdata(dev); 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci return dev_get_platdata(&dev->dev); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic inline int allow_clk_suspend(struct s3c2410_nand_info *info) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP 21262306a36Sopenharmony_ci return 1; 21362306a36Sopenharmony_ci#else 21462306a36Sopenharmony_ci return 0; 21562306a36Sopenharmony_ci#endif 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci/** 21962306a36Sopenharmony_ci * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock. 22062306a36Sopenharmony_ci * @info: The controller instance. 22162306a36Sopenharmony_ci * @new_state: State to which clock should be set. 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_cistatic void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info, 22462306a36Sopenharmony_ci enum s3c_nand_clk_state new_state) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND) 22762306a36Sopenharmony_ci return; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (info->clk_state == CLOCK_ENABLE) { 23062306a36Sopenharmony_ci if (new_state != CLOCK_ENABLE) 23162306a36Sopenharmony_ci clk_disable_unprepare(info->clk); 23262306a36Sopenharmony_ci } else { 23362306a36Sopenharmony_ci if (new_state == CLOCK_ENABLE) 23462306a36Sopenharmony_ci clk_prepare_enable(info->clk); 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci info->clk_state = new_state; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci/* timing calculations */ 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci#define NS_IN_KHZ 1000000 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci/** 24562306a36Sopenharmony_ci * s3c_nand_calc_rate - calculate timing data. 24662306a36Sopenharmony_ci * @wanted: The cycle time in nanoseconds. 24762306a36Sopenharmony_ci * @clk: The clock rate in kHz. 24862306a36Sopenharmony_ci * @max: The maximum divider value. 24962306a36Sopenharmony_ci * 25062306a36Sopenharmony_ci * Calculate the timing value from the given parameters. 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_cistatic int s3c_nand_calc_rate(int wanted, unsigned long clk, int max) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci int result; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci pr_debug("result %d from %ld, %d\n", result, clk, wanted); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci if (result > max) { 26162306a36Sopenharmony_ci pr_err("%d ns is too big for current clock rate %ld\n", 26262306a36Sopenharmony_ci wanted, clk); 26362306a36Sopenharmony_ci return -1; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (result < 1) 26762306a36Sopenharmony_ci result = 1; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci return result; 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci#define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk)) 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci/* controller setup */ 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci/** 27762306a36Sopenharmony_ci * s3c2410_nand_setrate - setup controller timing information. 27862306a36Sopenharmony_ci * @info: The controller instance. 27962306a36Sopenharmony_ci * 28062306a36Sopenharmony_ci * Given the information supplied by the platform, calculate and set 28162306a36Sopenharmony_ci * the necessary timing registers in the hardware to generate the 28262306a36Sopenharmony_ci * necessary timing cycles to the hardware. 28362306a36Sopenharmony_ci */ 28462306a36Sopenharmony_cistatic int s3c2410_nand_setrate(struct s3c2410_nand_info *info) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct s3c2410_platform_nand *plat = info->platform; 28762306a36Sopenharmony_ci int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4; 28862306a36Sopenharmony_ci int tacls, twrph0, twrph1; 28962306a36Sopenharmony_ci unsigned long clkrate = clk_get_rate(info->clk); 29062306a36Sopenharmony_ci unsigned long set, cfg, mask; 29162306a36Sopenharmony_ci unsigned long flags; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci /* calculate the timing information for the controller */ 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci info->clk_rate = clkrate; 29662306a36Sopenharmony_ci clkrate /= 1000; /* turn clock into kHz for ease of use */ 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (plat != NULL) { 29962306a36Sopenharmony_ci tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max); 30062306a36Sopenharmony_ci twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8); 30162306a36Sopenharmony_ci twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8); 30262306a36Sopenharmony_ci } else { 30362306a36Sopenharmony_ci /* default timings */ 30462306a36Sopenharmony_ci tacls = tacls_max; 30562306a36Sopenharmony_ci twrph0 = 8; 30662306a36Sopenharmony_ci twrph1 = 8; 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (tacls < 0 || twrph0 < 0 || twrph1 < 0) { 31062306a36Sopenharmony_ci dev_err(info->device, "cannot get suitable timings\n"); 31162306a36Sopenharmony_ci return -EINVAL; 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", 31562306a36Sopenharmony_ci tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), 31662306a36Sopenharmony_ci twrph1, to_ns(twrph1, clkrate)); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci switch (info->cpu_type) { 31962306a36Sopenharmony_ci case TYPE_S3C2410: 32062306a36Sopenharmony_ci mask = (S3C2410_NFCONF_TACLS(3) | 32162306a36Sopenharmony_ci S3C2410_NFCONF_TWRPH0(7) | 32262306a36Sopenharmony_ci S3C2410_NFCONF_TWRPH1(7)); 32362306a36Sopenharmony_ci set = S3C2410_NFCONF_EN; 32462306a36Sopenharmony_ci set |= S3C2410_NFCONF_TACLS(tacls - 1); 32562306a36Sopenharmony_ci set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); 32662306a36Sopenharmony_ci set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); 32762306a36Sopenharmony_ci break; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci case TYPE_S3C2440: 33062306a36Sopenharmony_ci case TYPE_S3C2412: 33162306a36Sopenharmony_ci mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) | 33262306a36Sopenharmony_ci S3C2440_NFCONF_TWRPH0(7) | 33362306a36Sopenharmony_ci S3C2440_NFCONF_TWRPH1(7)); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci set = S3C2440_NFCONF_TACLS(tacls - 1); 33662306a36Sopenharmony_ci set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1); 33762306a36Sopenharmony_ci set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1); 33862306a36Sopenharmony_ci break; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci default: 34162306a36Sopenharmony_ci BUG(); 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci local_irq_save(flags); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci cfg = readl(info->regs + S3C2410_NFCONF); 34762306a36Sopenharmony_ci cfg &= ~mask; 34862306a36Sopenharmony_ci cfg |= set; 34962306a36Sopenharmony_ci writel(cfg, info->regs + S3C2410_NFCONF); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci local_irq_restore(flags); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci return 0; 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci/** 35962306a36Sopenharmony_ci * s3c2410_nand_inithw - basic hardware initialisation 36062306a36Sopenharmony_ci * @info: The hardware state. 36162306a36Sopenharmony_ci * 36262306a36Sopenharmony_ci * Do the basic initialisation of the hardware, using s3c2410_nand_setrate() 36362306a36Sopenharmony_ci * to setup the hardware access speeds and set the controller to be enabled. 36462306a36Sopenharmony_ci*/ 36562306a36Sopenharmony_cistatic int s3c2410_nand_inithw(struct s3c2410_nand_info *info) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci int ret; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci ret = s3c2410_nand_setrate(info); 37062306a36Sopenharmony_ci if (ret < 0) 37162306a36Sopenharmony_ci return ret; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci switch (info->cpu_type) { 37462306a36Sopenharmony_ci case TYPE_S3C2410: 37562306a36Sopenharmony_ci default: 37662306a36Sopenharmony_ci break; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci case TYPE_S3C2440: 37962306a36Sopenharmony_ci case TYPE_S3C2412: 38062306a36Sopenharmony_ci /* enable the controller and de-assert nFCE */ 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT); 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci return 0; 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci/** 38962306a36Sopenharmony_ci * s3c2410_nand_select_chip - select the given nand chip 39062306a36Sopenharmony_ci * @this: NAND chip object. 39162306a36Sopenharmony_ci * @chip: The chip number. 39262306a36Sopenharmony_ci * 39362306a36Sopenharmony_ci * This is called by the MTD layer to either select a given chip for the 39462306a36Sopenharmony_ci * @mtd instance, or to indicate that the access has finished and the 39562306a36Sopenharmony_ci * chip can be de-selected. 39662306a36Sopenharmony_ci * 39762306a36Sopenharmony_ci * The routine ensures that the nFCE line is correctly setup, and any 39862306a36Sopenharmony_ci * platform specific selection code is called to route nFCE to the specific 39962306a36Sopenharmony_ci * chip. 40062306a36Sopenharmony_ci */ 40162306a36Sopenharmony_cistatic void s3c2410_nand_select_chip(struct nand_chip *this, int chip) 40262306a36Sopenharmony_ci{ 40362306a36Sopenharmony_ci struct s3c2410_nand_info *info; 40462306a36Sopenharmony_ci struct s3c2410_nand_mtd *nmtd; 40562306a36Sopenharmony_ci unsigned long cur; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci nmtd = nand_get_controller_data(this); 40862306a36Sopenharmony_ci info = nmtd->info; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci if (chip != -1) 41162306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci cur = readl(info->sel_reg); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (chip == -1) { 41662306a36Sopenharmony_ci cur |= info->sel_bit; 41762306a36Sopenharmony_ci } else { 41862306a36Sopenharmony_ci if (nmtd->set != NULL && chip > nmtd->set->nr_chips) { 41962306a36Sopenharmony_ci dev_err(info->device, "invalid chip %d\n", chip); 42062306a36Sopenharmony_ci return; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci if (info->platform != NULL) { 42462306a36Sopenharmony_ci if (info->platform->select_chip != NULL) 42562306a36Sopenharmony_ci (info->platform->select_chip) (nmtd->set, chip); 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci cur &= ~info->sel_bit; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci writel(cur, info->sel_reg); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (chip == -1) 43462306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); 43562306a36Sopenharmony_ci} 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci/* s3c2410_nand_hwcontrol 43862306a36Sopenharmony_ci * 43962306a36Sopenharmony_ci * Issue command and address cycles to the chip 44062306a36Sopenharmony_ci*/ 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_cistatic void s3c2410_nand_hwcontrol(struct nand_chip *chip, int cmd, 44362306a36Sopenharmony_ci unsigned int ctrl) 44462306a36Sopenharmony_ci{ 44562306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 44662306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci if (cmd == NAND_CMD_NONE) 44962306a36Sopenharmony_ci return; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci if (ctrl & NAND_CLE) 45262306a36Sopenharmony_ci writeb(cmd, info->regs + S3C2410_NFCMD); 45362306a36Sopenharmony_ci else 45462306a36Sopenharmony_ci writeb(cmd, info->regs + S3C2410_NFADDR); 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci/* command and control functions */ 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_cistatic void s3c2440_nand_hwcontrol(struct nand_chip *chip, int cmd, 46062306a36Sopenharmony_ci unsigned int ctrl) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 46362306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci if (cmd == NAND_CMD_NONE) 46662306a36Sopenharmony_ci return; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci if (ctrl & NAND_CLE) 46962306a36Sopenharmony_ci writeb(cmd, info->regs + S3C2440_NFCMD); 47062306a36Sopenharmony_ci else 47162306a36Sopenharmony_ci writeb(cmd, info->regs + S3C2440_NFADDR); 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci/* s3c2410_nand_devready() 47562306a36Sopenharmony_ci * 47662306a36Sopenharmony_ci * returns 0 if the nand is busy, 1 if it is ready 47762306a36Sopenharmony_ci*/ 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic int s3c2410_nand_devready(struct nand_chip *chip) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 48262306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 48362306a36Sopenharmony_ci return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY; 48462306a36Sopenharmony_ci} 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_cistatic int s3c2440_nand_devready(struct nand_chip *chip) 48762306a36Sopenharmony_ci{ 48862306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 48962306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 49062306a36Sopenharmony_ci return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY; 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_cistatic int s3c2412_nand_devready(struct nand_chip *chip) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 49662306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 49762306a36Sopenharmony_ci return readb(info->regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_READY; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci/* ECC handling functions */ 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic int s3c2410_nand_correct_data(struct nand_chip *chip, u_char *dat, 50362306a36Sopenharmony_ci u_char *read_ecc, u_char *calc_ecc) 50462306a36Sopenharmony_ci{ 50562306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 50662306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 50762306a36Sopenharmony_ci unsigned int diff0, diff1, diff2; 50862306a36Sopenharmony_ci unsigned int bit, byte; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci diff0 = read_ecc[0] ^ calc_ecc[0]; 51362306a36Sopenharmony_ci diff1 = read_ecc[1] ^ calc_ecc[1]; 51462306a36Sopenharmony_ci diff2 = read_ecc[2] ^ calc_ecc[2]; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci pr_debug("%s: rd %*phN calc %*phN diff %02x%02x%02x\n", 51762306a36Sopenharmony_ci __func__, 3, read_ecc, 3, calc_ecc, 51862306a36Sopenharmony_ci diff0, diff1, diff2); 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci if (diff0 == 0 && diff1 == 0 && diff2 == 0) 52162306a36Sopenharmony_ci return 0; /* ECC is ok */ 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci /* sometimes people do not think about using the ECC, so check 52462306a36Sopenharmony_ci * to see if we have an 0xff,0xff,0xff read ECC and then ignore 52562306a36Sopenharmony_ci * the error, on the assumption that this is an un-eccd page. 52662306a36Sopenharmony_ci */ 52762306a36Sopenharmony_ci if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff 52862306a36Sopenharmony_ci && info->platform->ignore_unset_ecc) 52962306a36Sopenharmony_ci return 0; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci /* Can we correct this ECC (ie, one row and column change). 53262306a36Sopenharmony_ci * Note, this is similar to the 256 error code on smartmedia */ 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 && 53562306a36Sopenharmony_ci ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 && 53662306a36Sopenharmony_ci ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) { 53762306a36Sopenharmony_ci /* calculate the bit position of the error */ 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci bit = ((diff2 >> 3) & 1) | 54062306a36Sopenharmony_ci ((diff2 >> 4) & 2) | 54162306a36Sopenharmony_ci ((diff2 >> 5) & 4); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* calculate the byte position of the error */ 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci byte = ((diff2 << 7) & 0x100) | 54662306a36Sopenharmony_ci ((diff1 << 0) & 0x80) | 54762306a36Sopenharmony_ci ((diff1 << 1) & 0x40) | 54862306a36Sopenharmony_ci ((diff1 << 2) & 0x20) | 54962306a36Sopenharmony_ci ((diff1 << 3) & 0x10) | 55062306a36Sopenharmony_ci ((diff0 >> 4) & 0x08) | 55162306a36Sopenharmony_ci ((diff0 >> 3) & 0x04) | 55262306a36Sopenharmony_ci ((diff0 >> 2) & 0x02) | 55362306a36Sopenharmony_ci ((diff0 >> 1) & 0x01); 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci dev_dbg(info->device, "correcting error bit %d, byte %d\n", 55662306a36Sopenharmony_ci bit, byte); 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci dat[byte] ^= (1 << bit); 55962306a36Sopenharmony_ci return 1; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci /* if there is only one bit difference in the ECC, then 56362306a36Sopenharmony_ci * one of only a row or column parity has changed, which 56462306a36Sopenharmony_ci * means the error is most probably in the ECC itself */ 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci diff0 |= (diff1 << 8); 56762306a36Sopenharmony_ci diff0 |= (diff2 << 16); 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci /* equal to "(diff0 & ~(1 << __ffs(diff0)))" */ 57062306a36Sopenharmony_ci if ((diff0 & (diff0 - 1)) == 0) 57162306a36Sopenharmony_ci return 1; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci return -1; 57462306a36Sopenharmony_ci} 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci/* ECC functions 57762306a36Sopenharmony_ci * 57862306a36Sopenharmony_ci * These allow the s3c2410 and s3c2440 to use the controller's ECC 57962306a36Sopenharmony_ci * generator block to ECC the data as it passes through] 58062306a36Sopenharmony_ci*/ 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_cistatic void s3c2410_nand_enable_hwecc(struct nand_chip *chip, int mode) 58362306a36Sopenharmony_ci{ 58462306a36Sopenharmony_ci struct s3c2410_nand_info *info; 58562306a36Sopenharmony_ci unsigned long ctrl; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); 58862306a36Sopenharmony_ci ctrl = readl(info->regs + S3C2410_NFCONF); 58962306a36Sopenharmony_ci ctrl |= S3C2410_NFCONF_INITECC; 59062306a36Sopenharmony_ci writel(ctrl, info->regs + S3C2410_NFCONF); 59162306a36Sopenharmony_ci} 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_cistatic void s3c2412_nand_enable_hwecc(struct nand_chip *chip, int mode) 59462306a36Sopenharmony_ci{ 59562306a36Sopenharmony_ci struct s3c2410_nand_info *info; 59662306a36Sopenharmony_ci unsigned long ctrl; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); 59962306a36Sopenharmony_ci ctrl = readl(info->regs + S3C2440_NFCONT); 60062306a36Sopenharmony_ci writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC, 60162306a36Sopenharmony_ci info->regs + S3C2440_NFCONT); 60262306a36Sopenharmony_ci} 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_cistatic void s3c2440_nand_enable_hwecc(struct nand_chip *chip, int mode) 60562306a36Sopenharmony_ci{ 60662306a36Sopenharmony_ci struct s3c2410_nand_info *info; 60762306a36Sopenharmony_ci unsigned long ctrl; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); 61062306a36Sopenharmony_ci ctrl = readl(info->regs + S3C2440_NFCONT); 61162306a36Sopenharmony_ci writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT); 61262306a36Sopenharmony_ci} 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_cistatic int s3c2410_nand_calculate_ecc(struct nand_chip *chip, 61562306a36Sopenharmony_ci const u_char *dat, u_char *ecc_code) 61662306a36Sopenharmony_ci{ 61762306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 61862306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0); 62162306a36Sopenharmony_ci ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1); 62262306a36Sopenharmony_ci ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2); 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci return 0; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_cistatic int s3c2412_nand_calculate_ecc(struct nand_chip *chip, 63062306a36Sopenharmony_ci const u_char *dat, u_char *ecc_code) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 63362306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 63462306a36Sopenharmony_ci unsigned long ecc = readl(info->regs + S3C2412_NFMECC0); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci ecc_code[0] = ecc; 63762306a36Sopenharmony_ci ecc_code[1] = ecc >> 8; 63862306a36Sopenharmony_ci ecc_code[2] = ecc >> 16; 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code); 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci return 0; 64362306a36Sopenharmony_ci} 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_cistatic int s3c2440_nand_calculate_ecc(struct nand_chip *chip, 64662306a36Sopenharmony_ci const u_char *dat, u_char *ecc_code) 64762306a36Sopenharmony_ci{ 64862306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 64962306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 65062306a36Sopenharmony_ci unsigned long ecc = readl(info->regs + S3C2440_NFMECC0); 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci ecc_code[0] = ecc; 65362306a36Sopenharmony_ci ecc_code[1] = ecc >> 8; 65462306a36Sopenharmony_ci ecc_code[2] = ecc >> 16; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff); 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci return 0; 65962306a36Sopenharmony_ci} 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci/* over-ride the standard functions for a little more speed. We can 66262306a36Sopenharmony_ci * use read/write block to move the data buffers to/from the controller 66362306a36Sopenharmony_ci*/ 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_cistatic void s3c2410_nand_read_buf(struct nand_chip *this, u_char *buf, int len) 66662306a36Sopenharmony_ci{ 66762306a36Sopenharmony_ci readsb(this->legacy.IO_ADDR_R, buf, len); 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cistatic void s3c2440_nand_read_buf(struct nand_chip *this, u_char *buf, int len) 67162306a36Sopenharmony_ci{ 67262306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(this); 67362306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci readsl(info->regs + S3C2440_NFDATA, buf, len >> 2); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci /* cleanup if we've got less than a word to do */ 67862306a36Sopenharmony_ci if (len & 3) { 67962306a36Sopenharmony_ci buf += len & ~3; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci for (; len & 3; len--) 68262306a36Sopenharmony_ci *buf++ = readb(info->regs + S3C2440_NFDATA); 68362306a36Sopenharmony_ci } 68462306a36Sopenharmony_ci} 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_cistatic void s3c2410_nand_write_buf(struct nand_chip *this, const u_char *buf, 68762306a36Sopenharmony_ci int len) 68862306a36Sopenharmony_ci{ 68962306a36Sopenharmony_ci writesb(this->legacy.IO_ADDR_W, buf, len); 69062306a36Sopenharmony_ci} 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_cistatic void s3c2440_nand_write_buf(struct nand_chip *this, const u_char *buf, 69362306a36Sopenharmony_ci int len) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(this); 69662306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci writesl(info->regs + S3C2440_NFDATA, buf, len >> 2); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci /* cleanup any fractional write */ 70162306a36Sopenharmony_ci if (len & 3) { 70262306a36Sopenharmony_ci buf += len & ~3; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci for (; len & 3; len--, buf++) 70562306a36Sopenharmony_ci writeb(*buf, info->regs + S3C2440_NFDATA); 70662306a36Sopenharmony_ci } 70762306a36Sopenharmony_ci} 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci/* device management functions */ 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_cistatic void s3c24xx_nand_remove(struct platform_device *pdev) 71262306a36Sopenharmony_ci{ 71362306a36Sopenharmony_ci struct s3c2410_nand_info *info = to_nand_info(pdev); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci if (info == NULL) 71662306a36Sopenharmony_ci return; 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci /* Release all our mtds and their partitions, then go through 71962306a36Sopenharmony_ci * freeing the resources used 72062306a36Sopenharmony_ci */ 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci if (info->mtds != NULL) { 72362306a36Sopenharmony_ci struct s3c2410_nand_mtd *ptr = info->mtds; 72462306a36Sopenharmony_ci int mtdno; 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci for (mtdno = 0; mtdno < info->mtd_count; mtdno++, ptr++) { 72762306a36Sopenharmony_ci pr_debug("releasing mtd %d (%p)\n", mtdno, ptr); 72862306a36Sopenharmony_ci WARN_ON(mtd_device_unregister(nand_to_mtd(&ptr->chip))); 72962306a36Sopenharmony_ci nand_cleanup(&ptr->chip); 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci } 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci /* free the common resources */ 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci if (!IS_ERR(info->clk)) 73662306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_DISABLE); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_cistatic int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, 74062306a36Sopenharmony_ci struct s3c2410_nand_mtd *mtd, 74162306a36Sopenharmony_ci struct s3c2410_nand_set *set) 74262306a36Sopenharmony_ci{ 74362306a36Sopenharmony_ci if (set) { 74462306a36Sopenharmony_ci struct mtd_info *mtdinfo = nand_to_mtd(&mtd->chip); 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci mtdinfo->name = set->name; 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci return mtd_device_register(mtdinfo, set->partitions, 74962306a36Sopenharmony_ci set->nr_partitions); 75062306a36Sopenharmony_ci } 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci return -ENODEV; 75362306a36Sopenharmony_ci} 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_cistatic int s3c2410_nand_setup_interface(struct nand_chip *chip, int csline, 75662306a36Sopenharmony_ci const struct nand_interface_config *conf) 75762306a36Sopenharmony_ci{ 75862306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 75962306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 76062306a36Sopenharmony_ci struct s3c2410_platform_nand *pdata = info->platform; 76162306a36Sopenharmony_ci const struct nand_sdr_timings *timings; 76262306a36Sopenharmony_ci int tacls; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci timings = nand_get_sdr_timings(conf); 76562306a36Sopenharmony_ci if (IS_ERR(timings)) 76662306a36Sopenharmony_ci return -ENOTSUPP; 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci tacls = timings->tCLS_min - timings->tWP_min; 76962306a36Sopenharmony_ci if (tacls < 0) 77062306a36Sopenharmony_ci tacls = 0; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci pdata->tacls = DIV_ROUND_UP(tacls, 1000); 77362306a36Sopenharmony_ci pdata->twrph0 = DIV_ROUND_UP(timings->tWP_min, 1000); 77462306a36Sopenharmony_ci pdata->twrph1 = DIV_ROUND_UP(timings->tCLH_min, 1000); 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci return s3c2410_nand_setrate(info); 77762306a36Sopenharmony_ci} 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci/** 78062306a36Sopenharmony_ci * s3c2410_nand_init_chip - initialise a single instance of an chip 78162306a36Sopenharmony_ci * @info: The base NAND controller the chip is on. 78262306a36Sopenharmony_ci * @nmtd: The new controller MTD instance to fill in. 78362306a36Sopenharmony_ci * @set: The information passed from the board specific platform data. 78462306a36Sopenharmony_ci * 78562306a36Sopenharmony_ci * Initialise the given @nmtd from the information in @info and @set. This 78662306a36Sopenharmony_ci * readies the structure for use with the MTD layer functions by ensuring 78762306a36Sopenharmony_ci * all pointers are setup and the necessary control routines selected. 78862306a36Sopenharmony_ci */ 78962306a36Sopenharmony_cistatic void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, 79062306a36Sopenharmony_ci struct s3c2410_nand_mtd *nmtd, 79162306a36Sopenharmony_ci struct s3c2410_nand_set *set) 79262306a36Sopenharmony_ci{ 79362306a36Sopenharmony_ci struct device_node *np = info->device->of_node; 79462306a36Sopenharmony_ci struct nand_chip *chip = &nmtd->chip; 79562306a36Sopenharmony_ci void __iomem *regs = info->regs; 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci nand_set_flash_node(chip, set->of_node); 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci chip->legacy.write_buf = s3c2410_nand_write_buf; 80062306a36Sopenharmony_ci chip->legacy.read_buf = s3c2410_nand_read_buf; 80162306a36Sopenharmony_ci chip->legacy.select_chip = s3c2410_nand_select_chip; 80262306a36Sopenharmony_ci chip->legacy.chip_delay = 50; 80362306a36Sopenharmony_ci nand_set_controller_data(chip, nmtd); 80462306a36Sopenharmony_ci chip->options = set->options; 80562306a36Sopenharmony_ci chip->controller = &info->controller; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci /* 80862306a36Sopenharmony_ci * let's keep behavior unchanged for legacy boards booting via pdata and 80962306a36Sopenharmony_ci * auto-detect timings only when booting with a device tree. 81062306a36Sopenharmony_ci */ 81162306a36Sopenharmony_ci if (!np) 81262306a36Sopenharmony_ci chip->options |= NAND_KEEP_TIMINGS; 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci switch (info->cpu_type) { 81562306a36Sopenharmony_ci case TYPE_S3C2410: 81662306a36Sopenharmony_ci chip->legacy.IO_ADDR_W = regs + S3C2410_NFDATA; 81762306a36Sopenharmony_ci info->sel_reg = regs + S3C2410_NFCONF; 81862306a36Sopenharmony_ci info->sel_bit = S3C2410_NFCONF_nFCE; 81962306a36Sopenharmony_ci chip->legacy.cmd_ctrl = s3c2410_nand_hwcontrol; 82062306a36Sopenharmony_ci chip->legacy.dev_ready = s3c2410_nand_devready; 82162306a36Sopenharmony_ci break; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci case TYPE_S3C2440: 82462306a36Sopenharmony_ci chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; 82562306a36Sopenharmony_ci info->sel_reg = regs + S3C2440_NFCONT; 82662306a36Sopenharmony_ci info->sel_bit = S3C2440_NFCONT_nFCE; 82762306a36Sopenharmony_ci chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; 82862306a36Sopenharmony_ci chip->legacy.dev_ready = s3c2440_nand_devready; 82962306a36Sopenharmony_ci chip->legacy.read_buf = s3c2440_nand_read_buf; 83062306a36Sopenharmony_ci chip->legacy.write_buf = s3c2440_nand_write_buf; 83162306a36Sopenharmony_ci break; 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci case TYPE_S3C2412: 83462306a36Sopenharmony_ci chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; 83562306a36Sopenharmony_ci info->sel_reg = regs + S3C2440_NFCONT; 83662306a36Sopenharmony_ci info->sel_bit = S3C2412_NFCONT_nFCE0; 83762306a36Sopenharmony_ci chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; 83862306a36Sopenharmony_ci chip->legacy.dev_ready = s3c2412_nand_devready; 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT) 84162306a36Sopenharmony_ci dev_info(info->device, "System booted from NAND\n"); 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci break; 84462306a36Sopenharmony_ci } 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W; 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci nmtd->info = info; 84962306a36Sopenharmony_ci nmtd->set = set; 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci chip->ecc.engine_type = info->platform->engine_type; 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_ci /* 85462306a36Sopenharmony_ci * If you use u-boot BBT creation code, specifying this flag will 85562306a36Sopenharmony_ci * let the kernel fish out the BBT from the NAND. 85662306a36Sopenharmony_ci */ 85762306a36Sopenharmony_ci if (set->flash_bbt) 85862306a36Sopenharmony_ci chip->bbt_options |= NAND_BBT_USE_FLASH; 85962306a36Sopenharmony_ci} 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_ci/** 86262306a36Sopenharmony_ci * s3c2410_nand_attach_chip - Init the ECC engine after NAND scan 86362306a36Sopenharmony_ci * @chip: The NAND chip 86462306a36Sopenharmony_ci * 86562306a36Sopenharmony_ci * This hook is called by the core after the identification of the NAND chip, 86662306a36Sopenharmony_ci * once the relevant per-chip information is up to date.. This call ensure that 86762306a36Sopenharmony_ci * we update the internal state accordingly. 86862306a36Sopenharmony_ci * 86962306a36Sopenharmony_ci * The internal state is currently limited to the ECC state information. 87062306a36Sopenharmony_ci*/ 87162306a36Sopenharmony_cistatic int s3c2410_nand_attach_chip(struct nand_chip *chip) 87262306a36Sopenharmony_ci{ 87362306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 87462306a36Sopenharmony_ci struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci switch (chip->ecc.engine_type) { 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci case NAND_ECC_ENGINE_TYPE_NONE: 87962306a36Sopenharmony_ci dev_info(info->device, "ECC disabled\n"); 88062306a36Sopenharmony_ci break; 88162306a36Sopenharmony_ci 88262306a36Sopenharmony_ci case NAND_ECC_ENGINE_TYPE_SOFT: 88362306a36Sopenharmony_ci /* 88462306a36Sopenharmony_ci * This driver expects Hamming based ECC when engine_type is set 88562306a36Sopenharmony_ci * to NAND_ECC_ENGINE_TYPE_SOFT. Force ecc.algo to 88662306a36Sopenharmony_ci * NAND_ECC_ALGO_HAMMING to avoid adding an extra ecc_algo field 88762306a36Sopenharmony_ci * to s3c2410_platform_nand. 88862306a36Sopenharmony_ci */ 88962306a36Sopenharmony_ci chip->ecc.algo = NAND_ECC_ALGO_HAMMING; 89062306a36Sopenharmony_ci dev_info(info->device, "soft ECC\n"); 89162306a36Sopenharmony_ci break; 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ci case NAND_ECC_ENGINE_TYPE_ON_HOST: 89462306a36Sopenharmony_ci chip->ecc.calculate = s3c2410_nand_calculate_ecc; 89562306a36Sopenharmony_ci chip->ecc.correct = s3c2410_nand_correct_data; 89662306a36Sopenharmony_ci chip->ecc.strength = 1; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci switch (info->cpu_type) { 89962306a36Sopenharmony_ci case TYPE_S3C2410: 90062306a36Sopenharmony_ci chip->ecc.hwctl = s3c2410_nand_enable_hwecc; 90162306a36Sopenharmony_ci chip->ecc.calculate = s3c2410_nand_calculate_ecc; 90262306a36Sopenharmony_ci break; 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci case TYPE_S3C2412: 90562306a36Sopenharmony_ci chip->ecc.hwctl = s3c2412_nand_enable_hwecc; 90662306a36Sopenharmony_ci chip->ecc.calculate = s3c2412_nand_calculate_ecc; 90762306a36Sopenharmony_ci break; 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci case TYPE_S3C2440: 91062306a36Sopenharmony_ci chip->ecc.hwctl = s3c2440_nand_enable_hwecc; 91162306a36Sopenharmony_ci chip->ecc.calculate = s3c2440_nand_calculate_ecc; 91262306a36Sopenharmony_ci break; 91362306a36Sopenharmony_ci } 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci dev_dbg(info->device, "chip %p => page shift %d\n", 91662306a36Sopenharmony_ci chip, chip->page_shift); 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci /* change the behaviour depending on whether we are using 91962306a36Sopenharmony_ci * the large or small page nand device */ 92062306a36Sopenharmony_ci if (chip->page_shift > 10) { 92162306a36Sopenharmony_ci chip->ecc.size = 256; 92262306a36Sopenharmony_ci chip->ecc.bytes = 3; 92362306a36Sopenharmony_ci } else { 92462306a36Sopenharmony_ci chip->ecc.size = 512; 92562306a36Sopenharmony_ci chip->ecc.bytes = 3; 92662306a36Sopenharmony_ci mtd_set_ooblayout(nand_to_mtd(chip), 92762306a36Sopenharmony_ci &s3c2410_ooblayout_ops); 92862306a36Sopenharmony_ci } 92962306a36Sopenharmony_ci 93062306a36Sopenharmony_ci dev_info(info->device, "hardware ECC\n"); 93162306a36Sopenharmony_ci break; 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_ci default: 93462306a36Sopenharmony_ci dev_err(info->device, "invalid ECC mode!\n"); 93562306a36Sopenharmony_ci return -EINVAL; 93662306a36Sopenharmony_ci } 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci if (chip->bbt_options & NAND_BBT_USE_FLASH) 93962306a36Sopenharmony_ci chip->options |= NAND_SKIP_BBTSCAN; 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci return 0; 94262306a36Sopenharmony_ci} 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_cistatic const struct nand_controller_ops s3c24xx_nand_controller_ops = { 94562306a36Sopenharmony_ci .attach_chip = s3c2410_nand_attach_chip, 94662306a36Sopenharmony_ci .setup_interface = s3c2410_nand_setup_interface, 94762306a36Sopenharmony_ci}; 94862306a36Sopenharmony_ci 94962306a36Sopenharmony_cistatic const struct of_device_id s3c24xx_nand_dt_ids[] = { 95062306a36Sopenharmony_ci { 95162306a36Sopenharmony_ci .compatible = "samsung,s3c2410-nand", 95262306a36Sopenharmony_ci .data = &s3c2410_nand_devtype_data, 95362306a36Sopenharmony_ci }, { 95462306a36Sopenharmony_ci /* also compatible with s3c6400 */ 95562306a36Sopenharmony_ci .compatible = "samsung,s3c2412-nand", 95662306a36Sopenharmony_ci .data = &s3c2412_nand_devtype_data, 95762306a36Sopenharmony_ci }, { 95862306a36Sopenharmony_ci .compatible = "samsung,s3c2440-nand", 95962306a36Sopenharmony_ci .data = &s3c2440_nand_devtype_data, 96062306a36Sopenharmony_ci }, 96162306a36Sopenharmony_ci { /* sentinel */ } 96262306a36Sopenharmony_ci}; 96362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, s3c24xx_nand_dt_ids); 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_cistatic int s3c24xx_nand_probe_dt(struct platform_device *pdev) 96662306a36Sopenharmony_ci{ 96762306a36Sopenharmony_ci const struct s3c24XX_nand_devtype_data *devtype_data; 96862306a36Sopenharmony_ci struct s3c2410_platform_nand *pdata; 96962306a36Sopenharmony_ci struct s3c2410_nand_info *info = platform_get_drvdata(pdev); 97062306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node, *child; 97162306a36Sopenharmony_ci struct s3c2410_nand_set *sets; 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci devtype_data = of_device_get_match_data(&pdev->dev); 97462306a36Sopenharmony_ci if (!devtype_data) 97562306a36Sopenharmony_ci return -ENODEV; 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_ci info->cpu_type = devtype_data->type; 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); 98062306a36Sopenharmony_ci if (!pdata) 98162306a36Sopenharmony_ci return -ENOMEM; 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci pdev->dev.platform_data = pdata; 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_ci pdata->nr_sets = of_get_child_count(np); 98662306a36Sopenharmony_ci if (!pdata->nr_sets) 98762306a36Sopenharmony_ci return 0; 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_ci sets = devm_kcalloc(&pdev->dev, pdata->nr_sets, sizeof(*sets), 99062306a36Sopenharmony_ci GFP_KERNEL); 99162306a36Sopenharmony_ci if (!sets) 99262306a36Sopenharmony_ci return -ENOMEM; 99362306a36Sopenharmony_ci 99462306a36Sopenharmony_ci pdata->sets = sets; 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci for_each_available_child_of_node(np, child) { 99762306a36Sopenharmony_ci sets->name = (char *)child->name; 99862306a36Sopenharmony_ci sets->of_node = child; 99962306a36Sopenharmony_ci sets->nr_chips = 1; 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci of_node_get(child); 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_ci sets++; 100462306a36Sopenharmony_ci } 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci return 0; 100762306a36Sopenharmony_ci} 100862306a36Sopenharmony_ci 100962306a36Sopenharmony_cistatic int s3c24xx_nand_probe_pdata(struct platform_device *pdev) 101062306a36Sopenharmony_ci{ 101162306a36Sopenharmony_ci struct s3c2410_nand_info *info = platform_get_drvdata(pdev); 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_ci info->cpu_type = platform_get_device_id(pdev)->driver_data; 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci return 0; 101662306a36Sopenharmony_ci} 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ci/* s3c24xx_nand_probe 101962306a36Sopenharmony_ci * 102062306a36Sopenharmony_ci * called by device layer when it finds a device matching 102162306a36Sopenharmony_ci * one our driver can handled. This code checks to see if 102262306a36Sopenharmony_ci * it can allocate all necessary resources then calls the 102362306a36Sopenharmony_ci * nand layer to look for devices 102462306a36Sopenharmony_ci*/ 102562306a36Sopenharmony_cistatic int s3c24xx_nand_probe(struct platform_device *pdev) 102662306a36Sopenharmony_ci{ 102762306a36Sopenharmony_ci struct s3c2410_platform_nand *plat; 102862306a36Sopenharmony_ci struct s3c2410_nand_info *info; 102962306a36Sopenharmony_ci struct s3c2410_nand_mtd *nmtd; 103062306a36Sopenharmony_ci struct s3c2410_nand_set *sets; 103162306a36Sopenharmony_ci struct resource *res; 103262306a36Sopenharmony_ci int err = 0; 103362306a36Sopenharmony_ci int size; 103462306a36Sopenharmony_ci int nr_sets; 103562306a36Sopenharmony_ci int setno; 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_ci info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 103862306a36Sopenharmony_ci if (info == NULL) { 103962306a36Sopenharmony_ci err = -ENOMEM; 104062306a36Sopenharmony_ci goto exit_error; 104162306a36Sopenharmony_ci } 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 104462306a36Sopenharmony_ci 104562306a36Sopenharmony_ci nand_controller_init(&info->controller); 104662306a36Sopenharmony_ci info->controller.ops = &s3c24xx_nand_controller_ops; 104762306a36Sopenharmony_ci 104862306a36Sopenharmony_ci /* get the clock source and enable it */ 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_ci info->clk = devm_clk_get(&pdev->dev, "nand"); 105162306a36Sopenharmony_ci if (IS_ERR(info->clk)) { 105262306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get clock\n"); 105362306a36Sopenharmony_ci err = -ENOENT; 105462306a36Sopenharmony_ci goto exit_error; 105562306a36Sopenharmony_ci } 105662306a36Sopenharmony_ci 105762306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci if (pdev->dev.of_node) 106062306a36Sopenharmony_ci err = s3c24xx_nand_probe_dt(pdev); 106162306a36Sopenharmony_ci else 106262306a36Sopenharmony_ci err = s3c24xx_nand_probe_pdata(pdev); 106362306a36Sopenharmony_ci 106462306a36Sopenharmony_ci if (err) 106562306a36Sopenharmony_ci goto exit_error; 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci plat = to_nand_plat(pdev); 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_ci /* allocate and map the resource */ 107062306a36Sopenharmony_ci 107162306a36Sopenharmony_ci /* currently we assume we have the one resource */ 107262306a36Sopenharmony_ci res = pdev->resource; 107362306a36Sopenharmony_ci size = resource_size(res); 107462306a36Sopenharmony_ci 107562306a36Sopenharmony_ci info->device = &pdev->dev; 107662306a36Sopenharmony_ci info->platform = plat; 107762306a36Sopenharmony_ci 107862306a36Sopenharmony_ci info->regs = devm_ioremap_resource(&pdev->dev, res); 107962306a36Sopenharmony_ci if (IS_ERR(info->regs)) { 108062306a36Sopenharmony_ci err = PTR_ERR(info->regs); 108162306a36Sopenharmony_ci goto exit_error; 108262306a36Sopenharmony_ci } 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ci dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs); 108562306a36Sopenharmony_ci 108662306a36Sopenharmony_ci if (!plat->sets || plat->nr_sets < 1) { 108762306a36Sopenharmony_ci err = -EINVAL; 108862306a36Sopenharmony_ci goto exit_error; 108962306a36Sopenharmony_ci } 109062306a36Sopenharmony_ci 109162306a36Sopenharmony_ci sets = plat->sets; 109262306a36Sopenharmony_ci nr_sets = plat->nr_sets; 109362306a36Sopenharmony_ci 109462306a36Sopenharmony_ci info->mtd_count = nr_sets; 109562306a36Sopenharmony_ci 109662306a36Sopenharmony_ci /* allocate our information */ 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci size = nr_sets * sizeof(*info->mtds); 109962306a36Sopenharmony_ci info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); 110062306a36Sopenharmony_ci if (info->mtds == NULL) { 110162306a36Sopenharmony_ci err = -ENOMEM; 110262306a36Sopenharmony_ci goto exit_error; 110362306a36Sopenharmony_ci } 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_ci /* initialise all possible chips */ 110662306a36Sopenharmony_ci 110762306a36Sopenharmony_ci nmtd = info->mtds; 110862306a36Sopenharmony_ci 110962306a36Sopenharmony_ci for (setno = 0; setno < nr_sets; setno++, nmtd++, sets++) { 111062306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(&nmtd->chip); 111162306a36Sopenharmony_ci 111262306a36Sopenharmony_ci pr_debug("initialising set %d (%p, info %p)\n", 111362306a36Sopenharmony_ci setno, nmtd, info); 111462306a36Sopenharmony_ci 111562306a36Sopenharmony_ci mtd->dev.parent = &pdev->dev; 111662306a36Sopenharmony_ci s3c2410_nand_init_chip(info, nmtd, sets); 111762306a36Sopenharmony_ci 111862306a36Sopenharmony_ci err = nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1); 111962306a36Sopenharmony_ci if (err) 112062306a36Sopenharmony_ci goto exit_error; 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_ci s3c2410_nand_add_partition(info, nmtd, sets); 112362306a36Sopenharmony_ci } 112462306a36Sopenharmony_ci 112562306a36Sopenharmony_ci /* initialise the hardware */ 112662306a36Sopenharmony_ci err = s3c2410_nand_inithw(info); 112762306a36Sopenharmony_ci if (err != 0) 112862306a36Sopenharmony_ci goto exit_error; 112962306a36Sopenharmony_ci 113062306a36Sopenharmony_ci if (allow_clk_suspend(info)) { 113162306a36Sopenharmony_ci dev_info(&pdev->dev, "clock idle support enabled\n"); 113262306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); 113362306a36Sopenharmony_ci } 113462306a36Sopenharmony_ci 113562306a36Sopenharmony_ci return 0; 113662306a36Sopenharmony_ci 113762306a36Sopenharmony_ci exit_error: 113862306a36Sopenharmony_ci s3c24xx_nand_remove(pdev); 113962306a36Sopenharmony_ci 114062306a36Sopenharmony_ci if (err == 0) 114162306a36Sopenharmony_ci err = -EINVAL; 114262306a36Sopenharmony_ci return err; 114362306a36Sopenharmony_ci} 114462306a36Sopenharmony_ci 114562306a36Sopenharmony_ci/* PM Support */ 114662306a36Sopenharmony_ci#ifdef CONFIG_PM 114762306a36Sopenharmony_ci 114862306a36Sopenharmony_cistatic int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm) 114962306a36Sopenharmony_ci{ 115062306a36Sopenharmony_ci struct s3c2410_nand_info *info = platform_get_drvdata(dev); 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_ci if (info) { 115362306a36Sopenharmony_ci info->save_sel = readl(info->sel_reg); 115462306a36Sopenharmony_ci 115562306a36Sopenharmony_ci /* For the moment, we must ensure nFCE is high during 115662306a36Sopenharmony_ci * the time we are suspended. This really should be 115762306a36Sopenharmony_ci * handled by suspending the MTDs we are using, but 115862306a36Sopenharmony_ci * that is currently not the case. */ 115962306a36Sopenharmony_ci 116062306a36Sopenharmony_ci writel(info->save_sel | info->sel_bit, info->sel_reg); 116162306a36Sopenharmony_ci 116262306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_DISABLE); 116362306a36Sopenharmony_ci } 116462306a36Sopenharmony_ci 116562306a36Sopenharmony_ci return 0; 116662306a36Sopenharmony_ci} 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_cistatic int s3c24xx_nand_resume(struct platform_device *dev) 116962306a36Sopenharmony_ci{ 117062306a36Sopenharmony_ci struct s3c2410_nand_info *info = platform_get_drvdata(dev); 117162306a36Sopenharmony_ci unsigned long sel; 117262306a36Sopenharmony_ci 117362306a36Sopenharmony_ci if (info) { 117462306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); 117562306a36Sopenharmony_ci s3c2410_nand_inithw(info); 117662306a36Sopenharmony_ci 117762306a36Sopenharmony_ci /* Restore the state of the nFCE line. */ 117862306a36Sopenharmony_ci 117962306a36Sopenharmony_ci sel = readl(info->sel_reg); 118062306a36Sopenharmony_ci sel &= ~info->sel_bit; 118162306a36Sopenharmony_ci sel |= info->save_sel & info->sel_bit; 118262306a36Sopenharmony_ci writel(sel, info->sel_reg); 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); 118562306a36Sopenharmony_ci } 118662306a36Sopenharmony_ci 118762306a36Sopenharmony_ci return 0; 118862306a36Sopenharmony_ci} 118962306a36Sopenharmony_ci 119062306a36Sopenharmony_ci#else 119162306a36Sopenharmony_ci#define s3c24xx_nand_suspend NULL 119262306a36Sopenharmony_ci#define s3c24xx_nand_resume NULL 119362306a36Sopenharmony_ci#endif 119462306a36Sopenharmony_ci 119562306a36Sopenharmony_ci/* driver device registration */ 119662306a36Sopenharmony_ci 119762306a36Sopenharmony_cistatic const struct platform_device_id s3c24xx_driver_ids[] = { 119862306a36Sopenharmony_ci { 119962306a36Sopenharmony_ci .name = "s3c2410-nand", 120062306a36Sopenharmony_ci .driver_data = TYPE_S3C2410, 120162306a36Sopenharmony_ci }, { 120262306a36Sopenharmony_ci .name = "s3c2440-nand", 120362306a36Sopenharmony_ci .driver_data = TYPE_S3C2440, 120462306a36Sopenharmony_ci }, { 120562306a36Sopenharmony_ci .name = "s3c2412-nand", 120662306a36Sopenharmony_ci .driver_data = TYPE_S3C2412, 120762306a36Sopenharmony_ci }, { 120862306a36Sopenharmony_ci .name = "s3c6400-nand", 120962306a36Sopenharmony_ci .driver_data = TYPE_S3C2412, /* compatible with 2412 */ 121062306a36Sopenharmony_ci }, 121162306a36Sopenharmony_ci { } 121262306a36Sopenharmony_ci}; 121362306a36Sopenharmony_ci 121462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids); 121562306a36Sopenharmony_ci 121662306a36Sopenharmony_cistatic struct platform_driver s3c24xx_nand_driver = { 121762306a36Sopenharmony_ci .probe = s3c24xx_nand_probe, 121862306a36Sopenharmony_ci .remove_new = s3c24xx_nand_remove, 121962306a36Sopenharmony_ci .suspend = s3c24xx_nand_suspend, 122062306a36Sopenharmony_ci .resume = s3c24xx_nand_resume, 122162306a36Sopenharmony_ci .id_table = s3c24xx_driver_ids, 122262306a36Sopenharmony_ci .driver = { 122362306a36Sopenharmony_ci .name = "s3c24xx-nand", 122462306a36Sopenharmony_ci .of_match_table = s3c24xx_nand_dt_ids, 122562306a36Sopenharmony_ci }, 122662306a36Sopenharmony_ci}; 122762306a36Sopenharmony_ci 122862306a36Sopenharmony_cimodule_platform_driver(s3c24xx_nand_driver); 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 123162306a36Sopenharmony_ciMODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); 123262306a36Sopenharmony_ciMODULE_DESCRIPTION("S3C24XX MTD NAND driver"); 1233