1. 程式人生 > 其它 >exynos4412之PWM

exynos4412之PWM

linux kernel version:4.4.38

hardware version:exynos4412-tiny4412 1312B

涉及知識點:DTS,platform bus,PWM, pinctrl,

本次實驗使用XpwmTOUT1,使用示波器觀察輸出波形

首先看硬體電路,LCD4的30腳XpwmTOUT1,如果PWM正常工作,我們可以在示波器上看到預期的波形。

XpwmTOUT1對應的PCB:XpwmTOUT1/LCD_PWM/GPD0_1,可以知道是GPD0_1,繼續找datasheet中GPD0

下圖為GPD0CON暫存器的資訊,可以看到需要將此暫存器的bit[7:4]設定為0x2,TOUT_1模式,可以使用ioremap的方式進行設定,但是這裡我們使用DTS的方式,所以可以使用pinctrl子系統將引腳設定放在DTS中,讓系統自動配置。

以下為DTS的資訊:compatible是匹配platform driver用的,clock的部分我們使用CLK_PWM座位時鐘源(100MHz),PWM暫存器的資訊,對PWM電路進行設定使其輸出我們預期的波形,pinctrl部分為引腳設定的部分

 1     my_pwm {
 2         compatible = "ethan,pwm";
 3   
 4         clocks = <&clock CLK_PWM>;
 5         clock-names = "timers";
 6 
 7         reg = <0x139d0000 0x100
>; 8 9 pinctrl-names = "default"; 10 pinctrl-0 = <&pwm1_out>; 11 12 status = "okay"; 13 };

其中<&pwm1_out>的詳細資訊如下:注意2行和3行,就是將GPD0_1設定為0x2,TOUT_1模式,以輸出PWM波形

1         pwm1_out: pwm1-out {
2             samsung,pins = "gpd0-1";
3             samsung,pin-function = <2
>; 4 samsung,pin-pud = <0>; 5 samsung,pin-drv = <0>; 6 };

第6行:reg = <0x139d0000 0x100>; 是PWM暫存器的資訊,如下圖所示

TCFG0和TCFG1:這兩個暫存器是預分頻時鐘源的,設定相應的值,可以將時鐘源降頻。

TCON:主要控制自動裝載,電平翻轉,更新計數暫存器和比較暫存器,開始和結束PWM。

TCNTBn:儲存計數值

TCMPBn:儲存比較值

TCNTOn:儲存當前計數值

TINT_CSTAT:中斷相關,本文不涉及中斷

正文程式碼如下;主要看probe的部分

28行:獲取DTS中reg的資訊

34行:獲取DTS中clock的資訊,並在40行使能時鐘源,100MHz

45行:將28行中獲取到的reg的資訊,進行ioremap,為後續操作PWM電路做準備

53行:將分頻器設定為0x7c,也就是124,可將時鐘源分頻為800KHz

55行:將分頻器設定為0x30,可將時鐘源分頻為100KHz

這樣的時鐘源,可以在一秒內計數100000次,也即10微秒計數一次

59行:裝載計數暫存器器100,即為從100開始遞減,等減到和比較暫存器的值一樣時翻轉電平,減到0即為一個週期。

結合時鐘源,就可以計算PWM的輸出波形的週期,10微妙計數一次,一個週期計數100次,PWM輸出的波形週期即為1毫秒

61行:裝載比較暫存器80,計數器從100開始遞減,減到80翻轉電平。

64~72行:將TCON[9]置為1,把計數暫存器和比較暫存器的值更新到計數器和比較器裡面,再清零(不清零PWM無法工作)

75~77行:使能自動裝載和電平翻轉

80~82行:使能PWM timer1

