PN5180的驱动源代码可以从官网下载,具体的下载地址上一篇文章有具体的链接。怎么将官方的驱动porting到实际的项目开发中,需要根据项目中使用的硬件资源来进行修改。下面就从分析驱动代码开始,逐一进行讲解。
1.驱动的入口函数
static int __init baldev_init(void)
{
int status;
baldev_class = class_create(THIS_MODULE, NFC_DEVICE_NAME);//创建类
if (IS_ERR(baldev_class))
return PTR_ERR(baldev_class);
status = register_chrdev(BALDEV_MAJOR, NFC_DEVICE_NAME, &baldev_fops);//创建字符设备
if (status < 0)
goto err_reg_dev;
//register driver
status = spi_register_driver(&bal_spi_driver);//注册驱动
if (status < 0)
goto err_reg_drv;
m_dev = spi_register_device(&m_spi_device,spi_bus_num);//注册设备
if (m_dev == NULL)
{
printk(KERN_ERR "pn5180: failed to register spi device \r\n");
status = -1;
goto err_register_device;
}
return 0;
err_register_device:
spi_unregister_device(m_dev);
err_reg_drv:
unregister_chrdev(BALDEV_MAJOR, BALDEV_NAME);
err_reg_dev:
class_destroy(baldev_class);
return status;
}
module_init(baldev_init);
驱动入口函数中包含了类的创建,字符设备的注册,以及驱动和设备的注册,因为PN5180是字符设备,在驱动加载后,需要通过static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) 函数注册字符设别,其中包含三个变量:major(主设备号)、name(设备名称)和fops(file_operations类型指针,指向设备的操作函数集合变量)。在程序中的定义:
#define BALDEV_MAJOR 100
#define NFC_DEVICE_NAME "nxp-pn5180"
static const struct file_operations baldev_fops = {
.owner = THIS_MODULE,
.write = baldev_write,
.read = baldev_read,
.open = baldev_open,
.unlocked_ioctl = baldev_ioctl,
.release = baldev_release,
.llseek = no_llseek,
};
2.驱动出口函数
有入口就会有出口,出口函数主要是卸载已注册的设备或驱动。
static void __exit baldev_exit(void)
{
spi_unregister_driver(&bal_spi_driver);
class_destroy(baldev_class);
unregister_chrdev(BALDEV_MAJOR, BALDEV_NAME);
}
module_exit(baldev_exit);
3.probe函数
当注册的device和driver在总线上匹配成功时,probe函数被调用,具体匹配的过程不详细分析。
static int bal_spi_probe(struct spi_device *spi)
{
struct device *dev;
int ret;
dev_info(&spi->dev, "Probing BAL driver\n");
mutex_init(&bal.use_lock);
//if (spi->dev.of_node) {
bal.busy_pin =busy_gpio;
if (!gpio_is_valid(bal.busy_pin)) {
dev_err(&spi->dev, "BUSY pin mapped to an invalid GPIO!\n");
return -ENODEV;
}
ret = gpio_request(bal.busy_pin, "nfc_int");
if (ret){
pr_err("%s :not able to get GPIO busy_pin \n", __func__);
return -ENODEV;
}
bal.busy_irq = gpio_to_irq(bal.busy_pin);
if (bal.busy_irq < 0) {
dev_err(&spi->dev, "BUSY pin GPIO can't be used as IRQ!\n");
return bal.busy_irq;
}
ret = gpio_direction_input(bal.busy_pin);
if (ret < 0) {
pr_err("%s :not able to set busy_gpio as input\n", __func__);
goto err_exit;
}
bal.spi = spi;
bal.devt = MKDEV(BALDEV_MAJOR, BALDEV_MINOR);
init_completion(&bal.busy_done);
dev = device_create(baldev_class, &spi->dev, bal.devt, &bal, NFC_DEVICE_NAME);//创建设备节点
if (IS_ERR(dev)) {
dev_err(&spi->dev, "Error creating device!\n");
return PTR_ERR(dev);
}
spi_set_drvdata(spi, &bal);//
return 0;
err_exit:
if (gpio_is_valid(bal.busy_pin))
gpio_free(bal.busy_pin);
dev_info(&spi->dev, "err_exit:\n");
/*
err_firm:
gpio_free(reset_gpio);
dev_info(&spi->dev, "err_firm\n");
err_ven:
gpio_free(power_gpio);
dev_info(&spi->dev, "err_ven\n");*/
return ret;
}
在probe函数中主要实现了GPIO的初始化和设备节点的创建。
4.Open函数
open 函数中完成了busy信号的中断注册,当有数据出传出的时候,busy的中断便被触发,中断函数bal_busy_isr被调用。
static int baldev_open(struct inode *inode, struct file *filp)
{
int status;
printk("baldev_open\r\n");
mutex_lock(&bal.use_lock);
if (bal.in_use) {
dev_err(&bal.spi->dev, "Can only be opened once!\n");
status = -EBUSY;
goto err;
}
bal.buffer = kmalloc(BAL_MAX_BUF_SIZE, GFP_KERNEL | GFP_DMA);
if (bal.buffer == NULL) {
status = -ENOMEM;
goto err;
}
bal.mode = BAL_MODE_NORMAL;
status = request_threaded_irq(bal.busy_irq, bal_busy_isr,
NULL,
IRQF_TRIGGER_FALLING,
NFC_DEVICE_NAME, &bal);//注册中断
if (status < 0) {
dev_err(&bal.spi->dev, "Can't request IRQ %d!\n", bal.busy_irq);
goto err;
}
bal.in_use = true;
reinit_completion(&bal.busy_done);//完成量初始化
bal.first_xfer = true;
mutex_unlock(&bal.use_lock);
nonseekable_open(inode, filp);
try_module_get(THIS_MODULE);
return 0;
err:
mutex_unlock(&bal.use_lock);
return status;
}
5.read 函数
read函数在读取数据时,会通过调用wait_for_completion_timeout函数等待数据的传出,当有数据传出时,busy中断函数被执行,其中complete会唤醒上述的等待函数,从而通过spi进行数据的读取,最后通过copy_to_user将数据传递给应用层程序。
static ssize_t
baldev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
ssize_t status = 0;
//("read data \r\n");
if (count > BAL_MAX_BUF_SIZE)
return -EMSGSIZE;
//printk("read:%x,%x,%x,cout:%d \r\n",buf[0],buf[1],buf[2],count);
status = wait_for_completion_timeout(
&bal.busy_done, (BAL_BUSY_TIMEOUT_SECS * HZ));//表示等待事件的发生,并且提供超时设置,如果超过了这一设置,则取消等待,可防止无限等待;如果在超时之前完成则返回剩余时间
if (status < 0) {
dev_err(&bal.spi->dev, "Timeout waiting for BUSY\n");
return status;
}
if (bal.mode == BAL_MODE_DWL)
bal.buffer[0] = BAL_DWL_DIRECTION_BYTE_RD;
//memset(bal.buffer,0,sizeof(bal.buffer));
//msleep(100);
status = bal_spi_sync_xfer(count);
if (status < 0)
return status;
if (copy_to_user(buf, bal.buffer, count))//将数据copy 给应用层
return -EFAULT;
return count;
}
6.write 函数
write函数和read函数的过程是相反的。
static ssize_t
baldev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
//printk("write data \r\n");
ssize_t status = 0;
if (count > BAL_MAX_BUF_SIZE)
return -ENOMEM;
if (copy_from_user(bal.buffer, buf, count))
return -EFAULT;
//printk("wirte:%x,%x,%x ,%x,%x,%x count:%d\r\n",buf[0],buf[1],(u8)buf[2],(u8)buf[3],(u8)buf[4],(u8)buf[5],count);
//msleep(2);
if ((bal.mode == BAL_MODE_NORMAL) &&
!bal.first_xfer) {
status = wait_for_completion_timeout(
&bal.busy_done, (BAL_BUSY_TIMEOUT_SECS * HZ));
if (status < 0) {
dev_err(&bal.spi->dev, "Timeout waiting for BUSY\n");
return status;
}
}
bal.first_xfer = false;
status = bal_spi_sync_xfer(count);
if (status < 0)
return status;
return count;
}
以上就是驱动中涉及的主要的函数,在移植的过程中,修改的地方大概有以下几个方面。
1.官方提供的是采用设备树匹配的方式,但是项目中使用的mcu不支持设备树,需要修改。
将
static const struct of_device_id baldev_dt_ids[] = {
{ .compatible = "nxp,bal" },
{},
};
改为:
//spi 设备结构体
static struct spi_board_info m_spi_device = {
.modalias = NFC_DEVICE_NAME,
.max_speed_hz = 7*1000*1000,
.mode = SPI_MODE_0,
.chip_select = 0,
.controller_data = (void *)(-1),
};
2.busy gpio的初始化
static int busy_gpio = GPIO_PB(24);//busy 信号 Busy line to indicate to host availability of data for reading
3.device的注册函数
struct spi_device *spi_register_device(struct spi_board_info *info, int spi_bus_num)
{
struct spi_master *master = spi_busnum_to_master(spi_bus_num);//获得spi_master指针
struct spi_device *dev;
if (!master)
{
printk(KERN_ERR "error:failed to get spi master %d \r\n",spi_bus_num);
return NULL;
}
info->bus_num = spi_bus_num;
dev = spi_new_device(master,info);//申请并注册设备,返回非空指针意味着可以使用
if (!dev)
{
printk(KERN_ERR "can not register spi device to %d busnum! \r\n",spi_bus_num);
}
spi_master_put(master);
return dev;
}
完成代码的修改之后,将编译好的代码烧写到主板上,会在/dev/挂载nxp-pn5180 的设备,这样驱动程序就算完成了。