一、概述

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

二、RC522 介绍

  1. 产品外观

    现在的生活中 IC 卡的生活场景大家都不陌生了,外观如下图所示,其中 PCB 部分是主机,白色和绿色的是 IC 卡

  2. 产品介绍

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

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

  3. 卡片的内部储存信息

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

    每个区的块3作为控制块,块0、1、2作为数据块,其中数据块用作一般的数据存储时,可以对其中的数据进行读写操作;用作数据值,可以进行初始化值、加值、减值、读值操作,我在网上找了一张图片,如下图所示:



    注意:我没见过其他的卡片,是否存在我就不知道了,我手里有一张卡片容量为8K位EEPROM,分为16个扇区,每个扇区为4块,每块16个字节,总共有64块,之前我就错误的以为只有一个卡片容量。

  4. 存取控制

    每个扇区的密码和存取控制都是独立的,存取控制是4个字节,即32位(在块3中)。

    每个块都有存取条件,存取条件是由密码和存取控制共同决定的。

    每个块都有相应的三个控制位,这三个控制位存在于存取控制字节中,相应的控制位决定了该块的访问权限,控制位如图:

    注意: 每个扇区的所有块的存取条件控制位,都放在了该扇区的块3中,如图:

  5. 数据块的存取控制

    对数据块,与就是块0、1、2的存取控制是由对应块的控制位来决定的:



    注意: 要想对数据块进行操作,首先要看该数据块的控制位是否允许对数据块的操作,如果允许操作,再看需要验证什么密码,只有验证密码正确后才可以对该数据块执行相应操作。 一般密码A的初始值都是 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF

  6. 控制块的存取控

    块3(控制块)的存取操作与数据块不同,如图:

  7. 通信流程

注意:具体说明参考 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

【Linux SPI】RFID RC522 设备驱动的更多相关文章

  1. Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化

    我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...

  2. Linux SPI总线和设备驱动架构之三:SPI控制器驱动

    通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:1. ...

  3. Linux SPI总线和设备驱动架构之二:SPI通用接口层

    通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...

  4. Linux SPI总线和设备驱动架构之一:系统概述

    SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,用 ...

  5. Linux SPI总线和设备驱动架构之一:系统概述【转】

    转自:http://blog.csdn.net/droidphone/article/details/23367051/ 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 硬 ...

  6. Linux学习 : 总线-设备-驱动模型

    platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...

  7. Linux与Windows的设备驱动模型对比

    Linux与Windows的设备驱动模型对比 名词缩写: API 应用程序接口(Application Program Interface ) ABI 应用系统二进制接口(Application Bi ...

  8. 【Linux操作系统分析】设备驱动处理流程

    1 驱动程序,操作系统,文件系统和应用程序之间的关系 字符设备和块设备映射到操作系统中的文件系统,由文件系统向上提供给应用程序统一的接口用以访问设备. Linux把设备视为文件,称为设备文件,通过对设 ...

  9. linux PMBus总线及设备驱动分析

    PMBus协议规范介绍 PMBus是一套对电源进行配置.控制和监控的通讯协议标准.其最新版本为1.3,该规范还在不断演进中,比如新标准中新增的zone PMBus.AVSBus等特性.在其官网上有详细 ...

  10. Linux Platform devices 平台设备驱动

    设备总线驱动模型:http://blog.csdn.net/lizuobin2/article/details/51570196 本文主要参考:http://www.wowotech.net/devi ...

随机推荐

  1. ChatGPT 可以联网了!浏览器插件下载

    Twitter 用户 An Qu 开发了一款新的 Chrome 插件帮助 ChatGPT 上网,安装插件以后 ChatGPT 就可以联!网!了! 简单来说开启插件后,他可以从网上搜索信息,并且根据用户 ...

  2. python各种小知识

    一.三元表达式 1. 简化步骤1:代码简单且只有一行,可以直接在冒号后面编写 三元表达式: 数据值1+ if 条件+else 数据值2条件成立则使用数据值1,条件不成立则使用数据值2: 当结果是二选一 ...

  3. AcWing340通信道路/ USACO2008 Telephone Line S

    AcWing题目 洛谷题目 解题思路 首先可以得到一个很容易得到的贪心策略,将一条路径上最贵的(边权最大)的\(K\)条边删去,那么我们剩下的路径中最贵(边权最大)的路就是原本这条路径上帝\(K + ...

  4. 【机器学习】李宏毅——Domain Adaptation(领域自适应)

    在前面介绍的模型中,一般我们都会假设训练资料和测试资料符合相同的分布,这样模型才能够有较好的效果.而如果训练资料和测试资料是来自于不同的分布,这样就会让模型在测试集上的效果很差,这种问题称为Domai ...

  5. 6、SQL模糊查询LIKE concat用法

    concat用来拼接查询的字符串: SELECT * FROM `page_demo` WHERE name LIKE concat('%',#{name},'%') 模糊查询: 1.%代表零个或多个 ...

  6. CH392/CH395常见问题解决方法指南

    CH395 问题 1: CH395 初始化失败.解答: 1.首先检查"check_exist"命令,正常情况下 CH395 会将该命令的输入值按位取反后输出,若该命令不正常,则说明 ...

  7. python进阶之路7 数据类型的内置方法

    内容回顾 while 循环补充说明 1.死循环 2.while循环嵌套和全局标志位 for循环 1.for 变量名 in 待遍历数据 for循环体代码 2.for 也可以与break continue ...

  8. elasticsearch之search template

    一.search template简介 elasticsearch提供了search template功能,其会在实际执行查询之前,对search template进行预处理并将参数填充到templa ...

  9. Grafana 系列文章(四):Grafana Explore

    ️URL: https://grafana.com/docs/grafana/latest/explore/ Description: Explore Grafana 的仪表盘 UI 是关于构建可视化 ...

  10. MySQL 如何实现数据更新

    一般在更新时会遇到以下场景:1.所有字段全部更新:2.根据条件更新字段中的某部分内容:3.根据不同的条件更新不同的值,以下是几种场景中常用的update方法. 一.方法分类 二.具体用法 (1)根据条 ...