Linux设备树系列-GPIO驱动实践

简介: Linux设备树系列-GPIO驱动实践

1. 前言


GPIO驱动开发可能算是Linux内核设备驱动开发中最为简单、最常见的一个方向,对于开发板的按键、LED、蜂鸣器、电源控制等模块,可能都是使用GPIO实现的。Linux内核的GPIO子系统在内核不断的演进过程中进行了多次的重构,本文的第二章所举的案例依照大家比较熟悉的GPIO开发模式展开,第3章会介绍GPIO架构最新的编程模式(基于4.18内核)。Linux内核GPIO子系统与pinctrl子系统存在的很大的关系,关于pinctrl子系统可以参见文章,关于GPIO子系统的讨论在后续文章中会总结。下面讨论GPIO基本的编程模式。


2. GPIO编程模式


本章所介绍的GPIO驱动demo基于Linux kernel 3.10版本,开发板为OKMX6UL-C2。该demo的功能十分的简单,其主要完成的功能就是通过DTS读取GPIO的配置信息,然后根据配置信息配置每个GPIO端口。之后,应用程序可以通过ioctl控制GPIO的具体行为(IO状态读取、IO状态输出等)。下面首先介绍demo的驱动实现。


2.1 编程接口


Linux内核关于GPIO的控制一般通过GPIOLIB框架,内核配置时需要启用该选项** CONFIG_GPIOLIB**。下面所有接口都是基于GPIOLIB实现的。


本demo中所涉及的GPIO编程接口如下:


(linux/asm-generic/gpio.h):
extern int  gpio_request(unsigned gpio, const char *label);
extern void gpio_free(unsigned gpio);
extern int  gpio_direction_input(unsigned gpio);
extern int  gpio_direction_output(unsigned gpio, int value);
extern int  gpio_set_debounce(unsigned gpio, unsigned debounce);
extern int  gpio_get_value_cansleep(unsigned gpio);
extern void gpio_set_value_cansleep(unsigned gpio, int value);
(linux/gpio.h)
static inline int gpio_get_value(unsigned int gpio);
static inline void gpio_set_value(unsigned int gpio, int value);


2.2 DTS配置


Demo通过DTS中关于GPIO的配置信息完成GPIO各个端口的初始化工作,下面举一个DTS配置例子(关于DTS基本语法可以参考文章):


gpios {
        compatible = "gpio-user";                                                                                                                                                                                   
        status = "okay";
        /*input*/
        gpio0 {
            label = "in0";
            gpios = <&gpio1 1 0>;
            default-direction = "in";
       };
    ... ...
     /*output*/
     gpio17 {
            label = "out1";
            gpios = <&gpio3 3 0>;
            default-direction = "out";
        };
  };


上面表示系统配置了两个GPIO,一个作为输入gpio,一个作为输出gpio,其中:


  1. label:标识该GPIO端口;


  1. gpios属性的定义格式一般依赖于GPIO controler的#gpio-cells属性字段。对于本开发板来说gpios的格式为:


,bank_num为GPIO的bank编号,offset为bank内的gpio编号偏移,defalut_value表示gpio输出状态下的默认值; 3. default-direction属性表示gpio为输入还是输出模式,"in"表示输入模式,"out"表示输出模式;

##2.3 GPIO驱动程序 该dmeo基于内核的misc设备开发框架实现,而后通过以platform_driver的形式注册到系统中。platform_driver的数据结构如下:


static struct platform_driver gpio_user_driver = {
  .probe = gpio_user_probe,
  .remove = gpio_user_remove,
  .driver = {
    .owner = THIS_MODULE,
    .name = "gpio-user",
    .of_match_table = of_gpio_user_id_table,
  },
};


其中,of_gpio_user_id_table定义了该驱动的设备兼容性,其与2.2节中的compatible = "gpio-user","gpio-user"字段相对应。下面是of_gpio_user_id_table的定义:


static const struct of_device_id of_gpio_user_id_table[] = {
  { .compatible = "gpio-user",},
  {},
};


如果DTS中所定义的设备与驱动匹配上的话,那么gpio_user_probe将会被执行,下面着重分析一下该函数的实现方式。在讲解之前需要分析一下驱动定义的私有数据结构,其定义如下:


