This article introduces the basic concepts of Linux PWM (Pulse Width Modulation), including period, duty cycle, and the area equivalence principle, and discusses in detail the characteristics, advantages and disadvantages, and pin multiplexing of the hardware PWM interface on the iTOP-RK3568 platform.
PWMFull name isPulse Width Modulation, translated 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. The PWM signal can be described by two units: period and duty cycle. The specific descriptions of the two units are as follows:
Period (T): Refers to the time required for a complete high and low level cycle, while frequency is the reciprocal of the period, indicating how many cycles occur in 1 second, with the unit being Hz. For example, if a 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
Area Equivalence Principle
The area equivalence principle states that even if the width and amplitude of pulse signals differ, as long as their area (pulse width × pulse amplitude) is the same,the response of the inertial elementis also essentially the same, as shown in the figure below
Area Equivalence Principle
For example: The RK3568 IO port has a high level of 3.3V and a low level of 0V. If we want to output different analog voltages, we can use the PWM area equivalence principle technology 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: controlling motor speed and torque.
Lighting control: controlling 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, allowing flexible response to interrupt events.
The PWM channels use a programmable prescaler, which can first divide the bus clock and then perform a secondary division.
The PWM channel has a built-in 32-bit timer/counter function.
The PWM channel supports capture mode, which can capture information such as the pulse width of external input signals.
The PWM channel supports continuous output mode and one-shot output mode.
The PWM channel provides a reference mode, capable of outputting 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 PWMs, meaning PWM output is implemented by a dedicated PWM hardware module. The introduction to hardware PWM is as follows:
Hardware PWM:
Implementation method: Hardware PWM is a method where PWM output is implemented by a dedicated PWM hardware module.
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.
Output waveform is stable and reliable, not easily affected by CPU load.
Disadvantages:
Requires a dedicated hardware PWM module, resulting in higher cost.
PWM output pins are limited and can only output on predefined pins.
The iTOP-RK3568 hardware PWM pin multiplexing function table is shown below. PWM can be selected according to this table:
PWM Interface
pinctrl function
Net Label
Corresponding GPIO
Function
PWM0
PWM0_M0
Working_LEDEN_H_GPIO0_B7
GPIO0_B7
LED Control Pin
PWM0_M1
LCD0_PWREN_H_GPIO0_C7
GPIO0_C7
LVDS Screen Power Enable Pin
PWM1
PWM1_M0
VGA_HPDIN_GPIO0_C0
GPIO0_C0
VGA Hot Plug Detection Pin
PWM1_M1
PCIE20_WAKEN_M0
GPIO0_B5
PCIE2.0 Wake-up
PWM2
PWM2_M0
PCIE20_PERSTn_GPIO0_C1
GPIO0_C1
PCIE2.0 Reset Pin
PWM2_M1
TP_RST_L_GPIO0_B6
GPIO0_B6
MIPI Touch Screen Reset Pin
PWM3_IR
PWM3_IR
PWM3_IR
GPIO0_C2
Infrared Receiver
PWM4
PWM4
LCD0_BL_PWM4
GPIO0_C3
LVDS Screen Backlight
PWM5
PWM5
LCD1_BL_PWM5
GPIO0_C4
MIPI Screen Backlight
PWM6
PWM6
PWM_FAN
GPIO0_C5
Cooling fan speed adjustment
PWM7_IR
PWM7_IR
RS485_DIR_GPIO0_C6
GPIO0_C6
485 transceiver control pin
PWM8
PWM8_M0
UART4_RX_M1
GPIO3_B1
Serial port 4 receive pin
PWM8_M1
SDMWC0_D0
GPIO1_D5
SD card data line
PWM9
PWM9_M0
UART4_TX_M1
GPIO3_B2
Serial port 4 transmit pin
PWM9_M1
SDMWC0_D1
GPIO1_D5
SD card data line
PWM10
PWM10_M0
GPIO3_B5
GPIO3_B5
Not used
PWM10_M1
SDMWC0_CMD
GPIO2_A1
SD card CMD pin
PWM11
PWM11_IR_M0
GPIO3_B6
GPIO3_B6
Not used
PWM11_IR_M1
CIF_CLKOUT
GPIO4_C0
Not used
PWM12
PWM12_M0
GMAC0_RSTn_GPIO3_B7
GPIO3_B7
Network card 0 reset pin
PWM12_M1
GPIO4_C5
GPIO4_C5
Serial port 9 transmit pin
PWM13
PWM13_M0
GMAC0_INT/PMEB_GPIO3_C0
GPIO3_C0
Network card 0 interrupt pin
PWM13_M1
GPIO4_C6
GPIO4_C6
Serial port 9 receive pin
PWM14
PWM14_M0
UART7_TX_M1
GPIO3_C4
Serial port 7 transmit pin
PWM14_M1
4G_DISABLE_GPIO4_C2
GPIO4_C2
CAN1 RX
PWM15
PWM15_IR_M0
UART7_RX_M1
GPIO3_C5
UART 7 receive pin
PWM15_IR_M1
HDMI_RX_INT_L_GPIO4_C3
GPIO4_C3
CAN1 TX
When hardware PWM is insufficient, GPIO can be used to simulate PWM, also known as software PWM. The introduction to software PWM is as follows:
Software PWM:
Implementation method: Software PWM is a method of achieving PWM output through software programming. By using timer interrupts or loop counting, it controls the switching time of high and low levels on the output pin in software, thereby generating PWM waveforms.
Advantages:
High flexibility, can generate PWM waveforms on any GPIO pin.
Low cost, no additional hardware PWM module required.
Disadvantages:
High CPU usage, as it requires real-time control of pin levels in interrupt service routines or loops.
The frequency and resolution of PWM output are affected by CPU clock speed and interrupt response time, making high frequency and high resolution unattainable.
High demands on 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 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 its associated PWM controller.
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, and controlling the device node, applications can interact with the PWM device
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 Throughstruct pwm_deviceuniformly describe PWM device information, such as device name and associated controller. Provide a standardized PWM device access API for upper-layer applications.
PWM Parameter Configuration Providepwm_configinterface for configuring PWM device working parameters, such as period, duty cycle, polarity, etc. Convert upper-layer configuration parameters into a form understandable by the underlying hardware and set them through the adapter driver.
PWM Output Control Providepwm_enableandpwm_disableinterfaces for controlling the start and stop 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: Controls 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 PWM core layer. It is responsible for abstracting and encapsulating the underlying PWM hardware, adapting to different types of PWM devices, providing standardized hardware control interfaces to the upper layer, and also handling hardware-related tasks such as initialization, state management, and power management.
Hardware Abstraction
The PWM adapter driver layer is responsible for abstracting and encapsulating the underlying PWM hardware devices. It defines a set of standardizedpwm_chipinterfaces, including functions such as device initialization, parameter configuration, and output control.
The upper PWM core layer can indirectly control the underlying PWM hardware by calling these standard interfaces, without needing to focus on specific hardware implementation details.
Hardware Adaptation
The PWM adapter driver needs to implement the functions defined in the pwm_chip interface to adapt to the specific working methods of the underlying PWM hardware. These adaptation functions convert the standardized requests from the upper 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 adapter driver is responsible for completing the initialization of PWM hardware devices, including the configuration and initialization of related resources such as GPIO, clocks, and power.
When registering a PWM device, the adapter driver performs initialization operations to ensure 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 PWM core layer to ensure the core layer can correctly control and manage the PWM device.
This concludes the explanation of the PWM subsystem framework. In the next chapter, we will discuss how to operate PWM using 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 pin as a PWM function. The selected IO pin is pin 6 of the iTOP-RK3568 backplane 20-pin GPIO interface, as shown in the following figure:
Hardware Connection
According to the PWM pin multiplexing table, the PWM12_M1 function pin is multiplexed as UART 9 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.
First, navigate to the/sys/class/pwm/directory, as shown in the following figure:
/sys/class/pwm/
The correspondence is as follows:
pwm
PWM12_M1 corresponds to pwmchip3. After entering the pwmchip3 directory, it is as shown in the following figure:
pwmchip3
In this directory, there are a total of seven files, which aredevice、export、npwm、power、subsystem、ueventandunexport. The three attribute files that need attention are export, npwm, and unexport.
npwm: It is a read-only attribute. Reading this file reveals how many PWM outputs are available under this PWM controller, as shown below:
npwm
export: Before using PWM, export it through the export attribute, as shown below:
export
unexport: After using PWM, the exported PWM needs to be deleted, for example:
unexport
Note that the export and unexport files are write-only and have no read permission.
Use the following command again to export the pwm0 directory. After successful export, enter the folder as shown in the figure below:
/sys/class/pwm/pwmchip3/pwm0
polarity: Used to view PWM polarity, with normal and inversed options
normal indicates that the PWM signal is positive polarity, meaning high level is the active signal
inversed indicates that the PWM signal is negative polarity, meaning 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 Duration
Rotation Angle
0.5 ms
0°
1.0 ms
45°
1.5 ms
90°
2.0 ms
135°
2.5 ms
180°
Hardware Connection
Cable Color
Function
Backplane 20-pin Pin Number
Net Label
Red Wire
Power Positive (5V)
10/12
VCC5V0_SYS
Brown Wire
Power Negative (GND)
19/20
GND
Orange Wire
Control Signal Line (PWM Signal)
6
UART9_TX_M1(GPIO4_C5)
Application Scenarios
Robots: Used for joints or other movable parts in robots, as its precise angle control makes it suitable for achieving 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:
1 2 3 4 5 6 7 8
# Set the PWM period to 20 ms echo 20000000 > period # Set the PWM high-level duration to 2 ms echo 2000000 > duty_cycle # Set the PWM polarity to normal echo normal > polarity # Enable PWM output echo 1 > enable
PWM Controller Registration Process Analysis
In the rk3568.dtsi device tree, the PWM-related nodes are as follows:
Searching 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
staticintrockchip_pwm_probe(struct platform_device *pdev) { conststructof_device_id *id;// Device Tree Match ID structrockchip_pwm_chip *pc;// PWM Chip Structure structresource *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 Resource and Map IO Address 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"); }
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 Pin Controller Active State 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 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; }
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. The details are as follows:
struct rockchip_pwm_chip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
structrockchip_pwm_chip { structpwm_chipchip;// Generic PWM controller chip interface structclk *clk;// Main clock of the PWM controller structclk *pclk;// Peripheral clock of the PWM controller structpinctrl *pinctrl;// Pin multiplexing controller of the PWM controller structpinctrl_state *active_state;// Current pin state used by the PWM controller conststructrockchip_pwm_data *data;// PWM controller configuration information specific to Rockchip SoC void __iomem *base;// Kernel virtual address of the PWM controller register space unsignedlong 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 single-shot trigger mode int channel_id; int irq; };
The second line of whichstruct pwm_chipstructure 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 PWM framework. This structure is defined in theinclude/linux/pwm.hfile, and its specific content is as follows:
/** * 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 */ structpwm_chip { structdevice *dev;// Device to which the PWM controller belongs conststructpwm_ops *ops;// Operation function set of the PWM controller int base;// Register base address offset of the PWM controller unsignedint npwm;// Number of PWM channels provided by the PWM controller
structpwm_device * (*of_xlate)(structpwm_chip *pc, conststructof_phandle_args *args); unsignedint of_pwm_n_cells;// Number of attributes required for PWM property description in the device tree
/* only used internally by the PWM framework */ structlist_headlist;// Global linked list where the PWM controller resides structpwm_device *pwms;// Array of PWM channel devices };
Among thempwm_opsrepresents the operation function set of the PWM controller
/** * 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 */ structpwm_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, unsignedlong timeout); int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm, conststruct 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); structmodule *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.cfound in the filerockchip_pwm_opsoperation set, it can be seen that Rockchip only implements two functions: status acquisition and request, as shown below
rockchip_pwm_probe()called inpwmchip_addfunction adds the PWM controller to the PWM subsystem,pwmchip_addfunction defined indrivers/pwm/core.cfile, as shown below:
/** * 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. */ intpwmchip_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. */ intpwmchip_add_with_polarity(struct pwm_chip *chip, enum pwm_polarity polarity) { structpwm_device *pwm; unsignedint 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 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 chip->base = ret;
// initialize each PWM device for (i = 0; i < chip->npwm; i++) { pwm = &chip->pwms[i];
The main function of this function is to initialize a new PWM controller and calllist_addfunction to add it to the management of the PWM subsystem.
PWM Subsystem API
pwm_config()
PWM Parameter Configuration Core Function
Item
Details
Function Prototype
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
Core Function
Configure the PWM signal’sduty cycleandperiod, is the core function for adjusting PWM output characteristics
Parameter Description
1.pwm: Pointer to the target PWM device structure (must first be obtained viadevm_of_pwm_getto obtain) 2.duty_ns: Duty cycle duration, unitnanosecond (ns), indicating the duration of the high level within one PWM cycle 3.period_ns: Total period of the PWM signal, unitnanosecond (ns), indicating the total duration of high level + low level
Must be called after PWM enable (pwm_enable) before calling, 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_ns / period_ns) * 100%
pwm_set_polarity()
PWM Polarity Setting Function
Item
Details
Function Prototype
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);
Core Function
Set the polarity of the PWM signal, defining the logical meaning of high and low levels
Parameter Description
1.pwm: Pointer to the target PWM device structure 2.polarity: Polarity enumeration value, only two types 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
Suitable for scenarios requiring reverse logic control (e.g., certain motor forward/reverse control, LED reverse drive circuits). It is recommended to callpwm_configafterpwm_enablebefore
pwm_enable()
the PWM enable function
Item
Details
Function Prototype
int pwm_enable(struct pwm_device *pwm);
Core Function
Enable the PWM controller to start outputting the PWM signal with configured parameters (duty cycle, period, polarity)
Parameter Description
pwm: Pointer to the target PWM device structure
Return Value
Success: returns 0; Failure: returns a negative error code
Instructions
must bepwm_config(andpwm_set_polarity, if polarity needs to be configured) to ensure the output signal parameters are valid; after calling, PWM continues to output until callingpwm_disablestop
pwm_disable()
PWM shutdown function
Item
Details
Function Prototype
void pwm_disable(struct pwm_device *pwm);
Core Function
Shuts down the PWM controller and stops outputting PWM signals
Parameter Description
pwm: Pointer to the target PWM device structure
Return Value
No return value
Usage Instructions
Call when PWM output is not needed (e.g., device sleep, function disabled) to save system power; after calling, PWM stops outputting signals, and re-enabling requires calling again.pwm_enable(No need to reconfigure parameters; parameters are retained.)
Obtains the PWM device handle from the Linux Device Tree, automatically managing device resources (no manual release required).
Parameter Description
1.dev: Pointer to the device structure corresponding to the current driver (e.g., in 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, passNULL)
Return Value
Success: Returns a pointer tostruct pwm_devicevalid pointer; on failure: returns an error pointer of type ERR_PTR (can be determined viaIS_ERR()macro)
Usage Instructions
is the preferred method for obtaining a PWM device in device drivers, based on thedevmcharacteristics of the series interface; when the device is unloaded, the kernel automatically releases the PWM device resources to avoid memory leaks; must be called before using other PWM functions (pwm_configetc.) to first obtain the device handle for subsequent operations
Example
First, modify the device tree, similarly set pwm12 multiplexing first:
1 2 3 4
&pwm12 { status = "okay"; pinctrl-0 = <&pwm12m1_pins>; };
// Callback function when opening the device staticintcdev_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 500,000 nanoseconds, period of 20,000,000 nanoseconds pwm_set_polarity(sg90_pwm_device, PWM_POLARITY_NORMAL); // Set PWM polarity to normal polarity pwm_enable(sg90_pwm_device); // Start PWM return0; }
// Callback function when writing to the device staticssize_tcdev_test_write(struct file *file, constchar __user *buf, size_t size, loff_t *off) { int ret; unsignedchar 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: pulse width adjusted based on user input data pwm_config(sg90_pwm_device, 500000 + data[0] * 100000 / 9, 20000000); return size; }
// Callback function when releasing the device staticintcdev_test_release(struct inode *inode, struct file *file) { printk("This is cdev test release\n");
// Define character device operation function set staticstructfile_operationscdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .write = cdev_test_write, .release = cdev_test_release, };
// Device probe function staticintsg90_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); }
// Request 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"); return0; }
// Device removal function staticintsg90_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"); return0; }
// Device tree match table staticconststructof_device_idsg90_of_device_id[] = { {.compatible = "sg90"}, {}, }; MODULE_DEVICE_TABLE(of, sg90_of_device_id);
// Module initialization function staticint __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");
intmain(int argc, char *argv[]) { // argc indicates the number of command-line arguments, including the program name itself // argv is a string array that stores each command-line argument
int fd; // File descriptor, used to identify the opened device file unsignedchar 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 indicates 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 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] = (unsignedchar)atoi(argv[1]); // Write 1 byte of data from buf to the opened device file if (write(fd, buf, 1) != 1) { // Write failed; output 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);
return0; // Program executed successfully }
Test
Use./app 90Run the compiled executable program, where 90 represents the rotation angle of the servo.
Simulated PWM
When hardware PWM is insufficient, GPIO can be used to simulate PWM, also known as 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 high and low levels on the output pin in software, thereby generating a PWM waveform.
High-resolution timer
The implementation of software PWM relies on timer interrupts, and**Traditional periodic timers typically range from 100Hz to 1000Hz, providing only millisecond-level precision.**This is insufficient for applications requiring high time precision (such as simulated PWM).
To address this issue, the Linux kernel introduced the high-resolution timer (hrtimers) mechanism.
hrtimersIt uses dynamic clocks (CLOCK_MONOTONIC) and dynamic timestamps (ktime_t) as the reference time. By leveraging underlying hardware timers (such asHPET、LAPICtimers, etc.), it provides nanosecond-level time precision.
High-resolution timer structurestruct hrtimerThe content is as follows:
1 2 3 4 5 6 7
structhrtimer { structlist_headnode;// Node of the timer in the clock base list ktime_t _softexpires;// Soft expiration time; actual expiration time may be slightly later enumhrtimer_restart(*function)(struct hrtimer *); // Callback function when the timer expires unsignedlong state; // Status flags of the timer structhrtimer_clock_base *base;// Clock base to which the timer belongs }
High-resolution timer workflow
Initialize the timer
Use thehrtimer_init()function to initialize astruct hrtimerobject, specifying the clock source and working mode. During initialization, a clock base (hrtimer_clock_base) is assigned to the timer. Each CPU has its own clock base manager.
Set the timer Use thehrtimer_start()orhrtimer_set()The function sets the expiration time of the timer. An absolute time or a relative time can be specified. 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.
Timer Expiration Handling When a system clock interrupt occurs, thehrtimer_interrupt()function is called.hrtimer_interrupt()It traverses the timer linked list of the clock base to check for expired timers. For expired timers, it calls their callback function (function) to handle the expiration event. If the timer has not expired, it updates the wake-up time of the next expired timer.
Timer Management
hrtimer_cancel()The function is used to cancel a running timer.
hrtimer_forward()The function is used to roll forward the timer, i.e., recalculate the timer’s expiration time based on the current time.
hrtimer_start_range_ns()The function is used to set the expiration time range of the timer to improve timer precision.
Initializationstruct hrtimerStructure, specifying the timer’s clock source and working mode
Parameter Description
-timer: Pointer to thehrtimerstructure pointer to be initialized -clockid: Clock source, common values: -CLOCK_MONOTONIC: Monotonic clock (does not go back after system startup, recommended for timers) -CLOCK_REALTIME: Real-time clock (can be modified, not recommended for high-precision timing) -CLOCK_BOOTTIME: Monotonic clock including system suspend time -mode: Working mode, common values: -HRTIMER_MODE_ABS: Absolute time mode (timer triggers at specified absolute time) -HRTIMER_MODE_REL: Relative time mode (timer triggers after a specified delay from current time)
Return Value
None
Instructions
Must be called before using other hrtimer APIs; it is a prerequisite for timer operations.
hrtimer_set
Set timer expiration time
Item
Details
Function prototype
void hrtimer_set(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
Core function
Sets the expiration time for an initialized timer (usually used withhrtimer_start)
Parameter description
-timer: Pointer tohrtimerstructure pointer -time: Expiration time (ktime_ttype, must usektime_set(秒, 纳秒)Construction) -mode: Time mode (HRTIMER_MODE_ABS/HRTIMER_MODE_REL, must be consistent withhrtimer_initthe pattern of)
Return value
None
Instructions
Can set the time separately before callinghrtimer_start, or directly throughhrtimer_startthe parameters to set the time (more common)
hrtimer_start
Start the timer
Item
Details
Function prototype
int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
Core function
Add the timer to the kernel timer queue and start timing (the expiration time can be set directly)
Parameter Description
-timer: Pointer tohrtimerstructure pointer -time: Expiration time (ktime_ttype) -mode: Time mode (same ashrtimer_init)
Return Value
Returns 0 on success; returns a negative error code on failure
Usage Instructions
is the core function for starting a timer, supporting the one-step completion of “set time + start” (replacinghrtimer_set+hrtimer_start)
Advance the expiration time in the timer callback function to achieveperiodic timer
Parameter Description
-timer: pointer tohrtimerstructure pointer -now: current time (usually obtained withhrtimer_cb_get_time(timer)) -interval: period time interval (ktime_ttype)
Return Value
Returns the new expiration time (ktime_ttype)
Usage Instructions
Must be called in the timer callback function, in conjunction withreturn HRTIMER_RESTARTImplement periodic triggering
hrtimer_cancel
Cancel timer
Project
Details
Function prototype
int hrtimer_cancel(struct hrtimer *timer);
Core function
Cancel the started timer and remove it from the kernel queue
Parameter description
-timer: Pointer tohrtimerstructure pointer
Return value
Returns 1 if the timer is active; returns 0 if it has been stopped
Usage instructions
Used to terminate the timer early (e.g., when unloading a module or disabling a function)
Example
We use simulated PWM to achieve the LED breathing light effect:
LED schematic
The pin used by the LED isGPIO0_B7, which is registered in the device tree using the LED subsystem. In this chapter, since we need to control this pin with simulated PWM, the device tree must be modified:
// Define the PWM LED data structure structpwm_led_data { int sum_count; // Total pulse count of the PWM period int high_count; // Pulse count for PWM high level duration structgpio_desc *gpiod;// GPIO descriptor structhrtimerpwm_timer;// High-resolution timer ktime_t time; // Timer interval };
// Declare a pointer to the PWM LED data structure structpwm_led_data *data;
// If the counter reaches the total number of pulses, set GPIO to high level if (timer_count == mydata->sum_count) { gpiod_set_value(mydata->gpiod, 1); timer_count = 0; } // If the counter reaches the number of high-level duration pulses, set GPIO to low level if (timer_count == mydata->high_count) { gpiod_set_value(mydata->gpiod, 0); } timer_count++; // If the number of high-level duration pulses is 0, reset the counter to 0 if (mydata->high_count == 0) { timer_count = 0; }
// Move the timer forward by the time interval and restart hrtimer_forward(timer, hrtimer_cb_get_time(timer), mydata->time); return HRTIMER_RESTART; }
// Character device open callback function staticintcdev_test_open(struct inode *inode, struct file *file) { printk("This is cdev test open\n"); return0; }
// Character device write callback function staticssize_tcdev_test_write(struct file *file, constchar __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 PWM LED data structure data->sum_count = kbuf[0]; data->high_count = kbuf[1]; return size; }
// Character device release callback function staticintcdev_test_release(struct inode *inode, struct file *file) { printk("This is cdev test release\n"); return0; }
// Character device operation function set staticstructfile_operationscdev_test_ops = { .owner = THIS_MODULE, .open = cdev_test_open, .write = cdev_test_write, .release = cdev_test_release, };
// Platform device probe callback function staticintled_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); }
// Module initialization function staticint __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");
intmain(int argc, char *argv[]) { int fd; // File descriptor int buf[2]; // Buffer, stores two integer values
// Check if 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 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 in buffer buf[0] = atoi(argv[1]); buf[1] = atoi(argv[2]);
// Write buffer data to PWM GPIO device if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { perror("write error"); close(fd); return-1; }
intmain(int argc, char *argv[]) { int fd; int buf[2]; int i;
// Check number of parameters if (argc != 3) { printf("Usage: %s <duty_cycle> <period>\n", argv[0]); return-1; }
// Open /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 infinite 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 device close(fd);
return0; }
PWM input capture driver
Rockchip only implemented two functions: status acquisition and request, but did not implement functions related to input capture. Input capture is also one of the common functions of PWM. To implement PWM input capture on the RK3568, we need to implement the PWM input capture driver ourselves.
Device Tree Modification
The RK3568 PWM hardware introduction mentions that only four PWM channels, PWM3, PWM7, PWM11, and PWM15, support input capture. We will use PWM3 for the input capture demonstration. By default, PWM3 functions as an infrared receiver. The specific hardware schematic is as follows:
IR Receiver
Modify the device tree, comment out the pwm3 node, and then add:
/* PWM data structure */ structpwm_data { int period_ns; /* Period (nanoseconds) */ int duty_ns; /* Duty cycle (nanoseconds) */ };
/* PWM state enumeration */ typedefenum _RMC_STATE { RMC_IDLE, /* Idle state */ RMC_IDLE1, /* Idle state 1 */ RMC_IDLE2, /* Idle state 2 */ RMC_GETDATA,/* Get Data Status */ RMC_DONE, /* Completion Status */ } eRMC_STATE;
/* PWM Capture Platform Data Structure */ structRKxx_remotectl_platform_data { int nbuttons; /* Button Count */ int rep; /* Repeat */ int timer; /* Timer */ int wakeup; /* Wake Up */ };
/* PWM Capture Character Device Data Structure */ structpwm_capture_cdev { dev_t dev_num; /* Device Number */ structcdevcdev_test;/* Character Device Structure */ structclass *class;/* Device Class */ structdevice *device;/* Device Structure */ structrkxx_capture_drvdata *ddata;/* Driver Data */ };
/* PWM Capture Driver Data Structure */ structrkxx_capture_drvdata { void __iomem *base; /* Base Address */ int irq; /* Interrupt Number */ structdevicedev;/* 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 */ structclk *clk;/* Clock */ structclk *p_clk;/* Parent Clock */ structpwm_capture_cdevpwm_cdev;/* PWM Capture Character Device */ structpwm_datadata;/* PWM Data Structure */ };
/* PWM Interrupt Control */ staticvoidrk_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 */ } }
/* PWM Capture Can Be Enabled Here (Commented Out Code) */ /* val = readl_relaxed(pwm_base + PWM_REG_CTRL); val = (val & 0xFFFFFFFE) | PWM_ENABLE; writel_relaxed(val, pwm_base + PWM_REG_CTRL); */ }
// 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 to complete 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; }
// Release callback function of character device staticintcdev_test_release(struct inode *inode, struct file *file) { printk("This is cdev_test_release\n"); return0; }
val = readl_relaxed(ddata->base + PWM_REG_INTSTS(channel)); if ((val & PWM_CH_INT(channel)) == 0) { return IRQ_NONE; }
// Read lpr or hpr based on 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 function intcapture_probe(struct platform_device *pdev) { int ret; structrkxx_capture_drvdata *ddata; structresource *r; structclk *clk; structclk *p_clk; structdevice_node *np = pdev->dev.of_node; int pwm_channel; int irq; structpwm_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; }