实现一个platporm架构的LED驱动

目标:编写一个platporm架构的LED驱动

参考知识:

在Linux字符设备驱动编程模型中,只要应用程序open()了相应的设备文件,就可以使用ioctl通过驱动程序来控制我们的硬件,这种模型直观,但是从软件设计的角度看,却是一种十分糟糕的方式,它有一个致命的问题,就是设备信息和驱动代码冗余在一起,一旦硬件信息发生改变甚至设备已经不在了,就必须要修改驱动源码,非常的麻烦,为了解决这种驱动代码和设备信息耦合的问题,Linux提出了platform bus(平台总线)的概念,即使用虚拟总线将设备信息和驱动程序进行分离,设备树的提出就是进一步深化这种思想,将设备信息进行更好的整理。平台总线会维护两条链表,分别管理设备和驱动,当一个设备被注册到总线上的时候,总线会根据其名字搜索对应的驱动,如果找到就将设备信息导入驱动程序并执行驱动;当一个驱动被注册到平台总线的时候,总线也会搜索设备。总之,平台总线负责将设备信息和驱动代码匹配,这样就可以做到驱动和设备信息的分离。

//include/linux/ioport.h
struct resource {    
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    unsigned long desc;
    struct resource *parent, *sibling, *child;
};

这个结构用来描述一个地址资源或中断资源,除了这个结构,内核还提供了一些宏来帮助我们快速的创建一个resource对象。

start表示资源开始的位置,如果是IO地址资源,就是起始物理地址,如果是中断资源,就是中断号;

 end表示资源结束的位置,如果是IO地址地址,就是映射的最后一个物理地址,如果是中断资源,就不用填;

name就是这个资源的名字。

flags表示资源类型,提取函数在寻找资源的时候会对比自己传入的参数和这个成员,工程师应该使用内核提供的宏,这些宏也在"ioport.h"中进行了定义,比如IORESOURCE_MEM表示这个资源是地址资源,IORESOURCE_IRQ表示这个资源是中断资源...。

     有了这几个属性,就可以完整的描述一个资源,但如果每个资源都需要单独管理而不是组成某种数据结构,显然是一种非常愚蠢的做法,所以内核的resource结构还提供了三个指针:parent,sibling,child(24),分别用来表示资源的父资源,兄弟资源,子资源,这样内核就可以使用树结构来高效的管理大量的系统资源,linux内核有两种树结构:iomem_resource,ioport_resource,进行板级开发的时候,通常将主板上的ROM资源放入iomem_resource树的一个节点,而将系统固有的I/O资源挂到ioport_resource树上。

Makefile的参考:

查看原理图:

要控制的LED,输出0就会亮,输出1就会灭

可以看到要控制LED就要通过PH2来控制

 

查看数据手册:

GPIO基址:

PH控制寄存器偏移地址:

PH2控制寄存器的位:

PH数据寄存器的偏移地址:

设备部分代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>

//#define GPIO_LED_PIN_NUM 0x00FC //物理地址
// GPIO  0x0300B000
//PH_CFG0 Offset: 0x00FC ->0x0300B0FC
//PH2[31:0]: [10:8] = 111 -> 001 output
//PH_DAT[31:0]: [15:0] Offset: 0x010C ->0x0300B10C
//定义LED资源
static struct resource led_resources[] = {
    [0] = {
        .start = 0x0300B0FC,      //资源的起始地址
        .end = 0x0300B0FC + 16 - 1,  //资源的结束地址
        .flags = IORESOURCE_MEM,     //资源的类型
    },
    [1] = {
        .start = 0x0300B10C,
        .end = 0x0300B10C + 16 - 1,
        .flags = IORESOURCE_MEM,
    },
};
//struct resource led_resources = DEFINE_RES_MEM(GPIO_LED_PIN_NUM, 4);

static void led_platform_release(struct device *dev) {
    return;
}

//定义平台设备
static struct platform_device led_platform_device = {
    .name = "myled", //platform_driver中的名字要一致
    .id = -1,  //表示只有一个设备
    .num_resources = ARRAY_SIZE(led_resources), //资源数组大小
    .resource = led_resources, //资源数组
    .dev = {
        .release = led_platform_release,
        //.platform_data = NULL,
    },
};
//注册平台设备
static int led_platform_init(void) {
    printk("led_platform_init\n");
    //platform_device_register(&led_platform_device);
    int ret;
    ret = platform_device_register(&led_platform_device);
    if(ret < 0) {
        platform_device_put(&led_platform_device);
        return ret;
    }
    return 0;
}

static void led_platform_exit(void) {
    printk("led_platform_exit\n");
    platform_device_unregister(&led_platform_device);
}

module_init(led_platform_init);
module_exit(led_platform_exit);

MODULE_LICENSE("GPL v2");

驱动部分代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/uaccess.h>

#define LED_ON 0
#define LED_OFF 1
#define LED_CON_PIN 8
#define LED_DAT_PIN 2

static int major;  //主设备号
struct cdev *led_cedv;
static struct class *led_class;
unsigned long *gpio_con;   //GPIO控制寄存器
unsigned long *gpio_dat;   //GPIO数据寄存器

//unsigned long a = 0, b = 0;

#define DEVICE_NAME "myled"  

