嵌入式Linux驱动笔记(十三)------spi设备之RFID-rc522驱动

2019-07-13 03:15发布

###你好!这里是风筝的博客, ###欢迎和我一起交流。 上一节讲了spi框架:通俗易懂式分析了解spi框架
现在我们写一下spi的设备驱动程序, rc522是一款刷卡模块,类似于学校食堂的刷卡机。 以kernel4.8.17为例:
之前我们给mach-smdk2440.c文件添加了:
&s3c_device_spi0,
现在我要把rc522设备接在2440的spi1接口上,所以我们应该修改为:
&s3c_device_spi1,
然后我们看下spi-s3c24xx.c的Makefile,里面: obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o spi-s3c24xx-hw-y := spi-s3c24xx.o 所以我们在menuconfig里,选上:Device Drivers —>[*] SPI support —> < M> Samsung S3C24XX series SPI
之前是作为M模块的,现在 选上编进内核。
make uImage,启动内核之后,发现:
s3c2410-spi s3c2410-spi.1: No platform data supplied
s3c2410-spi: probe of s3c2410-spi.1 failed with error -2
发现,没有platform 信息,我们看下&s3c_device_spi1,发现里面确实没有platform 信息,所以我们需要自己添加进去,修改为: #include /*by kite 2017.9.13*/ #include /*by kite 2017.9.13*/ #include /*by kite 2017.9.13*/ #include /*by kite 2017.9.13*/ #include /*by kite 2017.9.13*/ static void s3c24xx_spi_cs(struct s3c2410_spi_info *spi, int cs, int pol)/*by kite 2017.9.13*/ { s3c_gpio_cfgpin(cs, S3C_GPIO_OUTPUT); gpio_set_value(cs, pol); } static struct s3c2410_spi_info spi1_info={/*by kite 2017.9.13*/ .num_cs = 0xff ,/*最大片选数*/ .bus_num = 1, .set_cs = s3c24xx_spi_cs, }; struct platform_device s3c_device_spi1 = { .name = "s3c2410-spi", .id = 1, .num_resources = ARRAY_SIZE(s3c_spi1_resource), .resource = s3c_spi1_resource, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), .platform_data = &spi1_info,/*by kite 2017.9.13*/ } }; 然后启动Kernel,发现:s3c2410-spi.1:No clock for device
定位错误代码在drivers/spi/spi-s3c24xx.c文件里的s3c24xx_spi_probe函数里: hw->clk = devm_clk_get(&pdev->dev, "spi"); if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device "); err = PTR_ERR(hw->clk); goto err_no_pdata; } 其中devm_clk_get函数是对clk_get函数进行了封装: struct clk *devm_clk_get(struct device *dev, const char *id) { struct clk **ptr, *clk; ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); clk = clk_get(dev, id); if (!IS_ERR(clk)) { *ptr = clk; devres_add(dev, ptr); } else { devres_free(ptr); } return clk; } 这里可以看下这篇文章:详解clock时钟(CCF)框架及clk_get函数
这里我们clk_get失败了,我们知道,clk_get函数是从一个时钟list链表中以字符id名称来查找一个时钟clk结构体并且返回的,所以我们时钟链表里还未添加spi时钟:
在drivers/clk/samsung/clk-s3c2410.c文件里: struct samsung_clock_alias s3c2410_common_aliases[] __initdata = { ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"), ALIAS(PCLK_ADC, NULL, "adc"), ALIAS(PCLK_RTC, NULL, "rtc"), ALIAS(PCLK_PWM, NULL, "timers"), ALIAS(HCLK_LCD, NULL, "lcd"), ALIAS(HCLK_USBD, NULL, "usb-device"), ALIAS(HCLK_USBH, NULL, "usb-host"), ALIAS(UCLK, NULL, "usb-bus-host"), ALIAS(UCLK, NULL, "usb-bus-gadget"), ALIAS(ARMCLK, NULL, "armclk"), ALIAS(UCLK, NULL, "uclk"), ALIAS(HCLK, NULL, "hclk"), ALIAS(MPLL, NULL, "mpll"), ALIAS(FCLK, NULL, "fclk"), ALIAS(PCLK, NULL, "watchdog"), ALIAS(PCLK_SDI, NULL, "sdi"), ALIAS(HCLK_NAND, NULL, "nand"), ALIAS(PCLK_I2S, NULL, "iis"), ALIAS(PCLK_I2C, NULL, "i2c"), }; 在数组最后一行添加: ALIAS(PCLK_SPI, NULL, "spi"),/*add by kite*/ 然后编译好内核,启动后还发现:
WARNING: CPU: 0 PID: 1 at drivers/clk/clk.c:652 clk_core_enable+0x98/0xa4
有个警告,我们需要在drivers/spi/spi-s3c24xx.c文件里的s3c24xx_spi_probe函数里,在: hw->clk = devm_clk_get(&pdev->dev, "spi"); if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device "); err = PTR_ERR(hw->clk); goto err_no_pdata; } 后面加上两句: if (clk_prepare_enable(hw->clk)) { printk("spi: clock failed : "); } 因为:“名称中含有prepare、unprepare字符串的API是内核后来才加入的,过去只有clk_enable和clk_disable。只有clk_enable和clk_disable带来的问题是,有时候,某些硬件的enable/disable clk可能引起睡眠使得enable/disable不能在原子上下文进行。加上prepare后,把过去的clk_enable分解成不可在原子上下文调用的clk_prepare(该函数可能睡眠)和可以在原子上下文调用的clk_enable。而clk_prepare_enable则同时完成prepare和enable的工作,当然也只能在可能睡眠的上下文调用该API。” 这样,改好后编译,就能成功注册spi-master,顺利启动内核了! 因为还没弄好设备树,现在只好老老实实写platform_device设备了:
rc522_dev.c文件: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct spi_board_info spi_info_rc522[] = { { .modalias = "rc522", /* 对应的spi_driver名字也是"rc522" */ .max_speed_hz = 8000000, /* max spi clock (SCK) speed in HZ */ .bus_num = 1, /* 接在SPI CONTROLLER 1 */ .mode = SPI_MODE_0, .chip_select = S3C2410_GPF(2), /* oled_cs, 它的含义由spi_master确定 */ }, }; static int spi_info_rc522_init(void) { spi_register_board_info(spi_info_rc522, ARRAY_SIZE(spi_info_rc522)); return 0; } static int spi_info_rc522_exit(void) { //spi_unregister_device(); return 0; } module_init(spi_info_rc522_init); module_exit(spi_info_rc522_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("kite");/*modinfo rc522.ko*/ MODULE_DESCRIPTION("A spi-rc522 Module for testing module "); MODULE_VERSION("V1.0"); 里面就是注册spi_board_info板级设备信息,不过这里编写好后我发现会出个问题:
rc522_dev: Unknown symbol spi_register_board_info (err 0)
insmod: can’t insert ‘rc522_dev.ko’: unknown symbol in module or invalid parameter
也就是spi_register_board_info 函数没有导出内核符号到公共内核符号表,详情可以看这篇blog:http://blog.csdn.net/wuyongpeng0912/article/details/46739233。
也可以查看System.map文件有没有这个函数,参考:System.map文件的作用
所以我们在drivers/spi/spi.c文件里找到spi_register_board_info函数实现的地方,在后面加上一句:EXPORT_SYMBOL_GPL(spi_register_board_info);
重新编译即可。 然后继续看下platform_driver驱动:
rc522_drv.c文件: 编译好后又发现个问题,真是一波三折啊… WARNING: CPU: 0 PID: 1037 at drivers/base/dd.c:347 driver_probe_device+0x114/0x318 同样是个警告,定位到drivers/base/dd.c文件里的really_probe里的一句: WARN_ON(!list_empty(&dev->devres_head)); 就是这里出了警告,WARN_ON是调用dump_stack,打印堆栈信息,只要list_empty返回0,也就是链表非空时,会warning打印堆栈。
我们看下s3c24xx_spi_probe函数有句:
hw->master->setup = s3c24xx_spi_setup;
s3c24xx_spi_setup函数里面有一句: cs = devm_kzalloc(&spi->dev, sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL); devm_kzalloc函数里会加入 dev->devres_head的:
list_add_tail(&node->entry, &dev->devres_head);
所以这里,我们把devm_kzalloc修改为kzalloc即可: /*cs = devm_kzalloc(&spi->dev, sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL);*/ cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL); /*by kite 2017.9.16*/ 这里说下devm_kzalloc和kzalloc: devm_kzalloc()函数 和kzalloc()函数一样都是内核内存分配函数,但是devm_kzalloc()是跟设备(device)有关的,当设备(device)被detached或者驱动(driver)卸载(unloaded)时,内存会被自动释放。另外,当内存不在使用时,可以使用函数devm_kfree()释放。
而kzalloc()则需要手动释放(使用kfree()),但如果工程师检查不仔细,则有可能造成内存泄漏。

所以这里我们最好还要有kfree函数,如果你不打算释放spi_master的话,不写也没事。
在s3c24xx_spi_probe函数里,
hw->master->setup = s3c24xx_spi_setup;
下面添加一句:
hw->master->cleanup = s3c24xx_spi_cleanup;
同时实现s3c24xx_spi_cleanup函数: static void s3c24xx_spi_cleanup(struct spi_device *spi)/*by kite 2017.9.16*/ { struct chip_data *chip = spi_get_ctldata(spi); kfree(chip); spi_set_ctldata(spi, NULL); printk("spi_master has been rmmove ! "); } 接下来就是rc522_drv.c文件: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rc522.h" #include #include #include #include static volatile unsigned long *gpfcon; static volatile unsigned long *gpfdat;//f1 #define SET_RC522RST *gpfdat |= (0x01<<(1)); #define CLR_RC522RST *gpfdat &= ~(0x01<<(1)); static struct spi_device *spi_dev; /* 1. 确定主设备号 */ static int major; static struct cdev rc522_cdev; static struct class *cls; u8 ReadRawRC(u8 Address ) { unsigned char tx_buf[1]; unsigned char rx_buf[1]; tx_buf[0] = ((Address<<1)&0x7E)|0x80;//RC522 set spi_write_then_read(spi_dev, tx_buf, 1, rx_buf, 1); return rx_buf[0]; } void WriteRawRC(u8 Address, u8 value) { u8 ucAddr[2]; ucAddr[0] = ((Address<<1)&0x7E);//RC522 set ucAddr[1] = value; spi_write(spi_dev, ucAddr, 2); } void ClearBitMask(u8 reg,u8 mask) { char tmp = 0x0; tmp = ReadRawRC(reg); WriteRawRC(reg, tmp & ~mask); // clear bit mask } void SetBitMask(u8 reg,u8 mask) { char tmp = 0x0; tmp = ReadRawRC(reg); WriteRawRC(reg,tmp | mask); // set bit mask } static int rc522_open(struct inode *inode, struct file *file) { u8 i;u8 Re=0; printk("this is open "); /*复位*/ SET_RC522RST; ndelay(10); CLR_RC522RST; ndelay(10); SET_RC522RST; ndelay(10); WriteRawRC(CommandReg,PCD_RESETPHASE); WriteRawRC(CommandReg,PCD_RESETPHASE); ndelay(10); WriteRawRC(ModeReg,0x3D); //和Mifare卡通讯,CRC初始值0x6363 WriteRawRC(TReloadRegL,30); WriteRawRC(TReloadRegH,0); WriteRawRC(TModeReg,0x8D); WriteRawRC(TPrescalerReg,0x3E); WriteRawRC(TxAutoReg,0x40);//必须要 /*关闭天线*/ ClearBitMask(TxControlReg, 0x03);// mdelay(2); /*开启天线*/ i = ReadRawRC(TxControlReg); if (!(i & 0x03)) { SetBitMask(TxControlReg, 0x03); } /*设置RC632的工作方式 */ ClearBitMask(Status2Reg,0x08); WriteRawRC(ModeReg,0x3D);//3F WriteRawRC(RxSelReg,0x86);//84 WriteRawRC(RFCfgReg,0x7F); //4F WriteRawRC(TReloadRegL,30);//tmoLength);// TReloadVal = 'h6a =tmoLength(dec) WriteRawRC(TReloadRegH,0); WriteRawRC(TModeReg,0x8D); WriteRawRC(TPrescalerReg,0x3E); ndelay(1000); /*开启天线*/ i = ReadRawRC(TxControlReg); if (!(i & 0x03)) { SetBitMask(TxControlReg, 0x03); } return 0; } char PcdComMF522(u8 Command, u8 *pIn , u8 InLenByte, u8 *pOut , u8 *pOutLenBit) { char status = MI_ERR; u8 irqEn = 0x00; u8 waitFor = 0x00; u8 lastBits; u8 n; u16 i; switch (Command) { case PCD_AUTHENT: irqEn = 0x12; waitFor = 0x10; break; case PCD_TRANSCEIVE: irqEn = 0x77; waitFor = 0x30; break; default: break; } WriteRawRC(ComIEnReg,irqEn|0x80); ClearBitMask(ComIrqReg,0x80); //清所有中断位 WriteRawRC(CommandReg,PCD_IDLE); SetBitMask(FIFOLevelReg,0x80); //清FIFO缓存 for (i=0; i MAXRLEN) { n = MAXRLEN; } for (i=0; i rc522.h: #ifndef __RC522_H #define __RC522_H ///////////////////////////////////////////////////////////////////// //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 FIFO长度定义 ///////////////////////////////////////////////////////////////////// #define DEF_FIFO_LENGTH 64 //FIFO size=64byte #define MAXRLEN 18 ///////////////////////////////////////////////////////////////////// //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 #define FIFOLevelReg 0x0A #define WaterLevelReg 0x0B #define ControlReg 0x0C #define BitFramingReg 0x0D #define CollReg 0x0E #define RFU0F 0x0F // PAGE 1 #define RFU10 0x10 #define ModeReg 0x11 #define TxModeReg 0x12 #define RxModeReg 0x13 #define TxControlReg 0x14 #define TxAutoReg 0x15 #define TxSelReg 0x16 #define RxSelReg 0x17 #define RxThresholdReg 0x18 #define DemodReg 0x19 #define RFU1A 0x1A #define RFU1B 0x1B #define MifareReg 0x1C #define RFU1D 0x1D #define RFU1E 0x1E #define SerialSpeedReg 0x1F // PAGE 2 #define RFU20 0x20 #define CRCResultRegM 0x21 #define CRCResultRegL 0x22 #define RFU23 0x23 #define ModWidthReg 0x24 #define RFU25 0x25 #define RFCfgReg 0x26 #define GsNReg 0x27 #define CWGsCfgReg 0x28 #define ModGsCfgReg 0x29 #define TModeReg 0x2A #define TPrescalerReg 0x2B #define TReloadRegH 0x2C #define TReloadRegL 0x2D #define TCounterValueRegH 0x2E #define TCounterValueRegL 0x2F // PAGE 3 #define RFU30 0x30 #define TestSel1Reg 0x31 #define TestSel2Reg 0x32 #define TestPinEnReg 0x33 #define TestPinValueReg 0x34 #define TestBusReg 0x35 #define AutoTestReg 0x36 #define VersionReg 0x37 #define AnalogTestReg 0x38 #define TestDAC1Reg 0x39 #define TestDAC2Reg 0x3A #define TestADCReg 0x3B #define RFU3C 0x3C #define RFU3D 0x3D #define RFU3E 0x3E #define RFU3F 0x3F ///////////////////////////////////////////////////////////////////// //和MF522通讯时返回的错误代码 ///////////////////////////////////////////////////////////////////// #define MI_OK 0 #define MI_NOTAGERR (1) #define MI_ERR (2) #define SHAQU1 0X01 #define KUAI4 0X04 #define KUAI7 0X07 #define REGCARD 0xa1 #define CONSUME 0xa2 #define READCARD 0xa3 #define ADDMONEY 0xa4 #endif 这样,当我们cat rc522设备时,就会读出我们卡的id了 调试:
在Linux根目录下,找spidev_test.c文件,会发现在tools/spi目录下,
make编译即可得到可执行文件:spidev_test
放到板子上短接mosi和miso,就可以测试了。如图,
当然,别忘记了把spidev.c文件编进内核,以及在设备树里添加相应的节点,这样在dev下才会出现spidev的设备节点。 编译的时候发现直接make不好使,我就在spidev_test.c文件里修改:
注释两行:#include
#include
添加: #include “my_spidev.h” spidev my_spidev.h: #ifndef SPIDE_H #define SPIDE_H #include /* User space versions of kernel symbols for SPI clocking modes, * matching */ #define SPI_CPHA 0x01 #define SPI_CPOL 0x02 #define SPI_MODE_0 (0|0) #define SPI_MODE_1 (0|SPI_CPHA) #define SPI_MODE_2 (SPI_CPOL|0) #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) #define SPI_CS_HIGH 0x04 #define SPI_LSB_FIRST 0x08 #define SPI_3WIRE 0x10 #define SPI_LOOP 0x20 #define SPI_NO_CS 0x40 #define SPI_READY 0x80 #define SPI_TX_DUAL 0x100 #define SPI_TX_QUAD 0x200 #define SPI_RX_DUAL 0x400 #define SPI_RX_QUAD 0x800 #define SPI_IOC_MAGIC 'k' struct spi_ioc_transfer { __u64 tx_buf; __u64 rx_buf; __u32 len; __u32 speed_hz; __u16 delay_usecs; __u8 bits_per_word; __u8 cs_change; __u8 tx_nbits; __u8 rx_nbits; __u16 pad; /* If the contents of 'struct spi_ioc_transfer' ever change * incompatibly, then the ioctl number (currently 0) must change; * ioctls with constant size fields get a bit more in the way of * error checking than ones (like this) where that field varies. * * NOTE: struct layout is the same in 64bit and 32bit userspace. */ }; /* not all platforms use or _IOC_TYPECHECK() ... */ #define SPI_MSGSIZE(N) ((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) ? ((N)*(sizeof (struct spi_ioc_transfer))) : 0) #define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)]) /* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */ #define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8) #define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8) /* Read / Write SPI bit justification */ #define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8) #define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8) /* Read / Write SPI device word length (1..N) */ #define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8) #define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8) /* Read / Write SPI device default max speed hz */ #define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32) #define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32) /* Read / Write of the SPI mode field */ #define SPI_IOC_RD_MODE32 _IOR(SPI_IOC_MAGIC, 5, __u32) #define SPI_IOC_WR_MODE32 _IOW(SPI_IOC_MAGIC, 5, __u32) #endif /* SPIDEV_H */