简介

W25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。

引脚介绍

24 25系列的BIOS芯 主板bios芯片24和25_#include

串行数据输入、输出和 IOs(DI、DO 和 IO0、IO1、IO2、IO3)

W25Q64、W25Q16 和 W25Q32 支持标准 SPI、双倍 SPI 和四倍 SPI。标准的 SPI 传输用单向的 DI(输入)引脚连续的写命令、地址或者数据在串行时钟(CLK)的上升沿时写入到芯片内。

写保护(/WP)

写保护引脚(/WP)用来保护状态寄存器。和状态寄存器的块保护位(SEC、TB、BP2、BP1 和BP0)和状态寄存器保护位(SRP)对存储器进行一部分或者全部的硬件保护。/WP 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/WP 引脚(硬件写保护)的功能不可用。

保持端(/HOLD)

当/HOLD 引脚是有效时,允许芯片暂停工作。在/CS 为低电平时,当/HOLD 变为低电平,DO 引脚将变为高阻态,在 DI 和 CLK 引脚上的信号将无效。当/HOLD 变为高电平,芯片恢复工作。/HOLD 功能用在当有多个设备共享同一 SPI 总线时。/HOLD 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/ HOLD 引脚的功能不可用。

串行时钟(CLK)

串行时钟输入引脚为串行输入和输出操作提供时序。(见 SPI 操作)。
设备数据传输是从高位开始,数据传输的格式为 8bit,数据采样从第二个时间边沿开始,空闲状态时,时钟线 clk 为高电平。

W25Q64操作原理

通过SPI接口,用标准的SPI协议发送相应指令给flash,然后flash根据命令进行各种相关操作。
① 写使能:06H
② 读状态寄存器指令:05H
③ 写状态寄存器指令:01H
④ 读数据:03H
⑤ 页写:02H
⑥ 扇区擦除指令:20H
⑦ 块擦除指令:D8H
⑧ 芯片擦除指令:07H
⑨ 掉电指令:B9H
⑩ 读ID指令:90H

24 25系列的BIOS芯 主板bios芯片24和25_24 25系列的BIOS芯_02


24 25系列的BIOS芯 主板bios芯片24和25_ide_03

极性CPOL和相位CPHA

(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (时钟)相位

CPOL和CPHA,分别都可以是0或时1,对应的四种组合就是:

24 25系列的BIOS芯 主板bios芯片24和25_linux_04


SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high。

CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low。

24 25系列的BIOS芯 主板bios芯片24和25_linux_05

CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;

CPHA=1,表示第二个边沿:

对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;

对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

24 25系列的BIOS芯 主板bios芯片24和25_#include_06


24 25系列的BIOS芯 主板bios芯片24和25_#include_07

示例代码

DTSI

w25q64: w25q64@00 {
       status = "okay";
       compatible = "rockchip,w25q64";
       reg = <0x00>;
       spi-max-frequency = <24000000>;
       wp-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>; /*GPIO0_B4*/
//     spi-cs-high;
       spi-cpha;       /*SPI mode: CPHA = 1*/
       spi-cpol;       /*SPI mode: CPOL = 1*/
};

源码

w25q64.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/kernel.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/of_gpio.h>
#include "w25q64.h"

/*
 * This supports access to SPI devices using normal userspace I/O calls.
 * Note that while traditional UNIX/POSIX I/O semantics are half duplex,
 * and often mask message boundaries, full SPI support requires full duplex
 * transfers.  There are several kinds of internal message boundaries to
 * handle chipselect management and other protocol options.
 *
 * SPI has a character major number assigned.  We allocate minor numbers
 * dynamically using a bitmask.  You must use hotplug tools, such as udev
 * (or mdev with busybox) to create and destroy the /dev/spidevB.C device
 * nodes, since there is no fixed association of minor numbers with any
 * particular SPI bus or device.
 */
#define SPIDEV_MAJOR			155	/* assigned */
#define N_SPI_MINORS			32	/* ... up to 256 */

/*W25Q64 CMD*/
#define WRITE_ENABLE 0x06
#define PAGE_PROGRAM 0x02
#define READ_DATA 0x03
#define WRITE_STATUS_REG 0x01
#define READ_STATUS_REG 0x05
#define CHIP_ERASE 0xc7
#define SECTOR_ERASE 0x20
#define BLOCK_32KB_ERASE 0x52
#define BLOCK_64KB_ERASE 0xD8
#define READ_DEVICE_ID 0x90
#define READ_UID 0x9F

static DECLARE_BITMAP(minors, N_SPI_MINORS);


/* Bit masks for spi_device.mode management.  Note that incorrect
 * settings for some settings can cause *lots* of trouble for other
 * devices on a shared bus:
 *
 *  - CS_HIGH ... this device will be active when it shouldn't be
 *  - 3WIRE ... when active, it won't behave as it should
 *  - NO_CS ... there will be no explicit message boundaries; this
 *	is completely incompatible with the shared bus model
 *  - READY ... transfers may proceed when they shouldn't.
 *
 * REVISIT should changing those flags be privileged?
 */
#define SPI_MODE_MASK		(SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \
				| SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \
				| SPI_NO_CS | SPI_READY | SPI_TX_DUAL \
				| SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD)

