首页 > 其他分享 >泰山派学习12--GPIO_LED字符设备驱动

泰山派学习12--GPIO_LED字符设备驱动

时间:2024-06-15 23:23:18浏览次数:28  
标签:va 12 led val -- dev ALERT cdev LED

一、GPIO寄存器

1、对GPIO进行控制有以下步骤

①:是能GPIO的时钟(默认开启,不用配置);

②: 设置引脚复用为GPIO(复位默认配置GPIO,不用配置);

③:设置引脚属性(上下拉、速率、驱动能力,默认不用配置);

④:控制GPIO引脚为输出,并且输出高低电平。

2、GPIO功能引脚及寄存器

rk3566 有5个gpio控制器 官方称为bank 每个控制器下 控制32个引脚 32个引脚 被分为4组(A B C D)

A     B     C       D

0-7   8-15   16-23   24-31


基地址 PMU_GRF 0xFDC20000 SYS_GRF 0xFDC60000

偏移地址 PMU_GRF_GPIO3B_IOMUX_H 0x004C

复用功能寄存器: 基地址 + 偏移地址 = 0xFDC60000 + 0x004C = 0xFDC6004C

0xFDC6004C 0- 2位写0(gpio功能) 16-18 写1(写使能) 对寄存器进行操作时一定要先将值读出来 将此值或上我们要写得值 对gpio3_b4引脚初始化复用功能位gpio 0x00004440 | 0x70000

读四个字节的数据 root@linaro-alip:/# io -r -4 0xFDC6004C fdc6004c: 00004440

写四个字节的数据 root@linaro-alip:/# io -w -4 0xFDC6004C 0x00074440

gpio控制器基地址 GPIO3 0xFE760000 方向寄存器偏移地址

GPIO_SWPORT_DDR_L 0x0008

GPIO_SWPORT_DDR_H 0x000C

A B C D 0-7 8-15 16-23 24-31

b0 b1 b2 b3 b4 b5 b6 b7

8 /9 /10 /11/12 /13 /14 /15

0xFE760000 + 0x0008 = 0xFE760008 12位写1(设置为输出) 28位写1(写使能) 0x00000000 |= 0x10001000

读四个字节的数据 root@linaro-alip:/# io -r -4 0xFE760008 fe760008: 00000000

写四个字节的数据 root@linaro-alip:/# io -w -4 0xFE760008 0x10001000

数据寄存器 GPIO_SWPORT_DR_L 0x0000 0xFE760000 + 0x0000 = 0xFE760000 12位写1(设置输出高电平) 28位写1(写使能) 0x00000000 |= 0x10001000

读四个字节的数据 root@linaro-alip:/# io -r -4 0xFE760000 fe760000: 00000000

写四个字节的数据 root@linaro-alip:/# io -w -4 0xFE760000 0x10001000

寄存器地址 都是物理地址 驱动在内核层 运行在3-4g的虚拟内存中 是不可以直接操作物理地址的 我们要将物理地址映射到虚拟内存中 才可以进行操作
void * ioremap(unsigned long offset, unsigned long size)

功能:将物理内存映射到虚拟内存中

参数: offset 偏移地址 size 大小 字节

返回值:成返回虚拟地址 失败返回NULL

void iounmap(void __iomem *addr);

功能:注销映射

参数:虚拟地址

返回值:无

 

3、led设备驱动模块(ledchardev.c)

