firefly-rk3288开发板Linux驱动——W25Q32 SPI驱动

一、Linux SPI驱动框架

  Linux下的spi驱动和i2c驱动十分类似,也可以分为三个部分:SPI核心,spi主机控制器(i2c中叫做i2c适配器),spi设备。

1.spi核心

  spi核心提供了主机控制器的注册与注销方法、spi设备注册与注销方法、以及spi通信方法。源码位置位于kernel/drivers/spi/spi.c

2.spi主机控制器

  spi的主机控制器用spi_master结构体描述:

//有部分删减
struct spi_master {
	struct device	dev;
	struct list_head list;
	s16			bus_num;
	u16			num_chipselect;
	u16			dma_alignment;
	u16			mode_bits;

	/* bitmask of supported bits_per_word for transfers */
	u32			bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))

	/* limits on transfer speed */
	u32			min_speed_hz;
	u32			max_speed_hz;

	/* other constraints relevant to this driver */
	u16			flags;
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
#define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
#define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */
#define SPI_MASTER_MUST_RX      BIT(3)		/* requires rx */
#define SPI_MASTER_MUST_TX      BIT(4)		/* requires tx */

	/* lock and mutex for SPI bus locking */
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;

	/* flag indicating that the SPI bus is locked for exclusive use */
	bool			bus_lock_flag;

	int			(*setup)(struct spi_device *spi);

	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);
	bool				queued;
	struct kthread_worker		kworker;
	struct task_struct		*kworker_task;
	struct kthread_work		pump_messages;
	spinlock_t			queue_lock;
	struct list_head		queue;
	struct spi_message		*cur_msg;
	bool				idling;
	bool				busy;
	bool				running;
	bool				rt;
	bool				auto_runtime_pm;
	bool                            cur_msg_prepared;
	bool				cur_msg_mapped;
	struct completion               xfer_completion;
	size_t				max_dma_len;

	void (*set_cs)(struct spi_device *spi, bool enable);
	int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
			    struct spi_transfer *transfer);
	void (*handle_err)(struct spi_master *master,
			   struct spi_message *message);

	/* gpio chip select */
	int			*cs_gpios;

	/* statistics */
	struct spi_statistics	statistics;

	/* DMA channels for use with core dmaengine helpers */
	struct dma_chan		*dma_tx;
	struct dma_chan		*dma_rx;

	/* dummy data for full duplex devices */
	void			*dummy_rx;
	void			*dummy_tx;
};

transfer函数就是spi的通信函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。

spi的主机端最终会和硬件上的spi控制器打交道,因此spi主机控制器的驱动一般都是由芯片厂商完成的,他们的主要工作就是实现transfer函数。

spi核心提供的常用spi主机控制器的API函数如下:

/*申请一个spi主机控制器*/
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)

/*释放一个spi主机控制器*/
static inline void spi_master_put(struct spi_master *master)

/*向内核注册一个spi主机控制器*/
int spi_register_master(struct spi_master *master)

/*从内核注销一个spi主机控制器*/
void spi_unregister_master(struct spi_master *master)

RK3288开发板的spi0主机控制器的设备树节点如下:

	spi0: spi@ff110000 {
		compatible = "rockchip,rk3288-spi", "rockchip,rk3066-spi";
		clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
		clock-names = "spiclk", "apb_pclk";
		dmas = <&dmac_peri 11>, <&dmac_peri 12>;
		dma-names = "tx", "rx";
		interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0>;
		reg = <0x0 0xff110000 0x0 0x1000>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";
	};
	
    /*spi0引脚配置节点*/
	spi0 {
			spi0_clk: spi0-clk {
				rockchip,pins = <5 12 RK_FUNC_1 &pcfg_pull_up>;
			};
			spi0_cs0: spi0-cs0 {
				rockchip,pins = <5 13 RK_FUNC_1 &pcfg_pull_up>;
			};
			spi0_tx: spi0-tx {
				rockchip,pins = <5 14 RK_FUNC_1 &pcfg_pull_up>;
			};
			spi0_rx: spi0-rx {
				rockchip,pins = <5 15 RK_FUNC_1 &pcfg_pull_up>;
			};
			spi0_cs1: spi0-cs1 {
				rockchip,pins = <5 16 RK_FUNC_1 &pcfg_pull_up>;
			};
		};

与之对应的spi0主机控制器的驱动源码位于kernel/drivers/spi/spi-rockchip.c

static const struct of_device_id rockchip_spi_dt_match[] = {
	{ .compatible = "rockchip,px30-spi",   },
	{ .compatible = "rockchip,rv1108-spi", },
	{ .compatible = "rockchip,rk3036-spi", },
	{ .compatible = "rockchip,rk3066-spi", },
	{ .compatible = "rockchip,rk3188-spi", },
	{ .compatible = "rockchip,rk3228-spi", },
	{ .compatible = "rockchip,rk3288-spi", },
	{ .compatible = "rockchip,rk3368-spi", },
	{ .compatible = "rockchip,rk3399-spi", },
	{ },
};
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);

