一、概述

MFRC522 支持 SPI、I2C、UART 接口,我在某宝上购买了一个 SPI 接口的 RC522 模块。此笔记主要要是通过 RC522 模块学习 linux 中的 SPI 驱动,方便今后写其他 SPI 驱动时做参考。有需要的小伙伴可以收藏一下。

二、RC522 介绍

  1. 产品外观
    现在的生活中 IC 卡的生活场景大家都不陌生了,外观如下图所示,其中 PCB 部分是主机,白色和绿色的是 IC 卡
    【Linux SPI】RFID RC522 设备驱动

  2. 产品介绍
    MFRC522 是应用于 13.56MHz 非接触式通信中高集成度读写卡系列芯片中的一员。是NXP 公司针对“三表”应用推出的一款低 电压、低成本、体积小的非接触式读写卡芯片,是智能仪表和便携式手持设备研发的较好选择【百度百科】。

    更多信息可以参考芯片手册,对于英文不好的小伙伴,可以参考MFRC522中文手册 https://www.doc88.com/p-4042994624020.html?r=1,MFRC522 的引脚如下图所示:
    【Linux SPI】RFID RC522 设备驱动

  3. 卡片的内部储存信息
    一张卡片分成若干个扇区,一个扇区有四个块,每一块存储16字节的信息,以块为存取单位。第0扇区的第0块存储卡片的UID和厂商信息,每个扇区的第3块存储该扇区的密钥和控制字信息(这里的第三块是指 block * 4 + 3),其余均可以用来存储数据。

    每个区的块3作为控制块,块0、1、2作为数据块,其中数据块用作一般的数据存储时,可以对其中的数据进行读写操作;用作数据值,可以进行初始化值、加值、减值、读值操作,我在网上找了一张图片,如下图所示:
    【Linux SPI】RFID RC522 设备驱动
    注意:我没见过其他的卡片,是否存在我就不知道了,我手里有一张卡片容量为8K位EEPROM,分为16个扇区,每个扇区为4块,每块16个字节,总共有64块,之前我就错误的以为只有一个卡片容量。

  4. 存取控制
    每个扇区的密码和存取控制都是独立的,存取控制是4个字节,即32位(在块3中)。
    每个块都有存取条件,存取条件是由密码和存取控制共同决定的。
    每个块都有相应的三个控制位,这三个控制位存在于存取控制字节中,相应的控制位决定了该块的访问权限,控制位如图:
    【Linux SPI】RFID RC522 设备驱动

    注意: 每个扇区的所有块的存取条件控制位,都放在了该扇区的块3中,如图:
    【Linux SPI】RFID RC522 设备驱动

  5. 数据块的存取控制
    对数据块,与就是块0、1、2的存取控制是由对应块的控制位来决定的:
    【Linux SPI】RFID RC522 设备驱动
    注意: 要想对数据块进行操作,首先要看该数据块的控制位是否允许对数据块的操作,如果允许操作,再看需要验证什么密码,只有验证密码正确后才可以对该数据块执行相应操作。 一般密码A的初始值都是 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF

  6. 控制块的存取控
    块3(控制块)的存取操作与数据块不同,如图:
    【Linux SPI】RFID RC522 设备驱动

  7. 通信流程
    【Linux SPI】RFID RC522 设备驱动

注意:具体说明参考 MFRC522 手册

三、SPI 设备驱动

/**
 * @brief 向 spi 设备中写入多个寄存器数据
 * 
 * @param spi spi 设备
 * @param reg 要写入的寄存器首地址
 * @param buf 要写入的数据缓冲区
 * @param len 要写入的数据长度
 * @return 返回执行结果
 */
static s32 spi_write_regs(struct spi_device *spi, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message msg;
	struct spi_transfer *trf;

	/* 申请内存*/
	trf = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	if(!trf) {
		return -ENOMEM;
	}

	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}

	/* 一共发送 len+1 个字节的数据,第一个字节为寄存器首地址,len 为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址 bit8 要清零 */
	memcpy(txdata+1, buf, len); /* 把 len 个数据拷贝到 txdata 里 */
	trf->tx_buf = txdata; /* 要发送的数据 */
	trf->len = len+1; /* trf->len = 发送的长度+读取的长度 */
	spi_message_init(&msg); /* 初始化 spi_message */
	spi_message_add_tail(trf, &msg);/*添加到 spi_message 队列 */
	ret = spi_sync(spi, &msg); /* 同步发送 */
	if(ret) {
		goto out2;
	}

out2:
	kfree(txdata); /* 释放内存 */
out1:
	kfree(trf); /* 释放内存 */
	return ret;

}

/**
 * @brief 读取 spi 的多个寄存器数据
 * 
 * @param spi spi 设备
 * @param reg 要读取的寄存器首地址
 * @param buf 要读取的数据缓冲区
 * @param len 要读取的数据长度
 * @return 返回执行结果
 */
static int spi_read_regs(struct spi_device *spi, u8 reg, void *buf, int len)
{

	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	struct spi_message msg;
	struct spi_transfer *trf;

	/* 申请内存*/
	trf = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	if(!trf) {
		return -ENOMEM;
	}

	/* 申请内存 */
	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送 len+1 个字节的数据,第一个字节为寄存器首地址,一共要读取 len 个字节长度的数据,*/
	txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit8 要置 1 */ 
	trf->tx_buf = txdata; /* 要发送的数据 */

	trf->rx_buf = rxdata; /* 要读取的数据 */
	trf->len = len+1; /* trf->len = 发送的长度+读取的长度 */
	spi_message_init(&msg); /* 初始化 spi_message */
	spi_message_add_tail(trf, &msg);/* 将 spi_transfer 添加到 spi_message*/
	ret = spi_sync(spi, &msg); /* 同步发送 */
	if(ret) {
		goto out2;
	}

	memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */

out2:
	kfree(rxdata); /* 释放内存 */
out1: 
	kfree(trf); /* 释放内存 */

	return ret;
}

/**
 * @brief 打开设备
 * 
 * @param inode 传递给驱动的 inode
 * @param filp 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return 0 成功;其他 失败
 */
static int rc522_open(struct inode *inode, struct file *filp)
{

}

/**
 * @brief 从设备读取数据
 * 
 * @param filp 要打开的设备文件(文件描述符)
 * @param buf 返回给用户空间的数据缓冲区
 * @param cnt 要读取的数据长度
 * @param offt 相对于文件首地址的偏移
 * @return 0 成功;其他 失败
 */
static ssize_t rc522_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{

}

/**
 * @brief 向设备写数据
 * 
 * @param filp 设备文件,表示打开的文件描述符
 * @param buf 要写给设备写入的数据
 * @param cnt 要写入的数据长度
 * @param offt 相对于文件首地址的偏移
 * @return 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t rc522_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{

}

/**
 * @brief 关闭/释放设备
 * 
 * @param filp 要关闭的设备文件(文件描述符)
 * @return 0 成功;其他 失败
*/
static int rc522_release(struct inode *inode, struct file *filp)
{

}

/* 设备操作函数结构体 */
static struct file_operations rc522_ops = {
    .owner = THIS_MODULE, 
    .open = rc522_open,
    .read = rc522_read,
    .write = rc522_write,
    .release = rc522_release,
};