/*
** 复用型引脚分为5组(GPIO0~4),每组里面都有32个复用型引脚,而且又分为4个小组(A、B、C、D),每个小组8个引脚(0~7)
** GPIO3_4B
** 在GPIO3大组,第二的B小组,第4个引脚,
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/io.h>

 

//设备名称
#define DEV_NAME "ledchardev"
#define DEV_CNT 1
//复用功能寄存器
#define PMU_GRF_GPIO3B_IOMUX_H 0xFDC6004C
//GPIO方向控制寄存器
#define GPIO_SWPORT_DDR_L 0xFE760008
//GPIO数据寄存器
#define GPIO_SWPORT_DR_L 0xFE760000

#define LED_ON 1
#define LED_OFF 0

char kbuf[128] = {0};

//定义字符设备的设备编号
static dev_t dev_id;

//定义一个设备类
struct class *led_chrdev_class;

//定义新的一个结构体 struct chr_dev
struct led_chrdev{
struct cdev dev;
unsigned int __iomem *va_iomux;
unsigned int __iomem *va_ddr;
unsigned int __iomem *va_dr;
unsigned int led_pin;
};

static struct led_chrdev led_cdev[DEV_CNT] = {
  {.led_pin = 12 }, //GPIO3_B4,就是8+4=12,对应DR_L, A和B属于低,C和D属于高。
};

 

void led_switch(unsigned char state)
{
  unsigned int val;


  if(state == LED_ON){
    val = ioread32(led_cdev->va_dr);
    val |= ((unsigned int)0x01 << (led_cdev->led_pin+16));
    val |= ((unsigned int)0x01 << (led_cdev->led_pin)); //输出高电平
    iowrite32(val, led_cdev->va_dr);
    printk(KERN_ALERT "[ KERN_ALERT ] led turn on dr_val=%#lX.\n", *(led_cdev->va_dr));
  }
  else if(state == LED_OFF){
    val = ioread32(led_cdev->va_dr);
    val |= ((unsigned int)0x01 << (led_cdev->led_pin+16));
    val &= ~((unsigned int)0x01 << (led_cdev->led_pin)); //输出低电平
    iowrite32(val, led_cdev->va_dr);
    printk(KERN_ALERT "[ KERN_ALERT ] led turn off dr_val=%#lX.\n", *(led_cdev->va_dr));
  }
}


int led_open(struct inode *inode, struct file *file)
{
  unsigned int val = 0;

  //通过 led_chrdev 结构变量中 dev 成员的地址找到这个结构体变量的首地址
  struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);
  //把文件的私有数据 private_data 指向设备结构体 led_cdev
  file->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);

  printk(KERN_ALERT "[ KERN_ALERT ] ledchardev open ...\n");

  //设置IO复用(复位就默认设置GPIO复用功能,可以不配置iomux寄存器)
  val = ioread32(led_cdev->va_iomux);
  //val |= 0x70000;
  val |= ((unsigned int)0x07 << (0+16));
  val &= ~((unsigned int)0x01 << (0));
  iowrite32(val, led_cdev->va_iomux);
  printk(KERN_ALERT "[ KERN_ALERT ] va_iomux_val=%#lX.\n", *(led_cdev->va_iomux));

  //设置输出模式
  val = ioread32(led_cdev->va_ddr);
  val = ((unsigned int)0x01 << (led_cdev->led_pin+16));
  val |= ((unsigned int)0x01 << (led_cdev->led_pin));
  iowrite32(val, led_cdev->va_ddr);
  printk(KERN_ALERT "[ KERN_ALERT ] va_ddr_val=%#lX.\n", *(led_cdev->va_ddr));

  //输出低电平
  val = ioread32(led_cdev->va_dr);
  val |= ((unsigned int)0x01 << (led_cdev->led_pin+16));
  val &= ~((unsigned int)0x01 << (led_cdev->led_pin));
  iowrite32(val, led_cdev->va_dr);
  printk(KERN_ALERT "[ KERN_ALERT ] va_dr_val=%#lX.\n", *(led_cdev->va_dr));

  return 0;
}

ssize_t led_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
   printk(KERN_ALERT "[ KERN_ALERT ] ledchardev read!\n");
   return 0;
}

ssize_t led_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
  unsigned char ret;
  struct led_chrdev *led_cdev = (struct led_chrdev *)file->private_data;

  if(size > sizeof(kbuf)){
    size = sizeof(kbuf);

  }
  if(copy_from_user(kbuf, ubuf, size)){
    printk(KERN_ALERT "[ KERN_ALERT ] copy data form user fail!\n");
    return -EIO;
  }

  ret = kbuf[0];

  if(ret == LED_ON){
    led_switch(LED_ON);
  }
  else if(ret == LED_OFF){
    led_switch(LED_OFF);
  }

  printk(KERN_ALERT "[ KERN_ALERT ] ledchardev write!\n");
  return size;

  }

int led_close(struct inode *inode, struct file *file)
{
  printk(KERN_ALERT "[ KERN_ALERT ] ledchardev close!\n");
  return 0;
}

struct file_operations led_chrdev_fops= {
  .owner = THIS_MODULE,
  .open = led_open,
  .read = led_read,
  .write = led_write,
  .release = led_close
};


static int __init ledcdev_init(void)
{
  int i = 0;
  dev_t cur_dev;
  unsigned int val = 0;
  int ret = 0;

  //物理地址映射虚拟地址,32位==>A0~A7 B0~B7 C0~C7 D0~D7,其中A B对应低16位,C D对应高16位
  led_cdev[0].va_iomux = ioremap(PMU_GRF_GPIO3B_IOMUX_H, 4);
  led_cdev[0].va_ddr = ioremap(GPIO_SWPORT_DDR_L, 4);
  led_cdev[0].va_dr = ioremap(GPIO_SWPORT_DR_L, 4);

  //动态分配主设备号,从设备号为0,可通过cat /proc/devices查看分配my_chrdev的主设备号
  ret = alloc_chrdev_region(&dev_id, 0, DEV_CNT, DEV_NAME);
  if(ret < 0){
    printk(KERN_ALERT "[ KERN_ALERT ] fail to alloc dev major.\n");
    return ret;
  }

  //创建一个设备类
  led_chrdev_class = class_create(THIS_MODULE, "ledchardev");

  //关联结构体
  for(i=0; i<DEV_CNT; i++){
    cdev_init(&led_cdev[i].dev, &led_chrdev_fops);
    led_cdev[i].dev.owner = THIS_MODULE;
    cur_dev = MKDEV(MAJOR(dev_id), MINOR(dev_id)+i);
    cdev_add(&led_cdev[i].dev, cur_dev, 1);
    device_create(led_chrdev_class, NULL, cur_dev, NULL, DEV_NAME "%d", i);
  }

  printk(KERN_ALERT "[ KERN_ALERT ] ledchardev init ...\n");
  return 0;
}


static void __exit ledcdev_exit(void)
{
  int i = 0;
  dev_t cur_dev;

  printk(KERN_ALERT "[ KERN_ALERT ] ledchardev exit ...\n");

  for(i=0; i<DEV_CNT; i++){
    iounmap(led_cdev[i].va_dr);
    iounmap(led_cdev[i].va_ddr);
    iounmap(led_cdev[i].va_iomux);
  }

  for(i=0; i<DEV_CNT; i++){
    cur_dev = MKDEV(MAJOR(dev_id), MINOR(dev_id)+i);
    device_destroy(led_chrdev_class, cur_dev);
    cdev_del(&led_cdev[i].dev);
  }

  unregister_chrdev_region(dev_id, DEV_CNT);
  class_destroy(led_chrdev_class);

}

module_init(ledcdev_init);

module_exit(ledcdev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zbl");

4、应用程序(ledchardevapp.c)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>


#define LED_ON 1
#define LED_OFF 0


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

  int fd;
  int ret;
  unsigned char databuf[1] = {0};

  if(argc != 2){
    printf("Error Usage!\n");
    return -1;
  }

  fd = open("/dev/ledchardev0", O_RDWR);
  if(fd == -1)
  {
    printf("open %s failed.\n", "/dev/ledchardev0");
    return -1;
  }

  databuf[0] = atoi(argv[1]);
  ret = write(fd, databuf, sizeof(databuf));
  if(ret < 0){
    printf("LED control failed.\n");
    close(fd);
    return -1;
  }

  ret = close(fd);
  if(ret < 0){
    printf("close %s failed.\n", "/dev/ledchardev0");
    return -1;
  }

  return 0;
}

5、交叉编译makefile 

PWD ?= $(shell pwd)

KERNELDIR := /home/zbl/tspi-rk3566/sdk/linux/kernel
CROSS_COMPILE ?= /home/zbl/tspi-rk3566/sdk/linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc

obj-m += ledchardev.o

module:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 modules
@# -C 表示从当前目录切换到内核源码目录下,借助内核源码makefile进行make编译
@# M=$(PWD) 表示只编译当前目录下的驱动
@# ARCH=arm64 指定编译架构

$(CC) ledchardevapp.c -o app
@# 交叉编译应用程序

.PHONE:clean

clean:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 clean
rm app

6、整体执行流程

1、编写内核驱动ledchardev.c

2、编写应用程序ledchardevapp.c

3、编写makefile,添加交叉编译工具及编译应用执行文件

4、.ko及app应用执行文件导入开发板

5、修改执行权限 chmod 777 app ledchardev.ko

6、加载内核启动 sudo insmod ledchardev.ko

root@localhost:/home/lckfb/zbl-kernel-modules/05# sudo insmod ledchardev.ko

[ 4294.260486] ledchardev reg successed.
[ 4294.263374] led reg init ok.

        

7、查看驱动lsmod

lckfb@linux:~/zbl-kernel-modules$ lsmod
Module Size Used by
ledchardev 16384 0
bcmdhd 1048576 0

       

8、查看驱动主设备号 cat  /porc/devices

226 drm

236 ledchardev

237 hidraw

238 rpmb

239 ttyGS

       

 

9、查看节点是否加载成功 ls -l /dev/ledchardev0

lckfb@linux:~/zbl-kernel-modules$ ll /dev/ledchardev0
crw-r--r-- 1 root root 236, 0 Jun 4 13:59 /dev/ledchardev0

       

 

10、执行应用程序GPIO输出高电平,点亮LED ./app 1

root@localhost:/home/lckfb/zbl-kernel-modules/05# ./app 1

       

   11、执行应用程序GPIO输出低电平,熄灭LED ./app 0

  root@localhost:/home/lckfb/zbl-kernel-modules/05# ./app 1

       

12、卸载内核驱动(自动删除设备节点文件) sudo rmmod ledchardev.ko

root@localhost:/home/lckfb/zbl-kernel-modules# sudo rmmod ledchardev.ko

[ 2750.540287] [ KERN_ALERT ] ledchardev exit ...

       

 

 

标签:va,12,led,val,--,dev,ALERT,cdev,LED
From: https://www.cnblogs.com/zblblog/p/18249815

相关文章

  • docker部署wordpress个人博客
    技术:docker-compose部署wordpres和mysql,宿主机的nginx部署SSL证书将HTTPS反向代理到wordpress。使用的是ubuntu20.04准备工作:-一台云服务器,一个已经备案的域名-免费申请到的nginx的SSL证书-docker、docker-compose、nginx已部署,确认可以拉取镜像一、docker-compose部署word......
  • (转)Docker Compose:从零基础到实战应用的全面指南
    原文:https://juejin.cn/post/7306756690727747610#heading-22引言什么是Docker?Docker是一个开源项目,诞生于2013年初,最初是dotCloud公司内部的一个业余项目。它基于Google公司推出的Go语言实现,项目后来加入了Linux基金会,遵从了Apache2.0协议,项目代码在GitHu......
  • 旧文合集
    故事结束了,以这样一种滑稽的方式收尾,甚至没有勇气去写我的OIMemories.以前的文章就不显示在主页了,一些可能有用的文章,或者承载回忆的游记就在这放个链接吧(按时间倒序).图论codeforces2200左右dp题目练习Codeforces833题解CF做题记录Gym102798CCPC2020威海E......
  • 作物起源与多样性中心的世界地图
    我们的粮食作物从何而来?国际农业研究磋商组织(CGIAR)旗下子机构国际热带农业中心(CIAT)早前发表了全球农作物起源和主要多样性区域的交互式地图,以及其与饮食、产量之间的关系。交互地图详见:https://blog.ciat.cgiar.org/origin-of-crops/起源中心与多样性中心静态图注:官网对......
  • 5.8安卓开发日记30
    今天学习python实验,本次的实验为python中的实体类,随着实验的进行,让我发现它的实体类和cc++java的实体类大同小异,只需要记住相关写法,着重记一点区别,就能很快适应python实体类的书写。【题目描述】定义一个人员类People,其属性有:姓名、性别、年龄;基于People实现学生类Student,添加......
  • 对角矩阵统计图,so easy!
    问题群友发来一个问题,来自一篇文献中的图。分析这幅图很明显是一个对角矩阵的统计图形,用R中GGally包的ggpairs()函数就可以快速绘制。案例如下:library(GGally)head(tips)pm<-ggpairs(tips)pm绘图我将模拟一个数据绘制。library(GGally)library(ggplot2)#模......
  • 一些多组学整合的R包
    通用集成多组学数据集包MOFA2包含一系列用于训练和分析多组因子分析(MOFA)的工具。MOFA是一种概率因子模型,旨在从可以包含多个组学层和/或样本组的数据集中识别变异的主轴。有关样品的其他时间或空间信息可以使用MEFISTO框架进行合并,该框架是MOFA2的一部分。下游分析功能可用于......
  • 推荐 | 诺奖得主传记《解码者:珍妮弗·杜德纳基因编辑的历史与未来》
    今天小编给大家推荐一本关于基因编辑的科普故事书。此书曾入选中信出版2022年度好书,豆瓣读书评分也高达8.7分,值得一阅。此书作者沃尔特·艾萨克森(WalterIsaacson)是美国知名传记作家,杜兰大学历史学教授,《时代周刊》前主编,CNN前董事长兼首席执行官。其他畅销传记作品有《史蒂夫......
  • 王立志等(Iowa State University):一种用于作物产量预测的 CNN-RNN 框架
    这是美国爱荷华州立大学工业工程系王立志老师联合同校老师发表的一篇文章。Front.PlantSci.虽然影响因子不高(大家应该都知道偏应用的数量遗传学发表的期刊普遍不高),但本文的引用还是蛮高的,好像是年度最佳论文之一吧。本文介绍了一种基于深度学习的框架,用于预测作物产量。该框架......
  • SeqBreed:一个用于复杂性状基因组预测的 Python 工具
    本文介绍了一个名为SeqBreed的Python工具,用于评估基因组预测在复杂情况下的表现。该工具可以模拟任何数量的由任意数量的因果位点决定的复杂表型,可实现了GBLUP、SSGBLUP、PBLUP等,并支持多种基因组预测方法和复杂染色体类型。作者使用了果蝇和四倍体马铃薯的数据集进行了测试,并展示......