static struct platform_driver rockchip_spi_driver = {
	.driver = {
		.name	= DRIVER_NAME,
		.pm = &rockchip_spi_pm,
		.of_match_table = of_match_ptr(rockchip_spi_dt_match),
	},
	.probe = rockchip_spi_probe,
	.remove = rockchip_spi_remove,
};

可以看到rockchip_spi_dt_match匹配表中有很多芯片的compatible属性,只要设备树中有一个与之匹配,那么rockchip_spi_driver结构体的rockchip_spi_probe函数就会执行。

RK3288开发板的spi的通信函数为rockchip_spi_transfer_one。

static int rockchip_spi_transfer_one(
		struct spi_master *master,
		struct spi_device *spi,
		struct spi_transfer *xfer)

rockchip_spi_transfer_one函数最终会调用下面两个函数来完成与硬件的数据交互。

static int rockchip_spi_pio_transfer(struct rockchip_spi *rs)
static void rockchip_spi_pio_reader(struct rockchip_spi *rs)

3.spi设备和驱动

spi设备通过spi_device结构体描述。

struct spi_device {
	struct device		dev;
	struct spi_master	*master;
	u32			max_speed_hz;
	u8			chip_select;
	u8			bits_per_word;
	u16			mode;
#define	SPI_CPHA	0x01			/* clock phase */
#define	SPI_CPOL	0x02			/* clock polarity */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#define	SPI_MODE_1	(0|SPI_CPHA)
#define	SPI_MODE_2	(SPI_CPOL|0)
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)
#define	SPI_CS_HIGH	0x04			/* chipselect active high? */
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */
#define	SPI_READY	0x80			/* slave pulls low to pause */
#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */
#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */
#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */
#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */
	int			irq;
	void			*controller_state;
	void			*controller_data;
	char			modalias[SPI_NAME_SIZE];
	int			cs_gpio;	/* chip select gpio */

	/* the statistics */
	struct spi_statistics	statistics;
};

spi设备驱动通过spi_driver结构体描述。

struct spi_driver {
	const struct spi_device_id *id_table;
	int			(*probe)(struct spi_device *spi);
	int			(*remove)(struct spi_device *spi);
	void			(*shutdown)(struct spi_device *spi);
	struct device_driver	driver;
};

当spi设备和驱动匹配后,spi_driver结构体的probe函数就会执行。

spi_driver的常用的API函数如下:

/*spi_driver注册函数*/
#define spi_register_driver(driver)

/*spi_driver注销函数*/
static inline void spi_unregister_driver(struct spi_driver *sdrv)

4.spi设备和驱动匹配过程

spi设备和驱动的匹配是由spi总线完成的,spi总线定义如下:

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
};
EXPORT_SYMBOL_GPL(spi_bus_type);

spi_match_device就是匹配函数,分析如下:

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
	const struct spi_device	*spi = to_spi_device(dev);
	const struct spi_driver	*sdrv = to_spi_driver(drv);

	/* 设备树匹配方式 */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* ACPI匹配方式 */
	if (acpi_driver_match_device(dev, drv))
		return 1;
		
	/*传统id_table匹配方式*/
	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);
     
    /*spi_board_info 中 modalias 成员变量和 device_driver 中的 name 成员变量*/ 
	return strcmp(spi->modalias, drv->name) == 0;
}

二、Linux SPI 设备驱动编写(w25q32)

w25q32是一款32M bit的nor flash芯片。标准spi模式下最大频率可达80MHz。

1.设备树修改

spi外设需要一个片选引脚,这个引脚可以用spi主机控制器提供的,也就是让硬件自动控制片选,但是也可以使用普通GPIO,让软件控制片选。这里我们使用软件控制片选。

