linux内核驱动学习--带设备树的字符设备驱动程序示例

驱动文件

分为两个文件,一个字符设备添加和设备树相关节点的处理文件,一个负责LED的相关操作处理

dts_led.c

#include "dts_led.h"
#include "led_operator.h"

struct dtsled_dev  dtsled;

static int dtsled_open (struct inode *inode, struct file *filp)
{
    filp->private_data = &dtsled;
    return 0;
}

static int dtsled_release (struct inode *inode, struct file *filp)
{
    struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
    return 0;
}

static ssize_t dtsled_write (struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
    int retval = 0;
    unsigned char buffer[1] = { 0 };

    retval = copy_from_user(buffer, buf, count);
    if ( retval < 0 ) {
        printk("kernel write failed\r\n");
        return -EFAULT;
    }
    printk("read sta = %d\r\n", buffer[0]);
    //判断开灯还是关灯
    led_switch(buffer[0]);
    return 0;
}


static struct file_operations dtsled_fops = {
    .owner   = THIS_MODULE,
    .open    = dtsled_open,
    .release = dtsled_release,
    .write   = dtsled_write,
};

static int __init dts_led_start(void)
{
    int ret = 0;
    char *str = NULL;
    u32 regdata[10];
    int i = 0;
    
    /* 注册字符设备驱动 */

    /* 1、创建设备号 */
    if (dtsled.major) {    /* 定义了设备号 */
        dtsled.devid = MKDEV(dtsled.major, 0);
        ret = register_chrdev_region(dtsled.major, DTSLED_CNT, DTSLED_NAME);
    }
    else {                 /* 未定义设备号 */
        ret = alloc_chrdev_region(&(dtsled.devid), 0, DTSLED_CNT, DTSLED_NAME);
        dtsled.major = MAJOR(dtsled.devid);
        dtsled.minor = MINOR(dtsled.devid);
    }
    if (ret < 0) {
        goto FAILED_DEVID;
    }
    printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);
    
    /* 2. 添加字符设备 */
    cdev_init(&(dtsled.ledCdev), &dtsled_fops);
    ret = cdev_add(&(dtsled.ledCdev), dtsled.devid, DTSLED_CNT);
    if (ret < 0) {
        printk("添加字符设备错误\r\n");
        goto FAILED_CDEV;
    }

    /* 自动创建设备节点 */
    dtsled.ledClass = class_create(THIS_MODULE, DTSLED_NAME);
    if (IS_ERR(dtsled.ledClass)) {
        ret = PTR_ERR(dtsled.ledClass);
        goto FAILED_CLASS;
    }
    
    dtsled.ledDevice = device_create(dtsled.ledClass, NULL, dtsled.devid, NULL, DTSLED_NAME);
    if (IS_ERR(dtsled.ledDevice)) {
        ret = PTR_ERR(dtsled.ledDevice);
        goto FAILED_DEVICE;
    }

    /* 获取设备树属性内容 */
    dtsled.nd = of_find_node_by_path("/qinmianled");
    if (dtsled.nd == NULL) {
        ret = -EINVAL;
        goto FAILED_GETDTSNODE;
    }
    /* 判断获取的节点的正确性 */
    ret = of_property_read_string(dtsled.nd, "status", (const char **)&str);
    if (ret < 0) {
        goto FAILED_GETDTSNODE;
    }
    else {
        printk("status = %s\r\n", str);
    }
    ret = of_property_read_string(dtsled.nd, "compatible", (const char **)&str);
    if (ret < 0) {
        goto FAILED_GETDTSNODE;
    }
    else {
        printk("compatible = %s\r\n", str);
    }

    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    if (ret < 0) {
        goto FAILED_GETDTSNODE;
    }
    else {
        printk("reg data:\r\n");
        for (i = 0; i < 10; i++) {
            printk("%#X ", regdata[i]);
        }
        printk("\r\n");
    }

    /* led 初始化 */
    dts_led_init(regdata, 10);
    
    return 0;

FAILED_GETDTSNODE:
    device_destroy(dtsled.ledClass, dtsled.devid);

FAILED_DEVICE:
    class_destroy(dtsled.ledClass);

FAILED_CLASS:
    cdev_del(&(dtsled.ledCdev));

FAILED_CDEV:
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT);

FAILED_DEVID:
    return ret;
}


