天天看點

SPI flash D.TC.S25FL064A (W25Q64FV)驅動

D.TC.S25FL064A (W25Q64FV)是一個串行的spiflash,我們用于存儲fpga程式(epcs),fpga上電時通過spi從該flash讀取程式

進行fpga線上更新時,通過cpu的spi接口進行讀寫,該晶片隻有一組spi接口,是以需要一個選路器OE進行選路,當cpu的gpio11拉低時,CPU的spi控制有效

當gpio11拉高時,FPGA的spi有效

由于cpu spi的差異,需要适當修改spi接口

以下是W25Q64在CPU BCM53003(mips架構)的驅動

epcs.c

#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include "w25p16.h"
#include <linux/semaphore.h>
#include <typedefs.h>
#include <bcmutils.h>
#include <siutils.h>
#include <bcmdevs.h>
#include <linux_gpio.h>

#define  W25_ERASE_CHIP           	9
#define  W25_ERASE_SECTOR     		10
#define  W25P16_READ		  	11
#define   W25P16_WRITE	   		12
#define   W25P1165_ID    		13
#define SPI_IOC_OPER_FLASH              14
#define SPI_IOC_OPER_FLASH_DONE         15

static void *gpio_sih;
struct w25p flash;
#undef DEBUG_FPGA 

#ifdef DEBUG_FPGA
#define debugk(fmt,args...) printk(fmt ,##args)
#else
#define debugk(fmt,args...)
#endif


/*epcs  device struct */
struct epcs_dev_t {
	struct cdev *pdev;
	struct spi_device *spi;
	int devno;
};

static struct epcs_dev_t *epcs = NULL;


static ssize_t epcs_cdev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	return 0;
}

static ssize_t epcs_cdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	return 0;
}


static long epcs_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	w25_rw_date_t  w25p16_date;
	size_t retval = 0;
	size_t retlen = 0;
	unsigned int tmp, ret;

	si_gpioout(gpio_sih, 1<<11, 0<<11,GPIO_HI_PRIORITY);
	si_gpioouten(gpio_sih, 1<<11, 1<<11,GPIO_HI_PRIORITY);//0 mains do not clear other bits
//return 0;
	switch (cmd) {
	case W25_ERASE_CHIP:
		//printk("\nnow erase chip\n");
		retval = erase_chip(&flash);
		break;
	case W25_ERASE_SECTOR:
		retval = __get_user(tmp, (__u32 __user *)arg);
		if(retval == 0)
		  {	
			//printk("\n ++++tmp = 0x%08x+++++\n", tmp); 
			retval = erase_sector(&flash, tmp); 
		  }
		break;

	case W25P16_READ:
		retval =  copy_from_user(&w25p16_date, (w25_rw_date_t *)arg, sizeof(w25_rw_date_t));
		if(retval != 0)
			break;	
		//printk("\nnow read chip:w25p16_date.addr = 0x%08x, w25p16_date.len = 0x%08x \n", (u32)w25p16_date.addr,w25p16_date.len);	    
		retval  =  w25p16_read(&flash , w25p16_date.addr, w25p16_date.len, &retlen, w25p16_date.buf);
		#if 0
			ret =  w25p16_read(&flash , 0x100000, 0xff, &retlen, buf_t);
        		if(ret)
	   			printk("\n+++++++++\erase error ret = %d ++++\n", ret);
                #endif
		#if 0
		   {
		   	int i = 0;
			for(i = 0; i < 0x10; i++)
	 		{
				printk(" buf[%d ] = 0x%02x  ",i,   w25p16_date.buf[i]);
				if(i % 8 == 0)
				printk("\n");
         		 }

		   }
		#endif
		if(retval == 0)
		{
			retval = copy_to_user((w25_rw_date_t *)arg, &w25p16_date, sizeof(w25_rw_date_t));
		}
//                msleep(1);      /*this is needed,else cost a long time 2015-8-20 zhangjj */

		break;

	case W25P16_WRITE:
		retval =  copy_from_user(&w25p16_date, (w25_rw_date_t *)arg, sizeof(w25_rw_date_t));
		if(retval != 0)
			break;	
		retval  =  w25p16_write(&flash , w25p16_date.addr, w25p16_date.len, &retlen, w25p16_date.buf);
		if(retval == 0)
		{
			retval = copy_to_user((w25_rw_date_t *)arg, &w25p16_date, sizeof(w25_rw_date_t));
		}
 //               msleep(1);      /*this is needed,else cost a long time 2015-8-20 zhangjj */

		break;
#if 0
	case W25P1165_ID:
		 w25p16_read_id(spi);
		// w25p16_read_test(spi);
		break;
#endif
	default:
		break;
	}
	si_gpioout(gpio_sih, 1<<11, 1<<11,GPIO_HI_PRIORITY);
	si_gpioouten(gpio_sih, 1<<11, 1<<11,GPIO_HI_PRIORITY);//0 mains do not clear other bits

	return retval;
}

