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

  1. Linux学习笔记(15.2)中,将LED字符设备驱动程序分为led_drv和led_dev两个文件,其中led_drv提供led_drv_open、led_drv_write、led_drv_release等与应用程序对应的函数,而这些函数在实际执行时要通过函数指针调用底层的led_init、led_exit、led_ctrl等函数完成对LED的控制;而led_dev文件则提供LED的资源和实际对LED操作的函数。
    为提升程序的可移植性,可进一步将led_dev文件中LED资源和实际对LED操作的函数分成两个文件——与芯片相关的IO控制文件和LED资源文件,前者提供与芯片相关的LED的控制函数,即某款芯片对IO口的操作,后者只提供LED资源信息。这样,如果更换了芯片,led_drv文件无需更改,需要将与芯片相关的IO控制文件和LED资源文件替换成适合变更芯片的目标板即可;如果只是目前板的LED控制引脚或LED数量发生了变化,则只需要将LED资源文件做相应的调整即可。
    本文的LED驱动分别提供了led_drv、imx6_gpio_drv和atk_board_led源文件。
  2. led_drv文件如下
/**
 * @file led_drv.c
 * @author glen (glen_cao@126.com)
 * @brief 
 * @version 0.1
 * @date 2021-12-24
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#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 
 */
void led_ops_register(struct led_ops *p_ops)
{
    if (p_ops) 
        gled_drv.ops = p_ops;
}

void led_device_create(int minor)
{
    /* 创建设备 */
    device_create(gled_drv.drv_class, NULL, MKDEV(gled_drv.major, minor), NULL, "gled%d", minor);
}

void led_device_destroy(int minor)
{
    /* 销毁设备 */
    device_destroy(gled_drv.drv_class, MKDEV(gled_drv.major, minor));
}

EXPORT_SYMBOL(led_ops_register);
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destroy);

/**
 * @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;

    /* 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;
    }
    return 0;

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

/**
 * @brief   : 驱动退出函数 
 * @par     : 无
 * @retval  : 无
 */
static void __exit led_drv_exit(void)
{
    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);

/* 模块的许可证声明 */
MODULE_LICENSE("GPL");
/* 模块的作者声明 */
MODULE_AUTHOR("glen");
  1. imx6_gpio_drv文件如下
/**
 * @file imx6_gpio_drv.c
 * @author glen (glen_cao@126.com)
 * @brief 
 * @version 0.1
 * @date 2021-12-24
 * 
 * @copyright Copyright (c) 2021
 * 
 */

#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"

static struct gled_device gled[16];

static void imx6_gled_ctrl(u8 idx, u8 status);
static int imx6_gled_init(u8 idx);
static int imx6_gled_exit(u8 idx);

static struct led_ops imx6_gpio_led_ops = {
    .num = 0,
    .led_init = imx6_gled_init,
    .led_exit = imx6_gled_exit, 
    .led_ctrl = imx6_gled_ctrl,
};

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

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

    if (status == ON) {
        val = readl(gled[idx].vir_io.dr);
        if (gled[idx].on_level == HIGH) {
            val |= (1 << (gled[idx].pin));
        } else if (gled[idx].on_level == LOW) {
            val &= ~(1 << gled[idx].pin);
        }
        writel(val, gled[idx].vir_io.dr);
        printk("The led%d is openned!\r\n", idx);
    } else if (status == OFF) {
        val = readl(gled[idx].vir_io.dr);
        if (gled[idx].on_level == HIGH) {
            val &= ~(1 << gled[idx].pin);
        } else if (gled[idx].on_level == LOW) {
            val |= (1 << (gled[idx].pin));
        }
        writel(val, gled[idx].vir_io.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 imx6_gled_init(u8 idx)
{
    u32 val;

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

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

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

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

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

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

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

    return 0;
}

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

    return 0;
}

/**
 * @brief 
 */
static int imx6_gpio_probe(struct platform_device *pdev)
{
    struct resource *res = NULL;
    u8 i = 0;

    while (1) {
        res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
        if (res == NULL) {
            break;
        }

        /* 如果将gled定义为指针数组, 则不需要复制, 将地址赋给对应指针即可 */
        memcpy(&gled[i], (const void *)(res->start), sizeof(struct gled_device));

        /* 创建设备节点 */
        led_device_create(i);
        i++;
    }
    imx6_gpio_led_ops.num = i;
    
    return 0;
}

static int imx6_gpio_remove(struct platform_device *pdev)
{
    u8 i;

    /* 销毁设备节点 */
    for (i = 0; i < imx6_gpio_led_ops.num; i++) {
        led_device_destroy(i);
    }
    imx6_gpio_led_ops.num = 0;

    return 0;
}

static struct platform_driver imx6_gpio_driver = {
    .driver = {
        .name = "gled",
    },
    .probe = imx6_gpio_probe,
    .remove = imx6_gpio_remove,
};

static int __init imx6_gpio_drv_init(void)
{
    int ret;

    ret = platform_driver_register(&imx6_gpio_driver);
    if (ret)
        pr_err("Unable to initialize imx6 gpio driver\n");
    else
        pr_info("The imx6 gpio driver is registered.\n");
    
    led_ops_register(&imx6_gpio_led_ops);

    return ret;
}

static void __exit imx6_gpio_drv_exit(void)
{
    platform_driver_unregister(&imx6_gpio_driver);
}
module_init(imx6_gpio_drv_init);
module_exit(imx6_gpio_drv_exit);

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

/* insert author information for module */
MODULE_AUTHOR("glen");
  1. atk_board_led文件如下
/**
 * @file atk_board_led.c
 * @author glen (glen_cao@126.com)
 * @brief 
 * @version 0.1
 * @date 2021-12-24
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#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"

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

static struct resource led_res[] = {
    {
        .start = (resource_size_t)&gled_dev[0],
        .flags = IORESOURCE_IRQ,
        .name = "atk_led_pin",
    }, {
        .start = (resource_size_t)&gled_dev[1],
        .flags = IORESOURCE_IRQ,
        .name = "atk_led_pin",
    },
};

/**
 * 怎么写程序
 * 分配/设置/注册platform_device结构体
 * 在里面定义所用资源,指定设备名字。
 */

static struct platform_device led_dev = {
    .name = "gled",
    .num_resources = ARRAY_SIZE(led_res),
    .resource = led_res,
};

static int __init led_dev_init(void)
{
	return platform_device_register(&led_dev);
}
module_init(led_dev_init);

static void __exit led_dev_exit(void)
{
    platform_device_unregister(&led_dev);
}
module_exit(led_dev_exit);

/* 模块的许可证声明 */
MODULE_LICENSE("GPL");
/* 模块的作者声明 */
MODULE_AUTHOR("glen");
  1. led_dev头文件
/**
 * @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>

#define HIGH  1
#define LOW   0

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

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

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 {
    u8 dft_status; /* 默认状态: 打开或关闭 */
    u8 on_level;   /* 打开时电平: 高或低 */
    u8 clk_shft_bits;
    u32 pin;       /* led引脚信息 */
    struct vir_regs vir_io;
    struct phy_regs phy_io;
};

void led_ops_register(struct led_ops *p_ops);
void led_device_create(int minor);
void led_device_destroy(int minor);

#endif // !__LED_DEV_H__
  1. Makefile文件稍作更改,将三个源文件编译成独立的模块
KERNELDIR	:= /home/glen/linux/imx6ull/linux/glen_linux
CURRENT_PATH	:= $(shell pwd)
#led_driver-y	:= led_drv.o led_dev.o
obj-m	+= led_drv.o atk_board_led.o imx6_gpio_drv.o
build:	kernel_modules

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

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
  1. 测试程序不做更改,验证参考Linux学习笔记(15)——LED设备驱动
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值