597 lines
13 KiB
C
597 lines
13 KiB
C
|
/*
|
||
|
* MTD chip driver for pre-CFI Sharp flash chips
|
||
|
*
|
||
|
* Copyright 2000,2001 David A. Schleef <ds@schleef.org>
|
||
|
* 2000,2001 Lineo, Inc.
|
||
|
*
|
||
|
* $Id: sharp.c,v 1.14 2004/08/09 13:19:43 dwmw2 Exp $
|
||
|
*
|
||
|
* Devices supported:
|
||
|
* LH28F016SCT Symmetrical block flash memory, 2Mx8
|
||
|
* LH28F008SCT Symmetrical block flash memory, 1Mx8
|
||
|
*
|
||
|
* Documentation:
|
||
|
* http://www.sharpmeg.com/datasheets/memic/flashcmp/
|
||
|
* http://www.sharpmeg.com/datasheets/memic/flashcmp/01symf/16m/016sctl9.pdf
|
||
|
* 016sctl9.pdf
|
||
|
*
|
||
|
* Limitations:
|
||
|
* This driver only supports 4x1 arrangement of chips.
|
||
|
* Not tested on anything but PowerPC.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/mtd/map.h>
|
||
|
#include <linux/mtd/mtd.h>
|
||
|
#include <linux/mtd/cfi.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/init.h>
|
||
|
|
||
|
#define CMD_RESET 0xffffffff
|
||
|
#define CMD_READ_ID 0x90909090
|
||
|
#define CMD_READ_STATUS 0x70707070
|
||
|
#define CMD_CLEAR_STATUS 0x50505050
|
||
|
#define CMD_BLOCK_ERASE_1 0x20202020
|
||
|
#define CMD_BLOCK_ERASE_2 0xd0d0d0d0
|
||
|
#define CMD_BYTE_WRITE 0x40404040
|
||
|
#define CMD_SUSPEND 0xb0b0b0b0
|
||
|
#define CMD_RESUME 0xd0d0d0d0
|
||
|
#define CMD_SET_BLOCK_LOCK_1 0x60606060
|
||
|
#define CMD_SET_BLOCK_LOCK_2 0x01010101
|
||
|
#define CMD_SET_MASTER_LOCK_1 0x60606060
|
||
|
#define CMD_SET_MASTER_LOCK_2 0xf1f1f1f1
|
||
|
#define CMD_CLEAR_BLOCK_LOCKS_1 0x60606060
|
||
|
#define CMD_CLEAR_BLOCK_LOCKS_2 0xd0d0d0d0
|
||
|
|
||
|
#define SR_READY 0x80808080 // 1 = ready
|
||
|
#define SR_ERASE_SUSPEND 0x40404040 // 1 = block erase suspended
|
||
|
#define SR_ERROR_ERASE 0x20202020 // 1 = error in block erase or clear lock bits
|
||
|
#define SR_ERROR_WRITE 0x10101010 // 1 = error in byte write or set lock bit
|
||
|
#define SR_VPP 0x08080808 // 1 = Vpp is low
|
||
|
#define SR_WRITE_SUSPEND 0x04040404 // 1 = byte write suspended
|
||
|
#define SR_PROTECT 0x02020202 // 1 = lock bit set
|
||
|
#define SR_RESERVED 0x01010101
|
||
|
|
||
|
#define SR_ERRORS (SR_ERROR_ERASE|SR_ERROR_WRITE|SR_VPP|SR_PROTECT)
|
||
|
|
||
|
/* Configuration options */
|
||
|
|
||
|
#undef AUTOUNLOCK /* automatically unlocks blocks before erasing */
|
||
|
|
||
|
struct mtd_info *sharp_probe(struct map_info *);
|
||
|
|
||
|
static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd);
|
||
|
|
||
|
static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||
|
size_t *retlen, u_char *buf);
|
||
|
static int sharp_write(struct mtd_info *mtd, loff_t from, size_t len,
|
||
|
size_t *retlen, const u_char *buf);
|
||
|
static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr);
|
||
|
static void sharp_sync(struct mtd_info *mtd);
|
||
|
static int sharp_suspend(struct mtd_info *mtd);
|
||
|
static void sharp_resume(struct mtd_info *mtd);
|
||
|
static void sharp_destroy(struct mtd_info *mtd);
|
||
|
|
||
|
static int sharp_write_oneword(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr, __u32 datum);
|
||
|
static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr);
|
||
|
#ifdef AUTOUNLOCK
|
||
|
static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr);
|
||
|
#endif
|
||
|
|
||
|
|
||
|
struct sharp_info{
|
||
|
struct flchip *chip;
|
||
|
int bogus;
|
||
|
int chipshift;
|
||
|
int numchips;
|
||
|
struct flchip chips[1];
|
||
|
};
|
||
|
|
||
|
struct mtd_info *sharp_probe(struct map_info *map);
|
||
|
static void sharp_destroy(struct mtd_info *mtd);
|
||
|
|
||
|
static struct mtd_chip_driver sharp_chipdrv = {
|
||
|
.probe = sharp_probe,
|
||
|
.destroy = sharp_destroy,
|
||
|
.name = "sharp",
|
||
|
.module = THIS_MODULE
|
||
|
};
|
||
|
|
||
|
|
||
|
struct mtd_info *sharp_probe(struct map_info *map)
|
||
|
{
|
||
|
struct mtd_info *mtd = NULL;
|
||
|
struct sharp_info *sharp = NULL;
|
||
|
int width;
|
||
|
|
||
|
mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
|
||
|
if(!mtd)
|
||
|
return NULL;
|
||
|
|
||
|
sharp = kmalloc(sizeof(*sharp), GFP_KERNEL);
|
||
|
if(!sharp) {
|
||
|
kfree(mtd);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
memset(mtd, 0, sizeof(*mtd));
|
||
|
|
||
|
width = sharp_probe_map(map,mtd);
|
||
|
if(!width){
|
||
|
kfree(mtd);
|
||
|
kfree(sharp);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
mtd->priv = map;
|
||
|
mtd->type = MTD_NORFLASH;
|
||
|
mtd->erase = sharp_erase;
|
||
|
mtd->read = sharp_read;
|
||
|
mtd->write = sharp_write;
|
||
|
mtd->sync = sharp_sync;
|
||
|
mtd->suspend = sharp_suspend;
|
||
|
mtd->resume = sharp_resume;
|
||
|
mtd->flags = MTD_CAP_NORFLASH;
|
||
|
mtd->name = map->name;
|
||
|
|
||
|
memset(sharp, 0, sizeof(*sharp));
|
||
|
sharp->chipshift = 23;
|
||
|
sharp->numchips = 1;
|
||
|
sharp->chips[0].start = 0;
|
||
|
sharp->chips[0].state = FL_READY;
|
||
|
sharp->chips[0].mutex = &sharp->chips[0]._spinlock;
|
||
|
sharp->chips[0].word_write_time = 0;
|
||
|
init_waitqueue_head(&sharp->chips[0].wq);
|
||
|
spin_lock_init(&sharp->chips[0]._spinlock);
|
||
|
|
||
|
map->fldrv = &sharp_chipdrv;
|
||
|
map->fldrv_priv = sharp;
|
||
|
|
||
|
__module_get(THIS_MODULE);
|
||
|
return mtd;
|
||
|
}
|
||
|
|
||
|
static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd)
|
||
|
{
|
||
|
unsigned long tmp;
|
||
|
unsigned long base = 0;
|
||
|
u32 read0, read4;
|
||
|
int width = 4;
|
||
|
|
||
|
tmp = map_read32(map, base+0);
|
||
|
|
||
|
map_write32(map, CMD_READ_ID, base+0);
|
||
|
|
||
|
read0=map_read32(map, base+0);
|
||
|
read4=map_read32(map, base+4);
|
||
|
if(read0 == 0x89898989){
|
||
|
printk("Looks like sharp flash\n");
|
||
|
switch(read4){
|
||
|
case 0xaaaaaaaa:
|
||
|
case 0xa0a0a0a0:
|
||
|
/* aa - LH28F016SCT-L95 2Mx8, 32 64k blocks*/
|
||
|
/* a0 - LH28F016SCT-Z4 2Mx8, 32 64k blocks*/
|
||
|
mtd->erasesize = 0x10000 * width;
|
||
|
mtd->size = 0x200000 * width;
|
||
|
return width;
|
||
|
case 0xa6a6a6a6:
|
||
|
/* a6 - LH28F008SCT-L12 1Mx8, 16 64k blocks*/
|
||
|
/* a6 - LH28F008SCR-L85 1Mx8, 16 64k blocks*/
|
||
|
mtd->erasesize = 0x10000 * width;
|
||
|
mtd->size = 0x100000 * width;
|
||
|
return width;
|
||
|
#if 0
|
||
|
case 0x00000000: /* unknown */
|
||
|
/* XX - LH28F004SCT 512kx8, 8 64k blocks*/
|
||
|
mtd->erasesize = 0x10000 * width;
|
||
|
mtd->size = 0x80000 * width;
|
||
|
return width;
|
||
|
#endif
|
||
|
default:
|
||
|
printk("Sort-of looks like sharp flash, 0x%08x 0x%08x\n",
|
||
|
read0,read4);
|
||
|
}
|
||
|
}else if((map_read32(map, base+0) == CMD_READ_ID)){
|
||
|
/* RAM, probably */
|
||
|
printk("Looks like RAM\n");
|
||
|
map_write32(map, tmp, base+0);
|
||
|
}else{
|
||
|
printk("Doesn't look like sharp flash, 0x%08x 0x%08x\n",
|
||
|
read0,read4);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* This function returns with the chip->mutex lock held. */
|
||
|
static int sharp_wait(struct map_info *map, struct flchip *chip)
|
||
|
{
|
||
|
__u16 status;
|
||
|
unsigned long timeo = jiffies + HZ;
|
||
|
DECLARE_WAITQUEUE(wait, current);
|
||
|
int adr = 0;
|
||
|
|
||
|
retry:
|
||
|
spin_lock_bh(chip->mutex);
|
||
|
|
||
|
switch(chip->state){
|
||
|
case FL_READY:
|
||
|
map_write32(map,CMD_READ_STATUS,adr);
|
||
|
chip->state = FL_STATUS;
|
||
|
case FL_STATUS:
|
||
|
status = map_read32(map,adr);
|
||
|
//printk("status=%08x\n",status);
|
||
|
|
||
|
udelay(100);
|
||
|
if((status & SR_READY)!=SR_READY){
|
||
|
//printk(".status=%08x\n",status);
|
||
|
udelay(100);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
printk("Waiting for chip\n");
|
||
|
|
||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||
|
add_wait_queue(&chip->wq, &wait);
|
||
|
|
||
|
spin_unlock_bh(chip->mutex);
|
||
|
|
||
|
schedule();
|
||
|
remove_wait_queue(&chip->wq, &wait);
|
||
|
|
||
|
if(signal_pending(current))
|
||
|
return -EINTR;
|
||
|
|
||
|
timeo = jiffies + HZ;
|
||
|
|
||
|
goto retry;
|
||
|
}
|
||
|
|
||
|
map_write32(map,CMD_RESET, adr);
|
||
|
|
||
|
chip->state = FL_READY;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void sharp_release(struct flchip *chip)
|
||
|
{
|
||
|
wake_up(&chip->wq);
|
||
|
spin_unlock_bh(chip->mutex);
|
||
|
}
|
||
|
|
||
|
static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||
|
size_t *retlen, u_char *buf)
|
||
|
{
|
||
|
struct map_info *map = mtd->priv;
|
||
|
struct sharp_info *sharp = map->fldrv_priv;
|
||
|
int chipnum;
|
||
|
int ret = 0;
|
||
|
int ofs = 0;
|
||
|
|
||
|
chipnum = (from >> sharp->chipshift);
|
||
|
ofs = from & ((1 << sharp->chipshift)-1);
|
||
|
|
||
|
*retlen = 0;
|
||
|
|
||
|
while(len){
|
||
|
unsigned long thislen;
|
||
|
|
||
|
if(chipnum>=sharp->numchips)
|
||
|
break;
|
||
|
|
||
|
thislen = len;
|
||
|
if(ofs+thislen >= (1<<sharp->chipshift))
|
||
|
thislen = (1<<sharp->chipshift) - ofs;
|
||
|
|
||
|
ret = sharp_wait(map,&sharp->chips[chipnum]);
|
||
|
if(ret<0)
|
||
|
break;
|
||
|
|
||
|
map_copy_from(map,buf,ofs,thislen);
|
||
|
|
||
|
sharp_release(&sharp->chips[chipnum]);
|
||
|
|
||
|
*retlen += thislen;
|
||
|
len -= thislen;
|
||
|
buf += thislen;
|
||
|
|
||
|
ofs = 0;
|
||
|
chipnum++;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sharp_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||
|
size_t *retlen, const u_char *buf)
|
||
|
{
|
||
|
struct map_info *map = mtd->priv;
|
||
|
struct sharp_info *sharp = map->fldrv_priv;
|
||
|
int ret = 0;
|
||
|
int i,j;
|
||
|
int chipnum;
|
||
|
unsigned long ofs;
|
||
|
union { u32 l; unsigned char uc[4]; } tbuf;
|
||
|
|
||
|
*retlen = 0;
|
||
|
|
||
|
while(len){
|
||
|
tbuf.l = 0xffffffff;
|
||
|
chipnum = to >> sharp->chipshift;
|
||
|
ofs = to & ((1<<sharp->chipshift)-1);
|
||
|
|
||
|
j=0;
|
||
|
for(i=ofs&3;i<4 && len;i++){
|
||
|
tbuf.uc[i] = *buf;
|
||
|
buf++;
|
||
|
to++;
|
||
|
len--;
|
||
|
j++;
|
||
|
}
|
||
|
sharp_write_oneword(map, &sharp->chips[chipnum], ofs&~3, tbuf.l);
|
||
|
if(ret<0)
|
||
|
return ret;
|
||
|
(*retlen)+=j;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sharp_write_oneword(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr, __u32 datum)
|
||
|
{
|
||
|
int ret;
|
||
|
int timeo;
|
||
|
int try;
|
||
|
int i;
|
||
|
int status = 0;
|
||
|
|
||
|
ret = sharp_wait(map,chip);
|
||
|
|
||
|
for(try=0;try<10;try++){
|
||
|
map_write32(map,CMD_BYTE_WRITE,adr);
|
||
|
/* cpu_to_le32 -> hack to fix the writel be->le conversion */
|
||
|
map_write32(map,cpu_to_le32(datum),adr);
|
||
|
|
||
|
chip->state = FL_WRITING;
|
||
|
|
||
|
timeo = jiffies + (HZ/2);
|
||
|
|
||
|
map_write32(map,CMD_READ_STATUS,adr);
|
||
|
for(i=0;i<100;i++){
|
||
|
status = map_read32(map,adr);
|
||
|
if((status & SR_READY)==SR_READY)
|
||
|
break;
|
||
|
}
|
||
|
if(i==100){
|
||
|
printk("sharp: timed out writing\n");
|
||
|
}
|
||
|
|
||
|
if(!(status&SR_ERRORS))
|
||
|
break;
|
||
|
|
||
|
printk("sharp: error writing byte at addr=%08lx status=%08x\n",adr,status);
|
||
|
|
||
|
map_write32(map,CMD_CLEAR_STATUS,adr);
|
||
|
}
|
||
|
map_write32(map,CMD_RESET,adr);
|
||
|
chip->state = FL_READY;
|
||
|
|
||
|
wake_up(&chip->wq);
|
||
|
spin_unlock_bh(chip->mutex);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||
|
{
|
||
|
struct map_info *map = mtd->priv;
|
||
|
struct sharp_info *sharp = map->fldrv_priv;
|
||
|
unsigned long adr,len;
|
||
|
int chipnum, ret=0;
|
||
|
|
||
|
//printk("sharp_erase()\n");
|
||
|
if(instr->addr & (mtd->erasesize - 1))
|
||
|
return -EINVAL;
|
||
|
if(instr->len & (mtd->erasesize - 1))
|
||
|
return -EINVAL;
|
||
|
if(instr->len + instr->addr > mtd->size)
|
||
|
return -EINVAL;
|
||
|
|
||
|
chipnum = instr->addr >> sharp->chipshift;
|
||
|
adr = instr->addr & ((1<<sharp->chipshift)-1);
|
||
|
len = instr->len;
|
||
|
|
||
|
while(len){
|
||
|
ret = sharp_erase_oneblock(map, &sharp->chips[chipnum], adr);
|
||
|
if(ret)return ret;
|
||
|
|
||
|
adr += mtd->erasesize;
|
||
|
len -= mtd->erasesize;
|
||
|
if(adr >> sharp->chipshift){
|
||
|
adr = 0;
|
||
|
chipnum++;
|
||
|
if(chipnum>=sharp->numchips)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
instr->state = MTD_ERASE_DONE;
|
||
|
mtd_erase_callback(instr);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sharp_do_wait_for_ready(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr)
|
||
|
{
|
||
|
int ret;
|
||
|
unsigned long timeo;
|
||
|
int status;
|
||
|
DECLARE_WAITQUEUE(wait, current);
|
||
|
|
||
|
map_write32(map,CMD_READ_STATUS,adr);
|
||
|
status = map_read32(map,adr);
|
||
|
|
||
|
timeo = jiffies + HZ;
|
||
|
|
||
|
while(time_before(jiffies, timeo)){
|
||
|
map_write32(map,CMD_READ_STATUS,adr);
|
||
|
status = map_read32(map,adr);
|
||
|
if((status & SR_READY)==SR_READY){
|
||
|
ret = 0;
|
||
|
goto out;
|
||
|
}
|
||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||
|
add_wait_queue(&chip->wq, &wait);
|
||
|
|
||
|
//spin_unlock_bh(chip->mutex);
|
||
|
|
||
|
schedule_timeout(1);
|
||
|
schedule();
|
||
|
remove_wait_queue(&chip->wq, &wait);
|
||
|
|
||
|
//spin_lock_bh(chip->mutex);
|
||
|
|
||
|
if (signal_pending(current)){
|
||
|
ret = -EINTR;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
ret = -ETIME;
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr)
|
||
|
{
|
||
|
int ret;
|
||
|
//int timeo;
|
||
|
int status;
|
||
|
//int i;
|
||
|
|
||
|
//printk("sharp_erase_oneblock()\n");
|
||
|
|
||
|
#ifdef AUTOUNLOCK
|
||
|
/* This seems like a good place to do an unlock */
|
||
|
sharp_unlock_oneblock(map,chip,adr);
|
||
|
#endif
|
||
|
|
||
|
map_write32(map,CMD_BLOCK_ERASE_1,adr);
|
||
|
map_write32(map,CMD_BLOCK_ERASE_2,adr);
|
||
|
|
||
|
chip->state = FL_ERASING;
|
||
|
|
||
|
ret = sharp_do_wait_for_ready(map,chip,adr);
|
||
|
if(ret<0)return ret;
|
||
|
|
||
|
map_write32(map,CMD_READ_STATUS,adr);
|
||
|
status = map_read32(map,adr);
|
||
|
|
||
|
if(!(status&SR_ERRORS)){
|
||
|
map_write32(map,CMD_RESET,adr);
|
||
|
chip->state = FL_READY;
|
||
|
//spin_unlock_bh(chip->mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
printk("sharp: error erasing block at addr=%08lx status=%08x\n",adr,status);
|
||
|
map_write32(map,CMD_CLEAR_STATUS,adr);
|
||
|
|
||
|
//spin_unlock_bh(chip->mutex);
|
||
|
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
#ifdef AUTOUNLOCK
|
||
|
static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip,
|
||
|
unsigned long adr)
|
||
|
{
|
||
|
int i;
|
||
|
int status;
|
||
|
|
||
|
map_write32(map,CMD_CLEAR_BLOCK_LOCKS_1,adr);
|
||
|
map_write32(map,CMD_CLEAR_BLOCK_LOCKS_2,adr);
|
||
|
|
||
|
udelay(100);
|
||
|
|
||
|
status = map_read32(map,adr);
|
||
|
printk("status=%08x\n",status);
|
||
|
|
||
|
for(i=0;i<1000;i++){
|
||
|
//map_write32(map,CMD_READ_STATUS,adr);
|
||
|
status = map_read32(map,adr);
|
||
|
if((status & SR_READY)==SR_READY)
|
||
|
break;
|
||
|
udelay(100);
|
||
|
}
|
||
|
if(i==1000){
|
||
|
printk("sharp: timed out unlocking block\n");
|
||
|
}
|
||
|
|
||
|
if(!(status&SR_ERRORS)){
|
||
|
map_write32(map,CMD_RESET,adr);
|
||
|
chip->state = FL_READY;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
printk("sharp: error unlocking block at addr=%08lx status=%08x\n",adr,status);
|
||
|
map_write32(map,CMD_CLEAR_STATUS,adr);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void sharp_sync(struct mtd_info *mtd)
|
||
|
{
|
||
|
//printk("sharp_sync()\n");
|
||
|
}
|
||
|
|
||
|
static int sharp_suspend(struct mtd_info *mtd)
|
||
|
{
|
||
|
printk("sharp_suspend()\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static void sharp_resume(struct mtd_info *mtd)
|
||
|
{
|
||
|
printk("sharp_resume()\n");
|
||
|
|
||
|
}
|
||
|
|
||
|
static void sharp_destroy(struct mtd_info *mtd)
|
||
|
{
|
||
|
printk("sharp_destroy()\n");
|
||
|
|
||
|
}
|
||
|
|
||
|
int __init sharp_probe_init(void)
|
||
|
{
|
||
|
printk("MTD Sharp chip driver <ds@lineo.com>\n");
|
||
|
|
||
|
register_mtd_chip_driver(&sharp_chipdrv);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void __exit sharp_probe_exit(void)
|
||
|
{
|
||
|
unregister_mtd_chip_driver(&sharp_chipdrv);
|
||
|
}
|
||
|
|
||
|
module_init(sharp_probe_init);
|
||
|
module_exit(sharp_probe_exit);
|
||
|
|
||
|
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_AUTHOR("David Schleef <ds@schleef.org>");
|
||
|
MODULE_DESCRIPTION("Old MTD chip driver for pre-CFI Sharp flash chips");
|