Led驱动实验之Led灯初始化

一.  简介

本文继上一篇文章的学习,上一篇文章初步写好了 Led驱动框架代码的。

文章地址如下:

LED驱动框架代码的实现-CSDN博客

本文继续 Led灯驱动代码的实现,主要学习编写 Led灯 IO的初始化工作。

二.  Led驱动的 IO 初始化说明

1.  地址映射

前面进行 Led灯裸机开发实验时,关于 Led的 IO初始化工作包括如下:

1. 使能时钟信号,即设置 Led灯相关的时钟IO口 
2. 复用功能,即设置为 GPIO功能
3. 配置电气属性
4. 设置为输出功能

同理,LED驱动实验中,关于 IO初始化工作也如上。IO初始化工作就是向相关寄存器写入数据。Linux系统有 MMU(memory manage unit)的存在,所以Linux 不能直接访问寄存器的物理地址,MMU会负责将寄存器的物理地址映射为虚拟地址。

所以,Linux驱动开发中,Linux 访问的是 寄存器的虚拟地址值。这里就需要进行地址映射,即将Led灯 IO初始化相关的寄存器物理地址映射为虚拟地址。

参考之前 Led裸机实验,可以找到 Led涉及的寄存器的物理地址,如下:

//寄存器物理地址
#define  CCM_CCGR1_BASE          (0X020C406C)
#define  SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define  SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define  GPIO1_GDIR_BASE         (0X0209C004)
#define  GPIO1_DR_BASE           (0X0209C000)

地址映射函数

进行地址映射时,会调用到  ioremap() 函数,定 义 在 内核源码 arch/arm/include/asm/io.h 文件中,定义如下:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), 
MT_DEVICE)
 
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, 
unsigned int mtype)
{
 return arch_ioremap_caller(phys_addr, size, mtype,
 __builtin_return_address(0));
}

从上面函数返回值可以看出,这里需要定义一组 __iomem* 类型的指针,存放地址映射后的虚拟地址值:

//地址映射后的虚拟地址指针
static __iomem *  IMX6ULL_CCM_CCGR1;
static __iomem *  IMX6ULL_SW_MUX_GPIO1_IO03;
static __iomem *  IMX6ULL_SW_PAD_GPIO01_IO03;
static __iomem *  IMX6ULL_GDIR;
static __iomem *  IMX6ULL_DR;

关于 ioremap函数的参数传递,之前做过介绍:

Linux系统中的地址映射-CSDN博客

地址映射工作写在 led_init() 函数中,模块卸载接口 led_exit()接口中要进行取消,即取消地址映射。

2.  IO 初始化工作

led灯 IO初始化工作,一般写在 驱动模块加载接口,或者 打开设备接口中。

注意:使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议 这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

前面有过介绍:需要调用 readl()函数与 writel()函数。

IO内存访问函数-CSDN博客

这里我写在 加载驱动模块接口中,这里即写在 led_init() 函数中。Led灯 IO 初始化工作包括如下:

(1)使能 Led时钟

参考 Led灯硬件原理图可知, Led灯所使用 IO口是 GPIO01_IO03。结合 IMX6ULL芯片参考手册知道,所以,Led的 使能时钟引脚为 CCM_CCGR1寄存器的 26~27位:

(2)复用为 GPIO功能。

(3)配置电气属性。

(4)设置为输出功能。

以上 Led 的IO初始化工作,可以参考 Led 裸机开发代码的配置。

三.  代码实现

实现 2_led工程中 IO初始化工作。开始写代码之前需要我自己定一个主设备号,这个主设备号是开发板上的字符设备未使用的,所以,需要查看开发板上所有字符设备的设备号,开发板上电后进入根文件系统,调试串口输入如下即可查看字符设备的设备号:

/ # cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 29 fb
 81 video4linux
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
207 ttymxc
226 drm
250 ttyLP
251 watchdog
252 ptp
253 pps
254 rtc
.....................................

可以看到开发板上所有的字符设备的设备号,可以确定 200 作为主设备号没有字符设备使用, 所以可以选择 200作为字符设备的主设备号,是可以。

led.c 文件中代码实现如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define  LED_MAJOR   200   //Led的主设备号
#define  LED_NAME    "led" //Led灯的名字

//寄存器物理地址
#define  CCM_CCGR1_BASE          (0X020C406C)
#define  SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define  SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define  GPIO1_GDIR_BASE         (0X0209C004)
#define  GPIO1_DR_BASE           (0X0209C000)

//地址映射后的虚拟地址指针
static void __iomem *  IMX6ULL_CCM_CCGR1;
static void __iomem *  IMX6ULL_SW_MUX_GPIO1_IO03;
static void __iomem *  IMX6ULL_SW_PAD_GPIO01_IO03;
static void __iomem *  IMX6ULL_GDIR;
static void __iomem *  IMX6ULL_DR;

 //打开Led设备
