Linux学习笔记(15.2)——LED设备驱动之面向对象演进2

  1. LED字符设备驱动程序改进——构建led_drv.c文件和led_dev.c文件
  • 将二者都用到的公共部分提取出来,放入到led_dev.h文件当中去
/**
 * @file led_dev.h 
 * @author glen (glen_cao@126.com)
 * @brief 
 * @version 0.1
 * @date 2021-12-17
 * 
 * @copyright Copyright (c) 2021
 */

#ifndef __LED_DEV_H__
#define __LED_DEV_H__

#include <linux/types.h>

struct led_ops {
    u8 num;             /* LED数量 */
    int (* led_init)(u8 idx);
    int (* led_exit)(u8 idx);
    void (* led_ctrl)(u8 idx, u8 status);
};

extern void * get_led_ops(void);

#endif // !__LED_DEV_H__
  • led_dev.c为设备相关函数
/* 
 * 文件名   : led_dev.c
 * 作者     : glen  
 * 描述     : led_dev驱动文件
 */
#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/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include "led_dev.h"

#define LED_NUM     2   /* LED数量 */

#define HIGH  1
#define LOW   0

#define OFF         'C' /* 关灯 */
#define ON          'O' /* 开灯 */

struct phy_regs {
    phys_addr_t ccgr;
    phys_addr_t sw_mux;
    phys_addr_t sw_pad;
    phys_addr_t dr;
    phys_addr_t dir;
};

struct vir_regs {
    void __iomem *ccgr;
    void __iomem *sw_mux;
    void __iomem *sw_pad;
    void __iomem *dr;
    void __iomem *dir;
};

/* LED设备结构体 */
struct gled_device {
    char *name;             /* 设备名称 */
    u8 dft_status[LED_NUM]; /* 默认状态: 打开或关闭 */
    u8 on_level[LED_NUM];   /* 打开时电平: 高或低 */
    u8 clk_shft_bits[LED_NUM];
    u32 pin[LED_NUM];       /* led引脚信息 */
    struct vir_regs vir_io[LED_NUM];
    struct phy_regs phy_io[LED_NUM];
    struct led_ops ops;
};

static void led_ctrl(u8 idx, u8 status);
static int led_init(u8 idx);
static int led_exit(u8 idx);

/* gled设备 */
static struct gled_device gled_dev = {
    .name = "gled_dev",
    .dft_status = { OFF, ON },
    .on_level = {LOW, LOW },
    .clk_shft_bits = {26, 30},
    .pin = {3, 1},
    .phy_io = {
        {   /* IO物理地址 */
            .ccgr   = 0x020C406C, 
            .sw_mux = 0x020E0068,
            .sw_pad = 0x020E02F4,
            .dr     = 0x0209C000,
            .dir    = 0x0209C004
        },
        {
            .ccgr   = 0x020C406C, 
            .sw_mux = 0x0229000C,
            .sw_pad = 0x02290050,
            .dr     = 0x020AC000,
            .dir    = 0x020AC004
        },
    },
    .ops = {
        .num = LED_NUM,
        .led_init = led_init,
        .led_exit = led_exit,
        .led_ctrl = led_ctrl,
    },
};

/**
 * @brief   : 打开/关闭LED 
 * @par     : status    LEDON('O') 打开LED, LEDOFF('C') 关闭LED
 * @retval  : 无
 */
static void led_ctrl(u8 idx, u8 status)
{
    u32 val = 0;

    if (gled_dev.vir_io[idx].ccgr == NULL) 
        return;

    if (status == ON) {
        val = readl(gled_dev.vir_io[idx].dr);
        if (gled_dev.on_level[idx] == HIGH) {
            val |= (1 << (gled_dev.pin[idx]));
        } else if (gled_dev.on_level[idx] == LOW) {
            val &= ~(1 << gled_dev.pin[idx]);
        }
        writel(val, gled_dev.vir_io[idx].dr);
        printk("The led%d is openned!\r\n", idx);
    } else if (status == OFF) {
        val = readl(gled_dev.vir_io[idx].dr);
        if (gled_dev.on_level[idx] == HIGH) {
            val &= ~(1 << gled_dev.pin[idx]);
        } else if (gled_dev.on_level[idx] == LOW) {
            val |= (1 << (gled_dev.pin[idx]));
        }
        writel(val, gled_dev.vir_io[idx].dr);
        printk("The led%d is closed!\r\n", idx);
    } else {
        printk("Recived parameter is error!\r\n");
    }
}

/**
 * @brief LED状态初始化
 * @param idx LED
 */