/**
 * @brief spi 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
 * @param client spi 设备
 * @param id spi 设备 ID
 * @return 0,成功;其他负值,失败
*/
static int rc522_probe(struct spi_device *spi)
{
    int ret = -1; 							// 保存错误状态码
	struct rc522_dev_s *rc522_dev;			// 设备数据结构体

	/*---------------------注册字符设备驱动-----------------*/ 
	
	/* 驱动与总线设备匹配成功 */
	pr_info("\t  %s match successed  \r\n", spi->modalias);
	// dev_info(&spi->dev, "match successed\n");

	/* 申请内存并与 client->dev 进行绑定。*/
	/* 在 probe 函数中使用时,当设备驱动被卸载,该内存被自动释放,也可使用 devm_kfree() 函数直接释放 */
	rc522_dev = devm_kzalloc(&spi->dev, sizeof(*rc522_dev), GFP_KERNEL);
	if(!rc522_dev)
	{
		pr_err("Failed to request memory \r\n");
		return -ENOMEM;
	}
	/* 1、创建设备号 */
	/* 采用动态分配的方式,获取设备编号,次设备号为0 */
	/* 设备名称为 SPI_NAME,可通过命令 cat /proc/devices 查看 */
	/* RC522_CNT 为1,只申请一个设备编号 */
	ret = alloc_chrdev_region(&rc522_dev->devid, 0, RC522_CNT, RC522_NAME);
	if (ret < 0)
	{
		pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", RC522_NAME, ret);
		return -ENOMEM;
	}

	/* 2、初始化 cdev */
	/* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
	rc522_dev->cdev.owner = THIS_MODULE;
	cdev_init(&rc522_dev->cdev, &rc522_ops);

	/* 3、添加一个 cdev */
	/* 添加设备至cdev_map散列表中 */
	ret = cdev_add(&rc522_dev->cdev, rc522_dev->devid, RC522_CNT);
	if (ret < 0)
	{
		pr_err("fail to add cdev \r\n");
		goto del_unregister;
	}

	/* 4、创建类 */
	rc522_dev->class = class_create(THIS_MODULE, RC522_NAME);
	if (IS_ERR(rc522_dev->class)) 
	{
		pr_err("Failed to create device class \r\n");
		goto del_cdev;
	}

	/* 5、创建设备,设备名是 RC522_NAME */
	/*创建设备 RC522_NAME 指定设备名,*/
	rc522_dev->device = device_create(rc522_dev->class, NULL, rc522_dev->devid, NULL, RC522_NAME);
	if (IS_ERR(rc522_dev->device)) {
		goto destroy_class;
	}
	rc522_dev->spi = spi;

	/*初始化 rc522_device */
	spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);

	/* 保存 rc522_dev 结构体 */
	spi_set_drvdata(spi, rc522_dev);

	return 0;

destroy_class:
	device_destroy(rc522_dev->class, rc522_dev->devid);
del_cdev:
	cdev_del(&rc522_dev->cdev);
del_unregister:
	unregister_chrdev_region(rc522_dev->devid, RC522_CNT);
	return -EIO;
}

