4.平台设备+class内创建sysfs的节点+简单的dts调用

前言:其实通常驱动都只是修修改改,很少需要重零开始一个个字母敲的。但是,我总觉得不重新敲,心里对学习驱动框架很不踏实。像是平台设备,有多少新手不懂为啥写成这样呢。可能吧,我比较菜。以前平台设备,设备树,sysfs经常改,但都是我心里不清楚逻辑的。经过一段时间的研究终于大概清楚了。

目录

1.平台设备

2.例子(用设备树的)

接下来,我开始分解:

2.1 class的创建字段

效果图:

2.2 DTS获取部分


1.平台设备

平台设备就和其他总线一样,比如i2c总线。只是平台设备的总线是 人为注册 的。作用就是分开 设备 和 驱动 。体现一个 机制 与 策略 分离。

组成:

虚拟的总线platform_bus_type:这个我们一般不用管,用kernel的 bus 框架创造的。

设备device: 用了给设备的描述信息。比如引脚口,触发方式等等。但是随着设备牌子种类越来越多,那么就有很多if语句去判断设备信息。这个东西很多时候被dts取代了。在后面内核会发现有 驱动 无对应匹配的 驱动 情况。那么就是把设备的描述信息在设备树写着。

驱动driver:在设备或者设备树给了描述信息后,写驱动给设备的机制。

ps1:平台设备是个框架,这个三个组成部分一者占一个c文件。总线我们不用管。我们填充好 驱动 和 设备 就行了。在有设备树的情况下,填充 驱动 与 dts 。

ps2:写了框架不代表 传统意义上的驱动创建好。比如sysfs的class,/dev/这些东西还得在driver文件里面写好。应该继续写 字符设备 或者 复杂设备 ,创建class节点

2.例子(用设备树的)

/*
kernel 5.1
*/

#include<linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/sysfs.h>

static int class_test = 0;

static ssize_t class_test_show(struct class *class, struct class_attribute *attr, char *buf)
{
	return (sprintf(buf, "%u\n", class_test));
}

static ssize_t class_test_store(struct class *class, struct class_attribute *attr, const char *buf, size_t len)
{
	int rc;
	rc = kstrtouint(buf, 0, &class_test);
	if (rc < 0)
		pr_err("get class_test value failed!\n");
	return len;
}

static struct class_attribute class_test_add = 
    __ATTR(class_test, 0664, class_test_show, class_test_store);

static struct attribute *Odin_fan_class_attrs[] = {
	&class_test_add.attr,
	NULL,
};

ATTRIBUTE_GROUPS(Odin_fan_class);  
//先创造 Odin_fan_class_groups ,里面再包含 Odin_fan_class_attrs

static struct class Odin_fan_class = {
    .name = "Odin_pi_fan",
    .owner = THIS_MODULE,
    .class_groups = Odin_fan_class_groups,
    /*.class_attrs = Odin_fan_class_attrs,   
    4.9内核用这个 前面类属性注册也不一样,例子用的5.1
    旧版本的内核参照同同版本驱动的例子修改,大同小异
    */
};

int Odin_fan_probe(struct platform_device *pdev){
    int adb = 0;
    int ret;
    ret = device_property_read_u32(&pdev->dev,"adb",&adb);
    printk("<3>""oncethings adb:%d ret:%d",adb,ret);
    return 0;
}

int Odin_fan_remove(struct platform_device *pdev){
    return 0;
}

static const struct of_device_id Odin_fan_of_match[] = {
    { .compatible = "Odin-fan-gpio" },
    {/* sentinel */},
};

static struct platform_driver Odin_fan_driver = {
    .driver = {
        .name = "Odin_pi_fan",              //  /sys/bus/platform/drivers
        .of_match_table = Odin_fan_of_match, //匹配设备树
    },
    .probe = Odin_fan_probe,
    .remove = Odin_fan_remove,
};

static int __init Odin_fan_driver_init(void){
    int rc = 0;
    rc =  class_register(&Odin_fan_class);
    if(rc == 0){
        rc =  platform_driver_register(&Odin_fan_driver);
    }
    
    return rc;
}

static void __exit Odin_fan_driver_exit(void){
    platform_driver_unregister(&Odin_fan_driver);
    class_unregister(&Odin_fan_class);
}

