Linux pwm驱动笔记

参考视频地址

【北京迅为】嵌入式学习之Linux驱动(第十八期PWM全新升级)基于RK3568开发板哔哩哔哩_bilibili

1.基础知识

pwm基础知识

        PWM(Pulse Width Modulation) 是一种通过调节脉冲信号的 宽度(占空比) 来模拟不同电压或功率输出的技术。其核心是通过 快速切换高低电平 的开关状态,控制平均能量输出,从而实现对设备(如电机、LED等)的精准控制。

        占空比(Duty Cycle):高电平时间占整个周期的百分比,公式:

\text{duty} = \frac{T_{\text{high level}}}{T_{\text{period}}} \times 100\%

        占空比越大,平均电压越高(如占空比50%时,平均电压为峰值的一半)。

        频率(Frequency):每秒内脉冲信号的周期数,单位:Hz。高频减少噪声(如电机驱动),低频简化滤波电路设计。

典型应用
应用场景实现原理案例
LED调光调节占空比控制LED的平均亮度(人眼感知为连续变化)。Arduino的analogWrite()函数输出PWM。
电机调速改变占空比等效调节电机驱动电压,控制转速。无人机电调(ESC)控制无刷电机转速。
电源转换(DC-DC)通过PWM控制开关管通断时间,实现升降压转换(如Buck/Boost电路)。手机充电器中的高效电压调节。
音频合成通过高频PWM滤波生成模拟音频信号(需低通滤波器)。数字功放(Class D放大器)。

ITOP-RK3568开发板pwm接口介绍

16路PWM硬件控制器。

rk3568 PWM特性
  • 16 个片上 PWM(PWM0 ~ PWM15):支持基于中断的操作模式。

  • 可编程预分频总线时钟:可对总线时钟进行预分频,并进一步分频调整。

  • 内置 32 位定时器/计数器:提供高精度计时功能。

  • 支持捕获模式:用于捕捉外部信号的时间或频率。

  • 支持连续模式或单次模式

    • 连续模式:持续生成 PWM 信号。

    • 单次模式:单次触发后停止。

  • 提供参考模式:可输出多种占空比的波形。

  • 针对红外应用的优化:PWM3、PWM7、PWM11 和 PWM15 专门优化,适用于红外信号控制场景。

迅为rk3568开发板,PWM使用情况

ITOP-RK3588 开发板pwm接口介绍

PWM

  • 支持16个片上PWM模块(PWM0~PWM15):采用基于中断的操作模式。

  • 可编程预分频总线时钟:支持对总线时钟进行预分频,并可进一步调整分频参数。

  • 内置32位定时器/计数器模块:提供高精度计时与计数功能。

  • 支持捕获模式:用于精确捕捉外部信号的时间或频率。

  • 支持连续模式或单次触发模式

    • 连续模式:持续生成PWM信号。

    • 单次模式:单次触发后自动停止。

  • 提供参考模式:可输出多种占空比的波形(如方波、脉冲等)。

  • 针对红外应用的优化:PWM3、PWM7、PWM11和PWM15通道专门优化,适用于红外信号控制(如遥控器、传感器等场景),支持输入捕获功能。

2.pwm子系统框架

pwm子系统框架

实验外设:SG90舵机介绍

        舵机是一种位置(角度)伺服的驱动器。适用于那些需要角度步段变化的情景中,所以常用于舵面操纵。

rk3568硬件连接

SG90舵机模块引脚DH11模块引脚名称连接到开发板的引脚编号连接到开发板的应交名称
黄线PWM6UART9_TX_M1(GPIO4_C5)
红线VCC10VCC_5V
棕线GND20GND

SG90使用方法

        SG90 舵机的控制一般需要一个 20ms 周期的 pwm_信号。高电平持续时间一般为 0.5ms 到 2.5ms 范围内对角度进行控制。以 180 度角度 SG90 为例。对应关系如下:

	0.5ms->0 度
	1.0ms->45 度
	1.5ms->90 度
	2.0ms->135 度
	2.5ms->180 度

使用sysfs接口操作pwm

配置管脚支持PWM

​    迅为rk3588开发板,默认设备树有蜂鸣器的pwm配置

​    kernel\arch\arm64\boot\dts\rockchip\topeet_rk3588_config.dtsi

//蜂鸣器
&pwm15 {
    status = "okay";
    pinctrl-0 = <&pwm15m2_pins>;
};

        kernel\arch\arm64\boot\dts\rockchip\rk3588s.dtsi

	pwm15: pwm@febf0030 {
		compatible = "rockchip,rk3588-pwm", "rockchip,rk3328-pwm";
		reg = <0x0 0xfebf0030 0x0 0x10>;
		interrupts = <GIC_SPI 350 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH>;
		#pwm-cells = <3>;
		pinctrl-names = "active";
		pinctrl-0 = <&pwm15m0_pins>;
		clocks = <&cru CLK_PWM3>, <&cru PCLK_PWM3>;
		clock-names = "pwm", "pclk";
		status = "disabled";
	};

        查看3588板子是否有pwm15:

root@topeet:~# cat /sys/kernel/debug/pwm
platform/febf0030.pwm, 1 PWM device
 pwm-0   ((null)              ): period: 0 ns duty: 0 ns polarity: normal

platform/fd8b0030.pwm, 1 PWM device
 pwm-0   ((null)              ): period: 0 ns duty: 0 ns polarity: normal

platform/fd8b0010.pwm, 1 PWM device
 pwm-0   (backlight           ): requested enabled period: 25000 ns duty: 19607 ns polarity: normal

        我们看到第2行,febf0030 对应的地址就为pwm15,设备树reg的地址。

        pwm15对应哪个pwmchipX呢?对应关系如下所示:

        视频老师使用的是rk3568开发板子,对应设备树的修改如下所示:

        打开arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10-linux.dtsi 设备树文件。因为pwm12和串口9复用,请先注释掉串口9的节点。

        打开pwm12,并且使用pinctrl设置服用。因为我们使用的是pwm12的m1,所以要是m1这组。

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

        重新编译内核镜像,烧写到开发板。

        如果配置成功,cat /sys/kernel/debuf/pwm, 可以看到截图圈出来的信息:

操作pwm设备

​    以rk3588的板子为例:

cd /sys/devices/platform/febf0030.pwm/pwm/pwmchip2/
echo 0 > export //导出
ls
device  export  npwm  power  pwm0  subsystem  uevent  unexport  //执行导出后,增加了一个pwm0
cd pwm0
echo 20000000 > period  //PWM周期设置20ms
echo 2000000 > duty_cycle //设置高电平的时间
echo normal > polarity  // 设置极性,有normal或inversed两个参数选中
echo 1 > enable  //使能pwm,使能后开发板的蜂鸣器开始响声
echo 0 > enable  //关闭pwm,关闭后蜂鸣器发声停止

pwm控制器注册流程

        pwm控制器程序程序代码路径:kernel/drivers/pwm/pwm-rockchip.c

pwm子系统API函数

1. pwm_request() – 申请PWM设备
struct pwm_device *pwm_request(int pwm_id, const char *label);

参数

        pwm_id:PWM通道号(如0表示第一个PWM通道)。

        label:字符串标识,用于区分不同使用者(通常为驱动名称)。

返回值

        成功:返回struct pwm_device指针。 失败:返回ERR_PTR()包装的错误码(如-EBUSY表示设备已被占用)。

作用

        从系统中申请指定PWM通道的使用权,需在驱动初始化时调用。

2. pwm_config() – 配置PWM周期与占空比
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);

参数

        pwm:pwm_request()返回的设备指针。

        duty_ns:高电平时间(单位:纳秒,必须小于period_ns)。

        period_ns:信号周期(单位:纳秒,决定PWM频率:频率 = 1 / (period_ns * 1e-9))。

返回值

        成功:返回0。 失败:返回负数错误码(如-EINVAL表示参数无效)。

作用

        设置PWM的周期和占空比,需在pwm_enable()前调用。

3. pwm_enable() / pwm_disable() – 启用/禁用PWM输出
int pwm_enable(struct pwm_device *pwm);
void pwm_disable(struct pwm_device *pwm);

参数

        pwm:已配置好的PWM设备指针。

返回值

        pwm_enable():成功返回0,失败返回负数错误码。 pwm_disable():无返回值。

作用

        pwm_enable():启动PWM信号输出。 pwm_disable():停止PWM输出(占空比清零)。

4. pwm_set_polarity() – 设置PWM极性
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);

参数

        pwm:PWM设备指针。

        polarity:极性枚举值: PWM_POLARITY_NORMAL:默认极性(高电平有效)。 PWM_POLARITY_INVERSED:反转极性(低电平有效)。

返回值

        成功:0。 失败:负数错误码(如硬件不支持极性反转时返回-ENOTSUPP)。

作用

        调整PWM信号的有效电平方向,需在pwm_enable()前调用。

5. pwm_free() – 释放PWM设备
void pwm_free(struct pwm_device *pwm);

参数

         pwm:要释放的PWM设备指针。

返回值

        无。

作用

        释放PWM通道资源,通常在驱动卸载或设备移除时调用。

6. pwm_get() – 通过设备树获取PWM设备(推荐方式)
struct pwm_device *pwm_get(struct device *dev, const char *con_id);

参数

        dev:关联的设备结构体指针(通常为&pdev->dev)。

        con_id:设备树中PWM标识符(如"pwm"对应设备树的pwms属性)。

返回值

        与pwm_request()相同。

作用

        从设备树自动解析并申请PWM资源,替代手动调用pwm_request()。

7.devm_of_pwm_get 函数
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,const char *con_id);

参数

        dev: device

        np: 设备节点

        con_id: 名字

函数作用:获取pwm_device

3.蜂鸣器驱动编写

编写驱动程序(一)

        教学视频中编写的是S90舵机驱动,暂时没有这个模块,暂时选择RK3588板子的蜂鸣器,添加device部分代码,修改kernel\arch\arm64\boot\dts\rockchip\rk3588-evb7-lp4.dtsi,代码修改如下所示:

PWM_BZ {
	compatible = "PWM_BZ";
	//选择pwm15,占空比暂时设置为0,周期20ms,正极性
	pwms = <&pwm15 0 20000000 1>;
};

        接下来编写driver代码, touch PWM_BZ.c:

#include <linux/init.h>
#include <linux/module.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>

static dev_t dev_num;                   
static struct cdev cdev_test;
struct class *class;
struct device *device;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_open.\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size_t, loff_t *off)
{
    printk("This is cdev_test_read.\n");

    return 0;
}

ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    printk("This is cdev_test_write.\n");

    return 0;
}

int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release.\n");

    return 0;
}

static struct file_operations cdev_test_ops = {
	.owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release               
};

int BZ_probe(struct platform_device *dev)
{
    int ret;

    ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
    if (ret < 0){
        printk("alloc_chrdev_region is error\n");
    }
    printk("alloc_register_region is ok\n");

    cdev_init(&cdev_test,&cdev_test_ops);
	cdev_test.owner = THIS_MODULE;
    ret = cdev_add(&cdev_test,dev_num,1);
    if(ret < 0 )
    {
        printk("cdev_add is error\n");
    }
    printk("cdev_add is ok\n");

    class = class_create(THIS_MODULE, "test");
    device = device_create(class, NULL, dev_num, NULL, "BZ");

    return 0;
}

int BZ_remove(struct platform_device *dev)
{
    return 0;
}

const struct of_device_id BZ_of_device_id[] = {
    {.compatible = "PWM_BZ"},
    {}
};

struct platform_driver BZ_platform_driver = {
    .driver = {
        .name = "BZ",
        .of_match_table = BZ_of_device_id,
    },
    .probe = BZ_probe,
    .remove = BZ_remove,
};

static int __init module_cdev_init(void)
{
    platform_driver_register(&BZ_platform_driver);

    return 0;
}

static void __exit module_cdev_exit(void)
{
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&cdev_test);
    
    device_destroy(class, dev_num);
    class_destroy(class);

    platform_driver_unregister(&BZ_platform_driver);
    printk("module exit \n");
}

