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 | |
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>
-rw-r--r-- | drivers/char/Kconfig | 8 | ||||
-rw-r--r-- | drivers/char/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/tegra_pflash.c | 454 | ||||
-rw-r--r-- | drivers/mtd/maps/tegra_nor_gmi.c | 4 | ||||
-rw-r--r-- | include/linux/tegra_pflash.h | 74 | ||||
-rw-r--r-- | include/linux/tegra_snor.h | 13 |
6 files changed, 552 insertions, 2 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"); diff --git a/drivers/mtd/maps/tegra_nor_gmi.c b/drivers/mtd/maps/tegra_nor_gmi.c index 306dfd932546..76ef58705e26 100644 --- a/drivers/mtd/maps/tegra_nor_gmi.c +++ b/drivers/mtd/maps/tegra_nor_gmi.c @@ -332,7 +332,7 @@ void tegra_nor_copy_from(struct map_info *map, } -static map_word tegra_nor_read(struct map_info *map, +map_word tegra_nor_read(struct map_info *map, unsigned long ofs) { map_word ret; @@ -354,7 +354,7 @@ static map_word tegra_nor_read(struct map_info *map, return ret; } -static void tegra_nor_write(struct map_info *map, +void tegra_nor_write(struct map_info *map, map_word datum, unsigned long ofs) { diff --git a/include/linux/tegra_pflash.h b/include/linux/tegra_pflash.h new file mode 100644 index 000000000000..d5eea4608657 --- /dev/null +++ b/include/linux/tegra_pflash.h @@ -0,0 +1,74 @@ +/* + * include/linux/tegra_pflash.h + * + * 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/>. + * + */ + +#ifndef _TEGRA_PFLASH_H_ +#define _TEGRA_PFLASH_H_ + +#define MAX_CHIPS 16 +struct pflash_data { + char *buffer[MAX_CHIPS]; + unsigned long chip_ofs[MAX_CHIPS]; + unsigned int chip_numbyte[MAX_CHIPS]; + char start_prog[MAX_CHIPS]; + char chip_erase[MAX_CHIPS]; + unsigned long chip_erase_size[MAX_CHIPS]; +}; +struct nv_flash_data { + unsigned long erasesize; + unsigned long chipsize; + int cmd_set_type; + unsigned long long flash_total_size; + int max_num_chips; +}; +#define ERASESIZE 0x80000 +#define COMMAND_SET_TYPE 0x02 + +/* IOCTL numbers */ +#define PFLASH_MAGIC 0x85 +#define PFLASH_CFI2 _IOW(PFLASH_MAGIC, 2, struct pflash_data) +#define PFLASH_GET_MAXCHIPS _IO(PFLASH_MAGIC, 3) +#define PFLASH_CHIP_INFO _IOR(PFLASH_MAGIC, 4, struct nv_flash_data) + +#define CHIP_BUFFER_SIZE 1048576 +#define AMD_WRITE_BUFFER_SIZE 256 +#define FLASH_ERASED_VALUE 0xffffffff + +static int pflash_major; +static struct cdev pflash_cdev; +static struct class *pflash_class; + +#define PFLASH_DEVICE "pflash" +#define PFLASH_DEVICE_NO 1 +#define PFLASH_MAJOR 0 /* Get dynamically */ +#define PFLASH_MINOR 0 + +/* extern struct pflash_device pflash_device_info; */ +static unsigned int flash_num_chips; +struct nv_flash_data flash_data; + +#define pflash_nor_write(map, write_value, sectaddr) \ +({ tegra_nor_write(map, write_value, sectaddr); mb(); }) + +#define pflash_nor_read(map, sectaddr) \ + ({ map_word __readval; \ + __readval = tegra_nor_read(map, sectaddr); \ + mb(); __readval; }) + + +#endif diff --git a/include/linux/tegra_snor.h b/include/linux/tegra_snor.h index 59ae31be2c5d..355ee6126d45 100644 --- a/include/linux/tegra_snor.h +++ b/include/linux/tegra_snor.h @@ -23,6 +23,13 @@ * MTD mapping driver for the internal SNOR controller in Tegra SoCs * */ + +#ifndef TEGRA_SNOR +#define TGERA_SNOR + +#include<linux/mtd/map.h> + + #define __BITMASK0(len) (BIT(len) - 1) #define REG_FIELD(val, start, len) (((val) & __BITMASK0(len)) << (start)) #define REG_GET_FIELD(val, start, len) (((val) >> (start)) & __BITMASK0(len)) @@ -93,6 +100,12 @@ void tegra_nor_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len); +void tegra_nor_write(struct map_info *map, + map_word datum, unsigned long ofs); +map_word tegra_nor_read(struct map_info *map, + unsigned long ofs); struct map_info *get_map_info(unsigned int bank_index); int get_maps_no(void); unsigned long long getflashsize(void); + +#endif |