【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。
相关文章推荐
- linux下基于S3C2440的PWM蜂鸣器移植以及驱动程序分析
- 【TINY4412】LINUX移植笔记:(12)NFS网络文件系统
- 【TINY4412】LINUX移植笔记:(24)设备树EEPROM驱动
- 【TINY4412】LINUX移植笔记:(13)SD卡驱动
- 【TINY4412】LINUX移植笔记:(25)设备树RTC驱动
- Linux-2.6.32.2内核在mini2440上的移植(十七)---移植PWM控制蜂鸣器驱动
- 基于S3C2440的Linux-3.6.6移植——PWM蜂鸣器驱动
- 【TINY4412】LINUX移植笔记:(2)BusyBox制作最小文件系统
- 【TINY4412】LINUX移植笔记:(15)SD卡启动Linux内核
- 【TINY4412】LINUX移植笔记:(26)设备树ADC驱动
- 【TINY4412】LINUX移植笔记:(3)Initramfs文件系统
- 【TINY4412】LINUX移植笔记:(16)eMMC启动Linux内核
- linux-2.6.32在mini2440开发板上移植(18)之移植PWM蜂鸣器驱动
- 【TINY4412】LINUX移植笔记:(1)移植前准备
- 【TINY4412】LINUX移植笔记:(4)Ramdisk文件系统
- 【TINY4412】LINUX移植笔记:(17)设备树HELLO WORLD驱动
- 【TINY4412】LINUX移植笔记:(27)设备树LCD驱动
- 【TINY4412】LINUX移植笔记:(5)Rootfs文件系统
- 【TINY4412】LINUX移植笔记:(18)设备树BEEP驱动
- Linux-2.6.32.2内核在mini2440上的移植(十七)---移植PWM控制蜂鸣器驱动 .