module_init(module_cdev_init);
module_exit(module_cdev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

        Makefile:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-none-linux-gnu-#交叉编译器前缀
obj-m += PWM_BZ.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/rk3588-linux/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
        make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
        make -C $(KDIR) M=$(PWD) clean    #make clean操作

        编译内核代码并烧写到板子,编译PWM_BZ.c,将KO放到板子上,操作命令:

root@topeet:/run# insmod PWM_BZ.ko 
root@topeet:/run# ls /dev/BZ 
/dev/BZ

内核打印,说明设备和驱动匹配上了,执行了函数BZ_probe:
[   71.208444] alloc_register_region is ok
[   71.208468] cdev_add is ok

编写驱动程序(二)

#include <linux/init.h>
#include <linux/module.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/uacce.h>

static dev_t dev_num;                   
static struct cdev cdev_test;
struct class *class;
struct device *device;
struct pwm_device *bz_pwm_device;

static struct pwm_state state = {
    .period = 20000000,     // 周期(纳秒)
    .duty_cycle = 500000,  // 占空比(纳秒)
    .polarity = PWM_POLARITY_NORMAL,
    .enabled = false,       // 先禁用再配置
};

static int cdev_test_open(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_open.\n");

    //占空比:0.5ms, 周期:20ms,极性设置为默认极性(高电平有效)
    //pwm_config(bz_pwm_device, 500000, 20000000);
    //pwm_set_polarity(bz_pwm_device, PWM_POLARITY_NORMAL);  //奇怪RK3588的内核没有函数pwm_set_polarity

    pwm_apply_state(bz_pwm_device, &state);
    pwm_enable(bz_pwm_device);

    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size_t, loff_t *off)
{
    printk("This is cdev_test_read.\n");

    return 0;
}

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");

    ret = copy_from_user(data, buf, size);
    //通过用户传入的参数,改变占空比
    pwm_config(bz_pwm_device, 500000 + data[0] * (500000 / 45), 20000000);


    return 0;
}

int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release.\n");

    pwm_config(bz_pwm_device, 500000, 20000000);
    pwm_disable(bz_pwm_device);

    return 0;
}

static struct file_operations cdev_test_ops = {
	.owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release               
};

int BZ_probe(struct platform_device *dev)
{
    int ret;

    ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
    if (ret < 0){
        printk("alloc_chrdev_region is error\n");
    }
    printk("alloc_register_region is ok\n");

    cdev_init(&cdev_test,&cdev_test_ops);
	cdev_test.owner = THIS_MODULE;
    ret = cdev_add(&cdev_test,dev_num,1);
    if(ret < 0 )
    {
        printk("cdev_add is error\n");
    }
    printk("cdev_add is ok\n");

    class = class_create(THIS_MODULE, "test");
    device = device_create(class, NULL, dev_num, NULL, "BZ");

    bz_pwm_device = devm_of_pwm_get(&dev->dev, dev->dev.of_node, NULL);
    if( IS_ERR(bz_pwm_device) )
    {
        printk("devm_of_pwm_get is ERROR\n");
        return -1;
    }

    return 0;
}

int BZ_remove(struct platform_device *dev)
{
    return 0;
}

const struct of_device_id BZ_of_device_id[] = {
    {.compatible = "PWM_BZ"},
    {}
};

struct platform_driver BZ_platform_driver = {
    .driver = {
        .name = "BZ",
        .of_match_table = BZ_of_device_id,
    },
    .probe = BZ_probe,
    .remove = BZ_remove,
};

static int __init module_cdev_init(void)
{
    platform_driver_register(&BZ_platform_driver);

    return 0;
}

static void __exit module_cdev_exit(void)
{
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&cdev_test);
    
    device_destroy(class, dev_num);
    class_destroy(class);

    pwm_free(bz_pwm_device);

    platform_driver_unregister(&BZ_platform_driver);
    printk("module exit \n");
}

module_init(module_cdev_init);
module_exit(module_cdev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

        注意代码第31行,注释掉了函数pwm_set_polarity(),是由于RK3588内核代码,PWM不支持这个函数,所以改用函数pwm_apply_state()。

编写测试APP
#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; // 文件描述符
    unsigned char buf[1]; // 缓冲区,存放两个整数值

    // 检查命令行参数个数是否正确
    if (argc != 2) 
    {
        fprintf(stderr, "Usage: %s <count>\n", argv[0]);
        return -1;
    }

    // 打开 PWM GPIO 设备文件
    fd = open("/dev/BZ", O_WRONLY);
    if (fd < 0) 
    {
        perror("open error");
        return -1;
    }

    // 将命令行参数转换为整数,存入缓冲区
    buf[0] = atoi(argv[1]);

    // 将缓冲区的数据写入 PWM GPIO 设备
    write(fd, buf, sizeof(buf));

    //蜂鸣器响30秒
    sleep(30);

    // 关闭设备文件
    close(fd);
    return 0;
}

        编译驱动和APP程序,将两者拷贝到RK3588板子上,加载驱动,接着执行app程序,可以听到蜂鸣器响30s后停止。

4.模拟pwm驱动编写

        模拟PWM使用GPIO口,通过软件的方式,模拟PWM波形。

        普通定时器的时钟频率可以设置在100Hz到1000Hz之间,所以精度只能限制在毫秒级别,无法满足高精度的场景,为此Linux提供了高精度定时器,可以提供纳秒级别的精度。

        在Linux普通定时器用struct time_list来表示(include/linux/timer.h)。

struct timer_list {
	...;
	struct hlist_node	entry;  //定时器到期时间
	void			(*function)(struct timer_list *); //超时服务函数
	u32			flags;
	...;
};

        高精度定时器用struct hrtimer结构体来表示(include/linux/hrtimer.h)

struct hrtimer {
	...;
	ktime_t				_softexpires; //定时时间
	enum hrtimer_restart		(*function)(struct hrtimer *);//超时服务时间
	struct hrtimer_clock_base	*base;
	...;
};

高精度定时器API函数

1.初始化定时器
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);

参数

        timer:要初始化的 hrtimer 结构体。

        which_clock:时钟类型,常用值: CLOCK_MONOTONIC:单调递增时钟(不受系统时间调整影响)。 CLOCK_REALTIME:系统实时时钟(受时间调整影响)。

        mode:定时器模式: HRTIMER_MODE_ABS:绝对时间模式。 HRTIMER_MODE_REL:相对时间模式。

注意:

        如果which_clock 设置为CLOCK_MONOTONIC,那么mode要设置为HARTIMER_MODE_REL;

        如果which_clock 设置为CLOCK_REALTIME,那么mode要设置为HRTIMER_MODE_ABS。

2.启动定时器
int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode);

参数:

        tim:到期时间(根据 mode 为绝对或相对时间)。

返回 0 表示成功,负数表示错误。

3.重新调整定时器时间
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
  • 将定时器的到期时间向前推移一个间隔 interval。

  • 返回推移的次数(适用于周期性定时器)。

4.取消定时器
int hrtimer_cancel(struct hrtimer *timer);
  • 停止定时器,若定时器正在执行回调,会等待其完成。

  • 返回 1 表示定时器正在运行,0 表示未运行。