module_init(Odin_fan_driver_init);
module_exit(Odin_fan_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("oncethings");

接下来,我开始分解:

2.1 class的创建字段

static int class_test = 0;

static ssize_t class_test_show(struct class *class, struct class_attribute *attr, char *buf)
{
	return (sprintf(buf, "%u\n", class_test));
}

static ssize_t class_test_store(struct class *class, struct class_attribute *attr, const char *buf, size_t len)
{
	int rc;
	rc = kstrtouint(buf, 0, &class_test);
	if (rc < 0)
		pr_err("get class_test value failed!\n");
	return len;
}

static struct class_attribute class_test_add = 
    __ATTR(class_test, 0664, class_test_show, class_test_store);

static struct attribute *Odin_fan_class_attrs[] = {
	&class_test_add.attr,
	NULL,
};

ATTRIBUTE_GROUPS(Odin_fan_class);  
//先创造 Odin_fan_class_groups ,里面再包含 Odin_fan_class_attrs

static struct class Odin_fan_class = {
    .name = "Odin_pi_fan",// /sys/class/Odin_pi_fan/ 的名字
    .owner = THIS_MODULE,
    .class_groups = Odin_fan_class_groups,
    /*.class_attrs = Odin_fan_class_attrs,   
    4.9内核用这个 前面类属性注册也不一样,例子用的5.1
    旧版本的内核参照同同版本驱动的例子修改,大同小异
    */
};

static int __init Odin_fan_driver_init(void){
    int rc = 0;
    rc =  class_register(&Odin_fan_class);
    .....
    return rc;
}

ps1:写完后的作用就是在 /sys/class/Odin_pi_fan/class_test有个节点 这个节点我们可以cat echo 操作该节点。

ps2:ATTRIBUTE_GROUPS(Odin_fan_class);  这个是5.1有点,对于不同版本的class注册请参照同版本的其他驱动注册。本人4.9版本就和这个有点不同。

ps3:__ATTR(class_test, 0664, class_test_show, class_test_store);

664貌似已经是最高权限了,本人试过在安卓10的kernel写666都是编译失败的。

ps4:class_register(&Odin_fan_class);  用于注册class,与之匹配的是 class_unregister(&Odin_fan_class);

另外 class_create() 和 class_destroy() 匹配。 前者是写了把已有的class注册,后者class_create则是填写一个名字,并按照该名字创造一个class后返回该class。随便用一个就行。

同样,用了class_create()得到的类可以直接赋值groups的,即class的节点,ex:

//kernel 5.1
static ssize_t bl_power_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
............
}

static ssize_t bl_power_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
{
............
}
static DEVICE_ATTR_RW(bl_power);//5.1连函数读写,起名都帮你弄了前缀,没有自由
static struct attribute *bl_device_attrs[] = {
	&dev_attr_bl_power.attr,
	....
	NULL,
};
...
ATTRIBUTE_GROUPS(bl_device);//同样帮你把名字+ _group 后缀
...

static int __init backlight_class_init(void){
    ...
    backlight_class = class_create(THIS_MODULE, "backlight");
    backlight_class->dev_groups = bl_device_groups;
    ....
}

效果图:

2.2 DTS获取部分

/arch/arm/boot/dts/xxx.dts or dtsi

    fan@00000000 {
		compatible = "Odin-fan-gpio";
		adb = <123>;
	};

fan:名字

@00000000:正常来说是填地址,我这里只是个测试,也没地址

compatible :兼容字段,驱动凭着后面字段找该树节点。

我们的目标就是获取 adb 后面的 123 数值。

int Odin_fan_probe(struct platform_device *pdev){
    int adb = 0;
    int ret;
    ret = device_property_read_u32(&pdev->dev,"adb",&adb);
    //寻找adb字段
    printk("<3>""oncethings adb:%d ret:%d",adb,ret);
    return 0;
}

static const struct of_device_id Odin_fan_of_match[] = {
    { .compatible = "Odin-fan-gpio" }, //该名字与dts中的名字相同
    {/* sentinel */},
};

static struct platform_driver Odin_fan_driver = {
    .driver = {
        .name = "Odin_pi_fan",              
        .of_match_table = Odin_fan_of_match, //匹配设备树
    },
    .probe = Odin_fan_probe,
    .remove = Odin_fan_remove,
};

ps1:dts在编译后生成dtb,kernel启动时,会加载dtb这块内存,并获取设备信息。然后在平台设备注册时,就已经把该树节点 Odin_pi_fan 里的所有节点信息都有了。所以在回调函数 probe 里再过滤拿到adb里的信息。

 

无设备树版迟点写

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内核中,PWM 驱动程序通常会通过设备树(Device Tree)来描述硬件的信息,包括 PWM 控制器的地址、中断、时钟等。驱动程序会解析设备树,并根据设备树中描述的信息来初始化 PWM 控制器。在设备树中,PWM 控制器通常会被描述为一个 platform 设备,而 /sys/class/pwm/ 节点则是通过 platform 设备注册到系统中的。 当 PWM 控制器被成功初始化后,驱动程序调用 sysfs 接口来注册 /sys/class/pwm/ 节点。具体来说,驱动程序调用 pwmchip_add() 函数来注册 PWM 控制器,并在此过程中创建 /sys/class/pwm/ 节点。该函数会将 PWM 控制器作为一个 PWM chip 注册到内核 PWM 子系统中,并分配一个唯一的编号(PWM chip ID),该编号可以用于在 sysfs 中访问 PWM 控制器。例如,/sys/class/pwm/pwmchip0/ 目录下的文件就对应着 PWM chip ID 为 0 的 PWM 控制器。在 /sys/class/pwm/pwmchipX/ 目录下,还会有一个叫做 export 的文件,通过向该文件写入 PWM 通道号,可以将该 PWM 通道导出为一个独立的 PWM 设备,此时会在 /sys/class/pwm/pwmchipX/ 目录下创建一个名为 pwmY 的目录,表示导出的 PWM 设备。例如,向 /sys/class/pwm/pwmchip0/export 文件写入 0,就可以将 PWM chip ID 为 0 的 PWM 控制器的第一个 PWM 通道导出为一个独立的 PWM 设备,此时会在 /sys/class/pwm/pwmchip0/ 目录下创建一个名为 pwm0 的目录。在该目录下,可以通过 sysfs 接口来配置该 PWM 通道的参数,例如占空比、周期等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值