【Linux SPI】RFID RC522 设备驱动
一、概述
MFRC522 支持 SPI、I2C、UART 接口,我在某宝上购买了一个 SPI 接口的 RC522 模块。此笔记主要要是通过 RC522 模块学习 linux 中的 SPI 驱动,方便今后写其他 SPI 驱动时做参考。有需要的小伙伴可以收藏一下。
二、RC522 介绍
产品外观
现在的生活中 IC 卡的生活场景大家都不陌生了,外观如下图所示,其中 PCB 部分是主机,白色和绿色的是 IC 卡
产品介绍
MFRC522 是应用于 13.56MHz 非接触式通信中高集成度读写卡系列芯片中的一员。是NXP 公司针对“三表”应用推出的一款低 电压、低成本、体积小的非接触式读写卡芯片,是智能仪表和便携式手持设备研发的较好选择【百度百科】。更多信息可以参考芯片手册,对于英文不好的小伙伴,可以参考MFRC522中文手册 https://www.doc88.com/p-4042994624020.html?r=1,MFRC522 的引脚如下图所示:
卡片的内部储存信息
一张卡片分成若干个扇区,一个扇区有四个块,每一块存储16字节的信息,以块为存取单位。第0扇区的第0块存储卡片的UID和厂商信息,每个扇区的第3块存储该扇区的密钥和控制字信息(这里的第三块是指 block * 4 + 3),其余均可以用来存储数据。每个区的块3作为控制块,块0、1、2作为数据块,其中数据块用作一般的数据存储时,可以对其中的数据进行读写操作;用作数据值,可以进行初始化值、加值、减值、读值操作,我在网上找了一张图片,如下图所示:
注意:我没见过其他的卡片,是否存在我就不知道了,我手里有一张卡片容量为8K位EEPROM,分为16个扇区,每个扇区为4块,每块16个字节,总共有64块,之前我就错误的以为只有一个卡片容量。存取控制
每个扇区的密码和存取控制都是独立的,存取控制是4个字节,即32位(在块3中)。
每个块都有存取条件,存取条件是由密码和存取控制共同决定的。
每个块都有相应的三个控制位,这三个控制位存在于存取控制字节中,相应的控制位决定了该块的访问权限,控制位如图:
注意: 每个扇区的所有块的存取条件控制位,都放在了该扇区的块3中,如图:
数据块的存取控制
对数据块,与就是块0、1、2的存取控制是由对应块的控制位来决定的:
注意: 要想对数据块进行操作,首先要看该数据块的控制位是否允许对数据块的操作,如果允许操作,再看需要验证什么密码,只有验证密码正确后才可以对该数据块执行相应操作。 一般密码A的初始值都是0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
控制块的存取控
块3(控制块)的存取操作与数据块不同,如图:
通信流程
注意:具体说明参考 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 设备驱动的更多相关文章
- Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化
我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...
- Linux SPI总线和设备驱动架构之三:SPI控制器驱动
通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:1. ...
- Linux SPI总线和设备驱动架构之二:SPI通用接口层
通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...
- Linux SPI总线和设备驱动架构之一:系统概述
SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,用 ...
- Linux SPI总线和设备驱动架构之一:系统概述【转】
转自:http://blog.csdn.net/droidphone/article/details/23367051/ 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 硬 ...
- Linux学习 : 总线-设备-驱动模型
platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...
- Linux与Windows的设备驱动模型对比
Linux与Windows的设备驱动模型对比 名词缩写: API 应用程序接口(Application Program Interface ) ABI 应用系统二进制接口(Application Bi ...
- 【Linux操作系统分析】设备驱动处理流程
1 驱动程序,操作系统,文件系统和应用程序之间的关系 字符设备和块设备映射到操作系统中的文件系统,由文件系统向上提供给应用程序统一的接口用以访问设备. Linux把设备视为文件,称为设备文件,通过对设 ...
- linux PMBus总线及设备驱动分析
PMBus协议规范介绍 PMBus是一套对电源进行配置.控制和监控的通讯协议标准.其最新版本为1.3,该规范还在不断演进中,比如新标准中新增的zone PMBus.AVSBus等特性.在其官网上有详细 ...
- Linux Platform devices 平台设备驱动
设备总线驱动模型:http://blog.csdn.net/lizuobin2/article/details/51570196 本文主要参考:http://www.wowotech.net/devi ...
随机推荐
- ChatGPT 可以联网了!浏览器插件下载
Twitter 用户 An Qu 开发了一款新的 Chrome 插件帮助 ChatGPT 上网,安装插件以后 ChatGPT 就可以联!网!了! 简单来说开启插件后,他可以从网上搜索信息,并且根据用户 ...
- python各种小知识
一.三元表达式 1. 简化步骤1:代码简单且只有一行,可以直接在冒号后面编写 三元表达式: 数据值1+ if 条件+else 数据值2条件成立则使用数据值1,条件不成立则使用数据值2: 当结果是二选一 ...
- AcWing340通信道路/ USACO2008 Telephone Line S
AcWing题目 洛谷题目 解题思路 首先可以得到一个很容易得到的贪心策略,将一条路径上最贵的(边权最大)的\(K\)条边删去,那么我们剩下的路径中最贵(边权最大)的路就是原本这条路径上帝\(K + ...
- 【机器学习】李宏毅——Domain Adaptation(领域自适应)
在前面介绍的模型中,一般我们都会假设训练资料和测试资料符合相同的分布,这样模型才能够有较好的效果.而如果训练资料和测试资料是来自于不同的分布,这样就会让模型在测试集上的效果很差,这种问题称为Domai ...
- 6、SQL模糊查询LIKE concat用法
concat用来拼接查询的字符串: SELECT * FROM `page_demo` WHERE name LIKE concat('%',#{name},'%') 模糊查询: 1.%代表零个或多个 ...
- CH392/CH395常见问题解决方法指南
CH395 问题 1: CH395 初始化失败.解答: 1.首先检查"check_exist"命令,正常情况下 CH395 会将该命令的输入值按位取反后输出,若该命令不正常,则说明 ...
- python进阶之路7 数据类型的内置方法
内容回顾 while 循环补充说明 1.死循环 2.while循环嵌套和全局标志位 for循环 1.for 变量名 in 待遍历数据 for循环体代码 2.for 也可以与break continue ...
- elasticsearch之search template
一.search template简介 elasticsearch提供了search template功能,其会在实际执行查询之前,对search template进行预处理并将参数填充到templa ...
- Grafana 系列文章(四):Grafana Explore
️URL: https://grafana.com/docs/grafana/latest/explore/ Description: Explore Grafana 的仪表盘 UI 是关于构建可视化 ...
- MySQL 如何实现数据更新
一般在更新时会遇到以下场景:1.所有字段全部更新:2.根据条件更新字段中的某部分内容:3.根据不同的条件更新不同的值,以下是几种场景中常用的update方法. 一.方法分类 二.具体用法 (1)根据条 ...