5.设置定时时间
static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs)

参数:const s64 secs: 秒; const unsigned long nsecs: 纳秒;

编写驱动程序

​    目标使用模拟PWM控制LED小灯的亮暗。

​    由于我使用的迅为RK3588开发板,硬件电路上并GPIO并没有连接LED等,所以需要外接一个LED模块。

        这里我选择GPIO2_C4_d 这个GPIO引脚。修改设备树:kernel/arch/arm64/boot/dts/rockchip/rk3588-evb7-lp4.dtsi,添加代码

leds {
	compatible = "pwm-leds";
	led-gpios = <&gpio2 RK_PC4 GPIO_ACTIVE_HIGH>;
};

        接着编译内核代码,重新烧写到板子上。

        PWM_gpio.c驱动代码如下所示:

#include <linux/init.h>
#include <linux/module.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/uacce.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>

static dev_t dev_num;                   
static struct cdev cdev_test;
struct class *class;
struct device *device;

struct pwm_led_data {
    int sum_count;              //PWM 周期总脉冲数
    int high_count;             //PWM 高电平持续脉冲数
    struct gpio_desc *gpiod;    //GPIO 描述符
    struct hrtimer pwm_timer;   //高分辨率定时器
    ktime_t time;               //定时器时间间隔
};

//声明 PWM LED 数据结构体指针
struct pwm_led_data *data;

// PWM 定时器回调函数
enum hrtimer_restart pwm_timer_func(struct hrtimer *timer) 
{
	static int timer_count = 0; // 定时器计数器
	struct pwm_led_data *mydata = container_of(timer, struct pwm_led_data, pwm_timer);

	// 如果计数器达到总脉冲数, 将 GPIO 设为高电平
	if( timer_count == mydata->sum_count )
	{
		gpiod_set_value(mydata->gpiod, 1);
		timer_count = 0;
	}

	// 如果计数器达到高电平持续脉冲数, 将 GPIO 设为低电平
	if( timer_count == mydata->high_count )
	{
		gpiod_set_value(mydata->gpiod, 0);
	}

	timer_count++;

	// 如果高电平持续脉冲数为 0, 则计数器重置为 0
	hrtimer_forward(timer, hrtimer_cb_get_time(timer), mydata->time);
	return HRTIMER_RESTART;
}

static int cdev_test_open(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_open.\n");

    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size_t, loff_t *off)
{
    printk("This is cdev_test_read.\n");

    return 0;
}

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");

    ret = copy_from_user(kbuf, buf, size);
	if( ret != 0 )
	{
		printk("copy_from_user failed\n");
		return -EFAULT;
	}

	// 更新 PWM LED 数据结构体
	data->sum_count = kbuf[0];
	data->high_count = kbuf[1];
	
    return size;
}

int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is cdev_test_release.\n");

    return 0;
}

static struct file_operations cdev_test_ops = {
	.owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release               
};

int led_probe(struct platform_device *dev)
{
    int ret;

    // 分配 PWM LED 数据结构体内存
    data = kmalloc(sizeof(struct pwm_led_data), GFP_KERNEL);
    if( !data )
    {
        printk("kmalloc failed\n");
        return -ENOMEM;
    }

    // 初始化 PWM LED 数据结构体
    data->sum_count = 20;
    data->high_count = 10;

    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_register_region is ok\n");

    cdev_init(&cdev_test,&cdev_test_ops);
	cdev_test.owner = THIS_MODULE;
    ret = cdev_add(&cdev_test,dev_num,1);
    if(ret < 0 )
    {
        printk("cdev_add is error\n");
        unregister_chrdev_region(dev_num, 1);
        kfree(data);
        return ret;
    }
    printk("cdev_add is ok\n");

    class = class_create(THIS_MODULE, "test");
    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");  // 创建设备节点
    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);
    }
	
	//第2个参数"led",为设备树"led-gpios"的前缀,默认输出高电平
    data->gpiod = gpiod_get(&dev->dev, "led", GPIOD_OUT_HIGH);
    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);  //将 GPIO 设为高电平


    // 初始化高分辨率定时器
    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); // 启动高分辨率定时器

    printk("led_probe successful\n");

    return 0;
}

int led_remove(struct platform_device *dev)
{
	hrtimer_cancel(&data->pwm_timer); 			// 停止高分辨率定时器
	gpiod_put(data->gpiod); 					// 释放 GPIO 描述符
	device_destroy(class, dev_num); 			// 删除设备节点
	class_destroy(class); 						// 删除设备类
	cdev_del(&cdev_test); 						// 从内核注销字符设备
	unregister_chrdev_region(dev_num, 1); 		// 释放设备号
	kfree(data); 								// 释放 PWM LED 数据结构体内存
	printk("led_remove successful\n");
    return 0;
}

// 设备树匹配表
const struct of_device_id led_of_device_id[] = {
    {.compatible = "pwm-leds"},
    {}
};
//MODULE_DEVICE_TABLE 是一个关键宏,用于将设备驱动支持的设备标识符(如 PCI、USB、ACPI 等设备的 ID 表)导出到模块的元数据中。
MODULE_DEVICE_TABLE(of, led_of_device_id);

struct platform_driver led_platform_driver = {
    .driver = {
        .name = "pwm-leds",
        .of_match_table = led_of_device_id,
    },
    .probe = led_probe,
    .remove = led_remove,
};

static int __init module_cdev_init(void)
{
    int ret;

    ret = platform_driver_register(&led_platform_driver);
    if( ret )
    {
        printk("platform_driver_register is error\n");
        return ret;
    }

    return 0;
}

static void __exit module_cdev_exit(void)
{
	// 注销平台设备驱动
	platform_driver_unregister(&led_platform_driver);
	printk("bye bye\n");
}

module_init(module_cdev_init);
module_exit(module_cdev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

        Makefile:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-none-linux-gnu-#交叉编译器前缀
obj-m += PWM_gpio.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/rk3588-linux/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
	make -C $(KDIR) M=$(PWD) clean    #make clean操作
编写测试APP
#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; // 文件描述符
    int buf[2]; // 缓冲区,存放两个整数值

    // 检查命令行参数个数是否正确
    if (argc != 3) 
    {
        fprintf(stderr, "Usage: %s <sum_count> <high_count>\n", argv[0]);
        return -1;
    }

    // 打开 PWM GPIO 设备文件
    fd = open("/dev/pwm-gpio", O_WRONLY);
    if (fd < 0) 
    {
        perror("open error");
        return -1;
    }

    // 将命令行参数转换为整数,存入缓冲区
    buf[0] = atoi(argv[1]);
    buf[1] = atoi(argv[1]);

    // 将缓冲区的数据写入 PWM GPIO 设备
    if (write(fd, buf, sizeof(buf)) != sizeof(buf)) 
    {
        perror("write error");
        close(fd);
        return -1;
    }
    // 关闭设备文件
    close(fd);
    return 0;
}