static void __exit dts_led_exit(void)
{
    led_free();

    /* 销毁设备节点 */
    device_destroy(dtsled.ledClass, dtsled.devid);
    class_destroy(dtsled.ledClass);
    /* 删除字符设备 */
    cdev_del(&(dtsled.ledCdev));
    /* 释放设备号 */
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
}

module_init(dts_led_start);
module_exit(dts_led_exit);
MODULE_LICENSE("GPL");

led_operator.c

#include "dts_led.h"

/* 静态变量定义 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

void dts_led_init(u32 *regdata, int num)
{
    unsigned int val = 0;
#if  1
    /* 直接使用设备树的of函数来映射虚拟内存 */
    IMX6U_CCM_CCGR1   = of_iomap(dtsled.nd, 0);
    SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
    SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
    GPIO1_DR          = of_iomap(dtsled.nd, 3);
    GPIO1_GDIR        = of_iomap(dtsled.nd, 4);
#else
    /* 传统方法使用ioremap来映射 */
    IMX6U_CCM_CCGR1   = ioremap(regdata[0], regdata[1]);
    SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
    SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
    GPIO1_DR          = ioremap(regdata[6], regdata[7]);
    GPIO1_GDIR        = ioremap(regdata[8], regdata[9]);
#endif

    /* 2、使能 GPIO1 时钟 */
    val  = readl(IMX6U_CCM_CCGR1);
    val &= ~(0x3 << 26);
    val |=  (0x3 << 26);
    writel(val, IMX6U_CCM_CCGR1);

    /* 3、设置 GPIO1_IO03 的复用功能,将其复用为GPIO1_IO03 */
    writel(5, SW_MUX_GPIO1_IO03);

    /* 寄存器 SW_PAD_GPIO1_IO03 设置 IO电气属性 */
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /* 4、设置 GPIO1_IO03 为输出功能 */
    val  = readl(GPIO1_GDIR);
    val &= ~(1 << 3);
    val |=  (1 << 3);
    writel(val, GPIO1_GDIR);

    /* 5、默认关闭 LED */
    val  = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);
    return ;
}

void led_switch(u8 sta)
{
    unsigned int val = 0;
    if ( sta == LEDON ) {
        val = readl(GPIO1_DR);
        val &= ~(0x1 << 3);    //bit3清0, 打开LED
        writel(val, GPIO1_DR);
    }
    else if ( sta == LEDOFF ) {
        val = readl(GPIO1_DR);
        val |= (0x1 << 3);     //bit3置1, 关闭LED灯
        writel(val, GPIO1_DR);
    }
    return ;
}

void led_free(void)
{
    unsigned int val = 0;
    val = readl(GPIO1_DR);
    val |= (0x1 << 3);     //bit3置1, 关闭LED灯
    writel(val, GPIO1_DR);
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    return;
}

头文件

dts_led.h

#ifndef __DTS_LED_H
#define __DTS_LED_H

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_address.h>




#define DTSLED_CNT          1           /* 设备号个数 */
#define DTSLED_NAME         "dtsled"    /* 设备名字 */
#define LEDOFF              0           /* 关灯 */
#define LEDON               1           /* 开灯 */


/* 映射后的寄存器虚拟地址指针 */

/* dtsled 设备结构体 */
struct dtsled_dev{
    dev_t               devid;
    struct cdev         ledCdev;
    struct class       *ledClass;
    struct device      *ledDevice;
    int                 major;
    int                 minor;
    struct device_node *nd;        /* 设备节点 */
};
extern struct dtsled_dev  dtsled;

#endif

led_operator.h

#ifndef  __LED_OPERATOR_H
#define  __LED_OPERATOR_H

extern void dts_led_init(u32 *regdata, int num);
extern void led_switch(u8 sta);
extern void led_free(void);

#endif

测试文件

dtsled_test.c

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

int main(int argc, char *argv[])
{
    int fd;
    unsigned char sta = 0;
    int i;
    fd = open("/dev/dtsled", O_RDWR);
    if (fd == -1) {
        printf("/dev/dtsled open failed\r\n");
        return -1;
    }

    for (i = 0; i < 10; i++) {
        sta = i % 2;
        printf("sta = %d\n", sta);
        write(fd, &sta, sizeof(sta));
        sleep(1);
    }

    close(fd);
    return 0;
}

Makefile

KERNELDIR := /home//work/linux-kernel
CURRENT_PATH := $(shell pwd)

obj-m := dtsled.o
dtsled-objs = led_operator.o dts_led.o

build : kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	rm -rf *.o *.ko *.mod.c *.order *.symvers
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值