树莓派linux下gpio驱动,树莓派官方自带gpio驱动bcm2708_gpio.c原理分析 linux gpio架构 gpio子系统...

对树莓派gpio的操作有好多方法,比如mmap映射cpu内存,编写内核驱动模块等。这里推荐一篇文章外链网址已屏蔽

其实树莓派官方使用linux内核gpio驱动框架内置了一个驱动,让我们可以直接使用标准的内核gpio模型来开发。今天就来分析下这个驱动,驱动文件在arch/arm/mach-bcm2709/bcm2708_gpio.c 。该文件是一个模块,但是只能编译进内核,不能编译成模块,所以在实验的时候只能连内核加模块一起更新到树莓派。

一·首先来看下platform驱动的注册和取消注册函数

static int __init bcm2708_gpio_init(void)

{return platform_driver_register(&bcm2708_gpio_driver);//注册平台驱动

}static void __exit bcm2708_gpio_exit(void)

{

platform_driver_unregister(&bcm2708_gpio_driver);//取消注册平台驱动

}

很简单的调用platform_driver_register和platform_driver_unregister函数进行注册和取消注册平台驱动。

二·平台设备的注册

平台设备的注册在板级初始化文件中(arch/arm/mach-bcm2709/bcm2709.c)进行的。

static struct resource bcm2708_gpio_resources[] ={

[0] = { /*general purpose I/O*/.start=GPIO_BASE,

.end= GPIO_BASE + SZ_4K - 1,

.flags=IORESOURCE_MEM,

},

};static u64 gpio_dmamask =DMA_BIT_MASK(DMA_MASK_BITS_COMMON);static struct platform_device bcm2708_gpio_device ={

.name=BCM_GPIO_DRIVER_NAME,

.id= -1, /*only one VideoCore I/O area*/.resource=bcm2708_gpio_resources,

.num_resources=ARRAY_SIZE(bcm2708_gpio_resources),

.dev={

.dma_mask= &gpio_dmamask,

.coherent_dma_mask=DMA_BIT_MASK(DMA_MASK_BITS_COMMON),

},

};

我们可以看到设备资源的开始地址是GPIO_BASE,他的值在arch/arm/mach-bcm2709/includes/platform.h中定义为0x3F200000,这个地址就是bcm2709的gpio的基地址。

三·平台设备初始化probe函数分析

static int bcm2708_gpio_probe(struct platform_device *dev)

{struct bcm2708_gpio *ucb;struct resource *res;intbank;int err = 0;

printk(KERN_INFO DRIVER_NAME": bcm2708_gpio_probe %p\n", dev);

ucb= kzalloc(sizeof(*ucb), GFP_KERNEL);//分配自定义的结构体内存

if (NULL ==ucb) {

printk(KERN_ERR DRIVER_NAME": failed to allocate mailbox memory\n");

err= -ENOMEM;gotoerr;

}

res= platform_get_resource(dev, IORESOURCE_MEM, 0);//获取板级初始化中的平台资源

platform_set_drvdata(dev, ucb);//将ucb保存到本驱动的私有数据指针中

ucb->base = __io_address(GPIO_BASE);//获取gpio物理地址

/*以下是相关数据和函数的结构体赋值*/ucb->gc.label = "bcm2708_gpio";

ucb->gc.base = 0;

ucb->gc.ngpio =BCM2708_NR_GPIOS;

ucb->gc.owner =THIS_MODULE;

ucb->gc.direction_input =bcm2708_gpio_dir_in;

ucb->gc.direction_output =bcm2708_gpio_dir_out;

ucb->gc.get =bcm2708_gpio_get;

ucb->gc.set =bcm2708_gpio_set;

ucb->gc.can_sleep = 0;/*这个是初始化两个bank(这么多个gpio一共分成两个bank)的某个属性*/

for (bank = 0; bank < GPIO_BANKS; bank++) {

writel(0, ucb->base +GPIOREN(bank));

writel(0, ucb->base +GPIOFEN(bank));

writel(0, ucb->base +GPIOHEN(bank));

writel(0, ucb->base +GPIOLEN(bank));

writel(0, ucb->base +GPIOAREN(bank));

writel(0, ucb->base +GPIOAFEN(bank));

writel(~0, ucb->base +GPIOEDS(bank));

}

bcm2708_gpio_irq_init(ucb);//中断初始化

err= gpiochip_add(&ucb->gc);//向系统的gpio子系统注册

err:returnerr;

}

structbcm2708_gpio {structlist_head list;void __iomem *base;structgpio_chip gc;

unsignedlong rising[(BCM2708_NR_GPIOS + 31) / 32];

unsignedlong falling[(BCM2708_NR_GPIOS + 31) / 32];

unsignedlong high[(BCM2708_NR_GPIOS + 31) / 32];

unsignedlong low[(BCM2708_NR_GPIOS + 31) / 32];

};

首先驱动定义了一个存放私有数据的结构体bcm2708_gpio的指针,并为其分配了内存。接着获取板级初始化中的平台资源,将ucb私有数据保存到本驱动的私有数据指针中,获取gpio物理地址,有了这些信息后便进行ucb私有数据相关指针的赋值。由于我没有找到bcm2709的数据手册,所以下面代码的作用不是很了解,只是明白这个是初始化两个bank(这么多个gpio一共分成两个bank)的某个属性。函数最后向系统的gpio子系统注册gpio_chip。gpio_chip结构体需要实现多个系统回调函数,分别是GPIO方向设置函数,GPIO设置输出函数,GPIO读取状态函数。下面会一一看每个函数。

四·平台设备注销remove函数分析

gpiochip_remove函数向系统的gpio子系统取消注册,platform_set_drvdata函数清空驱动的数据指针。

五·GPIO方向设置函数分析

static int bcm2708_set_function(struct gpio_chip *gc, unsigned offset, intfunction)

{struct bcm2708_gpio *gpio = container_of(gc, structbcm2708_gpio, gc);

unsignedlongflags;

unsigned gpiodir;

unsigned gpio_bank= offset / 10;//确定是哪个寄存器(每个寄存器可以配置10个gpio 每个gpio需要3位)

unsigned gpio_field_offset = (offset - 10 * gpio_bank) * 3;//在确定寄存器中的找到需要操作的三个位的偏移

printk(KERN_ERR DRIVER_NAME": bcm2708_gpio_set_function %p (%d,%d)\n", gc, offset, function);if (offset >= BCM2708_NR_GPIOS)//判断端口偏移是否大于54

return -EINVAL;

spin_lock_irqsave(&lock, flags);

gpiodir= readl(gpio->base + GPIOFSEL(gpio_bank));//gpio->base=0x3f200000 求出该端口所在的寄存器地址并读出来

gpiodir &= ~(7 << gpio_field_offset);//将该端口所对应的3个位清零(输出)

gpiodir |= function << gpio_field_offset;//根据参数设置该端口实际的输入输出状态

writel(gpiodir, gpio->base + GPIOFSEL(gpio_bank));//写寄存器

spin_unlock_irqrestore(&lock, flags);

gpiodir= readl(gpio->base +GPIOFSEL(gpio_bank));return 0;

}

根据数据手册记载,每个gpio口占用方向寄存器的3个位,也就是说每个寄存器管理32/3一共10个gpio口,所以

1.unsignedgpio_bank = offset /10;//确定是哪个寄存器(每个寄存器可以配置10个gpio 每个gpio需要3位)

2.unsignedgpio_field_offset = (offset -10* gpio_bank) *3;//在确定寄存器中的找到需要操作的三个位的偏移

设置步骤为

A.读取该寄存器的值

1.gpiodir =readl(gpio->base +GPIOFSEL(gpio_bank));//gpio->base=0x3f200000 求出该端口所在的寄存器地址并读出来

B.修改该寄存器的值

1.gpiodir &= ~(7<< gpio_field_offset);//将该端口所对应的3个位清零(输出)

2.gpiodir |= function << gpio_field_offset;//根据参数设置该端口实际的输入输出状态

C.写入寄存器

1.writel(gpiodir, gpio->base +GPIOFSEL(gpio_bank));//写寄存器

六·GPIO设置输出函数分析

static void bcm2708_gpio_set(struct gpio_chip *gc, unsigned offset, intvalue)