然後就可以觀察示波器的波形

  1 #include <linux/err.h>
  2 #include <linux/gpio.h>
  3 #include <linux/fs.h>
  4 #include <linux/gpio/consumer.h>
  5 #include <linux/kernel.h>
  6 #include <linux/leds.h>
  7 #include <linux/module.h>
  8 #include <linux/of.h>
  9 #include <linux/of_gpio.h>
 10 #include <linux/of_irq.h>
 11 #include <linux/platform_device.h>
 12 #include <linux/property.h>
 13 #include <linux/slab.h>
 14 #include <linux/workqueue.h>
 15 #include <linux/interrupt.h>
 16 #include <linux/acpi.h>
 17 #include <linux/clk.h>
 18 #include <linux/delay.h>
 19 
 20 void __iomem *pwm_reg_base = NULL;
 21 static struct clk *my_pwm_clock;
 22 
 23 static int hello_probe(struct platform_device *pdev)
 24 {
 25     unsigned int tmp;
 26     struct resource    *mem_res;
 27 
 28     mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 29     if (mem_res == NULL) {
 30         dev_err(&pdev->dev, "Unable to get PWM MEM resource\n");
 31         return -ENXIO;
 32     }
 33 
 34     my_pwm_clock = devm_clk_get(&pdev->dev, "timers");
 35     if (IS_ERR(my_pwm_clock)) {
 36         dev_err(&pdev->dev, "Unable to acquire clock 'my_pwm_clock'\n");
 37         return 0;
 38     }
 39 
 40     if (clk_prepare_enable(my_pwm_clock)) {
 41         dev_err(&pdev->dev, "Couldn't enable clock 'my_pwm_clock'\n");
 42         return 0;
 43     }
 44     
 45     pwm_reg_base = devm_ioremap_resource(&pdev->dev, mem_res);
 46     if (IS_ERR(pwm_reg_base)) {
 47         dev_err(&pdev->dev, "Couldn't ioremap resource\n");
 48         clk_disable_unprepare(my_pwm_clock);
 49         return 0;
 50     }
 51 
 52     /*input clock = 100MHz / 125 = 800KHz*/
 53     writel(0x7c, pwm_reg_base);
 54     /*input clock = 800KHz / 8 = 100KHz*/
 55     writel(0x3 << 4, pwm_reg_base + 0x4);
 56 
 57 
 58     /*time 1 count buffer: 100*/
 59     writel(0x64, pwm_reg_base + 0x18);
 60     /*time 1 compare buffer: 80*/
 61     writel(0x50, pwm_reg_base + 0x1c);
 62 
 63 
 64     writel(0, pwm_reg_base + 0x8);
 65     /*update timer1 count & compare*/
 66     tmp = readl(pwm_reg_base + 0x8);
 67     tmp |= (1 << 9);
 68     writel(tmp, pwm_reg_base + 0x8);
 69 
 70     /*need clear*/
 71     tmp &= ~(1 << 9);
 72     writel(tmp, pwm_reg_base + 0x8);
 73 
 74     /*auto-load and inverter-on */
 75     tmp = readl(pwm_reg_base + 0x8);
 76     tmp |= (1 << 10) | (1 << 11);
 77     writel(tmp, pwm_reg_base + 0x8);
 78 
 79     /*enable timer1*/
 80     tmp = readl(pwm_reg_base + 0x8);
 81     tmp |= (0x1 << 8);
 82     writel(tmp, pwm_reg_base + 0x8);
 83 
 84     printk(KERN_ALERT "%s %d success===\n", __FUNCTION__, __LINE__);
 85     return 0;
 86 }
 87 
 88 static int hello_remove(struct platform_device *pdev)
 89 {
 90     clk_disable_unprepare(my_pwm_clock);
 91     printk(KERN_ALERT "%s %d success===\n", __FUNCTION__, __LINE__);
 92     return 0;
 93 }
 94 
 95 static struct of_device_id of_platform_hello_match[] = {
 96     { .compatible = "ethan,pwm",},
 97     { },
 98 };
 99 MODULE_DEVICE_TABLE(of, of_platform_hello_match);
100 
101 static struct platform_driver platform_hello_driver = {
102     .probe        = hello_probe,
103     .remove        = hello_remove,
104     .driver        = {
105         .name    = "my_pwm",
106         .of_match_table = of_platform_hello_match,
107     },
108 };
109 
110 module_platform_driver(platform_hello_driver);
111 
112 MODULE_AUTHOR("EthanDL");
113 MODULE_DESCRIPTION("platform pwm");
114 MODULE_LICENSE("GPL");

可以看到週期為1ms,20%時翻轉電平~