做完后只是部分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个单位
*/
};
//plat_driver.c
#include
#include
#include
#include
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 [email protected]:/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
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
#include
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
#include
#include
#include
#include //io操作的头文件(for ioremap readl)
#include //for copy_to_user
#include
#include
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"); //模块免费开源声明