diff options
author | Ashutosh Patel <ashutoshp@nvidia.com> | 2013-04-27 15:21:14 +0530 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2013-09-14 13:10:45 -0700 |
commit | 6caaafc7a97686e255faad5a3b92d6a02282be5d (patch) | |
tree | 8f15e561895debea3042ee64a8fd0d8f1ec355e7 /drivers/char | |
parent | 5d0e325acf460046ec4efe636b6d4f031bc6c7bb (diff) |
pflash: tegra pflash driver
- Added support for tegra pflash driver
bug 1182131
Change-Id: I67167057f288c3e661e8144021a1153985afe484
Signed-off-by: Ashutosh Patel <ashutoshp@nvidia.com>
Reviewed-on: http://git-master/r/218099
Reviewed-by: Nitin Sehgal <nsehgal@nvidia.com>
Tested-by: Nitin Sehgal <nsehgal@nvidia.com>
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/Kconfig | 8 | ||||
-rw-r--r-- | drivers/char/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/tegra_pflash.c | 454 |
3 files changed, 463 insertions, 0 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 4392d9143b5c..bcea982f209a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -642,4 +642,12 @@ config TEGRA_GMI_CHAR depends on ARCH_TEGRA help Enable tegra GMI char driver for Nvidia's board. + +config TEGRA_PFLASH + bool "Character-device interface for pflash" + depends on ARCH_TEGRA + default n + help + Enable tegra pflash helper driver for Nvidia's board. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 8b0415dd3870..95388f0d0ba8 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -64,5 +64,6 @@ js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o obj-$(CONFIG_TEGRA_EFS) += tegra-efshlp.o +obj-$(CONFIG_TEGRA_PFLASH) += tegra_pflash.o obj-$(CONFIG_TEGRA_GMI_CHAR) += tegra_gmi_char.o obj-$(CONFIG_TEGRA_GMI_ACCESS_CONTROL) += tegra_gmi_access.o diff --git a/drivers/char/tegra_pflash.c b/drivers/char/tegra_pflash.c new file mode 100644 index 000000000000..c8dc1d16147a --- /dev/null +++ b/drivers/char/tegra_pflash.c @@ -0,0 +1,454 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/mtd/gen_probe.h> +#include <linux/mtd/cfi.h> +#include <linux/mtd/map.h> +#include <linux/tegra_snor.h> +#include <linux/platform_data/tegra_nor.h> +#include <linux/mutex.h> +#include <linux/tegra_pflash.h> + +/* 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 2 seconds */ + timeout = jiffies + 2 * 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(1000); + + 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 2 second */ + timeout = jiffies + 2 * 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 3 second */ + timeout = jiffies + 5 * 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 <ashutoshp@nvidia.com>"); +MODULE_LICENSE("GPL v2"); |