struct gpio_user_data{
  const char *label;//DTS中的label字段
  bool input;//是否为输入模式
  unsigned gpio;//gpio编号
  unsigned dft;//gpio输出模式下的默认输出值
};
static struct gpio_misc{
  struct miscdevice misc;//misc设备模型
  struct gpio_user_data *data;//gpio配置数据
  int gpio_count;//gpio配置数据个数
} *gpio_misc;


gpio_user_probe的实现如下:


static int gpio_user_probe(struct platform_device *pdev)
{
  int index;
  struct device_node *node = pdev->dev.of_node, *child;
  gpio_misc = devm_kzalloc(&pdev->dev,sizeof(*gpio_misc),GFP_KERNEL);------------------>(1)
  if(!gpio_misc){
    return -ENOMEM;
  }
  gpio_misc->gpio_count = of_get_available_child_count(node);------------------>(2)
  if(!gpio_misc->gpio_count){
    return -ENODEV;
  }
  if(gpio_misc->gpio_count > MAX_GPIO_NR){
    gpio_misc->gpio_count = MAX_GPIO_NR;
  }
  gpio_misc->data = devm_kzalloc(&pdev->dev,sizeof(struct gpio_user_data) * gpio_misc->gpio_count,GFP_KERNEL);
  if(!gpio_misc->data){
    return -ENOMEM;
  }
  index = 0;
  for_each_available_child_of_node(node,child){------------------>(3)
      const char *input;
      struct gpio_user_data *data = &gpio_misc->data[index++];    
      data->label = of_get_property(child,"label",NULL) ? : child->name;
      input = of_get_property(child,"default-direction",NULL) ? : "in";
      if(strcmp(input,"in") == 0)
        data->input = true;
      data->gpio = of_get_gpio_flags(child,0,&data->dft);
  }
  gpio_user_init_default();------------------>(4)
  gpio_misc->misc.name = "gpio";------------------>(5)
  gpio_misc->misc.minor = MISC_DYNAMIC_MINOR;
  gpio_misc->misc.fops = &gpio_user_fops;
  return misc_register(&gpio_misc->misc);
}


下面分步骤对其进行解析:


  • (1)使用具有内存回收功能的devm_kzalloc为gpio_misc分配内存空间。


  • (2)通过of_get_available_child_count获取DTS的gpio子节点的个数。判断该值的有效性,然后根据gpio子节点个数创建gpio_misc->data数组。


  • (3)使用for_each_available_child_of_node变脸DTS中gpio子节点,初始化gpio_misc->data数组。


  • (4)初始化gpio配置信息,下面会详细介绍gpio_user_init_default函数。


  • (5)初始化misc设备信息,注册gpio_misc->misc设备到系统中。


gpio_user_init_default函数实现方式如下:


static void gpio_user_init_default(void)
{
    int i,ret;
    struct gpio_user_data *data;
    data = gpio_misc->data;
    for(i = 0;i < gpio_misc->gpio_count;i++) {
        if(!gpio_is_valid(data[i].gpio)) {
            continue;
        }
        ret = gpio_request(data[i].gpio,data[i].label);------------------>(1)
        if(ret < 0) {
            continue;
        }
        if(data[i].input) {
            gpio_direction_input(data[i].gpio);---------------------->(2)
        }
        else {
          gpio_direction_output(data[i].gpio,data[i].dft);
        }
    }
}


下面简单的介绍一下实现步骤:


  • (1)检测gpio_num的有效性,并通过gpio_request向系统申请gpio的使用权(如果申请成功,该gpio会被标记为已占用,并且GPIO的功能属性为GPIO);


  • (2)配置GPIO的输入、输出模式。


通过上面一系列的初始化工作,DTS中配置的gpio基本都配置完成了。下面分析一下ioctl接口,讲解一下如何控制GPIO的行为。


