After understanding the general process and basic introduction of character device driver development, let's carry out a specific character device driver development.
Just like our bare metal development or STM32 development based on the ARM core, we develop from the easy to the difficult. For example, when we operate the LED, in STM32, we actually initialize the registers and then perform specific register operations to turn the LED on and off. The LED driver written based on Linux must comply with the Linux driver framework.
The corresponding LED control port in ATK-CLMP135B is PI3:
When we are doing bare metal development or controlling stm32, we usually don't pay much attention to the location of the physical space. But at this time, we have to pay attention to one problem when doing Linux development. Address mapping, we must first understand the main functions of the memory management unit mmu. One is the mapping of virtual space and physical space, which is the specific function completed by the driver. The user layer does not need to consider the specific physical address of the device, but the specific address of the device is a direct operation of the peripherals, realizing the mapping of the user layer and the physical layer. The second function is memory protection, setting the access rights of the memory, and setting the buffer characteristics of the virtual storage space.
Due to the limitation of address mapping, there is usually a situation where there are many virtual addresses to one physical address. The problem of virtual address being larger than the physical address range is handled by the processor. When the kernel starts, the memory management unit will be initialized and the memory mapping will be set. All future CPU accesses are virtual addresses. At this time, we can use the ioremap function to obtain the virtual address corresponding to the specified physical address space. The actual operation of the application layer is also through the operation of the virtual address space. When uninstalling the driver, we also need to use the iounmap function to release the previous mapping.
The LED driver is actually the high and low level control of the IO port. Here, the IO input and output are not the gpio pins we talked about when we studied microcontrollers. There are two concepts involved here, IO port and IO memory. When an external register or memory is mapped to the IO space, it is called a port, and when a register or memory is mapped to the memory space, it is called memory. After the physical address of the register is mapped to the virtual address through the ioremap function, these addresses can be directly accessed through pointers. The Linux kernel recommends using the operation function to read and write the mapped memory.
Next, write the LED program:
head File:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
Macro Definition
#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 寄存器物理地址 */
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER (GPIOI_BASE + 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE + 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE + 0x000C)
#define GPIOI_BSRR (GPIOI_BASE + 0x0018)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;
function
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIOI_BSRR_PI);
val |= (1 << 19);
writel(val, GPIOI_BSRR_PI);
}else if(sta == LEDOFF) {
val = readl(GPIOI_BSRR_PI);
val|= (1 << 3);
writel(val, GPIOI_BSRR_PI);
}
}
void led_unmap(void)
{
/* 取消映射 */
iounmap(MPU_AHB4_PERIPH_RCC_PI);
iounmap(GPIOI_MODER_PI);
iounmap(GPIOI_OTYPER_PI);
iounmap(GPIOI_OSPEEDR_PI);
iounmap(GPIOI_PUPDR_PI);
iounmap(GPIOI_BSRR_PI);
}
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
led_switch(LEDON); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
led_switch(LEDOFF); /* 关闭LED灯 */
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);
/* 2、使能PI时钟 */
val = readl(MPU_AHB4_PERIPH_RCC_PI);
val &= ~(0X1 << 8); /* 清除以前的设置 */
val |= (0X1 << 8); /* 设置新值 */
writel(val, MPU_AHB4_PERIPH_RCC_PI);
/* 3、设置PI3通用的输出模式。*/
val = readl(GPIOI_MODER_PI);
val &= ~(0X3 << 3); /* bit0:1清零 */
val |= (0X1 << 3); /* bit0:1设置01 */
writel(val, GPIOI_MODER_PI);
/* 3、设置PI3为推挽模式。*/
val = readl(GPIOI_OTYPER_PI);
val &= ~(0X1 << 3); /* bit0清零,设置为上拉*/
writel(val, GPIOI_OTYPER_PI);
/* 4、设置PI3为高速。*/
val = readl(GPIOI_OSPEEDR_PI);
val &= ~(0X3 << 3); /* bit0:1 清零 */
val |= (0x2 << 3); /* bit0:1 设置为10*/
writel(val, GPIOI_OSPEEDR_PI);
/* 5、设置PI3为上拉。*/
val = readl(GPIOI_PUPDR_PI);
val &= ~(0X3 << 3); /* bit0:1 清零*/
val |= (0x1 << 3); /*bit0:1 设置为01*/
writel(val,GPIOI_PUPDR_PI);
/* 6、默认关闭LED */
val = readl(GPIOI_BSRR_PI);
val |= (0x1 << 3);
writel(val, GPIOI_BSRR_PI);
/* 6、注册字符设备驱动 */
retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if(retvalue < 0) {
printk("register chrdev failed!\r\n");
goto fail_map;
}
return 0;
fail_map:
led_unmap();
return -EIO;
}
static void __exit led_exit(void)
{
/* 取消映射 */
led_unmap();
/* 注销字符设备驱动 */
unregister_chrdev(LED_MAJOR, LED_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
Is it very similar to bare metal driver development? The main functions are LED initialization, LED operation, device registration and deregistration, device opening and releasing, etc. The main difference is that Linux driver development must be carried out under its development framework.