Cover image for Linux PWM

Linux PWM

Words 11.3k
Views
Visitors

Timeline

Timeline

2026-01-04

init

This article introduces the basic concepts and hardware implementation of PWM (Pulse Width Modulation) technology under Linux. It first explains the definition of PWM, which is a technology that controls analog circuits by changing the duty cycle of high/low levels, and explains the two core parameters: period and duty cycle. It then describes the area equivalence principle, which states that pulses of equal area produce basically the same response for inertial elements, and gives examples showing that different average voltages can be output by adjusting the duty cycle. It also lists applications of PWM in motor control, lighting control, and signal modulation. Finally, it summarizes the features of the 16 hardware PWM channels built into the iTOP-RK3568 chip, including independent control, interrupt-driven operation, programmable prescaler, 32-bit timer/counter, capture mode, continuous/one-shot output modes, and reference mode, and specifically points out that 4 of them are suitable for infrared applications. The article also compares the advantages and disadvantages of hardware PWM and provides a PWM pin multiplexing function table, making it convenient for developers to select PWM channels according to actual needs.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Topics
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. GPIO subsystem
13. Input subsystem
14. 1-Wire
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network devices
23. ADC
24. IIO
25. USB
26. LCD

PWM Basics

PWM The full name is Pulse Width Modulationtranslated into Chinese as pulse width modulation. It is a technology that uses digital signals to control analog circuits. By changing the duty cycle of high/low levels, it can control the average voltage or power, thereby achieving the purpose of controlling analog quantities.

A PWM signal consists of a rectangular wave, which includes high-level time and low-level time. A PWM signal can be described by two parameters: period and duty cycle. The specific descriptions of the two parameters are as follows:

  • Period (T): refers to the time required for one complete high/low level cycle. Frequency is the reciprocal of the period, indicating how many cycles occur in 1 second, with the unit Hz. For example, if one period is 20ms, then there are 50 PWM cycles in one second.
  • Duty Cycle: refers to the ratio of high-level time to the period, usually expressed as a percentage. For example, if the period is 20ms and the high-level time is 10ms, then the duty cycle is 50%.

PWM
PWM

Area Equivalence Principle

The area equivalence principle is defined as: even if the width and amplitude of pulse signals are different, as long as their areas (pulse width × pulse amplitude) are the same,the response of the inertial elementis also basically the same, as shown in the figure below.

Area Equivalence Principle
Area Equivalence Principle

For example: the high level of the RK3568 IO port is 3.3V, and the low level is 0V. If we want to output different analog voltages, we can use the PWM area equivalence principle to achieve this. By changing the duty cycle of the PWM signal, the average voltage of the output signal can be adjusted.

  • When the duty cycle is 50%, the high-level time and low-level time are equal, and the average output voltage is 1.65V (3.3V × 50%).

  • When the duty cycle is 75%, the high-level time accounts for 75% of the total period, and the average output voltage is 2.47V (3.3V × 75%).

In addition to voltage control, PWM also has the following application scenarios:

  • Motor control: controls motor speed and torque.
  • Lighting control: controls LED brightness.
  • Signal modulation: used for modulation in radio communications.

iTOP-RK3568 PWM Interface

The RK3568 chip has 16 built-in PWM channels, which can independently control and output PWM waveforms. It also supports interrupt-driven operation and can flexibly respond to interrupt events.

  • The PWM channels use a programmable prescaler, which can first divide the bus clock and then perform a second division.
  • The PWM channels have a built-in 32-bit timer/counter function.
  • PWM channels support capture mode, which can capture information such as the pulse width of external input signals.
  • PWM channels support continuous output mode and one-shot output mode.
  • PWM channels provide reference mode, which can output waveforms with various duty cycles.
  • The four PWM channels PWM3, PWM7, PWM11, and PWM15 are optimized and particularly suitable for infrared applications.

The 16 PWM channels above are all hardware PWM, that is, PWM output is implemented by a dedicated PWM hardware module. The introduction to hardware PWM is as follows:

Hardware PWM:

  1. Implementation methodHardware PWM is a method of implementing PWM output through a dedicated PWM hardware module.
  2. Advantages:
    • Low CPU usage: PWM output is automatically completed by the hardware module without CPU intervention.
    • High PWM output frequency and resolution, capable of reaching MHz-level frequencies and ns-level resolution.
    • The output waveform is stable and reliable, and is not easily affected by CPU load.
  3. Disadvantages:
    • Requires a dedicated hardware PWM module, resulting in higher cost.
    • PWM output pins are limited; output is only available on predefined pins.

The hardware PWM pin multiplexing function table for the iTOP-RK3568 is shown below. PWM can be selected according to this table:

PWM Interfacepinctrl functionNet labelCorresponding GPIOFunction
PWM0PWM0_M0Working_LEDEN_H_GPIO0_B7GPIO0_B7LED control pin
PWM0_M1LCD0_PWREN_H_GPIO0_C7GPIO0_C7LVDS screen power supply enable pin
PWM1PWM1_M0VGA_HPDIN_GPIO0_C0GPIO0_C0VGA hot-plug detection pin
PWM1_M1PCIE20_WAKEN_M0GPIO0_B5PCIE2.0 wake-up
PWM2PWM2_M0PCIE20_PERSTn_GPIO0_C1GPIO0_C1PCIE2.0 reset pin
PWM2_M1TP_RST_L_GPIO0_B6GPIO0_B6MIPI touchscreen reset pin
PWM3_IRPWM3_IRPWM3_IRGPIO0_C2Infrared receiver
PWM4PWM4LCD0_BL_PWM4GPIO0_C3LVDS screen backlight
PWM5PWM5LCD1_BL_PWM5GPIO0_C4MIPI screen backlight
PWM6PWM6PWM_FANGPIO0_C5Cooling fan speed adjustment
PWM7_IRPWM7_IRRS485_DIR_GPIO0_C6GPIO0_C6485 transceiver control pin
PWM8PWM8_M0UART4_RX_M1GPIO3_B1Serial port 4 receive pin
PWM8_M1SDMWC0_D0GPIO1_D5SD card data line
PWM9PWM9_M0UART4_TX_M1GPIO3_B2UART4 TX pin
PWM9_M1SDMWC0_D1GPIO1_D5SD card data line
PWM10PWM10_M0GPIO3_B5GPIO3_B5Unused
PWM10_M1SDMWC0_CMDGPIO2_A1SD card CMD pin
PWM11PWM11_IR_M0GPIO3_B6GPIO3_B6Unused
PWM11_IR_M1CIF_CLKOUTGPIO4_C0Unused
PWM12PWM12_M0GMAC0_RSTn_GPIO3_B7GPIO3_B7Ethernet 0 reset pin
PWM12_M1GPIO4_C5GPIO4_C5UART9 TX pin
PWM13PWM13_M0GMAC0_INT/PMEB_GPIO3_C0GPIO3_C0Ethernet 0 interrupt pin
PWM13_M1GPIO4_C6GPIO4_C6UART9 RX pin
PWM14PWM14_M0UART7_TX_M1GPIO3_C4UART7 TX pin
PWM14_M14G_DISABLE_GPIO4_C2GPIO4_C2CAN1 RX
PWM15PWM15_IR_M0UART7_RX_M1GPIO3_C5UART7 RX pin
PWM15_IR_M1HDMI_RX_INT_L_GPIO4_C3GPIO4_C3CAN1 TX

When hardware PWM is insufficient, GPIO can be used to simulate PWM, which can also be called software PWM. The introduction to software PWM is as follows:

Software PWM:

  1. Implementation methodSoftware PWM is a method of implementing PWM output through software programming. By using timer interrupts or loop counting, the switching time of the output pin’s high and low levels is controlled in software, thereby generating PWM waveforms.
  2. Advantages
    • It is highly flexible and can generate PWM waveforms on any GPIO pin.
    • Low cost, no additional hardware PWM module is required.
  3. Disadvantages
    • High CPU usage, because the pin level needs to be controlled in real time in interrupt service routines or loops.
    • The frequency and resolution of PWM output are affected by the CPU clock frequency and interrupt response time, making it difficult to achieve high frequency and high resolution.
    • It has high requirements for CPU performance and real-time capability.

PWM subsystem framework

The PWM subsystem can be divided into three layers: user space, kernel space, and hardware layer.

Kernel space includes the PWM device driver layer, PWM core layer, and PWM adapter driver layer.

PWM subsystem framework
PWM subsystem framework

PWM device driver layer

The PWM device driver layer plays a key role in the Linux kernel, providing a unified, flexible, and efficient PWM device access interface for upper-layer applications.

  • pwm_device
    • Represents a slave device connected to the PWM bus.
    • Contains information such as the PWM channel number of the slave device and the PWM controller it belongs to.
  • pwm_driver
    • Implements the driver for a specific PWM device.
    • Responsible for operations such as initialization, configuration, start and stop of the PWM device.
    • Interacts with the PWM hardware device through pwm_chip.
    • Provides a standardized PWM device access interface to the upper layer.
  • /dev/xxxDevice Node
    • Provides an interface for upper-layer applications to access PWM devices.
    • By opening/reading/writing/controlling device nodes, applications can interact with PWM devices.
    • The kernel PWM subsystem is responsible for forwarding application operations to the corresponding pwm_chip driver.

PWM core layer