static struct file_operations epcs_cdev_fops = 
{
	owner:		THIS_MODULE,
	read:		epcs_cdev_read,
	write:		epcs_cdev_write,
	unlocked_ioctl:		epcs_cdev_ioctl,
};

static struct class *epcs_class;
int major_epcs;
static int __devinit epcs_probe(struct spi_device *spi)
{
	int epcs_dev_no;
	struct cdev *pdev;

	
	alloc_chrdev_region(&epcs_dev_no, 0, 1, "epcs");

	major_epcs = MAJOR(epcs_dev_no);
//	#ifdef DEBUG_FPGA
	debugk("FPGA reg alloced major num: %u\n", MAJOR(epcs_dev_no));
//	#endif

	
	pdev = cdev_alloc();
	if (IS_ERR(pdev))
		return PTR_ERR(pdev);

	pdev->ops = &epcs_cdev_fops;
	cdev_add(pdev, epcs_dev_no, 1);

        epcs_class = class_create(THIS_MODULE, "epcs");

        device_create(epcs_class, NULL, epcs_dev_no, NULL, "epcs");


	spi->mode = SPI_MODE_3;
	spi->bits_per_word = 8;
	spi->max_speed_hz = 2000000;
	spi_setup(spi);

	epcs = kzalloc(sizeof *epcs, GFP_KERNEL);
	if (!epcs) {
		cdev_del(pdev);
		unregister_chrdev_region(epcs_dev_no, 1);
		return -ENOMEM;
	}
	epcs->spi = spi;
	epcs->pdev = pdev;
	epcs->devno = epcs_dev_no;
	dev_set_drvdata(&spi->dev, epcs);

	flash.spi = spi;
	flash.mtd.size = 0x7fffff;
	mutex_init(&(flash.lock));

	return 0;
}

static int __devexit epcs_remove(struct spi_device *spi)
{
        device_destroy(epcs_class, MKDEV(major_epcs, 0));
        class_destroy(epcs_class);
	if (epcs) {
	
		if (epcs->pdev)
			cdev_del(epcs->pdev);

		if (epcs->devno)
			unregister_chrdev_region(epcs->devno, 1);
			
		kfree(epcs);
	}

	return 0;
}

static struct spi_driver epcs_driver = {
	.driver = {
		.name	= "spi-epcs",
		.bus	= &spi_bus_type,
		.owner	= THIS_MODULE,
	},
	.probe	= epcs_probe,
	.remove = __devexit_p(epcs_remove),
};

static __init int epcs_init(void)
{
	int val=1;
	
	debugk("epcs spi driver init\n");
	spi_register_driver(&epcs_driver);

#if 1 /*gpio init for set*/
	if (!(gpio_sih = si_kattach(SI_OSH)))
		return -ENODEV;

	si_gpiosetcore(gpio_sih);
#endif

	return 0;
}
module_init(epcs_init);

static __exit void epcs_exit(void)
{
	debugk("epcs spi driver exit\n");
	spi_unregister_driver(&epcs_driver);
}
module_exit(epcs_exit);

MODULE_DESCRIPTION ("epcs driver");
MODULE_AUTHOR ("[email protected]");
MODULE_LICENSE ("GPL");
           

w25q64.c

/*
 * MTD SPI driver for ST M25Pxx (and similar) serial flash chips
 *
 * Author: Mike Lavender, [email protected]
 *
 * Copyright (c) 2005, Intec Automation Inc.
 *
 * Some parts are based on lart.c by Abraham Van Der Merwe
 *
 * Cleaned up and generalized based on mtd_dataflash.c
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/math64.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>

#include <linux/spi/spi.h>
#include <linux/spi/flash.h>
#include "w25p16.h"
#include <linux/delay.h>
/****************************************************************************/



static inline struct w25p *mtd_to_w25p(struct mtd_info *mtd)
{
	return container_of(mtd, struct w25p, mtd);
}