struct spidev_data {
	dev_t			devt;
	spinlock_t		spi_lock;
	struct spi_device	*spi;
	struct list_head	device_entry;

	/* TX/RX buffers are NULL unless this device is open (users > 0) */
	struct mutex		buf_lock;
	unsigned		users;
	u8			*tx_buffer;
	u8			*rx_buffer;
	u32			speed_hz;
	unsigned int cur_addr;
	unsigned wp_gpio;
};

static LIST_HEAD(device_list);
static DEFINE_MUTEX(device_list_lock);

static unsigned bufsiz = 4096;
module_param(bufsiz, uint, S_IRUGO);
MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");

/*-------------------------------------------------------------------------*/

static char spi_w25x_status(struct spi_device *spi)
{       
	int     status;
	char tbuf[]={READ_STATUS_REG};
	char rbuf[1] = {1};
	struct spi_transfer     t = {
		.tx_buf         = tbuf,
		.len            = ARRAY_SIZE(tbuf),
	};

	struct spi_transfer     r = {
		.rx_buf         = rbuf,
		.len            = ARRAY_SIZE(rbuf),
	};
	struct spi_message      m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	spi_message_add_tail(&r, &m);
	status = spi_sync(spi, &m);

	return rbuf[0];
}
//modified by xzq degain for retry 5 times @20211105
static int spi_w25x_wait_ready(struct spi_device *spi )
{
	char retval = 1;
	int retry = 5;
	dev_dbg(&spi->dev, "wait ready...");
	do {
		retval = spi_w25x_status(spi);
		retval &= 0xff;
		retval &= 1;
		retry --;
		mdelay(5);
	}while((retval != 0) && (retry != 0));
	if(retval)
		dev_err(&spi->dev,"no ready\n");
	else
		dev_dbg(&spi->dev, "OK\n");
	return 0;
}
//modified by xzq end
static int spi_w25x_write_enable(struct spi_device *spi)
{       
	int     status;
	char cmd_buf[1] = {WRITE_ENABLE};
	struct spi_transfer cmd = {
		.tx_buf = cmd_buf,
		.len = ARRAY_SIZE(cmd_buf),
	};

	struct spi_message      m;

	spi_message_init(&m);
	spi_message_add_tail(&cmd, &m);

	status = spi_sync(spi, &m);

	dev_dbg(&spi->dev, "write enable\n");

	return status;
}

static int spi_read_w25x_id_0(struct spi_device *spi)
{       
	int     status;
	char tbuf[]={READ_UID};
	char rbuf[5];

	struct spi_transfer     t = {
		.tx_buf         = tbuf,
		.len            = ARRAY_SIZE(tbuf),
	};

	struct spi_transfer     r = {
		.rx_buf         = rbuf,
		.len            = ARRAY_SIZE(rbuf),
	};
	struct spi_message      m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	spi_message_add_tail(&r, &m);
	status = spi_sync(spi, &m);

	dev_err(&spi->dev, "ID = %02x %02x %02x %02x %02x\n",
		rbuf[0], rbuf[1], rbuf[2], rbuf[3], rbuf[4]);
	return status;
}