/**
 * @brief spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
 * @param client spi 设备
 * @return 0,成功;其他负值,失败
*/
static int rc522_remove(struct spi_device *spi)
{
	struct rc522_dev_s *rc522_dev = spi_get_drvdata(spi);

	/*---------------------注销字符设备驱动-----------------*/

	/* 1、删除 cdev */
	cdev_del(&rc522_dev->cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(rc522_dev->devid, RC522_CNT);
	/* 3、注销设备 */
	device_destroy(rc522_dev->class, rc522_dev->devid);
	/* 4、注销类 */
	class_destroy(rc522_dev->class);
	return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct spi_device_id gtp_device_id[] = {
	{"rfid,rfid_rc522", 0},
	{}
};

/* 设备树匹配表 */
static const struct of_device_id rc522_of_match_table[] = {
	{.compatible = "rfid,rfid_rc522"},
	{/* sentinel */}
};

/* SPI 驱动结构体 */
static struct spi_driver rc522_driver = {
	.probe = rc522_probe,
	.remove = rc522_remove,
	.id_table = gtp_device_id,
	.driver = {
		.name = "rfid,rfid_rc522",
		.owner = THIS_MODULE,
		.of_match_table = rc522_of_match_table,
	},
};

/**
 * @brief 驱动入口函数
 * @return 0,成功;其他负值,失败
*/
static int __init rc522_driver_init(void)
{
	int ret;
	pr_info("spi_driver_init\n");
	ret = spi_register_driver(&rc522_driver);
	return ret;
}

/**
 * @brief 驱动出口函数
 * @return 0,成功;其他负值,失败
*/
static void __exit rc522_driver_exit(void)
{
	pr_info("spi_driver_exit\n");
	spi_unregister_driver(&rc522_driver);
}


/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(rc522_driver_init);
module_exit(rc522_driver_exit);

/* LICENSE 和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIAOZHU");
MODULE_INFO(intree, "Y");

注意:在不确认是驱动还是SPI从设备的问题时,可以通过下面函数进行简单测试,测试时只需要将发送和接收引脚短接,就可以直接读回发送的数据。

/**
 * @brief 向 spi 设备中同时读写多个寄存器数据
 * 
 * @param spi spi 设备
 * @param reg 要写入的寄存器首地址
 * @param write_buf 要写入的数据缓冲区
 * @param read_buf 要读取的数据缓冲区
 * @param len 要写入的数据长度
 * @return 返回执行结果
 */
static s32 spi_read_write_regs(struct spi_device *spi, u8 reg, u8 *write_buf, u8 *read_buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message msg;
	struct spi_transfer *trf;
	unsigned char * rxdata;

	/* 申请内存*/
	trf = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	if(!trf) {
		return -ENOMEM;
	}

	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}

	/* 申请内存 */
	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送 len+1 个字节的数据,第一个字节为寄存器首地址,len 为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;
	memcpy(txdata+1, write_buf, len); /* 把 len 个数据拷贝到 txdata 里 */
	trf->tx_buf = txdata; /* 要发送的数据 */
	trf->rx_buf = rxdata; /* 要读取的数据 */

	trf->len = len+1; /* trf->len = 发送的长度+读取的长度 */
	spi_message_init(&msg); /* 初始化 spi_message */
	spi_message_add_tail(trf, &msg);/*添加到 spi_message 队列 */
	ret = spi_sync(spi, &msg); /* 同步发送 */
	if(ret) {
		goto out2;
	}

	memcpy(read_buf , rxdata+1, len); /* 只需要读取的数据 */

out2:
	kfree(txdata); /* 释放内存 */
	kfree(rxdata); /* 释放内存 */
out1:
	kfree(trf); /* 释放内存 */
	return ret;
}

四、RC522 头文件

/**
 * @file rc522_device.h
 *
 */

#ifndef _RC522_DEVICE_H_
#define _RC522_DEVICE_H_


/*********************
 *      INCLUDES
 *********************/
// #include <stdbool.h>
/*********************
 *      DEFINES
 *********************/

/* MF522 FIFO长度定义 */
#define DEF_FIFO_LENGTH         (64)        // FIFO size=64byte

/* MF522命令字 */
#define PCD_IDLE                (0x00)      // 取消当前命令
#define PCD_AUTHENT             (0x0E)      // 验证密钥
#define PCD_RECEIVE             (0x08)      // 接收数据
#define PCD_TRANSMIT            (0x04)      // 发送数据
#define PCD_TRANSCEIVE          (0x0C)      // 发送并接收数据
#define PCD_RESETPHASE          (0x0F)      // 复位
#define PCD_CALCCRC             (0x03)      // CRC计算
/* Mifare_One卡片命令字 */
#define PICC_REQIDL             (0x26)      // 寻天线区内未进入休眠状态
#define PICC_REQALL             (0x52)      // 寻天线区内全部卡
#define PICC_ANTICOLL1          (0x93)      // 防冲撞
#define PICC_ANTICOLL2          (0x95)      // 防冲撞
#define PICC_AUTHENT1A          (0x60)      // 验证A密钥
#define PICC_AUTHENT1B          (0x61)      // 验证B密钥
#define PICC_READ               (0x30)      // 读块
#define PICC_WRITE              (0xA0)      // 写块
#define PICC_DECREMENT          (0xC0)      // 扣款
#define PICC_INCREMENT          (0xC1)      // 充值
#define PICC_RESTORE            (0xC2)      // 调块数据到缓冲区
#define PICC_TRANSFER           (0xB0)      // 保存缓冲区中数据
#define PICC_HALT               (0x50)      // 休眠

/*------------------------------ MF522寄存器定义 ------------------------------*/
/* PAGE 0 */
#define RFU00                   (0x00)      // 保留
#define CommandReg              (0x01)      // 启动和停止
#define ComIEnReg               (0x02)      // 中断请求传递的使能和失能控制位
#define DivlEnReg               (0x03)      // 中断请求传递的使能和失能控制位
#define ComIrqReg               (0x04)      // 包含中断请求标志
#define DivIrqReg               (0x05)      // 包含中断请求标志
#define ErrorReg                (0x06)      // 错误标志,指示执行的上个命令的错误状态
#define Status1Reg              (0x07)      // 包含通信的状态标志
#define Status2Reg              (0x08)      // 包含接收器和发送器的状态标志
#define FIFODataReg             (0x09)      // 64 字节 FIFO 缓冲区的输入和输出
#define FIFOLevelReg            (0x0A)      // 指示 FIFO 中存储的字节数
#define WaterLevelReg           (0x0B)      // 定义 FIFO 下溢和上溢报警的 FIFO 深度
#define ControlReg              (0x0C)      // 不同的控制寄存器
#define BitFramingReg           (0x0D)      // 面向位的帧的调节
#define CollReg                 (0x0E)      // RF 接口上检测到的第一位冲突的位的位置
#define RFU0F                   (0x0F)      // 保留
/* PAGE 1 */
#define RFU10                   (0x10)      // 保留用于未来使用
#define ModeReg                 (0x11)      // 定义发送和接收的常用模式
#define TxModeReg               (0x12)      // 定义发送过程中的数据传输速率
#define RxModeReg               (0x13)      // 定义接收过程中的数据传输速率
#define TxControlReg            (0x14)      // 控制天线驱动管脚 TX1 和 TX2 的逻辑特性
#define TxAutoReg               (0x15)      // 控制天线驱动器的设置
#define TxSelReg                (0x16)      // 控制天线驱动器的内部源
#define RxSelReg                (0x17)      // 选择内部的接收器
#define RxThresholdReg          (0x18)      // 选择位译码器的阀值
#define DemodReg                (0x19)      // 定义调节器的设置
#define RFU1A                   (0x1A)      // 保留用于未来使用
#define RFU1B                   (0x1B)      // 保留用于未来使用
#define MifareReg               (0x1C)      // 控制 ISO 14443/MIFARE 模式中 106kbit/s 的通信
#define RFU1D                   (0x1D)      // 保留用于未来使用
#define RFU1E                   (0x1E)      // 保留用于未来使用
#define SerialSpeedReg          (0x1F)      // 选择串行 UART 接口的速率
/* PAGE 2 */
#define RFU20                   (0x20)      // 保留用于未来使用
#define CRCResultRegM           (0x21)      // 显示 CRC 计算的实际 MSB 值
#define CRCResultRegL           (0x22)      // 显示 CRC 计算的实际 LSB 值
#define RFU23                   (0x23)      // 保留用于未来使用
#define ModWidthReg             (0x24)      // 控制 ModWidth 的设置
#define RFU25                   (0x25)      // 保留用于未来使用
#define RFCfgReg                (0x26)      // 配置接收器增益
#define GsNReg                  (0x27)      // 选择天线驱动器管脚 TX1 和 TX2 的调制电导
#define CWGsCfgReg              (0x28)      // 选择天线驱动器管脚 TX1 和 TX2 的调制电导
#define ModGsCfgReg             (0x29)      // 选择天线驱动器管脚 TX1 和 TX2 的调制电导
#define TModeReg                (0x2A)      // 定义内部定时器的设置
#define TPrescalerReg           (0x2B)      // 定义内部定时器的设置
#define TReloadRegH             (0x2C)      // 描述 16 位长的定时器重装值
#define TReloadRegL             (0x2D)      // 描述 16 位长的定时器重装值
#define TCounterValueRegH       (0x2E)      // 显示 16 位长的实际定时器值
#define TCounterValueRegL       (0x2F)      // 显示 16 位长的实际定时器值
/* PAGE 3 */
#define RFU30                   (0x30)      // 保留用于未来使用
#define TestSel1Reg             (0x31)      // 常用测试信号的配置
#define TestSel2Reg             (0x32)      // 常用测试信号的配置和 PRBS 控制
#define TestPinEnReg            (0x33)      // D1-D7 输出驱动器的使能管脚(仅用于串行接口)
#define TestPinValueReg         (0x34)      // 定义 D1-D7 用作 I/O 总线时的值
#define TestBusReg              (0x35)      // 显示内部测试总线的状态
#define AutoTestReg             (0x36)      // 控制数字自测试
#define VersionReg              (0x37)      // 显示版本
#define AnalogTestReg           (0x38)      // 控制管脚 AUX1 和 AUX2
#define TestDAC1Reg             (0x39)      // 定义 TestDAC1 的测试值
#define TestDAC2Reg             (0x3A)      // 定义 TestDAC2 的测试值
#define TestADCReg              (0x3B)      // 显示 ADC I 和 Q 通道的实际值
#define RFU3C                   (0x3C)      // 保留用于产品测试
#define RFU3D                   (0x3D)      // 保留用于产品测试
#define RFU3E                   (0x3E)      // 保留用于产品测试
#define RFU3F		            (0x3F)      // 保留用于产品测试


/* 与 RC522 通讯时返回的错误代码 */
#define MI_OK                   (0)         // 正确
#define MI_NOTAGERR             (-1)        // 未知错误
#define MI_ERR                  (-2)        // 错误


#define MAXRLEN                 (18)



/* 定义 RC522 驱动的最大数据空间 = 67 * 16 = 1056 */
#define RC522_MAX_OFFSET		(0x042F)



/**********************
 *      TYPEDEFS
 **********************/

/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**********************
 *      MACROS
 **********************/

#endif /* _RC522_DEVICE_H_ */

五、完成程序

为了方便操作,这里的 API 函数都被我修改过的,可以对比其他博客的进行参考,比如这位大佬的博客: https://blog.csdn.net/zhiyuan2021/article/details/128922757

#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/uaccess.h>

#include "rc522_device.h"
/***************************************************************
文件名 : spi_rc522_drive.c
作者 : jiaozhu
版本 : V1.0
描述 : RFID-RC522 设备驱动文件
其他 : 无
日志 : 初版 V1.0 2023/2/16
***************************************************************/


/*------------------字符设备内容----------------------*/
#define RC522_NAME	"spi_rc522"
#define RC522_CNT	(1)

static unsigned char card_type[2];		// 卡片类型
static unsigned char card_id[4];		// 卡片id
static unsigned char card_auth_mode = 0x60;		// 密码验证类型
static unsigned char card_cipher[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};		// 卡片块密码

struct rc522_dev_s {
	struct spi_device *spi; 			// spi 设备
	dev_t devid; 						// 设备号
	struct cdev cdev; 					// cdev
	struct class *class; 				// 类
	struct device *device; 				// 设备
	struct device_node *node;			// 设备节点
};

/* 声明 SPI 操作函数 */
static s32 spi_write_regs(struct spi_device *spi, u8 reg, u8 *buf, u8 len);
static int spi_read_regs(struct spi_device *spi, u8 reg, void *buf, int len);


/**
 * @brief 向 rc522 设备的寄存器中写入 8 位数据
 * 
 * @param rc522_dev rc522 设备
 * @param reg 寄存器地址
 * @param val 写入的值
 * @return 返回执行的结果
 */
static int rc522_write_reg8(struct rc522_dev_s *rc522_dev, u8 reg, u8 value)
{
	u8 buf = value;
	return spi_write_regs(rc522_dev->spi, (reg << 1) & 0x7E, &buf, 1);
}

/**
 * @brief 从 rc522 设备的寄存器中读取 8 位数据
 * 
 * @param rc522_dev rc522 设备
 * @param reg 寄存器地址
 * @param buf 读取的缓冲区
 * @return 返回执行的结果
 */
static int rc522_read_reg8(struct rc522_dev_s *rc522_dev, u8 reg, u8 *buf)
{
	return spi_read_regs(rc522_dev->spi, (reg << 1) & 0x7E, buf, 1);
}

/**
 * @brief 置RC522寄存器位
 * 
 * @param rc522_dev rc522 设备
 * @param reg 寄存器地址
 * @param mask 置位值
 * @return 返回执行的结果
 */
static int rc522_set_bit_mask(struct rc522_dev_s *rc522_dev, u8 reg, u8 mask)  
{
	int res = 0;
    u8 tmp = 0x0;
    res = rc522_read_reg8(rc522_dev, reg, &tmp);
	if (0 != res)
	{
		return MI_NOTAGERR;
	}
    rc522_write_reg8(rc522_dev, reg, tmp | mask);  // set bit mask
	return MI_OK;
}

/**
 * @brief 清RC522寄存器位
 * 
 * @param rc522_dev rc522 设备
 * @param reg 寄存器地址
 * @param mask 清位值
 * @return 返回执行的结果
 */
static int rc522_clear_bit_mask(struct rc522_dev_s *rc522_dev, u8 reg, u8 mask)
{
	int res = 0;
    u8 tmp = 0x0;
    res = rc522_read_reg8(rc522_dev, reg, &tmp);
	if (0 != res)
	{
		return MI_NOTAGERR;
	}
    rc522_write_reg8(rc522_dev, reg, tmp & ~mask);  // set bit mask
	return MI_OK;
}


/**
 * @brief 用 RC522 计算 CRC16 函数
 * 
 * @param rc522_dev rc522 设备
 * @param pIndata 需要计算的数据
 * @param len 数据长度
 * @param pOutData CRC 计算结果
 * @return 返回执行的结果
 */
static int rc522_calulate_crc(struct rc522_dev_s *rc522_dev, u8 *pIndata, u8 len, u8 *pOutData)
{
	u8 i,n;
	int res = 0;
	rc522_clear_bit_mask(rc522_dev, DivIrqReg, 0x04);
	rc522_write_reg8(rc522_dev, CommandReg, PCD_IDLE);
	rc522_set_bit_mask(rc522_dev, FIFOLevelReg, 0x80);
	for (i=0; i<len; i++)
	{   
		rc522_write_reg8(rc522_dev, FIFODataReg, *(pIndata+i));
	}
	rc522_write_reg8(rc522_dev, CommandReg, PCD_CALCCRC);
	i = 0xFF;
	do 
	{
		res = rc522_read_reg8(rc522_dev, DivIrqReg, &n);
		i--;
	}
	while ((i != 0) && !( n & 0x04));
	res |= rc522_read_reg8(rc522_dev, CRCResultRegL, &pOutData[0]);
	res |= rc522_read_reg8(rc522_dev, CRCResultRegM, &pOutData[1]);
	return res;
}

/**
 * @brief 通过RC522和ISO14443卡通讯
 * 
 * @param rc522_dev rc522 设备
 * @param Command RC522 命令字
 * @param pInData 通过 RC522 发送到卡片的数据
 * @param InLenByte 发送数据的字节长度
 * @param pOutData 接收到的卡片返回数据
 * @param pOutLenBit 返回数据的位长度
 * @return 返回执行的结果
 */
static int rc522_com_card(struct rc522_dev_s *rc522_dev, u8 Command, u8 *pInData, u8 InLenByte, u8 *pOutData, u32 *pOutLenBit)
{
	int status = MI_ERR;
	u8 irqEn   = 0x00;
	u8 waitFor = 0x00;
	u8 lastBits;
	u8 n;
	u32 i;
	switch (Command)
	{
		case PCD_AUTHENT:
			irqEn   = 0x12;
			waitFor = 0x10;
			break;
		case PCD_TRANSCEIVE:
			irqEn   = 0x77;
	    	waitFor = 0x30;
	    	break;
	   	default:
	     	break;
	}

	rc522_write_reg8(rc522_dev, ComIEnReg, irqEn|0x80);
	rc522_clear_bit_mask(rc522_dev, ComIrqReg, 0x80);
	rc522_write_reg8(rc522_dev, CommandReg, PCD_IDLE);
	rc522_set_bit_mask(rc522_dev, FIFOLevelReg, 0x80);
	
	for (i = 0; i < InLenByte; i++)
	{   
		rc522_write_reg8(rc522_dev, FIFODataReg, pInData[i]);
	}
	rc522_write_reg8(rc522_dev, CommandReg, Command);
	
	 
	if (Command == PCD_TRANSCEIVE)
	{    
		rc522_set_bit_mask(rc522_dev, BitFramingReg, 0x80);
	}
	 
	/* 根据时钟频率调整,操作 M1 卡最大等待时间25ms */
	i = 2000;
	do 
	{
		status = rc522_read_reg8(rc522_dev, ComIrqReg, &n);
		i--;
	}
	while ((i != 0) && !(n & 0x01) && !(n & waitFor));
	rc522_clear_bit_mask(rc522_dev, BitFramingReg, 0x80);
	      
	if (i !=0 )
	{    
		status = rc522_read_reg8(rc522_dev, ErrorReg, &n);
	 	if(!(n & 0x1B))
	   	{
	      	status = MI_OK;
	      	if (n & irqEn & 0x01)
	       	{   
				status = MI_NOTAGERR;
			}
	       	if (Command == PCD_TRANSCEIVE)
	      	{
				status = rc522_read_reg8(rc522_dev, FIFOLevelReg, &n);
				status = rc522_read_reg8(rc522_dev, ControlReg, &lastBits);
				lastBits = lastBits & 0x07;
	        	if (lastBits)
	        	{   
					*pOutLenBit = (n-1)*8 + lastBits;   }
	         	else
	         	{   
					*pOutLenBit = n * 8;
				}
	           	if (n == 0)
	          	{   
					n = 1;
				}
	        	if (n > MAXRLEN)
	           	{   
					n = MAXRLEN;
				}
	           	for (i=0; i<n; i++)
	          	{   
					status = rc522_read_reg8(rc522_dev, FIFODataReg, &pOutData[i]);
				}
	      	}
	 	}
	  	else
	   	{   
			status = MI_ERR;
		}
	}

	rc522_set_bit_mask(rc522_dev, ControlReg, 0x80);
	rc522_write_reg8(rc522_dev, CommandReg, PCD_IDLE);
	return status;
}

/**
 * @brief 关闭天线
 * 
 * @param rc522_dev rc522 设备
 * @return 返回执行的结果
 */
static int rc522_antenna_off(struct rc522_dev_s *rc522_dev)
{
    return rc522_clear_bit_mask(rc522_dev, TxControlReg, 0x03);
}

/**
 * @brief 开启天线
 * 
 * @param rc522_dev rc522 设备, 每次启动或关闭天险发射之间应至少有1ms的间隔
 * @return 返回执行的结果
 */
static int rc522_antenna_on(struct rc522_dev_s *rc522_dev)
{
    u8 tmp = 0x0;
    tmp = rc522_read_reg8(rc522_dev, TxControlReg, &tmp);
    if (!(tmp & 0x03))
    {
        return rc522_set_bit_mask(rc522_dev, TxControlReg, 0x03);
    }
	return MI_OK;
}

/**
 * @brief 设置 RC522 的工作方式
 * 
 * @param rc522_dev rc522 设备
 * @param type 工作模式,新增模式时,建议通过枚举
 * @return 返回执行的结果
 */
static int rc522_config_iso_type(struct rc522_dev_s *rc522_dev, u8 type)
{
	int res = MI_OK;

	switch (type)
	{
		case 1:
			res = rc522_clear_bit_mask(rc522_dev, Status2Reg, 0x08);
			res |= rc522_write_reg8(rc522_dev, ModeReg,0x3D);
			res |= rc522_write_reg8(rc522_dev, TxSelReg,0x10);
			res |= rc522_write_reg8(rc522_dev, RxSelReg,0x86);
			res |= rc522_write_reg8(rc522_dev, RFCfgReg,0x7F);
			res |= rc522_write_reg8(rc522_dev, TReloadRegL,30);
			res |= rc522_write_reg8(rc522_dev, TReloadRegH,0);
			res |= rc522_write_reg8(rc522_dev, TModeReg,0x8D);
			res |= rc522_write_reg8(rc522_dev, TPrescalerReg,0x3E);
			msleep (1);
			res |= rc522_antenna_on(rc522_dev);
			break;
		
		default:
			res = MI_NOTAGERR;
			break;
	}
   
	return res;
}

/**
 * @brief 复位RC522
 * 
 * @param rc522_dev rc522 设备
 * @return 返回执行的结果
 */
static int rc522_reset_dev(struct rc522_dev_s *rc522_dev)
{
	int ret = MI_OK;
	/* RC522 启动并复位 */
	ret = rc522_write_reg8(rc522_dev, CommandReg, PCD_RESETPHASE);
	ret |= rc522_write_reg8(rc522_dev, ModeReg, 0x3D);
	ret |= rc522_write_reg8(rc522_dev, TReloadRegL, 30);
	ret |= rc522_write_reg8(rc522_dev, TReloadRegH, 0);
	ret |= rc522_write_reg8(rc522_dev, TModeReg, 0x8D);
	ret |= rc522_write_reg8(rc522_dev, TPrescalerReg, 0x3E);
	ret |= rc522_write_reg8(rc522_dev, TxAutoReg, 0x40);
	return MI_OK;
}

/**
 * @brief RC522 寻卡
 * 
 * @param rc522_dev rc522 设备
 * @param req_code 寻卡方式, 
 * 				0x52 = 寻感应区内所有符合14443A标准的卡
 * 				0x26 = 寻未进入休眠状态的卡
 * @param pTagType 卡片类型代码
 * 				0x4400 = Mifare_UltraLight
 * 				0x0400 = Mifare_One(S50)
 * 				0x0200 = Mifare_One(S70)
 * 				0x0800 = Mifare_Pro(X)
 * 				0x4403 = Mifare_DESFire
 * @return 返回执行的结果
 */
static int rc522_request_card(struct rc522_dev_s *rc522_dev, u8 req_code, u8 *pTagType)
{
	int status;  
	unsigned int  unLen;
	unsigned char ucComMF522Buf[MAXRLEN];

	rc522_clear_bit_mask(rc522_dev, Status2Reg, 0x08);
	rc522_write_reg8(rc522_dev, BitFramingReg, 0x07);
	rc522_set_bit_mask(rc522_dev, TxControlReg, 0x03);
 
	ucComMF522Buf[0] = req_code;

	status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, &unLen);
	if ((status == MI_OK) && (unLen == 0x10))
	{    
   		*pTagType     = ucComMF522Buf[0];
   		*(pTagType+1) = ucComMF522Buf[1];
   	}
   	else
   	{
		status = MI_ERR;
	}

	return status;
}

