diff options
Diffstat (limited to 'drivers/mtd/devices/ms02-nv.c')
-rw-r--r-- | drivers/mtd/devices/ms02-nv.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c new file mode 100644 index 0000000..380ff08 --- /dev/null +++ b/drivers/mtd/devices/ms02-nv.c @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2001 Maciej W. Rozycki + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * $Id: ms02-nv.c,v 1.8 2005/01/05 18:05:12 dwmw2 Exp $ + */ + +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <asm/addrspace.h> +#include <asm/bootinfo.h> +#include <asm/dec/ioasic_addrs.h> +#include <asm/dec/kn02.h> +#include <asm/dec/kn03.h> +#include <asm/io.h> +#include <asm/paccess.h> + +#include "ms02-nv.h" + + +static char version[] __initdata = + "ms02-nv.c: v.1.0.0 13 Aug 2001 Maciej W. Rozycki.\n"; + +MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>"); +MODULE_DESCRIPTION("DEC MS02-NV NVRAM module driver"); +MODULE_LICENSE("GPL"); + + +/* + * Addresses we probe for an MS02-NV at. Modules may be located + * at any 8MiB boundary within a 0MiB up to 112MiB range or at any 32MiB + * boundary within a 0MiB up to 448MiB range. We don't support a module + * at 0MiB, though. + */ +static ulong ms02nv_addrs[] __initdata = { + 0x07000000, 0x06800000, 0x06000000, 0x05800000, 0x05000000, + 0x04800000, 0x04000000, 0x03800000, 0x03000000, 0x02800000, + 0x02000000, 0x01800000, 0x01000000, 0x00800000 +}; + +static const char ms02nv_name[] = "DEC MS02-NV NVRAM"; +static const char ms02nv_res_diag_ram[] = "Diagnostic RAM"; +static const char ms02nv_res_user_ram[] = "General-purpose RAM"; +static const char ms02nv_res_csr[] = "Control and status register"; + +static struct mtd_info *root_ms02nv_mtd; + + +static int ms02nv_read(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + struct ms02nv_private *mp = mtd->priv; + + if (from + len > mtd->size) + return -EINVAL; + + memcpy(buf, mp->uaddr + from, len); + *retlen = len; + + return 0; +} + +static int ms02nv_write(struct mtd_info *mtd, loff_t to, + size_t len, size_t *retlen, const u_char *buf) +{ + struct ms02nv_private *mp = mtd->priv; + + if (to + len > mtd->size) + return -EINVAL; + + memcpy(mp->uaddr + to, buf, len); + *retlen = len; + + return 0; +} + + +static inline uint ms02nv_probe_one(ulong addr) +{ + ms02nv_uint *ms02nv_diagp; + ms02nv_uint *ms02nv_magicp; + uint ms02nv_diag; + uint ms02nv_magic; + size_t size; + + int err; + + /* + * The firmware writes MS02NV_ID at MS02NV_MAGIC and also + * a diagnostic status at MS02NV_DIAG. + */ + ms02nv_diagp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_DIAG)); + ms02nv_magicp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_MAGIC)); + err = get_dbe(ms02nv_magic, ms02nv_magicp); + if (err) + return 0; + if (ms02nv_magic != MS02NV_ID) + return 0; + + ms02nv_diag = *ms02nv_diagp; + size = (ms02nv_diag & MS02NV_DIAG_SIZE_MASK) << MS02NV_DIAG_SIZE_SHIFT; + if (size > MS02NV_CSR) + size = MS02NV_CSR; + + return size; +} + +static int __init ms02nv_init_one(ulong addr) +{ + struct mtd_info *mtd; + struct ms02nv_private *mp; + struct resource *mod_res; + struct resource *diag_res; + struct resource *user_res; + struct resource *csr_res; + ulong fixaddr; + size_t size, fixsize; + + static int version_printed; + + int ret = -ENODEV; + + /* The module decodes 8MiB of address space. */ + mod_res = kmalloc(sizeof(*mod_res), GFP_KERNEL); + if (!mod_res) + return -ENOMEM; + + memset(mod_res, 0, sizeof(*mod_res)); + mod_res->name = ms02nv_name; + mod_res->start = addr; + mod_res->end = addr + MS02NV_SLOT_SIZE - 1; + mod_res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + if (request_resource(&iomem_resource, mod_res) < 0) + goto err_out_mod_res; + + size = ms02nv_probe_one(addr); + if (!size) + goto err_out_mod_res_rel; + + if (!version_printed) { + printk(KERN_INFO "%s", version); + version_printed = 1; + } + + ret = -ENOMEM; + mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); + if (!mtd) + goto err_out_mod_res_rel; + memset(mtd, 0, sizeof(*mtd)); + mp = kmalloc(sizeof(*mp), GFP_KERNEL); + if (!mp) + goto err_out_mtd; + memset(mp, 0, sizeof(*mp)); + + mtd->priv = mp; + mp->resource.module = mod_res; + + /* Firmware's diagnostic NVRAM area. */ + diag_res = kmalloc(sizeof(*diag_res), GFP_KERNEL); + if (!diag_res) + goto err_out_mp; + + memset(diag_res, 0, sizeof(*diag_res)); + diag_res->name = ms02nv_res_diag_ram; + diag_res->start = addr; + diag_res->end = addr + MS02NV_RAM - 1; + diag_res->flags = IORESOURCE_BUSY; + request_resource(mod_res, diag_res); + + mp->resource.diag_ram = diag_res; + + /* User-available general-purpose NVRAM area. */ + user_res = kmalloc(sizeof(*user_res), GFP_KERNEL); + if (!user_res) + goto err_out_diag_res; + + memset(user_res, 0, sizeof(*user_res)); + user_res->name = ms02nv_res_user_ram; + user_res->start = addr + MS02NV_RAM; + user_res->end = addr + size - 1; + user_res->flags = IORESOURCE_BUSY; + request_resource(mod_res, user_res); + + mp->resource.user_ram = user_res; + + /* Control and status register. */ + csr_res = kmalloc(sizeof(*csr_res), GFP_KERNEL); + if (!csr_res) + goto err_out_user_res; + + memset(csr_res, 0, sizeof(*csr_res)); + csr_res->name = ms02nv_res_csr; + csr_res->start = addr + MS02NV_CSR; + csr_res->end = addr + MS02NV_CSR + 3; + csr_res->flags = IORESOURCE_BUSY; + request_resource(mod_res, csr_res); + + mp->resource.csr = csr_res; + + mp->addr = phys_to_virt(addr); + mp->size = size; + + /* + * Hide the firmware's diagnostic area. It may get destroyed + * upon a reboot. Take paging into account for mapping support. + */ + fixaddr = (addr + MS02NV_RAM + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + fixsize = (size - (fixaddr - addr)) & ~(PAGE_SIZE - 1); + mp->uaddr = phys_to_virt(fixaddr); + + mtd->type = MTD_RAM; + mtd->flags = MTD_CAP_RAM | MTD_XIP; + mtd->size = fixsize; + mtd->name = (char *)ms02nv_name; + mtd->owner = THIS_MODULE; + mtd->read = ms02nv_read; + mtd->write = ms02nv_write; + + ret = -EIO; + if (add_mtd_device(mtd)) { + printk(KERN_ERR + "ms02-nv: Unable to register MTD device, aborting!\n"); + goto err_out_csr_res; + } + + printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %uMiB.\n", + mtd->index, ms02nv_name, addr, size >> 20); + + mp->next = root_ms02nv_mtd; + root_ms02nv_mtd = mtd; + + return 0; + + +err_out_csr_res: + release_resource(csr_res); + kfree(csr_res); +err_out_user_res: + release_resource(user_res); + kfree(user_res); +err_out_diag_res: + release_resource(diag_res); + kfree(diag_res); +err_out_mp: + kfree(mp); +err_out_mtd: + kfree(mtd); +err_out_mod_res_rel: + release_resource(mod_res); +err_out_mod_res: + kfree(mod_res); + return ret; +} + +static void __exit ms02nv_remove_one(void) +{ + struct mtd_info *mtd = root_ms02nv_mtd; + struct ms02nv_private *mp = mtd->priv; + + root_ms02nv_mtd = mp->next; + + del_mtd_device(mtd); + + release_resource(mp->resource.csr); + kfree(mp->resource.csr); + release_resource(mp->resource.user_ram); + kfree(mp->resource.user_ram); + release_resource(mp->resource.diag_ram); + kfree(mp->resource.diag_ram); + release_resource(mp->resource.module); + kfree(mp->resource.module); + kfree(mp); + kfree(mtd); +} + + +static int __init ms02nv_init(void) +{ + volatile u32 *csr; + uint stride = 0; + int count = 0; + int i; + + switch (mips_machtype) { + case MACH_DS5000_200: + csr = (volatile u32 *)KN02_CSR_BASE; + if (*csr & KN02_CSR_BNK32M) + stride = 2; + break; + case MACH_DS5000_2X0: + case MACH_DS5900: + csr = (volatile u32 *)KN03_MCR_BASE; + if (*csr & KN03_MCR_BNK32M) + stride = 2; + break; + default: + return -ENODEV; + break; + } + + for (i = 0; i < (sizeof(ms02nv_addrs) / sizeof(*ms02nv_addrs)); i++) + if (!ms02nv_init_one(ms02nv_addrs[i] << stride)) + count++; + + return (count > 0) ? 0 : -ENODEV; +} + +static void __exit ms02nv_cleanup(void) +{ + while (root_ms02nv_mtd) + ms02nv_remove_one(); +} + + +module_init(ms02nv_init); +module_exit(ms02nv_cleanup); |