#define GPIO_U_IOCTL_BASE 'x'
#define GPIOC_OPS _IOWR(GPIO_U_IOCTL_BASE,0,int)
static const struct file_operations gpio_user_fops = {
  .owner = THIS_MODULE,
  .open = gpio_user_open,
  .release = gpio_user_release,
  .unlocked_ioctl = gpio_user_ioctl,
};
static long gpio_user_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{
    int no, offset;
    unsigned long val;
    unsigned long __user *p = (void __user *)arg;
    struct gpio_user_data *data;
    unsigned long get_value;
    if(!gpio_misc)
        return -ENODEV;
    data = gpio_misc->data;
    if(_IOC_TYPE(cmd) != GPIO_U_IOCTL_BASE)
        return -EINVAL;
    switch(_IOC_NR(cmd)) {
      case 0:
      if(get_user(val,p))
      return -EFAULT;
      no = val & (~(1u << 31));------------------>(1)
      if(data[no].input) {
        get_value = gpio_get_value(data[no].gpio);------------------>(2)
        printk("get_value is %d\n", get_value);
        offset = data[no].gpio % 32;
        val = get_value >> offset;
        printk("val is %d\n",val);
        put_user(val,p);
      } else {
        gpio_set_value(data[no].gpio,val >> 31);------------------>(3)
      }
      break;
      default:
        return -ENOTTY;
    }
    return 0;
}


首先,通过_IOWR定义了一个ioctl操作命令:GPIOC_OPS,关于_IOWR的具体使用方式可以参考文章


下面简单的介绍一下实现步骤:


  • (1)检测ioctl控制命令有效性,获取gpio编号;


  • (2)如果为input模式下的gpio,读取GPIO当前value,并将该状态返回给用户程序;


  • (3)如果为output模式下的gpio, 设置GPIO输出value;


上面既是GPIO驱动端的代码实现,完整的代码可以在这里下载。


2.3 GPIO测试程序


GPIO的测试程序通过设备文件(/dev/gpio)完成GPIO的输出状态配置和GPIO输入状态的读取功能。代码可以在这里下载。


3. GPIO子系统的变化


关于GPIO子系统的变化,参考后续的文章,未完待续... ...


相关文章
|
10天前
|
Ubuntu Linux vr&ar
IM跨平台技术学习(十二):万字长文详解QQ Linux端实时音视频背后的跨平台实践
本文详细记录了新版QQ音视频通话在 Linux 平台适配开发过程中的技术方案与实现细节,希望能帮助大家理解在 Linux 平台从 0 到 1 实现音视频通话能力的过程。
32 2
|
10天前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
13天前
|
数据可视化 安全 Linux
探索Linux命令repo-graph:深入解析与应用实践
`repo-graph`是Linux的Yum-utils工具,用于可视化仓库中软件包的依赖关系,简化复杂网络管理。它通过分析元数据生成图形,支持自定义输出格式和特定包分析。例如,`repo-graph --repoid=updates`显示更新仓库的依赖,而`--packages=httpd`则专注httpd包。注意权限、复杂性和选择合适输出格式。定期分析和图形化展示是最佳实践。
|
19天前
|
Linux 芯片
一篇文章讲明白Linux下控制GPIO的三种方法
一篇文章讲明白Linux下控制GPIO的三种方法
20 3
|
22小时前
|
监控 安全 Linux
Linux命令ssltap的深入解析与应用实践
`ssltap`是一个假想的Linux命令,用于模拟SSL/TLS流量分析。它捕获、解密(如果有密钥)并分析加密流量,提供实时监控、协议解析和安全审计。特点包括实时性、灵活性、可扩展性和安全性。示例用法包括捕获特定端口流量和实时监控会话状态。在实际操作中应注意私钥安全、性能影响及合规性,建议定期审计和自动化监控。
|
28天前
|
Linux
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
|
28天前
|
Linux 程序员 芯片
【Linux驱动】普通字符设备驱动程序框架
【Linux驱动】普通字符设备驱动程序框架
|
10天前
|
Linux 开发者
Linux底层驱动社区饮水机系统详解
在Linux驱动开发中,入门时通常会关注驱动程序的三大核心步骤:入口函数、出口函数和声明许可证。这些步骤构成了驱动程序的基本结构,是驱动与内核交互的基础。下面是对这三个步骤的简要说明:
|
28天前
|
Linux
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
|
1月前
|
缓存 Linux Shell
Linux 内存管理与 Swap 空间扩展实践
该文介绍了Linux系统中`free`命令的使用,解析了其输出信息,包括物理内存(总内存、已用、空闲、缓存)和交换空间(总大小、使用和空闲)。Linux优先使用物理内存作缓存,当内存紧张时使用Swap空间。文章还提供了扩展Swap空间的步骤,并强调适度Swap使用对性能的影响,建议合理平衡物理内存和Swap的比例。