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

浙公网安备 33010602011771号