一、概述
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
RC522(RFID)讀寫驅動:https://blog.csdn.net/zhiyuan2021/article/details/128922757