课后作业--呼吸灯

int led_probe(struct platform_device *dev)
{
    ...;
	data->sum_count = 100;
    data->high_count = 10;
    ...;
	// 初始化高分辨率定时器
    data->time = ktime_set(0, 200000); // 1 ms
}

应用代码:

#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; // 文件描述符
    int buf[2]; // 缓冲区,存放两个整数值
    int i;

    // 检查命令行参数个数是否正确
    if (argc != 3) 
    {
        fprintf(stderr, "Usage: %s <sum_count> <high_count>\n", argv[0]);
        return -1;
    }

    // 打开 PWM GPIO 设备文件
    fd = open("/dev/pwm-gpio", O_WRONLY);
    if (fd < 0) 
    {
        perror("open error");
        return -1;
    }

    // 将命令行参数转换为整数,存入缓冲区
    buf[0] = atoi(argv[1]);
    buf[1] = atoi(argv[1]);

    // 进入死循环
    while (1) 
    {
        // 递增占空比
        for (i = 0; i <= buf[0]; i++) 
        {
            buf[1] = i;
            write(fd, buf, sizeof(buf));
            usleep(30000); // 延迟 30 毫秒
        }
        // 递减占空比
        for (i = buf[0]; i > 0; i--) 
        {
            buf[1] = i;
            write(fd, buf, sizeof(buf));
            usleep(30000); // 延迟 30 毫秒
        }
    }

    // 关闭设备文件
    close(fd);
    return 0;
}

5.扩展

为什么可以使用sysfs接口操作pwm?

    在函数pwmchip_add_width_polarity() 函数最后调用了pwmchip_sys_export(),pwmchip_sys_export()函数向sys文件系统注册了一个pwm控制器设备。pwmchip_sys_export()调用的时候传入全局变量pwm_class,这个全局变量在pwm_sysfs_init()中进行初始化(上面截图大红色框框圈出来那部分)。pwm_group保存了对sysfs操作的一部分文件操作函数,通过这些文件操作函数对PWM设备进行操作:显示,周期显示,占空比显示,设置收起,设置占空比等。

体验一把原厂工程师的工作 --- 支持PWM输入捕获功能

        由于瑞芯微只实现了状态获取和申请两个函数,并没有实现输入捕获相关的函数,而输入捕获也是 PWM 的常用功能之一,要想在RK3588 上实现 PWM 的输入捕获,就需要我们自己来实现 PWM 输入捕获的驱动了。这个章节介绍,如何实现输入捕获功能。

        在章节《ITOP-RK3588 开发板pwm接口介绍》我们知道,只有只有 PWM3、PWM7、PWM11 和 PWM15这 4 路 PWM 通道可以进行输入捕获,在本章节将使用 PWM15 进行输入捕获演示,默认情况下 PWM15 用到了蜂鸣器,具体硬件原理图如下所示:

        所以需要将默认设备树中有关蜂鸣器的进行修改,首先使用以下命令打开 topeet_rk3588_config.dtsi 设备树,修改以下内容,如下图所示:

vi kernel\arch\arm64\boot\dts\rockchip\topeet_rk3588_config.dtsi

&pwm15 {
	status = "okay";
	compatible = "pwm-capture";
	pinctrl-names = "default";
	pinctrl-0 = <&pwm15m2_pins>;
	pwm-channel = <15>;
};

查看pwm15 默认结点配置如下所示
vi kernel\arch\arm64\boot\dts\rockchip\rk3588s.dtsi

	pwm15: pwm@febf0030 {
		compatible = "rockchip,rk3588-pwm", "rockchip,rk3328-pwm";
		reg = <0x0 0xfebf0030 0x0 0x10>;
		interrupts = <GIC_SPI 350 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH>;
		#pwm-cells = <3>;
		pinctrl-names = "active";
		pinctrl-0 = <&pwm15m0_pins>;
		clocks = <&cru CLK_PWM3>, <&cru PCLK_PWM3>;
		clock-names = "pwm", "pclk";
		status = "disabled";
	};

        

        输入PWM设备树节点配置好后,还需要一个输出PWM,这里选择PWM4,操作步骤如下所示:

        从上面这张图中,我们可以知道PWM4_M1这个GPIO没有被使用,那么设备树修改, 并添加修改:

vim kernel/arch/arm64/boot/dts/rockchip/rk3588s-pinctrl.dtsi

	pwm4 {
		...;

		/omit-if-no-ref/
		pwm4m1_pins: pwm4m1-pins {
		rockchip,pins =
			/* pwm4_m1 */
			<4 RK_PC3 11 &pcfg_pull_none>;
		};
	};

        使用以下命令打开 topeet-rk3588-linux.dts 进行修改,进入该设备树之后, 添加如下结点:

vi kernel\arch\arm64\boot\dts\rockchip\topeet_rk3588_config.dtsi

&pwm4 {
	status = "okay";
	pinctrl-0 = <&pwm4m1_pins>;
};

        编译内核,然后将编译好的内核文件烧写到开发板。

cat /sys/kernel/debug/pwm

        根据 rk3588 的数据手册或者设备树可以得到 pwm4 的设备地址为 febd0000,与上图的第三个设备相匹配,就可以证明 PWM 配置成功了。

编写pwm输入捕获驱动程序
编写输入捕获驱动程序
capture_probe
// 驱动探测函数
int 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;

    // 分配驱动数据结构
    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;

    // 获取资源
    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;

    // 获取时钟
    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;

    // 从设备树中读取 PWM 通道
    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;

    // 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "Failed to get IRQ\n");
        return irq;
    }
    ddata->irq = irq;

    // 设置驱动数据
    platform_set_drvdata(pdev, ddata);

    // 请求中断;  IRQF_NO_SUSPEND 不要在挂起的时候进入中断
    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;
    }

    // 启用时钟
    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;
    }

    // 计算 PWM 频率
    freq = clk_get_rate(ddata->clk) / 64;
    ddata->pwm_freq_nstime = 1000000000 / freq;

    // 注册字符设备
    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;
    }

    // 创建设备类
    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;
    }

    // 创建设备
    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;
}
rk_pwm_capture_init

        这个函数将PWM设置为输入捕获模式,代码如下所示:

/* 初始化 PWM 捕获 */
static void rk_pwm_capture_init(void __iomem *pwm_base, uint pwm_id)
{
    int val;

    /* 禁用 PWM */
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFFE) | PWM_DISABLE;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);

    /* 设置为捕获模式 */
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFF9) | PWM_MODE_CAPTURE;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);

    /* 设置分频值 */
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFF0001FF) | PWM_DIV64;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);

    /* 启用中断 */
    rk_pwm_int_ctrl(pwm_base, pwm_id, PWM_INT_ENABLE);

    /* 这里可以启用 PWM 捕获(注释掉的代码) */
    /*
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFFE) | PWM_ENABLE;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);
    */
}

​    第7行代码读取PWM控制寄存器的地址。我们使用的是PWM3,对应控制寄存器偏移地址要为0x003C,但是代码中为啥宏PWM_REG_CTRL为0x0C呢?

        这个是由于设备树中对应PWM3配置的基地址为0xfebf0030,所以宏PWM_REG_CTRL要为0x0C,两者相加刚好为PWM3控制寄存器地址0xfebf003C。

        代码第11~第14行,将PWM设置为捕获模式。

        第16~19行代码,设置分频。代码中将prescale=2^6=64,scale设置为0,即默认512。

        代码第22行,启动中断。

中断初始化函数
/* PWM 中断控制 */
static void rk_pwm_int_ctrl(void __iomem *pwm_base, uint pwm_id, int ctrl)
{
    int val;

    if (pwm_id > 3)
        return; /* 如果 PWM ID 超过 3,直接返回 */
    
    val = readl_relaxed(pwm_base + PWM_REG_INT_EN(pwm_id)); /* 读取当前中断使能状态 */
    
    if (ctrl) {
        val |= PWM_CH_INT_ENABLE(pwm_id); /* 设置中断使能 */
        writel_relaxed(val, pwm_base + PWM_REG_INT_EN(pwm_id)); /* 写入中断使能寄存器 */
    } else {
        val &= ~PWM_CH_INT_ENABLE(pwm_id); /* 清除中断使能 */
        writel_relaxed(val, pwm_base + PWM_REG_INT_EN(pwm_id)); /* 写入中断使能寄存器 */
    }
}
中断服务函数
// PWM 捕获中断处理函数
irqreturn_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;
    }

    // 根据极性读取 lpr 或 hpr
    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;
        }
    }

    // 清除中断状态
    writel_relaxed(PWM_CH_INT(channel), ddata->base + PWM_REG_INTSTS(channel));

    // 状态机处理
    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;
}

        第3行代码,由于在capture_probe()函数第65行,使用platform_set_drvdata()设置了driver_data,所以可以直接通过参数dev_id,获取到struct rkxx_capture_drvdata类型的地址ddata;

        第7行代码是读取中断寄存器的值,如何获取到中断寄存器的地址? 打开RK3588芯片的数据手册《Rockchip RK3588 TRM V1.0-Part1-20220309.pdf》,第1411页:

        我们看到PWM_INSTS对应的偏移地址为0x0040,又由于通过设备树<reg>结点,我们知道基地址为:0xfebf0030,所以宏PWM_REG_INTSTS(n)

#define PWM_REG_INTSTS(n)           ((3 - (n)) * 0x10 + 0x10)

        比如说我们在《体验一把原厂工程师的工作 --- 支持PWM输入捕获功能》章节中,将pwm-channel配置为15:

15 % 4 = 3
(3 - 3) * 0x10 + 0x10 = 0x10

        那么PWM_INSTS的地址就为0xfebf0030+0x10 = 0xfebf0040。

        代码第8~第10行是判断pwm通道3是否产生中断, 打开芯片手册第1422页,我们可以看到PWM_INSTS的第三通道对应的bit取值,0表示没有产生中断,1表示产生中断:

        接下来关注芯片手册1422页,我们看到CH3_Pol,CH2_Pol, CH1_Pol,CH0_Pol。其中一个CH3_Pol bit翻译如下所示:

CH3_Pol 
该位用于捕获模式中,以识别产生中断时输入波形的跳变方向。  
当该位为 1 时,请参考 PWM3_PERIOD_HPR 寄存器获取通道3输入波形的有效高电平周期值;  
否则,请参考 PWM3_PERIOD_LPR 寄存器获取通道3输入波形的有效低电平周期值。  
向 CH3_IntSts 寄存器写入 1 可清除该位状态。

​    也就是说,通过CH3_Pol的值:

​        如果其值为高电平,那么通过PWM3_PERIOD_HPR寄存器,获取到通道3输入波形的有效高电平周期值;

​        如果其值为低电平,那么通过PWM3_PERIOD_LPR寄存器,获取到通道3输入波形的有效低电平周期值;

​    代码第13~23行,就是在做如上的操作,获取高电平的值,保存到ddata->hpr中,获取低电平的值保存到ddata->lpr中。

​    代码低26行,将对应通道中断位写1,表示中断已经发生。

驱动open()函数
static 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");

    // 从 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;
    }

    // 将设备数据保存到文件私有数据中
    file->private_data = ddata;

    return 0;
}

        第8~第9行代码目的是获取struct rkxx_capture_drvdata *ddata,并将其保存到file->private_data中。

