【TINY4412】LINUX移植笔记:(8)PWM蜂鸣器

环境

宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64
目标板[底板]: Tiny4412SDK - 1506
目标板[核心板]: Tiny4412 - 1412
LINUX内核: 4.12.0
交叉编译器: arm-none-linux-gnueabi-gcc(gcc version 4.8.3 20140320)
日期: 2017-9-9 20:30:09
作者: SY

简介

开发板的蜂鸣器接到PWM上,因此可以实现控制PWM的占空比和周期实现发出特殊的声音。

设备树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# exynos4412-tiny4412.dts
beep {
compatible = "pwm-leds";
beep1 {
label = "beep";
linux,default-trigger = "beep";
max-brightness = <0xff>;
pwms = <&pwm 0 100000000 0>;
};
};
&pwm {
samsung,pwm-outputs = <0>, <1>;
pinctrl-0 = <&pwm0_out &pwm1_out>;
pinctrl-names = "default";
status = "okay";
};
# exynos4.dtsi
pwm: pwm@139D0000 {
compatible = "samsung,exynos4210-pwm";
reg = <0x139D0000 0x1000>;
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clock CLK_PWM>;
clock-names = "timers";
#pwm-cells = <3>;
status = "disabled";
};
# exynos4412-pinctrl.dtsi
pwm0_out: pwm0-out {
samsung,pins = "gpd0-0";
samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};
pwm1_out: pwm1-out {
samsung,pins = "gpd0-1";
samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};

打开通过设备树打开PWM后,将蜂鸣器模拟为LED,通过控制小灯的方式控制LED

驱动分析

控制PWM的底层驱动在drivers/pwm/samsung.c,执行pwm_samsung_probe

1
2
3
4
5
6
7
8
9
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,
};

通过将pwm的操作绑定在一个结构体上,交给应用层调用

1
2
3
4
5
6
7
8
9
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,
};

在执行pwm_samsung_probe时,实现绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int pwm_samsung_probe(struct platform_device *pdev)
{
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;
ret = pwm_samsung_parse_dt(chip);
//of_xlate:很重要,关系到后面使用pwm-led应用层驱动时,底层操作其实是of_pwm_xlate_with_flags函 数,后面我们具体
chip->chip.of_xlate = of_pwm_xlate_with_flags;
chip->chip.of_pwm_n_cells = 3;
}

解析设备树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
{
//设备树匹配
match = of_match_node(samsung_pwm_matches, np);
static const struct of_device_id samsung_pwm_matches[] = {
{ .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant },
{ .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant },
{ .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant },
{ .compatible = "samsung,s5pc100-pwm", .data = &s5pc100_variant },
{ .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant },
{},
};
//遍历节点
of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
if (val >= SAMSUNG_PWM_NUM) {
dev_err(chip->chip.dev,
"%s: invalid channel index in samsung,pwm-outputs property\n",
__func__);
continue;
}
//标记输出的位
chip->variant.output_mask |= BIT(val);
}
}

然后分配存储空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//获取时钟
chip->base_clk = devm_clk_get(&pdev->dev, "timers");
//时钟使能
ret = clk_prepare_enable(chip->base_clk);
//设置私有数据
platform_set_drvdata(pdev, chip);
static inline void platform_set_drvdata(struct platform_device *pdev,
void *data)
{
dev_set_drvdata(&pdev->dev, data);
}
//添加设备
ret = pwmchip_add(&chip->chip);
int pwmchip_add(struct pwm_chip *chip)
{
return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL);
}
int pwmchip_add_with_polarity(struct pwm_chip *chip,
enum pwm_polarity polarity)
{
//为pwm分配空间
ret = alloc_pwms(chip->base, chip->npwm);
if (ret < 0)
goto out;
chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL);
if (!chip->pwms) {
ret = -ENOMEM;
goto out;
}
INIT_LIST_HEAD(&chip->list);
//将chip添加到pwm_chips链表中
list_add(&chip->list, &pwm_chips);
static LIST_HEAD(pwm_chips);
}

总的来说,驱动从设备树获取参数,然后分配pwm空间,再将当前pwm设备添加到pwm_chips链表中。

接着,我们分析跟操作pwm相关的driver,路径:./driver/led/led-pwm.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static const struct of_device_id of_pwm_leds_match[] = {
{ .compatible = "pwm-leds", },
{},
};
MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
static struct platform_driver led_pwm_driver = {
.probe = led_pwm_probe,
.remove = led_pwm_remove,
.driver = {
.name = "leds_pwm",
.of_match_table = of_pwm_leds_match,
},
};
module_platform_driver(led_pwm_driver);

