PN5180 嵌入式Linux移植-驱动篇

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 的设备,这样驱动程序就算完成了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

觉醒的奶爸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值