{struct bcm2708_gpio *gpio = container_of(gc, structbcm2708_gpio, gc);

unsigned gpio_bank= offset / 32;//确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)

unsigned gpio_field_offset = (offset - 32 * gpio_bank);//在确定寄存器中的找到需要操作的1个位的偏移//printk(KERN_ERR DRIVER_NAME ": bcm2708_gpio_set %p (%d=%d)\n", gc, offset, value);

if (offset >= BCM2708_NR_GPIOS)//判断端口偏移是否大于54

return;if(value)

writel(1 << gpio_field_offset, gpio->base + GPIOSET(gpio_bank));//直接根据偏移写入数值

elsewritel(1 << gpio_field_offset, gpio->base + GPIOCLR(gpio_bank));//直接根据偏移写入数值

}

根据数据手册记载,每个gpio口占用输出寄存器的1个位,也就是说每个寄存器管理32/1一共32个gpio口,所以

1.unsignedgpio_bank = offset /32;//确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)

2.unsignedgpio_field_offset = (offset -32* gpio_bank);//在确定寄存器中的找到需要操作的1个位的偏移

接着便可以进行写操作

1.writel(1<< gpio_field_offset, gpio->base +GPIOSET(gpio_bank));//直接根据偏移写入数值

七·GPIO读取状态函数分析

static int bcm2708_gpio_get(struct gpio_chip *gc, unsigned offset)

{struct bcm2708_gpio *gpio = container_of(gc, structbcm2708_gpio, gc);

unsigned gpio_bank= offset / 32;//确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)

unsigned gpio_field_offset = (offset - 32 * gpio_bank);//在确定寄存器中的找到需要操作的1个位的偏移

unsigned lev;if (offset >= BCM2708_NR_GPIOS)//判断端口偏移是否大于54

return 0;

lev= readl(gpio->base + GPIOLEV(gpio_bank));//直接根据偏移读取数值

printk(KERN_ERR DRIVER_NAME": bcm2708_gpio_get %p (%d)=%d\n", gc, offset, 0x1 & (lev>>gpio_field_offset));return 0x1 & (lev >> gpio_field_offset);//取所需的3位清零其他位

}

根据数据手册记载,每个gpio口占用输出寄存器的1个位,也就是说每个寄存器管理32/1一共32个gpio口,所以

1.unsignedgpio_bank = offset /32;//确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)

2.unsignedgpio_field_offset = (offset -32* gpio_bank);//在确定寄存器中的找到需要操作的1个位的偏移

接着便可以进行读操作

1.lev =readl(gpio->base +GPIOLEV(gpio_bank));//直接根据偏移读取数值

2.return0x1 & (lev >> gpio_field_offset);//取所需的3位清零其他位

8·关于几个宏定义的作用

#define GPIOFSEL(x) (0x00+(x)*4)//方向寄存器偏移地址

#define GPIOSET(x) (0x1c+(x)*4)//输出寄存器偏移地址

#define GPIOCLR(x) (0x28+(x)*4)//清零寄存器偏移地址

#define GPIOLEV(x) (0x34+(x)*4)//读取寄存器偏移地址

#define GPIOEDS(x) (0x40+(x)*4)

#define GPIOREN(x) (0x4c+(x)*4)

#define GPIOFEN(x) (0x58+(x)*4)

#define GPIOHEN(x) (0x64+(x)*4)

#define GPIOLEN(x) (0x70+(x)*4)

#define GPIOAREN(x) (0x7c+(x)*4)

#define GPIOAFEN(x) (0x88+(x)*4)

#define GPIOUD(x) (0x94+(x)*4)

#define GPIOUDCLK(x) (0x98+(x)*4)

这几个宏定义用来计算要操作的gpio的各种寄存器的地址。

9.烧录到树莓派实验一下

(烧到树莓派的时候要把内核和编译出来的modules全部都拷贝覆盖到树莓派)

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

最后给一个内核编译的脚本

#!/bin/shPWD=`pwd`

export PATH=${PWD}/../tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin:$PATH

#make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-bcm2709_defconfigmake ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- || exit 1

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules || exit 1

rm -rf _install*

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install INSTALL_PATH=$PWD/_installmake ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install INSTALL_MOD_PATH=$PWD/_installcp arch/arm/boot/Image _install/

cp ../RaspberryKernel/script/install.sh _install/

tar -czf _install.tar.gz _install/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值