static int
spi_w25x_sector_erase(struct spidev_data *spidev, unsigned long size)
{
	int status;
	char cmd[4] = {SECTOR_ERASE};
	struct spi_device *spi = spidev->spi;
	struct spi_transfer t = {
		.tx_buf = cmd,
		.len = ARRAY_SIZE(cmd),
	};
	struct spi_message m;
	unsigned int flash_addr = spidev->cur_addr;
	int count = (int)size;

	for ( ; count > 0; count -= W25Q64_SECTOR) {
		cmd[1] = (unsigned char)((flash_addr & 0xff0000) >> 16);
		cmd[2] = (unsigned char)((flash_addr & 0xff00) >> 8);
		cmd[3] = (unsigned char)(flash_addr & 0xff);

		spi_w25x_write_enable(spi);

		spi_message_init(&m);
		spi_message_add_tail(&t, &m);
		status = spi_sync(spi, &m);
		spi_w25x_wait_ready(spi);
		dev_dbg(&spi->dev,"start addr: %x, sector erase OK\n", flash_addr);
		flash_addr += W25Q64_SECTOR;
	}
	return status;
}

static int
spi_w25x_32kb_block_erase(struct spidev_data *spidev)
{
	int status;
	char cmd[4] = {BLOCK_32KB_ERASE};
	struct spi_device *spi = spidev->spi;
	struct spi_transfer t = {
		.tx_buf = cmd,
		.len = ARRAY_SIZE(cmd),
	};
	struct spi_message m;

	cmd[1] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16);
	cmd[2] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8);
	cmd[3] = (unsigned char)(spidev->cur_addr & 0xff);

	spi_w25x_write_enable(spi);

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	status = spi_sync(spi, &m);
	spi_w25x_wait_ready(spi);
	dev_dbg(&spi->dev,"32kb block erase OK\n");
	return status;
}

static int
spi_w25x_64kb_block_erase(struct spidev_data *spidev)
{
	int status;
	char cmd[4] = {BLOCK_64KB_ERASE};
	struct spi_device *spi = spidev->spi;
	struct spi_transfer t = {
		.tx_buf = cmd,
		.len = ARRAY_SIZE(cmd),
	};
	struct spi_message m;

	cmd[1] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16);
	cmd[2] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8);
	cmd[3] = (unsigned char)(spidev->cur_addr & 0xff);

	spi_w25x_write_enable(spi);

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	status = spi_sync(spi, &m);
	spi_w25x_wait_ready(spi);
	dev_dbg(&spi->dev,"64kb block erase OK\n");
	return status;
}

static int spi_w25x_chip_erase(struct spi_device *spi)
{
	int status;
	char chip_erase[1] = {CHIP_ERASE};

	struct spi_transfer erase = {
		.tx_buf = chip_erase,
		.len = ARRAY_SIZE(chip_erase),
	};
	struct spi_message m;

	spi_w25x_write_enable(spi);

	spi_message_init(&m);
	spi_message_add_tail(&erase, &m);
	status = spi_sync(spi, &m);
	spi_w25x_wait_ready(spi);
	dev_dbg(&spi->dev,"chip erase OK\n");
	return status;
}

static loff_t
spi_w25x_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	struct spidev_data	*spidev;

	spidev = filp->private_data;
	switch (orig) {
	case SEEK_SET:
		if (offset < 0) {
			ret = -EINVAL;
			break;
		}
		if ((unsigned int)offset > W25Q64_SIZE) {
			ret = -EINVAL;
			break;
		}
		spidev->cur_addr = (unsigned int)offset;
		ret = spidev->cur_addr;
		break;
	case SEEK_CUR:
		if ((spidev->cur_addr + offset) > W25Q64_SIZE) {
			ret = -EINVAL;
			break;
		}
		if ((spidev->cur_addr + offset) < 0) {
			ret = -EINVAL;
			break;
		}
		spidev->cur_addr += offset;
		ret = spidev->cur_addr;
		break;
	default:
		ret =  - EINVAL;
		break;
	}
	dev_dbg(&spidev->spi->dev, "set curr addr:%02X\n", (unsigned int)ret);
	return ret;

}

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	int status;
	struct spi_device *spi;

	spin_lock_irq(&spidev->spi_lock);
	spi = spidev->spi;
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_sync(spi, message);

	if (status == 0)
		status = message->actual_length;

	return status;
}

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	int status;
	char cmd[1] = {PAGE_PROGRAM};
	unsigned char addr[3];
	struct spi_transfer c[] = {
		{
			.tx_buf = cmd,
			.len = ARRAY_SIZE(cmd),
		},
		{
			.tx_buf = addr,
			.len = ARRAY_SIZE(addr),
		},
	};
	struct spi_transfer t = {
			.tx_buf		= spidev->tx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
	};
	struct spi_message	m;

	addr[0] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16);
	addr[1] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8);
	addr[2] = (unsigned char)(spidev->cur_addr & 0xff);

	spi_w25x_write_enable(spidev->spi);

	spi_message_init(&m);
	spi_message_add_tail(&c[0], &m);
	spi_message_add_tail(&c[1], &m);
	spi_message_add_tail(&t, &m);
	status = spidev_sync(spidev, &m);
	spi_w25x_wait_ready(spidev->spi);
	return status;
}

