嵌入式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成员填充好了。
当然,我没试过这样,有兴趣的小伙伴可以试试。

原创粉丝点击