static int led_open(struct inode *inode, struct file *file)
{
    return 0;
}

// 向Led设备写数据
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    return 0;
}

 //关闭/释放设备
static int led_release(struct inode *inode, struct file *file)
{
    return 0;
}

static const struct file_operations led_fops=
{
	.owner = THIS_MODULE,
	.open = led_open,
    .write = led_write,
	.release = led_release,
 };

//Led驱动模块入口函数
static int __init led_init(void)
{  
    int ret = 0;
    unsigned int value = 0;
    printk("led_init!\r\n");

    /* Led灯的IO初始化 */
    //地址映射
    IMX6ULL_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    IMX6ULL_SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    IMX6ULL_SW_PAD_GPIO01_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    IMX6ULL_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
    IMX6ULL_DR = ioremap(GPIO1_DR_BASE, 4);

    //使能 Led时钟
    value = readl(IMX6ULL_CCM_CCGR1);
    value &= ~(3 << 26);
    value |= (3 << 26);
    writel(value, IMX6ULL_CCM_CCGR1);

    //复用为 GPIO功能
    writel(0x05, IMX6ULL_SW_MUX_GPIO1_IO03);

    //配置电气属性
    writel(0X10B0, IMX6ULL_SW_PAD_GPIO01_IO03);

    //设置为输出功能
    value = readl(IMX6ULL_GDIR);
    value |= (1 << 3);
    writel(value, IMX6ULL_GDIR);

    //设置为低电平,打开 Led灯
    value = readl(IMX6ULL_DR);
    value &= ~(1 << 3);
    writel(value, IMX6ULL_DR);

    //注册字符设备
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
	if(ret < 0)
    {
        printk("led device register failed!\r\n");
        return -EIO;
    }		  
    return 0;
}

//Led驱动模块出口函数
static void __exit led_exit(void)
{
    unsigned int value = 0;
    printk("led_exit!\r\n");
    value = readl(IMX6ULL_DR);  
    value |= (1 << 3);         //bit3置1,关闭Led灯
    writel(value, IMX6ULL_DR);

    //取消地址映射
    iounmap(IMX6ULL_CCM_CCGR1);
    iounmap(IMX6ULL_SW_MUX_GPIO1_IO03);
    iounmap(IMX6ULL_SW_MUX_GPIO1_IO03);
    iounmap(IMX6ULL_GDIR);
    iounmap(IMX6ULL_DR);

    //卸载字符设备
    unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init); //入口
module_exit(led_exit); //出口

MODULE_LICENSE("GPL"); //模块 licence
MODULE_AUTHOR("lingxuewu"); //模块作者

下一篇文章编译 这里的代码,加载 Led驱动模块,打开或关闭 Led灯。

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
字符驱动设备控制 LED 需要使用 GPIO 接口来控制引脚的状态。在 Linux 内核中,可以使用 gpio_leds_probe 函数来初始化引脚的状态: ``` static int __init gpio_leds_probe(struct platform_device *pdev) { int ret = 0; int i; struct gpio_led *led = pdev->dev.platform_data; for (i = 0; i < ARRAY_SIZE(gpio_leds); i++) { gpio_direction_output(gpio_leds[i].gpio, !gpio_leds[i].default_state); ret = gpio_request(gpio_leds[i].gpio, "gpio_leds"); if (ret < 0) { pr_err("gpio_request failed for pin %d\n", gpio_leds[i].gpio); goto err_gpio_request; } gpio_leds[i].cdev.name = "gpio-leds"; gpio_leds[i].cdev.brightness_get = gpio_leds_get_brightness; gpio_leds[i].cdev.brightness_set = gpio_leds_set_brightness; gpio_leds[i].cdev.default_trigger = led->default_trigger; gpio_leds[i].cdev.brightness = !gpio_leds[i].default_state; ret = gpio_led_classdev_register(&pdev->dev, &gpio_leds[i].cdev); if (ret < 0) { pr_err("gpio_led_classdev_register failed for pin %d\n", gpio_leds[i].gpio); goto err_gpio_led_classdev_register; } } return 0; err_gpio_led_classdev_register: err_gpio_request: for (i = 0; i < ARRAY_SIZE(gpio_leds); i++) { gpio_free(gpio_leds[i].gpio); } return ret; } ``` 该函数会遍历一个 gpio_leds 数组,该数组包含了需要控制的 LED 的引脚信息。对于每个引脚,函数会使用 gpio_direction_output 函数来设置其方向为输出,并设置其初始状态为默认状态的反向(因为 LED 是低电平点亮的,所以默认状态为高电平)。然后使用 gpio_request 函数来申请引脚,如果失败则返回错误码。接下来,函数会创建一个 gpio_led 结构体,并设置该结构体的成员变量。最后,使用 gpio_led_classdev_register 函数来注册字符设备驱动。如果注册失败,则会释放申请的引脚并返回错误码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值