blob: 03e63461de64baeafa0f67620f24b5ce4f7cb2b4 [file] [log] [blame]
/*
* 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 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 <ashutoshp@nvidia.com>");
MODULE_LICENSE("GPL v2");