static int led_init(u8 idx)
{
    u32 val;

    if (idx >= gled_dev.ops.num) {
        return -EIO;
    }

    /* 寄存器地址映射 */
    gled_dev.vir_io[idx].ccgr    = ioremap(gled_dev.phy_io[idx].ccgr  ,  4);
    gled_dev.vir_io[idx].sw_mux  = ioremap(gled_dev.phy_io[idx].sw_mux,  4);
    gled_dev.vir_io[idx].sw_pad  = ioremap(gled_dev.phy_io[idx].sw_pad,  4);
    gled_dev.vir_io[idx].dr      = ioremap(gled_dev.phy_io[idx].dr    ,  4);
    gled_dev.vir_io[idx].dir     = ioremap(gled_dev.phy_io[idx].dir   ,  4);

    /* 使能GPIO时钟 */
    val = readl(gled_dev.vir_io[idx].ccgr);
    val |= (3 << gled_dev.clk_shft_bits[idx]);
    writel(val, gled_dev.vir_io[idx].ccgr);

    /* 设置gpio1_io03的复用功能 */
    writel(5, gled_dev.vir_io[idx].sw_mux);

    /* 寄存器sw_mux1_io03设置IO属性 */
    writel(0x10B0, gled_dev.vir_io[idx].sw_pad);

    /* 设置gpio1_gdir为输出功能 */
    val = readl(gled_dev.vir_io[idx].dir);
    val |= (1 << gled_dev.pin[idx]);
    writel(val, gled_dev.vir_io[idx].dir);

    /* 设置led为默认状态 */
    val = readl(gled_dev.vir_io[idx].dr);
    if (gled_dev.dft_status[idx] == ON) {
        if (gled_dev.on_level[idx] == HIGH) {
            val |= (1 << gled_dev.pin[idx]);
        } else if (gled_dev.on_level[idx] == LOW) {
            val &= ~(1 << gled_dev.pin[idx]);
        }
    } else if (gled_dev.dft_status[idx] == OFF) {
        if (gled_dev.on_level[idx] == HIGH) {
            val &= ~(1 << gled_dev.pin[idx]);
        } else if (gled_dev.on_level[idx] == LOW) {
            val |= (1 << gled_dev.pin[idx]);
        }
    }
    writel(val, gled_dev.vir_io[idx].dr);

    return 0;
}

/**
 * @brief LED状态解初始化
 * @param idx LED
 */
static int led_exit(u8 idx)
{
    if (idx >= gled_dev.ops.num) {
        return -EIO;
    }
    iounmap(gled_dev.vir_io[idx].ccgr);
    iounmap(gled_dev.vir_io[idx].sw_mux);
    iounmap(gled_dev.vir_io[idx].sw_pad);
    iounmap(gled_dev.vir_io[idx].dr);
    iounmap(gled_dev.vir_io[idx].dir);

    return 0;
}

/**
 * @brief Get the led ops object
 */
void * get_led_ops(void)
{
    return (void *)(&(gled_dev.ops));
}

/* 模块的许可证声明 */
MODULE_LICENSE("GPL");
/* 模块的作者声明 */
MODULE_AUTHOR("glen");

  • led_drv.c为驱动相关函数
/* 
 * 文件名   : led_drv.c
 * 作者     : glen  
 * 描述     : led_drv驱动文件
 */
#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/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include "led_dev.h"

/* LED驱动结构体 */
struct gled_driver {
    struct class *drv_class;
    char *name;
    dev_t major;

    struct led_ops *ops;
};

/* gled驱动 */
static struct gled_driver gled_drv = {
    .name  = "gled_drv",
    .major = 0,
    .ops = NULL,
};

/**
 * @brief Get the led ops object
 */
// __weak void *get_led_ops(void) 
// {
//     return NULL;
// }

/**
 * @brief   : 打开设备 
 * @par     : inode 传递给驱动的inode
 *            filp  设备文件, file结构体有个private_data的成员变量, 一般
 *            在open的时候将private_data指向设备结构体
 * @retval  : 0 成功, 其它 失败
 */
static int led_drv_open(struct inode *inode, struct file *filp)
{
    int minor = iminor(inode);
    struct led_ops *ops = gled_drv.ops;

    if (ops == NULL) {
        printk("Please register led_ops instance! \n");
        return 0;
    }

    if (ops->led_init)
        if (ops->led_init(minor) == 0) 
            printk("The led%d has openned successfully! \n", minor);
        else 
            printk("The led%d has openned failure! \n", minor);
    else 
        printk("Please register led_ops instance! \n");
    return 0;
}

/**
 * @brief   : 从设备读取数据 
 * @par     : filp  要打开的设备文件(文件描述符)
 *            buf   返回给用户空间的数据缓冲区
 *            cnt   要读取的数据长度
 *            offt  相对于文件首地址的偏移
 * @retval  : 读取数据长度, 若为负值则表示读取失败
 */
