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

【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_table = 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

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值