/**
 * @brief RC522 防冲撞
 * 
 * @param rc522_dev rc522 设备
 * @param pSnr 卡片序列号,4字节
 * @return 返回执行的结果
 */
static int rcc_anticoll_card(struct rc522_dev_s *rc522_dev, u8 *pSnr)
{
	int status;
	unsigned char i, snr_check=0;
	unsigned int  unLen;
	unsigned char ucComMF522Buf[MAXRLEN]; 

	rc522_clear_bit_mask(rc522_dev, Status2Reg, 0x08);
	rc522_write_reg8(rc522_dev, BitFramingReg, 0x00);
	rc522_clear_bit_mask(rc522_dev, CollReg, 0x80);

	ucComMF522Buf[0] = PICC_ANTICOLL1;
	ucComMF522Buf[1] = 0x20;

	status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, &unLen);
	if (status == MI_OK)
	{
		for (i=0; i<4; i++)
		{   
			*(pSnr+i)  = ucComMF522Buf[i];
			snr_check ^= ucComMF522Buf[i];
		}
		if (snr_check != ucComMF522Buf[i])
		{   status = MI_ERR;    }
	}

	rc522_set_bit_mask(rc522_dev, CollReg, 0x80);
	return status;
}

/**
 * @brief RC522 选定卡片
 * 
 * @param rc522_dev rc522 设备
 * @param pSnr 卡片序列号,4字节
 * @return 返回执行的结果
 */
static int rc522_select_card(struct rc522_dev_s *rc522_dev, u8 *pSnr)
{
    char status;
    unsigned char i;
    unsigned int  unLen;
    unsigned char ucComMF522Buf[MAXRLEN]; 
    
    ucComMF522Buf[0] = PICC_ANTICOLL1;
    ucComMF522Buf[1] = 0x70;
    ucComMF522Buf[6] = 0;
    for (i=0; i<4; i++)
    {
    	ucComMF522Buf[i+2] = *(pSnr+i);
    	ucComMF522Buf[6]  ^= *(pSnr+i);
    }
    rc522_calulate_crc(rc522_dev, ucComMF522Buf, 7, &ucComMF522Buf[7]);
  
    rc522_clear_bit_mask(rc522_dev, Status2Reg, 0x08);

    status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, &unLen);
    if ((status == MI_OK) && (unLen == 0x18))
    {   
		status = MI_OK;
	}
    else
    {   
		status = MI_ERR;
	}
    return status;
}
     
/**
 * @brief RC522 验证卡片密码
 * 
 * @param rc522_dev rc522 设备
 * @param auth_mode 密码验证模式,0x60 = 验证A密钥,0x61 = 验证B密钥
 * @param addr 块地址
 * @param pKey 密码
 * @param pSnr 卡片序列号,4字节
 * @return 返回执行的结果
 */
static int rc522_auth_state(struct rc522_dev_s *rc522_dev, u8 auth_mode, u8 addr, u8 *pKey, u8 *pSnr)
{
    int status;
    unsigned int  unLen;
    unsigned char i,ucComMF522Buf[MAXRLEN]; 
	u8 temp;

    ucComMF522Buf[0] = auth_mode;
    ucComMF522Buf[1] = addr;
    for (i=0; i<6; i++)
    {    
		ucComMF522Buf[i+2] = *(pKey+i);
	}
    for (i=0; i<6; i++)
    {    
		ucComMF522Buf[i+8] = *(pSnr+i);
	}
    
    status = rc522_com_card(rc522_dev, PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, &unLen);
	rc522_read_reg8(rc522_dev, Status2Reg, &temp);
    if ((status != MI_OK) || (!(temp & 0x08)))
    {   
		status = MI_ERR;
	}
    
    return status;
}

/**
 * @brief 读取 RC522 卡的一块数据
 * 
 * @param rc522_dev rc522 设备
 * @param addr 块地址
 * @param pData 读出的数据,16字节
 * @return 返回执行的结果
 */
