1 硬件原理图分析
上图为Jetson-nano对外扩展的40pin接口,这次使用15和33引脚,15对应PY.02,作为输出,33对应PE.06,作为输入
2 程序编写
编写设备树
可以在Jetson-nano上运行如下命令查看开发板上运行的设备树文件
cat /proc/device-tree/nvidia,dtsfilename
Jetson-nano设备树相关源码都在源码Linux_for_Tegra/source/public/hardware/nvidia目录下,GPIO主要涉及3个文件的更改
platform/t210/porg/kernel-dts/porg-platforms/tegra210-porg-pinmux-p3448-0000-b00.dtsi
lcd_te_py2 {
nvidia,pins = "lcd_te_py2";
nvidia,function = "rsvd1";
nvidia,pull = <TEGRA_PIN_PULL_DOWN>;
nvidia,tristate = <TEGRA_PIN_ENABLE>;
nvidia,enable-input = <TEGRA_PIN_DISABLE>;
};
pe6 {
nvidia,pins = "pe6";
nvidia,function = "rsvd0";
nvidia,pull = <TEGRA_PIN_PULL_DOWN>;
nvidia,tristate = <TEGRA_PIN_ENABLE>;
nvidia,enable-input = <TEGRA_PIN_DISABLE>;
};
platform/t210/porg/kernel-dts/porg-platforms/tegra210-porg-gpio-p3448-0000-b00.dtsi
gpio_default: default {
gpio-input = <
TEGRA_GPIO(A, 5)
TEGRA_GPIO(X, 4)
TEGRA_GPIO(X, 5)
TEGRA_GPIO(X, 6)
TEGRA_GPIO(Y, 1)
TEGRA_GPIO(V, 1)
TEGRA_GPIO(Z, 2)
TEGRA_GPIO(H, 2)
TEGRA_GPIO(H, 5)
TEGRA_GPIO(H, 6)
TEGRA_GPIO(I, 1)
TEGRA_GPIO(CC, 4)
TEGRA_GPIO(E, 6) /* 新增 */
>;
gpio-output-low = <
TEGRA_GPIO(S, 7)
TEGRA_GPIO(T, 0)
TEGRA_GPIO(Z, 3)
TEGRA_GPIO(H, 0)
TEGRA_GPIO(H, 3)
TEGRA_GPIO(H, 4)
TEGRA_GPIO(H, 7)
TEGRA_GPIO(I, 0)
TEGRA_GPIO(I, 2)
TEGRA_GPIO(Y, 2) /* 新增 */
>;
gpio-output-high = <
TEGRA_GPIO(A, 6)
TEGRA_GPIO(X, 3)
TEGRA_GPIO(CC, 7)
>;
};
platform/t210/porg/kernel-dts/tegra210-porg-p3448-common.dtsi
/* 根节点下新增gpio_test节点 */
gpio_test {
compatible = "gpio-test";
status = "okay";
input-gpio = <&gpio TEGRA_GPIO(E, 6) GPIO_ACTIVE_HIGH>;
output-gpio = <&gpio TEGRA_GPIO(Y, 2) GPIO_ACTIVE_LOW>;
};
编写驱动
// gpiobase.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#define GPIO_BASE_CNT 1 /* 设备号个数 */
#define GPIO_BASE_NAME "gpiobase" /* 名字 */
/* 设备结构体 */
struct gpiobase_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int gpioIdInput; /* Input GPIO 编号 */
int gpioIdOutput; /* Output GPIO 编号 */
};
struct gpiobase_dev gpioBaseDev; /* 字符设备 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int gpiobase_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioBaseDev; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t gpiobase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
struct gpiobase_dev *dev = filp->private_data;
if (cnt > 1) {
printk("read length err!\r\n");
return -EFAULT;
}
int gpioValue = gpio_get_value(dev->gpioIdInput); /* 获取 GPIO 的值 */
if (gpioValue < 0) {
printk("get gpio value failed!\r\n");
return -EFAULT;
}
char databuf = gpioValue ? 1 : 0;
int ret = copy_to_user(buf, &databuf, cnt);
if(ret < 0) {
printk("kernel read failed!\r\n");
return -EFAULT;
}
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t gpiobase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
struct gpiobase_dev *dev = filp->private_data;
if (cnt > 1) {
printk("write length err!\r\n");
return -EFAULT;
}
unsigned char databuf;
int ret = copy_from_user(&databuf, buf, cnt);
if(ret < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
int writeValue = databuf ? 1 : 0;
gpio_set_value(dev->gpioIdOutput, writeValue); /* 写 GPIO */
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int gpiobase_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations gpiobase_fops = {
.owner = THIS_MODULE,
.open = gpiobase_open,
.read = gpiobase_read,
.write = gpiobase_write,
.release = gpiobase_release,
};
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init gpiobase_init(void)
{
/* 设置 GPIO */
/* 获取设备节点: gpio_test */
gpioBaseDev.nd = of_find_node_by_path("/gpio_test");
if(gpioBaseDev.nd == NULL) {
printk("gpioBaseDev node cant not found!\r\n");
return -EINVAL;
}
else {
printk("gpioBaseDev node has been found!\r\n");
}
/* 获取设备树中的 gpio 属性,得到 gpio 编号 */
gpioBaseDev.gpioIdInput = of_get_named_gpio(gpioBaseDev.nd, "input-gpio", 0);
if(gpioBaseDev.gpioIdInput < 0) {
printk("can't get input-gpio");
return -EINVAL;
}
printk("input-gpio num = %d\r\n", gpioBaseDev.gpioIdInput);
gpioBaseDev.gpioIdOutput = of_get_named_gpio(gpioBaseDev.nd, "output-gpio", 0);
if(gpioBaseDev.gpioIdOutput < 0) {
printk("can't get output-gpio");
return -EINVAL;
}
printk("output-gpio num = %d\r\n", gpioBaseDev.gpioIdOutput);
/* 设置 GPIO */
int ret = gpio_direction_input(gpioBaseDev.gpioIdInput); /* input */
if(ret < 0) {
printk("can't set input gpio!\r\n");
}
ret = gpio_direction_output(gpioBaseDev.gpioIdOutput, 0); /* output 输出低电平 */
if(ret < 0) {
printk("can't set output gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 创建设备号 */
if (gpioBaseDev.major) { /* 定义了设备号 */
gpioBaseDev.devid = MKDEV(gpioBaseDev.major, 0);
register_chrdev_region(gpioBaseDev.devid, GPIO_BASE_CNT, GPIO_BASE_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&gpioBaseDev.devid, 0, GPIO_BASE_CNT, GPIO_BASE_NAME); /* 申请设备号 */
gpioBaseDev.major = MAJOR(gpioBaseDev.devid); /* 获取分配号的主设备号 */
gpioBaseDev.minor = MINOR(gpioBaseDev.devid); /* 获取分配号的次设备号 */
}
printk("gpioBaseDev major=%d,minor=%d\r\n",gpioBaseDev.major, gpioBaseDev.minor);
/* 初始化cdev */
gpioBaseDev.cdev.owner = THIS_MODULE;
cdev_init(&gpioBaseDev.cdev, &gpiobase_fops);
/* 添加一个cdev */
cdev_add(&gpioBaseDev.cdev, gpioBaseDev.devid, GPIO_BASE_CNT);
/* 创建类 */
gpioBaseDev.class = class_create(THIS_MODULE, GPIO_BASE_NAME);
if (IS_ERR(gpioBaseDev.class)) {
return PTR_ERR(gpioBaseDev.class);
}
/* 创建设备 */
gpioBaseDev.device = device_create(gpioBaseDev.class, NULL, gpioBaseDev.devid, NULL, GPIO_BASE_NAME);
if (IS_ERR(gpioBaseDev.device)) {
return PTR_ERR(gpioBaseDev.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit gpiobase_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&gpioBaseDev.cdev); /* 删除cdev */
unregister_chrdev_region(gpioBaseDev.devid, GPIO_BASE_CNT); /* 注销设备号 */
device_destroy(gpioBaseDev.class, gpioBaseDev.devid);
class_destroy(gpioBaseDev.class);
}
module_init(gpiobase_init);
module_exit(gpiobase_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("han");
Makefile
KERNELDIR := /home/e/jetson/output
CROSS_COMPILE=/home/e/jetson/source/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
CURRENT_PATH := $(shell pwd)
obj-m := gpiobase.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译命令
make ARCH=arm64
编写测试App
// gpiobaseApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
if(argc < 3){
printf("Error Usage!\r\n");
return -1;
}
if(strlen(argv[2]) > 1){
printf("please input \'w\' or \'r\'\r\n");
return -1;
}
char *filename = argv[1];
/* 打开驱动文件 */
int fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
int ret;
char readbuf;
if (argv[2][0] == 'r') { /* 从驱动文件读取数据 */
ret = read(fd, &readbuf, 1);
if (ret < 0) {
printf("read file %s failed!\r\n", filename);
}
else {
/* 读取成功,打印出读取成功的数据 */
printf("read data:%d\r\n",readbuf);
}
}
else if (argv[2][0] == 'w') {
/* 向设备驱动写数据 */
if (argc < 4) {
printf("Error Usage!\r\n");
return -1;
}
char writebuf = atoi(argv[3]) ? 1 : 0;
ret = write(fd, &writebuf, 1);
if(ret < 0){
printf("write file %s failed!\r\n", filename);
}
}
else {
printf("please input \'w\' or \'r\'\r\n");
return -1;
}
/* 关闭设备 */
ret = close(fd);
if(ret < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
编译命令
/home/e/jetson/source/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc gpiobaseApp.c -o gpiobaseApp
3 GPIO测试
编译内核
make ARCH=arm64 O=/home/e/jetson/output -j8
这一步主要是是为了编译修改的设备树文件。因为之前编译过,这次只修改了设备树文件,所以编译时间很短。编译完成后,成功生成的dtb文件就在/home/e/jetson/output/arch/arm64/boot/dts目录下。
可以使用dtc工具将dtb文件反编译为dts
/home/e/jetson/output/scripts/dtc/dtc -I dtb -O dts tegra210-p3448-0000-p3449-0000-b00.dtb > tegra210-p3448-0000-p3449-0000-b00.dts
/home/e/jetson/output/scripts/dtc/dtc为内核编译时生成的
当然也可以自行安装dtc工具
sudo apt-get install device-tree-compiler
反编译完成后打开生成的dts文件,查看新增的gpio_test节点是否存在,存在则说明修改成功。
接下来就将dtb文件拷贝到Jetson-nano的/boot目录下,替换原dtb文件,同时更改启动配置文件/boot/extlinux/extlinux.conf
TIMEOUT 30
DEFAULT primary
MENU TITLE L4T boot options
LABEL primary
MENU LABEL primary kernel
LINUX /boot/Image
FDT /boot/tegra210-p3448-0000-p3449-0000-b00.dtb # 这一行为新增,指定启动的设备树文件
INITRD /boot/initrd
APPEND ${cbootargs} quiet root=/dev/mmcblk0p1 rw rootwait rootfstype=ext4 console=ttyS0,115200n8 console=tty0 fbcon=map:0 net.ifnames=0
然后重启,重启后查看设备树下是否有gpio_test节点
ls /proc/device-tree
然后将gpiobase.ko与gpiobaseApp拷贝到开发板上,用杜邦线连接开发板的15和33引脚
sudo insmod gpiobase.ko
sudo ./gpiobaseApp /dev/gpiobase w 0
sudo ./gpiobaseApp /dev/gpiobase r
sudo ./gpiobaseApp /dev/gpiobase w 1
sudo ./gpiobaseApp /dev/gpiobase r