&pinctrl {
	        w25q32{
	              
	                w25q32_cs:w25q32_cs{
	
	                        rockchip,pins = <7 3 RK_FUNC_GPIO &pcfg_pull_up>;
	                };
	                 
	        };&spi0
{

    status = "okay"; 
	w25q32@0{
		compatible = "winbond,w25q32";
		spi-max-frequency = <1000000>;
		reg = <0>;
		cs-gpios = <&gpio7 3 GPIO_ACTIVE_LOW>;
 		pinctrl-names = "default";
		pinctrl-0 = <&w25q32_cs>;	            
	};
};

w25q32是引脚配置节点,配置w25q32片选脚。如果使用的是硬件控制片选,那么就不需要此节点。
w25q32@0,w25q32是设备名字,可以随意。0表示是挂在spi0片选0下面的,这个需要根据实际情况修改。
spi-max-frequency是spi总线最大通信频率,可以在设备树中指定,也可以在驱动代码中修改spi_device结构体的max_speed_hz成员,从而进行动态调整频率。

2.驱动程序编写

这里使用的是字符设备驱动框架,实际上这种nor flash也可以使用块设备框架。当设备和驱动匹配后,驱动程序的probe函数就会执行,将会打印读到的设备ID信息。

/**
* @file  spi_driver.c 
* @brief 
* @author   xzx   
* @date     2020-9-2
* @version  A001 
* @copyright    
* W25Q32 一共64个block 每个block 64KB  
* 每个block 分为16个sector  每个sector 4KB 
* 每个sector分为16个page  每个page 256字节
* 擦除的最小单位是扇区 也就是4KB
* 页写指令最多可写入256字节 超过256字节将会覆盖前面的 
*/
#include <linux/module.h>//模块加载卸载函数
#include <linux/kernel.h>//内核头文件
#include <linux/types.h>//数据类型定义
#include <linux/fs.h>//file_operations结构体
#include <linux/device.h>//class_create等函数
#include <linux/ioctl.h>
#include <linux/kernel.h>/*包含printk等操作函数*/
#include <linux/of.h>/*设备树操作相关的函数*/
#include <linux/gpio.h>/*gpio接口函数*/
#include <linux/of_gpio.h>
#include <linux/platform_device.h>/*platform device*/
#include <linux/spi/spi.h> /*spi相关api*/
#include <linux/delay.h> /*内核延时函数*/
#include <linux/slab.h> /*kmalloc、kfree函数*/

#include <linux/cdev.h>/*cdev_init cdev_add等函数*/
#include <asm/gpio.h>/*gpio接口函数*/
#include <asm/uaccess.h>/*__copy_from_user 接口函数*/


#define  DEVICE_NAME     "rk3288_spi"
#define  W25Qxx_PAGE_SIZE        256        /*页 大小256字节*/
#define  W25QXX_SECTOR_SIZE      4096       /*扇区 大小4096*/

/*W25Qxx 指令*/
#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg		0x05 
#define W25X_WriteStatusReg	0x01 
#define W25X_ReadData			0x03 
#define W25X_FastReadData		0x0B 
#define W25X_FastReadDual		0x3B 
#define W25X_PageProgram		0x02 
#define W25X_BlockErase			0xD8 
#define W25X_SectorErase		0x20 
#define W25X_ChipErase			0xC7 
#define W25X_PowerDown			0xB9 
#define W25X_ReleasePowerDown	0xAB 
#define W25X_DeviceID			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F 


typedef struct
{
   void *tx_buf;
   void *rx_buf;

   unsigned char cmd;//w25q32指令
   unsigned int address;//写入或者读取的地址
   unsigned int tx_len;//需要写入的字节数
   unsigned int rx_len;//需要读取的字节数

}w25qxx_data_def;


typedef struct
{
 struct device_node *node;//设备树节点
 
 struct cdev cdev;//定义一个cdev结构体
 struct class *class;//创建一个w25q32类
 struct device *device;//创建一个w25q32设备 该设备是需要挂在w25q32类下面的
 int major;//主设备号
 dev_t  dev_id;
 struct spi_device *spi; /*spi设备*/
 int cspin; /*片选脚*/
 struct mutex lock;

 w25qxx_data_def data;

}w25qxx_typdef;

static w25qxx_typdef w25qxx_dev;//定义一个w25q32设备


/*函数声明*/
static int w25qxx_read_bytes(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count);


static int w25q32_spi_read_write(w25qxx_typdef *w25q32)
{
   struct spi_device *spi = w25q32->spi;
	struct spi_transfer xfer[2];
	struct spi_message msg;
	int ret = 0;
   unsigned char *buf,*readbuf;

   memset(&xfer, 0, sizeof(xfer));/*必须清0 否则无法spi_sync函数无法发送数据*/

   xfer[0].tx_buf	= w25q32->data.tx_buf;
   xfer[0].len	= w25q32->data.tx_len;
   
   buf = (unsigned char *)(w25q32->data.tx_buf);

   //printk("write:len = %d\n",xfer[0].len);

   xfer[1].rx_buf	= w25q32->data.rx_buf;
   xfer[1].len	= w25q32->data.rx_len;

   spi_message_init(&msg);
   spi_message_add_tail(&xfer[0], &msg);

   if(w25q32->data.rx_len)
   {
      spi_message_add_tail(&xfer[1], &msg);
   }

   ret = spi_sync(spi, &msg);	
   if(ret != 0)
   {  
      printk("spi_sync failed %d\n", ret);
   }
   
   readbuf = (unsigned char *)(w25q32->data.rx_buf);
   //printk("read:len = %d\n",xfer[1].len);
	return ret;
}

static void spi_cs_enable(void)
{
  gpio_set_value(w25qxx_dev.cspin, 0); /* cs = 0 */
}

static void spi_cs_disable(void)
{
  gpio_set_value(w25qxx_dev.cspin, 1); /* cs = 1 */
}

static void spi_write_enable(void)
{
   int ret;
   unsigned char tx_buf[1];//

   spi_cs_enable();
    
   tx_buf[0] = W25X_WriteEnable;/*写使能指令*/
   w25qxx_dev.data.tx_buf= tx_buf; 
   w25qxx_dev.data.tx_len = 1;
   w25qxx_dev.data.rx_len = 0;

   ret = w25q32_spi_read_write(&w25qxx_dev);

   spi_cs_disable();
}

static void spi_write_disable(void)
{

   int ret;
   unsigned char tx_buf[1];//
   spi_cs_enable();
    
   tx_buf[0] = W25X_WriteDisable;/*写失能指令*/
   
   w25qxx_dev.data.tx_buf= tx_buf; 
   w25qxx_dev.data.tx_len = 1;
   w25qxx_dev.data.rx_len = 0;

   ret = w25q32_spi_read_write(&w25qxx_dev);

   spi_cs_disable();
}

/** 
 * 获取w25qxx状态寄存器
 * @param[in]    w25q32    w25qxx设备结构体
 * @return       大于0:状态寄存器的值 负值:失败
 * @note
 * //BIT7  6   5   4   3   2   1   0
 * //SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 */ 
static int w25qxx_get_sr(w25qxx_typdef *w25q32)
{
   int ret = -EINVAL;
   unsigned char tx_buf[1];//
   unsigned char rx_buf[1];//
   
   spi_cs_enable();

   tx_buf[0] = W25X_ReadStatusReg;
   
   w25q32->data.tx_buf= tx_buf; 
   w25q32->data.tx_len = 1;

   w25q32->data.rx_buf = rx_buf;
   w25q32->data.rx_len = 1;
 
   ret = w25q32_spi_read_write(w25q32);

   spi_cs_disable();

   if(ret < 0)
   {
      printk("w25qxx_get_sr failed \n");
      return ret;
   }

   return rx_buf[0];
}

/** 
 * 设置w25qxx状态寄存器
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    value     需要设置的新值
 * @return       0:执行成功 负值:失败
 * //BIT7  6   5   4   3   2   1   0
 * //SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 * 只有bit7 bit5 bit4 bit3 bit2这几位可写
 */ 
static int w25qxx_set_sr(w25qxx_typdef *w25q32,unsigned char value)
{
   int ret = -EINVAL;
   unsigned char tx_buf[2];//
   
   spi_cs_enable();

   tx_buf[0] = W25X_ReadStatusReg;
   tx_buf[1] = value;

   w25q32->data.tx_buf= tx_buf; 
   w25q32->data.tx_len = 2;

   w25q32->data.rx_len = 0;/*rx_len设置为0 表示不需要接收数据*/
 
   ret = w25q32_spi_read_write(w25q32);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("w25qxx_set_sr failed\n");
      return ret;
   }

   return 0;
}

/** 
 * 设置w25qxx的制造商ID
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    value     需要设置的新值
 * @return       0:执行成功 负值:失败
 * @note w25qxx芯片的制造商ID为EFh 后一个字节代表容量
 * W25Q80--13h   W25Q16--14h  W25Q32--15h  W25Q128--17h
 */ 
static int w25qxx_get_id(w25qxx_typdef *w25q32)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];//
   unsigned char rx_buf[2];//
   
   spi_cs_enable();

   tx_buf[0] = W25X_ManufactDeviceID;/*读取ID指令*/
   tx_buf[1] = 0x0;
   tx_buf[2] = 0x0;
   tx_buf[3] = 0x0;
   
   w25q32->data.tx_buf= tx_buf; 
   w25q32->data.tx_len = 4;

   w25q32->data.rx_buf = rx_buf;
   w25q32->data.rx_len = 2;
 
   ret = w25q32_spi_read_write(w25q32);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("w25qxx_get_id failed %d\n",ret);
      return ret;
   }

   return (rx_buf[0] << 8 | rx_buf[1]);
}

/** 
 * 判断W25Qxx是否空闲?
 * @return       0:空闲  1:忙
 * @note w25qxx状态寄存器的bit0
 */ 
static int w25qxx_wait_idle(void)
{
   int ret = -EINVAL; 
	do {
		  ret = w25qxx_get_sr(&w25qxx_dev);
        if(ret < 0 )
        {
           return ret;/*通信错误*/
        }
        else
        {
            if(!(ret & 0x01))
            {
               return 0;/*w25q32空闲*/
            }
        }  
		  /* REVISIT: at HZ=100, this is sloooow */
		  msleep(10);
	} while(1);   
   return 1; 
}

/** 
 * 擦除w25qxx的一个扇区
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    address   扇区地址
 * @return       0:执行成功 负值:失败
 * @note 需要关闭写保护,并等待flash操作完成
 */ 
static int w25qxx_erase_sector(w25qxx_typdef *w25q32,unsigned int address)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];//
   
   spi_write_enable();/*写保护关闭*/
   spi_cs_enable();

   tx_buf[0] = W25X_SectorErase;/*扇区擦除指令*/
   tx_buf[1] = (unsigned char)((address>>16) & 0xFF);
   tx_buf[2] = (unsigned char)((address>>8) & 0xFF);
   tx_buf[3] = (unsigned char)(address & 0xFF);
   
   w25q32->data.tx_buf= tx_buf; 
   w25q32->data.tx_len = 4;

   w25q32->data.rx_len = 0;
 
   ret = w25q32_spi_read_write(w25q32);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("erase sector@%d failed %d\n",address,ret);
      return ret;
   }
   ret = w25qxx_wait_idle();/*等待flash内部操作完成*/
   spi_write_disable();/*写保护打开*/

   return ret;
}


/** 
 * 判断扇区是否需要擦除
 * @param[in]    old     FLASH内旧数据首地址
 * @param[in]    new     新写入数据首地址
 * @param[in]    count   数据长度
 * @return       1:需要擦除 0:不需要
 * @note 
 */ 
static int w25qxx_need_erase(unsigned char*old,unsigned char*new,int count)
{
   int ret = 1;
   int i;
   unsigned char p;
	/*
	算法第1步:old 求反, new 不变
	      old    new
		   1101   0101
	~     
		 = 0010   0101

	算法第2步: old 求反的结果与 new 位与
		  old    new
	&	  0010   0101
		 =0000

	算法第3步: 结果为0,则表示无需擦除. 否则表示需要擦除
   原理:
   问题可以表述为:判断两个数据的对应的每一位是否存在0->1的变化
   将原数据按位取反,所有位的0都变成了1,再与新数据 & 操作
   结果不为0,说明新数据有些位是1 而原来是0 故需要擦除
	*/

   for ( i = 0; i < count; i++)
   {
      p = *old++;
      p = ~p; 
      
      if((p &(*new++))!=0)
      {
         return 1;
      }
   }
   return 0;

  /*for(i=0;i< count;i++)
  {
     if(*old++ != 0xFF )return 1;
  } 
  
  return 0;*/
}


/** 
 * 读取若干字节数据
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    address   需要读取的首地址
 * @param[out]    buf      缓存区
 * @param[in]    count     需要读取的数据长度
 * @return       0:执行成功 负值:失败
 * @note 可以读取任意字节 但不要超过FALSH容量
 */ 
static int w25qxx_read_bytes(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned char tx_buf[4];//
   
   spi_cs_enable();

   tx_buf[0] = W25X_ReadData;/*读取数据指令*/
   tx_buf[1] = (unsigned char)((address>>16) & 0xFF);
   tx_buf[2] = (unsigned char)((address>>8) & 0xFF);
   tx_buf[3] = (unsigned char)(address & 0xFF);
   
   w25q32->data.tx_buf= tx_buf; 
   w25q32->data.tx_len = 4;

   w25q32->data.rx_buf = buf;
   w25q32->data.rx_len = count;
 
   ret = w25q32_spi_read_write(w25q32);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("read@%d ,%d bytes failed %d\n",address,count,ret);
      return ret;
   }

   return ret;
}

/** 
 * 写入1页数据
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    address   需要写入的首地址
 * @param[in]    buf       缓存区
 * @param[in]    count     需要写入的数据长度
 * @return       0:执行成功 负值:失败
 * @note 最多写入256字节数据 
 */ 
static int w25qxx_write_page(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned char *tx_buf;/*数据缓冲区*/
   
   tx_buf = (unsigned char*)kzalloc(count+4,GFP_KERNEL);
   if(!tx_buf)
       return -ENOMEM;
 

   spi_write_enable();/*写保护关闭*/
   spi_cs_enable();

   tx_buf[0] = W25X_PageProgram;/*页写指令*/
   tx_buf[1] = (unsigned char)((address>>16) & 0xFF);
   tx_buf[2] = (unsigned char)((address>>8) & 0xFF);
   tx_buf[3] = (unsigned char)(address & 0xFF);
   
   memcpy(&tx_buf[4],buf,count);

   w25q32->data.tx_buf= tx_buf; 
   w25q32->data.tx_len = count+4;

   w25q32->data.rx_len = 0;/*不需要读*/
   
   //printk("tx_data:%d-%d-%d-%d,count=%d\n",tx_buf[4],tx_buf[5],tx_buf[6],tx_buf[7],w25q32->data.tx_len);

   ret = w25q32_spi_read_write(w25q32);

   spi_cs_disable();

   if(ret != 0)
   {
      printk("write page@%d ,%d bytes failed %d\n",address,count,ret);
      kfree(tx_buf);
      spi_write_disable();/*写保护打开*/
      return ret;
   }
   ret = w25qxx_wait_idle();
   kfree(tx_buf); 
   spi_write_disable();/*写保护打开*/
   return ret;
}

/** 
 * 写入多页数据
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    address   需要写入的首地址
 * @param[in]    buf       缓存区
 * @param[in]    count     需要写入的数据长度
 * @return       0:执行成功 负值:失败
 * @note 最多写入4096字节数据,,本函数带擦除功能 
 */ 
static int w25qxx_write_pages(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned int remain_of_page,need_to_write;
   unsigned int sector_first_address,sector_offset;
   unsigned char *write_buf;/*数据缓冲区*/

   write_buf = (unsigned char*)kzalloc(4096,GFP_KERNEL);
   if(!write_buf)
       return -ENOMEM;

   /*获取指定地址所在扇区的扇区首地址*/    
   sector_first_address = address & (~(W25Qxx_PAGE_SIZE-1)) ;

   /*获取指定地址在所在扇区内的偏移量*/
   sector_offset = address % 4096;

   ret = w25qxx_read_bytes(w25q32,sector_first_address,write_buf,4096);//读出整个扇区
   if(ret < 0 )
   {
      return ret;
   }
   
   /*判断是否需要擦除*/
   if(w25qxx_need_erase(&write_buf[sector_offset],buf,count))
   {
      printk("erase\n");
      w25qxx_erase_sector(w25q32,sector_first_address);
   }
   
   kfree(write_buf);

   remain_of_page = W25Qxx_PAGE_SIZE - address%W25Qxx_PAGE_SIZE;//获取本页还剩多少个字节空间可写入
   need_to_write = remain_of_page;/*下一次最多可写remain_of_page个字节*/
   
   printk("sector_first_address=%d,sector_offset=%d\n",sector_first_address,sector_offset);

   printk("address=%d,count=%d\n",address,count);

   if(count <= need_to_write) 
   {
      /*需要写入的字节数少于剩余空间  直接写入实际字节数*/
      ret = w25qxx_write_page(w25q32,address,buf,count);
      return ret;
   }
   else
   {    
      do
      {
         printk("address=%d\n,need_to_write=%d\n",address,need_to_write); 
         ret = w25qxx_write_page(w25q32,address,buf,need_to_write);
         if(ret !=0)
         {
            return ret;
         }
         if(need_to_write == count)
         {
             break;
         }
         else
         {
            buf+=need_to_write;
            address+=need_to_write;
            count-=need_to_write;         
            if(count > W25Qxx_PAGE_SIZE)
            {
               need_to_write = W25Qxx_PAGE_SIZE;
            }
            else
            {
               need_to_write = count;
            }
         }        
      } while (1);  
   }
   return ret;
}

/** 
 * 写入大量字节数据
 * @param[in]    w25q32    w25qxx设备结构体
 * @param[in]    address   需要写入的首地址
 * @param[in]    buf       缓存区
 * @param[in]    count     需要写入的数据长度
 * @return       0:执行成功 负值:失败
 * @note 写入数据不要超过FALSH最大容量,本函数带擦除功能 
 */ 
static int w25qxx_write_more_bytes(w25qxx_typdef *w25q32,unsigned int address,unsigned char* buf,int count)
{
   int ret = -EINVAL;
   unsigned int num_of_sector,remain_of_sector,sector_offset;
   unsigned int sector_first_address,need_to_write;
   unsigned char *write_buf;/*数据缓冲区*/
   
   write_buf = (unsigned char*)kzalloc(4096,GFP_KERNEL);
   if(!write_buf)
       return -ENOMEM;
   
   num_of_sector = address / W25QXX_SECTOR_SIZE;
   sector_offset = address % W25QXX_SECTOR_SIZE;
   remain_of_sector = W25QXX_SECTOR_SIZE - address % W25QXX_SECTOR_SIZE;/*当前地址所在扇区 还剩下多少空间*/
   
   need_to_write = remain_of_sector;

   if(count <= need_to_write)
   {
      ret = w25qxx_write_pages(w25q32,address,buf,count);
      return ret;
   }
   else
   {
       do
      {
         ret = w25qxx_write_pages(w25q32,address,buf,need_to_write);
         if(ret !=0)
         {
            return ret;
         }
         if(need_to_write == count)
         {
             break;
         }
         else
         {
            buf+=need_to_write;
            address+=need_to_write;
            count-=need_to_write;         
            if(count > W25QXX_SECTOR_SIZE)
            {
               need_to_write = W25QXX_SECTOR_SIZE;
            }
            else
            {
               need_to_write = count;
            }
         }        
      } while (1);  
   }
   return ret;
}



