fannifu 发表于 2022-12-30 22:59:11

【Linux开发】SemiDrive X9H GPIO 驱动学习篇

【Linux开发】SemiDrive X9H GPIO 驱动学习篇



一、概述

SemiDrive X9H 拥有不同的 domain 域,例如 AP,Safety,Secure 等等。对于 GPIO 资源,不同 domain 之间 gpio 控制器是不同的,本次主要是使用的 AP 使用的是 gpio4 控制器。除了GPIO 控制器在不同 domain 之间不同以外,pin 也是有各自不同的 domain 的,但是当它作为 gpio 使用时,可以通过设置挂靠到对应域下的 gpio 控制器上来在对应 domain 下使用。具体 pin 的 gpio 控制器的设置是通过 PC 工具 SDConfigTool 来进行。

本文主要是讲述将 pin 作为 GPIO 使用,之后编写字符设备驱动,测试。

二、适用环境

硬件:SemiDrive SD003_X9H REF_A03 DEMO Board
软件:X9 PTG3.9


三、设备树匹配

1、复用管脚为 GPIO

路径:
/buildsystem/yocto/source/linux/arch/arm64/boot/dts/semidrive/x9_high_ref_native_serdes_nobt.dts


在上面 dts 文件的 &pinctrl 的 sdx9-evk 中添加管脚复用,主要是将 GPIO_C1 管脚复用成 GPIO。

pinctrl_gpio_learning: gpiogrp_learning {

                     kunlun,pins = <

X9_PINCTRL_GPIO_C1__GPIO_MUX2_IO1_1          0x00

                        >;

               };



2、配置一个新节点

路径:
/buildsystem/yocto/source/linux/arch/arm64/boot/dts/semidrive/x9_high_ref_native_serdes_nobt.dts

在上面 dts 文件里面的根节点 / 下新建一个节点,添加属性,获取 gpio 编号。

gpio_learning {
      #address-cells = <1>;
      #size-cells = <1>;
      compatible = "gpioled_learning";
      pinctrl-0 = <&pinctrl_gpio_learning>;
      gpio = <&port4b 17 GPIO_ACTIVE_HIGH>;
      status = "okay";
    };


四、驱动编写

1、模块出/入口函数

其中 module_init 是驱动入口函数,主要进行 platform 平台驱动注册,module_exit 是驱动出口函数,主要是进行 platform 平台驱动注销;

其中 MODULE_LICENSE 主要是声明模块许可证,一般为 GPL。

/*设备入口函数*/

static int __init gpio_learning_init(void)
{
    return platform_driver_register(&gpio_learning_driver);
}

/*设备出口函数*/

static void __exit gpio_learning_exit(void)
{
   platform_driver_unregister(&gpio_learning_driver);
}

/*指定上面的入口和出口函数*/

module_init(gpio_learning_init);
module_exit(gpio_learning_exit);

MODULE_LICENSE("GPL"); //LICENSE 采用 GPL 协议


2、platform 平台驱动结构体

主要是通过 match 函数和对应的设备树里面节点匹配,只要 compatible 属性匹配成功即可,匹配成功就执行 probe 函数。

/*匹配列表*/

static const struct of_device_id gpio_learning_of_match[] = {
    { .compatible = "gpioled_learning" },
    { /*sentinel*/}
};

/*
* platform 平台驱动结构体
*/

static struct platform_driver gpio_learning_driver ={
    .driver = {
      .name = "gpio_learning_device",
      .of_match_table = gpio_learning_of_match,
    },

    .probe = gpio_learning_probe,

    .remove = gpio_learning_remove,

};



3、probe 函数

主要是注册字符设备,通过获取设备树的节点,获取 gpio 属性,从而获得 gpio编号,之后请求使用该 gpio,设置 gpio 模式。

/*
* platform 驱动的 probe 函数
* 驱动与设备匹配成功以后此函数就会执行
*/

static int gpio_learning_probe(struct platform_device *dev)
{

    printk("led driver and device was matched!\r\n");

    /* 1、设置设备号 */

    if (gpiodev.major) { //如果定义了主设备号

    gpiodev.devid = MKDEV(gpiodev.major, 0);//次设备号号默认 0,构建设备号

    register_chrdev_region(gpiodev.devid, GPIODEV_CNT,GPIODEV_NAME);//注册设备号

    } else {
    alloc_chrdev_region(&gpiodev.devid, 0, GPIODEV_CNT,GPIODEV_NAME);//动态申请设备号

    gpiodev.major = MAJOR(gpiodev.devid);//获取主设备号

    }


    /* 2、注册设备 */

cdev_init(&gpiodev.cdev,&gpio_learning_fops);

    cdev_add(&gpiodev.cdev, gpiodev.devid, GPIODEV_CNT);



    /* 3、创建类 */

    gpiodev.class = class_create(THIS_MODULE, GPIODEV_NAME);

    if (IS_ERR(gpiodev.class)) {

    return PTR_ERR(gpiodev.class);

    }

    /* 4、创建设备 */

    gpiodev.device = device_create(gpiodev.class, NULL, gpiodev.devid,NULL, GPIODEV_NAME);

    if (IS_ERR(gpiodev.device)) {

    return PTR_ERR(gpiodev.device);

    }



    /* 5、初始化 IO */

    gpiodev.node = of_find_node_by_path("/gpio_learning");

    if (gpiodev.node == NULL){

    printk("gpio_learning node nost find!\r\n");

    return -EINVAL;

    }


    gpiodev.led0 = of_get_named_gpio(gpiodev.node, "gpio", 0);/*获取要使用的 GPIO 编号 */

    if (gpiodev.led0 < 0) {

    printk("can't get gpio\r\n");

    return -EINVAL;

    }

    printk("led0 = %d\r\n",gpiodev.led0);

    int ret = gpio_request(gpiodev.led0, "led0");/*申请gpio管脚 */

    if(ret == 0)

    {
       printk("gpio_request success\r\n");
    }

    else

    {

      printk("gpio_request fail\r\n");

      return -1;

    }

    ret = gpio_direction_output(gpiodev.led0, 1); /*设置为输出,默认高电平 */

    if(ret == 0)

    {

      printk("gpio_direction_output success\r\n");

    }

    else
    {

      printk("gpio_direction_output fail\r\n");
      return -1;

    }

    return 0;

}