static ssize_t led_drv_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/**
 * @brief   : 向字符设备写入数据 
 * @par     : filp  设备文件, 表示打开的文件描述符
 *            buf   要写给设备的数据
 *            cnt   要写入的数据长度
 *            offt  相对于文件首地址的偏移
 * @retval  : 写入数据长度, 若为负值则表示读取失败
 */
static ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    u8 data_buf[1];
    u8 led_sta;
    int minor;
    struct inode *inode = file_inode(filp);
    struct led_ops *ops = gled_drv.ops;

    if (ops == NULL) {
        printk("Please register led_ops instance! \n");
        return 0;
    }
    minor = iminor(inode);
    ret = copy_from_user(data_buf, buf, 1);

    if (ret < 0) {
        printk("kenel recive data: %s\r\n", buf);
        return -EFAULT;
    }

    /* 获取状态值 */
    led_sta = data_buf[0];

    if (ops->led_ctrl) 
        ops->led_ctrl(minor, led_sta);

    return 0;

}

/**
 * @brief   : 打开设备 
 * @par     : inode 传递给驱动的inode
 *            filp  要关闭的设备文件(文件描述符)
 * @retval  : 0 成功, 其它 失败
 */
static int led_drv_release(struct inode *inode, struct file *filp)
{
    u8 minor = iminor(inode);
    struct led_ops *ops = gled_drv.ops;

    if (ops == NULL) {
        printk("Please register led_ops instance! \n");
        return 0;
    }

    if (minor >= ops->num)
        return -EINVAL;

    /* 虚拟地址映射销 */
    if (ops->led_exit)
        ops->led_exit(minor);

    printk("The led driver is closed! %s, %s, line %d\n", __FILE__, __FUNCTION__, __LINE__);

    return 0;
}

static struct file_operations led_drv_fops = {
    .owner = THIS_MODULE,
    .open = led_drv_open,
    .read = led_drv_read,
    .write = led_drv_write,
    .release = led_drv_release,
};


/**
 * @brief   : 驱动初始化函数 
 * @par     : 无
 * @retval  : 无
 */
static int __init led_drv_init(void)
{
    u8 err = 0;
    u8 i;

    /* register char device driver */
    gled_drv.major = register_chrdev(gled_drv.major, gled_drv.name, &led_drv_fops);
    if (gled_drv.major == 0) {
        printk("gled driver: Unable to register driver!\n");
        return -EIO;
    }
    printk("gled driver: Register driver successfully!\n");

    gled_drv.drv_class = class_create(THIS_MODULE, "gled");
    if (IS_ERR(gled_drv.drv_class)) {
        err = PTR_ERR(gled_drv.drv_class);
        goto out_chrdev;
    }

    gled_drv.ops = (struct led_ops *)get_led_ops();
    
    if (gled_drv.ops == NULL) {
        printk("Please register led_ops instance! \n");
        err = PTR_ERR(gled_drv.ops);
        goto out_chrdev;
    }

    for (i = 0; i < gled_drv.ops->num; i++) 
        device_create(gled_drv.drv_class, NULL, MKDEV(gled_drv.major, i), NULL, "gled%d", i);

    return 0;

out_chrdev:
    unregister_chrdev(gled_drv.major, gled_drv.name);
    return err;
}


/**
 * @brief   : 驱动退出函数 
 * @par     : 无
 * @retval  : 无
 */
static void __exit led_drv_exit(void)
{
    u8 i;

    for (i = 0; i < gled_drv.ops->num; i++)
        /* 销毁设备 */
        device_destroy(gled_drv.drv_class, MKDEV(gled_drv.major, i));

    class_destroy(gled_drv.drv_class);
    /* 注销字符设备驱动 */
    unregister_chrdev(gled_drv.major, gled_drv.name);
}

/* assign the functions as driver's import function and export function*/

module_init(led_drv_init);
module_exit(led_drv_exit);

/* insert license for module */
MODULE_LICENSE("GPL");

/* insert author information for module */
MODULE_AUTHOR("glen");
  1. Makefile稍作更改,将两个led_drv.c led_dev.c编译进 led_driver.ko
KERNELDIR	:= /home/glen/linux/imx6ull/linux/glen_linux
CURRENT_PATH	:= $(shell pwd)
led_driver-y	:= led_drv.o led_dev.o
obj-m	+= led_driver.o
build:	kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

【注】:本来是要编译成两个.ko文件,结果led_drv.ko死活insmod不成功,应该是依赖关系没整好,后面再研究吧。
4. 测试程序不做更改,验证参考Linux学习笔记(15)——LED设备驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值