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

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

2018-01-14 21:21 399 查看

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

宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64

目标板[底板]: Tiny4412SDK - 1506

目标板[核心板]: Tiny4412 - 1412

LINUX内核: 4.12.0

交叉编译器: gcc-arm-none-eabi-5_4-2016q3

日期: 2017-7-26 22:25:31

作者: SY

简介

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

设备树

# 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


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
的操作绑定在一个结构体上,交给应用层调用

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
时,实现绑定:

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;
}


解析设备树

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);
}
}


然后分配存储空间

//获取时钟
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


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_tab
f8ba
le = of_pwm_leds_match,
},
};
module_platform_driver(led_pwm_driver);


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

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
建立了联系。

继续分析:

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


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


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
即可使蜂鸣器鸣叫。

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

这个列表可以设置占空比的数值,因此可以确定设备树参数 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


接下来分析占空比:

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


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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: