diff options
Diffstat (limited to 'drivers/staging/spectra/ffsport.c')
-rw-r--r-- | drivers/staging/spectra/ffsport.c | 827 |
1 files changed, 827 insertions, 0 deletions
diff --git a/drivers/staging/spectra/ffsport.c b/drivers/staging/spectra/ffsport.c new file mode 100644 index 000000000000..d0c5c97eda3e --- /dev/null +++ b/drivers/staging/spectra/ffsport.c @@ -0,0 +1,827 @@ +/* + * NAND Flash Controller Device Driver + * Copyright (c) 2009, Intel Corporation and its suppliers. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "ffsport.h" +#include "flash.h" +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/blkdev.h> +#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/kthread.h> +#include <linux/log2.h> +#include <linux/init.h> + +/**** Helper functions used for Div, Remainder operation on u64 ****/ + +/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& +* Function: GLOB_Calc_Used_Bits +* Inputs: Power of 2 number +* Outputs: Number of Used Bits +* 0, if the argument is 0 +* Description: Calculate the number of bits used by a given power of 2 number +* Number can be upto 32 bit +*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/ +int GLOB_Calc_Used_Bits(u32 n) +{ + int tot_bits = 0; + + if (n >= 1 << 16) { + n >>= 16; + tot_bits += 16; + } + + if (n >= 1 << 8) { + n >>= 8; + tot_bits += 8; + } + + if (n >= 1 << 4) { + n >>= 4; + tot_bits += 4; + } + + if (n >= 1 << 2) { + n >>= 2; + tot_bits += 2; + } + + if (n >= 1 << 1) + tot_bits += 1; + + return ((n == 0) ? (0) : tot_bits); +} + +/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& +* Function: GLOB_u64_Div +* Inputs: Number of u64 +* A power of 2 number as Division +* Outputs: Quotient of the Divisor operation +* Description: It divides the address by divisor by using bit shift operation +* (essentially without explicitely using "/"). +* Divisor is a power of 2 number and Divided is of u64 +*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/ +u64 GLOB_u64_Div(u64 addr, u32 divisor) +{ + return (u64)(addr >> GLOB_Calc_Used_Bits(divisor)); +} + +/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& +* Function: GLOB_u64_Remainder +* Inputs: Number of u64 +* Divisor Type (1 -PageAddress, 2- BlockAddress) +* Outputs: Remainder of the Division operation +* Description: It calculates the remainder of a number (of u64) by +* divisor(power of 2 number ) by using bit shifting and multiply +* operation(essentially without explicitely using "/"). +*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/ +u64 GLOB_u64_Remainder(u64 addr, u32 divisor_type) +{ + u64 result = 0; + + if (divisor_type == 1) { /* Remainder -- Page */ + result = (addr >> DeviceInfo.nBitsInPageDataSize); + result = result * DeviceInfo.wPageDataSize; + } else if (divisor_type == 2) { /* Remainder -- Block */ + result = (addr >> DeviceInfo.nBitsInBlockDataSize); + result = result * DeviceInfo.wBlockDataSize; + } + + result = addr - result; + + return result; +} + +#define NUM_DEVICES 1 +#define PARTITIONS 8 + +#define GLOB_SBD_NAME "nd" +#define GLOB_SBD_IRQ_NUM (29) +#define GLOB_VERSION "driver version 20091110" + +#define GLOB_SBD_IOCTL_GC (0x7701) +#define GLOB_SBD_IOCTL_WL (0x7702) +#define GLOB_SBD_IOCTL_FORMAT (0x7703) +#define GLOB_SBD_IOCTL_ERASE_FLASH (0x7704) +#define GLOB_SBD_IOCTL_FLUSH_CACHE (0x7705) +#define GLOB_SBD_IOCTL_COPY_BLK_TABLE (0x7706) +#define GLOB_SBD_IOCTL_COPY_WEAR_LEVELING_TABLE (0x7707) +#define GLOB_SBD_IOCTL_GET_NAND_INFO (0x7708) +#define GLOB_SBD_IOCTL_WRITE_DATA (0x7709) +#define GLOB_SBD_IOCTL_READ_DATA (0x770A) + +static int reserved_mb = 0; +module_param(reserved_mb, int, 0); +MODULE_PARM_DESC(reserved_mb, "Reserved space for OS image, in MiB (default 25 MiB)"); + +int nand_debug_level; +module_param(nand_debug_level, int, 0644); +MODULE_PARM_DESC(nand_debug_level, "debug level value: 1-3"); + +MODULE_LICENSE("GPL"); + +struct spectra_nand_dev { + struct pci_dev *dev; + u64 size; + u16 users; + spinlock_t qlock; + void __iomem *ioaddr; /* Mapped address */ + struct request_queue *queue; + struct task_struct *thread; + struct gendisk *gd; + u8 *tmp_buf; +}; + + +static int GLOB_SBD_majornum; + +static char *GLOB_version = GLOB_VERSION; + +static struct spectra_nand_dev nand_device[NUM_DEVICES]; + +static struct mutex spectra_lock; + +static int res_blks_os = 1; + +struct spectra_indentfy_dev_tag IdentifyDeviceData; + +static int force_flush_cache(void) +{ + nand_dbg_print(NAND_DBG_DEBUG, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + if (ERR == GLOB_FTL_Flush_Cache()) { + printk(KERN_ERR "Fail to Flush FTL Cache!\n"); + return -EFAULT; + } +#if CMD_DMA + if (glob_ftl_execute_cmds()) + return -EIO; + else + return 0; +#endif + return 0; +} + +struct ioctl_rw_page_info { + u8 *data; + unsigned int page; +}; + +static int ioctl_read_page_data(unsigned long arg) +{ + u8 *buf; + struct ioctl_rw_page_info info; + int result = PASS; + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) + return -EFAULT; + + buf = kmalloc(IdentifyDeviceData.PageDataSize, GFP_ATOMIC); + if (!buf) { + printk(KERN_ERR "ioctl_read_page_data: " + "failed to allocate memory\n"); + return -ENOMEM; + } + + mutex_lock(&spectra_lock); + result = GLOB_FTL_Page_Read(buf, + (u64)info.page * IdentifyDeviceData.PageDataSize); + mutex_unlock(&spectra_lock); + + if (copy_to_user((void __user *)info.data, buf, + IdentifyDeviceData.PageDataSize)) { + printk(KERN_ERR "ioctl_read_page_data: " + "failed to copy user data\n"); + kfree(buf); + return -EFAULT; + } + + kfree(buf); + return result; +} + +static int ioctl_write_page_data(unsigned long arg) +{ + u8 *buf; + struct ioctl_rw_page_info info; + int result = PASS; + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) + return -EFAULT; + + buf = kmalloc(IdentifyDeviceData.PageDataSize, GFP_ATOMIC); + if (!buf) { + printk(KERN_ERR "ioctl_write_page_data: " + "failed to allocate memory\n"); + return -ENOMEM; + } + + if (copy_from_user(buf, (void __user *)info.data, + IdentifyDeviceData.PageDataSize)) { + printk(KERN_ERR "ioctl_write_page_data: " + "failed to copy user data\n"); + kfree(buf); + return -EFAULT; + } + + mutex_lock(&spectra_lock); + result = GLOB_FTL_Page_Write(buf, + (u64)info.page * IdentifyDeviceData.PageDataSize); + mutex_unlock(&spectra_lock); + + kfree(buf); + return result; +} + +/* Return how many blocks should be reserved for bad block replacement */ +static int get_res_blk_num_bad_blk(void) +{ + return IdentifyDeviceData.wDataBlockNum / 10; +} + +/* Return how many blocks should be reserved for OS image */ +static int get_res_blk_num_os(void) +{ + u32 res_blks, blk_size; + + blk_size = IdentifyDeviceData.PageDataSize * + IdentifyDeviceData.PagesPerBlock; + + res_blks = (reserved_mb * 1024 * 1024) / blk_size; + + if ((res_blks < 1) || (res_blks >= IdentifyDeviceData.wDataBlockNum)) + res_blks = 1; /* Reserved 1 block for block table */ + + return res_blks; +} + +static void SBD_prepare_flush(struct request_queue *q, struct request *rq) +{ + rq->cmd_type = REQ_TYPE_LINUX_BLOCK; + /* rq->timeout = 5 * HZ; */ + rq->cmd[0] = REQ_LB_OP_FLUSH; +} + +/* Transfer a full request. */ +static int do_transfer(struct spectra_nand_dev *tr, struct request *req) +{ + u64 start_addr, addr; + u32 logical_start_sect, hd_start_sect; + u32 nsect, hd_sects; + u32 rsect, tsect = 0; + char *buf; + u32 ratio = IdentifyDeviceData.PageDataSize >> 9; + + start_addr = (u64)(blk_rq_pos(req)) << 9; + /* Add a big enough offset to prevent the OS Image from + * being accessed or damaged by file system */ + start_addr += IdentifyDeviceData.PageDataSize * + IdentifyDeviceData.PagesPerBlock * + res_blks_os; + + if (req->cmd_type == REQ_TYPE_LINUX_BLOCK && + req->cmd[0] == REQ_LB_OP_FLUSH) { + if (force_flush_cache()) /* Fail to flush cache */ + return -EIO; + else + return 0; + } + + if (req->cmd_type != REQ_TYPE_FS) + return -EIO; + + if (blk_rq_pos(req) + blk_rq_cur_sectors(req) > get_capacity(tr->gd)) { + printk(KERN_ERR "Spectra error: request over the NAND " + "capacity!sector %d, current_nr_sectors %d, " + "while capacity is %d\n", + (int)blk_rq_pos(req), + blk_rq_cur_sectors(req), + (int)get_capacity(tr->gd)); + return -EIO; + } + + logical_start_sect = start_addr >> 9; + hd_start_sect = logical_start_sect / ratio; + rsect = logical_start_sect - hd_start_sect * ratio; + + addr = (u64)hd_start_sect * ratio * 512; + buf = req->buffer; + nsect = blk_rq_cur_sectors(req); + + if (rsect) + tsect = (ratio - rsect) < nsect ? (ratio - rsect) : nsect; + + switch (rq_data_dir(req)) { + case READ: + /* Read the first NAND page */ + if (rsect) { + if (GLOB_FTL_Page_Read(tr->tmp_buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + memcpy(buf, tr->tmp_buf + (rsect << 9), tsect << 9); + addr += IdentifyDeviceData.PageDataSize; + buf += tsect << 9; + nsect -= tsect; + } + + /* Read the other NAND pages */ + for (hd_sects = nsect / ratio; hd_sects > 0; hd_sects--) { + if (GLOB_FTL_Page_Read(buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + addr += IdentifyDeviceData.PageDataSize; + buf += IdentifyDeviceData.PageDataSize; + } + + /* Read the last NAND pages */ + if (nsect % ratio) { + if (GLOB_FTL_Page_Read(tr->tmp_buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + memcpy(buf, tr->tmp_buf, (nsect % ratio) << 9); + } +#if CMD_DMA + if (glob_ftl_execute_cmds()) + return -EIO; + else + return 0; +#endif + return 0; + + case WRITE: + /* Write the first NAND page */ + if (rsect) { + if (GLOB_FTL_Page_Read(tr->tmp_buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + memcpy(tr->tmp_buf + (rsect << 9), buf, tsect << 9); + if (GLOB_FTL_Page_Write(tr->tmp_buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + addr += IdentifyDeviceData.PageDataSize; + buf += tsect << 9; + nsect -= tsect; + } + + /* Write the other NAND pages */ + for (hd_sects = nsect / ratio; hd_sects > 0; hd_sects--) { + if (GLOB_FTL_Page_Write(buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + addr += IdentifyDeviceData.PageDataSize; + buf += IdentifyDeviceData.PageDataSize; + } + + /* Write the last NAND pages */ + if (nsect % ratio) { + if (GLOB_FTL_Page_Read(tr->tmp_buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + memcpy(tr->tmp_buf, buf, (nsect % ratio) << 9); + if (GLOB_FTL_Page_Write(tr->tmp_buf, addr)) { + printk(KERN_ERR "Error in %s, Line %d\n", + __FILE__, __LINE__); + return -EIO; + } + } +#if CMD_DMA + if (glob_ftl_execute_cmds()) + return -EIO; + else + return 0; +#endif + return 0; + + default: + printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req)); + return -EIO; + } +} + +/* This function is copied from drivers/mtd/mtd_blkdevs.c */ +static int spectra_trans_thread(void *arg) +{ + struct spectra_nand_dev *tr = arg; + struct request_queue *rq = tr->queue; + struct request *req = NULL; + + /* we might get involved when memory gets low, so use PF_MEMALLOC */ + current->flags |= PF_MEMALLOC; + + spin_lock_irq(rq->queue_lock); + while (!kthread_should_stop()) { + int res; + + if (!req) { + req = blk_fetch_request(rq); + if (!req) { + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(rq->queue_lock); + schedule(); + spin_lock_irq(rq->queue_lock); + continue; + } + } + + spin_unlock_irq(rq->queue_lock); + + mutex_lock(&spectra_lock); + res = do_transfer(tr, req); + mutex_unlock(&spectra_lock); + + spin_lock_irq(rq->queue_lock); + + if (!__blk_end_request_cur(req, res)) + req = NULL; + } + + if (req) + __blk_end_request_all(req, -EIO); + + spin_unlock_irq(rq->queue_lock); + + return 0; +} + + +/* Request function that "handles clustering". */ +static void GLOB_SBD_request(struct request_queue *rq) +{ + struct spectra_nand_dev *pdev = rq->queuedata; + wake_up_process(pdev->thread); +} + +static int GLOB_SBD_open(struct block_device *bdev, fmode_t mode) + +{ + nand_dbg_print(NAND_DBG_WARN, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + return 0; +} + +static int GLOB_SBD_release(struct gendisk *disk, fmode_t mode) +{ + int ret; + + nand_dbg_print(NAND_DBG_WARN, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + mutex_lock(&spectra_lock); + ret = force_flush_cache(); + mutex_unlock(&spectra_lock); + + return 0; +} + +static int GLOB_SBD_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + geo->heads = 4; + geo->sectors = 16; + geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + + nand_dbg_print(NAND_DBG_DEBUG, + "heads: %d, sectors: %d, cylinders: %d\n", + geo->heads, geo->sectors, geo->cylinders); + + return 0; +} + +int GLOB_SBD_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + int ret; + + nand_dbg_print(NAND_DBG_TRACE, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + switch (cmd) { + case GLOB_SBD_IOCTL_GC: + nand_dbg_print(NAND_DBG_DEBUG, + "Spectra IOCTL: Garbage Collection " + "being performed\n"); + if (PASS != GLOB_FTL_Garbage_Collection()) + return -EFAULT; + return 0; + + case GLOB_SBD_IOCTL_WL: + nand_dbg_print(NAND_DBG_DEBUG, + "Spectra IOCTL: Static Wear Leveling " + "being performed\n"); + if (PASS != GLOB_FTL_Wear_Leveling()) + return -EFAULT; + return 0; + + case GLOB_SBD_IOCTL_FORMAT: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: Flash format " + "being performed\n"); + if (PASS != GLOB_FTL_Flash_Format()) + return -EFAULT; + return 0; + + case GLOB_SBD_IOCTL_FLUSH_CACHE: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: Cache flush " + "being performed\n"); + mutex_lock(&spectra_lock); + ret = force_flush_cache(); + mutex_unlock(&spectra_lock); + return ret; + + case GLOB_SBD_IOCTL_COPY_BLK_TABLE: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: " + "Copy block table\n"); + if (copy_to_user((void __user *)arg, + get_blk_table_start_addr(), + get_blk_table_len())) + return -EFAULT; + return 0; + + case GLOB_SBD_IOCTL_COPY_WEAR_LEVELING_TABLE: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: " + "Copy wear leveling table\n"); + if (copy_to_user((void __user *)arg, + get_wear_leveling_table_start_addr(), + get_wear_leveling_table_len())) + return -EFAULT; + return 0; + + case GLOB_SBD_IOCTL_GET_NAND_INFO: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: " + "Get NAND info\n"); + if (copy_to_user((void __user *)arg, &IdentifyDeviceData, + sizeof(IdentifyDeviceData))) + return -EFAULT; + return 0; + + case GLOB_SBD_IOCTL_WRITE_DATA: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: " + "Write one page data\n"); + return ioctl_write_page_data(arg); + + case GLOB_SBD_IOCTL_READ_DATA: + nand_dbg_print(NAND_DBG_DEBUG, "Spectra IOCTL: " + "Read one page data\n"); + return ioctl_read_page_data(arg); + } + + return -ENOTTY; +} + +static struct block_device_operations GLOB_SBD_ops = { + .owner = THIS_MODULE, + .open = GLOB_SBD_open, + .release = GLOB_SBD_release, + .locked_ioctl = GLOB_SBD_ioctl, + .getgeo = GLOB_SBD_getgeo, +}; + +static int SBD_setup_device(struct spectra_nand_dev *dev, int which) +{ + int res_blks; + u32 sects; + + nand_dbg_print(NAND_DBG_TRACE, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + memset(dev, 0, sizeof(struct spectra_nand_dev)); + + nand_dbg_print(NAND_DBG_WARN, "Reserved %d blocks " + "for OS image, %d blocks for bad block replacement.\n", + get_res_blk_num_os(), + get_res_blk_num_bad_blk()); + + res_blks = get_res_blk_num_bad_blk() + get_res_blk_num_os(); + + dev->size = (u64)IdentifyDeviceData.PageDataSize * + IdentifyDeviceData.PagesPerBlock * + (IdentifyDeviceData.wDataBlockNum - res_blks); + + res_blks_os = get_res_blk_num_os(); + + spin_lock_init(&dev->qlock); + + dev->tmp_buf = kmalloc(IdentifyDeviceData.PageDataSize, GFP_ATOMIC); + if (!dev->tmp_buf) { + printk(KERN_ERR "Failed to kmalloc memory in %s Line %d, exit.\n", + __FILE__, __LINE__); + goto out_vfree; + } + + dev->queue = blk_init_queue(GLOB_SBD_request, &dev->qlock); + if (dev->queue == NULL) { + printk(KERN_ERR + "Spectra: Request queue could not be initialized." + " Aborting\n "); + goto out_vfree; + } + dev->queue->queuedata = dev; + + /* As Linux block layer doens't support >4KB hardware sector, */ + /* Here we force report 512 byte hardware sector size to Kernel */ + blk_queue_logical_block_size(dev->queue, 512); + + blk_queue_ordered(dev->queue, QUEUE_ORDERED_DRAIN_FLUSH, + SBD_prepare_flush); + + dev->thread = kthread_run(spectra_trans_thread, dev, "nand_thd"); + if (IS_ERR(dev->thread)) { + blk_cleanup_queue(dev->queue); + unregister_blkdev(GLOB_SBD_majornum, GLOB_SBD_NAME); + return PTR_ERR(dev->thread); + } + + dev->gd = alloc_disk(PARTITIONS); + if (!dev->gd) { + printk(KERN_ERR + "Spectra: Could not allocate disk. Aborting \n "); + goto out_vfree; + } + dev->gd->major = GLOB_SBD_majornum; + dev->gd->first_minor = which * PARTITIONS; + dev->gd->fops = &GLOB_SBD_ops; + dev->gd->queue = dev->queue; + dev->gd->private_data = dev; + snprintf(dev->gd->disk_name, 32, "%s%c", GLOB_SBD_NAME, which + 'a'); + + sects = dev->size >> 9; + nand_dbg_print(NAND_DBG_WARN, "Capacity sects: %d\n", sects); + set_capacity(dev->gd, sects); + + add_disk(dev->gd); + + return 0; +out_vfree: + return -ENOMEM; +} + +/* +static ssize_t show_nand_block_num(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", + (int)IdentifyDeviceData.wDataBlockNum); +} + +static ssize_t show_nand_pages_per_block(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", + (int)IdentifyDeviceData.PagesPerBlock); +} + +static ssize_t show_nand_page_size(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", + (int)IdentifyDeviceData.PageDataSize); +} + +static DEVICE_ATTR(nand_block_num, 0444, show_nand_block_num, NULL); +static DEVICE_ATTR(nand_pages_per_block, 0444, show_nand_pages_per_block, NULL); +static DEVICE_ATTR(nand_page_size, 0444, show_nand_page_size, NULL); + +static void create_sysfs_entry(struct device *dev) +{ + if (device_create_file(dev, &dev_attr_nand_block_num)) + printk(KERN_ERR "Spectra: " + "failed to create sysfs entry nand_block_num.\n"); + if (device_create_file(dev, &dev_attr_nand_pages_per_block)) + printk(KERN_ERR "Spectra: " + "failed to create sysfs entry nand_pages_per_block.\n"); + if (device_create_file(dev, &dev_attr_nand_page_size)) + printk(KERN_ERR "Spectra: " + "failed to create sysfs entry nand_page_size.\n"); +} +*/ + +static int GLOB_SBD_init(void) +{ + int i; + + /* Set debug output level (0~3) here. 3 is most verbose */ + printk(KERN_ALERT "Spectra: %s\n", GLOB_version); + + mutex_init(&spectra_lock); + + GLOB_SBD_majornum = register_blkdev(0, GLOB_SBD_NAME); + if (GLOB_SBD_majornum <= 0) { + printk(KERN_ERR "Unable to get the major %d for Spectra", + GLOB_SBD_majornum); + return -EBUSY; + } + + if (PASS != GLOB_FTL_Flash_Init()) { + printk(KERN_ERR "Spectra: Unable to Initialize Flash Device. " + "Aborting\n"); + goto out_flash_register; + } + + /* create_sysfs_entry(&dev->dev); */ + + if (PASS != GLOB_FTL_IdentifyDevice(&IdentifyDeviceData)) { + printk(KERN_ERR "Spectra: Unable to Read Flash Device. " + "Aborting\n"); + goto out_flash_register; + } else { + nand_dbg_print(NAND_DBG_WARN, "In GLOB_SBD_init: " + "Num blocks=%d, pagesperblock=%d, " + "pagedatasize=%d, ECCBytesPerSector=%d\n", + (int)IdentifyDeviceData.NumBlocks, + (int)IdentifyDeviceData.PagesPerBlock, + (int)IdentifyDeviceData.PageDataSize, + (int)IdentifyDeviceData.wECCBytesPerSector); + } + + printk(KERN_ALERT "Spectra: searching block table, please wait ...\n"); + if (GLOB_FTL_Init() != PASS) { + printk(KERN_ERR "Spectra: Unable to Initialize FTL Layer. " + "Aborting\n"); + goto out_ftl_flash_register; + } + printk(KERN_ALERT "Spectra: block table has been found.\n"); + + for (i = 0; i < NUM_DEVICES; i++) + if (SBD_setup_device(&nand_device[i], i) == -ENOMEM) + goto out_ftl_flash_register; + + nand_dbg_print(NAND_DBG_DEBUG, + "Spectra: module loaded with major number %d\n", + GLOB_SBD_majornum); + + return 0; + +out_ftl_flash_register: + GLOB_FTL_Cache_Release(); +out_flash_register: + GLOB_FTL_Flash_Release(); + unregister_blkdev(GLOB_SBD_majornum, GLOB_SBD_NAME); + printk(KERN_ERR "Spectra: Module load failed.\n"); + + return -ENOMEM; +} + +static void __exit GLOB_SBD_exit(void) +{ + int i; + + nand_dbg_print(NAND_DBG_TRACE, "%s, Line %d, Function: %s\n", + __FILE__, __LINE__, __func__); + + for (i = 0; i < NUM_DEVICES; i++) { + struct spectra_nand_dev *dev = &nand_device[i]; + if (dev->gd) { + del_gendisk(dev->gd); + put_disk(dev->gd); + } + if (dev->queue) + blk_cleanup_queue(dev->queue); + kfree(dev->tmp_buf); + } + + unregister_blkdev(GLOB_SBD_majornum, GLOB_SBD_NAME); + + mutex_lock(&spectra_lock); + force_flush_cache(); + mutex_unlock(&spectra_lock); + + GLOB_FTL_Cache_Release(); + + GLOB_FTL_Flash_Release(); + + nand_dbg_print(NAND_DBG_DEBUG, + "Spectra FTL module (major number %d) unloaded.\n", + GLOB_SBD_majornum); +} + +module_init(GLOB_SBD_init); +module_exit(GLOB_SBD_exit); |