嵌入式Linux驱动笔记(八)------依赖Linux kernel驱动的pwm编写
来源:互联网 发布:云计算的运用 编辑:程序博客网 时间:2024/06/07 20:48
你好!这里是风筝的博客,
欢迎和我一起多多交流。
之前我们写Linux驱动,都是自己写,从platform driver到platform device,都是自己一手包办,其实,在kernel里,很多驱动都已经写好了的,只需我们会用就好了,省时省力。
现在以pwm驱动为例,芯片是2440的芯片:
查阅芯片手册我们可以知道,S3C2440上有4 通道 16 位具有 PWM 功能的定时器,1 通道 16 位基于 DMA或基于中断运行的内部定时器,其中,pwm定时器0的输出引脚在GPIOB0,pwm定时器挂载PCLK时钟上。
好了,现在我们看下kernel源码:
以kernel4.8.17为例,在pwm-samusng.c(drivers/pwm目录)文件里,有:
static struct platform_driver pwm_samsung_driver = { .driver = { .name = "samsung-pwm", .pm = &pwm_samsung_pm_ops, .of_match_table = of_match_ptr(samsung_pwm_matches), }, .probe = pwm_samsung_probe, .remove = pwm_samsung_remove,};module_platform_driver(pwm_samsung_driver);
我们可以非常简单的看出,这是一个platform driver,
.name = “samsung-pwm”,
这就是platform用来匹配的关键,记住他。
ok,现在知道了driver,现在我们来写device文件:
其实kernel里也有关于pwm的demo的,在kernel里搜索:”samsung-pwm”,就会在devs.c(arch/arm/plat-samsung目录)文件中找到蛛丝马迹:
static struct resource samsung_pwm_resource[] = { DEFINE_RES_MEM(SAMSUNG_PA_TIMER, SZ_4K),};struct platform_device samsung_device_pwm = { .name = "samsung-pwm", .id = -1, .num_resources = ARRAY_SIZE(samsung_pwm_resource), .resource = samsung_pwm_resource,};
好了,我们直接照抄just ok!
新建一个pwm_dev.c文件:
#include <linux/module.h>#include <linux/version.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/interrupt.h>#include <linux/list.h>#include <linux/timer.h>#include <linux/init.h>#include <linux/serial_core.h>#include <linux/platform_device.h>#include <asm/mach/map.h>#include <clocksource/samsung_pwm.h>#include <plat/pwm-core.h>static struct resource s3c_pwm_resource[] = { DEFINE_RES_MEM(SAMSUNG_PA_TIMER, SZ_4K),};static struct platform_device s3c_device_pwm = {/*platform里注册设备文件*/ .name = "samsung-pwm", .id = -1, .num_resources = ARRAY_SIZE(s3c_pwm_resource), .resource = s3c_pwm_resource,};static int S3C_PWM_init(void){ /* 2. 注册 */ int result = platform_device_register(&s3c_device_pwm); if (result != 0) /*注册失败*/ printk("register false \n"); return 0;}static void S3C_PWM_exit(void){ platform_device_unregister(&s3c_device_pwm);}module_init(S3C_PWM_init);module_exit(S3C_PWM_exit);MODULE_LICENSE("GPL");
写好后就行了吗?如果你编译好拿去测试,sorry,不行,insmod时会发现:no platform data specified.
这是为什么呢?
还记得driver文件吗?在他的probe函数里(名字匹配成功就会调用probe函数),pwm_samsung_probe函数,有:
if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { ret = pwm_samsung_parse_dt(chip); if (ret) return ret; chip->chip.of_xlate = of_pwm_xlate_with_flags; chip->chip.of_pwm_n_cells = 3; } else { if (!pdev->dev.platform_data) { dev_err(&pdev->dev, "no platform data specified\n"); return -EINVAL; }
这里,因为我们没有使用dts设备树,所以会执行else分支。
里面
if (!pdev->dev.platform_data)
因为我们pwm_dev.c这个device文件里,没有为.platform_data赋值,所以到这里就会error了!
那问题来了,.platform_data到底改写什么呢???
其实,kernel就是最好的demo,搜索一下就会看到了:
在pwm-samsung.h里有个结构体:
struct samsung_pwm_variant { u8 bits; u8 div_base; u8 tclk_mask; u8 output_mask; bool has_tint_cstat;};
再搜索下哪里使用有samsung_pwm_variant结构体就可以参考下了,结果发现:
static const struct samsung_pwm_variant s3c24xx_variant = { .bits = 16, .div_base = 1, .has_tint_cstat = false, .tclk_mask = (1 << 4),};
这四个结构体成员,我也不知道具体的是什么,bits应该哦是16位定时器的意思,div_base应该是分频吧,其他两个我就不知道是什么了,看了下pwm-samsung.txt这个文档也没见说有……
这四个结构体成员就够了吗?
还不够!!!
还要再加一个:.output_mask = 1,
至于为什么?待会说。
所以,pwm_dev.c修改为:
static struct samsung_pwm_variant s3c_pwm_pdata = { .bits = 16, .div_base = 1, .has_tint_cstat = false, .tclk_mask = (1 << 4), .output_mask = 1,};static struct platform_device s3c_device_pwm = {/*platform里注册设备文件*/ .name = "samsung-pwm", .id = -1, .num_resources = ARRAY_SIZE(s3c_pwm_resource), .resource = s3c_pwm_resource, .dev = { .platform_data = &s3c_pwm_pdata, },};
这样,编译成功后,insmod后,会在/sys/class/pwm/目录下产生pwmchip0目录
执行命令:
cd /sys/class/pwm/pwmchip0/
会发现里面这七个文件:
device export npwm power subsystem uevent unexport
执行命令:
echo 0 > export
就是向export文件写入0,就是打开pwm定时器0,会产生一个pwn0目录。
这是为什么呢?
我们看下driver文件里的probe函数,即pwm_samsung_probe函数,有:
static int pwm_samsung_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; struct samsung_pwm_chip *chip; struct resource *res; unsigned int chan; int ret; //printk("the probe name is %s \n",pdev->name);//2017.8.6 chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (chip == NULL) return -ENOMEM; chip->chip.dev = &pdev->dev; chip->chip.ops = &pwm_samsung_ops; chip->chip.base = -1; chip->chip.npwm = SAMSUNG_PWM_NUM; chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1; /*以下代码省略......*/}
在里面有一句:
chip->chip.ops = &pwm_samsung_ops;
看一下pwm_samsung_ops这个结构体:
static const struct pwm_ops pwm_samsung_ops = { .request = pwm_samsung_request, .free = pwm_samsung_free, .enable = pwm_samsung_enable, .disable = pwm_samsung_disable, .config = pwm_samsung_config, .set_polarity = pwm_samsung_set_polarity, .owner = THIS_MODULE,};
没错,刚刚我们向export文件写入0时,就会调用这里的pwm_samsung_request函数了:
static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm){ struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); struct samsung_pwm_channel *our_chan; if (!(our_chip->variant.output_mask & BIT(pwm->hwpwm))) { dev_warn(chip->dev, "tried to request PWM channel %d without output\n", pwm->hwpwm); return -EINVAL; } our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL); if (!our_chan) return -ENOMEM; pwm_set_chip_data(pwm, our_chan); return 0;}
看到第一个if判断了吗?就是这里,如果之前我们没有设置output_mask这个结构体成员,这里就一定会error掉了!
接着上面的操作,继续执行命令:
cd pwm0
里面有七个文件:
capture enable polarity uevent duty_cycle period power
其中,
enable:写入1使能pwm,写入0关闭pwm;
polarity:有normal或inversed两个参数选择,表示TOUT_n输出引脚电平翻转;
duty_cycle:在normal模式下,表示一个周期内高电平持续的时间(单位:纳秒),在reversed模式下,表示一个周期中低电平持续的时间(单位:纳秒);
period:表示pwm波的周期(单位:纳秒);
往里面写入,就是会调用到pwm_samsung_ops结构体里的成员函数。
所以可以这样使用pwm:
echo 1000000000 > period
echo 500000000 > duty_cycle
echo normal > polarity
echo 1 > enable
这样就是占空比为500000000/1000000000的pwm出现。
但是!!你会发现,GPIOB0引脚没有反应……
这是为什么?这个小问题困扰了我两天,终于发现!!!
原来是GPIOB0没有配置,而且要配置成复用引脚!!!都是泪啊…..
添加上配置引脚部分就好了,这样,
往duty_cycle写入不同的值,就会出现不同占空比的pwm了。
最后,往unexport写入0就会关闭pwm定时器了,同时pwm0目录会被删除
即:
cd ..
echo 0 > unexport
最后附上完整代码:
#include <linux/module.h>#include <linux/version.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/interrupt.h>#include <linux/list.h>#include <linux/timer.h>#include <linux/init.h>#include <linux/serial_core.h>#include <linux/platform_device.h>#include <asm/mach/map.h>#include <clocksource/samsung_pwm.h>#include <plat/pwm-core.h>static volatile unsigned long *gpbcon;static volatile unsigned long *gpbdat;static struct samsung_pwm_variant s3c_pwm_pdata = { .bits = 16, .div_base = 1, .has_tint_cstat = false, .tclk_mask = (1 << 4), .output_mask = 1,};static struct resource s3c_pwm_resource[] = { DEFINE_RES_MEM(SAMSUNG_PA_TIMER, SZ_4K),};static struct platform_device s3c_device_pwm = {/*platform里注册设备文件*/ .name = "samsung-pwm", .id = -1, .num_resources = ARRAY_SIZE(s3c_pwm_resource), .resource = s3c_pwm_resource, .dev = { .platform_data = &s3c_pwm_pdata, },};static int S3C_PWM_init(void){ /* 2. 注册 */ int result = platform_device_register(&s3c_device_pwm); if (result != 0) /*注册失败*/ printk("register false \n"); gpbcon = ioremap(0x56000010, 8); gpbdat = gpbcon+1; *gpbcon &= ~(0x3<<(0*2)); *gpbcon |= (0x2<<(0*2)); /*复用模式*/ return 0;}static void S3C_PWM_exit(void){ platform_device_unregister(&s3c_device_pwm); iounmap(gpbcon);}module_init(S3C_PWM_init);module_exit(S3C_PWM_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("kite");MODULE_DESCRIPTION("A pwm Module for testing module ");MODULE_VERSION("V2.0");
后记:
其实我感觉就
platform_device_register(&samsung_device_pwm);
就好了,因为在devs.c这个文件里就把platform_device这个结构体实现了,同时在smdk2440_map_io这个函数里,调用了两个 函数:
s3c24xx_init_io函数
samsung_set_timer_source函数
这两个函数里就会把samsung_device_pwm.dev.platform_data和output_mask成员填充好了。
当然,我没试过这样,有兴趣的小伙伴可以试试。
- 嵌入式Linux驱动笔记(八)------依赖Linux kernel驱动的pwm编写
- 嵌入式Linux应用学习(二)------依赖Linux kernel驱动的uart串口应用程序编写
- 编写Linux标准的PWM驱动
- 编写Linux驱动八步骤
- 编写Linux驱动八步骤
- 编写Linux驱动八步骤
- 嵌入式linux驱动模块编写
- Linux系统PWM驱动
- Linux系统PWM驱动
- 嵌入式linux驱动-网络设备驱动笔记
- 嵌入式linux驱动-触摸屏笔记
- 嵌入式linux驱动-I2C笔记
- 嵌入式linux驱动-DMA笔记
- linux驱动学习笔记1--字符型驱动的编写
- 嵌入式Linux设备驱动编写原理
- 嵌入式Linux设备驱动编写原理
- Android linux PWM驱动(s5pv210)
- linux驱动之PWM(定时器)
- Android线程管理(一)——线程通信
- 给结构体里定义的二维数组赋值
- Java集合-ArrayList源码分析
- 设计模式
- arch/x86/mm/pageattr.c
- 嵌入式Linux驱动笔记(八)------依赖Linux kernel驱动的pwm编写
- ssh登陆Linux
- 如何在Spark2.0.2中启动Ipython Notebook
- Hbase 集群维护
- HDU 2049-不容易系列之(4)——考新郎
- TreeSet集合的两种排序
- rbtree原理及应用--使用
- ZooKeeper学习笔记:netcat命令基本用法与客户端连接zookeeper
- QGCToolbox