container_of

    container_of` 是 Linux 内核中一个重要的宏,用于**通过结构体成员的指针反向获取外层结构体的指针**。它在内核链表、设备驱动等场景中被广泛使用,是内核开发中的核心工具之一。

#define container_of(ptr, type, member) ({              \
    const typeof(((type *)0)->member) *__mptr = (ptr);  \
    (type *)((char *)__mptr - offsetof(type, member));  \
})

参数说明:

  • ptr:结构体内部某成员的指针(已知地址)

  • type:外层结构体的类型(需显式指定)

  • member:成员在结构体中的名称(需与ptr对应)

工作流程

  1. 类型安全检查typeof(((type *)0)->member) 通过 GCC 扩展获取成员的类型,并定义临时指针 __mptr,确保 ptr 的类型与 member 的类型匹配。

  2. 计算偏移量offsetof(type, member) 计算成员 member 在结构体 type 中的字节偏移量(通过编译器宏实现)。

  3. 反向定位结构体: 将成员指针 ptr 转换为 char*(确保按字节计算),减去偏移量后得到外层结构体的起始地址,最后强制转换为 type* 类型。

以驱动open()函数中第9行代码为例子:

ddata = container_of(pcdev, struct rkxx_capture_drvdata, pwm_cdev);

struct rkxx_capture_drvdata {
    void __iomem *base;       
    int irq;                  
    struct device dev;        
    int pwm_freq_nstime;      
    int pwm_channel;         
    int hpr;                 
    int lpr;                  
    eRMC_STATE state;         
    struct clk *clk;          
    struct clk *p_clk;        
    struct pwm_capture_cdev pwm_cdev; 
    struct pwm_data data;             
};

const typeof(((type *)0)->pwm_cdev) *__mptr = (pcdev); //确保pcdev与typeof(((type *)0)->pwm_cdev)类型要匹配
//计算成员pwm_cdev在结构体struct rkxx_capture_drvdata内的地址偏移,接着使用__mptr地址,减去offsetof(type, member)得到的地址,即可获取外层结构体的地址。
(type *)((char *)__mptr - offsetof(type, member)); 

在举一个Linux 内核链表的例子:

// 自定义结构体,内嵌链表节点
struct my_data {
    int value;
    struct list_head list; // 链表成员
};

// 已知链表节点指针,反向获取外层结构体指针
struct list_head *node_ptr = ...; // 某个链表节点
struct my_data *data = container_of(node_ptr, struct my_data, list);
驱动remove函数
// 驱动移除函数
int 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;
}
驱动read函数
// 读取字符设备的回调函数
static 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;
    }

    // 初始化捕获数据
    ddata->lpr = 0;
    ddata->hpr = 0;
    ddata->state = RMC_IDLE1;

    // 启用 PWM
    val = readl_relaxed(ddata->base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFFE) | PWM_ENABLE;
    writel_relaxed(val, ddata->base + PWM_REG_CTRL);

    // 等待数据捕获完成
    for (i = 0; i < 100; i++) {
        msleep(1);
        if (ddata->state == RMC_DONE && ddata->hpr && ddata->lpr) {
            printk("capture ok!\n");
            break;
        }
    }

    // 禁用 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;
    }

    // 计算周期和占空比
    ddata->data.period_ns = (ddata->lpr + ddata->hpr) * ddata->pwm_freq_nstime;
    ddata->data.duty_ns = ddata->hpr * ddata->pwm_freq_nstime;

    // 将数据拷贝到用户空间
    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;
}
完整驱动代码

PWM_capture.c

#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"

// 打开字符设备的回调函数
static 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");

    // 从 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;
    }

    // 将设备数据保存到文件私有数据中
    file->private_data = ddata;

    return 0;
}

// 读取字符设备的回调函数
static 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;
    }

    // 初始化捕获数据
    ddata->lpr = 0;
    ddata->hpr = 0;
    ddata->state = RMC_IDLE1;

    // 启用 PWM
    val = readl_relaxed(ddata->base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFFE) | PWM_ENABLE;
    writel_relaxed(val, ddata->base + PWM_REG_CTRL);

    // 等待数据捕获完成
    for (i = 0; i < 100; i++) {
        msleep(1);
        if (ddata->state == RMC_DONE && ddata->hpr && ddata->lpr) {
            printk("capture ok!\n");
            break;
        }
    }

    // 禁用 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;
    }

    // 计算周期和占空比
    ddata->data.period_ns = (ddata->lpr + ddata->hpr) * ddata->pwm_freq_nstime;
    ddata->data.duty_ns = ddata->hpr * ddata->pwm_freq_nstime;

    // 将数据拷贝到用户空间
    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;
}

// 释放字符设备的回调函数
static int cdev_test_release(struct inode *inode, struct file *file) {
    printk("This is cdev_test_release\n");
    return 0;
}

// 字符设备操作函数结构体
static struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .release = cdev_test_release
};

// PWM 捕获中断处理函数
irqreturn_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;
    }

    // 根据极性读取 lpr 或 hpr
    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;
        }
    }

    // 清除中断状态
    writel_relaxed(PWM_CH_INT(channel), ddata->base + PWM_REG_INTSTS(channel));

    // 状态机处理
    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;
}

// 驱动探测函数
int 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;

    // 分配驱动数据结构
    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;

    // 获取资源
    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;

    // 获取时钟
    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;

    // 从设备树中读取 PWM 通道
    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;

    // 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "Failed to get IRQ\n");
        return irq;
    }
    ddata->irq = irq;

    // 设置驱动数据
    platform_set_drvdata(pdev, ddata);

    // 请求中断
    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;
    }

    // 启用时钟
    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;
    }

    // 计算 PWM 频率
    freq = clk_get_rate(ddata->clk) / 64;
    ddata->pwm_freq_nstime = 1000000000 / freq;

    // 注册字符设备
    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;
    }

    // 创建设备类
    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;
    }

    // 创建设备
    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;
}

// 驱动移除函数
int 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;
}

// 设备树匹配表
const struct of_device_id capture_of_device_id[] = {
    {.compatible = "pwm-capture"},
    {}
};

// 平台驱动结构体
struct platform_driver capture_platform_driver = {
    .driver = {
        .name = "pwm-capture",
        .of_match_table = capture_of_device_id,
    },
    .probe = capture_probe,
    .remove = capture_remove,
};

// 模块初始化函数
static int __init modulecdev_init(void)
{
    return platform_driver_register(&capture_platform_driver);
}

// 模块退出函数
static 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");

rockchip_pwm_capture.h

/* 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>

/* 最大按键数 */
#define MAX_NUM_KEYS                60
/* 最大PWM捕获数 */
#define PWM_PWR_KEY_CAPURURE_MAX    10

/* PWM寄存器定义 */
#define PWM_REG_CNTR                0x00  /* 计数器寄存器 */
#define PWM_REG_HPR                 0x04  /* 周期寄存器 */
#define PWM_REG_LPR                 0x08  /* 占空比寄存器 */
#define PWM_REG_CTRL                0x0c  /* 控制寄存器 */
#define PWM3_REG_INTSTS             0x10  /* PWM3中断状态寄存器 */
#define PWM2_REG_INTSTS             0x20  /* PWM2中断状态寄存器 */
#define PWM1_REG_INTSTS             0x30  /* PWM1中断状态寄存器 */
#define PWM0_REG_INTSTS             0x40  /* PWM0中断状态寄存器 */
#define PWM3_REG_INT_EN             0x14  /* PWM3中断使能寄存器 */
#define PWM2_REG_INT_EN             0x24  /* PWM2中断使能寄存器 */
#define PWM1_REG_INT_EN             0x34  /* PWM1中断使能寄存器 */
#define PWM0_REG_INT_EN             0x44  /* PWM0中断使能寄存器 */

/* 控制寄存器位定义 */
#define PWM_ENABLE                  (1 << 0) /* PWM使能 */
#define PWM_DISABLE                 (0 << 0) /* PWM禁用 */

/* 操作模式 */
#define PWM_MODE_ONESHOT            (0x00 << 1) /* 单次模式 */
#define PWM_MODE_CONTINUMOUS        (0x01 << 1) /* 连续模式 */
#define PWM_MODE_CAPTURE            (0x02 << 1) /* 捕获模式 */

/* 占空比输出极性 */
#define PWM_DUTY_POSTIVE            (0x01 << 3) /* 正极性 */
#define PWM_DUTY_NEGATIVE           (0x00 << 3) /* 负极性 */

/* 非活动状态输出极性 */
#define PWM_INACTIVE_POSTIVE        (0x01 << 4) /* 正极性 */
#define PWM_INACTIVE_NEGATIVE       (0x00 << 4) /* 负极性 */

/* 时钟源选择 */
#define PWM_CLK_SCALE               (1 << 9) /* 时钟分频 */
#define PWM_CLK_NON_SCALE           (0 << 9) /* 无时钟分频 */

#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

/* 预分频因子 */
#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           0xFFFF

enum 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 协议 */
#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 数据结构 */
struct pwm_data {
    int period_ns; /* 周期(纳秒) */
    int duty_ns;   /* 占空比(纳秒) */
};

/* PWM 状态枚举 */
typedef enum _RMC_STATE {
    RMC_IDLE,   /* 空闲状态 */
    RMC_IDLE1,  /* 空闲状态1 */
    RMC_IDLE2,  /* 空闲状态2 */
    RMC_GETDATA,/* 获取数据状态 */
    RMC_DONE,   /* 完成状态 */
} eRMC_STATE;

/* PWM 捕获平台数据结构 */
struct RKxx_remotectl_platform_data {
    int nbuttons; /* 按钮数 */
    int rep;      /* 重复 */
    int timer;    /* 计时器 */
    int wakeup;   /* 唤醒 */
};

/* PWM 捕获字符设备数据结构 */
struct pwm_capture_cdev {
    dev_t dev_num;            /* 设备号 */
    struct cdev cdev_test;    /* 字符设备结构体 */
    struct class *class;      /* 设备类 */
    struct device *device;    /* 设备结构体 */
    struct rkxx_capture_drvdata *ddata; /* 驱动数据 */
};

/* PWM 捕获驱动数据结构 */
struct rkxx_capture_drvdata {
    void __iomem *base;       /* 基地址 */
    int irq;                  /* 中断号 */
    struct device dev;        /* 设备结构体 */
    int pwm_freq_nstime;      /* PWM 频率(纳秒) */
    int pwm_channel;          /* PWM 通道 */
    int hpr;                  /* 高电平周期 */
    int lpr;                  /* 低电平周期 */
    eRMC_STATE state;         /* PWM 状态 */
    struct clk *clk;          /* 时钟 */
    struct clk *p_clk;        /* 父时钟 */
    struct pwm_capture_cdev pwm_cdev; /* PWM 捕获字符设备 */
    struct pwm_data data;             /* PWM 数据结构 */
};

/* PWM 中断控制 */
static void rk_pwm_int_ctrl(void __iomem *pwm_base, uint pwm_id, int ctrl)
{
    int val;

    if (pwm_id > 3)
        return; /* 如果 PWM ID 超过 3,直接返回 */
    
    val = readl_relaxed(pwm_base + PWM_REG_INT_EN(pwm_id)); /* 读取当前中断使能状态 */
    
    if (ctrl) {
        val |= PWM_CH_INT_ENABLE(pwm_id); /* 设置中断使能 */
        writel_relaxed(val, pwm_base + PWM_REG_INT_EN(pwm_id)); /* 写入中断使能寄存器 */
    } else {
        val &= ~PWM_CH_INT_ENABLE(pwm_id); /* 清除中断使能 */
        writel_relaxed(val, pwm_base + PWM_REG_INT_EN(pwm_id)); /* 写入中断使能寄存器 */
    }
}

/* 初始化 PWM 捕获 */
static void rk_pwm_capture_init(void __iomem *pwm_base, uint pwm_id)
{
    int val;

    /* 禁用 PWM */
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFFE) | PWM_DISABLE;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);

    /* 设置为捕获模式 */
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFF9) | PWM_MODE_CAPTURE;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);

    /* 设置分频值 */
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFF0001FF) | PWM_DIV64;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);

    /* 启用中断 */
    rk_pwm_int_ctrl(pwm_base, pwm_id, PWM_INT_ENABLE);

    /* 这里可以启用 PWM 捕获(注释掉的代码) */
    /*
    val = readl_relaxed(pwm_base + PWM_REG_CTRL);
    val = (val & 0xFFFFFFFE) | PWM_ENABLE;
    writel_relaxed(val, pwm_base + PWM_REG_CTRL);
    */
}

#endif

Makefile:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-none-linux-gnu-#交叉编译器前缀
obj-m += PWM_capture.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/rk3588-linux/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
        make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
        make -C $(KDIR) M=$(PWD) clean    #make clean操作
编写测试APP
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

// 定义 PWM 数据结构体
struct pwm_data {
    int period_ns;  // PWM 周期,单位纳秒
    int duty_ns;    // PWM 占空比,单位纳秒
};

int main(int argc, char *argv[]) {
    int fd;
    struct pwm_data data;

    // 打开 PWM 捕获设备文件
    fd = open("/dev/capture", O_RDONLY);
    if (fd < 0) {
        printf("打开设备文件失败\n");
        return -1;
    }

    // 读取 PWM 捕获数据
    if (read(fd, &data, sizeof(data)) != sizeof(data)) {
        printf("读取数据失败\n");
        close(fd);
        return -1;
    }

    // 打印 PWM 捕获数据
    printf("period_ns is %d, duty_ns is %d\n", data.period_ns, data.duty_ns);

    // 关闭设备文件
    close(fd);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yan12368

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值