/****************************************************************************/

/*
 * Internal helper functions
 */

/*
 * Read the status register, returning its value in the location
 * Return the status register value.
 * Returns negative if error occurred.
 */
static int read_sr(struct w25p *flash)
{
	ssize_t retval;
	u8 code = OPCODE_RDSR;
	u8 val;
	int ret;
	struct spi_message message;
	struct spi_transfer xfer;

	unsigned char buf[14] = {0};
	unsigned char rx_buf[14] = {0};


	buf[0] = code;



	/* Build our spi message */
	spi_message_init(&message);
	memset(&xfer, 0, sizeof(xfer));
	xfer.len = 1; 
	//xfer.len = count ;
	/* Can tx_buf and rx_buf be equal? The doc in spi.h is not sure... */
	xfer.tx_buf = buf;
	xfer.rx_buf = rx_buf;

	spi_message_add_tail(&xfer, &message);

	ret = spi_sync(flash->spi, &message);

	if (ret < 0) {
		dev_err(&flash->spi->dev, "error %d reading SR\n",
				(int) ret);
		return ret;
	}

	//printk("\n   rx_buf[1]0x%08x,  0x%08x \n", rx_buf[1], rx_buf[0]);

	return rx_buf[0];
}

/*
 * Write status register 1 byte
 * Returns negative if error occurred.
 */
static int write_sr(struct w25p *flash, u8 val)
{
	flash->command[0] = OPCODE_WRSR;
	flash->command[1] = val;

	return spi_write(flash->spi, flash->command, 2);
}

/*
 * Set write enable latch with Write Enable command.
 * Returns negative if error occurred.
 */
static inline int write_enable(struct w25p *flash)
{
	u8	code = OPCODE_WREN;

	//return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
	return spi_write(flash->spi, &code, 1);
}


/*
 * Service routine to read status register until ready, or timeout occurs.
 * Returns non-zero if error.
 */
static int wait_till_ready(struct w25p *flash)
{
	int count;
	int sr;

	/* one chip guarantees max 5 msec wait here after page writes,
	 * but potentially three seconds (!) after page erase.
	 */
	for (count = 0; count < MAX_READY_WAIT_COUNT; count++) {
		if ((sr = read_sr(flash)) < 0)
			break;
		else if (!(sr & SR_WIP))
			return 0;
		msleep(1);
		/* REVISIT sometimes sleeping would be best */
	}

	return 1;
}

/*
 * Erase the whole flash memory
 *
 * Returns 0 if successful, non-zero otherwise.
 */
int erase_chip(struct w25p *flash)
{
	DEBUG(MTD_DEBUG_LEVEL3, "%s: %s %lldKiB\n",
	      dev_name(&flash->spi->dev), __func__,
	      (long long)(flash->mtd.size >> 10));

	/* Wait until finished previous write command. */
	if (wait_till_ready(flash))
		return 1;
	
	/* Send write enable, then erase commands. */
	write_enable(flash);

	/* Set up command buffer. */
	flash->command[0] = OPCODE_CHIP_ERASE;

	spi_write(flash->spi, flash->command, 1);

	return 0;
}

/*
 * Erase one sector of flash memory at offset ``offset'' which is any
 * address within the sector which should be erased.
 *
 * Returns 0 if successful, non-zero otherwise.
 */
int erase_sector(struct w25p *flash, u32 offset)
{
	DEBUG(MTD_DEBUG_LEVEL3, "%s: %s %dKiB at 0x%08x\n",
			dev_name(&flash->spi->dev), __func__,
			flash->mtd.erasesize / 1024, offset);

	/* Wait until finished previous write command. */
	if (wait_till_ready(flash))
		return 1;

	/* Send write enable, then erase commands. */
	write_enable(flash);

	/* Set up command buffer. */
	flash->command[0] = OPCODE_SE;
	flash->command[1] = offset >> 16;
	flash->command[2] = offset >> 8;
	flash->command[3] = offset;

	spi_write(flash->spi, flash->command, CMD_SIZE);

	return 0;
}

/****************************************************************************/

/*
 * MTD implementation
 */

/*
 * Erase an address range on the flash chip.  The address range may extend
 * one or more erase sectors.  Return an error is there is a problem erasing.
 */