static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
	int status;
	char cmd[] = {READ_DATA};
	unsigned char addr[3];
	struct spi_transfer	t[] = {
		{
			.tx_buf = cmd,
			.len = ARRAY_SIZE(cmd),
			.speed_hz = spidev->speed_hz,
		},
		{
			.tx_buf = addr,
			.len = ARRAY_SIZE(addr),
		},
		{
			.rx_buf		= spidev->rx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		}
	};
	struct spi_message	m;

	addr[0] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16);
	addr[1] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8);
	addr[2] = (unsigned char)(spidev->cur_addr & 0xff);

	spi_message_init(&m);
	spi_message_add_tail(&t[0], &m);
	spi_message_add_tail(&t[1], &m);
	spi_message_add_tail(&t[2], &m);
	status = spidev_sync(spidev, &m);
	spi_w25x_wait_ready(spidev->spi);
	return status;
}

/* Read-only message with current device setup */
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)
		return -EMSGSIZE;

	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	status = spidev_sync_read(spidev, count);
	if (status > 0) {
		unsigned long	missing;

		missing = copy_to_user(buf, spidev->rx_buffer, status);
		if (missing == status)
			status = -EFAULT;
		else
			status = status - missing;
	}
	mutex_unlock(&spidev->buf_lock);

	return status;
}

/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)
		return -EMSGSIZE;

	spidev = filp->private_data;

	gpio_set_value(spidev->wp_gpio, 1);
	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->tx_buffer, buf, count);
	if (missing == 0)
		status = spidev_sync_write(spidev, count);
	else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);
	gpio_set_value(spidev->wp_gpio, 0);

	return status;
}

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int			err = 0;
	int			retval = 0;
	struct spidev_data	*spidev;
	struct spi_device	*spi;
	u32			tmp;

	/* Check type and command number */
	if (_IOC_TYPE(cmd) != W25Q64_MAGIC)
		return -ENOTTY;

	/* Check access direction once here; don't repeat below.
	 * IOC_DIR is from the user perspective, while access_ok is
	 * from the kernel perspective; so they look reversed.
	 */
	if (_IOC_DIR(cmd) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE,
				(void __user *)arg, _IOC_SIZE(cmd));
	if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)
		err = !access_ok(VERIFY_READ,
				(void __user *)arg, _IOC_SIZE(cmd));
	if (err)
		return -EFAULT;

	/* guard against device removal before, or while,
	 * we issue this ioctl.
	 */
	spidev = filp->private_data;
	spin_lock_irq(&spidev->spi_lock);
	spi = spi_dev_get(spidev->spi);
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		return -ESHUTDOWN;

	/* use the buffer lock here for triple duty:
	 *  - prevent I/O (from us) so calling spi_setup() is safe;
	 *  - prevent concurrent SPI_IOC_WR_* from morphing
	 *    data fields while SPI_IOC_RD_* reads them;
	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
	 */
	mutex_lock(&spidev->buf_lock);

	switch (cmd) {
	/* read requests */
	case W25Q64_IOC_SECTOR_ERASE:
		retval = spi_w25x_sector_erase(spidev, arg);
		break;
	case W25Q64_IOC_32KB_BLOCK_ERASE:
		retval = spi_w25x_32kb_block_erase(spidev);
		break;
	case W25Q64_IOC_64KB_BLOCK_ERASE:
		retval = spi_w25x_64kb_block_erase(spidev);
		break;
	case W25Q64_IOC_CHIP_ERASE:
		retval = spi_w25x_chip_erase(spi);
		break;
	case SPI_IOC_RD_MODE:
		retval = __put_user(spi->mode & SPI_MODE_MASK,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MODE32:
		retval = __put_user(spi->mode & SPI_MODE_MASK,
					(__u32 __user *)arg);
		break;
	case SPI_IOC_RD_LSB_FIRST:
		retval = __put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_BITS_PER_WORD:
		retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MAX_SPEED_HZ:
		retval = __put_user(spidev->speed_hz, (__u32 __user *)arg);
		break;

	/* write requests */
	case SPI_IOC_WR_MODE:
	case SPI_IOC_WR_MODE32:
		if (cmd == SPI_IOC_WR_MODE)
			retval = __get_user(tmp, (u8 __user *)arg);
		else
			retval = __get_user(tmp, (u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp & ~SPI_MODE_MASK) {
				retval = -EINVAL;
				break;
			}

			tmp |= spi->mode & ~SPI_MODE_MASK;
			spi->mode = (u16)tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "spi mode %x\n", tmp);
		}
		break;
	case SPI_IOC_WR_LSB_FIRST:
		retval = __get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp)
				spi->mode |= SPI_LSB_FIRST;
			else
				spi->mode &= ~SPI_LSB_FIRST;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "%csb first\n",
						tmp ? 'l' : 'm');
		}
		break;
	case SPI_IOC_WR_BITS_PER_WORD:
		retval = __get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->bits_per_word;

			spi->bits_per_word = tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->bits_per_word = save;
			else
				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
		}
		break;
	case SPI_IOC_WR_MAX_SPEED_HZ:
		retval = __get_user(tmp, (__u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->max_speed_hz;

			spi->max_speed_hz = tmp;
			retval = spi_setup(spi);
			if (retval >= 0)
				spidev->speed_hz = tmp;
			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
			spi->max_speed_hz = save;
		}
		break;
		default:
			return -EINVAL;

	}

	mutex_unlock(&spidev->buf_lock);
	spi_dev_put(spi);
	return retval;
}

#ifdef CONFIG_COMPAT
static long
spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
#else
#define spidev_compat_ioctl NULL
#endif /* CONFIG_COMPAT */

static int spidev_open(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;
	int			status = -ENXIO;

	mutex_lock(&device_list_lock);

	list_for_each_entry(spidev, &device_list, device_entry) {
		if (spidev->devt == inode->i_rdev) {
			status = 0;
			break;
		}
	}

	if (status) {
		pr_debug("spidev: nothing for minor %d\n", iminor(inode));
		goto err_find_dev;
	}

	if (!spidev->tx_buffer) {
		spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
		if (!spidev->tx_buffer) {
			dev_err(&spidev->spi->dev, "open/ENOMEM\n");
			status = -ENOMEM;
			goto err_find_dev;
		}
	}

	if (!spidev->rx_buffer) {
		spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
		if (!spidev->rx_buffer) {
			dev_err(&spidev->spi->dev, "open/ENOMEM\n");
			status = -ENOMEM;
			goto err_alloc_rx_buf;
		}
	}

	spidev->users++;
	filp->private_data = spidev;

	mutex_unlock(&device_list_lock);
	return 0;

err_alloc_rx_buf:
	kfree(spidev->tx_buffer);
	spidev->tx_buffer = NULL;
err_find_dev:
	mutex_unlock(&device_list_lock);
	return status;
}

static int spidev_release(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;

	mutex_lock(&device_list_lock);
	spidev = filp->private_data;
	filp->private_data = NULL;

	/* last close? */
	spidev->users--;
	if (!spidev->users) {
		int		dofree;

		kfree(spidev->tx_buffer);
		spidev->tx_buffer = NULL;

		kfree(spidev->rx_buffer);
		spidev->rx_buffer = NULL;

		spin_lock_irq(&spidev->spi_lock);
		if (spidev->spi)
			spidev->speed_hz = spidev->spi->max_speed_hz;

		/* ... after we unbound from the underlying device? */
		dofree = (spidev->spi == NULL);
		spin_unlock_irq(&spidev->spi_lock);

		if (dofree)
			kfree(spidev);
	}
	mutex_unlock(&device_list_lock);

	return 0;
}

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.write =	spidev_write,
	.read =		spidev_read,
	.unlocked_ioctl = spidev_ioctl,
	.compat_ioctl = spidev_compat_ioctl,
	.open =		spidev_open,
	.release =	spidev_release,
	.llseek =	spi_w25x_llseek,
};

/*-------------------------------------------------------------------------*/

/* The main reason to have this class is to make mdev/udev create the
 * /dev/spidevB.C character device nodes exposing our userspace API.
 * It also simplifies memory management.
 */

static struct class *spidev_class;