The PWM core layer is an important component in the Linux kernel responsible for managing and controlling PWM devices. Its main functions are as follows:

  • PWM device management
    Through thestruct pwm_deviceUniformly describe PWM device information, such as device name, associated controller, etc. Provide a standardized PWM device access API for upper-layer applications.
  • PWM parameter configuration
    providepwm_configInterface for configuring the working parameters of the PWM device, such as period, duty cycle, polarity, etc. Converts upper-layer configuration parameters into a form understandable by the underlying hardware and sets them through the adapter driver.
  • PWM output control
    providepwm_enableandpwm_disableInterface for controlling the enabling and disabling of PWM output. Responsible for calling the corresponding functions of the adapter driver to perform actual output control.
  • PWM sysfs interface
    The PWM core layer also provides a set of sysfs-based interfaces for upper-layer applications to configure and control PWM devices. These sysfs interfaces include:
    • /sys/class/pwm/pwmX/period: Set PWM period
    • /sys/class/pwm/pwmX/duty_cycle: Set PWM duty cycle
    • /sys/class/pwm/pwmX/polarity: Set PWM polarity
    • /sys/class/pwm/pwmX/enable: Control the enabling and disabling of PWM output

PWM controller driver layer

The PWM controller driver layer is a key component connecting PWM hardware and the upper-layer PWM core layer. It is responsible for abstracting and encapsulating the underlying PWM hardware, adapting to different types of PWM devices, providing a standardized hardware control interface to the upper layer, and also handling tasks such as hardware-related initialization, state management, and power management.

Hardware abstraction

The PWM controller driver layer is responsible for abstracting and encapsulating the underlying PWM hardware devices. It defines a set of standardizedpwm_opsinterfaces, including device initialization, parameter configuration, output control, and other functions.

The upper-layer PWM core layer can indirectly control the underlying PWM hardware by calling these standard interfaces, without needing to pay attention to specific hardware implementation details.

Hardware Adaptation

The PWM controller driver needs to implementpwm_opsthe functions defined in the interface to adapt to the specific working mode of the underlying PWM hardware. These adaptation functions convert the standardized requests from the upper-layer PWM core layer into operation instructions that the underlying hardware can understand.

Through this adaptation layer, the PWM core layer can seamlessly support different types of PWM hardware devices.

Hardware Initialization

The PWM controller driver is responsible for completing the initialization of the PWM hardware device, including the configuration and initialization of related resources such as GPIO, clock, and power.

When the PWM device is registered, the adapter driver performs initialization operations to ensure that the hardware device is in an available state.

State Management

The adapter driver needs to maintain the current state of the PWM hardware device, such as whether the device has been enabled and the current operating parameters. This state information is fed back to the upper-layer PWM core layer to ensure that the core layer can correctly control and manage the PWM device.

At this point, the explanation of the PWM subsystem framework is complete. In the next chapter, we will explain how to operate PWM through the sysfs interface.

Using the sysfs Interface to Operate PWM

The PWM core layer provides a sysfs interface for operating PWM. However, to operate PWM, you first need to configure the corresponding IO port as a PWM function. The selected IO port is pin 6 of the 20-pin GPIO interface on the iTOP-RK3568 backplane, as shown in the following figure:

Hardware Connection
Hardware Connection

According to the PWM pin multiplexing table, the PWM12_M1 function pin is multiplexed as UART9 by default, so the device tree needs to be modified to set the multiplexing function to PWM.

Find the uart9 node, change its status from ‘okay’ to ‘disabled’, and then configure PWM12:

1234
&pwm12 {	status = "okay";	pinctrl-0 = <&pwm12m1_pins>;};

/sys/class/pwm/

First, go to the/sys/class/pwm/directory, as shown in the following figure:

/sys/class/pwm/
/sys/class/pwm/

The corresponding relationship is as follows

pwm
pwm

PWM12_M1 corresponds to pwmchip3. After entering the pwmchip3 directory, it is shown in the following figure

pwmchip3
pwmchip3

There are seven files in this directory, namely deviceexportnpwmpowersubsystemuevent and unexportThe three attribute files that need attention are export, npwm, and unexport.

  • npwmIt is a read-only attribute. Reading this file tells you how many PWM outputs are available under this PWM controller, as shown below:

npwm
npwm

  • export: Before using PWM, export it via the export attribute, as shown below:

export
export

  • unexport: After using PWM, you need to delete the exported PWM, for example:

unexport
unexport

Note that both the export and unexport files are write-only and have no read permission.

Then use the following command to export the pwm0 directory. After successful export, enter the folder as shown below:

/sys/class/pwm/pwmchip3/pwm0
/sys/class/pwm/pwmchip3/pwm0

  • polarity: Used to view PWM polarity, with normal and inversed options.
    • normal means the PWM signal is positive polarity, i.e., high level is the active signal.
    • inversed means the PWM signal is negative polarity, i.e., low level is the active signal.
  • period: Used to configure the PWM period, readable and writable; write a string numeric value, in ns (nanoseconds).
  • duty_cycle: Used to configure the PWM duty cycle, readable and writable; write a string numeric value, also in ns.
  • enable: Enable PWM output. Usually, after configuring PWM, enable PWM. Readable and writable.
    • Writing “0” disables PWM;
    • Writing “1” enables PWM.

SG-90 Servo Introduction

The SG90 servo is a small servo motor widely used in fields such as robotics, remote-controlled aircraft, and remote-controlled cars. It is favored for its compact size, light weight, low power consumption, and high cost-effectiveness.

Working Principle

The SG90 servo controls its position through Pulse Width Modulation (PWM) signals.The width of the PWM signal determines the rotation angle of the servo.

The typical control signal period is 20ms, where the duration of the high-level portion (0.5ms to 2.5ms) corresponds to the servo’s rotation range from 0 to 180 degrees. The specific correspondence is as follows:

High-level durationRotation angle
0.5 ms
1.0 ms45°
1.5 ms90°
2.0 ms135°
2.5 ms180°

Hardware Connection

Cable colorFunctionBackplane 20-pin pin numberNet label
Red wirePower positive (5V)10/12VCC5V0_SYS
Brown wirePower negative (GND)19/20GND
Orange wireControl signal wire (PWM signal)6UART9_TX_M1(GPIO4_C5)

Application scenarios

  • Robotics: Used for joints or other movable parts in robots; its precise angle control makes it suitable for multi-degree-of-freedom motion.
  • Remote control models: Used to control the direction of control surfaces or wheels, enabling flexible maneuvering.

Example

Use the following commands to rotate the servo 135 degrees:

12345678
# Set the PWM period to 20 msecho 20000000 > period# Set the PWM high-level duration to 2 msecho 2000000 > duty_cycle# Set the PWM polarity to positive (normal)echo normal > polarity# Enable PWM outputecho 1 > enable

PWM Controller Registration Process Analysis

The PWM-related nodes in the rk3568.dtsi device tree are as follows:

123456789101112131415161718192021
pwm0: pwm@fdd70000 {	compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";	reg = <0x0 0xfdd70000 0x0 0x10>;	#pwm-cells = <3>;	pinctrl-names = "active";	pinctrl-0 = <&pwm0m0_pins>;	clocks = <&pmucru CLK_PWM0>, <&pmucru PCLK_PWM0>;	clock-names = "pwm", "pclk";	status = "disabled";};pwm1: pwm@fdd70010 {	compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";	reg = <0x0 0xfdd70010 0x0 0x10>;	#pwm-cells = <3>;	pinctrl-names = "active";	pinctrl-0 = <&pwm1m0_pins>;	clocks = <&pmucru CLK_PWM0>, <&pmucru PCLK_PWM0>;	clock-names = "pwm", "pclk";	status = "disabled";};

Based on the compatible property of the PWM node, the Rockchip PWM driver path can be found in the kernel directory:drivers/pwm/pwm-rockchip.c

12345678
static struct platform_driver rockchip_pwm_driver = {	.driver = {		.name = "rockchip-pwm",		.of_match_table = rockchip_pwm_dt_ids,	},	.probe = rockchip_pwm_probe,	.remove = rockchip_pwm_remove,};

Then let’s look at the probe function of this driver.rockchip_pwm_probe, the specific content is as follows:

rockchip_pwm_probe()

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
static int rockchip_pwm_probe(struct platform_device *pdev){	const struct of_device_id *id;// Device tree match ID	struct rockchip_pwm_chip *pc;// PWM chip structure	struct resource *r;// Resource information	u32 enable_conf, ctrl;	bool enabled;	int ret, count;// Return value and count variable    // Check device tree match	id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);	if (!id)		return -EINVAL;    // Allocate PWM chip structure	pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);	if (!pc)		return -ENOMEM;    // Get memory resources and map IO addresses	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);	pc->base = devm_ioremap(&pdev->dev, r->start,				resource_size(r));	if (IS_ERR(pc->base))		return PTR_ERR(pc->base);    // Get PWM bus clock	pc->clk = devm_clk_get(&pdev->dev, "pwm");	if (IS_ERR(pc->clk)) {		pc->clk = devm_clk_get(&pdev->dev, NULL);		if (IS_ERR(pc->clk))			return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),					     "Can't get bus clk\n");	}    // Get APB clock	count = of_count_phandle_with_args(pdev->dev.of_node,					   "clocks", "#clock-cells");	if (count == 2)		pc->pclk = devm_clk_get(&pdev->dev, "pclk");	else		pc->pclk = pc->clk;	if (IS_ERR(pc->pclk)) {		ret = PTR_ERR(pc->pclk);		if (ret != -EPROBE_DEFER)			dev_err(&pdev->dev, "Can't get APB clk: %d\n", ret);		return ret;	}	// Enable bus clock	ret = clk_prepare_enable(pc->clk);	if (ret) {		dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret);		return ret;	}	// Enable APB clock	ret = clk_prepare_enable(pc->pclk);	if (ret) {		dev_err(&pdev->dev, "Can't prepare enable APB clk: %d\n", ret);		goto err_clk;	}	pc->channel_id = rockchip_pwm_get_channel_id(pdev->dev.of_node->full_name);	if (pc->channel_id < 0 || pc->channel_id >= PWM_MAX_CHANNEL_NUM) {		dev_err(&pdev->dev, "Channel id is out of range: %d\n", pc->channel_id);		ret = -EINVAL;		goto err_pclk;	}	if (IS_ENABLED(CONFIG_PWM_ROCKCHIP_ONESHOT)) {		pc->irq = platform_get_irq(pdev, 0);		if (pc->irq < 0) {			dev_err(&pdev->dev, "Get oneshot mode irq failed\n");			ret = pc->irq;			goto err_pclk;		}		ret = devm_request_irq(&pdev->dev, pc->irq, rockchip_pwm_oneshot_irq,				       IRQF_NO_SUSPEND | IRQF_SHARED,				       "rk_pwm_oneshot_irq", pc);		if (ret) {			dev_err(&pdev->dev, "Claim oneshot IRQ failed\n");			goto err_pclk;		}	}	// Get pin controller	pc->pinctrl = devm_pinctrl_get(&pdev->dev);	if (IS_ERR(pc->pinctrl)) {		dev_err(&pdev->dev, "Get pinctrl failed!\n");		ret = PTR_ERR(pc->pinctrl);		goto err_pclk;	}	// Get the active state of the pin controller	pc->active_state = pinctrl_lookup_state(pc->pinctrl, "active");	if (IS_ERR(pc->active_state)) {		dev_err(&pdev->dev, "No active pinctrl state\n");		ret = PTR_ERR(pc->active_state);		goto err_pclk;	}    // Set driver data	platform_set_drvdata(pdev, pc);	    // Initialize PWM chip structure	pc->data = id->data;	pc->chip.dev = &pdev->dev;	pc->chip.ops = &rockchip_pwm_ops;	pc->chip.base = of_alias_get_id(pdev->dev.of_node, "pwm");	pc->chip.npwm = 1;	pc->clk_rate = clk_get_rate(pc->clk);	    // If polarity setting is supported, use the of with polarity_pwm_xlate function	if (pc->data->supports_polarity) {		pc->chip.of_xlate = of_pwm_xlate_with_flags;		pc->chip.of_pwm_n_cells = 3;	}	enable_conf = pc->data->enable_conf;	ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);	enabled = (ctrl & enable_conf) == enable_conf;    // Check whether it is center-aligned mode	pc->center_aligned =		device_property_read_bool(&pdev->dev, "center-aligned");    // Add PWM chip	ret = pwmchip_add(&pc->chip);	if (ret < 0) {		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);		goto err_pclk;	}	/* Keep the PWM clk enabled if the PWM appears to be up and running. */	if (!enabled)// If PWM is not enabled, disable the bus clock		clk_disable(pc->clk);	clk_disable(pc->pclk);	return 0;err_pclk:	clk_disable_unprepare(pc->pclk);err_clk:	clk_disable_unprepare(pc->clk);	return ret;}

Created astruct rockchip_pwm_chippointer variable of type, which is mainly used to describe the hardware characteristics and configuration information of the PWM controller on Rockchip series SoCs, as shown below:

struct rockchip_pwm_chip

123456789101112131415
struct rockchip_pwm_chip {	struct pwm_chip chip;// Generic PWM controller chip interface	struct clk *clk;// Main clock of the PWM controller	struct clk *pclk;// Peripheral clock of the PWM controller	struct pinctrl *pinctrl;// Pin mux controller of the PWM controller	struct pinctrl_state *active_state;// Pin state currently used by the PWM controller	const struct rockchip_pwm_data *data;// PWM controller configuration information specific to Rockchip SoC	void __iomem *base;// Kernel virtual address of the PWM controller register space	unsigned long clk_rate;// Clock frequency currently used by the PWM controller	bool vop_pwm_en; // Indicates whether the VOP (Video Output Processor) also enables the PWM function	bool center_aligned;// Indicates whether the PWM controller supports center-aligned mode	bool oneshot;// Indicates whether the PWM controller supports one-shot mode	int channel_id;	int irq;};

The second line of whichstruct pwm_chipThe structure is an abstraction and encapsulation of the PWM controller in the PWM subsystem.

It centralizes the basic information and operation functions of the PWM controller into a structure, facilitating management and invocation by the upper-level PWM framework. This structure is defined ininclude/linux/pwm.hfile, and the specific content is as follows:

struct pwm_chip

1234567891011121314151617181920212223242526
/** * struct pwm_chip - abstract a PWM controller * @dev: device providing the PWMs * @ops: callbacks for this PWM controller * @base: number of first PWM controlled by this chip * @npwm: number of PWMs controlled by this chip * @of_xlate: request a PWM device given a device tree PWM specifier * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier * @list: list node for internal use * @pwms: array of PWM devices allocated by the framework */struct pwm_chip {	struct device *dev;// The device to which the PWM controller belongs	const struct pwm_ops *ops;// The operation function set of the PWM controller	int base;// The first PWM channel number controlled by the chip	unsigned int npwm;// The number of PWM channels provided by the PWM controller	struct pwm_device * (*of_xlate)(struct pwm_chip *pc,					const struct of_phandle_args *args);	unsigned int of_pwm_n_cells;// The number of cells required to describe the PWM property in the device tree	/* only used internally by the PWM framework */	struct list_head list;// The global linked list where the PWM controller resides	struct pwm_device *pwms;// PWM channel device array};

wherepwm_opsRepresents the operation function set of the PWM controller

struct pwm_ops

12345678910111213141516171819202122232425262728293031323334353637
/** * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM * @free: optional hook for freeing a PWM * @capture: capture and report PWM signal * @apply: atomically apply a new PWM config * @get_state: get the current PWM state. This function is only *	       called once per PWM device when the PWM chip is *	       registered. * @get_output_type_supported: get the supported output type of this PWM * @owner: helps prevent removal of modules exporting active PWMs * @config: configure duty cycles and period length for this PWM * @set_polarity: configure the polarity of this PWM * @enable: enable PWM output toggling * @disable: disable PWM output toggling */struct pwm_ops {	int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);	void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);	int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,		       struct pwm_capture *result, unsigned long timeout);	int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,		     const struct pwm_state *state);	void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,			  struct pwm_state *state);	int (*get_output_type_supported)(struct pwm_chip *chip,			struct pwm_device *pwm);	struct module *owner;	/* Only used by legacy drivers */	int (*config)(struct pwm_chip *chip, struct pwm_device *pwm,		      int duty_ns, int period_ns);	int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,			    enum pwm_polarity polarity);	int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);	void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);};

Then return todrivers/pwm/pwm-rockchip.cthe file and findrockchip_pwm_opsthe operation set, and it can be seen that Rockchip only implements two functions: state get and apply, and the specific content is as follows

12345
static const struct pwm_ops rockchip_pwm_ops = {	.get_state = rockchip_pwm_get_state,	.apply = rockchip_pwm_apply,	.owner = THIS_MODULE,};

pwmchip_add()

rockchip_pwm_probe()callspwmchip_addthe function to add the PWM controller to the PWM subsystem,pwmchip_addThe function is defined indrivers/pwm/core.cfile, and the specific content is as follows:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
/** * pwmchip_add() - register a new PWM chip * @chip: the PWM chip to add * * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base * will be used. The initial polarity for all channels is normal. * * Returns: 0 on success or a negative error code on failure. */int pwmchip_add(struct pwm_chip *chip){	return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL);}EXPORT_SYMBOL_GPL(pwmchip_add);/** * pwmchip_add_with_polarity() - register a new PWM chip * @chip: the PWM chip to add * @polarity: initial polarity of PWM channels * * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base * will be used. The initial polarity for all channels is specified by the * @polarity parameter. * * Returns: 0 on success or a negative error code on failure. */int pwmchip_add_with_polarity(struct pwm_chip *chip,			      enum pwm_polarity polarity){	struct pwm_device *pwm;	unsigned int i;	int ret;	if (!chip || !chip->dev || !chip->ops || !chip->npwm)// Check the validity of the chip structure		return -EINVAL;	    // Check the validity of the pwm_ops structure	if (!pwm_ops_check(chip))		return -EINVAL;    // Acquire the global pwm_lock mutex	mutex_lock(&pwm_lock);    // Allocate a PWM device index number for the chip	ret = alloc_pwms(chip->base, chip->npwm);	if (ret < 0)		goto out;    // Dynamically allocate chip->npwm pwm_device structures	chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL);	if (!chip->pwms) {		ret = -ENOMEM;		goto out;	}    // Save the allocated PWM device index number	chip->base = ret;    // Initialize each PWM device	for (i = 0; i < chip->npwm; i++) {		pwm = &chip->pwms[i];		pwm->chip = chip;		pwm->pwm = chip->base + i;		pwm->hwpwm = i;		pwm->state.polarity = polarity;		pwm->state.output_type = PWM_OUTPUT_FIXED;        // Add the PWM device to the global PWM device tree		radix_tree_insert(&pwm_tree, pwm->pwm, pwm);	}    // Mark the allocated PWM device as used.	bitmap_set(allocated_pwms, chip->base, chip->npwm);    // Add the PWM controller to the global PWM controller linked list.	INIT_LIST_HEAD(&chip->list);	list_add(&chip->list, &pwm_chips);	ret = 0;    // If the kernel has Device Tree support enabled, register the PWM controller with the Device Tree.	if (IS_ENABLED(CONFIG_OF))		of_pwmchip_add(chip);out:	mutex_unlock(&pwm_lock);// Release the global pwm_lock mutex.	if (!ret)// If the addition succeeds, export the PWM controller in sysfs.		pwmchip_sysfs_export(chip);	return ret;}EXPORT_SYMBOL_GPL(pwmchip_add_with_polarity);

The primary purpose of this function is to initialize a new PWM controller and add it to the global linked list of the PWM subsystem for management.

PWM subsystem API

pwm_config()

Core function for PWM parameter configuration

ItemDetails
Function prototypeint pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
Core functionConfigure the PWM signal’sDuty cycleandPeriodis the core function for adjusting PWM output characteristics.
Parameter description1.pwm: pointer to the target PWM device structure (must first be obtained viadevm_of_pwm_getobtained)
2.duty_ns: duty cycle duration, unitnanoseconds (ns), indicating the duration of the high level within one PWM cycle
3.period_ns: total period of the PWM signal, unitnanoseconds (ns), indicating the total duration of high level + low level
Return valueSuccess: returns 0; failure: returns a negative error code (e.g., -EINVAL indicates invalid parameters)
Usage instructionsMust be before PWM is enabled (pwm_enable) before calling, to ensure the output PWM signal parameters are correctly configured; by adjustingduty_nsthe output average voltage can be changed (e.g., controlling LED brightness, motor speed), duty cycle calculation formula:Duty cycle (%) = (duty_ns / period_ns) * 100%

pwm_set_polarity()

PWM polarity setting function

ItemDetails
Function prototypeint pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);
Core functionSet the polarity of the PWM signal, defining the logical meaning of high and low levels
Parameter description1.pwm: pointer to the target PWM device structure
2.polarity: polarity enumeration value, only two types are supported:
-PWM_POLARITY_NORMAL: positive polarity (default), high level is “active/on” state, low level is “inactive/off” state
-PWM_POLARITY_INVERSED: negative polarity, low level is “active/on” state, high level is “inactive/off” state
Return valueSuccess: returns 0; failure: returns a negative error code
Usage instructionsSuitable for scenarios requiring reverse logic control (such as certain motor forward/reverse control, LED reverse drive circuits), it is recommended topwm_configafter,pwm_enablecall before