static int w25qxx_open(struct inode *inode, struct file *filp)
{
   filp->private_data = &w25qxx_dev;

	return 0;
}

static int w25qxx_release(struct inode* inode ,struct file *filp)
{
   w25qxx_typdef * dev = (w25qxx_typdef *) filp->private_data;
	return 0;
}


static int w25qxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{  
  int ret;  
  unsigned char *write_buf;/*数据缓冲区*/
  w25qxx_typdef * dev = (w25qxx_typdef *) filp->private_data;
  unsigned char address = filp->f_pos;

  write_buf = (unsigned char*)kzalloc(count,GFP_KERNEL);
  if(!write_buf )
       return -ENOMEM;
  
  if (copy_from_user(write_buf, buf, count))
  {
		kfree(write_buf);
      return -EFAULT;
  }

  printk("write@%d,count:%d\n",address,count);
  
  ret = w25qxx_write_more_bytes(dev,address,write_buf,count);

  kfree(write_buf);
  return ret;
}



static ssize_t w25qxx_read(struct file *filp,char __user *buf, size_t count,loff_t *f_pos)
{
  int ret;  
  unsigned char *read_buf;/*数据缓冲区*/
  w25qxx_typdef * dev = (w25qxx_typdef *) filp->private_data;
  unsigned char address = filp->f_pos;
  
  read_buf = (unsigned char*)kzalloc(count,GFP_KERNEL);
  if(!read_buf )
       return -ENOMEM;

  printk("read@%d,count:%d\n",address,count);

  ret = w25qxx_read_bytes(dev,address,read_buf,count);
  
  if (copy_to_user(buf, read_buf, count))
  {
      ret = -EFAULT;
  }

  kfree(read_buf);
  return ret;
}


loff_t w25qxx_llseek(struct file *file, loff_t offset, int whence)
{
	loff_t ret,pos,oldpos;
	oldpos = file->f_pos;
	switch (whence) 
	{
		case SEEK_SET:
		     pos = offset; 
			break;
		case SEEK_CUR:
	        pos = oldpos + offset;
			break;
		case SEEK_END:
	        pos = W25Qxx_PAGE_SIZE - offset;
			break;	
		default:
		    printk("cmd not supported\n");
			break;
	}
	
	if(pos < 0 || pos > W25Qxx_PAGE_SIZE)
	{ 	
		printk("error: pos > W25Qxx_PAGE_SIZE !\n");
		ret = -EINVAL;
		return ret;
	}
   file->f_pos = pos;
	ret = offset;	
	return ret;
}



static struct file_operations w25qxx_fops={
	.owner		= THIS_MODULE,
	.open 		= w25qxx_open,
	.write		= w25qxx_write,
	.read       = w25qxx_read,
	.release	   = w25qxx_release,
	.llseek     = w25qxx_llseek,
};


static int w25qxx_probe(struct spi_device *spi)
{
   int ret = -1;
   const char *string = NULL;

   w25qxx_typdef *dev = &w25qxx_dev;
   
   printk("w25q32 probe!\n"); 
   /*获取设备节点*/
   w25qxx_dev.node = of_find_node_by_path("/spi@ff110000/w25q32@0");
   if(w25qxx_dev.node == NULL)
   {
	  printk("device-tree:not found w25q32!\r\n"); 
     return -1;
   }
   
   /*读取W25Q32设备节点的compatible属性值*/
   ret = of_property_read_string(w25qxx_dev.node,"compatible",&string);
   if(ret == 0)
   {
      printk("%s\n",string);
   }
   
   /*申请gpio 用作片选*/
   w25qxx_dev.cspin = of_get_named_gpio(w25qxx_dev.node,"cs-gpios",0);
   if(!gpio_is_valid(w25qxx_dev.cspin))
   {
     printk("get gpio error\n");
     ret = -EINVAL;
     return ret;
   }
   
   printk("gpio = %d\n",w25qxx_dev.cspin);

   //gpio_free(w25qxx_dev.cspin);
   ret = gpio_request(w25qxx_dev.cspin,"spi-cs");
   if(ret < 0) 
   {
      printk("gpio_request %d failed\n",w25qxx_dev.cspin);
      return ret;
   }
   gpio_direction_output(w25qxx_dev.cspin,1);

   /*申请设备号*/
   alloc_chrdev_region(&w25qxx_dev.dev_id,0,1,DEVICE_NAME);


   /*初始化一个cdev*/
   cdev_init(&w25qxx_dev.cdev,&w25qxx_fops);

   
   /*向cdev中添加一个设备*/
   cdev_add(&w25qxx_dev.cdev,w25qxx_dev.dev_id,1);


   /*创建一个eeprom_class类*/
   w25qxx_dev.class = class_create(THIS_MODULE, "eeprom_class");
   if(w25qxx_dev.class == NULL)
   {
      printk("class_create failed\r\n");
	  return -1;
   }

   /*在eeprom_class类下创建一个eeprom_class设备*/
   w25qxx_dev.device = device_create(w25qxx_dev.class, NULL, w25qxx_dev.dev_id, NULL, DEVICE_NAME);

   /*获取与本驱动匹配的spi设备*/
   w25qxx_dev.spi = spi;

   w25qxx_dev.spi->mode = SPI_MODE_0; /*spi flash对应的模式*/

   //w25qxx_dev.spi->bits_per_word = 8;

   spi_setup(w25qxx_dev.spi);

   mutex_init(&dev->lock);

   ret = w25qxx_get_id(&w25qxx_dev);
   
   printk("id=%04x\n",ret);

   return  0;
}

