AM437x——LED驱动
CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/08/12/AM437x——LED驱动/#more
本文主要记录AM437X驱动的LED。含简单的字符设备驱动、设备驱动模型、设备树以及LED子系统。
目前就Linux驱动的理解是:
Linux驱动 = 裸机 + 框架
关于框架,目前的理解是:
以LED驱动为例,之前印象中就是韦老大的思路,先在init里注册字符设备、硬件初始化,然后应用层open()、read()就调用了file_operations里面的drv_open()、drv_write()等,算是最简单的驱动框架。
然后韦老大又提及了总线设备驱动模型,将设备和驱动分离,慢慢的体会到了新的框架。不久前,简单接触了下设备树,感觉就是总线设备驱动模型的修改(升级),将原来的设备部分,不再单独放在代码里,而是放在dts里面,开机在U-boot加载,然后与驱动匹配获取硬件资源。因此,感觉驱动的框架在一步一步的发展,优化,最原始的注册字符设备、open等框架,还是不变。
同时,了解到了除输入子系统的其它子系统,加深了对这一模式的理解。感觉就是,将某个硬件资源无缝的融入现有的环境中,而无须改变应用层的程序。
这就是目前的一点小小理解吧,算是打开了个入口,希望以后了解得更加全面、细致。
1.搭建开发环境
1.1安装TI_SDK
先在TI官网下载ti-processor-sdk-linux-am437x-evm-01.00.00.03-Linux-x86-Install.bin
在Ubuntu(only Ubuntu 12.04 LTS and Ubuntu 14.04 LTS are supported)下,对该文件加入可执行权限,然后直接运行。安装目录选择默认即可。完成之后,便在当前用户的home目录生成了所有所需文件。
1.2编译内核
在当前生成ti-processor-sdk-linux-am437x-evm-01.00.00.03目录下,有个Makefile,打开后可以看到相关的编译选项,如:
- 编译全部文件:
make all
- 编译内核:
make linux
- 编译u-boot:
make u-boot-spl
以及make的依赖:-include Rules.make
。在本层目录里,打开Rules.make
,可以知道内核的默认配置文件:
#defconfig
DEFCONFIG=tisdk_am437x-evm_defconfig
通过查找,tisdk_am437x-evm_defconfig在~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/configs
里。
这里通过修改该配置文件,然后重新编译内核,即可关闭系统LED相关的驱动,在后面自己写LED驱动时,防止互相干扰。
因此将tisdk_am437x-evm_defconfig配置文件里的所有有关LED的配置都关闭掉。
最后在顶层目录执行make linux
,编译完成后,生成~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/boot/zImage
文件。
1.3烧写SD卡
回到~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/bin
下,TI制作了很多脚本,其中的create-sdcard.sh
就是制作SD卡的。Ubuntu插上SD卡,然后切换成root用户,执行该脚本,根据提示一路选择下去即可。
这里烧写完了,测试发现并没有使用之前编译的内核,分析脚本后发现,该脚本直接使用的~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/filesystem
下的tisdk-rootfs-image-am437x-evm.tar.gz
。脚本将该文件作为根文件系统放入SD卡,因此并没有使用之前编译的内核。解决方法要么在执行脚本的过程中根据提示输入相关的路径,要么在制作好SD卡后,将编译好的内核覆盖掉SD卡的内核即可。我选择的后者:cp ~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/boot/zImage /media/hceng/rootfs/boot/
。
最后将制作好的SD卡插上开发板启动即可。
2.简单的字符驱动
先记录下几个重要类型或结构体:
- 表示设备号(32位机中:高12位表示主设备号,低20位表示次设备号)
typedef __kernel_dev_t dev_t;
- 描述字符设备
struct cdev {struct kobject kobj; //内嵌kobject结构体,用于设备驱动模型管理struct module *owner; //包含指向该结构的模块的指针,用于引用计数const struct file_operations *ops; //指向字符设备操作函数集的指针struct list_head list; //该结构将使用该驱动的字符设备连接成一个链表dev_t dev; //该字符设备的其实设备号,一个设备可能有多个设备号unsigned int count; //使用该字符设备驱动的设备数量
};
- 描述类
struct class{const char *name; //类名称struct module *owner; //对应模块struct subsystem subsys; //对应的subsystem;struct list_head children; //class_device链表struct list_head interfaces; //class_interface链表struct semaphore sem; /用于同步的信号锁struct class_attribute *class_attrs; //类属性int (*uevent)(struct class_device *dev,char **envp,int num_envp,char *buffer,int buffer_size); //事件void (*release)(struct class_device *dev); //释放类设备void (*class_release)(struct class *class); //释放类
}
总结下,目前理解的字符设备编写流程:
1)驱动加载函数:
xx_drv_init()
1.1)申请设备号:alloc_chrdev_region()
1.2)cde初始化(绑定fops):cdev_init()
1.3)注册到内核:cdev_add()
1.4)创建类:class_create()
1.5)向类中添加设备(mdev自动创建设备节点):device_create()
1.6)硬件相关(内存映射):ioremap()
2)驱动卸载函数:xx_drv_exit()
2.1)移除设备:device_destroy()
2.2)移除类:class_destroy()
2.3)注销cdev:cdev_del()
2.4)释放设备号:unregister_chrdev()
2.5)释放内存:iounmap()
3)必要修饰:module_init(xx_drv_init);module_exit(xx_drv_exit);MODULE_LICENSE("GPL");
4)构造file_operations:struct file_operations xx_drv_fops;
5)实现file_operations里每个函数:xx_open()、xx_write()……
2.1驱动代码
{% codeblock lang:c [leds_drv.c] https://github.com/hceng/am437x/blob/master/drive/1th_led/v1.0/leds_drv.c %}
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#define TI_LEDS_CNT 4
int major;
static struct cdev leds_cdev;
static struct class *leds_cls;
static volatile unsigned long *PRCM_CM_PER_GPIO5_CLKCTRL = NULL;
static volatile unsigned long *CTRL_CONF_UART3_RXD = NULL;
static volatile unsigned long *CTRL_CONF_UART3_TXD = NULL;
static volatile unsigned long *CTRL_CONF_UART3_CTSN = NULL;
static volatile unsigned long *CTRL_CONF_UART3_RTSN = NULL;
static volatile unsigned long *GPIO_OE = NULL;
static volatile unsigned long *GPIO_SETDATAOUT = NULL;
static volatile unsigned long *GPIO_DATAOUT = NULL;
static int leds_drv_open(struct inode *inode, struct file *file)
{
int minor = iminor(file->f_inode);
printk(KERN_INFO"%s OK.\n",__func__);*PRCM_CM_PER_GPIO5_CLKCTRL = (0x01<<1);*CTRL_CONF_UART3_RXD &= ~(0x7<<0 | 0x01<<16 | 0x01<<17 | 0x01<<18);
*CTRL_CONF_UART3_RXD |= (0x7<<0 | 0x01<<17);*GPIO_OE &= ~(0x01<<minor);
*GPIO_SETDATAOUT |= (0x01<<minor);return 0;
}
static ssize_t leds_drv_write(struct file *file, const char __user *user_buf, size_t count, loff_t * ppos)
{
int minor = iminor(file->f_inode);
char buf;
printk(KERN_INFO"%s OK.\n",__func__);if(count != 1){printk(KERN_INFO"write count != 1.\n"); return 1;
}if (copy_from_user(&buf, user_buf, count))return -EFAULT;if (0x01 == buf) *GPIO_DATAOUT |= (0x01<<minor);
else if(0x00 == buf)*GPIO_DATAOUT &= ~(0x01<<minor);return 0;
}
static struct file_operations leds_fops = {
.owner = THIS_MODULE,
.open = leds_drv_open,
.write = leds_drv_write,
};
static int leds_drv_init(void)
{
//1.申请设备号
dev_t devid;
printk(KERN_INFO"%s OK.\n",__func__);if(alloc_chrdev_region(&devid, 0, TI_LEDS_CNT, "ti_leds") < 0)
{printk(KERN_INFO"%s ERROR.\n",__func__);goto error;
}major = MAJOR(devid);//2.注册到系统中
cdev_init(&leds_cdev, &leds_fops);
cdev_add(&leds_cdev, devid, TI_LEDS_CNT); leds_cls = class_create(THIS_MODULE, "ti_leds");device_create(leds_cls, NULL, MKDEV(major, 0), NULL, "ti_led0");
device_create(leds_cls, NULL, MKDEV(major, 1), NULL, "ti_led1");
device_create(leds_cls, NULL, MKDEV(major, 2), NULL, "ti_led2");
device_create(leds_cls, NULL, MKDEV(major, 3), NULL, "ti_led3");//3.硬件相关
PRCM_CM_PER_GPIO5_CLKCTRL = ioremap(0x44DF8800+0x498, 0x04*1);CTRL_CONF_UART3_RXD = ioremap(0x44E10000+0xA28, 0x04*4);
CTRL_CONF_UART3_TXD = CTRL_CONF_UART3_RXD + 1;
CTRL_CONF_UART3_CTSN = CTRL_CONF_UART3_RXD + 2;
CTRL_CONF_UART3_RTSN = CTRL_CONF_UART3_RXD + 3; GPIO_OE = ioremap(0x48322000+0x134, 0x04);
GPIO_DATAOUT = ioremap(0x48322000+0x13C, 0x04);
GPIO_SETDATAOUT = ioremap(0x48322000+0x194, 0x04);
error:
unregister_chrdev_region(MKDEV(major, 0), TI_LEDS_CNT);
return 0;
}
static void leds_drv_exit(void)
{
unsigned i;
printk(KERN_INFO"%s OK.\n",func);
for(i=0;i<TI_LEDS_CNT;i++)
{device_destroy(leds_cls, MKDEV(major, i));
}
class_destroy(leds_cls);
cdev_del(&leds_cdev);
unregister_chrdev(major, "ti_leds"); iounmap(PRCM_CM_PER_GPIO5_CLKCTRL);
iounmap(CTRL_CONF_UART3_RXD);
iounmap(GPIO_OE);
iounmap(GPIO_DATAOUT);
iounmap(GPIO_SETDATAOUT);
}
module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board leds drvice”);
MODULE_ALIAS(“character device:ti_leds”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}
2.2测试代码(跑马灯)
{% codeblock lang:c [leds_app.c] https://github.com/hceng/am437x/blob/master/drive/1th_led/v1.0/leds_app.c %}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#define msleep(x) usleep(x*1000)
int main(int argc, char **argv)
{
int fd[4];
int val = 0;
int i = 0;
//const char *dev[] = {"/dev/ti_led0", “/dev/ti_led1”, “/dev/ti_led2”, “/dev/ti_led3”};
const char *dev[] = {"/dev/ti_led2", “/dev/ti_led0”, “/dev/ti_led3”, “/dev/ti_led1”};
for(i=0; i<4; i++)
{fd[i] = open(dev[i], O_RDWR);if (fd[i] < 0){printf("can't open %s\n", *dev[i]);return 0;}
}//leds off all.
for(i=0; i<4; i++)
{write(fd[i], &val, 1);
} //flicker leds.
while(1)
{val = !val;for(i=0; i<4; i++){write(fd[i], &val, 1);msleep(300);}
}
}
{% endcodeblock %}
2.3关于printk调试
内核的printk定义了如下的打印等级:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
- 如果使用串口登陆,可通过修改/proc/sys/kernel/printk里的参数进行设置:
echo "8 4 1 7" >/proc/sys/kernel/printk
上面的四个数字分别代表:
控制台日志级别:优先级[s1] 高于该值的消息将被打印至控制台,[s1]数值越小,优先级越高;
默认的消息日志级别:将用该优先级来打印没有优先级的消息;
最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);
默认的控制台日志级别:控制台日志级别的缺省值;
- 如果使用SSH登陆,是无法显示printk的打印信息的,但打印的数据会被放在
/var/log/messages
和/proc/kmsg
中,利用这一特性,可以后台运行tail命令进行侦测:
tail -f /var/log/messages &
缺点是不能设置打印等级,同时内核的其它信息也会被打印出来。
3.设备驱动模型驱动
关于设备驱动模型前面以及有点了解,在写驱动的时候,主要体现在将原本的硬件资源信息抽取了出来,单独放在了一个文件里,当两个文件的分别加载的时候,根据名字匹配,匹配成功则调用probe()函数,进行类似前面的init()进行初始化。其它的内容基本一样,该干嘛就干嘛。
3.1驱动代码
{% codeblock lang:c [leds_dev.c] https://github.com/hceng/am437x/blob/master/drive/1th_led/v2.0/leds_dev.c %}
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
/**************************************************
TI_BOARD
Ball Color Mode Pin
H24 D7_Blue 0x07 uart3_txd(GPIO5_3)
H25 D8_Blue 0x07 uart3_rxd(GPIO5_2)
K24 D9_Green 0x07 uart3_rtsn(GPIO5_1)
H22 D10_Red 0x07 uart3_ctsn(GPIO5_0)
**************************************************/
static struct resource leds_resource[] = {
//PRCM_CM_PER_GPIO5_CLKCTRL(498h)
[0] = {
.start = 0x44DF8800,
.end = 0x44DFFFFF,
.name = “CM_PER”,
.flags = IORESOURCE_MEM,
},
//CTRL_CONF_UART3_RXD(A28h)、CTRL_CONF_UART3_TXD(A2Ch)、CTRL_CONF_UART3_CTSN(A30h)、CTRL_CONF_UART3_RTSN((A34h))
[1] = {
.start = 0x44E10000,
.end = 0x44E1FFFF,
.name = “CONTROL_MODULE”,
.flags = IORESOURCE_MEM,
},
//GPIO_OE(134h)、GPIO_SETDATAOUT(194h)、GPIO_DATAOUT(13Ch)
[2] = {
.start = 0x48322000,
.end = 0x48322FFF,
.name = “GOIP5”,
.flags = IORESOURCE_MEM,
},
[3] = {
.start = 0,
.end = 3,
.name = “GOIP5_PIN”,
.flags = IORESOURCE_IO,
}
};
static void leds_release(struct device * dev)
{
printk(KERN_INFO"%s OK.\n",func);
}
static struct platform_device leds_dev = {
.name = “ti_am437x_leds_platform”,
.id = -1,
.num_resources = ARRAY_SIZE(leds_resource),
.resource = leds_resource,
.dev = {
.release = leds_release,
},
};
static int leds_dev_init(void)
{
printk(KERN_INFO"%s OK.\n",func);
return platform_device_register(&leds_dev);;
}
static void leds_dev_exit(void)
{
printk(KERN_INFO"%s OK.\n",func);
platform_device_unregister(&leds_dev);
}
module_init(leds_dev_init);
module_exit(leds_dev_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board leds drvice”);
MODULE_ALIAS(“platform:ti_leds”);
MODULE_VERSION(“V2.0”);
{% endcodeblock %}
{% codeblock lang:c [leds_drv.c] https://github.com/hceng/am437x/blob/master/drive/1th_led/v2.0/leds_drv.c %}
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define TI_LEDS_CNT 4
int major;
static struct cdev leds_cdev;
static struct class *leds_cls;
static volatile unsigned long *PRCM_CM_PER_GPIO5_CLKCTRL = NULL;
static volatile unsigned long *CTRL_CONF_UART3_RXD = NULL;
static volatile unsigned long *CTRL_CONF_UART3_TXD = NULL;
static volatile unsigned long *CTRL_CONF_UART3_CTSN = NULL;
static volatile unsigned long *CTRL_CONF_UART3_RTSN = NULL;
static volatile unsigned long *GPIO_OE = NULL;
static volatile unsigned long *GPIO_SETDATAOUT = NULL;
static volatile unsigned long *GPIO_DATAOUT = NULL;
static int leds_drv_open(struct inode *inode, struct file *file)
{
int minor = iminor(file->f_inode);
printk(KERN_INFO"%s OK.\n",__func__);*PRCM_CM_PER_GPIO5_CLKCTRL = (0x01<<1);*CTRL_CONF_UART3_RXD &= ~(0x7<<0 | 0x01<<16 | 0x01<<17 | 0x01<<18);
*CTRL_CONF_UART3_RXD |= (0x7<<0 | 0x01<<17);*GPIO_OE &= ~(0x01<<minor);
*GPIO_SETDATAOUT |= (0x01<<minor);return 0;
}
static ssize_t leds_drv_write(struct file *file, const char __user *user_buf, size_t count, loff_t * ppos)
{
int minor = iminor(file->f_inode);
char buf;
printk(KERN_INFO"%s OK.\n",__func__);if(count != 1){printk(KERN_INFO"write count != 1.\n"); return 1;
}if (copy_from_user(&buf, user_buf, count))return -EFAULT;if (0x01 == buf) *GPIO_DATAOUT |= (0x01<<minor);
else if(0x00 == buf)*GPIO_DATAOUT &= ~(0x01<<minor);return 0;
}
static struct file_operations leds_fops = {
.owner = THIS_MODULE,
.open = leds_drv_open,
.write = leds_drv_write,
};
static int leds_probe(struct platform_device *pdev)
{
struct resource *res;
dev_t devid;
printk(KERN_INFO"%s OK.\n",__func__);//1.申请设备号
if(alloc_chrdev_region(&devid, 0, TI_LEDS_CNT, "ti_leds") < 0)
{printk("%s ERROR\n",__func__);goto error;
}major = MAJOR(devid);//2.注册到系统中
cdev_init(&leds_cdev, &leds_fops);
cdev_add(&leds_cdev, devid, TI_LEDS_CNT); leds_cls = class_create(THIS_MODULE, "ti_leds");device_create(leds_cls, NULL, MKDEV(major, 0), NULL, "ti_led0");
device_create(leds_cls, NULL, MKDEV(major, 1), NULL, "ti_led1");
device_create(leds_cls, NULL, MKDEV(major, 2), NULL, "ti_led2");
device_create(leds_cls, NULL, MKDEV(major, 3), NULL, "ti_led3");//3.硬件相关
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "CM_PER");
if (!res) return -EINVAL;
PRCM_CM_PER_GPIO5_CLKCTRL = ioremap(res->start+0x498, 0x04*1);res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "CONTROL_MODULE");
if (!res) return -EINVAL;
CTRL_CONF_UART3_RXD = ioremap(res->start+0xA28, 0x04*4);
CTRL_CONF_UART3_TXD = CTRL_CONF_UART3_RXD + 1;
CTRL_CONF_UART3_CTSN = CTRL_CONF_UART3_RXD + 2;
CTRL_CONF_UART3_RTSN = CTRL_CONF_UART3_RXD + 3; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "GOIP5");
if (!res) return -EINVAL;
GPIO_OE = ioremap(res->start+0x134, 0x04);
GPIO_DATAOUT = ioremap(res->start+0x13C, 0x04);
GPIO_SETDATAOUT = ioremap(res->start+0x194, 0x04);*PRCM_CM_PER_GPIO5_CLKCTRL = (0x01<<1);//使能GPIO外设时钟
error:
unregister_chrdev_region(MKDEV(major, 0), TI_LEDS_CNT);
return 0;
}
static int leds_remove(struct platform_device *pdev)
{
unsigned i;
printk(KERN_INFO"%s OK.\n",func);
for(i=0;i<TI_LEDS_CNT;i++)
{device_destroy(leds_cls, MKDEV(major, i));
}class_destroy(leds_cls);
cdev_del(&leds_cdev);
unregister_chrdev(major, "ti_leds"); iounmap(PRCM_CM_PER_GPIO5_CLKCTRL);
iounmap(CTRL_CONF_UART3_RXD);
iounmap(GPIO_OE);
iounmap(GPIO_DATAOUT);
iounmap(GPIO_SETDATAOUT);return 0;
}
struct platform_driver leds_drv = {
.probe = leds_probe,
.remove = leds_remove,
.driver = {
.name = “ti_am437x_leds_platform”,
}
};
static int leds_drv_init(void)
{
printk(KERN_INFO"%s OK.\n",func);
return platform_driver_register(&leds_drv);
}
static void leds_drv_exit(void)
{
printk(KERN_INFO"%s OK.\n",func);
platform_driver_unregister(&leds_drv);
}
module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board leds drvice”);
MODULE_ALIAS(“platform:ti_leds”);
MODULE_VERSION(“V2.0”);
{% endcodeblock %}
测试程序同前面的一样。
4.设备树驱动
4.1初识设备树
1)前面的总线设备驱动模型中,硬件资源来自于leds_dev.c里面的信息,这样会导致不同的板子,会添加不同的硬件资源信息,造成内核的臃肿。
2)使用设备树后,内核不再包含硬件的描述,硬件描述放在单独的DTS里面,然后编译成二进制的DTB,在U-Boot启动的时候加载进去,然后内核进行解析。
3)DTS、DTC和DTB之间的关系:
DTS经过DTC编译得到DTB,DTB通过DTC反编译得到DTS.
4)ARM中,所有的DTS文件放在
arch/arm/boot/dts
目录中,为了简化,将Soc公用部分提取了出来作为dtsi,类似头文件。
5)DTC编译工具的源代码在scripts/dtc目录中,编译内核时,编译内核时,需要使能才能将源码编译成工具,对应于scripts/dtc/Makefile
中"hostprogs-y:=dtc"
。Ubuntu也可直接安装DTC工具:
sudo apt-get install device-tree-compiler
6)内核的
arch/arm/boot/dts/Makefile
中,描述了当某种Soc被选中后,哪些.dtb会编译出来。执行make dtbs
,会根据arch/arm/Makefile
编译指定目标。
7)单独编译与反编译:
./scripts/dtc/dtc -I dts -O dtb -o xxx.dtb arch/arm/boot/dts/xxx.dts //dts->dtb
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb //dtb->dts
8)后面认识深刻了,再总结总结。
4.2修改AM437x设备树
AM437x的设备树文件在~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/boot/dts/
中,主要是am4372.dtsi和am437x-sk-evm.dts。我的目的是希望写个设备树框架的LED程序,因此想让am437x-sk-evm.dts干净点,只包含LED硬件描述,因此我需要删除am437x-sk-evm.dts里面的其它硬件描述。经过测试,am437x-sk-evm.dts里面包含部分MMC的描述,一旦删除将不能成功启动内核。而且,后面调试的时候,希望开发板通过NFS挂载的方式,直接加载编译的驱动模块,因此需要保留网卡描述部分。最后,将MMC和网卡必须的部分,提取了出来,放在了am4372.dtsi中。精简后的am437x-sk-evm.dts内容如下:
{% codeblock lang:c [am437x-sk-evm.dts] https://github.com/hceng/am437x/blob/master/drive/1th_led/v3.0/am437x-sk-evm.dts %}
/* AM437x SK EVM */
/dts-v1/;
#include “am4372.dtsi”
#include <dt-bindings/pinctrl/am43xx.h>
#include <dt-bindings/gpio/gpio.h>
/ {
model = “TI AM437x SK EVM”;
compatible = “ti,am437x-sk-evm”,“ti,am4372”,“ti,am43”;
led_pin {compatible = "ti_leds";pinctrl-names = "default";pinctrl-0 = <&leds_pins>;am437x,led_gpio0 = <&gpio5 0 GPIO_ACTIVE_HIGH>;am437x,led_gpio1 = <&gpio5 1 GPIO_ACTIVE_HIGH>;am437x,led_gpio2 = <&gpio5 2 GPIO_ACTIVE_HIGH>;am437x,led_gpio3 = <&gpio5 3 GPIO_ACTIVE_HIGH>;
};
};
&am43xx_pinmux {
leds_pins: leds_pins {pinctrl-single,pins = <0x228 (PIN_OUTPUT | MUX_MODE7) /* uart3_rxd.gpio5_2 */0x22c (PIN_OUTPUT | MUX_MODE7) /* uart3_txd.gpio5_3 */0x230 (PIN_OUTPUT | MUX_MODE7) /* uart3_ctsn.gpio5_0 */0x234 (PIN_OUTPUT | MUX_MODE7) /* uart3_rtsn.gpio5_1 */>;
};
};
&gpio5 {
status = “okay”;
};
{% endcodeblock %}
额,在调试的过程中,需要不断编译新的DTB和复制到SD卡的rootfs分区中,仿照前面写了个脚本进行自动编译和复制,同时检查文件的生成时间间隔,实际中,确实减少了焦躁的重复操作。
4.3驱动代码
{% codeblock lang:c [leds_drv.c] https://github.com/hceng/am437x/blob/master/drive/1th_led/v3.0/leds_drv.c %}
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define TI_LEDS_CNT 4
int major;
static struct cdev leds_cdev;
static struct class *leds_cls;
static int led0,led1,led2,led3;
static int leds_drv_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO"%s OK.\n",func);
return 0;
}
static ssize_t leds_drv_write(struct file *file, const char __user *user_buf, size_t count, loff_t * ppos)
{
int minor = iminor(file->f_inode);
char buf;
printk(KERN_INFO"%s OK.\n",__func__);if(count != 1){printk(KERN_INFO"write count != 1.\n"); return 1;
}if (copy_from_user(&buf, user_buf, count))return -EFAULT;if(0x01 == buf)
{switch(minor){case 0:gpio_set_value(led0, 0);break;case 1:gpio_set_value(led1, 0);break;case 2:gpio_set_value(led2, 0);break;case 3:gpio_set_value(led3, 0);break;default:printk(KERN_INFO"%s receive minor error.\n",__func__);}
}
else if(0x00 == buf)
{switch(minor){case 0:gpio_set_value(led0, 1);break;case 1:gpio_set_value(led1, 1);break;case 2:gpio_set_value(led2, 1);break;case 3:gpio_set_value(led3, 1);break;default:printk(KERN_INFO"%s receive minor error\n",__func__);}
}return 0;
}
static struct file_operations leds_fops = {
.owner = THIS_MODULE,
.open = leds_drv_open,
.write = leds_drv_write,
};
static int leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_t devid;
printk(KERN_INFO"%s OK.\n",__func__);//1.申请设备号
if(alloc_chrdev_region(&devid, 0, TI_LEDS_CNT, "ti_leds") < 0)
{printk(KERN_INFO"%s ERROR.\n",__func__);goto error;
}major = MAJOR(devid);//2.注册到系统中
cdev_init(&leds_cdev, &leds_fops);
cdev_add(&leds_cdev, devid, TI_LEDS_CNT); leds_cls = class_create(THIS_MODULE, "ti_leds");device_create(leds_cls, NULL, MKDEV(major, 0), NULL, "ti_led0");
device_create(leds_cls, NULL, MKDEV(major, 1), NULL, "ti_led1");
device_create(leds_cls, NULL, MKDEV(major, 2), NULL, "ti_led2");
device_create(leds_cls, NULL, MKDEV(major, 3), NULL, "ti_led3");//3.硬件相关
led0 = of_get_named_gpio(dev->of_node, "am437x,led_gpio0", 0);;
led1 = of_get_named_gpio(dev->of_node, "am437x,led_gpio1", 0);;
led2 = of_get_named_gpio(dev->of_node, "am437x,led_gpio2", 0);;
led3 = of_get_named_gpio(dev->of_node, "am437x,led_gpio3", 0);//printk(KERN_INFO"led0 = %d\n",led0);
//printk(KERN_INFO"led1 = %d\n",led1);
//printk(KERN_INFO"led2 = %d\n",led2);
//printk(KERN_INFO"led3 = %d\n",led3);devm_gpio_request_one(dev, led0, GPIOF_OUT_INIT_HIGH, "LED0");
devm_gpio_request_one(dev, led1, GPIOF_OUT_INIT_HIGH, "LED1");
devm_gpio_request_one(dev, led2, GPIOF_OUT_INIT_HIGH, "LED2");
devm_gpio_request_one(dev, led3, GPIOF_OUT_INIT_HIGH, "LED3");
error:
unregister_chrdev_region(MKDEV(major, 0), TI_LEDS_CNT);
return 0;
}
static int leds_remove(struct platform_device *pdev)
{
unsigned i;
printk(KERN_INFO"%s OK.\n",func);
for(i=0;i<TI_LEDS_CNT;i++)
{device_destroy(leds_cls, MKDEV(major, i));
}class_destroy(leds_cls);
cdev_del(&leds_cdev);
unregister_chrdev(major, "ti_leds"); return 0;
}
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = “ti_leds”, },
{},
};
static struct platform_driver leds_drv = {
.probe = leds_probe,
.remove = leds_remove,
.driver = {
.name = “ti_am437x_leds_platform”,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_gpio_leds_match),
},
};
static int leds_drv_init(void)
{
printk(KERN_INFO"%s OK.\n",func);
return platform_driver_register(&leds_drv);
}
static void leds_drv_exit(void)
{
printk(KERN_INFO"%s OK.\n",func);
platform_driver_unregister(&leds_drv);
}
module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board leds drvice”);
MODULE_ALIAS(“platform:device tree:ti_leds”);
MODULE_VERSION(“V3.0”);
{% endcodeblock %}
测试程序同前面的一样。
5.LED子系统驱动
5.1 框架分析
LED子系统框架如下:
用户态
在用户态,可以直接访问节点/sys/class/leds/xxx/
下的文件操作LED。
在该路径下会有brightness
、max_brightness
、trigger
,根据trigger
的不同,还可能有delay_off
、delay_on
、invert
等。
brightness
用于设置LED亮度,范围为0(LED_OFF)~255(LED_FULL);
max_brightness
用于显示LED的最大亮度访问,一般值为255;
trigger
用于设置LED的触发模式,通常可选的有:none
、nand-disk
、mmc0
、cpu0
、heartbeat
、timer
、default-on
、oneshot
、backlight
、gpio
;
delay_off
、delay_on
用于trigger
为timer
等模式时,LED亮灭的时间,单位为毫秒;内核驱动
LED子系统的驱动都在drivers/leds/
下面。
核心的文件是led-class.c
、led-core.c
、led-triggers.c
;
可选触发器方式有:
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o //定时触发
obj-$(CONFIG_LEDS_TRIGGER_ONESHOT) += ledtrig-oneshot.o //单次触发
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o //硬盘触发
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o //心跳触发
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o //背光设置
obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o //GPIO触发
obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.o //CPU触发
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o //默认开
obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o
obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o
leds-gpio.c
和leds-xxx.c
对应具体的设备,比如现在某个LED接在了GPIO上,理论上我们就要编写leds-gpio.c
这个驱动文件,但LED子系统已经帮我们做好了,我们只需要在设备树文件添加相应的设备信息即可。
下面对主要文件的内容进行分析:
led-core.c
:
抽象出LED操作逻辑,封装成函数导出,供其它文件使用:
led_init_core()
:核心初始化;
led_blink_set()
:设置led闪烁时间;
led_blink_set_oneshot()
:闪烁一次;
led_stop_software_blink()
:led停止闪烁;
led_set_brightness()
:设置led的亮度;
led_set_brightness_nopm
:如果可以休眠,设置led的亮度保证休眠可用;
led_set_brightness_nosleep
:如果没有休眠,设置led的亮度;
led_set_brightness_sync
:设置led亮度同步;
led_update_brightness
:更新亮度;
led_sysfs_disable
:用户态关闭;
led_sysfs_enable
:用户态打开;
leds_list
:leds链表;
leds_list_lock
:leds链表锁;
led-class.c
:
维护LED子系统的所有LED设备,为LED设备提供
注册操作函数:led_classdev_register()
/of_led_classdev_register()
/devm_of_led_classdev_register()
/
devm_led_classdev_register()
;
注销操作函数:led_classdev_unregister()
/devm_led_classdev_unregister()
;
电源管理的休眠和恢复操作函数:led_classdev_suspend()
、led_classdev_resume()
;
同时提供基本的用户态操作接口,brightness
、max_brightness
等;
led-triggers.c
:
维护LED子系统的所有触发器,为触发器提供
注册操作函数:led_trigger_register()
、devm_led_trigger_register()
,led_trigger_register_simple()
;
注销操作函数:led_trigger_unregister()
、led_trigger_unregister_simple()
;
以及其它触发器相关的操作函数;
ledtrig-timer.c ledtrig-xxxx.c
:
以ledtrig-timer.c
为例的触发器,入口函数调用led_trigger_register()
注册触发器,注册时候传入led_trigger
结构体,里面有activate
和deactivate
成员函数指针,这里的作用是生成delay_on
、delay_off
文件;
同时还提供delay_on
和delay_off
的用户态操作接口;
卸载时,使用led_trigger_unregister()
注销触发器。
leds-gpio.c leds-xxxx.c
:
以leds-gpio.c
为例的LED设备,在通过设备树或者其它途径匹配到设备信息后,将调用probe()
函数,然后再根据设备信息设置led_classdev
,最后调用devm_of_led_classdev_register()
注册LED设备。
5.2 示例参考
首先在内核里,配置LED子系统:
Device Drivers --->[*] LED Support ---> <*> LED Class Support <*> LED Support for GPIO connected LEDs *** LED Triggers *** -*- LED Trigger support ---> [*] LED Timer Trigger [*] LED One-shot Trigger[*] LED Heartbeat Trigger[*] LED backlight Trigger[*] LED CPU Trigger[*] LED GPIO Trigger [*] LED Default ON Trigger
根据自己需求,配置LED Trigger support
的内容。
然后设备树里添加设备节点,参考Documentation/devicetree/bindings/leds/leds-gpio.txt
:
leds {compatible = "gpio-leds";led-heartbeat {gpios = <&gpio1 16 GPIO_ACTIVE_LOW>;linux,default-trigger = "heartbeat";};led-cpu0 {gpios = <&gpio1 17 GPIO_ACTIVE_LOW>;linux,default-trigger = "cpu0";};led-timer {gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;linux,default-trigger = "timer";};led-none {gpios = <&gpio1 21 GPIO_ACTIVE_LOW>;linux,default-trigger = "none";};};
重新编译内核和设备树,烧写,启动。
在/sys/class/leds
路径下,生成了以下文件夹:
led-cpu0 led-heartbeat led-timer led-none
进入led-heartbeat
目录,可以看到以下文件:
brightness max_brightness subsystem uevent
device power trigger
可以通过echo 0 > brightness
来关闭LED灯,通过echo timer > trigger
改变触发模式。
同时,cat trigger
可以显示支持的触发模式,以及当前的触发模式:
none nand-disk mmc0 cpu0 [heartbeat] timer default-on oneshot backlight gpio
6.心得
在我理解到驱动=裸机+软件框架的时候,我对之前的裸机也就没那么排斥了。
而且这个软件框架,就现在来看,核心的那几步:申请设备号、注册设备、创建类和创建节点这些都不变,早晚会做。
对于LED子系统,虽然表面上自己没做上述的操作,但实际上是LED子系统帮我们做了。
AM437x——LED驱动相关推荐
- AM437x——RTC驱动
CSDN仅用于增加百度收录权重,排版未优化,日常不维护.请访问:www.hceng.cn 查看.评论. 本博文对应地址: https://hceng.cn/2017/08/27/AM437x--RTC ...
- 驱动学习之LED驱动框架
一:什么是驱动框架 (1)内核中驱动部分维护者针对每个种类的驱动设计一套成熟的.标准的.典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开 ...
- linux修改led中颜色,Linux学习三之LED驱动(修改内核)
昨天到今天折腾了两天才把LED的驱动实现了,当终于成功的时候还是很开心的.在此把错误的原因总结了一下, 也供正在学习的童鞋们参考一下,因为有些原因是手册误导的. 第一,在编译镜像前首先要把天嵌配置好的 ...
- NanoPi NEO Air使用九:使用Linux内核自带的LED驱动
NanoPi NEO Air使用一:介绍 NanoPi NEO Air使用二:固件烧录 NanoPi NEO Air使用三:OverlayFS.CPU温度和频率.wifi.蓝牙.npi-config ...
- LED驱动设计及实现
文章目录 1 LED驱动流程图分析 1.1 led_on流程图分析 1.2 led_blink流程图分析 2 代码实现 1 LED驱动流程图分析 这里主要分析led_on和led_blink流程. 1 ...
- 友善之臂 mini2440 linux led 驱动代码,mini2440 led驱动程序
这个led驱动程序只在linux-2.6.32.2内核中测试通过,至于其他的内核可能头文件有一些改动就不能 编译成功了.下面给出源程序: 这是友善之臂提供的源码: #include #include ...
- Linux LED驱动开发实验(直接操作寄存器 -- 实际开发很少这样做)
目录 Linux 下LED 灯驱动原理 地址映射(ioremap映射.iounmap释放) I/O 内存访问函数 硬件原理图分析 实验程序编写 LED 灯驱动程序编写 APP测试程序编写 运行测试 编 ...
- ARM(IMX6U)裸机C语言版本LED驱动实验(汇编进入处理器SVC模式、SP堆内存、跳转main函数、链接起始地址)
参考:Linux之ARM(IMX6U)裸机C语言LED驱动实验–驱动编写,编译 作者:一只青木呀 发布时间: 2020-08-11 11:20:17 网址:https://blog.csdn.net/ ...
- linux3.0-内核自带led驱动移植
**************************************************************************************************** ...
- 多通道_人机交互界面的多通道RGB LED驱动设计
现在的产品变得非常智能且彼此相互连接.扬声器.电视.冰箱.机顶盒和烟雾探测器等器件不再只是处在某一位置的工具 - 用户可远程或通过语音唤醒控制它们.这些器件比以前更加智能,这意味着它们还需要更加智慧的 ...
最新文章
- R语言使用ggplot2包使用geom_dotplot函数绘制分组点图(单色填充、分组颜色填充)实战(dot plot)
- 克服DIMM近存计算系统的通信瓶颈,清华软件定义芯片团队提出DIMM间广播技术 | ISCA 2021...
- 计算机技能最新需求排名:Python增速爆炸,SQL和Java老当益壮,AWS令人惊讶
- 企业邮箱自建战略优势大盘点
- 关于jupyter几个不得不知道的tips
- linux mysql date 格式_关于MySQL中的三种日期类型
- 关于Linux内核的一些问题
- [Java] 蓝桥杯ALGO-150 算法训练 6-1 递归求二项式系数值
- 三角形外接圆圆心公式
- 【板绘必读】完全0基础如何学板绘?
- java Socket 编程实例
- 英伟达显卡虚拟化vGPU实践指南
- 音乐鉴赏 周海宏 网络课程 题库(Ctrl+f查找)点赞哦
- Can not connect to the Service chromedriver的解决方法
- 苹果cmsV10 会员收费设置 会员VIP权限设置教程
- Python网络相关面试题
- ArcMap 制图出图
- 计算机接口与技术 英语怎么说,微型计算机接口与技术考点(国外英文资料).doc...
- SuperSocket
- 大火烧毁的翁丁古寨在云端地球上“活”过来了
热门文章
- 智能手机和平板电脑设计中的单键开/关机和复位的智能方案
- 报刊订阅管理系统数据库
- 【空气质量数据分析专题四】超标状况分析
- golang-ffmpeg-goav拉流解码器,支持视频拉流解码并转换为YUV、BGR24或RGB24等图像像素数据
- 1092 最好吃的月饼
- Linux的LILO引导程序,linux —— 启动引导程序 lilo 与 grub(示例代码)
- 如何查看linux是grub还lilo,linux中LILO及GRUB配置实例
- mysql db_owner_SQL Server修改数据库对象所有者(Owner)浅析
- vue2 vue3 js es6 html css 知识点
- CodeForces 1009D Relatively Prime Graph 贪心+枚举