4、设备函数操作结构体

主要是一个 open 和 write 的函数。

/*
* 设备操作函数结构体
*/

static struct file_operations gpio_learning_fops = {
    .owner = THIS_MODULE,
    .open = gpio_learning_open,
    .write = gpio_learning_write,
};


5、设备操作 open 和 write函数

主要 open 函数是将 private_data 指向设备结构体。

主要 write 函数是接受用户层传来的数据,之后根据数据设置 gpio 操作。

#define GPIODEV_CNT 1 /* 设备号长度 */

#define GPIODEV_NAME "gpio_learning" /* 设备名字 */

#define GPIOOFF 0

#define GPIOON 1

//设备结构体

struct gpiodev_dev{

    dev_t devid; /* 设备号 */

    struct cdev cdev; /* cdev */

    struct class *class; /* 类 */

    struct device *device; /* 设备 */

    int major; /* 主设备号 */

    struct device_node *node; /*设备节点 */

    int led0; /* LED 灯 GPIO 标号 */

};

struct gpiodev_dev gpiodev;

/*
* @description : LED 打开/关闭
* @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
* @return : 无
*/

void led0_switch(u8 sta)
{
    if (sta == GPIOON )
    {
      gpio_set_value(gpiodev.led0, 1);
      printk("gpio_set_value = 1!\r\n");
    }

    else if (sta == GPIOOFF)
    {
      gpio_set_value(gpiodev.led0, 0);
      printk("gpio_set_value = 0!\r\n");
    }

    return 0;
}

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量

* 一般在 open 的时候将 private_data 指向设备结构体。

* @return : 0 成功;其他 失败

*/

static int gpio_learning_open(struct inode *inode,struct file *filp)
{
    filp->private_data = &gpiodev; /* 设置私有数据 */
    return 0;
}



static ssize_t gpio_learning_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt)
{
    int retvalue;
    unsigned char databuf;
    unsigned char ledstat;
    retvalue = copy_from_user(databuf, buf, cnt);

    if(retvalue < 0) {
         printk("kernel write failed!\r\n");
         return -EFAULT;
    }

    printk("retvalue = %d\r\n",retvalue);
    ledstat = databuf;

    if (ledstat == GPIOON) {

    printk("ledstat = 1\r\n");
    led0_switch(GPIOON);

    } else if (ledstat == GPIOOFF) {

    printk("ledstat = 0\r\n");
    led0_switch(GPIOOFF);

    }
    return 0;
}



五、Kernel 配置

1、Makefile 文件

obj-$(CONFIG_GPIO_LEARNING)   += gpio_learning.o


2、Kconfig 文件

config GPIO_LEARNING

    tristate "GPIO learning block support"
    default m
    help
      This is enable gpio learning test


3、引用

在自己编写文件的上一级目录 Makefile 和 Kconfig 添加对应引用,并在对应 deconfig 配置。

Makefile 文件引用

obj-$(CONFIG_GPIO_LEARNING)       += gpio_learning/

Kconfig 文件引用

source "drivers/gpio_learning/Kconfig"

deconfig 文件配置



路径:

/buildsystem/yocto/source/linux/arch/arm64/configs/x9_ref_linux_defconfig

等于 m 表示编译成 ko 文件,等于 y 表示编译进内核。

CONFIG_GPIO_LEARNING=m



六、APP 测试

主要是传入三个参数,一个是运行的 app,一个是对应的 /dev/xxx,一个是对 gpio 的操作(1/0),通过 /dev/xxx 打开对应驱动文件,通过 write 发送对应的操作。

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

int main(int argc, char *argv[])

{

   int fd, retvalue;

   char *filename;

   unsigned char databuf;

   if(argc != 3){ //传入三个参数:运行的app /dev/xxx 操作

   printf("Error Usage!\r\n");

   return -1;

   }



   filename = argv;

   /* 打开 /dev/xxx 驱动文件 */

   fd = open(filename, O_RDWR);

   if(fd < 0){

   printf("file %s open failed!\r\n", argv);

   return -1;

   }



   databuf = atoi(argv); /* 要执行的操作:打开或关闭 */

   retvalue = write(fd, databuf, sizeof(databuf));

   if(retvalue < 0){

   printf("LED Control Failed!\r\n");

   close(fd);

   return -1;

   }


   retvalue = close(fd); /* 关闭文件 */

   if(retvalue < 0){

   printf("file %s close failed!\r\n", argv);

   return -1;

   }

   return 0;

}


以上完成了 SemiDrive X9H GPIO 功能的实现。

接下来将会更新更多关于 SemiDrive X9H 的开发博文,如有相关技术问题,可在评论区留言。



七、参考文档

《 SemiDrive_9_Series_GPIO使用手册_Rev01.00.pdf 》

《 X9H处理器数据手册_Rev04.00.pdf 》

《 SD003_X9H_REF_A03_SCH.pdf 》

《 X9_Processor_TRM_Rev00.07.pdf 》
页: [1]
查看完整版本: 【Linux开发】SemiDrive X9H GPIO 驱动学习篇