我们的设备树已经声明了pwm-leds,那么led_pwm_probe将会执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
static int led_pwm_probe(struct platform_device *pdev)
{
//获取leds_pwm的子节点个数
count = of_get_child_count(pdev->dev.of_node);
ret = led_pwm_create_of(&pdev->dev, priv); -->
//从设备树获取配置参数
static int led_pwm_create_of(struct device *dev, struct led_pwm_priv *priv)
{
for_each_child_of_node(dev->of_node, child) {
led.name = of_get_property(child, "label", NULL) ? :
child->name;
led.default_trigger = of_get_property(child,
"linux,default-trigger", NULL);
led.active_low = of_property_read_bool(child, "active-low");
of_property_read_u32(child, "max-brightness",
&led.max_brightness);
ret = led_pwm_add(dev, priv, &led, child); -->
if (ret) {
of_node_put(child);
break;
}
}
}
static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
struct led_pwm *led, struct device_node *child)
{
if (child)
led_data->pwm = devm_of_pwm_get(dev, child, NULL); -->
}
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
const char *con_id)
{
pwm = of_pwm_get(np, con_id); -->
}
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
{
//获取设备树中的pwms = <&pwm 0 100000000 0>;
err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index,
&args);
//查找匹配的pwm设备
pc = of_node_to_pwmchip(args.np); -->
static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
{
struct pwm_chip *chip;
mutex_lock(&pwm_lock);
//我们又看到pwm_chips,这个全局变量的链表上在pwm驱动中已挂载了pwm设备,
现在遍历链表,查找匹配的device_node
list_for_each_entry(chip, &pwm_chips, list)
if (chip->dev && chip->dev->of_node == np) {
mutex_unlock(&pwm_lock);
return chip;
}
mutex_unlock(&pwm_lock);
return ERR_PTR(-EPROBE_DEFER);
}
//解析pwms参数,前面提到这个指针of_xlate,因为pc指针已经指向了底层pwm设备,这样调用的就是
前面的 of_pwm_xlate_with_flags 函数。
pwm = pc->of_xlate(pc, &args);
struct pwm_device *
of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
/* check, whether the driver supports a third cell for flags */
if (pc->of_pwm_n_cells < 3)
return ERR_PTR(-EINVAL);
/* flags in the third cell are optional */
if (args->args_count < 2)
return ERR_PTR(-EINVAL);
if (args->args[0] >= pc->npwm)
return ERR_PTR(-EINVAL);
pwm = pwm_request_from_chip(pc, args->args[0], NULL);
if (IS_ERR(pwm))
return pwm;
pwm->args.period = args->args[1];
pwm->args.polarity = PWM_POLARITY_NORMAL;
if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED)
pwm->args.polarity = PWM_POLARITY_INVERSED;
return pwm;
}
}

这样,可以得知设备树中的pwms = <&pwm 0 100000000 0>; 含义。第一个是设备号,必须小于pc->npwm

第二个参数是pwm->args.period周期,第三个参数是pwm->args.polarity

此时, pwm驱动层和leds-pwm建立了联系。

继续分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
struct led_pwm *led, struct device_node *child)
{
led_data->cdev.brightness_set_blocking = led_pwm_set; -->
static int led_pwm_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
__led_pwm_set(led_dat); -->
}
static void __led_pwm_set(struct led_pwm_data *led_dat)
{
int new_duty = led_dat->duty;
pwm_config(led_dat->pwm, new_duty, led_dat->period);
if (new_duty == 0)
pwm_disable(led_dat->pwm);
else
pwm_enable(led_dat->pwm);
}
}

这样我们找到了控制pwm输出的操作,只要调用brightness_set_blocking函数指针,即可让pwm工作,间接让蜂鸣器鸣叫。

搜索关键字brightness_set_blocking

1
2
3
4
5
6
7
8
static int __led_set_brightness_blocking(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (!led_cdev->brightness_set_blocking)
return -ENOTSUPP;
return led_cdev->brightness_set_blocking(led_cdev, value);
}

搜索__led_set_brightness_blocking

1
2
3
4
5
6
7
8
9
10
11
12
13
int led_set_brightness_sync(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (led_cdev->blink_delay_on || led_cdev->blink_delay_off)
return -EBUSY;
led_cdev->brightness = min(value, led_cdev->max_brightness);
if (led_cdev->flags & LED_SUSPENDED)
return 0;
return __led_set_brightness_blocking(led_cdev, led_cdev->brightness);
}

这样,只要调用函数led_set_brightness_sync即可使蜂鸣器鸣叫。

现在我们来分析蜂鸣器的周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这个列表可以设置占空比的数值,因此可以确定设备树参数 max-brightness = <0xff>;
enum led_brightness {
LED_OFF = 0,
LED_ON = 1,
LED_HALF = 127,
LED_FULL = 255,
};
蜂鸣器鸣叫,需要配置寄存器参数,这个肯定是执行了函数:pwm_samsung_config
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
}
#define NSEC_PER_SEC 1000000000L
可以得知,pwms = <&pwm 0 100000000 0>;周期最大可以设置为 1000000000L

接下来分析占空比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int led_pwm_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct led_pwm_data *led_dat =
container_of(led_cdev, struct led_pwm_data, cdev);
unsigned int max = led_dat->cdev.max_brightness;
unsigned long long duty = led_dat->period;
duty *= brightness;
do_div(duty, max);
if (led_dat->active_low)
duty = led_dat->period - duty;
led_dat->duty = duty;
__led_pwm_set(led_dat);
return 0;
}
上述可以合并为:duty = led_dat->period * brightness / led_dat->cdev.max_brightness;
假如设置brightness = LED_HALF(127),
duty = led_dat->period * 127 / 255 = 0.5 * led_dat->period;
这与 enum led_brightness值是吻合的!

验证

找到函数led_pwm_add

1
2
3
4
5
6
7
8
9
10
11
12
static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
struct led_pwm *led, struct device_node *child)
{
ret = led_classdev_register(dev, &led_data->cdev);
if (ret == 0) {
priv->num_leds++;
led_pwm_set(&led_data->cdev, LED_HALF);
} else {
dev_err(dev, "failed to register PWM led for %s: %d\n",
led->name, ret);
}
}

烧录测试,开发板上电后,蜂鸣器鸣叫500ms,关闭500ms

SY wechat
扫一扫,用手机访问本站