18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * (C) 2003 Red Hat, Inc. 48c2ecf20Sopenharmony_ci * (C) 2004 Dan Brown <dan_brown@ieee.org> 58c2ecf20Sopenharmony_ci * (C) 2004 Kalev Lember <kalev@smartlink.ee> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: David Woodhouse <dwmw2@infradead.org> 88c2ecf20Sopenharmony_ci * Additional Diskonchip 2000 and Millennium support by Dan Brown <dan_brown@ieee.org> 98c2ecf20Sopenharmony_ci * Diskonchip Millennium Plus support by Kalev Lember <kalev@smartlink.ee> 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Error correction code lifted from the old docecc code 128c2ecf20Sopenharmony_ci * Author: Fabrice Bellard (fabrice.bellard@netgem.com) 138c2ecf20Sopenharmony_ci * Copyright (C) 2000 Netgem S.A. 148c2ecf20Sopenharmony_ci * converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@linutronix.de> 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * Interface to generic NAND code for M-Systems DiskOnChip devices 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <linux/kernel.h> 208c2ecf20Sopenharmony_ci#include <linux/init.h> 218c2ecf20Sopenharmony_ci#include <linux/sched.h> 228c2ecf20Sopenharmony_ci#include <linux/delay.h> 238c2ecf20Sopenharmony_ci#include <linux/rslib.h> 248c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 258c2ecf20Sopenharmony_ci#include <linux/slab.h> 268c2ecf20Sopenharmony_ci#include <linux/io.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 298c2ecf20Sopenharmony_ci#include <linux/mtd/rawnand.h> 308c2ecf20Sopenharmony_ci#include <linux/mtd/doc2000.h> 318c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h> 328c2ecf20Sopenharmony_ci#include <linux/mtd/inftl.h> 338c2ecf20Sopenharmony_ci#include <linux/module.h> 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* Where to look for the devices? */ 368c2ecf20Sopenharmony_ci#ifndef CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS 378c2ecf20Sopenharmony_ci#define CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS 0 388c2ecf20Sopenharmony_ci#endif 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic unsigned long doc_locations[] __initdata = { 418c2ecf20Sopenharmony_ci#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__) 428c2ecf20Sopenharmony_ci#ifdef CONFIG_MTD_NAND_DISKONCHIP_PROBE_HIGH 438c2ecf20Sopenharmony_ci 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, 448c2ecf20Sopenharmony_ci 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000, 458c2ecf20Sopenharmony_ci 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, 468c2ecf20Sopenharmony_ci 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, 478c2ecf20Sopenharmony_ci 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000, 488c2ecf20Sopenharmony_ci#else 498c2ecf20Sopenharmony_ci 0xc8000, 0xca000, 0xcc000, 0xce000, 508c2ecf20Sopenharmony_ci 0xd0000, 0xd2000, 0xd4000, 0xd6000, 518c2ecf20Sopenharmony_ci 0xd8000, 0xda000, 0xdc000, 0xde000, 528c2ecf20Sopenharmony_ci 0xe0000, 0xe2000, 0xe4000, 0xe6000, 538c2ecf20Sopenharmony_ci 0xe8000, 0xea000, 0xec000, 0xee000, 548c2ecf20Sopenharmony_ci#endif 558c2ecf20Sopenharmony_ci#endif 568c2ecf20Sopenharmony_ci 0xffffffff }; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic struct mtd_info *doclist = NULL; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistruct doc_priv { 618c2ecf20Sopenharmony_ci struct nand_controller base; 628c2ecf20Sopenharmony_ci void __iomem *virtadr; 638c2ecf20Sopenharmony_ci unsigned long physadr; 648c2ecf20Sopenharmony_ci u_char ChipID; 658c2ecf20Sopenharmony_ci u_char CDSNControl; 668c2ecf20Sopenharmony_ci int chips_per_floor; /* The number of chips detected on each floor */ 678c2ecf20Sopenharmony_ci int curfloor; 688c2ecf20Sopenharmony_ci int curchip; 698c2ecf20Sopenharmony_ci int mh0_page; 708c2ecf20Sopenharmony_ci int mh1_page; 718c2ecf20Sopenharmony_ci struct rs_control *rs_decoder; 728c2ecf20Sopenharmony_ci struct mtd_info *nextdoc; 738c2ecf20Sopenharmony_ci bool supports_32b_reads; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci /* Handle the last stage of initialization (BBT scan, partitioning) */ 768c2ecf20Sopenharmony_ci int (*late_init)(struct mtd_info *mtd); 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci/* This is the ecc value computed by the HW ecc generator upon writing an empty 808c2ecf20Sopenharmony_ci page, one with all 0xff for data. */ 818c2ecf20Sopenharmony_cistatic u_char empty_write_ecc[6] = { 0x4b, 0x00, 0xe2, 0x0e, 0x93, 0xf7 }; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci#define INFTL_BBT_RESERVED_BLOCKS 4 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci#define DoC_is_MillenniumPlus(doc) ((doc)->ChipID == DOC_ChipID_DocMilPlus16 || (doc)->ChipID == DOC_ChipID_DocMilPlus32) 868c2ecf20Sopenharmony_ci#define DoC_is_Millennium(doc) ((doc)->ChipID == DOC_ChipID_DocMil) 878c2ecf20Sopenharmony_ci#define DoC_is_2000(doc) ((doc)->ChipID == DOC_ChipID_Doc2k) 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int debug = 0; 908c2ecf20Sopenharmony_cimodule_param(debug, int, 0); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int try_dword = 1; 938c2ecf20Sopenharmony_cimodule_param(try_dword, int, 0); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic int no_ecc_failures = 0; 968c2ecf20Sopenharmony_cimodule_param(no_ecc_failures, int, 0); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic int no_autopart = 0; 998c2ecf20Sopenharmony_cimodule_param(no_autopart, int, 0); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic int show_firmware_partition = 0; 1028c2ecf20Sopenharmony_cimodule_param(show_firmware_partition, int, 0); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci#ifdef CONFIG_MTD_NAND_DISKONCHIP_BBTWRITE 1058c2ecf20Sopenharmony_cistatic int inftl_bbt_write = 1; 1068c2ecf20Sopenharmony_ci#else 1078c2ecf20Sopenharmony_cistatic int inftl_bbt_write = 0; 1088c2ecf20Sopenharmony_ci#endif 1098c2ecf20Sopenharmony_cimodule_param(inftl_bbt_write, int, 0); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic unsigned long doc_config_location = CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS; 1128c2ecf20Sopenharmony_cimodule_param(doc_config_location, ulong, 0); 1138c2ecf20Sopenharmony_ciMODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip"); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci/* Sector size for HW ECC */ 1168c2ecf20Sopenharmony_ci#define SECTOR_SIZE 512 1178c2ecf20Sopenharmony_ci/* The sector bytes are packed into NB_DATA 10 bit words */ 1188c2ecf20Sopenharmony_ci#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / 10) 1198c2ecf20Sopenharmony_ci/* Number of roots */ 1208c2ecf20Sopenharmony_ci#define NROOTS 4 1218c2ecf20Sopenharmony_ci/* First consective root */ 1228c2ecf20Sopenharmony_ci#define FCR 510 1238c2ecf20Sopenharmony_ci/* Number of symbols */ 1248c2ecf20Sopenharmony_ci#define NN 1023 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 1278c2ecf20Sopenharmony_ci * The HW decoder in the DoC ASIC's provides us a error syndrome, 1288c2ecf20Sopenharmony_ci * which we must convert to a standard syndrome usable by the generic 1298c2ecf20Sopenharmony_ci * Reed-Solomon library code. 1308c2ecf20Sopenharmony_ci * 1318c2ecf20Sopenharmony_ci * Fabrice Bellard figured this out in the old docecc code. I added 1328c2ecf20Sopenharmony_ci * some comments, improved a minor bit and converted it to make use 1338c2ecf20Sopenharmony_ci * of the generic Reed-Solomon library. tglx 1348c2ecf20Sopenharmony_ci */ 1358c2ecf20Sopenharmony_cistatic int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci int i, j, nerr, errpos[8]; 1388c2ecf20Sopenharmony_ci uint8_t parity; 1398c2ecf20Sopenharmony_ci uint16_t ds[4], s[5], tmp, errval[8], syn[4]; 1408c2ecf20Sopenharmony_ci struct rs_codec *cd = rs->codec; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci memset(syn, 0, sizeof(syn)); 1438c2ecf20Sopenharmony_ci /* Convert the ecc bytes into words */ 1448c2ecf20Sopenharmony_ci ds[0] = ((ecc[4] & 0xff) >> 0) | ((ecc[5] & 0x03) << 8); 1458c2ecf20Sopenharmony_ci ds[1] = ((ecc[5] & 0xfc) >> 2) | ((ecc[2] & 0x0f) << 6); 1468c2ecf20Sopenharmony_ci ds[2] = ((ecc[2] & 0xf0) >> 4) | ((ecc[3] & 0x3f) << 4); 1478c2ecf20Sopenharmony_ci ds[3] = ((ecc[3] & 0xc0) >> 6) | ((ecc[0] & 0xff) << 2); 1488c2ecf20Sopenharmony_ci parity = ecc[1]; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* Initialize the syndrome buffer */ 1518c2ecf20Sopenharmony_ci for (i = 0; i < NROOTS; i++) 1528c2ecf20Sopenharmony_ci s[i] = ds[0]; 1538c2ecf20Sopenharmony_ci /* 1548c2ecf20Sopenharmony_ci * Evaluate 1558c2ecf20Sopenharmony_ci * s[i] = ds[3]x^3 + ds[2]x^2 + ds[1]x^1 + ds[0] 1568c2ecf20Sopenharmony_ci * where x = alpha^(FCR + i) 1578c2ecf20Sopenharmony_ci */ 1588c2ecf20Sopenharmony_ci for (j = 1; j < NROOTS; j++) { 1598c2ecf20Sopenharmony_ci if (ds[j] == 0) 1608c2ecf20Sopenharmony_ci continue; 1618c2ecf20Sopenharmony_ci tmp = cd->index_of[ds[j]]; 1628c2ecf20Sopenharmony_ci for (i = 0; i < NROOTS; i++) 1638c2ecf20Sopenharmony_ci s[i] ^= cd->alpha_to[rs_modnn(cd, tmp + (FCR + i) * j)]; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* Calc syn[i] = s[i] / alpha^(v + i) */ 1678c2ecf20Sopenharmony_ci for (i = 0; i < NROOTS; i++) { 1688c2ecf20Sopenharmony_ci if (s[i]) 1698c2ecf20Sopenharmony_ci syn[i] = rs_modnn(cd, cd->index_of[s[i]] + (NN - FCR - i)); 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci /* Call the decoder library */ 1728c2ecf20Sopenharmony_ci nerr = decode_rs16(rs, NULL, NULL, 1019, syn, 0, errpos, 0, errval); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* Incorrectable errors ? */ 1758c2ecf20Sopenharmony_ci if (nerr < 0) 1768c2ecf20Sopenharmony_ci return nerr; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* 1798c2ecf20Sopenharmony_ci * Correct the errors. The bitpositions are a bit of magic, 1808c2ecf20Sopenharmony_ci * but they are given by the design of the de/encoder circuit 1818c2ecf20Sopenharmony_ci * in the DoC ASIC's. 1828c2ecf20Sopenharmony_ci */ 1838c2ecf20Sopenharmony_ci for (i = 0; i < nerr; i++) { 1848c2ecf20Sopenharmony_ci int index, bitpos, pos = 1015 - errpos[i]; 1858c2ecf20Sopenharmony_ci uint8_t val; 1868c2ecf20Sopenharmony_ci if (pos >= NB_DATA && pos < 1019) 1878c2ecf20Sopenharmony_ci continue; 1888c2ecf20Sopenharmony_ci if (pos < NB_DATA) { 1898c2ecf20Sopenharmony_ci /* extract bit position (MSB first) */ 1908c2ecf20Sopenharmony_ci pos = 10 * (NB_DATA - 1 - pos) - 6; 1918c2ecf20Sopenharmony_ci /* now correct the following 10 bits. At most two bytes 1928c2ecf20Sopenharmony_ci can be modified since pos is even */ 1938c2ecf20Sopenharmony_ci index = (pos >> 3) ^ 1; 1948c2ecf20Sopenharmony_ci bitpos = pos & 7; 1958c2ecf20Sopenharmony_ci if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) { 1968c2ecf20Sopenharmony_ci val = (uint8_t) (errval[i] >> (2 + bitpos)); 1978c2ecf20Sopenharmony_ci parity ^= val; 1988c2ecf20Sopenharmony_ci if (index < SECTOR_SIZE) 1998c2ecf20Sopenharmony_ci data[index] ^= val; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci index = ((pos >> 3) + 1) ^ 1; 2028c2ecf20Sopenharmony_ci bitpos = (bitpos + 10) & 7; 2038c2ecf20Sopenharmony_ci if (bitpos == 0) 2048c2ecf20Sopenharmony_ci bitpos = 8; 2058c2ecf20Sopenharmony_ci if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) { 2068c2ecf20Sopenharmony_ci val = (uint8_t) (errval[i] << (8 - bitpos)); 2078c2ecf20Sopenharmony_ci parity ^= val; 2088c2ecf20Sopenharmony_ci if (index < SECTOR_SIZE) 2098c2ecf20Sopenharmony_ci data[index] ^= val; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci /* If the parity is wrong, no rescue possible */ 2148c2ecf20Sopenharmony_ci return parity ? -EBADMSG : nerr; 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic void DoC_Delay(struct doc_priv *doc, unsigned short cycles) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci volatile char dummy; 2208c2ecf20Sopenharmony_ci int i; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci for (i = 0; i < cycles; i++) { 2238c2ecf20Sopenharmony_ci if (DoC_is_Millennium(doc)) 2248c2ecf20Sopenharmony_ci dummy = ReadDOC(doc->virtadr, NOP); 2258c2ecf20Sopenharmony_ci else if (DoC_is_MillenniumPlus(doc)) 2268c2ecf20Sopenharmony_ci dummy = ReadDOC(doc->virtadr, Mplus_NOP); 2278c2ecf20Sopenharmony_ci else 2288c2ecf20Sopenharmony_ci dummy = ReadDOC(doc->virtadr, DOCStatus); 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1) 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ 2368c2ecf20Sopenharmony_cistatic int _DoC_WaitReady(struct doc_priv *doc) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 2398c2ecf20Sopenharmony_ci unsigned long timeo = jiffies + (HZ * 10); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if (debug) 2428c2ecf20Sopenharmony_ci printk("_DoC_WaitReady...\n"); 2438c2ecf20Sopenharmony_ci /* Out-of-line routine to wait for chip response */ 2448c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) { 2458c2ecf20Sopenharmony_ci while ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) { 2468c2ecf20Sopenharmony_ci if (time_after(jiffies, timeo)) { 2478c2ecf20Sopenharmony_ci printk("_DoC_WaitReady timed out.\n"); 2488c2ecf20Sopenharmony_ci return -EIO; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci udelay(1); 2518c2ecf20Sopenharmony_ci cond_resched(); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci } else { 2548c2ecf20Sopenharmony_ci while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { 2558c2ecf20Sopenharmony_ci if (time_after(jiffies, timeo)) { 2568c2ecf20Sopenharmony_ci printk("_DoC_WaitReady timed out.\n"); 2578c2ecf20Sopenharmony_ci return -EIO; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci udelay(1); 2608c2ecf20Sopenharmony_ci cond_resched(); 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci return 0; 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic inline int DoC_WaitReady(struct doc_priv *doc) 2688c2ecf20Sopenharmony_ci{ 2698c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 2708c2ecf20Sopenharmony_ci int ret = 0; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) { 2738c2ecf20Sopenharmony_ci DoC_Delay(doc, 4); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) 2768c2ecf20Sopenharmony_ci /* Call the out-of-line routine to wait */ 2778c2ecf20Sopenharmony_ci ret = _DoC_WaitReady(doc); 2788c2ecf20Sopenharmony_ci } else { 2798c2ecf20Sopenharmony_ci DoC_Delay(doc, 4); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) 2828c2ecf20Sopenharmony_ci /* Call the out-of-line routine to wait */ 2838c2ecf20Sopenharmony_ci ret = _DoC_WaitReady(doc); 2848c2ecf20Sopenharmony_ci DoC_Delay(doc, 2); 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (debug) 2888c2ecf20Sopenharmony_ci printk("DoC_WaitReady OK\n"); 2898c2ecf20Sopenharmony_ci return ret; 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_cistatic void doc2000_write_byte(struct nand_chip *this, u_char datum) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 2958c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci if (debug) 2988c2ecf20Sopenharmony_ci printk("write_byte %02x\n", datum); 2998c2ecf20Sopenharmony_ci WriteDOC(datum, docptr, CDSNSlowIO); 3008c2ecf20Sopenharmony_ci WriteDOC(datum, docptr, 2k_CDSN_IO); 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cistatic void doc2000_writebuf(struct nand_chip *this, const u_char *buf, 3048c2ecf20Sopenharmony_ci int len) 3058c2ecf20Sopenharmony_ci{ 3068c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 3078c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 3088c2ecf20Sopenharmony_ci int i; 3098c2ecf20Sopenharmony_ci if (debug) 3108c2ecf20Sopenharmony_ci printk("writebuf of %d bytes: ", len); 3118c2ecf20Sopenharmony_ci for (i = 0; i < len; i++) { 3128c2ecf20Sopenharmony_ci WriteDOC_(buf[i], docptr, DoC_2k_CDSN_IO + i); 3138c2ecf20Sopenharmony_ci if (debug && i < 16) 3148c2ecf20Sopenharmony_ci printk("%02x ", buf[i]); 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci if (debug) 3178c2ecf20Sopenharmony_ci printk("\n"); 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_cistatic void doc2000_readbuf(struct nand_chip *this, u_char *buf, int len) 3218c2ecf20Sopenharmony_ci{ 3228c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 3238c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 3248c2ecf20Sopenharmony_ci u32 *buf32 = (u32 *)buf; 3258c2ecf20Sopenharmony_ci int i; 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci if (debug) 3288c2ecf20Sopenharmony_ci printk("readbuf of %d bytes: ", len); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci if (!doc->supports_32b_reads || 3318c2ecf20Sopenharmony_ci ((((unsigned long)buf) | len) & 3)) { 3328c2ecf20Sopenharmony_ci for (i = 0; i < len; i++) 3338c2ecf20Sopenharmony_ci buf[i] = ReadDOC(docptr, 2k_CDSN_IO + i); 3348c2ecf20Sopenharmony_ci } else { 3358c2ecf20Sopenharmony_ci for (i = 0; i < len / 4; i++) 3368c2ecf20Sopenharmony_ci buf32[i] = readl(docptr + DoC_2k_CDSN_IO + i); 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci/* 3418c2ecf20Sopenharmony_ci * We need our own readid() here because it's called before the NAND chip 3428c2ecf20Sopenharmony_ci * has been initialized, and calling nand_op_readid() would lead to a NULL 3438c2ecf20Sopenharmony_ci * pointer exception when dereferencing the NAND timings. 3448c2ecf20Sopenharmony_ci */ 3458c2ecf20Sopenharmony_cistatic void doc200x_readid(struct nand_chip *this, unsigned int cs, u8 *id) 3468c2ecf20Sopenharmony_ci{ 3478c2ecf20Sopenharmony_ci u8 addr = 0; 3488c2ecf20Sopenharmony_ci struct nand_op_instr instrs[] = { 3498c2ecf20Sopenharmony_ci NAND_OP_CMD(NAND_CMD_READID, 0), 3508c2ecf20Sopenharmony_ci NAND_OP_ADDR(1, &addr, 50), 3518c2ecf20Sopenharmony_ci NAND_OP_8BIT_DATA_IN(2, id, 0), 3528c2ecf20Sopenharmony_ci }; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci struct nand_operation op = NAND_OPERATION(cs, instrs); 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci if (!id) 3578c2ecf20Sopenharmony_ci op.ninstrs--; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci this->controller->ops->exec_op(this, &op, false); 3608c2ecf20Sopenharmony_ci} 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_cistatic uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr) 3638c2ecf20Sopenharmony_ci{ 3648c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 3658c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 3668c2ecf20Sopenharmony_ci uint16_t ret; 3678c2ecf20Sopenharmony_ci u8 id[2]; 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci doc200x_readid(this, nr, id); 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci ret = ((u16)id[0] << 8) | id[1]; 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci if (doc->ChipID == DOC_ChipID_Doc2k && try_dword && !nr) { 3748c2ecf20Sopenharmony_ci /* First chip probe. See if we get same results by 32-bit access */ 3758c2ecf20Sopenharmony_ci union { 3768c2ecf20Sopenharmony_ci uint32_t dword; 3778c2ecf20Sopenharmony_ci uint8_t byte[4]; 3788c2ecf20Sopenharmony_ci } ident; 3798c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci doc200x_readid(this, nr, NULL); 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci ident.dword = readl(docptr + DoC_2k_CDSN_IO); 3848c2ecf20Sopenharmony_ci if (((ident.byte[0] << 8) | ident.byte[1]) == ret) { 3858c2ecf20Sopenharmony_ci pr_info("DiskOnChip 2000 responds to DWORD access\n"); 3868c2ecf20Sopenharmony_ci doc->supports_32b_reads = true; 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci } 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci return ret; 3918c2ecf20Sopenharmony_ci} 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_cistatic void __init doc2000_count_chips(struct mtd_info *mtd) 3948c2ecf20Sopenharmony_ci{ 3958c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 3968c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 3978c2ecf20Sopenharmony_ci uint16_t mfrid; 3988c2ecf20Sopenharmony_ci int i; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci /* Max 4 chips per floor on DiskOnChip 2000 */ 4018c2ecf20Sopenharmony_ci doc->chips_per_floor = 4; 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci /* Find out what the first chip is */ 4048c2ecf20Sopenharmony_ci mfrid = doc200x_ident_chip(mtd, 0); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci /* Find how many chips in each floor. */ 4078c2ecf20Sopenharmony_ci for (i = 1; i < 4; i++) { 4088c2ecf20Sopenharmony_ci if (doc200x_ident_chip(mtd, i) != mfrid) 4098c2ecf20Sopenharmony_ci break; 4108c2ecf20Sopenharmony_ci } 4118c2ecf20Sopenharmony_ci doc->chips_per_floor = i; 4128c2ecf20Sopenharmony_ci pr_debug("Detected %d chips per floor.\n", i); 4138c2ecf20Sopenharmony_ci} 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_cistatic void doc2001_write_byte(struct nand_chip *this, u_char datum) 4168c2ecf20Sopenharmony_ci{ 4178c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 4188c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci WriteDOC(datum, docptr, CDSNSlowIO); 4218c2ecf20Sopenharmony_ci WriteDOC(datum, docptr, Mil_CDSN_IO); 4228c2ecf20Sopenharmony_ci WriteDOC(datum, docptr, WritePipeTerm); 4238c2ecf20Sopenharmony_ci} 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_cistatic void doc2001_writebuf(struct nand_chip *this, const u_char *buf, int len) 4268c2ecf20Sopenharmony_ci{ 4278c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 4288c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 4298c2ecf20Sopenharmony_ci int i; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci for (i = 0; i < len; i++) 4328c2ecf20Sopenharmony_ci WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i); 4338c2ecf20Sopenharmony_ci /* Terminate write pipeline */ 4348c2ecf20Sopenharmony_ci WriteDOC(0x00, docptr, WritePipeTerm); 4358c2ecf20Sopenharmony_ci} 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_cistatic void doc2001_readbuf(struct nand_chip *this, u_char *buf, int len) 4388c2ecf20Sopenharmony_ci{ 4398c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 4408c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 4418c2ecf20Sopenharmony_ci int i; 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci /* Start read pipeline */ 4448c2ecf20Sopenharmony_ci ReadDOC(docptr, ReadPipeInit); 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci for (i = 0; i < len - 1; i++) 4478c2ecf20Sopenharmony_ci buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff)); 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci /* Terminate read pipeline */ 4508c2ecf20Sopenharmony_ci buf[i] = ReadDOC(docptr, LastDataRead); 4518c2ecf20Sopenharmony_ci} 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_cistatic void doc2001plus_writebuf(struct nand_chip *this, const u_char *buf, int len) 4548c2ecf20Sopenharmony_ci{ 4558c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 4568c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 4578c2ecf20Sopenharmony_ci int i; 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci if (debug) 4608c2ecf20Sopenharmony_ci printk("writebuf of %d bytes: ", len); 4618c2ecf20Sopenharmony_ci for (i = 0; i < len; i++) { 4628c2ecf20Sopenharmony_ci WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i); 4638c2ecf20Sopenharmony_ci if (debug && i < 16) 4648c2ecf20Sopenharmony_ci printk("%02x ", buf[i]); 4658c2ecf20Sopenharmony_ci } 4668c2ecf20Sopenharmony_ci if (debug) 4678c2ecf20Sopenharmony_ci printk("\n"); 4688c2ecf20Sopenharmony_ci} 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_cistatic void doc2001plus_readbuf(struct nand_chip *this, u_char *buf, int len) 4718c2ecf20Sopenharmony_ci{ 4728c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 4738c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 4748c2ecf20Sopenharmony_ci int i; 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci if (debug) 4778c2ecf20Sopenharmony_ci printk("readbuf of %d bytes: ", len); 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci /* Start read pipeline */ 4808c2ecf20Sopenharmony_ci ReadDOC(docptr, Mplus_ReadPipeInit); 4818c2ecf20Sopenharmony_ci ReadDOC(docptr, Mplus_ReadPipeInit); 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci for (i = 0; i < len - 2; i++) { 4848c2ecf20Sopenharmony_ci buf[i] = ReadDOC(docptr, Mil_CDSN_IO); 4858c2ecf20Sopenharmony_ci if (debug && i < 16) 4868c2ecf20Sopenharmony_ci printk("%02x ", buf[i]); 4878c2ecf20Sopenharmony_ci } 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci /* Terminate read pipeline */ 4908c2ecf20Sopenharmony_ci if (len >= 2) { 4918c2ecf20Sopenharmony_ci buf[len - 2] = ReadDOC(docptr, Mplus_LastDataRead); 4928c2ecf20Sopenharmony_ci if (debug && i < 16) 4938c2ecf20Sopenharmony_ci printk("%02x ", buf[len - 2]); 4948c2ecf20Sopenharmony_ci } 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci buf[len - 1] = ReadDOC(docptr, Mplus_LastDataRead); 4978c2ecf20Sopenharmony_ci if (debug && i < 16) 4988c2ecf20Sopenharmony_ci printk("%02x ", buf[len - 1]); 4998c2ecf20Sopenharmony_ci if (debug) 5008c2ecf20Sopenharmony_ci printk("\n"); 5018c2ecf20Sopenharmony_ci} 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_cistatic void doc200x_write_control(struct doc_priv *doc, u8 value) 5048c2ecf20Sopenharmony_ci{ 5058c2ecf20Sopenharmony_ci WriteDOC(value, doc->virtadr, CDSNControl); 5068c2ecf20Sopenharmony_ci /* 11.4.3 -- 4 NOPs after CSDNControl write */ 5078c2ecf20Sopenharmony_ci DoC_Delay(doc, 4); 5088c2ecf20Sopenharmony_ci} 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_cistatic void doc200x_exec_instr(struct nand_chip *this, 5118c2ecf20Sopenharmony_ci const struct nand_op_instr *instr) 5128c2ecf20Sopenharmony_ci{ 5138c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 5148c2ecf20Sopenharmony_ci unsigned int i; 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci switch (instr->type) { 5178c2ecf20Sopenharmony_ci case NAND_OP_CMD_INSTR: 5188c2ecf20Sopenharmony_ci doc200x_write_control(doc, CDSN_CTRL_CE | CDSN_CTRL_CLE); 5198c2ecf20Sopenharmony_ci doc2000_write_byte(this, instr->ctx.cmd.opcode); 5208c2ecf20Sopenharmony_ci break; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci case NAND_OP_ADDR_INSTR: 5238c2ecf20Sopenharmony_ci doc200x_write_control(doc, CDSN_CTRL_CE | CDSN_CTRL_ALE); 5248c2ecf20Sopenharmony_ci for (i = 0; i < instr->ctx.addr.naddrs; i++) { 5258c2ecf20Sopenharmony_ci u8 addr = instr->ctx.addr.addrs[i]; 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci if (DoC_is_2000(doc)) 5288c2ecf20Sopenharmony_ci doc2000_write_byte(this, addr); 5298c2ecf20Sopenharmony_ci else 5308c2ecf20Sopenharmony_ci doc2001_write_byte(this, addr); 5318c2ecf20Sopenharmony_ci } 5328c2ecf20Sopenharmony_ci break; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci case NAND_OP_DATA_IN_INSTR: 5358c2ecf20Sopenharmony_ci doc200x_write_control(doc, CDSN_CTRL_CE); 5368c2ecf20Sopenharmony_ci if (DoC_is_2000(doc)) 5378c2ecf20Sopenharmony_ci doc2000_readbuf(this, instr->ctx.data.buf.in, 5388c2ecf20Sopenharmony_ci instr->ctx.data.len); 5398c2ecf20Sopenharmony_ci else 5408c2ecf20Sopenharmony_ci doc2001_readbuf(this, instr->ctx.data.buf.in, 5418c2ecf20Sopenharmony_ci instr->ctx.data.len); 5428c2ecf20Sopenharmony_ci break; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci case NAND_OP_DATA_OUT_INSTR: 5458c2ecf20Sopenharmony_ci doc200x_write_control(doc, CDSN_CTRL_CE); 5468c2ecf20Sopenharmony_ci if (DoC_is_2000(doc)) 5478c2ecf20Sopenharmony_ci doc2000_writebuf(this, instr->ctx.data.buf.out, 5488c2ecf20Sopenharmony_ci instr->ctx.data.len); 5498c2ecf20Sopenharmony_ci else 5508c2ecf20Sopenharmony_ci doc2001_writebuf(this, instr->ctx.data.buf.out, 5518c2ecf20Sopenharmony_ci instr->ctx.data.len); 5528c2ecf20Sopenharmony_ci break; 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci case NAND_OP_WAITRDY_INSTR: 5558c2ecf20Sopenharmony_ci DoC_WaitReady(doc); 5568c2ecf20Sopenharmony_ci break; 5578c2ecf20Sopenharmony_ci } 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci if (instr->delay_ns) 5608c2ecf20Sopenharmony_ci ndelay(instr->delay_ns); 5618c2ecf20Sopenharmony_ci} 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_cistatic int doc200x_exec_op(struct nand_chip *this, 5648c2ecf20Sopenharmony_ci const struct nand_operation *op, 5658c2ecf20Sopenharmony_ci bool check_only) 5668c2ecf20Sopenharmony_ci{ 5678c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 5688c2ecf20Sopenharmony_ci unsigned int i; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci if (check_only) 5718c2ecf20Sopenharmony_ci return true; 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci doc->curchip = op->cs % doc->chips_per_floor; 5748c2ecf20Sopenharmony_ci doc->curfloor = op->cs / doc->chips_per_floor; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci WriteDOC(doc->curfloor, doc->virtadr, FloorSelect); 5778c2ecf20Sopenharmony_ci WriteDOC(doc->curchip, doc->virtadr, CDSNDeviceSelect); 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci /* Assert CE pin */ 5808c2ecf20Sopenharmony_ci doc200x_write_control(doc, CDSN_CTRL_CE); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci for (i = 0; i < op->ninstrs; i++) 5838c2ecf20Sopenharmony_ci doc200x_exec_instr(this, &op->instrs[i]); 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci /* De-assert CE pin */ 5868c2ecf20Sopenharmony_ci doc200x_write_control(doc, 0); 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci return 0; 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_cistatic void doc2001plus_write_pipe_term(struct doc_priv *doc) 5928c2ecf20Sopenharmony_ci{ 5938c2ecf20Sopenharmony_ci WriteDOC(0x00, doc->virtadr, Mplus_WritePipeTerm); 5948c2ecf20Sopenharmony_ci WriteDOC(0x00, doc->virtadr, Mplus_WritePipeTerm); 5958c2ecf20Sopenharmony_ci} 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_cistatic void doc2001plus_exec_instr(struct nand_chip *this, 5988c2ecf20Sopenharmony_ci const struct nand_op_instr *instr) 5998c2ecf20Sopenharmony_ci{ 6008c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 6018c2ecf20Sopenharmony_ci unsigned int i; 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci switch (instr->type) { 6048c2ecf20Sopenharmony_ci case NAND_OP_CMD_INSTR: 6058c2ecf20Sopenharmony_ci WriteDOC(instr->ctx.cmd.opcode, doc->virtadr, Mplus_FlashCmd); 6068c2ecf20Sopenharmony_ci doc2001plus_write_pipe_term(doc); 6078c2ecf20Sopenharmony_ci break; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci case NAND_OP_ADDR_INSTR: 6108c2ecf20Sopenharmony_ci for (i = 0; i < instr->ctx.addr.naddrs; i++) { 6118c2ecf20Sopenharmony_ci u8 addr = instr->ctx.addr.addrs[i]; 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci WriteDOC(addr, doc->virtadr, Mplus_FlashAddress); 6148c2ecf20Sopenharmony_ci } 6158c2ecf20Sopenharmony_ci doc2001plus_write_pipe_term(doc); 6168c2ecf20Sopenharmony_ci /* deassert ALE */ 6178c2ecf20Sopenharmony_ci WriteDOC(0, doc->virtadr, Mplus_FlashControl); 6188c2ecf20Sopenharmony_ci break; 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci case NAND_OP_DATA_IN_INSTR: 6218c2ecf20Sopenharmony_ci doc2001plus_readbuf(this, instr->ctx.data.buf.in, 6228c2ecf20Sopenharmony_ci instr->ctx.data.len); 6238c2ecf20Sopenharmony_ci break; 6248c2ecf20Sopenharmony_ci case NAND_OP_DATA_OUT_INSTR: 6258c2ecf20Sopenharmony_ci doc2001plus_writebuf(this, instr->ctx.data.buf.out, 6268c2ecf20Sopenharmony_ci instr->ctx.data.len); 6278c2ecf20Sopenharmony_ci doc2001plus_write_pipe_term(doc); 6288c2ecf20Sopenharmony_ci break; 6298c2ecf20Sopenharmony_ci case NAND_OP_WAITRDY_INSTR: 6308c2ecf20Sopenharmony_ci DoC_WaitReady(doc); 6318c2ecf20Sopenharmony_ci break; 6328c2ecf20Sopenharmony_ci } 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci if (instr->delay_ns) 6358c2ecf20Sopenharmony_ci ndelay(instr->delay_ns); 6368c2ecf20Sopenharmony_ci} 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_cistatic int doc2001plus_exec_op(struct nand_chip *this, 6398c2ecf20Sopenharmony_ci const struct nand_operation *op, 6408c2ecf20Sopenharmony_ci bool check_only) 6418c2ecf20Sopenharmony_ci{ 6428c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 6438c2ecf20Sopenharmony_ci unsigned int i; 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ci if (check_only) 6468c2ecf20Sopenharmony_ci return true; 6478c2ecf20Sopenharmony_ci 6488c2ecf20Sopenharmony_ci doc->curchip = op->cs % doc->chips_per_floor; 6498c2ecf20Sopenharmony_ci doc->curfloor = op->cs / doc->chips_per_floor; 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci /* Assert ChipEnable and deassert WriteProtect */ 6528c2ecf20Sopenharmony_ci WriteDOC(DOC_FLASH_CE, doc->virtadr, Mplus_FlashSelect); 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ci for (i = 0; i < op->ninstrs; i++) 6558c2ecf20Sopenharmony_ci doc2001plus_exec_instr(this, &op->instrs[i]); 6568c2ecf20Sopenharmony_ci 6578c2ecf20Sopenharmony_ci /* De-assert ChipEnable */ 6588c2ecf20Sopenharmony_ci WriteDOC(0, doc->virtadr, Mplus_FlashSelect); 6598c2ecf20Sopenharmony_ci 6608c2ecf20Sopenharmony_ci return 0; 6618c2ecf20Sopenharmony_ci} 6628c2ecf20Sopenharmony_ci 6638c2ecf20Sopenharmony_cistatic void doc200x_enable_hwecc(struct nand_chip *this, int mode) 6648c2ecf20Sopenharmony_ci{ 6658c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 6668c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci /* Prime the ECC engine */ 6698c2ecf20Sopenharmony_ci switch (mode) { 6708c2ecf20Sopenharmony_ci case NAND_ECC_READ: 6718c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_RESET, docptr, ECCConf); 6728c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_EN, docptr, ECCConf); 6738c2ecf20Sopenharmony_ci break; 6748c2ecf20Sopenharmony_ci case NAND_ECC_WRITE: 6758c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_RESET, docptr, ECCConf); 6768c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); 6778c2ecf20Sopenharmony_ci break; 6788c2ecf20Sopenharmony_ci } 6798c2ecf20Sopenharmony_ci} 6808c2ecf20Sopenharmony_ci 6818c2ecf20Sopenharmony_cistatic void doc2001plus_enable_hwecc(struct nand_chip *this, int mode) 6828c2ecf20Sopenharmony_ci{ 6838c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 6848c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 6858c2ecf20Sopenharmony_ci 6868c2ecf20Sopenharmony_ci /* Prime the ECC engine */ 6878c2ecf20Sopenharmony_ci switch (mode) { 6888c2ecf20Sopenharmony_ci case NAND_ECC_READ: 6898c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); 6908c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf); 6918c2ecf20Sopenharmony_ci break; 6928c2ecf20Sopenharmony_ci case NAND_ECC_WRITE: 6938c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); 6948c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf); 6958c2ecf20Sopenharmony_ci break; 6968c2ecf20Sopenharmony_ci } 6978c2ecf20Sopenharmony_ci} 6988c2ecf20Sopenharmony_ci 6998c2ecf20Sopenharmony_ci/* This code is only called on write */ 7008c2ecf20Sopenharmony_cistatic int doc200x_calculate_ecc(struct nand_chip *this, const u_char *dat, 7018c2ecf20Sopenharmony_ci unsigned char *ecc_code) 7028c2ecf20Sopenharmony_ci{ 7038c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 7048c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 7058c2ecf20Sopenharmony_ci int i; 7068c2ecf20Sopenharmony_ci int emptymatch = 1; 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_ci /* flush the pipeline */ 7098c2ecf20Sopenharmony_ci if (DoC_is_2000(doc)) { 7108c2ecf20Sopenharmony_ci WriteDOC(doc->CDSNControl & ~CDSN_CTRL_FLASH_IO, docptr, CDSNControl); 7118c2ecf20Sopenharmony_ci WriteDOC(0, docptr, 2k_CDSN_IO); 7128c2ecf20Sopenharmony_ci WriteDOC(0, docptr, 2k_CDSN_IO); 7138c2ecf20Sopenharmony_ci WriteDOC(0, docptr, 2k_CDSN_IO); 7148c2ecf20Sopenharmony_ci WriteDOC(doc->CDSNControl, docptr, CDSNControl); 7158c2ecf20Sopenharmony_ci } else if (DoC_is_MillenniumPlus(doc)) { 7168c2ecf20Sopenharmony_ci WriteDOC(0, docptr, Mplus_NOP); 7178c2ecf20Sopenharmony_ci WriteDOC(0, docptr, Mplus_NOP); 7188c2ecf20Sopenharmony_ci WriteDOC(0, docptr, Mplus_NOP); 7198c2ecf20Sopenharmony_ci } else { 7208c2ecf20Sopenharmony_ci WriteDOC(0, docptr, NOP); 7218c2ecf20Sopenharmony_ci WriteDOC(0, docptr, NOP); 7228c2ecf20Sopenharmony_ci WriteDOC(0, docptr, NOP); 7238c2ecf20Sopenharmony_ci } 7248c2ecf20Sopenharmony_ci 7258c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) { 7268c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) 7278c2ecf20Sopenharmony_ci ecc_code[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i); 7288c2ecf20Sopenharmony_ci else 7298c2ecf20Sopenharmony_ci ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); 7308c2ecf20Sopenharmony_ci if (ecc_code[i] != empty_write_ecc[i]) 7318c2ecf20Sopenharmony_ci emptymatch = 0; 7328c2ecf20Sopenharmony_ci } 7338c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) 7348c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); 7358c2ecf20Sopenharmony_ci else 7368c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_DIS, docptr, ECCConf); 7378c2ecf20Sopenharmony_ci#if 0 7388c2ecf20Sopenharmony_ci /* If emptymatch=1, we might have an all-0xff data buffer. Check. */ 7398c2ecf20Sopenharmony_ci if (emptymatch) { 7408c2ecf20Sopenharmony_ci /* Note: this somewhat expensive test should not be triggered 7418c2ecf20Sopenharmony_ci often. It could be optimized away by examining the data in 7428c2ecf20Sopenharmony_ci the writebuf routine, and remembering the result. */ 7438c2ecf20Sopenharmony_ci for (i = 0; i < 512; i++) { 7448c2ecf20Sopenharmony_ci if (dat[i] == 0xff) 7458c2ecf20Sopenharmony_ci continue; 7468c2ecf20Sopenharmony_ci emptymatch = 0; 7478c2ecf20Sopenharmony_ci break; 7488c2ecf20Sopenharmony_ci } 7498c2ecf20Sopenharmony_ci } 7508c2ecf20Sopenharmony_ci /* If emptymatch still =1, we do have an all-0xff data buffer. 7518c2ecf20Sopenharmony_ci Return all-0xff ecc value instead of the computed one, so 7528c2ecf20Sopenharmony_ci it'll look just like a freshly-erased page. */ 7538c2ecf20Sopenharmony_ci if (emptymatch) 7548c2ecf20Sopenharmony_ci memset(ecc_code, 0xff, 6); 7558c2ecf20Sopenharmony_ci#endif 7568c2ecf20Sopenharmony_ci return 0; 7578c2ecf20Sopenharmony_ci} 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_cistatic int doc200x_correct_data(struct nand_chip *this, u_char *dat, 7608c2ecf20Sopenharmony_ci u_char *read_ecc, u_char *isnull) 7618c2ecf20Sopenharmony_ci{ 7628c2ecf20Sopenharmony_ci int i, ret = 0; 7638c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 7648c2ecf20Sopenharmony_ci void __iomem *docptr = doc->virtadr; 7658c2ecf20Sopenharmony_ci uint8_t calc_ecc[6]; 7668c2ecf20Sopenharmony_ci volatile u_char dummy; 7678c2ecf20Sopenharmony_ci 7688c2ecf20Sopenharmony_ci /* flush the pipeline */ 7698c2ecf20Sopenharmony_ci if (DoC_is_2000(doc)) { 7708c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, 2k_ECCStatus); 7718c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, 2k_ECCStatus); 7728c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, 2k_ECCStatus); 7738c2ecf20Sopenharmony_ci } else if (DoC_is_MillenniumPlus(doc)) { 7748c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, Mplus_ECCConf); 7758c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, Mplus_ECCConf); 7768c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, Mplus_ECCConf); 7778c2ecf20Sopenharmony_ci } else { 7788c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, ECCConf); 7798c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, ECCConf); 7808c2ecf20Sopenharmony_ci dummy = ReadDOC(docptr, ECCConf); 7818c2ecf20Sopenharmony_ci } 7828c2ecf20Sopenharmony_ci 7838c2ecf20Sopenharmony_ci /* Error occurred ? */ 7848c2ecf20Sopenharmony_ci if (dummy & 0x80) { 7858c2ecf20Sopenharmony_ci for (i = 0; i < 6; i++) { 7868c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) 7878c2ecf20Sopenharmony_ci calc_ecc[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i); 7888c2ecf20Sopenharmony_ci else 7898c2ecf20Sopenharmony_ci calc_ecc[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); 7908c2ecf20Sopenharmony_ci } 7918c2ecf20Sopenharmony_ci 7928c2ecf20Sopenharmony_ci ret = doc_ecc_decode(doc->rs_decoder, dat, calc_ecc); 7938c2ecf20Sopenharmony_ci if (ret > 0) 7948c2ecf20Sopenharmony_ci pr_err("doc200x_correct_data corrected %d errors\n", 7958c2ecf20Sopenharmony_ci ret); 7968c2ecf20Sopenharmony_ci } 7978c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) 7988c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); 7998c2ecf20Sopenharmony_ci else 8008c2ecf20Sopenharmony_ci WriteDOC(DOC_ECC_DIS, docptr, ECCConf); 8018c2ecf20Sopenharmony_ci if (no_ecc_failures && mtd_is_eccerr(ret)) { 8028c2ecf20Sopenharmony_ci pr_err("suppressing ECC failure\n"); 8038c2ecf20Sopenharmony_ci ret = 0; 8048c2ecf20Sopenharmony_ci } 8058c2ecf20Sopenharmony_ci return ret; 8068c2ecf20Sopenharmony_ci} 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ci//u_char mydatabuf[528]; 8098c2ecf20Sopenharmony_ci 8108c2ecf20Sopenharmony_cistatic int doc200x_ooblayout_ecc(struct mtd_info *mtd, int section, 8118c2ecf20Sopenharmony_ci struct mtd_oob_region *oobregion) 8128c2ecf20Sopenharmony_ci{ 8138c2ecf20Sopenharmony_ci if (section) 8148c2ecf20Sopenharmony_ci return -ERANGE; 8158c2ecf20Sopenharmony_ci 8168c2ecf20Sopenharmony_ci oobregion->offset = 0; 8178c2ecf20Sopenharmony_ci oobregion->length = 6; 8188c2ecf20Sopenharmony_ci 8198c2ecf20Sopenharmony_ci return 0; 8208c2ecf20Sopenharmony_ci} 8218c2ecf20Sopenharmony_ci 8228c2ecf20Sopenharmony_cistatic int doc200x_ooblayout_free(struct mtd_info *mtd, int section, 8238c2ecf20Sopenharmony_ci struct mtd_oob_region *oobregion) 8248c2ecf20Sopenharmony_ci{ 8258c2ecf20Sopenharmony_ci if (section > 1) 8268c2ecf20Sopenharmony_ci return -ERANGE; 8278c2ecf20Sopenharmony_ci 8288c2ecf20Sopenharmony_ci /* 8298c2ecf20Sopenharmony_ci * The strange out-of-order free bytes definition is a (possibly 8308c2ecf20Sopenharmony_ci * unneeded) attempt to retain compatibility. It used to read: 8318c2ecf20Sopenharmony_ci * .oobfree = { {8, 8} } 8328c2ecf20Sopenharmony_ci * Since that leaves two bytes unusable, it was changed. But the 8338c2ecf20Sopenharmony_ci * following scheme might affect existing jffs2 installs by moving the 8348c2ecf20Sopenharmony_ci * cleanmarker: 8358c2ecf20Sopenharmony_ci * .oobfree = { {6, 10} } 8368c2ecf20Sopenharmony_ci * jffs2 seems to handle the above gracefully, but the current scheme 8378c2ecf20Sopenharmony_ci * seems safer. The only problem with it is that any code retrieving 8388c2ecf20Sopenharmony_ci * free bytes position must be able to handle out-of-order segments. 8398c2ecf20Sopenharmony_ci */ 8408c2ecf20Sopenharmony_ci if (!section) { 8418c2ecf20Sopenharmony_ci oobregion->offset = 8; 8428c2ecf20Sopenharmony_ci oobregion->length = 8; 8438c2ecf20Sopenharmony_ci } else { 8448c2ecf20Sopenharmony_ci oobregion->offset = 6; 8458c2ecf20Sopenharmony_ci oobregion->length = 2; 8468c2ecf20Sopenharmony_ci } 8478c2ecf20Sopenharmony_ci 8488c2ecf20Sopenharmony_ci return 0; 8498c2ecf20Sopenharmony_ci} 8508c2ecf20Sopenharmony_ci 8518c2ecf20Sopenharmony_cistatic const struct mtd_ooblayout_ops doc200x_ooblayout_ops = { 8528c2ecf20Sopenharmony_ci .ecc = doc200x_ooblayout_ecc, 8538c2ecf20Sopenharmony_ci .free = doc200x_ooblayout_free, 8548c2ecf20Sopenharmony_ci}; 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_ci/* Find the (I)NFTL Media Header, and optionally also the mirror media header. 8578c2ecf20Sopenharmony_ci On successful return, buf will contain a copy of the media header for 8588c2ecf20Sopenharmony_ci further processing. id is the string to scan for, and will presumably be 8598c2ecf20Sopenharmony_ci either "ANAND" or "BNAND". If findmirror=1, also look for the mirror media 8608c2ecf20Sopenharmony_ci header. The page #s of the found media headers are placed in mh0_page and 8618c2ecf20Sopenharmony_ci mh1_page in the DOC private structure. */ 8628c2ecf20Sopenharmony_cistatic int __init find_media_headers(struct mtd_info *mtd, u_char *buf, const char *id, int findmirror) 8638c2ecf20Sopenharmony_ci{ 8648c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 8658c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 8668c2ecf20Sopenharmony_ci unsigned offs; 8678c2ecf20Sopenharmony_ci int ret; 8688c2ecf20Sopenharmony_ci size_t retlen; 8698c2ecf20Sopenharmony_ci 8708c2ecf20Sopenharmony_ci for (offs = 0; offs < mtd->size; offs += mtd->erasesize) { 8718c2ecf20Sopenharmony_ci ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf); 8728c2ecf20Sopenharmony_ci if (retlen != mtd->writesize) 8738c2ecf20Sopenharmony_ci continue; 8748c2ecf20Sopenharmony_ci if (ret) { 8758c2ecf20Sopenharmony_ci pr_warn("ECC error scanning DOC at 0x%x\n", offs); 8768c2ecf20Sopenharmony_ci } 8778c2ecf20Sopenharmony_ci if (memcmp(buf, id, 6)) 8788c2ecf20Sopenharmony_ci continue; 8798c2ecf20Sopenharmony_ci pr_info("Found DiskOnChip %s Media Header at 0x%x\n", id, offs); 8808c2ecf20Sopenharmony_ci if (doc->mh0_page == -1) { 8818c2ecf20Sopenharmony_ci doc->mh0_page = offs >> this->page_shift; 8828c2ecf20Sopenharmony_ci if (!findmirror) 8838c2ecf20Sopenharmony_ci return 1; 8848c2ecf20Sopenharmony_ci continue; 8858c2ecf20Sopenharmony_ci } 8868c2ecf20Sopenharmony_ci doc->mh1_page = offs >> this->page_shift; 8878c2ecf20Sopenharmony_ci return 2; 8888c2ecf20Sopenharmony_ci } 8898c2ecf20Sopenharmony_ci if (doc->mh0_page == -1) { 8908c2ecf20Sopenharmony_ci pr_warn("DiskOnChip %s Media Header not found.\n", id); 8918c2ecf20Sopenharmony_ci return 0; 8928c2ecf20Sopenharmony_ci } 8938c2ecf20Sopenharmony_ci /* Only one mediaheader was found. We want buf to contain a 8948c2ecf20Sopenharmony_ci mediaheader on return, so we'll have to re-read the one we found. */ 8958c2ecf20Sopenharmony_ci offs = doc->mh0_page << this->page_shift; 8968c2ecf20Sopenharmony_ci ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf); 8978c2ecf20Sopenharmony_ci if (retlen != mtd->writesize) { 8988c2ecf20Sopenharmony_ci /* Insanity. Give up. */ 8998c2ecf20Sopenharmony_ci pr_err("Read DiskOnChip Media Header once, but can't reread it???\n"); 9008c2ecf20Sopenharmony_ci return 0; 9018c2ecf20Sopenharmony_ci } 9028c2ecf20Sopenharmony_ci return 1; 9038c2ecf20Sopenharmony_ci} 9048c2ecf20Sopenharmony_ci 9058c2ecf20Sopenharmony_cistatic inline int __init nftl_partscan(struct mtd_info *mtd, struct mtd_partition *parts) 9068c2ecf20Sopenharmony_ci{ 9078c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 9088c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 9098c2ecf20Sopenharmony_ci struct nand_memory_organization *memorg; 9108c2ecf20Sopenharmony_ci int ret = 0; 9118c2ecf20Sopenharmony_ci u_char *buf; 9128c2ecf20Sopenharmony_ci struct NFTLMediaHeader *mh; 9138c2ecf20Sopenharmony_ci const unsigned psize = 1 << this->page_shift; 9148c2ecf20Sopenharmony_ci int numparts = 0; 9158c2ecf20Sopenharmony_ci unsigned blocks, maxblocks; 9168c2ecf20Sopenharmony_ci int offs, numheaders; 9178c2ecf20Sopenharmony_ci 9188c2ecf20Sopenharmony_ci memorg = nanddev_get_memorg(&this->base); 9198c2ecf20Sopenharmony_ci 9208c2ecf20Sopenharmony_ci buf = kmalloc(mtd->writesize, GFP_KERNEL); 9218c2ecf20Sopenharmony_ci if (!buf) { 9228c2ecf20Sopenharmony_ci return 0; 9238c2ecf20Sopenharmony_ci } 9248c2ecf20Sopenharmony_ci if (!(numheaders = find_media_headers(mtd, buf, "ANAND", 1))) 9258c2ecf20Sopenharmony_ci goto out; 9268c2ecf20Sopenharmony_ci mh = (struct NFTLMediaHeader *)buf; 9278c2ecf20Sopenharmony_ci 9288c2ecf20Sopenharmony_ci le16_to_cpus(&mh->NumEraseUnits); 9298c2ecf20Sopenharmony_ci le16_to_cpus(&mh->FirstPhysicalEUN); 9308c2ecf20Sopenharmony_ci le32_to_cpus(&mh->FormattedSize); 9318c2ecf20Sopenharmony_ci 9328c2ecf20Sopenharmony_ci pr_info(" DataOrgID = %s\n" 9338c2ecf20Sopenharmony_ci " NumEraseUnits = %d\n" 9348c2ecf20Sopenharmony_ci " FirstPhysicalEUN = %d\n" 9358c2ecf20Sopenharmony_ci " FormattedSize = %d\n" 9368c2ecf20Sopenharmony_ci " UnitSizeFactor = %d\n", 9378c2ecf20Sopenharmony_ci mh->DataOrgID, mh->NumEraseUnits, 9388c2ecf20Sopenharmony_ci mh->FirstPhysicalEUN, mh->FormattedSize, 9398c2ecf20Sopenharmony_ci mh->UnitSizeFactor); 9408c2ecf20Sopenharmony_ci 9418c2ecf20Sopenharmony_ci blocks = mtd->size >> this->phys_erase_shift; 9428c2ecf20Sopenharmony_ci maxblocks = min(32768U, mtd->erasesize - psize); 9438c2ecf20Sopenharmony_ci 9448c2ecf20Sopenharmony_ci if (mh->UnitSizeFactor == 0x00) { 9458c2ecf20Sopenharmony_ci /* Auto-determine UnitSizeFactor. The constraints are: 9468c2ecf20Sopenharmony_ci - There can be at most 32768 virtual blocks. 9478c2ecf20Sopenharmony_ci - There can be at most (virtual block size - page size) 9488c2ecf20Sopenharmony_ci virtual blocks (because MediaHeader+BBT must fit in 1). 9498c2ecf20Sopenharmony_ci */ 9508c2ecf20Sopenharmony_ci mh->UnitSizeFactor = 0xff; 9518c2ecf20Sopenharmony_ci while (blocks > maxblocks) { 9528c2ecf20Sopenharmony_ci blocks >>= 1; 9538c2ecf20Sopenharmony_ci maxblocks = min(32768U, (maxblocks << 1) + psize); 9548c2ecf20Sopenharmony_ci mh->UnitSizeFactor--; 9558c2ecf20Sopenharmony_ci } 9568c2ecf20Sopenharmony_ci pr_warn("UnitSizeFactor=0x00 detected. Correct value is assumed to be 0x%02x.\n", mh->UnitSizeFactor); 9578c2ecf20Sopenharmony_ci } 9588c2ecf20Sopenharmony_ci 9598c2ecf20Sopenharmony_ci /* NOTE: The lines below modify internal variables of the NAND and MTD 9608c2ecf20Sopenharmony_ci layers; variables with have already been configured by nand_scan. 9618c2ecf20Sopenharmony_ci Unfortunately, we didn't know before this point what these values 9628c2ecf20Sopenharmony_ci should be. Thus, this code is somewhat dependent on the exact 9638c2ecf20Sopenharmony_ci implementation of the NAND layer. */ 9648c2ecf20Sopenharmony_ci if (mh->UnitSizeFactor != 0xff) { 9658c2ecf20Sopenharmony_ci this->bbt_erase_shift += (0xff - mh->UnitSizeFactor); 9668c2ecf20Sopenharmony_ci memorg->pages_per_eraseblock <<= (0xff - mh->UnitSizeFactor); 9678c2ecf20Sopenharmony_ci mtd->erasesize <<= (0xff - mh->UnitSizeFactor); 9688c2ecf20Sopenharmony_ci pr_info("Setting virtual erase size to %d\n", mtd->erasesize); 9698c2ecf20Sopenharmony_ci blocks = mtd->size >> this->bbt_erase_shift; 9708c2ecf20Sopenharmony_ci maxblocks = min(32768U, mtd->erasesize - psize); 9718c2ecf20Sopenharmony_ci } 9728c2ecf20Sopenharmony_ci 9738c2ecf20Sopenharmony_ci if (blocks > maxblocks) { 9748c2ecf20Sopenharmony_ci pr_err("UnitSizeFactor of 0x%02x is inconsistent with device size. Aborting.\n", mh->UnitSizeFactor); 9758c2ecf20Sopenharmony_ci goto out; 9768c2ecf20Sopenharmony_ci } 9778c2ecf20Sopenharmony_ci 9788c2ecf20Sopenharmony_ci /* Skip past the media headers. */ 9798c2ecf20Sopenharmony_ci offs = max(doc->mh0_page, doc->mh1_page); 9808c2ecf20Sopenharmony_ci offs <<= this->page_shift; 9818c2ecf20Sopenharmony_ci offs += mtd->erasesize; 9828c2ecf20Sopenharmony_ci 9838c2ecf20Sopenharmony_ci if (show_firmware_partition == 1) { 9848c2ecf20Sopenharmony_ci parts[0].name = " DiskOnChip Firmware / Media Header partition"; 9858c2ecf20Sopenharmony_ci parts[0].offset = 0; 9868c2ecf20Sopenharmony_ci parts[0].size = offs; 9878c2ecf20Sopenharmony_ci numparts = 1; 9888c2ecf20Sopenharmony_ci } 9898c2ecf20Sopenharmony_ci 9908c2ecf20Sopenharmony_ci parts[numparts].name = " DiskOnChip BDTL partition"; 9918c2ecf20Sopenharmony_ci parts[numparts].offset = offs; 9928c2ecf20Sopenharmony_ci parts[numparts].size = (mh->NumEraseUnits - numheaders) << this->bbt_erase_shift; 9938c2ecf20Sopenharmony_ci 9948c2ecf20Sopenharmony_ci offs += parts[numparts].size; 9958c2ecf20Sopenharmony_ci numparts++; 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci if (offs < mtd->size) { 9988c2ecf20Sopenharmony_ci parts[numparts].name = " DiskOnChip Remainder partition"; 9998c2ecf20Sopenharmony_ci parts[numparts].offset = offs; 10008c2ecf20Sopenharmony_ci parts[numparts].size = mtd->size - offs; 10018c2ecf20Sopenharmony_ci numparts++; 10028c2ecf20Sopenharmony_ci } 10038c2ecf20Sopenharmony_ci 10048c2ecf20Sopenharmony_ci ret = numparts; 10058c2ecf20Sopenharmony_ci out: 10068c2ecf20Sopenharmony_ci kfree(buf); 10078c2ecf20Sopenharmony_ci return ret; 10088c2ecf20Sopenharmony_ci} 10098c2ecf20Sopenharmony_ci 10108c2ecf20Sopenharmony_ci/* This is a stripped-down copy of the code in inftlmount.c */ 10118c2ecf20Sopenharmony_cistatic inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partition *parts) 10128c2ecf20Sopenharmony_ci{ 10138c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 10148c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 10158c2ecf20Sopenharmony_ci int ret = 0; 10168c2ecf20Sopenharmony_ci u_char *buf; 10178c2ecf20Sopenharmony_ci struct INFTLMediaHeader *mh; 10188c2ecf20Sopenharmony_ci struct INFTLPartition *ip; 10198c2ecf20Sopenharmony_ci int numparts = 0; 10208c2ecf20Sopenharmony_ci int blocks; 10218c2ecf20Sopenharmony_ci int vshift, lastvunit = 0; 10228c2ecf20Sopenharmony_ci int i; 10238c2ecf20Sopenharmony_ci int end = mtd->size; 10248c2ecf20Sopenharmony_ci 10258c2ecf20Sopenharmony_ci if (inftl_bbt_write) 10268c2ecf20Sopenharmony_ci end -= (INFTL_BBT_RESERVED_BLOCKS << this->phys_erase_shift); 10278c2ecf20Sopenharmony_ci 10288c2ecf20Sopenharmony_ci buf = kmalloc(mtd->writesize, GFP_KERNEL); 10298c2ecf20Sopenharmony_ci if (!buf) { 10308c2ecf20Sopenharmony_ci return 0; 10318c2ecf20Sopenharmony_ci } 10328c2ecf20Sopenharmony_ci 10338c2ecf20Sopenharmony_ci if (!find_media_headers(mtd, buf, "BNAND", 0)) 10348c2ecf20Sopenharmony_ci goto out; 10358c2ecf20Sopenharmony_ci doc->mh1_page = doc->mh0_page + (4096 >> this->page_shift); 10368c2ecf20Sopenharmony_ci mh = (struct INFTLMediaHeader *)buf; 10378c2ecf20Sopenharmony_ci 10388c2ecf20Sopenharmony_ci le32_to_cpus(&mh->NoOfBootImageBlocks); 10398c2ecf20Sopenharmony_ci le32_to_cpus(&mh->NoOfBinaryPartitions); 10408c2ecf20Sopenharmony_ci le32_to_cpus(&mh->NoOfBDTLPartitions); 10418c2ecf20Sopenharmony_ci le32_to_cpus(&mh->BlockMultiplierBits); 10428c2ecf20Sopenharmony_ci le32_to_cpus(&mh->FormatFlags); 10438c2ecf20Sopenharmony_ci le32_to_cpus(&mh->PercentUsed); 10448c2ecf20Sopenharmony_ci 10458c2ecf20Sopenharmony_ci pr_info(" bootRecordID = %s\n" 10468c2ecf20Sopenharmony_ci " NoOfBootImageBlocks = %d\n" 10478c2ecf20Sopenharmony_ci " NoOfBinaryPartitions = %d\n" 10488c2ecf20Sopenharmony_ci " NoOfBDTLPartitions = %d\n" 10498c2ecf20Sopenharmony_ci " BlockMultiplierBits = %d\n" 10508c2ecf20Sopenharmony_ci " FormatFlgs = %d\n" 10518c2ecf20Sopenharmony_ci " OsakVersion = %d.%d.%d.%d\n" 10528c2ecf20Sopenharmony_ci " PercentUsed = %d\n", 10538c2ecf20Sopenharmony_ci mh->bootRecordID, mh->NoOfBootImageBlocks, 10548c2ecf20Sopenharmony_ci mh->NoOfBinaryPartitions, 10558c2ecf20Sopenharmony_ci mh->NoOfBDTLPartitions, 10568c2ecf20Sopenharmony_ci mh->BlockMultiplierBits, mh->FormatFlags, 10578c2ecf20Sopenharmony_ci ((unsigned char *) &mh->OsakVersion)[0] & 0xf, 10588c2ecf20Sopenharmony_ci ((unsigned char *) &mh->OsakVersion)[1] & 0xf, 10598c2ecf20Sopenharmony_ci ((unsigned char *) &mh->OsakVersion)[2] & 0xf, 10608c2ecf20Sopenharmony_ci ((unsigned char *) &mh->OsakVersion)[3] & 0xf, 10618c2ecf20Sopenharmony_ci mh->PercentUsed); 10628c2ecf20Sopenharmony_ci 10638c2ecf20Sopenharmony_ci vshift = this->phys_erase_shift + mh->BlockMultiplierBits; 10648c2ecf20Sopenharmony_ci 10658c2ecf20Sopenharmony_ci blocks = mtd->size >> vshift; 10668c2ecf20Sopenharmony_ci if (blocks > 32768) { 10678c2ecf20Sopenharmony_ci pr_err("BlockMultiplierBits=%d is inconsistent with device size. Aborting.\n", mh->BlockMultiplierBits); 10688c2ecf20Sopenharmony_ci goto out; 10698c2ecf20Sopenharmony_ci } 10708c2ecf20Sopenharmony_ci 10718c2ecf20Sopenharmony_ci blocks = doc->chips_per_floor << (this->chip_shift - this->phys_erase_shift); 10728c2ecf20Sopenharmony_ci if (inftl_bbt_write && (blocks > mtd->erasesize)) { 10738c2ecf20Sopenharmony_ci pr_err("Writeable BBTs spanning more than one erase block are not yet supported. FIX ME!\n"); 10748c2ecf20Sopenharmony_ci goto out; 10758c2ecf20Sopenharmony_ci } 10768c2ecf20Sopenharmony_ci 10778c2ecf20Sopenharmony_ci /* Scan the partitions */ 10788c2ecf20Sopenharmony_ci for (i = 0; (i < 4); i++) { 10798c2ecf20Sopenharmony_ci ip = &(mh->Partitions[i]); 10808c2ecf20Sopenharmony_ci le32_to_cpus(&ip->virtualUnits); 10818c2ecf20Sopenharmony_ci le32_to_cpus(&ip->firstUnit); 10828c2ecf20Sopenharmony_ci le32_to_cpus(&ip->lastUnit); 10838c2ecf20Sopenharmony_ci le32_to_cpus(&ip->flags); 10848c2ecf20Sopenharmony_ci le32_to_cpus(&ip->spareUnits); 10858c2ecf20Sopenharmony_ci le32_to_cpus(&ip->Reserved0); 10868c2ecf20Sopenharmony_ci 10878c2ecf20Sopenharmony_ci pr_info(" PARTITION[%d] ->\n" 10888c2ecf20Sopenharmony_ci " virtualUnits = %d\n" 10898c2ecf20Sopenharmony_ci " firstUnit = %d\n" 10908c2ecf20Sopenharmony_ci " lastUnit = %d\n" 10918c2ecf20Sopenharmony_ci " flags = 0x%x\n" 10928c2ecf20Sopenharmony_ci " spareUnits = %d\n", 10938c2ecf20Sopenharmony_ci i, ip->virtualUnits, ip->firstUnit, 10948c2ecf20Sopenharmony_ci ip->lastUnit, ip->flags, 10958c2ecf20Sopenharmony_ci ip->spareUnits); 10968c2ecf20Sopenharmony_ci 10978c2ecf20Sopenharmony_ci if ((show_firmware_partition == 1) && 10988c2ecf20Sopenharmony_ci (i == 0) && (ip->firstUnit > 0)) { 10998c2ecf20Sopenharmony_ci parts[0].name = " DiskOnChip IPL / Media Header partition"; 11008c2ecf20Sopenharmony_ci parts[0].offset = 0; 11018c2ecf20Sopenharmony_ci parts[0].size = mtd->erasesize * ip->firstUnit; 11028c2ecf20Sopenharmony_ci numparts = 1; 11038c2ecf20Sopenharmony_ci } 11048c2ecf20Sopenharmony_ci 11058c2ecf20Sopenharmony_ci if (ip->flags & INFTL_BINARY) 11068c2ecf20Sopenharmony_ci parts[numparts].name = " DiskOnChip BDK partition"; 11078c2ecf20Sopenharmony_ci else 11088c2ecf20Sopenharmony_ci parts[numparts].name = " DiskOnChip BDTL partition"; 11098c2ecf20Sopenharmony_ci parts[numparts].offset = ip->firstUnit << vshift; 11108c2ecf20Sopenharmony_ci parts[numparts].size = (1 + ip->lastUnit - ip->firstUnit) << vshift; 11118c2ecf20Sopenharmony_ci numparts++; 11128c2ecf20Sopenharmony_ci if (ip->lastUnit > lastvunit) 11138c2ecf20Sopenharmony_ci lastvunit = ip->lastUnit; 11148c2ecf20Sopenharmony_ci if (ip->flags & INFTL_LAST) 11158c2ecf20Sopenharmony_ci break; 11168c2ecf20Sopenharmony_ci } 11178c2ecf20Sopenharmony_ci lastvunit++; 11188c2ecf20Sopenharmony_ci if ((lastvunit << vshift) < end) { 11198c2ecf20Sopenharmony_ci parts[numparts].name = " DiskOnChip Remainder partition"; 11208c2ecf20Sopenharmony_ci parts[numparts].offset = lastvunit << vshift; 11218c2ecf20Sopenharmony_ci parts[numparts].size = end - parts[numparts].offset; 11228c2ecf20Sopenharmony_ci numparts++; 11238c2ecf20Sopenharmony_ci } 11248c2ecf20Sopenharmony_ci ret = numparts; 11258c2ecf20Sopenharmony_ci out: 11268c2ecf20Sopenharmony_ci kfree(buf); 11278c2ecf20Sopenharmony_ci return ret; 11288c2ecf20Sopenharmony_ci} 11298c2ecf20Sopenharmony_ci 11308c2ecf20Sopenharmony_cistatic int __init nftl_scan_bbt(struct mtd_info *mtd) 11318c2ecf20Sopenharmony_ci{ 11328c2ecf20Sopenharmony_ci int ret, numparts; 11338c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 11348c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 11358c2ecf20Sopenharmony_ci struct mtd_partition parts[2]; 11368c2ecf20Sopenharmony_ci 11378c2ecf20Sopenharmony_ci memset((char *)parts, 0, sizeof(parts)); 11388c2ecf20Sopenharmony_ci /* On NFTL, we have to find the media headers before we can read the 11398c2ecf20Sopenharmony_ci BBTs, since they're stored in the media header eraseblocks. */ 11408c2ecf20Sopenharmony_ci numparts = nftl_partscan(mtd, parts); 11418c2ecf20Sopenharmony_ci if (!numparts) 11428c2ecf20Sopenharmony_ci return -EIO; 11438c2ecf20Sopenharmony_ci this->bbt_td->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT | 11448c2ecf20Sopenharmony_ci NAND_BBT_SAVECONTENT | NAND_BBT_WRITE | 11458c2ecf20Sopenharmony_ci NAND_BBT_VERSION; 11468c2ecf20Sopenharmony_ci this->bbt_td->veroffs = 7; 11478c2ecf20Sopenharmony_ci this->bbt_td->pages[0] = doc->mh0_page + 1; 11488c2ecf20Sopenharmony_ci if (doc->mh1_page != -1) { 11498c2ecf20Sopenharmony_ci this->bbt_md->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT | 11508c2ecf20Sopenharmony_ci NAND_BBT_SAVECONTENT | NAND_BBT_WRITE | 11518c2ecf20Sopenharmony_ci NAND_BBT_VERSION; 11528c2ecf20Sopenharmony_ci this->bbt_md->veroffs = 7; 11538c2ecf20Sopenharmony_ci this->bbt_md->pages[0] = doc->mh1_page + 1; 11548c2ecf20Sopenharmony_ci } else { 11558c2ecf20Sopenharmony_ci this->bbt_md = NULL; 11568c2ecf20Sopenharmony_ci } 11578c2ecf20Sopenharmony_ci 11588c2ecf20Sopenharmony_ci ret = nand_create_bbt(this); 11598c2ecf20Sopenharmony_ci if (ret) 11608c2ecf20Sopenharmony_ci return ret; 11618c2ecf20Sopenharmony_ci 11628c2ecf20Sopenharmony_ci return mtd_device_register(mtd, parts, no_autopart ? 0 : numparts); 11638c2ecf20Sopenharmony_ci} 11648c2ecf20Sopenharmony_ci 11658c2ecf20Sopenharmony_cistatic int __init inftl_scan_bbt(struct mtd_info *mtd) 11668c2ecf20Sopenharmony_ci{ 11678c2ecf20Sopenharmony_ci int ret, numparts; 11688c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 11698c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 11708c2ecf20Sopenharmony_ci struct mtd_partition parts[5]; 11718c2ecf20Sopenharmony_ci 11728c2ecf20Sopenharmony_ci if (nanddev_ntargets(&this->base) > doc->chips_per_floor) { 11738c2ecf20Sopenharmony_ci pr_err("Multi-floor INFTL devices not yet supported.\n"); 11748c2ecf20Sopenharmony_ci return -EIO; 11758c2ecf20Sopenharmony_ci } 11768c2ecf20Sopenharmony_ci 11778c2ecf20Sopenharmony_ci if (DoC_is_MillenniumPlus(doc)) { 11788c2ecf20Sopenharmony_ci this->bbt_td->options = NAND_BBT_2BIT | NAND_BBT_ABSPAGE; 11798c2ecf20Sopenharmony_ci if (inftl_bbt_write) 11808c2ecf20Sopenharmony_ci this->bbt_td->options |= NAND_BBT_WRITE; 11818c2ecf20Sopenharmony_ci this->bbt_td->pages[0] = 2; 11828c2ecf20Sopenharmony_ci this->bbt_md = NULL; 11838c2ecf20Sopenharmony_ci } else { 11848c2ecf20Sopenharmony_ci this->bbt_td->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | NAND_BBT_VERSION; 11858c2ecf20Sopenharmony_ci if (inftl_bbt_write) 11868c2ecf20Sopenharmony_ci this->bbt_td->options |= NAND_BBT_WRITE; 11878c2ecf20Sopenharmony_ci this->bbt_td->offs = 8; 11888c2ecf20Sopenharmony_ci this->bbt_td->len = 8; 11898c2ecf20Sopenharmony_ci this->bbt_td->veroffs = 7; 11908c2ecf20Sopenharmony_ci this->bbt_td->maxblocks = INFTL_BBT_RESERVED_BLOCKS; 11918c2ecf20Sopenharmony_ci this->bbt_td->reserved_block_code = 0x01; 11928c2ecf20Sopenharmony_ci this->bbt_td->pattern = "MSYS_BBT"; 11938c2ecf20Sopenharmony_ci 11948c2ecf20Sopenharmony_ci this->bbt_md->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | NAND_BBT_VERSION; 11958c2ecf20Sopenharmony_ci if (inftl_bbt_write) 11968c2ecf20Sopenharmony_ci this->bbt_md->options |= NAND_BBT_WRITE; 11978c2ecf20Sopenharmony_ci this->bbt_md->offs = 8; 11988c2ecf20Sopenharmony_ci this->bbt_md->len = 8; 11998c2ecf20Sopenharmony_ci this->bbt_md->veroffs = 7; 12008c2ecf20Sopenharmony_ci this->bbt_md->maxblocks = INFTL_BBT_RESERVED_BLOCKS; 12018c2ecf20Sopenharmony_ci this->bbt_md->reserved_block_code = 0x01; 12028c2ecf20Sopenharmony_ci this->bbt_md->pattern = "TBB_SYSM"; 12038c2ecf20Sopenharmony_ci } 12048c2ecf20Sopenharmony_ci 12058c2ecf20Sopenharmony_ci ret = nand_create_bbt(this); 12068c2ecf20Sopenharmony_ci if (ret) 12078c2ecf20Sopenharmony_ci return ret; 12088c2ecf20Sopenharmony_ci 12098c2ecf20Sopenharmony_ci memset((char *)parts, 0, sizeof(parts)); 12108c2ecf20Sopenharmony_ci numparts = inftl_partscan(mtd, parts); 12118c2ecf20Sopenharmony_ci /* At least for now, require the INFTL Media Header. We could probably 12128c2ecf20Sopenharmony_ci do without it for non-INFTL use, since all it gives us is 12138c2ecf20Sopenharmony_ci autopartitioning, but I want to give it more thought. */ 12148c2ecf20Sopenharmony_ci if (!numparts) 12158c2ecf20Sopenharmony_ci return -EIO; 12168c2ecf20Sopenharmony_ci return mtd_device_register(mtd, parts, no_autopart ? 0 : numparts); 12178c2ecf20Sopenharmony_ci} 12188c2ecf20Sopenharmony_ci 12198c2ecf20Sopenharmony_cistatic inline int __init doc2000_init(struct mtd_info *mtd) 12208c2ecf20Sopenharmony_ci{ 12218c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 12228c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 12238c2ecf20Sopenharmony_ci 12248c2ecf20Sopenharmony_ci doc->late_init = nftl_scan_bbt; 12258c2ecf20Sopenharmony_ci 12268c2ecf20Sopenharmony_ci doc->CDSNControl = CDSN_CTRL_FLASH_IO | CDSN_CTRL_ECC_IO; 12278c2ecf20Sopenharmony_ci doc2000_count_chips(mtd); 12288c2ecf20Sopenharmony_ci mtd->name = "DiskOnChip 2000 (NFTL Model)"; 12298c2ecf20Sopenharmony_ci return (4 * doc->chips_per_floor); 12308c2ecf20Sopenharmony_ci} 12318c2ecf20Sopenharmony_ci 12328c2ecf20Sopenharmony_cistatic inline int __init doc2001_init(struct mtd_info *mtd) 12338c2ecf20Sopenharmony_ci{ 12348c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 12358c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 12368c2ecf20Sopenharmony_ci 12378c2ecf20Sopenharmony_ci ReadDOC(doc->virtadr, ChipID); 12388c2ecf20Sopenharmony_ci ReadDOC(doc->virtadr, ChipID); 12398c2ecf20Sopenharmony_ci ReadDOC(doc->virtadr, ChipID); 12408c2ecf20Sopenharmony_ci if (ReadDOC(doc->virtadr, ChipID) != DOC_ChipID_DocMil) { 12418c2ecf20Sopenharmony_ci /* It's not a Millennium; it's one of the newer 12428c2ecf20Sopenharmony_ci DiskOnChip 2000 units with a similar ASIC. 12438c2ecf20Sopenharmony_ci Treat it like a Millennium, except that it 12448c2ecf20Sopenharmony_ci can have multiple chips. */ 12458c2ecf20Sopenharmony_ci doc2000_count_chips(mtd); 12468c2ecf20Sopenharmony_ci mtd->name = "DiskOnChip 2000 (INFTL Model)"; 12478c2ecf20Sopenharmony_ci doc->late_init = inftl_scan_bbt; 12488c2ecf20Sopenharmony_ci return (4 * doc->chips_per_floor); 12498c2ecf20Sopenharmony_ci } else { 12508c2ecf20Sopenharmony_ci /* Bog-standard Millennium */ 12518c2ecf20Sopenharmony_ci doc->chips_per_floor = 1; 12528c2ecf20Sopenharmony_ci mtd->name = "DiskOnChip Millennium"; 12538c2ecf20Sopenharmony_ci doc->late_init = nftl_scan_bbt; 12548c2ecf20Sopenharmony_ci return 1; 12558c2ecf20Sopenharmony_ci } 12568c2ecf20Sopenharmony_ci} 12578c2ecf20Sopenharmony_ci 12588c2ecf20Sopenharmony_cistatic inline int __init doc2001plus_init(struct mtd_info *mtd) 12598c2ecf20Sopenharmony_ci{ 12608c2ecf20Sopenharmony_ci struct nand_chip *this = mtd_to_nand(mtd); 12618c2ecf20Sopenharmony_ci struct doc_priv *doc = nand_get_controller_data(this); 12628c2ecf20Sopenharmony_ci 12638c2ecf20Sopenharmony_ci doc->late_init = inftl_scan_bbt; 12648c2ecf20Sopenharmony_ci this->ecc.hwctl = doc2001plus_enable_hwecc; 12658c2ecf20Sopenharmony_ci 12668c2ecf20Sopenharmony_ci doc->chips_per_floor = 1; 12678c2ecf20Sopenharmony_ci mtd->name = "DiskOnChip Millennium Plus"; 12688c2ecf20Sopenharmony_ci 12698c2ecf20Sopenharmony_ci return 1; 12708c2ecf20Sopenharmony_ci} 12718c2ecf20Sopenharmony_ci 12728c2ecf20Sopenharmony_cistatic int doc200x_attach_chip(struct nand_chip *chip) 12738c2ecf20Sopenharmony_ci{ 12748c2ecf20Sopenharmony_ci if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) 12758c2ecf20Sopenharmony_ci return 0; 12768c2ecf20Sopenharmony_ci 12778c2ecf20Sopenharmony_ci chip->ecc.placement = NAND_ECC_PLACEMENT_INTERLEAVED; 12788c2ecf20Sopenharmony_ci chip->ecc.size = 512; 12798c2ecf20Sopenharmony_ci chip->ecc.bytes = 6; 12808c2ecf20Sopenharmony_ci chip->ecc.strength = 2; 12818c2ecf20Sopenharmony_ci chip->ecc.options = NAND_ECC_GENERIC_ERASED_CHECK; 12828c2ecf20Sopenharmony_ci chip->ecc.hwctl = doc200x_enable_hwecc; 12838c2ecf20Sopenharmony_ci chip->ecc.calculate = doc200x_calculate_ecc; 12848c2ecf20Sopenharmony_ci chip->ecc.correct = doc200x_correct_data; 12858c2ecf20Sopenharmony_ci 12868c2ecf20Sopenharmony_ci return 0; 12878c2ecf20Sopenharmony_ci} 12888c2ecf20Sopenharmony_ci 12898c2ecf20Sopenharmony_cistatic const struct nand_controller_ops doc200x_ops = { 12908c2ecf20Sopenharmony_ci .exec_op = doc200x_exec_op, 12918c2ecf20Sopenharmony_ci .attach_chip = doc200x_attach_chip, 12928c2ecf20Sopenharmony_ci}; 12938c2ecf20Sopenharmony_ci 12948c2ecf20Sopenharmony_cistatic const struct nand_controller_ops doc2001plus_ops = { 12958c2ecf20Sopenharmony_ci .exec_op = doc2001plus_exec_op, 12968c2ecf20Sopenharmony_ci .attach_chip = doc200x_attach_chip, 12978c2ecf20Sopenharmony_ci}; 12988c2ecf20Sopenharmony_ci 12998c2ecf20Sopenharmony_cistatic int __init doc_probe(unsigned long physadr) 13008c2ecf20Sopenharmony_ci{ 13018c2ecf20Sopenharmony_ci struct nand_chip *nand = NULL; 13028c2ecf20Sopenharmony_ci struct doc_priv *doc = NULL; 13038c2ecf20Sopenharmony_ci unsigned char ChipID; 13048c2ecf20Sopenharmony_ci struct mtd_info *mtd; 13058c2ecf20Sopenharmony_ci void __iomem *virtadr; 13068c2ecf20Sopenharmony_ci unsigned char save_control; 13078c2ecf20Sopenharmony_ci unsigned char tmp, tmpb, tmpc; 13088c2ecf20Sopenharmony_ci int reg, len, numchips; 13098c2ecf20Sopenharmony_ci int ret = 0; 13108c2ecf20Sopenharmony_ci 13118c2ecf20Sopenharmony_ci if (!request_mem_region(physadr, DOC_IOREMAP_LEN, "DiskOnChip")) 13128c2ecf20Sopenharmony_ci return -EBUSY; 13138c2ecf20Sopenharmony_ci virtadr = ioremap(physadr, DOC_IOREMAP_LEN); 13148c2ecf20Sopenharmony_ci if (!virtadr) { 13158c2ecf20Sopenharmony_ci pr_err("Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n", 13168c2ecf20Sopenharmony_ci DOC_IOREMAP_LEN, physadr); 13178c2ecf20Sopenharmony_ci ret = -EIO; 13188c2ecf20Sopenharmony_ci goto error_ioremap; 13198c2ecf20Sopenharmony_ci } 13208c2ecf20Sopenharmony_ci 13218c2ecf20Sopenharmony_ci /* It's not possible to cleanly detect the DiskOnChip - the 13228c2ecf20Sopenharmony_ci * bootup procedure will put the device into reset mode, and 13238c2ecf20Sopenharmony_ci * it's not possible to talk to it without actually writing 13248c2ecf20Sopenharmony_ci * to the DOCControl register. So we store the current contents 13258c2ecf20Sopenharmony_ci * of the DOCControl register's location, in case we later decide 13268c2ecf20Sopenharmony_ci * that it's not a DiskOnChip, and want to put it back how we 13278c2ecf20Sopenharmony_ci * found it. 13288c2ecf20Sopenharmony_ci */ 13298c2ecf20Sopenharmony_ci save_control = ReadDOC(virtadr, DOCControl); 13308c2ecf20Sopenharmony_ci 13318c2ecf20Sopenharmony_ci /* Reset the DiskOnChip ASIC */ 13328c2ecf20Sopenharmony_ci WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, virtadr, DOCControl); 13338c2ecf20Sopenharmony_ci WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, virtadr, DOCControl); 13348c2ecf20Sopenharmony_ci 13358c2ecf20Sopenharmony_ci /* Enable the DiskOnChip ASIC */ 13368c2ecf20Sopenharmony_ci WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, virtadr, DOCControl); 13378c2ecf20Sopenharmony_ci WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, virtadr, DOCControl); 13388c2ecf20Sopenharmony_ci 13398c2ecf20Sopenharmony_ci ChipID = ReadDOC(virtadr, ChipID); 13408c2ecf20Sopenharmony_ci 13418c2ecf20Sopenharmony_ci switch (ChipID) { 13428c2ecf20Sopenharmony_ci case DOC_ChipID_Doc2k: 13438c2ecf20Sopenharmony_ci reg = DoC_2k_ECCStatus; 13448c2ecf20Sopenharmony_ci break; 13458c2ecf20Sopenharmony_ci case DOC_ChipID_DocMil: 13468c2ecf20Sopenharmony_ci reg = DoC_ECCConf; 13478c2ecf20Sopenharmony_ci break; 13488c2ecf20Sopenharmony_ci case DOC_ChipID_DocMilPlus16: 13498c2ecf20Sopenharmony_ci case DOC_ChipID_DocMilPlus32: 13508c2ecf20Sopenharmony_ci case 0: 13518c2ecf20Sopenharmony_ci /* Possible Millennium Plus, need to do more checks */ 13528c2ecf20Sopenharmony_ci /* Possibly release from power down mode */ 13538c2ecf20Sopenharmony_ci for (tmp = 0; (tmp < 4); tmp++) 13548c2ecf20Sopenharmony_ci ReadDOC(virtadr, Mplus_Power); 13558c2ecf20Sopenharmony_ci 13568c2ecf20Sopenharmony_ci /* Reset the Millennium Plus ASIC */ 13578c2ecf20Sopenharmony_ci tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT; 13588c2ecf20Sopenharmony_ci WriteDOC(tmp, virtadr, Mplus_DOCControl); 13598c2ecf20Sopenharmony_ci WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm); 13608c2ecf20Sopenharmony_ci 13618c2ecf20Sopenharmony_ci usleep_range(1000, 2000); 13628c2ecf20Sopenharmony_ci /* Enable the Millennium Plus ASIC */ 13638c2ecf20Sopenharmony_ci tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT; 13648c2ecf20Sopenharmony_ci WriteDOC(tmp, virtadr, Mplus_DOCControl); 13658c2ecf20Sopenharmony_ci WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm); 13668c2ecf20Sopenharmony_ci usleep_range(1000, 2000); 13678c2ecf20Sopenharmony_ci 13688c2ecf20Sopenharmony_ci ChipID = ReadDOC(virtadr, ChipID); 13698c2ecf20Sopenharmony_ci 13708c2ecf20Sopenharmony_ci switch (ChipID) { 13718c2ecf20Sopenharmony_ci case DOC_ChipID_DocMilPlus16: 13728c2ecf20Sopenharmony_ci reg = DoC_Mplus_Toggle; 13738c2ecf20Sopenharmony_ci break; 13748c2ecf20Sopenharmony_ci case DOC_ChipID_DocMilPlus32: 13758c2ecf20Sopenharmony_ci pr_err("DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n"); 13768c2ecf20Sopenharmony_ci fallthrough; 13778c2ecf20Sopenharmony_ci default: 13788c2ecf20Sopenharmony_ci ret = -ENODEV; 13798c2ecf20Sopenharmony_ci goto notfound; 13808c2ecf20Sopenharmony_ci } 13818c2ecf20Sopenharmony_ci break; 13828c2ecf20Sopenharmony_ci 13838c2ecf20Sopenharmony_ci default: 13848c2ecf20Sopenharmony_ci ret = -ENODEV; 13858c2ecf20Sopenharmony_ci goto notfound; 13868c2ecf20Sopenharmony_ci } 13878c2ecf20Sopenharmony_ci /* Check the TOGGLE bit in the ECC register */ 13888c2ecf20Sopenharmony_ci tmp = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT; 13898c2ecf20Sopenharmony_ci tmpb = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT; 13908c2ecf20Sopenharmony_ci tmpc = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT; 13918c2ecf20Sopenharmony_ci if ((tmp == tmpb) || (tmp != tmpc)) { 13928c2ecf20Sopenharmony_ci pr_warn("Possible DiskOnChip at 0x%lx failed TOGGLE test, dropping.\n", physadr); 13938c2ecf20Sopenharmony_ci ret = -ENODEV; 13948c2ecf20Sopenharmony_ci goto notfound; 13958c2ecf20Sopenharmony_ci } 13968c2ecf20Sopenharmony_ci 13978c2ecf20Sopenharmony_ci for (mtd = doclist; mtd; mtd = doc->nextdoc) { 13988c2ecf20Sopenharmony_ci unsigned char oldval; 13998c2ecf20Sopenharmony_ci unsigned char newval; 14008c2ecf20Sopenharmony_ci nand = mtd_to_nand(mtd); 14018c2ecf20Sopenharmony_ci doc = nand_get_controller_data(nand); 14028c2ecf20Sopenharmony_ci /* Use the alias resolution register to determine if this is 14038c2ecf20Sopenharmony_ci in fact the same DOC aliased to a new address. If writes 14048c2ecf20Sopenharmony_ci to one chip's alias resolution register change the value on 14058c2ecf20Sopenharmony_ci the other chip, they're the same chip. */ 14068c2ecf20Sopenharmony_ci if (ChipID == DOC_ChipID_DocMilPlus16) { 14078c2ecf20Sopenharmony_ci oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution); 14088c2ecf20Sopenharmony_ci newval = ReadDOC(virtadr, Mplus_AliasResolution); 14098c2ecf20Sopenharmony_ci } else { 14108c2ecf20Sopenharmony_ci oldval = ReadDOC(doc->virtadr, AliasResolution); 14118c2ecf20Sopenharmony_ci newval = ReadDOC(virtadr, AliasResolution); 14128c2ecf20Sopenharmony_ci } 14138c2ecf20Sopenharmony_ci if (oldval != newval) 14148c2ecf20Sopenharmony_ci continue; 14158c2ecf20Sopenharmony_ci if (ChipID == DOC_ChipID_DocMilPlus16) { 14168c2ecf20Sopenharmony_ci WriteDOC(~newval, virtadr, Mplus_AliasResolution); 14178c2ecf20Sopenharmony_ci oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution); 14188c2ecf20Sopenharmony_ci WriteDOC(newval, virtadr, Mplus_AliasResolution); // restore it 14198c2ecf20Sopenharmony_ci } else { 14208c2ecf20Sopenharmony_ci WriteDOC(~newval, virtadr, AliasResolution); 14218c2ecf20Sopenharmony_ci oldval = ReadDOC(doc->virtadr, AliasResolution); 14228c2ecf20Sopenharmony_ci WriteDOC(newval, virtadr, AliasResolution); // restore it 14238c2ecf20Sopenharmony_ci } 14248c2ecf20Sopenharmony_ci newval = ~newval; 14258c2ecf20Sopenharmony_ci if (oldval == newval) { 14268c2ecf20Sopenharmony_ci pr_debug("Found alias of DOC at 0x%lx to 0x%lx\n", 14278c2ecf20Sopenharmony_ci doc->physadr, physadr); 14288c2ecf20Sopenharmony_ci goto notfound; 14298c2ecf20Sopenharmony_ci } 14308c2ecf20Sopenharmony_ci } 14318c2ecf20Sopenharmony_ci 14328c2ecf20Sopenharmony_ci pr_notice("DiskOnChip found at 0x%lx\n", physadr); 14338c2ecf20Sopenharmony_ci 14348c2ecf20Sopenharmony_ci len = sizeof(struct nand_chip) + sizeof(struct doc_priv) + 14358c2ecf20Sopenharmony_ci (2 * sizeof(struct nand_bbt_descr)); 14368c2ecf20Sopenharmony_ci nand = kzalloc(len, GFP_KERNEL); 14378c2ecf20Sopenharmony_ci if (!nand) { 14388c2ecf20Sopenharmony_ci ret = -ENOMEM; 14398c2ecf20Sopenharmony_ci goto fail; 14408c2ecf20Sopenharmony_ci } 14418c2ecf20Sopenharmony_ci 14428c2ecf20Sopenharmony_ci /* 14438c2ecf20Sopenharmony_ci * Allocate a RS codec instance 14448c2ecf20Sopenharmony_ci * 14458c2ecf20Sopenharmony_ci * Symbolsize is 10 (bits) 14468c2ecf20Sopenharmony_ci * Primitve polynomial is x^10+x^3+1 14478c2ecf20Sopenharmony_ci * First consecutive root is 510 14488c2ecf20Sopenharmony_ci * Primitve element to generate roots = 1 14498c2ecf20Sopenharmony_ci * Generator polinomial degree = 4 14508c2ecf20Sopenharmony_ci */ 14518c2ecf20Sopenharmony_ci doc = (struct doc_priv *) (nand + 1); 14528c2ecf20Sopenharmony_ci doc->rs_decoder = init_rs(10, 0x409, FCR, 1, NROOTS); 14538c2ecf20Sopenharmony_ci if (!doc->rs_decoder) { 14548c2ecf20Sopenharmony_ci pr_err("DiskOnChip: Could not create a RS codec\n"); 14558c2ecf20Sopenharmony_ci ret = -ENOMEM; 14568c2ecf20Sopenharmony_ci goto fail; 14578c2ecf20Sopenharmony_ci } 14588c2ecf20Sopenharmony_ci 14598c2ecf20Sopenharmony_ci nand_controller_init(&doc->base); 14608c2ecf20Sopenharmony_ci if (ChipID == DOC_ChipID_DocMilPlus16) 14618c2ecf20Sopenharmony_ci doc->base.ops = &doc2001plus_ops; 14628c2ecf20Sopenharmony_ci else 14638c2ecf20Sopenharmony_ci doc->base.ops = &doc200x_ops; 14648c2ecf20Sopenharmony_ci 14658c2ecf20Sopenharmony_ci mtd = nand_to_mtd(nand); 14668c2ecf20Sopenharmony_ci nand->bbt_td = (struct nand_bbt_descr *) (doc + 1); 14678c2ecf20Sopenharmony_ci nand->bbt_md = nand->bbt_td + 1; 14688c2ecf20Sopenharmony_ci 14698c2ecf20Sopenharmony_ci mtd->owner = THIS_MODULE; 14708c2ecf20Sopenharmony_ci mtd_set_ooblayout(mtd, &doc200x_ooblayout_ops); 14718c2ecf20Sopenharmony_ci 14728c2ecf20Sopenharmony_ci nand->controller = &doc->base; 14738c2ecf20Sopenharmony_ci nand_set_controller_data(nand, doc); 14748c2ecf20Sopenharmony_ci nand->bbt_options = NAND_BBT_USE_FLASH; 14758c2ecf20Sopenharmony_ci /* Skip the automatic BBT scan so we can run it manually */ 14768c2ecf20Sopenharmony_ci nand->options |= NAND_SKIP_BBTSCAN | NAND_NO_BBM_QUIRK; 14778c2ecf20Sopenharmony_ci 14788c2ecf20Sopenharmony_ci doc->physadr = physadr; 14798c2ecf20Sopenharmony_ci doc->virtadr = virtadr; 14808c2ecf20Sopenharmony_ci doc->ChipID = ChipID; 14818c2ecf20Sopenharmony_ci doc->curfloor = -1; 14828c2ecf20Sopenharmony_ci doc->curchip = -1; 14838c2ecf20Sopenharmony_ci doc->mh0_page = -1; 14848c2ecf20Sopenharmony_ci doc->mh1_page = -1; 14858c2ecf20Sopenharmony_ci doc->nextdoc = doclist; 14868c2ecf20Sopenharmony_ci 14878c2ecf20Sopenharmony_ci if (ChipID == DOC_ChipID_Doc2k) 14888c2ecf20Sopenharmony_ci numchips = doc2000_init(mtd); 14898c2ecf20Sopenharmony_ci else if (ChipID == DOC_ChipID_DocMilPlus16) 14908c2ecf20Sopenharmony_ci numchips = doc2001plus_init(mtd); 14918c2ecf20Sopenharmony_ci else 14928c2ecf20Sopenharmony_ci numchips = doc2001_init(mtd); 14938c2ecf20Sopenharmony_ci 14948c2ecf20Sopenharmony_ci if ((ret = nand_scan(nand, numchips)) || (ret = doc->late_init(mtd))) { 14958c2ecf20Sopenharmony_ci /* DBB note: i believe nand_cleanup is necessary here, as 14968c2ecf20Sopenharmony_ci buffers may have been allocated in nand_base. Check with 14978c2ecf20Sopenharmony_ci Thomas. FIX ME! */ 14988c2ecf20Sopenharmony_ci nand_cleanup(nand); 14998c2ecf20Sopenharmony_ci goto fail; 15008c2ecf20Sopenharmony_ci } 15018c2ecf20Sopenharmony_ci 15028c2ecf20Sopenharmony_ci /* Success! */ 15038c2ecf20Sopenharmony_ci doclist = mtd; 15048c2ecf20Sopenharmony_ci return 0; 15058c2ecf20Sopenharmony_ci 15068c2ecf20Sopenharmony_ci notfound: 15078c2ecf20Sopenharmony_ci /* Put back the contents of the DOCControl register, in case it's not 15088c2ecf20Sopenharmony_ci actually a DiskOnChip. */ 15098c2ecf20Sopenharmony_ci WriteDOC(save_control, virtadr, DOCControl); 15108c2ecf20Sopenharmony_ci fail: 15118c2ecf20Sopenharmony_ci if (doc) 15128c2ecf20Sopenharmony_ci free_rs(doc->rs_decoder); 15138c2ecf20Sopenharmony_ci kfree(nand); 15148c2ecf20Sopenharmony_ci iounmap(virtadr); 15158c2ecf20Sopenharmony_ci 15168c2ecf20Sopenharmony_cierror_ioremap: 15178c2ecf20Sopenharmony_ci release_mem_region(physadr, DOC_IOREMAP_LEN); 15188c2ecf20Sopenharmony_ci 15198c2ecf20Sopenharmony_ci return ret; 15208c2ecf20Sopenharmony_ci} 15218c2ecf20Sopenharmony_ci 15228c2ecf20Sopenharmony_cistatic void release_nanddoc(void) 15238c2ecf20Sopenharmony_ci{ 15248c2ecf20Sopenharmony_ci struct mtd_info *mtd, *nextmtd; 15258c2ecf20Sopenharmony_ci struct nand_chip *nand; 15268c2ecf20Sopenharmony_ci struct doc_priv *doc; 15278c2ecf20Sopenharmony_ci int ret; 15288c2ecf20Sopenharmony_ci 15298c2ecf20Sopenharmony_ci for (mtd = doclist; mtd; mtd = nextmtd) { 15308c2ecf20Sopenharmony_ci nand = mtd_to_nand(mtd); 15318c2ecf20Sopenharmony_ci doc = nand_get_controller_data(nand); 15328c2ecf20Sopenharmony_ci 15338c2ecf20Sopenharmony_ci nextmtd = doc->nextdoc; 15348c2ecf20Sopenharmony_ci ret = mtd_device_unregister(mtd); 15358c2ecf20Sopenharmony_ci WARN_ON(ret); 15368c2ecf20Sopenharmony_ci nand_cleanup(nand); 15378c2ecf20Sopenharmony_ci iounmap(doc->virtadr); 15388c2ecf20Sopenharmony_ci release_mem_region(doc->physadr, DOC_IOREMAP_LEN); 15398c2ecf20Sopenharmony_ci free_rs(doc->rs_decoder); 15408c2ecf20Sopenharmony_ci kfree(nand); 15418c2ecf20Sopenharmony_ci } 15428c2ecf20Sopenharmony_ci} 15438c2ecf20Sopenharmony_ci 15448c2ecf20Sopenharmony_cistatic int __init init_nanddoc(void) 15458c2ecf20Sopenharmony_ci{ 15468c2ecf20Sopenharmony_ci int i, ret = 0; 15478c2ecf20Sopenharmony_ci 15488c2ecf20Sopenharmony_ci if (doc_config_location) { 15498c2ecf20Sopenharmony_ci pr_info("Using configured DiskOnChip probe address 0x%lx\n", 15508c2ecf20Sopenharmony_ci doc_config_location); 15518c2ecf20Sopenharmony_ci ret = doc_probe(doc_config_location); 15528c2ecf20Sopenharmony_ci if (ret < 0) 15538c2ecf20Sopenharmony_ci return ret; 15548c2ecf20Sopenharmony_ci } else { 15558c2ecf20Sopenharmony_ci for (i = 0; (doc_locations[i] != 0xffffffff); i++) { 15568c2ecf20Sopenharmony_ci doc_probe(doc_locations[i]); 15578c2ecf20Sopenharmony_ci } 15588c2ecf20Sopenharmony_ci } 15598c2ecf20Sopenharmony_ci /* No banner message any more. Print a message if no DiskOnChip 15608c2ecf20Sopenharmony_ci found, so the user knows we at least tried. */ 15618c2ecf20Sopenharmony_ci if (!doclist) { 15628c2ecf20Sopenharmony_ci pr_info("No valid DiskOnChip devices found\n"); 15638c2ecf20Sopenharmony_ci ret = -ENODEV; 15648c2ecf20Sopenharmony_ci } 15658c2ecf20Sopenharmony_ci return ret; 15668c2ecf20Sopenharmony_ci} 15678c2ecf20Sopenharmony_ci 15688c2ecf20Sopenharmony_cistatic void __exit cleanup_nanddoc(void) 15698c2ecf20Sopenharmony_ci{ 15708c2ecf20Sopenharmony_ci /* Cleanup the nand/DoC resources */ 15718c2ecf20Sopenharmony_ci release_nanddoc(); 15728c2ecf20Sopenharmony_ci} 15738c2ecf20Sopenharmony_ci 15748c2ecf20Sopenharmony_cimodule_init(init_nanddoc); 15758c2ecf20Sopenharmony_cimodule_exit(cleanup_nanddoc); 15768c2ecf20Sopenharmony_ci 15778c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 15788c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 15798c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver"); 1580