static int w25qxx_remove(struct spi_device *spi)
{
   
    printk("w25qxx remove!\n"); 

    /*删除at24c02类*/
	cdev_del(&w25qxx_dev.cdev);

	/*释放at24c02设备号*/
	unregister_chrdev_region(w25qxx_dev.dev_id, 1);

	/*注销at24c02设备*/
	device_destroy(w25qxx_dev.class, w25qxx_dev.dev_id);

	/*注销at24c02类*/
	class_destroy(w25qxx_dev.class);

   gpio_free(w25qxx_dev.cspin);

   return 0;
}


static const struct of_device_id w25qxx_of_match[] = {
   {.compatible = "winbond,w25q32"},
   {},
};

static const struct spi_device_id w25q32_id[] = {
	{ "xxxx", 0 },
	{},
};

static struct spi_driver w25qxx_driver = {

   .driver = {
	  .owner = THIS_MODULE,
      .name = "w25q32",
      .of_match_table = w25qxx_of_match,
   },
   .probe = w25qxx_probe,
   .remove  = w25qxx_remove,  
   .id_table	= w25q32_id,      
};

static int __init w25qxx_init(void)
{
   return spi_register_driver(&w25qxx_driver);
}

static void w25qxx_exit(void)
{
   spi_unregister_driver(&w25qxx_driver);
   printk("module exit ok\n");
}


module_init(w25qxx_init);
module_exit(w25qxx_exit);


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("W25Q32 driver");
MODULE_AUTHOR("xzx2020");

3.测试APP

测试app首先向FLASH指定地址写入1000字节数据,然后再读出数据。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>
 #include<string.h>

#define  buf_size 1000


void print_array(const char *title,char *buf,int count)
{
   int i = 0; 
   printf(title);
   for(i=0;i<count;i++) 
   {
      printf(" %d",buf[i]);
   }
   printf("\n");
}

int main(int argc, char *argv[])
{
   int fd;
   int i;
   int ret;
   int count = buf_size;
   char buf[buf_size]={0};
   int offset = 0;

   /*判断传入的参数是否合法*/
   if(argc != 2)
   {
      printf("Usage:error\n");
      return -1;
   }
  
   /*解析传入的参数*/
   offset =atoi(argv[1]);
   printf("offset = %d\n",offset);
 
    /*打开设备文件*/
   fd = open("/dev/rk3288_spi",O_RDWR);
   if(fd < 0)
   {
      printf("open dev fail fd=%d\n",fd); 
      close(fd);
      return fd;
   }

   /*缓存数组赋值*/
   for(i=0;i<buf_size;i++) 
   {
     buf[i] = i;
   }
      
    /*写入数据*/ 
   lseek(fd,offset,SEEK_SET);
   ret = write(fd,buf,buf_size);
   if(ret < 0)
   {
      printf("write to w25q32 error\n");
      close(fd);
      return ret;
   }

   /*打印数据*/
   print_array("write to w25q32:",buf,count);

   /*清空缓冲区*/
   memset(buf,0,buf_size);
   
   /*读取数据*/
   ret = lseek(fd,offset,SEEK_SET);
   printf("lseek = %d\n",ret);
   
   ret = read(fd,buf,count);
   if(ret < 0)
   {
      printf("read from w25q32 error\n");
      close(fd);
      return ret;
   }
   
   /*打印数据*/
   print_array("read from w25q32:",buf,count);

   close(fd);
   return 0;   
}
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值