static int w25p16_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct w25p *flash = mtd_to_w25p(mtd);
	u32 addr,len;
	uint32_t rem;

	DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%llx, len %lld\n",
	      dev_name(&flash->spi->dev), __func__, "at",
	      (long long)instr->addr, (long long)instr->len);

	/* sanity checks */
	if (instr->addr + instr->len > flash->mtd.size)
		return -EINVAL;
	div_u64_rem(instr->len, mtd->erasesize, &rem);
	if (rem)
		return -EINVAL;

	addr = instr->addr;
	len = instr->len;

	mutex_lock(&flash->lock);

	/* whole-chip erase? */
	if (len == flash->mtd.size && erase_chip(flash)) {
		instr->state = MTD_ERASE_FAILED;
		mutex_unlock(&flash->lock);
		return -EIO;

	/* REVISIT in some cases we could speed up erasing large regions
	 * by using OPCODE_SE instead of OPCODE_BE_4K.  We may have set up
	 * to use "small sector erase", but that's not always optimal.
	 */

	/* "sector"-at-a-time erase */
	} else {
		while (len) {
			if (erase_sector(flash, addr)) {
				instr->state = MTD_ERASE_FAILED;
				mutex_unlock(&flash->lock);
				return -EIO;
			}

			addr += mtd->erasesize;
			len -= mtd->erasesize;
		}
	}

	mutex_unlock(&flash->lock);

	instr->state = MTD_ERASE_DONE;
	mtd_erase_callback(instr);

	return 0;
}

/*
 * Read an address range from the flash chip.  The address range
 * may be any size provided it is within the physical boundaries.
 */
int w25p16_read(struct w25p *flash , loff_t from, size_t len,
	size_t *retlen, u_char *buf)
{
	struct spi_transfer t[2];
	struct spi_message m;

	 u_char rx_buf[256]= {0};
	 u_char tx_buf[256]= {0};

	DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n",
			dev_name(&flash->spi->dev), __func__, "from",
			(u32)from, len);

	/* sanity checks */
	if (!len)
		return 0;

	if (from + len > flash->mtd.size)
		return -EINVAL;

	spi_message_init(&m);
	memset(t, 0, (sizeof t));

	/* NOTE:
	 * OPCODE_FAST_READ (if available) is faster.
	 * Should add 1 byte DUMMY_BYTE.
	 */

	tx_buf[0] = OPCODE_READ;
	tx_buf[1] = from >> 16;
	tx_buf[2] = from >> 8;
	tx_buf[3] = from;
	t[0].tx_buf =tx_buf;
	t[0].len = len;
	t[0].rx_buf = rx_buf;

	spi_message_add_tail(&t[0], &m);

//	t[1].rx_buf = buf;
//	t[1].len = len;
//	spi_message_add_tail(&t[1], &m);

	/* Byte count starts at zero. */
	if (retlen)
		*retlen = 0;

	mutex_lock(&flash->lock);
#if 1
	/* Wait till previous write/erase is done. */
	if (wait_till_ready(flash)) {
		/* REVISIT status return?? */
		mutex_unlock(&flash->lock);
		return 1;
	}
#endif
	/* FIXME switch to OPCODE_FAST_READ.  It's required for higher
	 * clocks; and at this writing, every chip this driver handles
	 * supports that opcode.
	 */

	/* Set up the write data buffer. */
	flash->command[0] = OPCODE_READ;
	flash->command[1] = from >> 16;
	flash->command[2] = from >> 8;
	flash->command[3] = from;

	spi_sync(flash->spi, &m);

	*retlen = m.actual_length - CMD_SIZE - FAST_READ_DUMMY_BYTE;
	
	memcpy(buf, rx_buf, len);
	

	mutex_unlock(&flash->lock);

	return 0;
}

/*
 * Write an address range to the flash chip.  Data must be written in
 * FLASH_PAGESIZE chunks.  The address range may be any size provided
 * it is within the physical boundaries.
 */
int w25p16_write(struct w25p *flash , loff_t to, size_t len,
	size_t *retlen, const u_char *buf)
{
	u32 page_offset, page_size;
	struct spi_transfer t[2];
	struct spi_message m;

	unsigned char tx_buf[256] = {0};
	//unsigned char rx_buf[256] = {0};
	t[0].tx_buf  = tx_buf;
	

	DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n",
			dev_name(&flash->spi->dev), __func__, "to",
			(u32)to, len);

	if (retlen)
		*retlen = 0;

	/* sanity checks */
	if (!len)
		return(0);

	if (to + len > flash->mtd.size)
		return -EINVAL;

	spi_message_init(&m);
	memset(t, 0, (sizeof t));

	t[0].tx_buf = tx_buf;
	//t[0].rx_buf  = rx_buf;
	t[0].len = CMD_SIZE;
	spi_message_add_tail(&t[0], &m);

	//t[1].tx_buf = buf;
	//spi_message_add_tail(&t[1], &m);

	mutex_lock(&flash->lock);
