* (C) 2005, 2006 Red Hat Inc.
*
* Author: David Woodhouse <dwmw2@infradead.org>
+ * Tom Sylla <tom.sylla@amd.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Overview:
- * This is a device driver for the NAND flash controller found on
+ * This is a device driver for the NAND flash controller found on
* the AMD CS5535/CS5536 companion chipsets for the Geode processor.
+ * mtd-id for command line partitioning is cs553x_nand_cs[0-3]
+ * where 0-3 reflects the chip select for NAND.
*
*/
+#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
-#include <linux/pci.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>
#include <asm/msr.h>
#define NR_CS553X_CONTROLLERS 4
+#define MSR_DIVIL_GLD_CAP 0x51400000 /* DIVIL capabilitiies */
+#define CAP_CS5535 0x2df000ULL
+#define CAP_CS5536 0x5df500ULL
+
/* NAND Timing MSRs */
#define MSR_NANDF_DATA 0x5140001b /* NAND Flash Data Timing MSR */
#define MSR_NANDF_CTL 0x5140001c /* NAND Flash Control Timing */
/* Pin function selection MSR (IDE vs. flash on the IDE pins) */
#define MSR_DIVIL_BALL_OPTS 0x51400015
-#define PIN_OPT_IDE (1<<0) /* 0 for flash, 1 for IDE */
+#define PIN_OPT_IDE (1<<0) /* 0 for flash, 1 for IDE */
/* Registers within the NAND flash controller BAR -- memory mapped */
#define MM_NAND_DATA 0x00 /* 0 to 0x7ff, in fact */
#define CS_NAND_ECC_CLRECC (1<<1)
#define CS_NAND_ECC_ENECC (1<<0)
+static void cs553x_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+
+ while (unlikely(len > 0x800)) {
+ memcpy_fromio(buf, this->IO_ADDR_R, 0x800);
+ buf += 0x800;
+ len -= 0x800;
+ }
+ memcpy_fromio(buf, this->IO_ADDR_R, len);
+}
+
+static void cs553x_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+
+ while (unlikely(len > 0x800)) {
+ memcpy_toio(this->IO_ADDR_R, buf, 0x800);
+ buf += 0x800;
+ len -= 0x800;
+ }
+ memcpy_toio(this->IO_ADDR_R, buf, len);
+}
+
static unsigned char cs553x_read_byte(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- unsigned char foo = readb(this->IO_ADDR_R);
- return foo;
+ return readb(this->IO_ADDR_R);
}
static void cs553x_write_byte(struct mtd_info *mtd, u_char byte)
udelay(1);
i--;
}
- writeb(byte, this->IO_ADDR_W+0x801);
+ writeb(byte, this->IO_ADDR_W + 0x801);
}
-static void cs553x_hwcontrol(struct mtd_info *mtd, int cmd)
+static void cs553x_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
{
struct nand_chip *this = mtd->priv;
void __iomem *mmio_base = this->IO_ADDR_R;
+ if (ctrl & NAND_CTRL_CHANGE) {
+ unsigned char ctl = (ctrl & ~NAND_CTRL_CHANGE ) ^ 0x01;
+ writeb(ctl, mmio_base + MM_NAND_CTL);
+ }
+ if (cmd != NAND_CMD_NONE)
+ cs553x_write_byte(mtd, cmd);
+}
- uint8_t old = readb(mmio_base + MM_NAND_CTL);
-
- switch(cmd) {
- case NAND_CTL_SETCLE:
- old |= CS_NAND_CTL_CLE;
- break;
-
- case NAND_CTL_CLRCLE:
- old &= ~CS_NAND_CTL_CLE;
- break;
-
- case NAND_CTL_SETALE:
- old |= CS_NAND_CTL_ALE;
- break;
+static int cs553x_device_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ void __iomem *mmio_base = this->IO_ADDR_R;
+ unsigned char foo = readb(mmio_base + MM_NAND_STS);
- case NAND_CTL_CLRALE:
- old &= ~CS_NAND_CTL_ALE;
- break;
+ return (foo & CS_NAND_STS_FLASH_RDY) && !(foo & CS_NAND_CTLR_BUSY);
+}
- case NAND_CTL_SETNCE:
- old &= ~CS_NAND_CTL_CE;
- break;
+static void cs_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct nand_chip *this = mtd->priv;
+ void __iomem *mmio_base = this->IO_ADDR_R;
- case NAND_CTL_CLRNCE:
- old |= CS_NAND_CTL_CE;
- break;
- }
- writeb(old, mmio_base + MM_NAND_CTL);
+ writeb(0x07, mmio_base + MM_NAND_ECC_CTL);
}
-
-static int cs553x_device_ready(struct mtd_info *mtd)
+static int cs_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
{
+ uint32_t ecc;
struct nand_chip *this = mtd->priv;
void __iomem *mmio_base = this->IO_ADDR_R;
- unsigned char foo = readb(mmio_base + MM_NAND_STS);
- return (foo & CS_NAND_STS_FLASH_RDY) && !(foo & CS_NAND_CTLR_BUSY);
+ ecc = readl(mmio_base + MM_NAND_STS);
+
+ ecc_code[1] = ecc >> 8;
+ ecc_code[0] = ecc >> 16;
+ ecc_code[2] = ecc >> 24;
+ return 0;
}
static struct mtd_info *cs553x_mtd[4];
}
/* Allocate memory for MTD device structure and private data */
- new_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip), GFP_KERNEL);
+ new_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
if (!new_mtd) {
printk(KERN_WARNING "Unable to allocate CS553X NAND MTD device structure.\n");
err = -ENOMEM;
}
/* Get pointer to private data */
- this = (struct nand_chip *) (&new_mtd[1]);
+ this = (struct nand_chip *)(&new_mtd[1]);
/* Initialize structures */
- memset((char *) new_mtd, 0, sizeof(struct mtd_info));
- memset((char *) this, 0, sizeof(struct nand_chip));
+ memset(new_mtd, 0, sizeof(struct mtd_info));
+ memset(this, 0, sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
new_mtd->priv = this;
+ new_mtd->owner = THIS_MODULE;
/* map physical address */
this->IO_ADDR_R = this->IO_ADDR_W = ioremap(adr, 4096);
goto out_mtd;
}
- this->hwcontrol = cs553x_hwcontrol;
+ this->cmd_ctrl = cs553x_hwcontrol;
this->dev_ready = cs553x_device_ready;
this->read_byte = cs553x_read_byte;
- this->write_byte = cs553x_write_byte;
+ this->read_buf = cs553x_read_buf;
+ this->write_buf = cs553x_write_buf;
+
+ this->chip_delay = 0;
- /* 20 us command delay time */
- this->chip_delay = 20;
- this->eccmode = NAND_ECC_SOFT;
+ this->ecc.mode = NAND_ECC_HW;
+ this->ecc.size = 256;
+ this->ecc.bytes = 3;
+ this->ecc.hwctl = cs_enable_hwecc;
+ this->ecc.calculate = cs_calculate_ecc;
+ this->ecc.correct = nand_correct_data;
/* Enable the following for a flash based bad block table */
- // this->options = NAND_USE_FLASH_BBT;
+ this->options = NAND_USE_FLASH_BBT | NAND_NO_AUTOINCR;
/* Scan to find existance of the device */
- if (nand_scan (new_mtd, 1)) {
+ if (nand_scan(new_mtd, 1)) {
err = -ENXIO;
goto out_ior;
}
+ new_mtd->name = kasprintf(GFP_KERNEL, "cs553x_nand_cs%d", cs);
+
cs553x_mtd[cs] = new_mtd;
goto out;
out_ior:
- iounmap((void *)this->IO_ADDR_R);
+ iounmap(this->IO_ADDR_R);
out_mtd:
- kfree (new_mtd);
+ kfree(new_mtd);
out:
return err;
}
-int __init cs553x_init(void)
+static int is_geode(void)
+{
+ /* These are the CPUs which will have a CS553[56] companion chip */
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
+ boot_cpu_data.x86 == 5 &&
+ boot_cpu_data.x86_model == 10)
+ return 1; /* Geode LX */
+
+ if ((boot_cpu_data.x86_vendor == X86_VENDOR_NSC ||
+ boot_cpu_data.x86_vendor == X86_VENDOR_CYRIX) &&
+ boot_cpu_data.x86 == 5 &&
+ boot_cpu_data.x86_model == 5)
+ return 1; /* Geode GX (née GX2) */
+
+ return 0;
+}
+
+
+#ifdef CONFIG_MTD_PARTITIONS
+static const char *part_probes[] = { "cmdlinepart", NULL };
+#endif
+
+
+static int __init cs553x_init(void)
{
int err = -ENXIO;
int i;
uint64_t val;
- /* Check whether we actually have a CS5535 or CS5536 */
- if (!pci_find_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA, NULL) &&
- !pci_find_device(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA, NULL))
+ int mtd_parts_nb = 0;
+ struct mtd_partition *mtd_parts = NULL;
+
+ /* If the CPU isn't a Geode GX or LX, abort */
+ if (!is_geode())
+ return -ENXIO;
+
+ /* If it doesn't have the CS553[56], abort */
+ rdmsrl(MSR_DIVIL_GLD_CAP, val);
+ val &= ~0xFFULL;
+ if (val != CAP_CS5535 && val != CAP_CS5536)
return -ENXIO;
+ /* If it doesn't have the NAND controller enabled, abort */
rdmsrl(MSR_DIVIL_BALL_OPTS, val);
- if (val & 1) {
+ if (val & PIN_OPT_IDE) {
printk(KERN_INFO "CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS.\n");
return -ENXIO;
}
- for (i=0; i<NR_CS553X_CONTROLLERS; i++) {
- rdmsrl(MSR_DIVIL_LBAR_FLSH0+i, val);
+ for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
+ rdmsrl(MSR_DIVIL_LBAR_FLSH0 + i, val);
if ((val & (FLSH_LBAR_EN|FLSH_NOR_NAND)) == (FLSH_LBAR_EN|FLSH_NOR_NAND))
err = cs553x_init_one(i, !!(val & FLSH_MEM_IO), val & 0xFFFFFFFF);
}
-
- /* Register all devices together here. This means we can easily hack it to
+
+ /* Register all devices together here. This means we can easily hack it to
do mtdconcat etc. if we want to. */
- for (i=0; i<NR_CS553X_CONTROLLERS; i++) {
+ for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
if (cs553x_mtd[i]) {
- add_mtd_device(cs553x_mtd[i]);
/* If any devices registered, return success. Else the last error. */
+#ifdef CONFIG_MTD_PARTITIONS
+ mtd_parts_nb = parse_mtd_partitions(cs553x_mtd[i], part_probes, &mtd_parts, 0);
+ if (mtd_parts_nb > 0) {
+ printk(KERN_NOTICE "Using command line partition definition\n");
+ add_mtd_partitions(cs553x_mtd[i], mtd_parts, mtd_parts_nb);
+ } else {
+ add_mtd_device(cs553x_mtd[i]);
+ }
+#else
+ add_mtd_device(cs553x_mtd[i]);
+#endif
err = 0;
}
}
return err;
}
+
module_init(cs553x_init);
-static void __exit cs553x_cleanup (void)
+static void __exit cs553x_cleanup(void)
{
int i;
- for (i=0; i<NR_CS553X_CONTROLLERS; i++) {
+ for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
struct mtd_info *mtd = cs553x_mtd[i];
struct nand_chip *this;
void __iomem *mmio_base;
if (!mtd)
- break;
+ continue;
this = cs553x_mtd[i]->priv;
mmio_base = this->IO_ADDR_R;
/* Release resources, unregister device */
- nand_release (cs553x_mtd[i]);
+ nand_release(cs553x_mtd[i]);
+ kfree(cs553x_mtd[i]->name);
cs553x_mtd[i] = NULL;
- /* unmap physical adress */
+ /* unmap physical address */
iounmap(mmio_base);
/* Free the MTD device structure */
- kfree (mtd);
+ kfree(mtd);
}
}
+
module_exit(cs553x_cleanup);
MODULE_LICENSE("GPL");