一、概述
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[2];
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[0];
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[2];
if(argc != 3){ //传入三个参数:运行的app /dev/xxx 操作
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开 /dev/xxx 驱动文件 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
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[1]);
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 》
全部0条评论
快来发表一下你的评论吧 !