【嵌入式Linux内核驱动】03_平台设备驱动最简

一、最简

做完后只是部分GPIO可以用,只放了GPIO的基地址和复用的基地址,后面控制GPIO通用化可解决这问题

.driver = {
    .name   = "bbcen plat driver" ,
    .owner = THIS_MODULE,
    .of_match_table = of_plat_match,
}, 
//根据这些名字让驱动driver匹配设备device,优先匹配of_match_table(设备树里的),没有就匹配name(自己写的设备device文件)

设备树:描述设备的硬件信息

//hardware/nvidia/platform/t210/porg/kernel-dts/tegra210-p3448-0000-p3449-0000-b00.dts
添加:
   yhai-plat {
       compatible = "bbcen-plat";
       status = "okay";
       reg = <0 0x6000D200 0 0xff>,<0 0x70003150  0 0X10>;  /*注意上层的
       	  #address-cells = <2>; //设置子节点"reg"属性的 首地址 占2个单位(1个cell单位,占32位)
	  #size-cells = <2>;    //设置子节点"reg"属性的 地址长度 占2个单位
	*/
   };

*驱动

  • of_plat_match //注意这个要和设备树里的一样
  • platform_get_resource //从平台设备里获取资源
//plat_driver.c	
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
struct resource *res1;
struct resource *res2;
static int plat_probe(struct platform_device *pdev)
{
    printk("match ok,plat_probe go\n");   
    res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); /*从平台设备里获取资源<0 0x6000D200 0 0xff>
       pdev: 指向平台设备的指针
       IORESOURCE_MEM:资源类型
       0: 资源序号
    */
    if (res1 == NULL) {
	printk("res1 platform_get_resource fail \n");
	return -ENODEV;
    }
    printk("res1->start=%llx\n",res1->start);

    res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);//<0 0x70003150  0 0X10>
    if (res2 == NULL) {
	printk("res2 platform_get_resource fail\n");
	return -ENODEV;
    }
    printk("res2->start=%llx\n",res2->start);
    return 0;
}

static int plat_remove(struct platform_device *pdev)
{
    printk("plat_remove go\n");
    return 0;
}

static const struct of_device_id of_plat_match[] = { //与驱动匹配的平台设备id列表
    { .compatible = "bbcen-plat", },
    {},  
};
MODULE_DEVICE_TABLE(of, of_plat_match);

static struct platform_driver plat_driver = {
    .driver = {
        .name   = "bbcen plat driver" ,
        .owner = THIS_MODULE,
        .of_match_table = of_plat_match,
    },   
    .probe = plat_probe,   //当在设备树里找到对应的平台设备,才会调用probe函数
    .remove = plat_remove,  //删除设备时,会自动调用
};

module_platform_driver(plat_driver); //声明平台设备驱动的入口

编译文件

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= ~/kernel-4.9 
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules* a.out 

else
    obj-m := plat_driver.o
endif

验证测试

//用NFS + TFTP方式(TFTP传设备树到板子)
$ make dtbs
$ make modules
$ make Image -j4
		
$ scp arch/arm64/boot/dts/tegra210-p3448-0000-p3449-0000-b00.dtb   /tftpboot/
         //老方式 $ scp arch/arm64/boot/dts/tegra210-p3448-0000-p3449-0000-b00.dtb   yhbd@192.168.10.237:/home/yhbd/
$ scp arch/arm64/boot/Image   bbcen@192.168.10.237:/home/yhbd/		
$ cp plat_driver.ko  /nfs/rootfs

# setenv bootargs  root=/dev/nfs rw nfsroot=192.168.9.119:/nfs/rootfs,v3 console=ttyS0,115200 init=/linuxrc ip=192.168.9.9
# setenv nfsboot pci enum \; pci \; ext4load mmc 1:1 0x84000000 /boot/Image \; tftp 83100000 tegra210-p3448-0000-p3449-0000-b00.dtb \; booti 0x84000000 - 83100000

# saveenv
# run nfsboot  //手动运行

# setenv bootcmd run nfsboot  //测试成功后,可设为自启动命令,避免每次都手动运行
# saveenv	
	
$ insmod plat_driver.ko //发现plat_probe未进入
	 //原因时,内核 设备树,模块的编译必须在同一套源码下编译才行,它们是要相互匹配的
$ rmmod plat_driver

*实例:改造字符设备成平台设备

  • platform_get_resource //从平台设备里获取资源
  • class_create/device_create //自动创建设备节点

static int led_init(void) 改为 static int led_probe(struct platform_device *pdev)

static void led_exit(void) 改为 static int led_remove(struct platform_device *pdev)

地址改成读出来的

#include <linux/platform_device.h>
#include <linux/of.h>
struct resource *res1;
struct resource *res2;	