static int rc522_read_card(struct rc522_dev_s *rc522_dev, u8 addr, u8 *pData)
{
	char status;
	unsigned int  unLen;
	unsigned char i, ucComMF522Buf[MAXRLEN]; 

	ucComMF522Buf[0] = PICC_READ;
	ucComMF522Buf[1] = addr;
	rc522_calulate_crc(rc522_dev, ucComMF522Buf, 2 , &ucComMF522Buf[2]);

	status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
	if ((status == MI_OK) && (unLen == 0x90))
	{
		for (i=0; i<16; i++)
		{    
			*(pData+i) = ucComMF522Buf[i];
		}
	}
	else
	{   
		status = MI_ERR;
	}
	return status;
}

/**
 * @brief 写入 RC522 卡的一块数据
 * 
 * @param rc522_dev rc522 设备
 * @param addr 块地址
 * @param pData 读出的数据,16字节
 * @return 返回执行的结果
 */
static int rc522_write_card(struct rc522_dev_s *rc522_dev, u8 addr, u8 *pData)
{
    char status;
    unsigned int unLen;
    unsigned char i, ucComMF522Buf[MAXRLEN];
    ucComMF522Buf[0] = PICC_WRITE;
    ucComMF522Buf[1] = addr;

    rc522_calulate_crc(rc522_dev, ucComMF522Buf, 2, &ucComMF522Buf[2]);
    status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
    if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
    {
        status = MI_ERR;
    }
    if (status == MI_OK)
    {
        for (i = 0; i < 16; i++)
        {
            ucComMF522Buf[i] = *(pData + i);
        }
        rc522_calulate_crc(rc522_dev, ucComMF522Buf, 16, &ucComMF522Buf[16]);

        status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, &unLen);
        if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
        {
            status = MI_ERR;
        }
    }
    return status;
}


/**
 * @brief RC522 命令卡片进入休眠状态
 * 
 * @param rc522_dev rc522 设备
 * @return 返回执行的结果
 */
static int rc522_halt_card(struct rc522_dev_s *rc522_dev)
{
    char status;
    unsigned int  unLen;
    unsigned char ucComMF522Buf[MAXRLEN]; 

    ucComMF522Buf[0] = PICC_HALT;
    ucComMF522Buf[1] = 0;
    rc522_calulate_crc(rc522_dev, ucComMF522Buf, 2, &ucComMF522Buf[2]);
 
    status = rc522_com_card(rc522_dev, PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
	if ((status != MI_OK))
	{
		return MI_ERR;
	}
    return MI_OK;
}


/**
 * @brief 向 spi 设备中写入多个寄存器数据
 * 
 * @param spi spi 设备
 * @param reg 要写入的寄存器首地址
 * @param buf 要写入的数据缓冲区
 * @param len 要写入的数据长度
 * @return 返回执行结果
 */
static s32 spi_write_regs(struct spi_device *spi, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message msg;
	struct spi_transfer *trf;

	/* 申请内存*/
	trf = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	if(!trf) {
		return -ENOMEM;
	}

	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}

	/* 一共发送 len+1 个字节的数据,第一个字节为寄存器首地址,len 为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址 bit8 要清零 */
	memcpy(txdata+1, buf, len); /* 把 len 个数据拷贝到 txdata 里 */
	trf->tx_buf = txdata; /* 要发送的数据 */
	trf->len = len+1; /* trf->len = 发送的长度+读取的长度 */
	spi_message_init(&msg); /* 初始化 spi_message */
	spi_message_add_tail(trf, &msg);/*添加到 spi_message 队列 */
	ret = spi_sync(spi, &msg); /* 同步发送 */
	if(ret) {
		goto out2;
	}

out2:
	kfree(txdata); /* 释放内存 */
out1:
	kfree(trf); /* 释放内存 */
	return ret;

}

/**
 * @brief 读取 spi 的多个寄存器数据
 * 
 * @param spi spi 设备
 * @param reg 要读取的寄存器首地址
 * @param buf 要读取的数据缓冲区
 * @param len 要读取的数据长度
 * @return 返回执行结果
 */
static int spi_read_regs(struct spi_device *spi, u8 reg, void *buf, int len)
{

	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	struct spi_message msg;
	struct spi_transfer *trf;

	/* 申请内存*/
	trf = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	if(!trf) {
		return -ENOMEM;
	}

	/* 申请内存 */
	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送 len+1 个字节的数据,第一个字节为寄存器首地址,一共要读取 len 个字节长度的数据,*/
	txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit8 要置 1 */ 
	trf->tx_buf = txdata; /* 要发送的数据 */

	trf->rx_buf = rxdata; /* 要读取的数据 */
	trf->len = len+1; /* trf->len = 发送的长度+读取的长度 */
	spi_message_init(&msg); /* 初始化 spi_message */
	spi_message_add_tail(trf, &msg);/* 将 spi_transfer 添加到 spi_message*/
	ret = spi_sync(spi, &msg); /* 同步发送 */
	if(ret) {
		goto out2;
	}

	memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */

out2:
	kfree(rxdata); /* 释放内存 */
out1: 
	kfree(trf); /* 释放内存 */

	return ret;
}

/**
 * @brief 向 spi 设备中同时读写多个寄存器数据
 * 
 * @param spi spi 设备
 * @param reg 要写入的寄存器首地址
 * @param write_buf 要写入的数据缓冲区
 * @param read_buf 要读取的数据缓冲区
 * @param len 要写入的数据长度
 * @return 返回执行结果
 */
static s32 spi_read_write_regs(struct spi_device *spi, u8 reg, u8 *write_buf, u8 *read_buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message msg;
	struct spi_transfer *trf;
	unsigned char * rxdata;

	/* 申请内存*/
	trf = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	if(!trf) {
		return -ENOMEM;
	}

	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}

	/* 申请内存 */
	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送 len+1 个字节的数据,第一个字节为寄存器首地址,len 为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;
	memcpy(txdata+1, write_buf, len); /* 把 len 个数据拷贝到 txdata 里 */
	trf->tx_buf = txdata; /* 要发送的数据 */
	trf->rx_buf = rxdata; /* 要读取的数据 */

	trf->len = len+1; /* trf->len = 发送的长度+读取的长度 */
	spi_message_init(&msg); /* 初始化 spi_message */
	spi_message_add_tail(trf, &msg);/*添加到 spi_message 队列 */
	ret = spi_sync(spi, &msg); /* 同步发送 */
	if(ret) {
		goto out2;
	}

	memcpy(read_buf , rxdata+1, len); /* 只需要读取的数据 */

out2:
	kfree(txdata); /* 释放内存 */
	kfree(rxdata); /* 释放内存 */
out1:
	kfree(trf); /* 释放内存 */
	return ret;
}

/**
 * @brief 打开设备
 * 
 * @param inode 传递给驱动的 inode
 * @param filp 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return 0 成功;其他 失败
 */
static int rc522_open(struct inode *inode, struct file *filp)
{
	u8 value[5];
	u8 buf[5] = {0x11, 0x22, 0x33, 0x44, 0x55};
	int res = -1;

	struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
	struct rc522_dev_s *rc522_dev = container_of(cdev, struct rc522_dev_s, cdev);
	filp->private_data = rc522_dev;

	// pr_info("rc522_open\n");
	/* 复位 RC522 */
	res = rc522_reset_dev(rc522_dev);
	/* 关闭天线 */
	res |= rc522_antenna_off(rc522_dev);
	msleep (1);
	/* 打开天线,天线操作之间需要间隔 1ms */
	res |= rc522_antenna_on(rc522_dev);
	/* 设置 RC522 的工作模式*/
	res |= rc522_config_iso_type(rc522_dev, 1);

	if (MI_OK != res)
	{
		return MI_NOTAGERR;
	}

    return MI_OK;

	rc522_write_reg8(rc522_dev, 0x05, 0xFF);

	/* 验证 spi 是否正常工作 */
	mutex_lock(&rc522_dev->spi->dev.mutex);
	spi_read_write_regs(rc522_dev->spi, 0x01, buf, value, 5);
	mutex_unlock(&rc522_dev->spi->dev.mutex);
	pr_info("spi read value is: %x	%x	%x	%x	%x\n", value[0], value[1], value[2], value[3], value[4]);
}