#if 1  /*delete temp by zhangjiajie*/
	/* Wait until finished previous write command. */
	if (wait_till_ready(flash)) {
		mutex_unlock(&flash->lock);
		return 1;
	}
#endif

	write_enable(flash);

	/* Set up the opcode in the write buffer. */
	tx_buf [0] = OPCODE_PP;
	tx_buf [1] = to >> 16;
	tx_buf [2] = to >> 8;
	tx_buf [3] = to;

	/* what page do we start with? */
	page_offset = to % FLASH_PAGESIZE;

	/* do all the bytes fit onto one page? */
	if (page_offset + len <= FLASH_PAGESIZE) {
		//t[1].len = len;
	//	t[0].tx_buf + 4  = buf;
		t[0].len = CMD_SIZE + len ;
		memcpy(tx_buf + 4 , buf, len);

		spi_sync(flash->spi, &m);

		*retlen = m.actual_length - CMD_SIZE;
	} else {
		u32 i;
		
		/* the size of data remaining on the first page */
		page_size = FLASH_PAGESIZE - page_offset;

		t[1].len = page_size;
		spi_sync(flash->spi, &m);

		*retlen = m.actual_length - CMD_SIZE;

		/* write everything in PAGESIZE chunks */
		for (i = page_size; i < len; i += page_size) {
			page_size = len - i;
			if (page_size > FLASH_PAGESIZE)
				page_size = FLASH_PAGESIZE;

			/* write the next page to flash */
			flash->command[1] = (to + i) >> 16;
			flash->command[2] = (to + i) >> 8;
			flash->command[3] = (to + i);

			t[1].tx_buf = buf + i;
			t[1].len = page_size;

			wait_till_ready(flash);

			write_enable(flash);

			spi_sync(flash->spi, &m);

			if (retlen)
				*retlen += m.actual_length - CMD_SIZE;
		}
	}

	mutex_unlock(&flash->lock);

	return 0;
}
void w25p16_read_id(struct spi_device *spi)
{

	int ret;
	struct spi_message message;
	struct spi_transfer xfer;

	unsigned char buf[14] = {0};
	unsigned char rx_buf[14] = {0};

	
	buf[0] = 0x90;
	buf[1] = 0x00;
	buf[2]=0x00;
	buf[3]=0x00;
	
		

		/* Build our spi message */
		spi_message_init(&message);
		memset(&xfer, 0, sizeof(xfer));
		xfer.len = 6; 
		//xfer.len = count ;
		/* Can tx_buf and rx_buf be equal? The doc in spi.h is not sure... */
		xfer.tx_buf = buf;
		xfer.rx_buf = rx_buf;

		spi_message_add_tail(&xfer, &message);

		ret = spi_sync(spi, &message);
     {
		int i;
		
		for (i = 0; i < xfer.len; i++) {
			printk(" %02x", rx_buf[i]);
		}
		printk("\n");
    }	


	//printk("now test begin \n");
	
}

void w25p16_read_test(struct spi_device *spi)
{

	int ret;
	struct spi_message message;
	struct spi_transfer xfer;

	unsigned char buf[14] = {0};
	unsigned char rx_buf[14] = {0};

	
	buf[0] = 0x30;
	buf[1] = 0x00;
	buf[2]=0x10;
	buf[3]=0x00;
	
		

		/* Build our spi message */
		spi_message_init(&message);
		memset(&xfer, 0, sizeof(xfer));
		xfer.len = 6; 
		//xfer.len = count ;
		/* Can tx_buf and rx_buf be equal? The doc in spi.h is not sure... */
		xfer.tx_buf = buf;
		xfer.rx_buf = rx_buf;

		spi_message_add_tail(&xfer, &message);

		ret = spi_sync(spi, &message);
     {
		int i;
		
		for (i = 0; i < xfer.len; i++) {
			printk(" %02x", rx_buf[i]);
		}
		printk("\n");
    }	


	//printk("now test begin \n");
	
}
           

繼續閱讀