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%时翻转电平~

 

posted @ 2021-06-23 14:20  王东力  阅读(570)  评论(0)    收藏  举报