static int led_open(struct inode *inode, struct file *file) {
    printk("led_open\n");
    *gpio_con &= ~(7<< LED_CON_PIN);
    *gpio_con |= (1 << LED_CON_PIN); 
    //设置控制寄存器[10:8]为001,引脚输出
    return 0;
}

static int led_release(struct inode *inode, struct file *file) {
    printk("led_release\n");
    //*gpio_con |= (7<< LED_CON_PIN); //tmdsb加了这句让我测了一天灯不变
    return 0;
}
ssize_t led_read(struct file *filp, //其中参数filp是文件指针,
char __user *buf, //参数buff指向用户空间的缓冲区,这个缓冲区是一个存放新读入数据的空缓冲区;
size_t count, //参数count是请求传输的数据长度,
loff_t *f_pos) { //offp指明了用户在文件中进行存放操作的位置;
    printk("read led\n");
    int val = 0;
    val = *gpio_dat & (0x01 << LED_DAT_PIN);
    copy_to_user(buf, &val, 1);  //把读取的数据传递到用户层
    return 1; //读取成功返回读取的字节数
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
    int val = 0;
    if(count != 1) return -1;
    copy_from_user(&val, buf, count);
    printk("led_write %d\n", val);
    if(val) { //1 | 0
        *gpio_dat |= (0x01 << LED_DAT_PIN);  //写入1
    } else {
        *gpio_dat &= ~(0x01 << LED_DAT_PIN); //写入0
    }
    return 1;
}

long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    printk("led_ioctl %d\n", cmd);
    switch (cmd)
    {
    case LED_ON:
        printk("led_ioctl LED_ON\n");
        *gpio_dat &= ~(0x01 << LED_DAT_PIN);
        break;
    case LED_OFF:
        printk("led_ioctl LED_OFF\n");
        *gpio_dat |= (0x01 << LED_DAT_PIN);
        break;
    default:
        printk("led_ioctl default\n");
        *gpio_dat &= ~(0x01 << LED_DAT_PIN);
        break;
    }
    return 0;
}

struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
};
//平台设备和平台驱动匹配成功后就会调用下面的函数
static int led_probe(struct platform_device *pdev) {
    printk("led_probe\n");
    int ret;
    struct resource *res;
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取设备资源,拿出resource[0]资源
    gpio_con = ioremap(res->start, res->end - res->start + 1);  //把物理地址映射为虚拟地址

    res = platform_get_resource(pdev, IORESOURCE_MEM, 1); //获取设备资源,拿出resource[1]资源
    gpio_dat = ioremap(res->start, res->end - res->start + 1);
    //gpio_con = &a;
    //gpio_dat = &b;

    //gpio_con = ioremap(0x0300B0FC, 16);
    //gpio_dat = ioremap(0x0300B10C, 16);

    major = register_chrdev(0, "myled", &led_fops); //申请设备号
    led_class = class_create(THIS_MODULE, "myled"); //创建设备类
    device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled0", NULL); //创建设备节点

    return 0;
}

static int led_remove(struct platform_device *dev) {
    printk("led_remove\n");
    device_destroy(led_class, MKDEV(major, 0));
    class_destroy(led_class);
    unregister_chrdev(major, "mydev");

    iounmap(gpio_con);  //注销虚拟地址
    iounmap(gpio_dat);
    return 0;
}

//定义platform驱动
static struct platform_driver led_platform_driver = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "myled",     //与platform_device中的名称对应
        .owner = THIS_MODULE, 
    },
};

static int led_init(void) {
    printk("led_init\n");
    //注册平台驱动的同时会匹配相应的平台设备
    return platform_driver_register(&led_platform_driver);
}

static void led_exit(void) {
    printk("led_exit\n");
    platform_driver_unregister(&led_platform_driver);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL v2");

Makefile文件:

##CONFIG_MODULE_SIG=n

CROSS_COMPILE:=/home/e123/workspace/V536_CDR_v2.0/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin/arm-openwrt-linux-

##ARCH:=arm


CC:= $(CROSS_COMPILE)gcc

LD:= $(CROSS_COMPILE)ld

##CC = /home/e123/workspace/V536_CDR_v2.0/out/v536-cdr/staging_dir/toolchain/bin/arm-openwrt-linux-muslgnueabi-gcc-6.4.1
 

obj-m := led_platform_driver.o led_platform_device.o

KVERS = /lib/modules/$(shell uname -r)/build
##KVERS = /home/e123/workspace/V536_CDR_v2.0/lichee/linux-4.9

build: kernel_modules

kernel_modules:
	make ARCH=arm -C $(KVERS) M=$(CURDIR) modules
clean:
	make ARCH=arm -C $(KVERS) M=$(CURDIR) clean

测试程序:

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(){
	int fd = open("/dev/myled0", O_RDWR);
	if(fd < 0) {
		printf("open faild!");
		return -1;
	}
  long a = 500;
  int b = 1;
  scanf(" %d", &a);
  if(a < 0 || a > 123456789) a = 1000;
  a = a * 1000;
  while(1) {
    usleep(a);
    b ^= 1;
    write(fd, &b, 1);
  }
	close(fd);
	return 0;
}

现象:可以看到红色的LED按照输入的时间间隔(毫秒)闪烁。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值