//把硬件地址剥离到设备树中,间接访问
//#define  PINMUX_AUX_DAP4_SCLK_0  0x70003150   //管脚复用设置

//#define GPIO3  	0x6000D200 // 第3个Bank GPIO 的基地址
	
//led_probe里添加	
	res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0);   //IORESOURCE_MEM资源类型,0指0号资源
	if (res1 == NULL) {
		printk("res1 platform_get_resource fail \n");
		return -ENODEV;
	}
	printk("res1->start=%llx\n",res1->start);
	res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (res2 == NULL) {
		printk("res2 platform_get_resource fail\n");
		return -ENODEV;
	}	 	
	
	PINMUX_AUX_DAP4_SCLK_0 //替换为res2->start
	GPIO3 //res1->start

    { .compatible = "bbcen-plat", },	 //要和设备树里节点名字一致


//自动创建设备节点( 无需再mknod /dev/led c 500 0)
struct class *led_class;	
led_probe{
	led_class = class_create(THIS_MODULE, "bbcen_dev_class");  //自动创建类在/class生成
	if (IS_ERR(led_class)) {
		printk("Err: failed in creating class.\n");
		goto err6;
	}

	/* register your own device in sysfs, and this will cause udevd to create corresponding device node */
	device_create(led_class,NULL, devno, NULL,"led");       //insmod自动创建,不用再加设备号,设备节点
}
led_remove{
  device_destroy(led_class, devno);	 //释放资源,和前面配对
  class_destroy(led_class);
}

改造后的完整程序

//led.c 改造后的OK代码, -> 好处时,如果你换了管脚,更改设备树地址即可,驱动少变甚至不变
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>  
#include <asm/io.h>   //io操作的头文件(for  ioremap readl)
#include <asm/uaccess.h>  //for copy_to_user
#include <linux/platform_device.h>
#include <linux/of.h>
struct resource *res1;
struct resource *res2;
struct class *led_class;	


#define LED_MA   500  //定义主设备号(区分不同的类别)

#define LED_MI   0  //次设备号

struct cdev cdev; //定义字符设备


#define LED_MAGIC   'L'  //幻数
#define  LED_ON   _IOW(LED_MAGIC,1,int)  //点亮灯
#define  LED_OFF  _IOW(LED_MAGIC,2,int)   //灭灯

//把硬件地址剥离到设备树中,间接访问
//#define  PINMUX_AUX_DAP4_SCLK_0  0x70003150   //管脚复用设置

//#define GPIO3  	0x6000D200 // 第3个Bank GPIO 的基地址
#define CNF     0x04  //配置寄存器 (0:GPIO  1:SFIO)  偏移量
#define OE   	0x14  //输出使能寄存器 (1:使能 0:关闭)
#define OUT  	0x24  //输出寄存器(1:高电平 0:低电平)
#define MSK_CNF 0x84  //配置屏蔽寄存器(高位1:屏蔽 高位0:不屏蔽   低位1:GPIO模式 低位0:SFIO模式)
#define MSK_OE  0x94  //输出使能屏蔽寄存器(高位1:禁止写   低位1:使能)
#define MSK_OUT 0xA4  //输出屏蔽寄存器(高位1:禁止写   低位1:高电平)

 

unsigned char *gpio_pinmux;
unsigned char * gpio_base;

int led_open (struct inode * inode, struct file * file){

    printk(" led open go\n");
    return 0;
}

int led_release(struct inode *inode, struct file * file){
    printk(" led release go\n");
    return 0;
}

//开灯
void led_on(void)
{
    writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //引脚输出高电平,点亮灯
	printk("out put high ,led on 输出高电平,点亮灯\n");
}

//关灯
void led_off(void)
{
    writel(readl(gpio_base+OUT) & ~(1 << 7), gpio_base+OUT);  //引脚输出低电平,灭灯
	printk("out put low, led off 输出低电平,灭灯\n");
}


long led_ioctl(struct file *inode, unsigned int cmd , unsigned long args)
{
    printk("cmd=%d\n",cmd);
   	switch(cmd)
   	{
	case LED_ON:
		led_on();
		break;   	
	case LED_OFF:
		led_off();
	default:
		printk("no fond cmd =%d\n",cmd);
		return -1;
   	}
   	return 0;
}



 struct file_operations led_fops={//实现需要的文件操作
    .owner = THIS_MODULE,
    .unlocked_ioctl = led_ioctl,
};	


