Zhengdian Atom Embedded Linux Driver Development–Linux PWM Driver

PWM is a very commonly used function. You can use PWM to control the motor speed, or you can use PWM to control the backlight brightness of the LCD. In this chapter, we will learn how to develop PWM driver under Linux.

PWM driver analysis

I won’t introduce what PWM is, let’s go directly to its use.

Input a PWM signal to the LCD backlight pin, so that the LCD backlight brightness can be adjusted by adjusting the duty cycle. Increasing the duty cycle will increase the backlight brightness, and reducing the duty cycle will reduce the backlight brightness. The focus is on the generation of the PWM signal and the control of the duty cycle.

PWM controller node under the device tree

Timer node

STM32MP157 has many channels of PWM, which are generated by timers:

  • TIM1/TIM8: These 2 are 16-bit advanced timers, mainly used for motor control. These two timers support PWM models, and each timer supports 4-channel PWM signals.
  • TIM2/TIM3/TIM4/TIM5: These 4 are general timers, TIM3/TIM4 are 16-bit timers, and TIM2/TIM5 are 32-bit timers. These 4 timers also support PWM output, and each timer supports 4-channel PWM signals.
  • TIM12/TIM13/TIM14: These three are 16-bit general-purpose timers. TIM12 supports 2-channel PWM signals, and TIM13/TIM14 each only supports 1-channel PWM signal.
  • TIM15/TIM16/TIM17: These three are also 16-bit general-purpose timers. TIM15 supports 2-channel PWM signals, and each timer of TIM16/TIM17 supports 1-channel PWM signal.

It can be seen that STM32MP157 has many PWM channels, and different PWM channels have different functions.
Select the appropriate PWM channel according to the actual situation. This section uses the PA10 pin to implement the PWM function, pay attention! The PA10 pin is used as the USB ID pin. If the development board used uses PA10 as the USB OTG ID pin, then the USB OTG interface of the development board cannot be connected to the computer when doing this experiment! The USB interface of the Zhengdian Atomic STM32MP157 development board uses a TypeC interface, so PA10 is not used as the ID pin.

Open the data sheet of STM32MP157 and you can see that PA10 can be used as channel 3 of TIM1.

TIM1 Introduction

In fact, you can read the bare metal development notes here. For the introduction of TIM1, an advanced timer, I only focus on the Linux driver part.

TIM1 device node

Next, take a look at the device tree of TIM1. The STM32 timer device tree binding information document is: Documentation/devicetree/bindings/mfd/stm32-timers.txt. Here is a brief summary of the timer node information.

1. Necessary parameters

  • compatible: must be “st,stm32-timers”.
  • reg: Timer controller physical register base address. For TIM1, this address is 0x44000000. This can be found in the STM32MP157 data sheet.
  • clock-names: clock source name, set to “int”.
  • clocks: clock source.

2. Optional parameters

  • resets: reset handle, used to reset the timer controller, please refer to the document reset/st, stm32-rcc.txt.
  • dmas: DMA channel, up to 7 channels of DMA.
  • dma-names: DMA name list, which must match the “dmas” attribute. Optional names are “ch1”, “ch2”, “ch3”, “ch4”, “up”, “trig”, and “com”.

3. Optional child nodes

The STM32 timer has a variety of functions, such as timing, PWM, counter, etc. Different functions need to be represented by different sub-nodes. There are three optional sub-nodes, which correspond to different functions:

  • pwm: The pwm subnode describes the PWM function of the timer. For detailed information about PWM, please refer to the binding document pwm/pwm-stm32.txt.
  • timer: The timer subnode describes the timing function of the timer. For timing related information, please refer to the binding document iio/timer/stm32-timer-trigger.txt.
  • counter: The counter subnode describes the counting function of the timer. For related information, please refer to the binding document counter/stm32-timer-cnt.txt.

After understanding the timer binding document, let’s take a look at the actual timer node of STM32MP157. Open stm32mp151.dtsi and find the device node named “timers1”. This is the TIM1 timer node. Contents as follows:
TIM1 node
Lines 19-23, the pwm function sub-node of TIM1, this is the focus of this section.

PWM device subnode

Through the above explanation of the timer binding document, we know that PWM is a sub-node of the timer. Let’s take a look at thePWM sub-node binding document: Documentation/devicetree/bindings/pwm/pwm-stm32.tx< /strong>t, briefly summarize the PWM sub-node attribute information:

  • compatible: must be “st,stm32-pwm”.
  • pinctrl-names: Set to “default”, you can also add “sleep”, so that the PWM pin introduces sleep mode when entering low power consumption.
  • pinctrl-n: PWM pin pinctrl handle, used to specify the PWM signal output pin.
  • #pwm-cells: should be set to 3.

The compatible attribute of the PWM node of STM32MP157 is “st,stm32-pwm”. You can search for this string in the Linux kernel source code to find the PWM driver file. This file is: drivers/pwm/pwm-stm32.c.

PWM subsystem

The Linux kernel provides a PWM subsystem framework, and you must comply with this framework when writing a PWM driver. The core of the PWM subsystem is the pwm_chip structure, which is defined in the file include/linux/pwm.h and is defined as follows:
pwm_chip structure
Line 292, the pwm_ops structure is a collection of various operating functions of PWM peripherals, which needs to be implemented by developers when writing PWM peripheral drivers. The pwm_ops structure is also defined in the pwm.h header file and is defined as follows:
pwm_ops structure
These functions in pwm_ops may not all be implemented, but the functions that configure PWM must be implemented, such as apply or config. The apply function on line 264 is the latest PWM configuration function. Use this function to configure the PWM period and duty cycle. In the old kernel, the config function on line 271 will be used to configure the PWM. Among them, config, set_polarity, enable and disable in lines 271-276 are all functions used by the old version of the kernel.

The core of the PWM subsystem driverinitializes the pwm_chip structure, and then registers pwm_chip with the kernel after the initialization is completed. The pwmchip_add function is used here. This function is defined in the drivers/pwm/core.c file. The function prototype is as follows:

int pwmchip_add(struct pwm_chip *chip)

The meanings of function parameters and return values are as follows:

  • chip: pwm_chip to be registered with the kernel.
  • Return value: 0, success; negative number, failure.

When uninstalling the PWM driver, you need to remove the previously registered pwm_chip from the kernel, which is used here.
pwmchip_remove function
, the function prototype is as follows:

int pwmchip_remove(struct pwm_chip *chip)

The meanings of function parameters and return values are as follows:

  • chip: pwm_chip to be removed.
  • Return value: 0, success; negative number, failure.

PWM driver source code analysis

Let’s briefly analyze the STM32MP157 PWM driver that comes with the Linux kernel. The driver file is the file pwm-stm32.c. Open this file, you can see that this is a standard platform device driver file, as shown below:
STM32MP157 PWM platform driver
Line 2, when the compatible attribute value of the device tree PWM node is “st, stm32-pwm”, this driver will be matched.

Line 14, the stm32_pwm_probe function will be executed when the device tree node matches the driver.

Before looking at the stm32_pwm_probe function, let’s take a look at the stm32_pwm structure. This structure is the STM32 PWM structure officially created by ST. This structure will run through the entire PWM driver. The stm32_pwm structure is defined in the pwm-stm32.c file. The content of the structure is as follows:
stm32_pwm structure
Focus on line 2. This is a pwm_chip structure member variable chip. As mentioned earlier, the core of the PWM subsystem is pwm_chip.

The stm32_pwm_probe function is as follows (with reduction):

Sample code 39.1.3.3 stm32_pwm_probe function
608 static int stm32_pwm_probe(struct platform_device *pdev)
609 {<!-- -->
610 struct device *dev = & amp;pdev->dev;
611 struct device_node *np = dev->of_node;
612 struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent);
613 struct stm32_pwm *priv;
614 int ret;
615
616 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
617 if (!priv)
618 return -ENOMEM;
619
620 mutex_init( & amp;priv->lock);
621 priv->regmap = ddata->regmap;
622 priv->clk = ddata->clk;
623 priv->max_arr = ddata->max_arr;
624 priv->chip.of_xlate = of_pwm_xlate_with_flags;
625 priv->chip.of_pwm_n_cells = 3;
626
627 if (!priv->regmap || !priv->clk)
628 return -EINVAL;
629
630 ret = stm32_pwm_probe_breakinputs(priv, np);
631 if (ret)
632 return ret;
633
634 stm32_pwm_detect_complementary(priv);
635
636 priv->chip.base = -1;
637 priv->chip.dev = dev;
638 priv->chip.ops = & amp;stm32pwm_ops;
639 priv->chip.npwm = stm32_pwm_detect_channels(priv);
640
641 ret = pwmchip_add( & amp;priv->chip);
642 if (ret < 0)
643 return ret;
644
645 platform_set_drvdata(pdev, priv);
646
647 return 0;
648

Line 616, priv is a structure pointer variable of type stm32_pwm, and memory is allocated for it here. The stm32_pwm structure has an important member variable chip, which is of type pwm_chip. So this line leads to the core component of the PWM subsystem pwm_chip. The focus later is to initialize the chip.

Lines 621-625: Initialize each member variable of priv. Lines 624 and 625 also initialize the two member variables of_xlate and of_pwm_n_cells of pwm_chip.

Line 630 calls the stm32_pwm_probe_breakinputs function to read the “st,breakinput” attribute and set the break input, which is not used in the routines in this chapter.

Line 634, calls the stm32_pwm_detect_complementary function to detect whether the complementary output function of TIM1 is enabled.

Lines 636-639, focus, Initialize each member variable of pwm_chip, Line 638 Set the ops function of pwm_chip to stm32pwm_ops, stm32pwm_ops contains the specific operations of PWM, slightly Later focused analysis. Line 639 sets the npwm of pwm_chip, that is, sets how many PWM channels are currently turned on. Here, the stm32_pwm_detect_channels function is used directly to read the CCER register of TIM1. The four bits CC1E (bit0), CC2E (bit4), CC3E (bit8) and CC4E (bit12) of the CCER register are used to turn on the 4-channel PWM of TIM1. If 1 means that the corresponding PWM channel is open. Therefore, the stm32_pwm_detect_channels function will directly read these 4 bits to determine whether the corresponding PWM channel is open.

Let’s focus on stm32pwm_ops, which is defined as follows:
stm32pwm_ops operation collection
Line 487 stm32_pwm_apply_locked is the final PWM setting function. The PWM frequency and duty cycle set in the application are ultimately completed by the stm32_pwm_apply_locked function. This function will ultimately operate the STM32 related registers.

The source code of stm32_pwm_apply_locked function is as follows:
stm32_pwm_apply_locked function
Line 478 adds a mutex lock to prevent competition. Only one application can set PWM at a time.

Line 479, Call the stm32_pwm_apply function to set PWM.

The contents of the stm32_pwm_apply function are as follows:
stm32_pwm_apply function
Line 453, before setting the PWM, first call the stm32_pwm_disable function to turn off the PWM.

Line 458, calls the stm32_pwm_set_polarity function to set the polarity of the specified PWM channel.

Line 460, call stm32_pwm_config to set the PWM frequency and duty cycle.

Line 465, after the PWM setting is completed, the stm32_pwm_enable function is called to enable the PWM.

The contents of the stm32_pwm_config function are as follows:

Sample code 39.1.3.7 stm32_pwm_config function
322 static int stm32_pwm_config(struct stm32_pwm *priv, int ch,
323 int duty_ns, int period_ns)
324 {<!-- -->
325 unsigned long long prd, div, dty;
326 unsigned int prescaler = 0;
327 u32 ccmr, mask, shift;
328
329 /* Period and prescaler values depends on clock rate */
330 div = (unsigned long long)clk_get_rate(priv->clk) * period_ns;
331
332 do_div(div, NSEC_PER_SEC);
333 prd = div;
334
335 while (div > priv->max_arr) {<!-- -->
336 prescaler + + ;
337 div = prd;
338 do_div(div, prescaler + 1);
339 }
340
341 prd = div;
342
343 if (prescaler > MAX_TIM_PSC)
344 return -EINVAL;
345
346/*
347 * All channels share the same prescaler and counter so when two
348 * channels are active at the same time we can't change them
349 */
350 if (active_channels(priv) & amp; ~(1 << ch * 4)) {<!-- -->
351 u32 psc, arr;
352
353 regmap_read(priv->regmap, TIM_PSC, & amp;psc);
354 regmap_read(priv->regmap, TIM_ARR, & amp;arr);
355
356 if ((psc != prescaler) || (arr != prd - 1))
357 return -EBUSY;
358 }
359
360 regmap_write(priv->regmap, TIM_PSC, prescaler);
361 regmap_write(priv->regmap, TIM_ARR, prd - 1);
362 regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE);
363
364 /* Calculate the duty cycles */
365 dty = prd * duty_ns;
366 do_div(dty, period_ns);
367
368 write_ccrx(priv, ch, dty);
369
370 /* Configure output mode */
371 shift = (ch & amp; 0x1) * CCMR_CHANNEL_SHIFT;
372 ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
373 mask = CCMR_CHANNEL_MASK << shift;
374
375 if (ch < 2)
376 regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
377 else
378 regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
379
380 regmap_update_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE, TIM_BDTR_MOE);
381
382 return 0;
383}

The setting of PWM mainly involves two aspects: frequency and duty cycle. Lines 330 to 362 all set the PWM frequency. The function parameter period_ns is the period value, which is the frequency of PWM. TIM’sPSC register is used to set the timer divider value. After the TIM clock source is determined, setting the PSC divider value can get the final clock frequency of TIM. TIM’sARR register is an automatic loading register. Set TIM as a down counter. After the timer is turned on, the counter will decrease by one every clock cycle until the counter decreases to 0. At this time, load the value in ARR into the counter, and the counter will start counting down again, and this will repeat. Therefore, the two registers PSC and ARR determine the period value of PWM. Pay attention! Since a timer has 4 channels of PWM, and these 4 PWM channels can only be set to the same period, if you want multiple PWM signals with different periods, you have to use multiple different TIMs!

Lines 365-380, Set the duty cycle of PWM, and the parameter duty_ns represents the duty cycle. The 4 channels of PWM under one timer can be set to different duty cycles, which is equivalent to the 4 channels of PWM signals under one timer. The period is the same, but the duty cycle can be different. The design principle of the duty cycle is relatively simple. We have already known that when the timer clock frequency is determined (the PSC frequency division value remains unchanged), the value in theARR register determines the PWM period. This value is called the comparison value. Changing the comparison value can change the duty cycle of PWM. A timer of STM32MP157 has 4 PWM channels. Each channel has a register used to store comparison values. Therefore, there are 4 registers CCR1-CCR4 in total. These 4 registers are called comparison registers. So lines 365 and 366 calculate the value corresponding to the corresponding CCRx (x=1~4) register based on the parameter duty_ns, and then write the corresponding value to the corresponding register through the write_ccrx function on line 368.
inside the CCRx register.

Lines 371-378 are to set the PWM output mode. Channel 1 and channel 2 use the CCMR1 register, and channel 3 and channel 4 use the CCMR2 register. The last 380 lines set the BDTR register. This register is related to break and dead zone control and will not be used in this chapter.

At this point, the analysis of the PWM driver of STM32MP157 has been completed.

PWM driver writing

Modify device tree

There is no need to write the PWM driver anymore, ST has already been written, and the driver source code has been analyzed in detail before. In actual use, you only need to modify the device tree (this can be compared to the HAL library for bare metal development). The JP1 pin header on the STM32MP157 development board leads to the PA10 pin, as shown in the following figure:
PA10 pin
PA10 can be used as the PWM output pin of channel 3 of TIM1, so you need to add the pin information of PA10 and the PWM information of channel 3 of TIM1 in the device tree.

Add PA10 pin information

Open the stm32mp15-pinctrl.dtsi file, Add the pin information of GPIO1_IO04 under the iomuxc node, as shown below:

Sample code 39.2.1.1 TIM1 PWM pin information
1 pwm1_pins_a: pwm1-0 {<!-- -->
2 pins {<!-- -->
3 pinmux = <STM32_PINMUX('E', 9, AF1)>, /* TIM1_CH1 */
4 <STM32_PINMUX('E', 11, AF1)>, /* TIM1_CH2 */
5 <STM32_PINMUX('E', 14, AF1)>; /* TIM1_CH4 */
6 bias-pull-down;
7 drive-push-pull;
8 slew-rate = <0>;
9      }; 
10};
11
12 pwm1_sleep_pins_a: pwm1-sleep-0 {<!-- -->
13 pins {<!-- -->
14 pinmux = <STM32_PINMUX('E', 9, ANALOG)>, /* TIM1_CH1 */
15 <STM32_PINMUX('E', 11, ANALOG)>, /* TIM1_CH2 */
16 <STM32_PINMUX('E', 14, ANALOG)>; /* TIM1_CH4 */
17};
18};

It can be seen that ST has officially set the pin configurations of the three channels of CH1, CH2 and CH4 of TIM1, but only CH3 is needed here, so change the sample code 29.2.1.1 to the following:

Sample code 39.2.1.2 PA10 pin configuration
1 pwm1_pins_a: pwm1-0 {<!-- -->
2 pins {<!-- -->
3 pinmux = <STM32_PINMUX('A', 10, AF1)>; /* TIM1_CH3 */
4 bias-pull-down;
5 drive-push-pull;
6 slew-rate = <0>;
7};
8  }; 
9 
10 pwm1_sleep_pins_a: pwm1-sleep-0 {<!-- -->
11 pins {<!-- -->
12 pinmux = <STM32_PINMUX('A', 10, ANALOG)>; /* TIM1_CH3 */
13};
14};

In the sample code 39.2.1.2, PA10 is only reused as CH3 of TIM1. You must configure the pins according to the board hardware you are using.

Append information to timer1 node

There is already a “timers1” node in the stm32mp151.dtsi file, but this node is disabled by default and cannot be used directly. You need to add some content to the timers1 node in the stm32mp157d-atk.dts file. Add the following content to the stm32mp157d-atk.dts file:

Sample code 39.2.1.3 Content added to timers1
1 &timers1 {<!-- -->
2 status = "okay";
3 /* spare all DMA channels since they are not needed for PWM output */
4/delete-property/dmas;
5 /delete-property/dma-names;
6 pwm1: pwm {<!-- -->
7 pinctrl-0 = < & amp;pwm1_pins_a>;
8 pinctrl-1 = < & amp;pwm1_sleep_pins_a>;
9 pinctrl-names = "default", "sleep";
10 #pwm-cells = <2>;
11 status = "okay";
12};
13};

Lines 4 and 5 turn off the DMA function because PWM output does not require DMA.

Line 7, the pinctrl-0 attribute specifies the pinctrl node corresponding to the output pin used by CH3 of TIM1, set here
is pwm1_pins_a in sample code 39.2.1.2.

Shield other multiplexed IO

Check if there are any other peripherals in the device tree that use PA10. If so, they need to be blocked! Note that you cannot just block the pinctrl configuration information of PA10. You must also search for “gpioa 10” to see if it is used anywhere. If it is used, block it too.

After the device tree modification is completed, recompile the device tree, and then start the system using the new device tree.

Enable PWM driver

ST’s official Linux kernel has enabled the PWM driver by default, so there is no need to modify it, but in order to learn, you still need to know how to enable it. Open the Linux kernel configuration interface and find the configuration items according to the following path:

-> Device Drivers
-> Pulse-Width Modulation (PWM) Support
-> <*> STMicroelectronics STM32 PWM //Selected

The configuration is shown in the figure below:
PWM configuration item

PWM drive test

Determine the price of pwmchipX corresponding to TIM1

