泰山派学习12--GPIO_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);

功能:注销映射

参数:虚拟地址

返回值:无  

void writel(unsigned u32 data, unsigned short addr)

功能:往内存映射的IO空间上写入32位数据,(writeb-->8位, writew-->16位,writel-->32位)

参数:数据、虚拟地址

返回值:无

unsigned  u32 readl(unsigned short addr)

功能:读取内存映射的IO空间32位数据

参数:虚拟地址

返回值:数据

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

功能:内核自动分配设备号

参数:设备号、从设备起始(一般0起始),设备个数,设备名称

返回值:

void unregister_chrdev_region(dev_t from, unsigned count)

功能:内核自动释放设备号

参数:设备号,设备个数

返回值:无

①、字符设备结构体

  struct cdev {

    struct kobject kobj;

    struct module *owner;

    const struct file_opreations *ops; //字符设备文件操作结构体

    struct list_head list;

    dev_t dev;  //设备号

    unsigned int count;

   }__randomize_layout;

 ②、对cdev进行初始化函数cdev_init

    void cdev_init(struct cdev *cdev, const struct file_operations *fops)

    功能:初始化cdev 绑定 ops结构体

    参数:字符设备结构体、字符设备文件操作结构体

    返回值:无

  ③、向linux系统添加字符设备

    int cdev_add(struct cdev *p, dev_t dev, unsigned count)

    功能:把字符设备添加到linux系统内核中

    参数:字符设备指针,设备号,设备数

    返回值:

  ④、从linux系统删除对应字符设备

    void cdev_del(struct cdev *p)

    功能:删除字符设备

    参数: 字符设备指针

    返回值:无

  ⑤、创建设备节点

    struct device*device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

    功能:

    参数:字符设备类,父设备, 字符设备, 字符设备名称、字符设备个数

  ⑥、删除设备节点

    void device_destroy(struct class *cls, dev_t devt)

    功能:

    参数:字符设备类, 字符设备

  ⑦、类创建

     struct class class_create( owner, char *name)

    参数:THIS_MODULE, 类名称

  ⑧、类删除

    class_destroy(struct  class *class)

    参数:类结构体

 

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; //字符设备结构体,有ops 和 dev 两个结构体很重要
unsigned int __iomem *va_iomux;  //物理地址映射虚拟内存地址
unsigned int __iomem *va_ddr;
unsigned int __iomem *va_dr;
unsigned int led_pin; // led 引脚 A-B为低,C-D为高,其中B、D需要加上8
};

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); //与调用writel效果一样,都是调用__raw__writel,其中在端序检验中有区别,建议使用iowrite32.
  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 ...

       

 

 

posted @   zbl1118  阅读(251)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示