static int led_probe(struct platform_device *pdev)
{
     int  devno = MKDEV(LED_MA,LED_MI); //合并主次设备号,生成设备ID
     int  ret; 

    //1.注册设备号
     ret = register_chrdev_region(devno,1,"yhai_led");
     if(ret<0){
	 	printk("register_chrdev_region fail \n");
		return ret;
     }

    //2.初始化字符设备
    cdev_init(&cdev,&led_fops);  //字符设备的初始化

    ret =cdev_add(&cdev,devno,1); 
     if(ret<0){
	 	printk("cdev_add fail \n");
		goto err1;
     }

	res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); 
	if (res1 == NULL) {
		printk("res1 platform_get_resource fail \n");
		goto err4;
	}
	printk("res1->start=%llx\n",res1->start);
	res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (res2 == NULL) {
		printk("res2 platform_get_resource fail\n");
		goto err5;
	}	 

     //硬件控制
     //a. 管脚复用的配置,配置为GPIO功能
     //内核驱动里不能直接操作硬件地址,必须映射转化
     	// *PINMUX_AUX_DAP4_SCLK_0 = 0x45;
	// *gpio_out  =0x1
      gpio_pinmux = ioremap(res2->start,8); /*从物理地址PINMUX_AUX_DAP4_SCLK_0开始,映射 8字节长度的空间到内核空间
    动态映射 物理地址 到内核虚拟地址
	phys_addr  起始物理地址
	size       映射范围大小,单位字节
		返回值     映射后的内核虚拟地址 
	*/	
     	if(gpio_pinmux ==NULL){
		printk("ioremap  gpio_pinmux faile \n");		
		goto err2;
     	}
       writel((readl(gpio_pinmux) & ~(1 << 4))|1, gpio_pinmux); /*管脚复用配置用于 GPIO
        1:0 I2S4B    PM: 0 = I2S4B 1 = RSVD1 2 = RSVD2  3 = RSVD3 
        设为非0,表示不用作I2S功能,则默认用做GPIO功能

        4 TRISTATE TRISTATE:   0 = PASSTHROUGH  1 = TRISTATE
        设为0,设为直通状态才能驱动外面的设备

        见 9.5.1 Per Pad Options
     Tristate     高阻态 -> 与外界是断开的,默认启动设为高阻太,避免驱动影响外面的设备             
     passthrough  直通态 -> 才能驱动外面设备   
	*/


       //b.GPIO功能的内部设置
	    gpio_base = ioremap(res1->start, 0xFF); 
	    if (gpio_base == NULL) {
		printk("ioremap gpio_base error\n");
		goto err3;
	    }

	writel(readl(gpio_base+CNF) | 1 << 7, gpio_base+CNF);   //配置引脚GPIO3_PJ.07 为 GPIO模式
      writel(readl(gpio_base+OE) | 1 << 7, gpio_base+OE);	  //使能引脚(7号)
      writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //输出高电平,点亮灯
     writel(readl(gpio_base+MSK_CNF) | 1 << 7, gpio_base+MSK_CNF); //取消对GPIO模下引脚的屏蔽
     writel(readl(gpio_base+MSK_OE) | 1 << 7, gpio_base+MSK_OE);  //取消引脚 使能屏蔽


	led_class = class_create(THIS_MODULE, "yhai_dev_class");
	if (IS_ERR(led_class)) {
		printk("Err: failed in creating class.\n");
		goto err6;
	}

	/* register your own device in sysfs, and this will cause udevd to create corresponding device node */
	device_create(led_class,NULL, devno, NULL,"led");     
		
	printk("platform led probe 1\n");
	return 0;

	//goto 实现 顺序申请,逆序释放
err6:	
err5:	
err4:	
err3:
	iounmap(gpio_pinmux);
err2:
	cdev_del(&cdev);
err1:
	unregister_chrdev_region(devno,1);
	return ret;
	
}

static int led_remove(struct platform_device *pdev)

{
      //配对
      //注销设备
      int  devno = MKDEV(LED_MA,LED_MI); 
      //顺序申请,逆序释放
      
      	device_destroy(led_class, devno);
	class_destroy(led_class);
	iounmap(gpio_pinmux); 
	iounmap(gpio_base);
       cdev_del(&cdev);
       unregister_chrdev_region(devno,1);
	printk("platform led exit 1\n");
	return 0;
}



static const struct of_device_id of_led_match[] = {
    { .compatible = "bbcen-plat", },
    {},  
};
MODULE_DEVICE_TABLE(of, of_led_match);

static struct platform_driver led_driver = {
    .driver = {
        .name   = "bbcen platform led" ,
        .owner = THIS_MODULE,
        .of_match_table = of_led_match,
    },   
    .probe = led_probe,
    .remove = led_remove,
};

module_platform_driver(led_driver);

MODULE_DESCRIPTION(" cdev to platform device ");
MODULE_AUTHOR("bbcen"); //模块作者声明(可选)
MODULE_LICENSE("GPL"); //模块免费开源声明
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值