Linux内核IOREMAP驱动
1 Linux内核IOREMAP驱动
在内核驱动的代码中,存在大量代码使用ioremap进行物理地址和虚拟地址映射,使得内核更加容易操作硬件,对比于简单的gpio控制,实际的代码同样是使用了ioremap进行映射封装。
2 ITOP4412示例
2.1 硬件手册
- 本次测试使用LED3(同理LED2),查看原理图或设备树可知 具体引脚号。
- 当前使用的为LED3,使用时需要将设备树使用到的节点进行删除。因为设备树会优先占用当前使用的GPIO,需要将GPIO的资源释放。
leds {compatible = "gpio-leds";led2 {label = "red:system";gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;default-state = "off";linux,default-trigger = "heartbeat";};/*led3 {label = "red:user";gpios = <&gpk1 1 GPIO_ACTIVE_HIGH>;default-state = "off";};*/};
- 管脚寄存器说明《SEC_Exynos 4412 SCP_Users Manual_Ver.0.10.00_Preliminary.pdf》
- 查看原理图知道我们操作的是GPK1_1,则只要操控如下寄存器值。
*GPK1CON &= 0xFFFFFF0F;
*GPK1CON |= 0x00000010;
- 查看原理图知道我们操作的是GPK1_1,则只要操控如下寄存器值。
*GPK1PUD |= 0x000C;
- 查看原理图知道我们操作的是GPK1_1,则只要操控如下寄存器值。
计算公式稍微复杂,但n就等于gpk1_n
*GPK1DAT &= 0x3D;
*GPK1DAT |= 0x02;
2.2 软件示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <asm/io.h>#define PRINTKERR(fmt, args...) \do { \printk(KERN_ERR "%-10s: ***cdev_gpio ERROR*** (%s,%d) %s: " fmt,\(in_interrupt() ? "interrupt" : (current)->comm), \__FILE__, __LINE__, __FUNCTION__ , ##args); \} while(0)#define PRINTKINFO(fmt, args...) \do { \printk(KERN_INFO "cdev_gpio: " fmt , ## args); \} while(0)static dev_t cdev_gpio_devno;
static int cdev_gpio_major = 0;
static struct cdev *pcdev_gpio_cdev = NULL;
static struct class *pcdev_gpio_class = NULL;
static struct device *pcdev_gpio_device = NULL;static int cdev_gpio_open (struct inode *inode, struct file *file)
{printk("cdev_gpio_open!!!\n");return 0;
}static ssize_t cdev_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{printk("cdev_gpio_read!!!\n");return 0;
}static ssize_t cdev_gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{printk("gpioi2c_write!!!\n");return 0;
}static long cdev_gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{printk("cdev_gpio_ioctl!!!\n");return 0;
}static int cdev_gpio_release (struct inode *inode, struct file *file)
{printk("cdev_gpio_release!!!\n");return 0;
}static struct file_operations ioaccess_fops = {.owner = THIS_MODULE,.open = cdev_gpio_open,.read = cdev_gpio_read,.write = cdev_gpio_write,.unlocked_ioctl = cdev_gpio_ioctl,.release = cdev_gpio_release,
};volatile unsigned long virt_addr, phys_addr;
volatile unsigned long *GPK1CON, *GPK1DAT, *GPK1PUD;static ssize_t show_cdev_gpio_status(struct device *dev, struct device_attribute *attr, char *buf)
{int status = 0;status = (*GPK1DAT & 0x02) >> 1;sprintf(buf, "%u\n", status);return strlen(buf);
}static ssize_t store_cdev_gpio_status(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{int cfg, c;c = sscanf(buf, "%d", &cfg);if( c >= 1 ){if( cfg > 0 )*GPK1DAT |= 0x02;else*GPK1DAT &= 0x3d;}return count;
}static DEVICE_ATTR(cdev_gpio_status, (S_IRUGO | S_IWUSR), show_cdev_gpio_status, store_cdev_gpio_status);static int __init cdev_gpio_init(void)
{int ret = 0;/* 注册设备号 */ret = alloc_chrdev_region(&cdev_gpio_devno, cdev_gpio_major, 1/*count*/, KBUILD_MODNAME);if (ret) {PRINTKERR("alloc_chrdev_region failed!\n");return ret;}PRINTKINFO("alloc_chrdev_region cdev_gpio_major %d cdev_gpio_minor %d devno %d!\n",MAJOR(cdev_gpio_devno),MINOR(cdev_gpio_devno),cdev_gpio_devno);/* 初始化cdev结构,赋值file_operations结构 */pcdev_gpio_cdev = cdev_alloc();if (IS_ERR(pcdev_gpio_cdev)) {ret = PTR_ERR(pcdev_gpio_cdev);goto err_unregister_devno;}cdev_init(pcdev_gpio_cdev, &ioaccess_fops);pcdev_gpio_cdev->owner = THIS_MODULE;ret = cdev_add(pcdev_gpio_cdev, cdev_gpio_devno, 1);if (ret) {PRINTKERR("cdev add fail!\n");goto err_unregister_cdev;}/* 创建设备类/sys/class/xxx/ */pcdev_gpio_class = class_create(THIS_MODULE, KBUILD_MODNAME);if (IS_ERR(pcdev_gpio_class)){PRINTKERR("failed to create class %s", "cdev_gpio");ret = PTR_ERR(pcdev_gpio_class);goto err_unregister_cdev;}/* 创建生成设备类节点 */pcdev_gpio_device = device_create(pcdev_gpio_class, NULL, cdev_gpio_devno, NULL, KBUILD_MODNAME);if (IS_ERR(pcdev_gpio_device)){PRINTKERR("failed to create device %s", "cdev_gpio");ret = PTR_ERR(pcdev_gpio_device);goto err_free_class;}/* 创建sysfs接口 */device_create_file(pcdev_gpio_device,&dev_attr_cdev_gpio_status);/* 转换物理地址 */phys_addr = 0x11000060;virt_addr = (unsigned long)ioremap(phys_addr, 0x10);GPK1CON = (unsigned long *)(virt_addr + 0x00);GPK1DAT = (unsigned long *)(virt_addr + 0x04);GPK1PUD = (unsigned long *)(virt_addr + 0x08);*GPK1CON &= 0xFFFFFF0F;*GPK1CON |= 0x00000010;*GPK1PUD |= 0x000C;*GPK1DAT &= 0x3D;printk(KERN_INFO "cdev_gpio_init.\n");return 0;err_free_class:class_destroy(pcdev_gpio_class);
err_unregister_cdev:cdev_del(pcdev_gpio_cdev);
err_unregister_devno:unregister_chrdev_region(cdev_gpio_devno, 1);return ret;
}static void __exit cdev_gpio_exit(void)
{if (pcdev_gpio_class != NULL && pcdev_gpio_device != NULL ) {device_destroy(pcdev_gpio_class, cdev_gpio_devno);class_destroy(pcdev_gpio_class);pcdev_gpio_class = NULL;}cdev_del(pcdev_gpio_cdev);unregister_chrdev_region(cdev_gpio_devno, 1);printk(KERN_INFO "cdev_gpio_exit.\n");
}module_init(cdev_gpio_init);
module_exit(cdev_gpio_exit);MODULE_LICENSE("GPL"); /*指定代码许可证*/
MODULE_AUTHOR("creed"); /*指定作者*/
MODULE_VERSION("V1.00.00"); /*指定代码修订号*/
MODULE_DESCRIPTION("sample_led for modules");
3 获取gpio序号
3.1 debugfs获取gpio序号
Symbol: DEBUG_FS [=y]Prompt: Debug FilesystemDefined at lib/Kconfig.debug:77Depends on: SYSFS Location:-> Kernel configuration-> Kernel hacking
mount -t debugfs none /sys/kernel/debug
获取后从如下的gpio打印中可以看出GPIO号,并且是否被占用
[root@itop4412 /]# cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-7, parent: platform/11400000.pinctrl, gpa0:gpiochip1: GPIOs 8-13, parent: platform/11400000.pinctrl, gpa1:gpiochip2: GPIOs 14-21, parent: platform/11400000.pinctrl, gpb:gpiochip3: GPIOs 22-26, parent: platform/11400000.pinctrl, gpc0:gpio-23 ( |ehci_vbus_gpio ) out hi gpiochip4: GPIOs 27-31, parent: platform/11400000.pinctrl, gpc1:
3.2 dmesg的GPIO加载
dmesg | grep gpio
从如下打印可以看出与方法一致
[root@itop4412 /]# dmesg | grep gpio
[ 0.431932] gpio gpiochip0: (gpa0): added GPIO chardev (254:0)
[ 0.431947] gpiochip_setup_dev: registered GPIOs 0 to 7 on device: gpiochip0 (gpa0)
[ 0.432223] gpio gpiochip1: (gpa1): added GPIO chardev (254:1)
[ 0.432234] gpiochip_setup_dev: registered GPIOs 8 to 13 on device: gpiochip1 (gpa1)
[ 0.432510] gpio gpiochip2: (gpb): added GPIO chardev (254:2)
[ 0.432520] gpiochip_setup_dev: registered GPIOs 14 to 21 on device: gpiochip2 (gpb)
[ 0.432779] gpio gpiochip3: (gpc0): added GPIO chardev (254:3)
[ 0.432790] gpiochip_setup_dev: registered GPIOs 22 to 26 on device: gpiochip3 (gpc0)
[ 0.433054] gpio gpiochip4: (gpc1): added GPIO chardev (254:4)
[ 0.433065] gpiochip_setup_dev: registered GPIOs 27 to 31 on device: gpiochip4 (gpc1)
[ 0.433324] gpio gpiochip5: (gpd0): added GPIO chardev (254:5)
[ 0.433335] gpiochip_setup_dev: registered GPIOs 32 to 35 on device: gpiochip5 (gpd0)
[ 0.433599] gpio gpiochip6: (gpd1): added GPIO chardev (254:6)
[ 0.433610] gpiochip_setup_dev: registered GPIOs 36 to 39 on device: gpiochip6 (gpd1)
[ 0.433869] gpio gpiochip7: (gpf0): added GPIO chardev (254:7)
[ 0.433880] gpiochip_setup_dev: registered GPIOs 40 to 47 on device: gpiochip7 (gpf0)
[ 0.434146] gpio gpiochip8: (gpf1): added GPIO chardev (254:8)
[ 0.434157] gpiochip_setup_dev: registered GPIOs 48 to 55 on device: gpiochip8 (gpf1)
3.3 内核gpio操作示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>#define PRINTKERR(fmt, args...) \do { \printk(KERN_ERR "%-10s: ***gpio ERROR*** (%s,%d) %s: " fmt,\(in_interrupt() ? "interrupt" : (current)->comm), \__FILE__, __LINE__, __FUNCTION__ , ##args); \} while(0)#define PRINTKINFO(fmt, args...) \do { \printk(KERN_INFO "gpio: " fmt , ## args); \} while(0)/*
*[ 0.437965] gpiochip_setup_dev: registered GPIOs 90 to 96 on device: gpiochip14 (gpk1)
*/
#define GPK1_1_GPIO_NUMBER 91static void platform_device_gpio_release(struct device *dev)
{printk("platform_device_gpio_release \n");return;
}static struct platform_device platform_device_gpio = {.name = KBUILD_MODNAME, /* should be the same name as driver_name */.id = -1,.dev = {.release = platform_device_gpio_release,},
};static ssize_t show_cdev_gpio_status(struct device *dev, struct device_attribute *attr, char *buf)
{printk("show_cdev_gpio_status!!!\n");return 0;
}static ssize_t store_cdev_gpio_status(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{int cfg, c;c = sscanf(buf, "%d", &cfg);if( c >= 1 ){if( cfg > 0 )gpio_set_value(GPK1_1_GPIO_NUMBER, 0);elsegpio_set_value(GPK1_1_GPIO_NUMBER, 1);}return count;
}static DEVICE_ATTR(cdev_gpio_status, (S_IRUGO | S_IWUSR), show_cdev_gpio_status, store_cdev_gpio_status);static struct attribute *dev_attrs[] = {&dev_attr_cdev_gpio_status.attr,NULL
};struct attribute_group dev_attr_group = {.attrs = dev_attrs,
};static int __init platform_device_gpio_init(void)
{int ret = 0;/* 检测GPIO是否合法 */if (!gpio_is_valid(GPK1_1_GPIO_NUMBER)) {printk(KERN_ERR "gpio is unavailable.\n");return -1;}/* 申请GPIO口,参数一:申请的端口号;参数二:申请后的GPIO标识 */ret = gpio_request(GPK1_1_GPIO_NUMBER, "gpio_led");if ( ret < 0 ) {printk(KERN_ERR "gpio request error.\n");return -1;}/* 设置GPIO方向,具体点亮需要结合硬件 */ret= gpio_direction_output(GPK1_1_GPIO_NUMBER, 1);if ( ret < 0 ) {PRINTKERR("gpio direct output error");goto err_free_gpio;}/* 创建sysfs接口 */ret = platform_device_register(&platform_device_gpio);if ( ret < 0 ) {PRINTKERR("failed to platform_device_register %s", "device_gpio");goto err_free_gpio;}if (sysfs_create_group(&platform_device_gpio.dev.kobj, &dev_attr_group)) {PRINTKERR("can't create sysfs files\n");goto err_unregister_platform_device;}printk(KERN_INFO "platform_device_gpio_init.\n");return 0;err_unregister_platform_device:platform_device_unregister(&platform_device_gpio);
err_free_gpio:gpio_free(GPK1_1_GPIO_NUMBER);return ret;
}static void __exit platform_device_gpio_exit(void)
{platform_device_unregister(&platform_device_gpio);printk(KERN_INFO "platform_device_gpio_exit.\n");
}module_init(platform_device_gpio_init);
module_exit(platform_device_gpio_exit);MODULE_LICENSE("GPL"); /*指定代码许可证*/
MODULE_AUTHOR("creed"); /*指定作者*/
MODULE_VERSION("V1.00.00"); /*指定代码修订号*/
MODULE_DESCRIPTION(" gpio control for modules");
4 sysfs接口控制gpio
确认是否开启了sysfs方式控制gpio,查看系统是否有/sys/class/gpio这个文件夹,假如没有进入内核则Device Drivers-> GPIO Support
#system ready set gpioe7 high level
echo 39 > /sys/class/gpio/exportc
echo out > /sys/class/gpio/gpio39/direction
echo 1 > /sys/class/gpio/gpio39/value
关于的/sys/class/gpio/用法
- export:用于通知系统需要导出控制的GPIO引脚编号
- unexport:用于通知系统取消导出c
- gpiochipx:目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号base,寄存器名称,引脚总数 导出一个引脚的操作
/ # ls /sys/class/gpio/gpio39/
active_low device/ direction edge subsystem/ uevent value
- direction:in、out、high、low,high/low同时设置方向为输出,并将value设置为相应的1/0
- value:0/1
5 ioremap解析
arch\arm\include\asm\io.h
void __iomem *ioremap(resource_size_t res_cookie, size_t size);
在开发过程中ioremap操作过程充斥格式各样的读写函数,对比每一种函数怎么使用显得尤为重要。
void __iomem *gpio_io_base;
include/linux/compile.h
# define __iomem __attribute__((noderef, address_space(2)))
v: 0 内核空间
v: 1 用户空间
v: 2 io存储空间
v: 3 cpu空间
5.1 读函数
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
unsigned char readl (unsigned int addr);
readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )
5.2 写函数
unsigned int iowrite8(u8 value, void *addr);
unsigned int iowrite16(u8 value, void *addr);
unsigned int iowrite32(u8 value, void *addr);
void writel (unsigned char data , unsigned short addr);
writel() 往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节)
5.3 内核gpio模拟I2C
#define SET_SCL0_1 gpio_set_value(AST1500_GPJ3,1)
#define SET_SDA0_1 gpio_set_value(AST1500_GPJ2,1)#define SET_SCL0_0 gpio_set_value(AST1500_GPJ3,0)
#define SET_SDA0_0 gpio_set_value(AST1500_GPJ2,0)#define GET_SDA0 gpio_get_value(AST1500_GPJ2)#define SCL0_IN gpio_cfg(AST1500_GPJ3,AST1500_GPIO_INPUT)
#define SCL0_OUT gpio_cfg(AST1500_GPJ3,AST1500_GPIO_OUTPUT)#define SDA0_IN gpio_cfg(AST1500_GPJ2,AST1500_GPIO_INPUT)
#define SDA0_OUT gpio_cfg(AST1500_GPJ2,AST1500_GPIO_OUTPUT)void I2CDelay(unsigned short delay)
{unsigned short j = 0;for(j=0;j<delay;j++);return;
}/*
PB0-1: index 0 I2C 0:SDA, 1:SCL
PB2-3: index 1 I2C 2:SDA, 3:SCL
PB4-5: index 2 I2C 4:SDA, 5:SCL
*/void ManReleaseScl(unsigned char index)
{switch(index){case 0:SCL0_IN;break;}}void ManDriveScl(unsigned char index)
{switch(index){case 0:SCL0_OUT;break;}}void ManReleaseSda(unsigned char index)
{switch(index){case 0:SDA0_IN;break;}}void ManDriveSda(unsigned char index)
{switch(index){case 0:SDA0_OUT;break;}}void ManSclHigh(unsigned char index)
{switch(index){case 0:SET_SCL0_1;break;}
}void ManSclLow(unsigned char index)
{switch(index){case 0:SET_SCL0_0;break;}
}void ManSdaHigh(unsigned char index)
{switch(index){case 0:SET_SDA0_1;break;}
}void ManSdaLow(unsigned char index)
{switch(index){case 0:SET_SDA0_0;break;}
}unsigned char GetSdaValue(unsigned char index)
{unsigned int value = 0;switch(index){case 0:value = GET_SDA0;break;}if(value)value = 1;elsevalue = 0;return value;
}
5.4 内核writel/readl模拟I2C
#define IO_ADDRESS_GPIO(pin) ( (void __iomem*)AST1500_GPIO_CON(pin) )
#define IO_ADDRESS_GPIO_OFFSET(pin) ( AST1500_GPIO_OFFSET(pin) )
#define SCL AST1500_GPJ3
#define SDA AST1500_GPJ2#define DELAY(us) udelay(us) //time_delay_us(us)static spinlock_t gpioi2c_lock;static void i2c_clr(unsigned char whichline)
{unsigned long regvalue;unsigned long mask;unsigned int direction;unsigned long dat;unsigned long offs = IO_ADDRESS_GPIO(SCL);regvalue = __raw_readl(IO_ADDRESS_GPIO(SCL));mask = 1 << IO_ADDRESS_GPIO_OFFSET(SCL);direction = 1 << IO_ADDRESS_GPIO_OFFSET(SCL);regvalue &= ~mask;regvalue |= direction;__raw_writel(regvalue, IO_ADDRESS_GPIO(SCL));//__raw_writel(0, IO_ADDRESS_GPIO(SCL));dat = __raw_readl(IO_ADDRESS_GPIO(SCL));dat &= ~(1 << offs);dat |= 0 << offs;__raw_writel(dat, IO_ADDRESS_GPIO(SCL));#if 0if(whichline == SCL){regvalue = __raw_readl(IO_ADDRESS_GPIO(SCL));return;}else if(whichline == SDA){regvalue = __raw_readl(IO_ADDRESS_GPIO(SDA));return;}else if(whichline == (SDA|SCL)){return;}else{cprintk("Error input.\n");return;}#endif
}
6 参考资料
操作硬件IO内存
不同的CPU架构提供不同的IO和内存管理
CPU如何通过MMU访问到物理地址
7 资源链接
https://download.csdn.net/download/weixin_44143206/85032052
Linux内核IOREMAP驱动相关推荐
- Linux内核网络设备驱动
本文首先从宏观上介绍数据包的接收过程,然后详细介绍了Linux网络设备驱动的工作过程,最后介绍网卡监控与调优,包括网络数据包总数.丢包.错包数量的相关统计. 1. 接收数据包过程概述 介绍数据包收包过 ...
- Android系统 linux内核按键驱动开发
Android系统 linux内核按键驱动开发 前言 刚入门的小白,在csdn的帮助下完成了第一个按键驱动,特写此文记录学习并分享给有需要的人. 1.修改设备树.dts 我是用的开发板是rp-rk32 ...
- linux内核led驱动开发,从Linux内核LED驱动来理解字符设备驱动开发流程
目录 博客说明 开发环境 1. Linux字符设备驱动的组成 1.1 字符设备驱动模块加载与卸载函数 1.2 字符设备驱动的file_operations 结构体中的成员函数 2. 字符设备驱动--设 ...
- 【嵌入式环境下linux内核及驱动学习笔记-(16)linux总线、设备、驱动模型之input框架】
目录 1.Linux内核输入子系统概念导入 1.1 输入设备工作机制 1.2 运行框架 1.3 分层思想 2.驱动开发步骤 2.1 在init()或probe()函数中 2.2 在exit()或rem ...
- 【嵌入式环境下linux内核及驱动学习笔记-(15-1)例程】
目录 1.在APP直接调用标准文件IO操作I2C(针对学习笔记-15的15.3节) 1.1 mail.c 1.2 mpu6050.h 1.3 mpu6050.c 1.4 Makefile 2.以外称i ...
- 深入讲解Linux内核网络设备驱动(图例解析)
1. 接收数据包过程概述 介绍数据包收包过程,有助于我们了解Linux内核网络设备在数据收包过程中的位置,下面从宏观的角度介绍数据包从被网卡接收到进入 socket 接收队列的整个过程: 加载网卡驱动 ...
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的I2C驱动架构分析
PowerPC + Linux2.6.25平台下的I2C驱动架构分析 Sailor_forever sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人b ...
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的SPI驱动架构分析
PowerPC + Linux2.6.25平台下的SPI驱动架构分析 Sailor_forever sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人b ...
- 面经——Linux内核及驱动
VFS https://www.linuxprobe.com/linux-vfs.html 三种设备类型 字符设备:字符设备是能够以字节流形式被访问的设备,字符设备驱动程序通常至少实现open,clo ...
最新文章
- ​Xamarin iOS教程之自定义视图
- 麦子mysql_[数据库]MySQL基础 (麦子学员 php 第二阶段)
- 大数——大数阶乘(hdu1042)
- 分析java程序的命令总结jps,jstack
- persevere的用法_persevere的用法是什么
- 科技经济调整td-scdma中国
- Pearson 相关分析 理论与应用
- google地图距离算法_谷歌地图计算两个坐标点的距离函数
- android ps4 远程,手机上玩PS4游戏!Remote Play扩大至所有Android设备
- buu刷题日记 asis2016_b00ks
- 数据库原理与应用实验3--(数据库的简单查询和连接查询)
- web前端开发的6个福利网站
- sql简介香气和sql简介_香气和SQL简介
- Golang Tools
- HTML笔记及案例(全)
- Mogafx澳元技术性预测
- 阿里巴巴 Excel工具easyExcel
- ERP实施工程师必备知识体系及职业操守
- java word 加密_Java 加密Word文档
- 优化算法之梯度下降法、牛顿法、拟牛顿法和拉格朗日乘数法