#ifdef CONFIG_OF
static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "rockchip,w25q64" },
	{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
#endif

/*-------------------------------------------------------------------------*/

static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	struct device_node *np = spi->dev.of_node;
	int			status;
	unsigned long		minor;
	dev_err(&spi->dev, "probe,rk3399.0\n");
	/*
	 * spidev should never be referenced in DT without a specific
	 * compatible string, it is a Linux implementation thing
	 * rather than a description of the hardware.
	 */
	if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
		dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");
		WARN_ON(spi->dev.of_node &&
			!of_match_device(spidev_dt_ids, &spi->dev));
	}

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->spi = spi;
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "w25q64");
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		dev_err(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	spidev->wp_gpio = of_get_named_gpio(np, "wp-gpio", 0);
	if (!gpio_is_valid(spidev->wp_gpio)) {
        dev_err(&spi->dev, "wp-gpio: %d is invalid\n", spidev->wp_gpio);
        return -ENODEV;
    }

	status = gpio_request(spidev->wp_gpio, "wp-gpio");
	if (status) {
        dev_err(&spi->dev, "wp-gpio: %d request failed!\n", spidev->wp_gpio);
        gpio_free(spidev->wp_gpio);
        return -ENODEV;
    }

	gpio_direction_output(spidev->wp_gpio, 0);
	gpio_export(spidev->wp_gpio, 0);
	
	spi_read_w25x_id_0(spi);

	return status;
}

static int spidev_remove(struct spi_device *spi)
{
	struct spidev_data	*spidev = spi_get_drvdata(spi);

	gpio_free(spidev->wp_gpio);

	/* make sure ops on existing fds can abort cleanly */
	spin_lock_irq(&spidev->spi_lock);
	spidev->spi = NULL;
	spin_unlock_irq(&spidev->spi_lock);

	/* prevent new opens */
	mutex_lock(&device_list_lock);
	list_del(&spidev->device_entry);
	device_destroy(spidev_class, spidev->devt);
	clear_bit(MINOR(spidev->devt), minors);
	if (spidev->users == 0)
		kfree(spidev);
	mutex_unlock(&device_list_lock);

	return 0;
}

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"w25q64",
		.of_match_table = of_match_ptr(spidev_dt_ids),
	},
	.probe =	spidev_probe,
	.remove =	spidev_remove,

	/* NOTE:  suspend/resume methods are not necessary here.
	 * We don't do anything except pass the requests to/from
	 * the underlying controller.  The refrigerator handles
	 * most issues; the controller driver handles the rest.
	 */
};

/*-------------------------------------------------------------------------*/

static int __init spidev_init(void)
{
	int status;
	
	/* Claim our 256 reserved device numbers.  Then register a class
	 * that will key udev/mdev to add/remove /dev nodes.  Last, register
	 * the driver which manages those device numbers.
	 */
	BUILD_BUG_ON(N_SPI_MINORS > 256);
	status = register_chrdev(SPIDEV_MAJOR, "w25q64", &spidev_fops);
	if (status < 0)
		return status;

	spidev_class = class_create(THIS_MODULE, "w25q64");
	if (IS_ERR(spidev_class)) {
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
		return PTR_ERR(spidev_class);
	}

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
		class_destroy(spidev_class);
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
	}
	return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
	spi_unregister_driver(&spidev_spi_driver);
	class_destroy(spidev_class);
	unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
module_exit(spidev_exit);

MODULE_AUTHOR("Yubel");
MODULE_DESCRIPTION("User mode SPI device interface");
MODULE_LICENSE("GPL");

w25q64.h

#ifndef W25Q64_H
#define W25Q64_H

#include <linux/types.h>
#include <linux/ioctl.h>

#define W25Q64_SIZE 0x800000
#define W25Q64_PAGE_LENGTH	256
#define W25Q64_SECTOR	4096
#define W25Q64_32KB_BLOCK 32768
#define W25Q64_64KB_BLOCK 65536

/* IOCTL commands */
#define W25Q64_MAGIC			'k'

/*Erase SPI Flash*/
#define W25Q64_IOC_SECTOR_ERASE			_IOW(W25Q64_MAGIC, 6, __u32)
#define W25Q64_IOC_32KB_BLOCK_ERASE		_IOW(W25Q64_MAGIC, 7, __u32)
#define W25Q64_IOC_64KB_BLOCK_ERASE		_IOW(W25Q64_MAGIC, 8, __u32)
#define W25Q64_IOC_CHIP_ERASE			_IOW(W25Q64_MAGIC, 9, __u32)

#endif /* W25Q64_H */