/**
 * @brief 从设备读取数据
 * 
 * @param filp 要打开的设备文件(文件描述符)
 * @param buf 返回给用户空间的数据缓冲区
 * @param cnt 要读取的数据长度
 * @param offt 相对于文件首地址的偏移
 * @return 0 成功;其他 失败
 */
static ssize_t rc522_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int res = 0;
	unsigned char card_data[16];
	int read_position = *offt/16;			// 用户空间读取的位置
	struct rc522_dev_s *rc522_dev = filp->private_data;

	/* RC522 只有16个扇区,每个扇区4个块,总共64块 */
	if (read_position > 66)
	{
		return MI_NOTAGERR;
	}

	/* 寻卡 */
	if (64 == read_position)
	{
		res = rc522_request_card(rc522_dev, 0x26, card_type);
		if (MI_OK != res)
		{
			return MI_NOTAGERR;
		}

		/* 将卡片 id 拷贝到用户空间中 */
		return copy_to_user(buf, &card_type, cnt);
	}
	
	/* 防冲撞,读取卡的ID */
	if (65 == read_position)
	{
		res = rcc_anticoll_card(rc522_dev, card_id);
		if (MI_OK != res)
		{
			return MI_NOTAGERR;
		}

		return copy_to_user(buf, card_id, cnt);
	}

	/* 读取卡片密码 */
	if (66 == read_position)
	{
		return copy_to_user(buf, card_cipher, cnt);
	}

	/* 验证卡片密码 */
	res = rc522_auth_state(rc522_dev, card_auth_mode, read_position, card_cipher, card_id);
	if (MI_OK != res)
	{
		// pr_info("Verification card password setting error when reading\n");
		return MI_NOTAGERR;
	}

	/* 读取指定块中的数据 */
	memset(card_data, 0, sizeof(card_data));
	res = rc522_read_card(rc522_dev, read_position, card_data);
	if (MI_OK != res)
	{
		// pr_info("Failed to read card\n");
		return MI_NOTAGERR;
	}

	return copy_to_user(buf, card_data, cnt);
}

/**
 * @brief 向设备写数据
 * 
 * @param filp 设备文件,表示打开的文件描述符
 * @param buf 要写给设备写入的数据
 * @param cnt 要写入的数据长度
 * @param offt 相对于文件首地址的偏移
 * @return 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t rc522_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int res = 0;
	unsigned char temp = 0;
	unsigned char card_data[16] = {0};
	struct rc522_dev_s *rc522_dev = filp->private_data;
	int write_position = *offt/16;			// 用户空间读取的位置

	/* RC522 只有16个扇区,每个扇区4个块,总共64块 */
	if (write_position > 66)
	{
		return MI_NOTAGERR;
	}

	/* 设置密码验证方式 */
	if (64 == write_position)
	{
		res = copy_from_user(&temp, buf, 1);
		if (MI_OK != res)
		{
			return MI_NOTAGERR;
		}
		
		if (temp)
		{
			/* 验证 B 密钥 */
			card_auth_mode = 0x61;
		}
		else
		{
			/* 验证 A 密钥 */
			card_auth_mode = 0x60;
		}
		return MI_OK;
	}

	/* 选择卡片 */
	if (65 == write_position)
	{
		if (cnt > sizeof(card_id))
		{
			res = copy_from_user(card_id, buf, sizeof(card_id));
		}
		else
		{
			res = copy_from_user(card_id, buf, cnt);
		}

		if (MI_OK != res)
		{
			return MI_NOTAGERR;
		}
		
		/* 选择卡片 */
		res = rc522_select_card(rc522_dev, card_id);
		if (MI_OK != res)
		{
			// pr_info("Failed to select card when reading\n");
			return MI_NOTAGERR;
		}
		return MI_OK;
	}

	/* 设置卡片密码 */
	if (66 == write_position)
	{
		if (cnt > sizeof(card_cipher))
		{
			return copy_from_user(card_cipher, buf, sizeof(card_cipher));
		}
		return copy_from_user(card_cipher, buf, cnt);
	}

	/* 验证卡片密码 */
	res = rc522_auth_state(rc522_dev, card_auth_mode, write_position, card_cipher, card_id);
	if (MI_OK != res)
	{
		pr_info("Verification card password setting error when writing\n");
		return MI_NOTAGERR;
	}

	/* 向指定块中写数据 */
	memset(card_data, write_position, sizeof(card_data));
	if (cnt > sizeof(card_data))
	{
		res = copy_from_user(card_data, buf, sizeof(card_data));
	}
	else
	{
		res = copy_from_user(card_data, buf, cnt);
	}
	if (MI_OK != res)
	{
		return MI_NOTAGERR;
	}
	
    return rc522_write_card(rc522_dev, 6, card_data);
}

/**
 * @brief 关闭/释放设备
 * 
 * @param filp 要关闭的设备文件(文件描述符)
 * @return 0 成功;其他 失败
*/
static int rc522_release(struct inode *inode, struct file *filp)
{
	int res = MI_OK;
	struct rc522_dev_s *rc522_dev = filp->private_data;
	// pr_info("rc522_release\n");

	/* 复位 RC522 */
	res = rc522_reset_dev(rc522_dev);
	if (MI_OK != res)
	{
		return MI_NOTAGERR;
	}

	/* 卡片进入休眠 */
    return rc522_halt_card(rc522_dev);
}

/**
 * @brief 修改文件读写的偏移位置
 * 
 * @param filp 要关闭的设备文件(文件描述符)
 * @param loff_t 偏移位置
 * @param whence 文件位置
 * @return 0 成功;其他 失败
*/
loff_t file_llseek (struct file *filp, loff_t offset, int whence)
{
	loff_t new_pos; 				//新偏移量
	loff_t old_pos = filp->f_pos; 	//旧偏移量

	// pr_info("file llseek !\n");
	switch(whence){
	case SEEK_SET:
		new_pos = offset;
		break;
	case SEEK_CUR:
		new_pos = old_pos + offset;
		break;
	case SEEK_END:
		new_pos = RC522_MAX_OFFSET + offset;
		break;
	default:
		printk("error: Unknow whence !\n");
		return - EINVAL;
	}
	
	/* 偏移量的合法检查 */
	if(new_pos < 0 || new_pos > RC522_MAX_OFFSET){ 	
		printk("error: Set offset error !\n");
		return - EINVAL;
	}

	filp->f_pos = new_pos;
	// printk("The new pos = %lld and offset = %lld!\n", new_pos, offset);
	
	return new_pos; 					//正确返回新的偏移量	
 }

/* 设备操作函数结构体 */
static struct file_operations rc522_ops = {
    .owner = THIS_MODULE, 
    .open = rc522_open,
    .read = rc522_read,
    .write = rc522_write,
    .release = rc522_release,
	.llseek = file_llseek,
};

