【Linux SPI】RFID RC522 裝置驅動

澆築菜鳥發表於2023-03-02

一、概述

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

相關文章