您的位置:首页 > 运维架构 > Linux

嵌入式Linux驱动笔记(八)------依赖Linux kernel驱动的pwm编写

2017-08-16 20:32 651 查看

你好!这里是风筝的博客,

欢迎和我一起多多交流。

之前我们写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这个结构体实现了,同时在smd
b6f4
k2440_map_io这个函数里,调用了两个 函数:

s3c24xx_init_io函数

samsung_set_timer_source函数

这两个函数里就会把samsung_device_pwm.dev.platform_data和output_mask成员填充好了。

当然,我没试过这样,有兴趣的小伙伴可以试试。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: