/* * drivers/char/tegra_pflash.c * * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. * * 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, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Please extend above function to support diffrent bank widths */ static inline map_word convert_map_word(const unsigned long data) { map_word r; r.x[0] = data; return r; } static inline int cfi2_chip_ready(unsigned int index, unsigned long sectAddr) { struct map_info *map; map_word first_read, second_read; map = get_map_info(index); first_read = pflash_nor_read(map, sectAddr); second_read = pflash_nor_read(map, sectAddr); pr_debug("Consecutive reads @ physical address" \ "0x%lx gave 0x%lx and 0x%lx\n", (flash_data.chipsize*index)+sectAddr, first_read.x[0], second_read.x[0]); if (map_word_equal(map, first_read, second_read)) return 1; else return 0; } static inline int cfi2_chip_good(unsigned int index, unsigned long sectAddr, unsigned long expected) { struct map_info *map; map_word first_read, second_read; map = get_map_info(index); mb(); first_read = pflash_nor_read(map, sectAddr); second_read = pflash_nor_read(map, sectAddr); pr_debug("Consecutive reads @ physical address " \ "0x%lx gave 0x%lx and 0x%lx\n", (flash_data.chipsize*index)+sectAddr, first_read.x[0], second_read.x[0]); pr_debug("Expected value : 0x%lx\n", expected); if (((map_word_equal(map, first_read, second_read))) && (second_read.x[0] == expected)) return 1; cfi_udelay(100); return 0; } static void cfi2_write_buffer(unsigned int index, unsigned long sectAddr, void *buffer) { int i; struct map_info *map; map_word datum; unsigned long *data = (unsigned long *)buffer; map = get_map_info(index); pflash_nor_write(map, convert_map_word(0x00AA00AA), 0x555 * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00550055), 0x2AA * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00250025), sectAddr); pflash_nor_write(map, convert_map_word(0x00FF00FF), sectAddr); /* move length -words of data to the chip */ for (i = 0; i < AMD_WRITE_BUFFER_SIZE; i++) { datum = map_word_load(map, data); pflash_nor_write(map, datum, sectAddr + (i*4)); data++; } /* Command to start flushing buffer to sectors */ pflash_nor_write(map, convert_map_word(0x00290029), sectAddr); } static void cfi2_erase_block(unsigned int index, unsigned long sectAddr) { struct map_info *map; map = get_map_info(index); pflash_nor_write(map, convert_map_word(0x00AA00AA), 0x555 * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00550055), 0x2AA * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00800080), 0x555 * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00AA00AA), 0x555 * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00550055), 0x2AA * map->bankwidth); pflash_nor_write(map, convert_map_word(0x00300030), sectAddr); } static char *write_buffer[MAX_CHIPS]; static long parallel_flash_writer_ioctl(struct file *filep, unsigned int ioctlno, unsigned long addr) { unsigned int chip_no, blk_no; struct pflash_data pdata; unsigned long buf_no, timeout, loc, status; switch (ioctlno) { case PFLASH_CFI2: if (copy_from_user(&pdata, (void *)addr, sizeof(struct pflash_data))) return -EFAULT; /* Reject the buffer if not multiple of 1MB */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if ((pdata.chip_numbyte[chip_no] & (SZ_1M - 1)) != 0) { printk(KERN_ERR"Buffer is not 1MB aligned." \ "Cannot write buffer to flash.\n"); return -EIO; } } /* Get user space buffers */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if (!pdata.chip_erase[chip_no]) continue; if (copy_from_user(write_buffer[chip_no], (void *)pdata.buffer[chip_no], CHIP_BUFFER_SIZE)) return -EFAULT; } /* Send erase command to all relevent sectors */ for (blk_no = 0; blk_no < CHIP_BUFFER_SIZE; blk_no += flash_data.erasesize) { /* Give erase command to all chips */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { pr_debug("Giving erase commands to all chips.\n"); /* Give erase only if the sector is required */ if (!pdata.chip_erase[chip_no]) continue; cfi2_erase_block(chip_no, blk_no + pdata.chip_ofs[chip_no]); pdata.start_prog[chip_no] = 0; } pr_debug("Erase command sent," \ "checking whether chip is ready\n"); /* 875ms is document as per datasheet */ mdelay(1500); /* Check the status of erase * Wait for chip to be ready */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if (!pdata.chip_erase[chip_no]) continue; /* timeout is 4 seconds */ timeout = jiffies + 4 * HZ; do { loc = blk_no + pdata.chip_ofs[chip_no]; status = cfi2_chip_ready(chip_no, loc); } while (!status && time_after(jiffies, timeout)); if (!status) return -EIO; } /* check erase is successful */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if (!pdata.chip_erase[chip_no]) continue; /* timeout is 10 seconds */ timeout = jiffies + 10 * HZ; do { loc = blk_no + pdata.chip_ofs[chip_no]; status = cfi2_chip_good(chip_no, loc, FLASH_ERASED_VALUE); } while (!status && time_after(jiffies, timeout)); if (!status) return -EIO; /* Declare chip OK to be programmed */ pdata.start_prog[chip_no] = 1; } pr_debug("Erase over. Giving program commands\n"); /* For writing to one sector to flash, program all chips erase/buff_size times */ for (buf_no = 0; buf_no < flash_data.erasesize; buf_no += AMD_WRITE_BUFFER_SIZE * sizeof(unsigned long)) { /* Send program buffer command to all chips */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if (!pdata.start_prog[chip_no]) continue; loc = blk_no + buf_no; cfi2_write_buffer(chip_no, pdata.chip_ofs[chip_no] + loc, write_buffer[chip_no] + loc); } /* Giving delay of more than 340us for program to complete */ cfi_udelay(1500); pr_debug("Program commands sent." \ "Checking whether chip is ready\n"); /* Check status of chip */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if (!pdata.start_prog[chip_no]) continue; /* Timeout set to 4 second */ timeout = jiffies + 4 * HZ; do { loc = pdata.chip_ofs[chip_no] + blk_no + buf_no; status = cfi2_chip_ready( chip_no, loc); } while (!status && time_after(jiffies, timeout)); if (!status) return -EIO; } /* Verify atleast first word is written */ for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { if (!pdata.start_prog[chip_no]) continue; /* Timeout set to 7 second */ timeout = jiffies + 7 * HZ; do { unsigned long data; loc = blk_no + buf_no; data = *(unsigned long *) ((unsigned long) write_buffer[chip_no] + loc); status = cfi2_chip_good( chip_no, pdata.chip_ofs[chip_no] + loc, data); } while (!status && time_after(jiffies, timeout)); if (!status) return -EIO; mb(); } mb(); } pr_debug("Programming complete." \ "Going for next sector\n"); } break; case PFLASH_GET_MAXCHIPS: return flash_num_chips; case PFLASH_CHIP_INFO: if (copy_to_user((void *)addr, (void *)&flash_data, sizeof(struct nv_flash_data))) return -EFAULT; } return 0; } static int parallel_flash_writer_open(struct inode *inp, struct file *filep) { int chip_no; struct map_info *map; /* Get map info of first bank */ map = get_map_info(0); flash_data.chipsize = map->size; flash_data.max_num_chips = get_maps_no(); flash_data.erasesize = ERASESIZE; flash_data.flash_total_size = getflashsize(); flash_data.cmd_set_type = COMMAND_SET_TYPE; /* Fill up bank_list, device_list (GLOBALS) */ flash_num_chips = get_maps_no(); for (chip_no = 0; chip_no < flash_num_chips; chip_no++) { write_buffer[chip_no] = kmalloc(CHIP_BUFFER_SIZE, GFP_KERNEL); if (!write_buffer[chip_no]) return -ENOMEM; } return 0; } static int parallel_flash_writer_release(struct inode *inp, struct file *filep) { int chip_no; for (chip_no = 0; chip_no < flash_num_chips; chip_no++) kfree(write_buffer[chip_no]); return 0; } static const struct file_operations pflash_fops = { .owner = THIS_MODULE, .open = parallel_flash_writer_open, .release = parallel_flash_writer_release, .unlocked_ioctl = parallel_flash_writer_ioctl, }; static void pflash_cleanup(void) { cdev_del(&pflash_cdev); device_destroy(pflash_class, MKDEV(pflash_major, PFLASH_MINOR)); if (pflash_class) class_destroy(pflash_class); unregister_chrdev_region(MKDEV(pflash_major, PFLASH_MINOR), PFLASH_DEVICE_NO); } static int __init parallel_flash_writer_init(void) { int result; int ret = -ENODEV; dev_t pflash_dev ; printk(KERN_INFO "Pflash driver.\n"); result = alloc_chrdev_region(&pflash_dev, 0, PFLASH_DEVICE_NO, PFLASH_DEVICE); pflash_major = MAJOR(pflash_dev); if (result < 0) { printk(KERN_ERR "alloc_chrdev_region() failed for pflash\n"); goto fail_err; } /* Register a character device. */ cdev_init(&pflash_cdev, &pflash_fops); pflash_cdev.owner = THIS_MODULE; pflash_cdev.ops = &pflash_fops; result = cdev_add(&pflash_cdev, pflash_dev, PFLASH_DEVICE_NO); if (result < 0) goto fail_chrdev; pflash_class = class_create(THIS_MODULE, PFLASH_DEVICE); if (IS_ERR(pflash_class)) { pr_err(KERN_ERR "pflash: device class file already in use.\n"); pflash_cleanup(); return PTR_ERR(pflash_class); } device_create(pflash_class, NULL, MKDEV(pflash_major, PFLASH_MINOR), NULL, "%s", PFLASH_DEVICE); return 0; fail_chrdev: unregister_chrdev_region(pflash_dev, PFLASH_DEVICE_NO); fail_err: return ret; } static void __exit parallel_flash_writer_exit(void) { pflash_cleanup(); } module_init(parallel_flash_writer_init); module_exit(parallel_flash_writer_exit); MODULE_AUTHOR("Ashutosh Patel "); MODULE_LICENSE("GPL v2");