Start the system using the new device tree, and then connect the PA10 pin on the development board to the oscilloscope to view the PWM waveform diagram through the oscilloscope. You can configure PWM directly at the user level and enter the directory /sys/class/pwm, as shown in the following figure:
TIM1 corresponding PWM
Notice! There is a pwmchip0 in the picture above, but I don’t know whether this pwmchip0 is the file corresponding to TIM1. You can determine whether it belongs to TIM1 by checking whether the address corresponding to pwmchip0 is consistent with the starting address of the TIM1 timer register. Enter the pwmchip0 directory and its path will be printed:
pwmchip0 path name
As can be seen from the above figure, the starting address of the timer register corresponding to pwmchip0 is 0X44000000. According to the timers1 node in the sample code 39.1.1.1, we can know that the starting address of the register of the TIM1 timer is 0X44000000. Therefore, pwmchip0 is the file corresponding to TIM1.

Why use such a complicated method to determine the pwmchip file corresponding to the timer? Because when the STM32MP157 turns on the PWM function of multiple timers, its pwmchip file will change!

Calling out the pwm2 subdirectory of pwmchip0

pwmchip0 is the general directory of the entire TIM1, and TIM1 has 4 channels of PWM, each of which can be turned on or off independently. The corresponding numbers of CH1-CH4 are 0~3, so open CH3 of TIM1 and enter the following command:

echo 2 > /sys/class/pwm/pwmchip0/export

In the above command, 2 is TIM1_CH3. If you want to open CH1 of TIM1, it is 0. After the execution is completed, a subdirectory named “pwm2” will be generated in the pwmchip0 directory, as shown in the following figure:
Newly generated pwm2 subdirectory

Set PWM frequency

Note that the set here is the period value, the unit is ns. For example, the period of 20KHz frequency is 50000ns. Enter the following command:

echo 50000 > /sys/class/pwm/pwmchip0/pwm2/period

Set the PWM duty cycle

The duty cycle cannot be set directly here, but the ON time of one cycle is set, which is the high level time. For example, the ON time of 20% duty cycle at 20KHz frequency is 10000. Enter the following command :

echo 10000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle

Enable TIM1 CH3

Be sure to set the frequency and baud rate first, and finally turn on the timer, otherwise it will prompt a parameter error! Enter the following command to enable the PWM of channel 3 of TIM1:

echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable

After the setting is completed, use an oscilloscope to check whether the waveform is correct. If it is correct, it will be as shown in the figure below:
PWM waveform chart
As can be seen from the above figure, the PWM frequency is 20KHz and the duty cycle is 20% at this time, which is consistent with the settings. If you want to modify the frequency or duty cycle, you must pay attention to the two time values. For example, the period value of the 20KHz frequency is 50000ns, so when adjusting the duty cycle, the ON time cannot be set greater than 50000. Otherwise, it will prompt that the parameter is invalid.

Polarity reversal

You can also modify the polarity of PWM earlier. The PWM duty cycle set above is 20%. You only need to modify the polarity to change the duty cycle to 80%. Write “inversed” to the /pwmchip0/pwm2/polarity file to invert the polarity. The command is as follows:

echo “inversed” > /sys/class/pwm/pwmchip0/pwm2/polarity

After the polarity is reversed, the duty cycle becomes 80%. If you want to restore the original polarity, just write “normal” to the /pwmchip0/pwm2/polarity file. The command is as follows:

echo “normal” > /sys/class/pwm/pwmchip0/pwm2/polarity

Summary

The Linux kernel directly modifies the PWM, and only needs to add the corresponding content in the pinctrl and device tree.

In stm32mp15-pinctrl.dtsi, under the iomuxc node, find the reuse of TIM1 used in this experiment, which is the pwm1_pins_a and pwm1_sleep_pins_a nodes, and add the electrical attribute content.

In stm32mp157d-atk.dts, add content to &timer1, set status to “okay” to enable it, and then add pinctrl.

Finally, you can open the corresponding PWM output through the “echo” command in ./export in the corresponding pwmchip file in /sys/class/pwm, set the frequency in ./period and set the duty in ./duty_cycle Than; finally, “echo 1” in ./enable turns on the PWM output.

syntaxbug.com © 2021 All Rights Reserved.