summaryrefslogtreecommitdiff
path: root/drivers/char
diff options
context:
space:
mode:
authorAshutosh Patel <ashutoshp@nvidia.com>2013-04-27 15:21:14 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2013-09-14 13:10:45 -0700
commit6caaafc7a97686e255faad5a3b92d6a02282be5d (patch)
tree8f15e561895debea3042ee64a8fd0d8f1ec355e7 /drivers/char
parent5d0e325acf460046ec4efe636b6d4f031bc6c7bb (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/Kconfig8
-rw-r--r--drivers/char/Makefile1
-rw-r--r--drivers/char/tegra_pflash.c454
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");