linux驱动开发(一)—GPIO驱动框架
前言
GPIO驱动是Linux驱动开发中最基础、但却是很常用、很重要的驱动。比如你要点亮一个LED灯、键盘扫描、输出高低电平等等。而Linux内核的强大之处在于对最底层的GPIO硬件操作层的基础上封装了一些统一的GPIO操作接口,也就是所谓的GPIO驱动框架。这样开发人员可以调用这些接口去操作设备的IO口,不需要担心硬件平台的不同导致IO口的不同。
今天,我主要讲的就是如何使用Linux内核封装好的GPIO接口函数在驱动开发中需要操作GPIO时候的使用。
文章部分内容参考他人博客,特此声明!
概述
GPIO是与硬件体系密切相关的,linux提供一个模型来让驱动统一处理GPIO,即各个板卡都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq...然后把控制模块注册到内核中,这时会改变全局gpio数组:gpio_desc[]. 当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。gpio实现为一组可用的 gpio_chip, 由驱动传入对应 gpio的全局序号去 request, dataout ,datain, free. 这时会调用gpio_chip中具体的实现。
GPIO是一组可控件的脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能。数据状态,输入输出方向,清零,中断(哪个边沿触发), 一般是一组(bank)一组的。寄存器读写函数: __raw_writel() __raw_writeb() __raw_readl() __raw_readb()。
1. Linux内核中GPIO模型的结构
1.1 struct gpio_desc
//表示一个gpio口,含对应的gpio_chip.
//对于每一个gpio,都有一个gpio描述符,这个描述符包含了这个gpio所属的控制器即chip和一些标志,label等。
struct gpio_desc {
struct gpio_chip *chip;
unsigned long flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_EXPORT 2 /* protected by sysfs_lock */
#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
#define FLAG_TRIG_FALL 4 /* trigger on falling edge */
#define FLAG_TRIG_RISE 5 /* trigger on rising edge */
#define FLAG_ACTIVE_LOW 6 /* value has active low */
#define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */
#define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */
#define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */
#define ID_SHIFT 16 /* add new flags before this one */
#define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1)
#define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE))
#ifdef CONFIG_DEBUG_FS
const char *label;
#endif
};
//采用了一个具有ARCH_NR_GPIOS大小的gpio描述符数组。这个描述符数组便代表了系统所有的gpio。
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
1.2 struct davinci_gpio_controller
//一组GPIO控制器结构,例如GPIO0和GPIO1是一组(共32个GPIO口),共用一组寄存器,所以GPIO0和GPIO1荷载一起用chips[0]来控制
//假如有144个GPIO,分为4组(GPIO0~GPIO8),每组有2个banks(即GPIO0和GPIO1为1组),每组最多可以有32个GPIO,每组的控制寄存器空间有10个
struct davinci_gpio_controller {
struct gpio_chip chip;//每组对应的gpio_chip
int irq_base;//每组对应的中断
spinlock_t lock;//自旋锁
void __iomem *regs;//每组的寄存器地址
void __iomem *set_data;//设置数据寄存器地址
void __iomem *clr_data;//清除数据寄存器地址
void __iomem *in_data;//输入数据寄存器地址
};
1.3 struct gpio_chip
//每一个davinci_gpio_controller结构都对应于一个gpio_chip结构,gpio_chip既可看成是davinci_gpio_controller结构的补充
//表示一个gpio controller.通过这个结构抽象化所有的GPIO源,而让板上其它的模块可以用相同的接口调用使用这些GPIO。
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip,unsigned offset);//请求gpio
void *free)(struct gpio_chip *chip,unsigned offset);//释放gpio
int (*get_direction)(struct gpio_chip *chip,unsigned offset);
int (*direction_input)(struct gpio_chip *chip,unsigned offset);//配置gpio为输入,返回当前gpio状态
int (*get)(struct gpio_chip *chip,unsigned offset);//获取gpio的状态
int (*direction_output)(struct gpio_chip *chip,unsigned offset, int value);//配置gpio为输出,并设置为value
int (*set_debounce)(struct gpio_chip *chip,unsigned offset, unsigned debounce);//设置消抖动时间,尤其是gpio按键时有用
void (*set)(struct gpio_chip *chip,unsigned offset, int value);//设置gpio为value值
int (*to_irq)(struct gpio_chip *chip,unsigned offset);//把gpio号转换为中断号
void (*dbg_show)(struct seq_file *s,struct gpio_chip *chip);
int base;// 这个gpio控制器的gpio开始编号
u16 ngpio;//这个gpio控制器说控制的gpio数
const char *const *names;
unsigned can_sleep:1;
unsigned exported:1;
#if defined(CONFIG_OF_GPIO)
struct device_node *of_node;
int of_gpio_n_cells;
int (*of_xlate)(struct gpio_chip *gc,const struct of_phandle_args *gpiospec, u32 *flags);
#endif
#ifdef CONFIG_PINCTRL
struct list_head pin_ranges;
#endif
};
1.4 struct davinci_gpio_regs
//GPIO寄存器结构
struct davinci_gpio_regs {
u32 dir; // gpio方向设置寄存器
u32 out_data; // gpio设置为输出时,表示输出状态(0或1)
u32 set_data; // gpio设置为输出时,用于输出高电平
u32 clr_data; // gpio设置为输出时,用于输出低电平
u32 in_data; // gpio设置为输入时,用于读取输入值
u32 set_rising; // gpio中断上升沿触发设置
u32 clr_rising; // gpio中断上升沿触发清除
u32 set_falling; // gpio中断下降沿触发设置
u32 clr_falling; // gpio中断下降沿触发清除
u32 intstat; // gpio中断状态位,由硬件设置,可读取,写1时清除。
};
1.5 struct gpio
struct gpio {
unsigned gpio;//gpio号
unsigned long flags;//gpio标志
const char *label;//gpio名
};
2. 驱动开发中GPIO初始化操作
在实际的驱动开发中,根据板级资源和CPU手册,GPIO初始化一般需要以下三个步骤:
1.设置IO口的复用模式,如果某个IO当作GPIO使用,那么就需要根据CPU手册去配置iomux(IO复用寄存器)为GPIO模式;
2.设置IO口的输入输出方向,根据实际开发需求,将相应的GPIO配置为相应的输入输出方向;
3.GPIO初始化赋值(输出高低电平)、拉高拉低操作;
2.1 GPIO申请
#########################################
#description:申请一个GPIO资源
#unsigned gpio:要申请的GPIO管脚号,为一个正整数
# const char *label:为申请的GPIO管脚取个名字
#########################################
int gpio_request(unsigned gpio, const char *label);
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
2.2 GPIO输入输出设置
#########################################
#description:设置某个GPIO的输入输出方向
#unsigned gpio:要设置的GPIO管脚号,为一个正整数
# int value:设置的值
#########################################
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
2.3 获取GPIO管脚的值和设置GPIO管脚的值
#########################################
#description:获取、设置某个GPIO的值
#unsigned gpio:要获取、设置的GPIO管脚号
# int value:设置的值
#########################################
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
2.4 GPIO当作中断口使用
#########################################
#description:设置某个GPIO为中断口
#unsigned gpio:要设置中断的GPIO管脚号
#########################################
int gpio_to_irq(unsigned gpio);
返回的值即中断编号可以传给request_irq()和free_irq(),内核通过调用该函数将gpio端口转换为中断,在用户空间也有类似方法。
3. GPIO驱动实例
3.1 以下GPIO驱动例子为矩阵键盘中对GPIO的操作
//设置某个管脚为输入
int set_key_input(unsigned int gpio)
{
char name[32];
sprintf(name, "GPIO%d", gpio);
if(gpio_request(gpio,NULL) != 0)
{
printk("gpio request error!\n");
return -1;
}
gpio_direction_input(gpio);
gpio_free(gpio);
return 0;
}
//设置某个管脚为输出
int set_key_output(unsigned int gpio,int value)
{
char name[32];
sprintf(name, "GPIO%d", gpio);
if(gpio_request(gpio,NULL) != 0)
{
printk("gpio request error!\n");
return -1;
}
gpio_direction_output(gpio,value);
gpio_free(gpio);
return 0;
}
//获取某个GPIO管脚的值
int get_key_value(unsigned int gpio)
{
int value= -1;
if (gpio_request(gpio, NULL) != 0)
{
printk("get_key_value err\n");
return -1;
}
value = gpio_get_value(gpio);
gpio_free(gpio);
return value;
}
//设置某个GPIO输出为低电平
int set_key_low(unsigned int gpio)
{
if (gpio_request(gpio, NULL) != 0) {
//printk("set_key_low request err\n");
return -1;
}
gpio_direction_output(gpio, 0);
__gpio_set_value(gpio, 0);
gpio_free(gpio);
return 0;
}
//拉高、拉低某个GPIO
//GPIO的拉高拉低操作内核没有提供通用的接口函数,这个需要驱动开发人员根据CPU手册的寄存器配置去封装拉高拉低函数,以下给出一个伪代码的例子:
//假设拉高GPIO1_IO01这个IO:
<span style="font-size:12px;">#define SET_PULL_UP 0x01
#define SET_PULL_DOWN 0x00
#define REG_GPIO_BASE 0x8e000000
#define GPIO1_IO01_OFFSET 0x400
int set_pull_up(unsigned int reg_base,unsigned int offset,int up)
{
unsigned int gpio_base;
unsigned int gpio;
gpio_base = ioreamap(reg_base,SIZE_4K);//调用ioreamap映射GPIO空间到内存,映射大小根据实际需求而定
gpio = gpio_base + offset;
gpio |= up; //将某个GPIO拉高,根据具体的寄存器操作而定
__raw_writel(gpio,gpio_base + offset);
return 0;
}
set_pull_up(REG_GPIO_BASE,GPIO1_IO01_OFFSET,SET_PULL_UP);</span>
//拉低某个GPIO
#define SET_PULL_UP 0x01
#define SET_PULL_DOWN 0x00
#define REG_GPIO_BASE 0x8e000000
#define GPIO1_IO01_OFFSET 0x400
int set_pull_down(unsigned int reg_base,unsigned int offset,int down)
{
unsigned int gpio_base;
unsigned int gpio;
gpio_base = ioreamap(reg_base,SIZE_4K);//调用ioreamap映射GPIO空间到内存,映射大小根据实际需求而定
gpio = gpio_base + offset;
gpio &= down; //将某个GPIO拉低,根据具体的寄存器操作而定
__raw_writel(gpio,gpio_base + offset);
return 0;
}
set_pull_up(REG_GPIO_BASE,GPIO1_IO01_OFFSET,SET_PULL_DOWN);
3.2 总结
3.1中展示了基本的GPIO操作函数的编写,在实际的驱动开发中,比如对某个连接到CPU的GPIO管脚的外设模块需要初始化的时候,一般都是调用GPIO接口函数进行输入输出、拉高、拉低设置,读者可以参考3.1的例子根据实际开发需求进行修改。
linux驱动开发(一)—GPIO驱动框架相关推荐
- Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)
文章目录 全系列传送门 1. 在/arch/arm/boot/dts/imx6q-pinfunc.h查找 2. 在设备树配置文件中添加设备节点定义以及其引脚定义 3. 修改设备树文件添加配置 4. d ...
- LINUX驱动开发(二)GPIO驱动框架
1. 驱动框架 pinctrl子系统+gpio子系统+设备树+platform总线. pinctrl子系统重点在设置引脚复用,gpio子系统用于初始化引脚. Linxu提供总线-设备-驱动模型,用于将 ...
- Linux驱动开发—内核I2C驱动详解
Linux驱动开发--内核I2C驱动 I2C驱动文件结构 I2C数据传输过程 i2c_transfer i2c_msg I2C通讯常用的接口函数(老版本) 快速读写接口函数:(连续读写) 常用的读操作 ...
- <Linux开发>驱动开发 -之-platform 驱动
<Linux开发>驱动开发 -之-platform 驱动 交叉编译环境搭建: <Linux开发> linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下: < ...
- <Linux开发>--驱动开发-- 字符设备驱动(3) 过程详细记录
<Linux开发>–驱动开发-- 字符设备驱动(3) 过程详细记录 驱动开发是建立再系统之上的,前面作者也记录了系统移植的过程记录,如果有兴趣,可进入博主的主页查看相关文章,这里就不添加链 ...
- Linux驱动开发1:驱动开发与裸机开发的区别
Linux驱动开发1:驱动开发与裸机开发的区别 1.裸机驱动开发回顾: 裸机驱动开发是非常底层的,跟寄存器打交道,有些MCU为了方便我们开发,提供了一些库,让我们通过调用API函数来间接的实现利用寄存 ...
- Linux 设备驱动开发 —— platform设备驱动应用实例解析
前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 -- platform 设备驱动 ,下面将通过一个实例来深入我们的学习. 一.platform 驱动的工作过程 platfor ...
- STM32MP157驱动开发——platform设备驱动(中)
STM32MP157驱动开发--platform设备驱动(中) 0.前言 一.platform设备模块--设备信息解析 二.platform驱动模块--加载设备 三.测试App 四.编译及运行 相关文 ...
- STM32MP157驱动开发——蜂鸣器设备驱动
STM32MP157驱动开发--蜂鸣器设备驱动 0.相关知识 一.驱动程序开发 1.设备树修改 2.启动程序编写 3.测试程序编写 二.编译及运行测试 0.相关知识 蜂鸣器常用于计算机.打印机.报 ...
- STM32MP157驱动开发——USB设备驱动
STM32MP157驱动开发--USB设备驱动 一.简介 1.电气属性 2.USB OTG 3.STM32MP1 USB 接口简介 4.Type-C 电气属性 二.USB HOST 驱动开发 1.US ...
最新文章
- 如何自学php框架,如何学习php框架
- 【CLR】解析AppDomain
- Laravel学习笔记之冒泡、快速、选择和插入排序(持续更新)
- Google的21道面试问题
- 74HC595在【8x8LED点阵】中的运用
- 不恢复余数除法原理_小学数学基础概念大全,家长收藏起来,一条一条讲给孩子听。能全背下来的,考试也不带怕的了!...
- Prince和学生们侃侃而谈系列06
- 2010提升你幽默感的经典短句
- springboot搭建redis时提示RedisCommandExecutionException: CLUSTERDOWN Hash slot not served解决办法
- 电源管理芯片的电路结构与分析
- 基于主轴变换的医学图像倾斜校正
- 【聊聊Java】Java中HashMap常见问题 -- 扩容、树化、死链问题
- 爬虫:Robots协议
- 正点原子嵌入式linux视频教程,正点原子嵌入式开发完整全套视频教程
- “熊猫烧香”李俊的病毒人生
- 折腾日记:树莓派如何硬盘、u盘启动 (usb boot)
- 基于全志A33开发板linux系统移植学习记录(Boot0)
- Zabbix6.0新功能Geomap 地图标记 你会用吗?
- android 星星流逝动画,Android仿开心消消乐大树星星无限循环效果
- 一个幸运的本命年,2015记事
热门文章
- 我的2017云栖之行
- android九宫格菜单,Android实现九宫格 主界面应用列表效果
- The 2019 ICPC Asia-East Continent Final A-City
- CLOCK_MONOTONIC与CLOCK_REALTIME
- python(10.30)
- html给手机号发送验证码,js如何实现手机发送验证码功能
- android中 拍照 视频 录音 显示缩略图
- 让你成功安装vscode中go的相关插件
- JMeter -Maven包引用问题的解决方法
- “sample”, “batch”, “epoch” 分别是什么?