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%時翻轉電平~