pwm_enable()

PWM enable function

ItemDetails
Function prototypeint pwm_enable(struct pwm_device *pwm);
Core functionEnable the PWM controller to start outputting the PWM signal with configured parameters (duty cycle, period, polarity)
Parameter descriptionpwm: pointer to the target PWM device structure
Return valueSuccess: returns 0; failure: returns a negative error code
Usage instructionsMust bepwm_config(andpwm_set_polarity, after calling (if polarity needs to be configured), ensure the output signal parameters are valid; after calling, PWM continues to output until callingpwm_disableStop

pwm_disable()

PWM disable function

ItemDetails
Function prototypevoid pwm_disable(struct pwm_device *pwm);
Core functionDisable the PWM controller and stop outputting PWM signals.
Parameter descriptionpwm: pointer to the target PWM device structure
Return valueNo return value
Usage instructionsCall when PWM output is not needed (e.g., device sleep, function disabled) to save system power; after calling, PWM no longer outputs signals, and to re-enable, call againpwm_enable(no need to reconfigure parameters; parameters are retained)

devm_of_pwm_get()

PWM device get function

ItemDetails
Function prototypestruct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, const char *con_id);
Core functionObtains the PWM device handle from the Linux Device Tree and automatically manages device resources (no manual release required).
Parameter description1.dev: pointer to the device structure corresponding to the current driver (e.g., in a platform driverstruct deviceinstance)
2.np: pointer to the device tree node corresponding to the PWM device
3.con_id: connection ID (string type), used to distinguish multiple PWM channels of the same device (if there is only one PWM channel, you can passNULL
Return valueSuccess: return pointerstruct pwm_devicea valid pointer; on failure: returns an error pointer of type ERR_PTR (which can be checked usingIS_ERR()macro check)
Usage instructionsis the preferred method for obtaining PWM devices in device drivers, based ondevmthe characteristics of the series interface; when the device is unloaded, the kernel automatically releases the PWM device resources, avoiding memory leaks; it must be called before using other PWM functions (pwm_configetc.) before calling; obtain the device handle first before subsequent operations.

Example

First modify the device tree, and similarly set the pwm12 multiplexing first:

1234
&pwm12 {	status = "okay";	pinctrl-0 = <&pwm12m1_pins>;};

Then add the sg90 node

1234
sg90 {	compatible = "sg90";	pwms = <&pwm12 0 20000000 1>;}
  • compatibleUsed to specify the device’s compatibility string, based on which the kernel matches the appropriate driver.
  • pwmsUsed to configure the PWM controller information.
    • &pwm12Represents a reference to the PWM controller
    • 0Represents the PWM channel number
    • 20000000Represents the PWM period in nanoseconds, i.e., set the period to 20ms
    • 1Indicates polarity, meaning active high, i.e., the high level of the PWM signal represents the ‘on’ state

Driver

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
#include <linux/module.h>#include <linux/init.h>#include <linux/moduleparam.h>#include <linux/fs.h>#include <linux/kdev_t.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/uaccess.h>#include <linux/pwm.h>dev_t dev_num;struct cdev cdev_test;struct class *class;struct device *device;struct pwm_device *sg90_pwm_device;// Callback function when opening the devicestatic int cdev_test_open(struct inode *inode, struct file *file) {    printk("This is cdev test open\n");    pwm_config(sg90_pwm_device, 500000, 20000000);    // Configure PWM parameters: pulse width of 500000 nanoseconds, period of 20,000,000 nanoseconds    pwm_set_polarity(sg90_pwm_device, PWM_POLARITY_NORMAL);    // Set PWM polarity to active high    pwm_enable(sg90_pwm_device);    // Start PWM    return 0;}// Callback function when writing to the devicestatic ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off) {    int ret;    unsigned char data[1];    printk("This is cdev test write\n");    // Copy data from user space to kernel space    ret = copy_from_user(data, buf, size);    if (ret) {        printk("copy_from_user failed\n");        return -EFAULT;    }    // Update PWM parameters: adjust the pulse width based on user input data    pwm_config(sg90_pwm_device, 500000 + data[0] * 100000 / 9, 20000000);    return size;}// Callback function when releasing the devicestatic int cdev_test_release(struct inode *inode, struct file *file) {    printk("This is cdev test release\n");    pwm_config(sg90_pwm_device, 500000, 20000000);    // Restore the initial PWM parameter configuration    pwm_disable(sg90_pwm_device);    // Stop PWM    return 0;}// Define the character device operation function setstatic struct file_operations cdev_test_ops = {    .owner = THIS_MODULE,    .open = cdev_test_open,    .write = cdev_test_write,    .release = cdev_test_release,};// Device probe functionstatic int sg90_probe(struct platform_device *pdev) {    int ret;    // Get PWM device    sg90_pwm_device = devm_pwm_get(&pdev->dev, NULL);    if (IS_ERR(sg90_pwm_device)) {        printk("Failed to get PWM device\n");        return PTR_ERR(sg90_pwm_device);    }    // Allocate device number    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");    if (ret < 0) {        printk("alloc_chrdev_region is error\n");        return ret;    }    printk("alloc_chrdev_region is ok\n");    // Initialize character device    cdev_init(&cdev_test, &cdev_test_ops);    cdev_test.owner = THIS_MODULE;    ret = cdev_add(&cdev_test, dev_num, 1);// Add character device    class = class_create(THIS_MODULE, "test");    // Create device class    device = device_create(class, NULL, dev_num, NULL, "sg90");    // Create device    printk("sg90_probe successful\n");    return 0;}// Device remove functionstatic int sg90_remove(struct platform_device *pdev) {    device_destroy(class, dev_num);    // Delete device    class_destroy(class);    // Delete device class    cdev_del(&cdev_test);    // Delete character device    unregister_chrdev_region(dev_num, 1);    // Release device number    printk("sg90_remove successful\n");    return 0;}// Device tree match tablestatic const struct of_device_id sg90_of_device_id[] = {    {.compatible = "sg90"},    {},};MODULE_DEVICE_TABLE(of, sg90_of_device_id);// Define platform driverstatic struct platform_driver sg90_platform_driver = {    .driver = {        .name = "sg90",        .of_match_table = sg90_of_device_id,    },    .probe = sg90_probe,    .remove = sg90_remove,};// Module initialization functionstatic int __init modulecdev_init(void) {    int ret;    // Register platform driver    ret = platform_driver_register(&sg90_platform_driver);    if (ret) {        printk("platform_driver_register is error\n");        return ret;    }    printk("platform_driver_register is ok\n");    return 0;}// Module exit functionstatic void __exit modulecdev_exit(void) {    // Unregister platform driver    platform_driver_unregister(&sg90_platform_driver);    printk("bye bye\n");}// Declare module license and authormodule_init(modulecdev_init);module_exit(modulecdev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("topeet");

app

1234567891011121314151617181920212223242526272829303132333435363738394041
#include <fcntl.h>  #include <unistd.h>  #include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) {    // argc represents the number of command-line arguments, including the program name itself    // argv is a string array that stores the individual command-line arguments    int fd;  // File descriptor, used to identify the opened device file    unsigned char buf[1];  // Store a single byte of data to be written to the device    // If the number of command-line arguments is less than 2, it means the value to write is missing; print usage and return an error    if (argc < 2) {        printf("Usage: %s <value>\n", argv[0]);        return -1;    }    // Open the device file "/dev/sg90" in write-only mode    fd = open("/dev/sg90", O_WRONLY);    if (fd < 0) {  // Failed to open the device file; output an error message and return an error        perror("open");        return -1;    }    // Convert the command-line argument to an integer and store it in buf[0]    buf[0] = (unsigned char)atoi(argv[1]);    // Write the 1-byte data from buf to the opened device file    if (write(fd, buf, 1) != 1) {  // Write failed; output an error message, close the file, and return an error        perror("write");        close(fd);        return -1;    }    // Delay for 3 seconds to simulate device operation    sleep(3);    // Close the device file    close(fd);    return 0;  // Program executed successfully}

Test

use./app 90Run the compiled executable; here 90 represents the angle of the servo rotation

Simulated PWM

When hardware PWM is insufficient, GPIO can be used to simulate PWM, which can also be called software PWM. Software PWM is a method of implementing PWM output through software programming, using timer interrupts or loop counting to control the switching time of the output pin’s high and low levels in software, thereby generating a PWM waveform.

High-precision timer

The implementation of software PWM relies on timer interrupts, andTraditional periodic timers are usually between 100Hz and 1000Hz, providing only millisecond-level precision, for some application scenarios that require high time precision (e.g., analog PWM), the precision is still insufficient.

To solve this problem, the Linux kernel introduced the high-resolution timer (hrtimers) mechanism.

hrtimersUsing a monotonic clock (CLOCK_MONOTONIC) and high-precision timestamps (ktime_t) as the base time, by utilizing underlying hardware timers (such asHPETLAPICtimers, etc.) to provide nanosecond-level time precision.

High-resolution timer structurestruct hrtimerThe content is as follows:

1234567
struct hrtimer {	struct list_head node; // Node of the timer in the clock base list	ktime_t _softexpires;// Soft expiration time; the actual expiration time may be slightly later.	enum hrtimer_restart (*function)(struct hrtimer *); // Callback function when the timer expires	unsigned long	state; // Status flags of the timer	struct hrtimer_clock_base *base; // The clock base to which the timer belongs}

High-resolution timer workflow

  1. Initialize timer

    usehrtimer_init()The function initializes astruct hrtimerobject, specifying the clock source and working mode to be used. During initialization, a clock base (hrtimer_clock_base) is assigned to the timer. Each CPU has its own clock base manager.

  2. Setting the timer
    usehrtimer_start()The function sets the expiration time of the timer and starts the timer. You can specify an absolute time or a relative time. The timer expiration time is converted to the soft expiration time of the corresponding clock base (_softexpires). The timer is inserted into the timer linked list of the clock base, sorted by expiration time.

  3. Timer expiration handling
    When a system clock interrupt occurs, it callshrtimer_interrupt()function.hrtimer_interrupt()it will traverse the timer linked list of the clock base to check whether there are expired timers. For expired timers, it calls their callback function (function) to handle the expiration event. If the timer has not expired, update the wake-up time of the next expired timer.

  4. Timer Management

    • hrtimer_cancel()This function is used to cancel a running timer.
    • hrtimer_forward()This function is used to forward the timer, i.e., recalculate the timer’s expiration time based on the current time.
    • hrtimer_start_range_ns()This function is used to set the expiration time range of the timer to improve timer precision.

High-Resolution Timer API

hrtimer_init

Timer Initialization

ItemDetails
Function prototypevoid hrtimer_init(struct hrtimer *timer, clockid_t clockid, enum hrtimer_mode mode);
Core functionInitializestruct hrtimerStructure that specifies the timer’s clock source and working mode.
Parameter description-timer: Points to the to-be-initializedhrtimerstructure pointer
-clockid: Clock source, common values:
-CLOCK_MONOTONIC: Monotonic clock (does not go backwards after system boot, recommended for timers)
-CLOCK_REALTIME: Real-time clock (can be modified, not recommended for high-precision timing)
-CLOCK_BOOTTIME: Monotonic clock that includes system suspend time
-mode: Working mode, common values:
-HRTIMER_MODE_ABS: Absolute time mode (timer triggers at a specified absolute time)
-HRTIMER_MODE_REL: Relative time mode (timer triggers after a specified duration from the current time)
Return valueNone
Usage instructionsMust be called before using other hrtimer APIs; it is a prerequisite step for timer operations.

hrtimer_start

Start the timer

ItemDetails
Function prototypeint hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
Core functionAdds the timer to the kernel timer queue and starts the timer (the expiration time can be set directly).
Parameter description-timer: points tohrtimerPointer to the structure -time: Expiration time (ktime_tType) -mode: time mode (samehrtimer_init
Return valueReturns 0 on success; returns a negative error code on failure.
Usage instructionsIt is the core function for starting a timer, allowing ‘set time + start’ to be completed in one step.

hrtimer_forward

Advance the timer expiration time.

ItemDetails
Function prototypektime_t hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
Core functionAdvance the expiration time in the timer callback function to implementperiodic timer
Parameter description-timer: points tohrtimerpointer to the structure
-now: current time (usually obtained usinghrtimer_cb_get_time(timer)obtained)
-interval: period interval (ktime_ttype)
Return valueReturns the new expiration time (ktime_ttype)
Usage instructionsMust be called in the timer callback function, together withreturn HRTIMER_RESTARTto implement periodic triggering

hrtimer_cancel

Cancel the timer

ItemDetails
Function prototypeint hrtimer_cancel(struct hrtimer *timer);
Core functionCancel a started timer and remove it from the kernel queue.
Parameter description-timer: points tohrtimerpointer to the structure
Return valueReturns 1 when the timer is active; returns 0 when it is stopped.
Usage instructionsUsed to terminate the timer early (e.g., when the module is unloaded or the function is disabled)

example

We use simulated PWM to achieve the LED breathing light effect:

LED Schematic
LED Schematic

The pin used by the LED isGPIO0_B7, and it is registered in the device tree using the LED subsystem. In this chapter, because we need to use simulated PWM to control this pin, we need to modify the device tree:

Originally:

123456789101112131415161718
#if LED_PWM        leds {                                                                                           				 compatible = "pwm-leds";                work {                        pwms = <&pwm0 0 500000 0>;                        linux,default-trigger = "heartbeat";                        default-state = "on";                };        };#else        leds {                compatible = "gpio-leds";                work {                        gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;                        linux,default-trigger = "heartbeat";                        default-state = "on";                };        };#endif

Change to:

1234
leds {	compatible = "pwm-leds";	led-gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;};

Driver

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
#include <linux/module.h>#include <linux/init.h>#include <linux/moduleparam.h>#include <linux/fs.h>#include <linux/kdev_t.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/uaccess.h>#include <linux/pwm.h>#include <linux/gpio/consumer.h>#include <linux/hrtimer.h>#include <linux/slab.h>dev_t dev_num;struct cdev cdev_test;struct class *class;struct device *device;// Define the PWM LED data structurestruct pwm_led_data {    int sum_count;  // Total pulse count of the PWM period    int high_count;  // PWM high-level pulse count    struct gpio_desc *gpiod;  // GPIO descriptor    struct hrtimer pwm_timer;  // High-resolution timer    ktime_t time;  // Timer interval};// Declare a pointer to the PWM LED data structurestruct pwm_led_data *data;// PWM timer callback functionenum hrtimer_restart pwm_timer_func(struct hrtimer *timer) {    static int timer_count = 0;  // Timer counter    struct pwm_led_data *mydata = container_of(timer, struct pwm_led_data, pwm_timer);    // If the counter reaches the total pulse count, set the GPIO to high level    if (timer_count == mydata->sum_count) {        gpiod_set_value(mydata->gpiod, 1);        timer_count = 0;    }    // If the counter reaches the high-level pulse count, set the GPIO to low level    if (timer_count == mydata->high_count) {        gpiod_set_value(mydata->gpiod, 0);    }    timer_count++;    // If the high-level pulse count is 0, reset the counter to 0    if (mydata->high_count == 0) {        timer_count = 0;    }    // Advance the timer by the time interval and restart it    hrtimer_forward(timer, hrtimer_cb_get_time(timer), mydata->time);    return HRTIMER_RESTART;}// Character device open callback functionstatic int cdev_test_open(struct inode *inode, struct file *file) {    printk("This is cdev test open\n");    return 0;}// Character device write callback functionstatic ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off) {    int ret;    int kbuf[2];    printk("This is cdev test write\n");    // Copy data from user space to kernel space    ret = copy_from_user(kbuf, buf, size);    if (ret != 0) {        printk("copy_from_user failed\n");        return -EFAULT;    }    // Update the PWM LED data structure    data->sum_count = kbuf[0];    data->high_count = kbuf[1];    return size;}// Character device release callback functionstatic int cdev_test_release(struct inode *inode, struct file *file) {    printk("This is cdev test release\n");    return 0;}// Character device operation function setstatic struct file_operations cdev_test_ops = {    .owner = THIS_MODULE,    .open = cdev_test_open,    .write = cdev_test_write,    .release = cdev_test_release,};// Platform device probe callback functionstatic int led_probe(struct platform_device *pdev) {    int ret;    // Allocate memory for PWM LED data structure    data = kmalloc(sizeof(struct pwm_led_data), GFP_KERNEL);    if (!data) {        printk("kmalloc failed\n");        return -ENOMEM;    }    // Initialize PWM LED data structure    data->sum_count = 20;    data->high_count = 10;    // Dynamically allocate device number    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");    if (ret < 0) {        printk("alloc_chrdev_region is error\n");        kfree(data);        return ret;    }    printk("alloc_chrdev_region is ok\n");    // Initialize character device    cdev_init(&cdev_test, &cdev_test_ops);    cdev_test.owner = THIS_MODULE;    ret = cdev_add(&cdev_test, dev_num, 1);    // Register character device with kernel    if (ret) {        printk("cdev_add is error\n");        unregister_chrdev_region(dev_num, 1);        kfree(data);        return ret;    }    class = class_create(THIS_MODULE, "test");    // Create device class    if (IS_ERR(class)) {        printk("class_create is error\n");        cdev_del(&cdev_test);        unregister_chrdev_region(dev_num, 1);        kfree(data);        return PTR_ERR(class);    }    device = device_create(class, NULL, dev_num, NULL, "pwm-gpio");    // Create device node    if (IS_ERR(device)) {        printk("device_create is error\n");        class_destroy(class);        cdev_del(&cdev_test);        unregister_chrdev_region(dev_num, 1);        kfree(data);        return PTR_ERR(device);    }    data->gpiod = gpiod_get(&pdev->dev, "led", GPIOD_OUT_HIGH);    // Get GPIO descriptor    if (IS_ERR(data->gpiod)) {        printk("gpiod_get is error\n");        device_destroy(class, dev_num);        class_destroy(class);        cdev_del(&cdev_test);        unregister_chrdev_region(dev_num, 1);        kfree(data);        return PTR_ERR(data->gpiod);    }    gpiod_set_value(data->gpiod, 1);    // Set GPIO to high level    // Initialize high-resolution timer    data->time = ktime_set(0, 1000000);  // 1 ms    hrtimer_init(&data->pwm_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);    data->pwm_timer.function = pwm_timer_func;    hrtimer_start(&data->pwm_timer, data->time, HRTIMER_MODE_REL);    // Start high-resolution timer    printk("led_probe successful\n");    return 0;}// Platform device remove callback functionstatic int led_remove(struct platform_device *pdev) {    hrtimer_cancel(&data->pwm_timer);    // Stop high-resolution timer    gpiod_put(data->gpiod);    // Release GPIO descriptor    device_destroy(class, dev_num);    // Delete device node    class_destroy(class);    // Delete device class    cdev_del(&cdev_test);    // Unregister character device from kernel    unregister_chrdev_region(dev_num, 1);    // Release device number    kfree(data);    // Free memory of PWM LED data structure    printk("led_remove successful\n");    return 0;}// Device tree match tablestatic const struct of_device_id led_of_device_id[] = {    {.compatible = "pwm-leds"},    {},};MODULE_DEVICE_TABLE(of, led_of_device_id);// Platform device driver structurestatic struct platform_driver led_platform_driver = {    .driver = {        .name = "pwm-leds",        .of_match_table = led_of_device_id,    },    .probe = led_probe,    .remove = led_remove,};// Module initialization functionstatic int __init modulecdev_init(void) {    int ret;    // Register platform device driver    ret = platform_driver_register(&led_platform_driver);    if (ret) {        printk("platform_driver_register is error\n");        return ret;    }    printk("platform_driver_register is ok\n");    return 0;}// Module exit functionstatic void __exit modulecdev_exit(void) {    // Unregister platform device driver    platform_driver_unregister(&led_platform_driver);    printk("bye bye\n");}// Module initialization and exit function registrationmodule_init(modulecdev_init);module_exit(modulecdev_exit);// Module license, author, and description informationMODULE_LICENSE("GPL");MODULE_AUTHOR("topeet");

Test

12345678910111213141516171819202122232425262728293031323334353637383940
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>int main(int argc, char *argv[]) {    int fd; // file descriptor to be operated on    int buf[2]; // Buffer, stores two integer values    // Check whether the number of command-line arguments is correct    if (argc != 3) {        fprintf(stderr, "Usage: %s <sum_count> <high_count>\n", argv[0]);        return -1;    }    // Open the PWM GPIO device file    fd = open("/dev/pwm-gpio", O_WRONLY);    if (fd < 0) {        perror("open error");        return -1;    }    // Convert command-line arguments to integers and store them in the buffer    buf[0] = atoi(argv[1]);    buf[1] = atoi(argv[2]);    // Write the data in the buffer to the PWM GPIO device    if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {        perror("write error");        close(fd);        return -1;    }    // Close the device file    close(fd);    return 0;}

Breathing light

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>int main(int argc, char *argv[]) {    int fd;    int buf[2];    int i;    // Check the number of arguments    if (argc != 3) {        printf("Usage: %s <duty_cycle> <period>\n", argv[0]);        return -1;    }    // Open the /dev/pwm-gpio device    fd = open("/dev/pwm-gpio", O_WRONLY);    if (fd < 0) {        printf("Error opening /dev/pwm-gpio\n");        return -1;    }    // Set PWM parameters    buf[0] = atoi(argv[1]);  // Duty cycle    buf[1] = atoi(argv[2]);  // Period    // Enter the loop    while (1) {        // Increment duty cycle        for (i = 0; i <= buf[0]; i++) {            buf[1] = i;            write(fd, buf, sizeof(buf));            usleep(30000);  // Delay 30 milliseconds        }        // Decrement duty cycle        for (i = buf[0]; i > 0; i--) {            buf[1] = i;            write(fd, buf, sizeof(buf));            usleep(30000);  // Delay 30 milliseconds        }    }    // Close the device    close(fd);    return 0;}

PWM input capture driver

Rockchip only implemented the status acquisition and application functions, but not the input capture related functions. Since input capture is also a common PWM feature, to implement PWM input capture on the RK3568, we need to implement the PWM input capture driver ourselves.

Device tree modification

In the RK3568 PWM hardware introduction, it is mentioned that only four PWM channels, PWM3, PWM7, PWM11, and PWM15, can perform input capture. We will use PWM3 for the input capture demonstration. By default, PWM3 is used for infrared reception. The specific hardware schematic is as follows:

IR Receiver
IR Receiver

Modify the device tree, comment out the pwm3 node, and then add:

123456
&pwm3 {	status = "okay";	compatible = "pwm-capture";	pinctrl-names = "default";	pwm-channel = <3>;};

Driver

rockchip_pwm_capture.h

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
/* SPDX-License-Identifier: GPL-2.0 */#ifndef __RKXX_PWM_REMOTECTL_H__#define __RKXX_PWM_REMOTECTL_H__#include <linux/input.h>#include <linux/pwm.h>#include <linux/uaccess.h>#include <linux/io.h>#include <linux/slab.h>#include <linux/fs.h>#include <linux/kdev_t.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/platform_device.h>/* Maximum number of keys */#define MAX_NUM_KEYS                60/* Maximum PWM capture count */#define PWM_PWR_KEY_CAPURURE_MAX    10/* PWM Register Definitions */#define PWM_REG_CNTR                0x00  /* Counter register */#define PWM_REG_HPR                 0x04  /* Period register */#define PWM_REG_LPR                 0x08  /* Duty cycle register */#define PWM_REG_CTRL                0x0c  /* Control register */#define PWM3_REG_INTSTS             0x10  /* PWM3 interrupt status register */#define PWM2_REG_INTSTS             0x20  /* PWM2 interrupt status register */#define PWM1_REG_INTSTS             0x30  /* PWM1 interrupt status register */#define PWM0_REG_INTSTS             0x40  /* PWM0 interrupt status register */#define PWM3_REG_INT_EN             0x14  /* PWM3 interrupt enable register */#define PWM2_REG_INT_EN             0x24  /* PWM2 interrupt enable register */#define PWM1_REG_INT_EN             0x34  /* PWM1 interrupt enable register */#define PWM0_REG_INT_EN             0x44  /* PWM0 interrupt enable register *//* Control Register Bit Definitions */#define PWM_ENABLE                  (1 << 0) /* PWM enable */#define PWM_DISABLE                 (0 << 0) /* PWM disable *//* Operating Modes */#define PWM_MODE_ONESHOT            (0x00 << 1) /* One-shot mode */#define PWM_MODE_CONTINUOUS         (0x01 << 1) /* Continuous mode */#define PWM_MODE_CAPTURE            (0x02 << 1) /* Capture mode *//* Duty Cycle Output Polarity */#define PWM_DUTY_POSITIVE           (0x01 << 3) /* Positive polarity */#define PWM_DUTY_NEGATIVE           (0x00 << 3) /* Negative polarity *//* Inactive State Output Polarity */#define PWM_INACTIVE_POSITIVE       (0x01 << 4) /* Positive polarity */#define PWM_INACTIVE_NEGATIVE       (0x00 << 4) /* Negative polarity *//* Clock source selection */#define PWM_CLK_SCALE               (1 << 9) /* Clock division */#define PWM_CLK_NON_SCALE           (0 << 9) /* No clock division */#define PWM_CH0_INT                 (1 << 0)#define PWM_CH1_INT                 (1 << 1)#define PWM_CH2_INT                 (1 << 2)#define PWM_CH3_INT                 (1 << 3)#define PWM_PWR_KEY_INT             (1 << 7)#define PWM_CH0_POL                 (1 << 8)#define PWM_CH1_POL                 (1 << 9)#define PWM_CH2_POL                 (1 << 10)#define PWM_CH3_POL                 (1 << 11)#define PWM_CH0_INT_ENABLE          (1 << 0)#define PWM_CH0_INT_DISABLE         (0 << 0)#define PWM_CH1_INT_ENABLE          (1 << 1)#define PWM_CH1_INT_DISABLE         (0 << 1)#define PWM_CH2_INT_ENABLE          (1 << 2)#define PWM_CH2_INT_DISABLE         (0 << 2)#define PWM_CH3_INT_ENABLE          (1 << 3)#define PWM_CH3_INT_DISABLE         (0 << 3)#define PWM_INT_ENABLE              1#define PWM_INT_DISABLE             0/* Prescaler factor */#define PWMCR_MIN_PRESCALE          0x00#define PWMCR_MAX_PRESCALE          0x07#define PWMDCR_MIN_DUTY             0x0001#define PWMDCR_MAX_DUTY             0xFFFF#define PWMPCR_MIN_PERIOD           0x0001#define PWMPCR_MAX_PERIOD           0xFFFFenum pwm_div {    PWM_DIV1    = (0x0 << 12),    PWM_DIV2    = (0x1 << 12),    PWM_DIV4    = (0x2 << 12),    PWM_DIV8    = (0x3 << 12),    PWM_DIV16   = (0x4 << 12),    PWM_DIV32   = (0x5 << 12),    PWM_DIV64   = (0x6 << 12),    PWM_DIV128  = (0x7 << 12),};/* NEC protocol */#define RK_PWM_TIME_PRE_MIN         4000#define RK_PWM_TIME_PRE_MAX         5000#define RK_PWM_TIME_PRE_MIN_LOW     8000#define RK_PWM_TIME_PRE_MAX_LOW     10000#define RK_PWM_TIME_BIT0_MIN        390#define RK_PWM_TIME_BIT0_MAX        730#define RK_PWM_TIME_BIT1_MIN        1300#define RK_PWM_TIME_BIT1_MAX        2000#define RK_PWM_TIME_BIT_MIN_LOW     390#define RK_PWM_TIME_BIT_MAX_LOW     730#define RK_PWM_TIME_RPT_MIN         2000#define RK_PWM_TIME_RPT_MAX         2500#define RK_PWM_TIME_SEQ1_MIN        95000#define RK_PWM_TIME_SEQ1_MAX        98000#define RK_PWM_TIME_SEQ2_MIN        30000#define RK_PWM_TIME_SEQ2_MAX        55000#define PWM_REG_INTSTS(n)           ((3 - (n)) * 0x10 + 0x10)#define PWM_REG_INT_EN(n)           ((3 - (n)) * 0x10 + 0x14)#define RK_PWM_VERSION_ID(n)        ((3 - (n)) * 0x10 + 0x2c)#define PWM_REG_PWRMATCH_CTRL(n)    ((3 - (n)) * 0x10 + 0x50)#define PWM_REG_PWRMATCH_LPRE(n)    ((3 - (n)) * 0x10 + 0x54)#define PWM_REG_PWRMATCH_HPRE(n)    ((3 - (n)) * 0x10 + 0x58)#define PWM_REG_PWRMATCH_LD(n)      ((3 - (n)) * 0x10 + 0x5C)#define PWM_REG_PWRMATCH_HD_ZERO(n) ((3 - (n)) * 0x10 + 0x60)#define PWM_REG_PWRMATCH_HD_ONE(n)  ((3 - (n)) * 0x10 + 0x64)#define PWM_PWRMATCH_VALUE(n)       ((3 - (n)) * 0x10 + 0x68)#define PWM_PWRCAPTURE_VALUE(n)     ((3 - (n)) * 0x10 + 0x9c)#define PWM_CH_INT(n)               BIT(n)#define PWM_CH_POL(n)               BIT(n + 8)#define PWM_CH_INT_ENABLE(n)        BIT(n)#define PWM_PWR_INT_ENABLE          BIT(7)#define CH3_PWRKEY_ENABLE           BIT(3)/* PWM data structure */struct pwm_data {    int period_ns; /* Period (nanoseconds) */    int duty_ns;   /* Duty cycle (nanoseconds) */};/* PWM state enumeration */typedef enum _RMC_STATE {    RMC_IDLE,   /* Idle state */    RMC_IDLE1,  /* Idle state 1 */    RMC_IDLE2,  /* Idle state 2 */    RMC_GETDATA,/* Data acquisition state */    RMC_DONE,   /* Completion state */} eRMC_STATE;/* PWM capture platform data structure */struct RKxx_remotectl_platform_data {    int nbuttons; /* Button count */    int rep;      /* Repeat */    int timer;    /* Timer */    int wakeup;   /* Wakeup */};/* PWM capture character device data structure */struct pwm_capture_cdev {    dev_t dev_num;            /* Device ID */    struct cdev cdev_test;    /* Character device structure */    struct class *class;      /* Device class */    struct device *device;    /* Device structure */    struct rkxx_capture_drvdata *ddata; /* Driver data */};/* PWM capture driver data structure */struct rkxx_capture_drvdata {    void __iomem *base;       /* base address */    int irq;                  /* Interrupt number */    struct device dev;        /* Device structure */    int pwm_freq_nstime;      /* PWM frequency (nanoseconds) */    int pwm_channel;          /* PWM channel */    int hpr;                  /* High level period */    int lpr;                  /* Low level period */    eRMC_STATE state;         /* PWM status */    struct clk *clk;          /* Clock */    struct clk *p_clk;        /* Parent clock */    struct pwm_capture_cdev pwm_cdev; /* PWM capture character device */    struct pwm_data data;             /* PWM data structure */};/* PWM interrupt control */static void rk_pwm_int_ctrl(void __iomem *pwm_base, uint pwm_id, int ctrl){    int val;    if (pwm_id > 3)        return; /* If PWM ID exceeds 3, return directly */    val = readl_relaxed(pwm_base + PWM_REG_INT_EN(pwm_id)); /* Read current interrupt enable status */    if (ctrl) {        val |= PWM_CH_INT_ENABLE(pwm_id); /* Set interrupt enable */        writel_relaxed(val, pwm_base + PWM_REG_INT_EN(pwm_id)); /* Write interrupt enable register */    } else {        val &= ~PWM_CH_INT_ENABLE(pwm_id); /* Clear interrupt enable */        writel_relaxed(val, pwm_base + PWM_REG_INT_EN(pwm_id)); /* Write interrupt enable register */    }}/* Initialize PWM capture */static void rk_pwm_capture_init(void __iomem *pwm_base, uint pwm_id){    int val;    /* Disable PWM */    val = readl_relaxed(pwm_base + PWM_REG_CTRL);    val = (val & 0xFFFFFFFE) | PWM_DISABLE;    writel_relaxed(val, pwm_base + PWM_REG_CTRL);    /* Set to capture mode */    val = readl_relaxed(pwm_base + PWM_REG_CTRL);    val = (val & 0xFFFFFFF9) | PWM_MODE_CAPTURE;    writel_relaxed(val, pwm_base + PWM_REG_CTRL);    /* Set divider value */    val = readl_relaxed(pwm_base + PWM_REG_CTRL);    val = (val & 0xFF0001FF) | PWM_DIV64;    writel_relaxed(val, pwm_base + PWM_REG_CTRL);    /* Enable interrupt */    rk_pwm_int_ctrl(pwm_base, pwm_id, PWM_INT_ENABLE);    /* Here you can enable PWM capture (commented-out code) */    /*    val = readl_relaxed(pwm_base + PWM_REG_CTRL);    val = (val & 0xFFFFFFFE) | PWM_ENABLE;    writel_relaxed(val, pwm_base + PWM_REG_CTRL);    */}#endif

pwm-rockchip-capture.c

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
#include <linux/module.h>#include <linux/init.h>#include <linux/moduleparam.h>#include <linux/fs.h>#include <linux/kdev_t.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/pwm.h>#include <linux/uaccess.h>#include <linux/clk.h>#include <linux/irq.h>#include <linux/interrupt.h>#include <linux/delay.h>#include "rockchip_pwm_capture.h"// Callback function for opening the character devicestatic int cdev_test_open(struct inode *inode, struct file *file) {    struct rkxx_capture_drvdata *ddata;    struct pwm_capture_cdev *pcdev;    printk("This is cdev_test_open\n");    // Get device data from inode    pcdev = container_of(inode->i_cdev, struct pwm_capture_cdev, cdev_test);    ddata = container_of(pcdev, struct rkxx_capture_drvdata, pwm_cdev);    if (!ddata) {        printk(KERN_ERR "Failed to get device data\n");        return -ENODEV;    }    // Save device data to file private data    file->private_data = ddata;    return 0;}// Callback function for reading the character devicestatic ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off) {    struct rkxx_capture_drvdata *ddata;    int val, i, ret;    ddata = file->private_data;    if (!ddata) {        printk(KERN_ERR "Device data is NULL\n");        return -EINVAL;    }    // Initialize capture data    ddata->lpr = 0;    ddata->hpr = 0;    ddata->state = RMC_IDLE1;    // Enable PWM    val = readl_relaxed(ddata->base + PWM_REG_CTRL);    val = (val & 0xFFFFFFFE) | PWM_ENABLE;    writel_relaxed(val, ddata->base + PWM_REG_CTRL);    // Wait for data capture completion    for (i = 0; i < 100; i++) {        msleep(1);        if (ddata->state == RMC_DONE && ddata->hpr && ddata->lpr) {            printk("capture ok!\n");            break;        }    }    // Disable PWM    val = readl_relaxed(ddata->base + PWM_REG_CTRL);    val = (val & 0xFFFFFFFE) | PWM_DISABLE;    writel_relaxed(val, ddata->base + PWM_REG_CTRL);    if (ddata->hpr == 0 || ddata->lpr == 0) {        printk(KERN_ERR "Failed to capture PWM data\n");        return -EIO;    }    // Calculate period and duty cycle    ddata->data.period_ns = (ddata->lpr + ddata->hpr) * ddata->pwm_freq_nstime;    ddata->data.duty_ns = ddata->hpr * ddata->pwm_freq_nstime;    // Copy data to user space    ret = copy_to_user(buf, &ddata->data, size);    if (ret) {        printk(KERN_ERR "Failed to copy data to user space\n");        return -EFAULT;    }    printk("This is cdev_test_read\n");    ddata->state = RMC_IDLE;    return size;}// Character device release callback functionstatic int cdev_test_release(struct inode *inode, struct file *file) {    printk("This is cdev_test_release\n");    return 0;}// Character device operation function structurestatic struct file_operations cdev_test_ops = {    .owner = THIS_MODULE,    .open = cdev_test_open,    .read = cdev_test_read,    .release = cdev_test_release};// PWM capture interrupt handler functionirqreturn_t rk_pwm_capture(int irq, void *dev_id) {    struct rkxx_capture_drvdata *ddata = dev_id;    unsigned int channel = ddata->pwm_channel;    int val, lpr, hpr;    val = readl_relaxed(ddata->base + PWM_REG_INTSTS(channel));    if ((val & PWM_CH_INT(channel)) == 0) {        return IRQ_NONE;    }    // Read lpr or hpr according to polarity    if ((val & PWM_CH_POL(channel)) == 0) {        if (ddata->state != RMC_DONE) {            lpr = readl_relaxed(ddata->base + PWM_REG_LPR);            ddata->lpr = lpr;        }    } else {        if (ddata->state != RMC_DONE) {            hpr = readl_relaxed(ddata->base + PWM_REG_HPR);            ddata->hpr = hpr;        }    }    // Clear interrupt status    writel_relaxed(PWM_CH_INT(channel), ddata->base + PWM_REG_INTSTS(channel));    // State machine processing    switch (ddata->state) {    case RMC_IDLE1:        ddata->hpr = 0;        ddata->lpr = 0;        ddata->state = RMC_IDLE2;        break;    case RMC_IDLE2:        ddata->hpr = 0;        ddata->lpr = 0;        ddata->state = RMC_GETDATA;        break;    case RMC_GETDATA:        printk("ddata->hpr is %d, ddata->lpr is %d\n", ddata->hpr, ddata->lpr);        if (ddata->hpr && ddata->lpr) {            ddata->state = RMC_DONE;        }        break;    default:        break;    }    return IRQ_HANDLED;}// Driver probe functionint capture_probe(struct platform_device *pdev){    int ret;    struct rkxx_capture_drvdata *ddata;    struct resource *r;    struct clk *clk;    struct clk *p_clk;    struct device_node *np = pdev->dev.of_node;    int pwm_channel;    int irq;    struct pwm_capture_cdev *pcdev;    int freq;    // Allocate driver data structure    ddata = devm_kzalloc(&pdev->dev, sizeof(struct rkxx_capture_drvdata), GFP_KERNEL);    if (!ddata) {        dev_err(&pdev->dev, "Failed to allocate memory for driver data\n");        return -ENOMEM;    }    ddata->state = RMC_IDLE;    // Get resources    r = platform_get_resource(pdev, IORESOURCE_MEM, 0);    ddata->base = devm_ioremap_resource(&pdev->dev, r);    if (IS_ERR(ddata->base)) {        dev_err(&pdev->dev, "Failed to map memory resource\n");        return PTR_ERR(ddata->base);    }    ddata->dev = pdev->dev;    // Get clock    clk = devm_clk_get(&pdev->dev, "pwm");    if (IS_ERR(clk)) {        dev_err(&pdev->dev, "Failed to get PWM clock\n");        return PTR_ERR(clk);    }    ddata->clk = clk;    p_clk = devm_clk_get(&pdev->dev, "pclk");    if (IS_ERR(p_clk)) {        dev_err(&pdev->dev, "Failed to get peripheral clock\n");        return PTR_ERR(p_clk);    }    ddata->p_clk = p_clk;    // Read PWM channel from device tree    ret = of_property_read_u32(np, "pwm-channel", &pwm_channel);    if (ret) {        dev_err(&pdev->dev, "Failed to get PWM channel from device tree\n");        return ret;    }    pwm_channel %= 4;    ddata->pwm_channel = pwm_channel;    // Get interrupt number    irq = platform_get_irq(pdev, 0);    if (irq < 0) {        dev_err(&pdev->dev, "Failed to get IRQ\n");        return irq;    }    ddata->irq = irq;    // Set driver data    platform_set_drvdata(pdev, ddata);    // Request interrupt    ret = devm_request_irq(&pdev->dev, irq, rk_pwm_capture, IRQF_NO_SUSPEND, "rk_pwm_capture_irq", ddata);    if (ret) {        dev_err(&pdev->dev, "Failed to request IRQ\n");        return ret;    }    // Enable clock    ret = clk_prepare_enable(ddata->clk);    if (ret) {        dev_err(&pdev->dev, "Failed to enable PWM clock\n");        return ret;    }    ret = clk_prepare_enable(ddata->p_clk);    if (ret) {        clk_disable_unprepare(ddata->clk);        dev_err(&pdev->dev, "Failed to enable peripheral clock\n");        return ret;    }    // Calculate PWM frequency    freq = clk_get_rate(ddata->clk) / 64;    ddata->pwm_freq_nstime = 1000000000 / freq;    // Register character device    pcdev = &ddata->pwm_cdev;    ret = alloc_chrdev_region(&pcdev->dev_num, 0, 1, "alloc_name");    if (ret < 0) {        dev_err(&pdev->dev, "alloc_chrdev_region error\n");        goto err_alloc_chrdev;    }    printk("alloc_chrdev_region success\n");    pcdev->cdev_test.owner = THIS_MODULE;    cdev_init(&pcdev->cdev_test, &cdev_test_ops);    ret = cdev_add(&pcdev->cdev_test, pcdev->dev_num, 1);    if (ret) {        dev_err(&pdev->dev, "Failed to add cdev\n");        goto err_cdev_add;    }    // Create device class    pcdev->class = class_create(THIS_MODULE, "test");    if (IS_ERR(pcdev->class)) {        ret = PTR_ERR(pcdev->class);        dev_err(&pdev->dev, "Failed to create class\n");        goto err_class_create;    }    // Create device    pcdev->device = device_create(pcdev->class, NULL, pcdev->dev_num, NULL, "capture");    if (IS_ERR(pcdev->device)) {        ret = PTR_ERR(pcdev->device);        dev_err(&pdev->dev, "Failed to create device\n");        goto err_device_create;    }    rk_pwm_capture_init(ddata->base, ddata->pwm_channel);    return 0;err_device_create:    class_destroy(pcdev->class);err_class_create:    cdev_del(&pcdev->cdev_test);err_cdev_add:    unregister_chrdev_region(pcdev->dev_num, 1);err_alloc_chrdev:    clk_disable_unprepare(ddata->p_clk);    clk_disable_unprepare(ddata->clk);    return ret;}// Driver remove functionint capture_remove(struct platform_device *pdev){    struct rkxx_capture_drvdata *ddata = platform_get_drvdata(pdev);    struct pwm_capture_cdev *pcdev = &ddata->pwm_cdev;    device_destroy(pcdev->class, pcdev->dev_num);    class_destroy(pcdev->class);    cdev_del(&pcdev->cdev_test);    unregister_chrdev_region(pcdev->dev_num, 1);    clk_disable_unprepare(ddata->p_clk);    clk_disable_unprepare(ddata->clk);    return 0;}// Device tree match tableconst struct of_device_id capture_of_device_id[] = {    {.compatible = "pwm-capture"},    {}};// Platform driver structurestruct platform_driver capture_platform_driver = {    .driver = {        .name = "pwm-capture",        .of_match_table = capture_of_device_id,    },    .probe = capture_probe,    .remove = capture_remove,};// Module initialization functionstatic int __init modulecdev_init(void){    return platform_driver_register(&capture_platform_driver);}// Module exit functionstatic void __exit modulecdev_exit(void){    platform_driver_unregister(&capture_platform_driver);}module_init(modulecdev_init);module_exit(modulecdev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("topeet");

Test code:

12345678910111213141516171819202122232425262728293031323334353637383940414243
#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>#include <string.h>// Use fixed-width types to avoid ABI issues.struct __attribute__((packed)) pwm_data {    int32_t period_ns;    int32_t duty_ns;};int main(int argc, char *argv[]) {    const char *dev_path = "/dev/capture";    int fd = open(dev_path, O_RDONLY);    if (fd < 0) {        fprintf(stderr, "无法打开设备 %s: %s\n", dev_path, strerror(errno));        return EXIT_FAILURE;    }    struct pwm_data data;    ssize_t n = read(fd, &data, sizeof(data));    if (n < 0) {        fprintf(stderr, "读取设备失败: %s\n", strerror(errno));        close(fd);        return EXIT_FAILURE;    }    if (n != sizeof(data)) {        fprintf(stderr, "读取数据不完整: 期望 %zu 字节,实际 %zd 字节\n",                 sizeof(data), n);        close(fd);        return EXIT_FAILURE;    }    printf("period_ns = %d ns, duty_ns = %d ns\n", data.period_ns, data.duty_ns);    printf("占空比 = %.2f%%\n",            (data.period_ns > 0) ? (100.0 * data.duty_ns / data.period_ns) : 0.0);    close(fd);    return EXIT_SUCCESS;}

We need another PWM as the input signal, connect it to PWM3, and let it capture.

Loading comments…