/**
 * @brief spi 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
 * @param client spi 设备
 * @param id spi 设备 ID
 * @return 0,成功;其他负值,失败
*/
static int rc522_probe(struct spi_device *spi)
{
    int ret = -1; 							// 保存错误状态码
	struct rc522_dev_s *rc522_dev;			// 设备数据结构体

	/*---------------------注册字符设备驱动-----------------*/ 
	
	/* 驱动与总线设备匹配成功 */
	pr_info("\t  %s match successed  \r\n", spi->modalias);
	// dev_info(&spi->dev, "match successed\n");

	/* 申请内存并与 client->dev 进行绑定。*/
	/* 在 probe 函数中使用时,当设备驱动被卸载,该内存被自动释放,也可使用 devm_kfree() 函数直接释放 */
	rc522_dev = devm_kzalloc(&spi->dev, sizeof(*rc522_dev), GFP_KERNEL);
	if(!rc522_dev)
	{
		pr_err("Failed to request memory \r\n");
		return -ENOMEM;
	}
	/* 1、创建设备号 */
	/* 采用动态分配的方式,获取设备编号,次设备号为0 */
	/* 设备名称为 SPI_NAME,可通过命令 cat /proc/devices 查看 */
	/* RC522_CNT 为1,只申请一个设备编号 */
	ret = alloc_chrdev_region(&rc522_dev->devid, 0, RC522_CNT, RC522_NAME);
	if (ret < 0)
	{
		pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", RC522_NAME, ret);
		return -ENOMEM;
	}

	/* 2、初始化 cdev */
	/* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
	rc522_dev->cdev.owner = THIS_MODULE;
	cdev_init(&rc522_dev->cdev, &rc522_ops);

	/* 3、添加一个 cdev */
	/* 添加设备至cdev_map散列表中 */
	ret = cdev_add(&rc522_dev->cdev, rc522_dev->devid, RC522_CNT);
	if (ret < 0)
	{
		pr_err("fail to add cdev \r\n");
		goto del_unregister;
	}

	/* 4、创建类 */
	rc522_dev->class = class_create(THIS_MODULE, RC522_NAME);
	if (IS_ERR(rc522_dev->class)) 
	{
		pr_err("Failed to create device class \r\n");
		goto del_cdev;
	}

	/* 5、创建设备,设备名是 RC522_NAME */
	/*创建设备 RC522_NAME 指定设备名,*/
	rc522_dev->device = device_create(rc522_dev->class, NULL, rc522_dev->devid, NULL, RC522_NAME);
	if (IS_ERR(rc522_dev->device)) {
		goto destroy_class;
	}
	rc522_dev->spi = spi;

	/*初始化 rc522_device */
	spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);

	/* 保存 rc522_dev 结构体 */
	spi_set_drvdata(spi, rc522_dev);

	return 0;

destroy_class:
	device_destroy(rc522_dev->class, rc522_dev->devid);
del_cdev:
	cdev_del(&rc522_dev->cdev);
del_unregister:
	unregister_chrdev_region(rc522_dev->devid, RC522_CNT);
	return -EIO;
}

/**
 * @brief spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
 * @param client spi 设备
 * @return 0,成功;其他负值,失败
*/
static int rc522_remove(struct spi_device *spi)
{
	struct rc522_dev_s *rc522_dev = spi_get_drvdata(spi);

	/*---------------------注销字符设备驱动-----------------*/

	/* 1、删除 cdev */
	cdev_del(&rc522_dev->cdev);
	/* 2、注销设备号 */
	unregister_chrdev_region(rc522_dev->devid, RC522_CNT);
	/* 3、注销设备 */
	device_destroy(rc522_dev->class, rc522_dev->devid);
	/* 4、注销类 */
	class_destroy(rc522_dev->class);
	return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct spi_device_id gtp_device_id[] = {
	{"rfid,rfid_rc522", 0},
	{}
};

/* 设备树匹配表 */
static const struct of_device_id rc522_of_match_table[] = {
	{.compatible = "rfid,rfid_rc522"},
	{/* sentinel */}
};

/* SPI 驱动结构体 */
static struct spi_driver rc522_driver = {
	.probe = rc522_probe,
	.remove = rc522_remove,
	.id_table = gtp_device_id,
	.driver = {
		.name = "rfid,rfid_rc522",
		.owner = THIS_MODULE,
		.of_match_table = rc522_of_match_table,
	},
};

/**
 * @brief 驱动入口函数
 * @return 0,成功;其他负值,失败
*/
static int __init rc522_driver_init(void)
{
	int ret;
	pr_info("spi_driver_init\n");
	ret = spi_register_driver(&rc522_driver);
	return ret;
}

/**
 * @brief 驱动出口函数
 * @return 0,成功;其他负值,失败
*/
static void __exit rc522_driver_exit(void)
{
	pr_info("spi_driver_exit\n");
	spi_unregister_driver(&rc522_driver);
}


/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(rc522_driver_init);
module_exit(rc522_driver_exit);

/* LICENSE 和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIAOZHU");
MODULE_INFO(intree, "Y");

六、测试程序

#include "sys/stat.h"
#include <stdio.h> 
#include <linux/types.h> 
#include <stdlib.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/ioctl.h> 
#include <errno.h> 
#include <assert.h> 
#include <string.h> 
#include <linux/i2c.h> 
#include <linux/i2c-dev.h>

#include "rc522_device.h"
/***************************************************************
文件名 : drive_read_app.c
作者 : jiaozhu
版本 : V1.0
描述 : 驱动读取测试
其他 : 使用方法:./drive_read_app [/dev/xxx]
argv[1] 需要读取的驱动
日志 : 初版 V1.0 2023/1/4
***************************************************************/

/**
* @brief main 主程序
* @param argc argv 数组元素个数
* @param argv 具体参数
* @return 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    unsigned char card_buf[16];
    unsigned char card_id[16];
    unsigned char write_buf[2];
    int value[18];
    int cmd;
    int ret = 0;

    if(argc != 2){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if(!fd){
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    

    /* 设置卡片密码 */
    lseek(fd, 66*16, SEEK_SET);
    memset(card_buf, 0xFF, sizeof(card_buf));
    ret = write(fd, card_buf, sizeof(card_buf));
    if(ret < 0){
        printf("Failed to set integration time...........!\r\n");
    }

    /* 获取卡片类型 */
    //sleep(1);
    lseek(fd, 64*16, SEEK_SET);
    ret = read(fd, card_buf, sizeof(card_buf));
    if (ret == 0)       
    {
        printf("Card type is : (0x%4x)\n", (card_buf[0] << 8) | card_buf[1]);
    }
    else
    {
        printf("read file %s failed!\r\n", filename);
    }


    /* 获取卡片id */
    //sleep(1);
    lseek(fd, 65*16, SEEK_SET);
    ret = read(fd, card_id, sizeof(card_id));
    if (ret == 0)       
    {
        printf("card id is : %02x%02x%02x%02x\n", card_id[0], card_id[1], card_id[2], card_id[3]);
    }
    else
    {
        printf("read file %s failed!\r\n", filename);
    }

    /* 选择卡片 */
    //sleep(1);
    lseek(fd, 65*16, SEEK_SET);
    ret = write(fd, card_id, sizeof(card_id));
    if(ret < 0){
        printf("Failed to select card!\r\n");
    }

    /* 写数据 */
    //sleep(1);
    lseek(fd, 4*16, SEEK_SET);
    memset(card_buf, 0x58, sizeof(card_buf));
    ret = write(fd, card_buf, sizeof(card_buf));
    if(ret < 0){
    	printf("Failed to write card block information\r\n");
    }


    /* 获取卡片块数据 */
    sleep(1);
    lseek(fd, 0*16, SEEK_SET);
    ret = read(fd, card_buf, sizeof(card_buf));
    if (ret == 0)       
    {
        for (int i = 0; i < 16; i++)
        {
            printf("%02x ", card_buf[i]);
        }
        printf("\r\n ");
    }
    else
    {
        printf("Failed to read card block information");
    }

    //sleep(1);
    close(fd);

    return 0;
}


注意:以上程序只供学习使用,还有许多需要完善的地方,这里我就不继续优化了,有需要的小伙伴可以根据自己的需求进行完善即可。

参考链接

MFRC522中文手册:https://www.doc88.com/p-4042994624020.html?r=1
rfid-rc522模块中文资料_驱动模块:https://cloud.tencent.com/developer/article/2152140
RC522(RFID)读写驱动:https://blog.csdn.net/zhiyuan2021/article/details/128922757