文章目录
devmem+pwmchip方式
devmem
使用devmem无需更改设备树与烧写新固件,可用于初步调试pwm功能。如果insmod pwm-user.ko
后,会出现pwmchip*无法打开pwm0。
安装devmem
sudo apt-get install busybox
pwmchip 节点
/sys/class/pwm# ls -l
lrwxrwxrwx 1 root root 0 Apr 20 10:10 pwmchip0 -> ../../devices/3280000.pwm/pwm/pwmchip0 pwm1
lrwxrwxrwx 1 root root 0 Apr 20 10:10 pwmchip1 -> ../../devices/c340000.pwm/pwm/pwmchip1 pwm4
lrwxrwxrwx 1 root root 0 Apr 20 10:10 pwmchip2 -> ../../devices/32c0000.pwm/pwm/pwmchip2 pwm5
lrwxrwxrwx 1 root root 0 Apr 20 10:10 pwmchip3 -> ../../devices/32f0000.pwm/pwm/pwmchip3 pwm8
lrwxrwxrwx 1 root root 0 Apr 20 10:10 pwmchip4 -> ../../devices/39c0000.tachometer/pwm/pwmchip4
调试pwm
pwm5
busybox devmem 0x02434090 32 0x00000401
busybox devmem 0x02434090
echo 0 > /sys/class/pwm/pwmchip2/export
echo 20000000 > /sys/class/pwm/pwmchip2/pwm0/period
echo 1500000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
echo 15000000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
echo 20000000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
pwm1
busybox devmem 0x02440020 32 0x00000402
busybox devmem 0x02440020
echo 0 > /sys/class/pwm/pwmchip0/export
echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/period
echo 1500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
echo 15000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
pwm8
busybox devmem 0x02430040 32 0x00000401
busybox devmem 0x02430040
echo 0 > /sys/class/pwm/pwmchip3/export
echo 20000000 > /sys/class/pwm/pwmchip3/pwm0/period
echo 1500000 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip3/pwm0/enable
echo 15000000 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
echo 20000000 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
pwm debug信息
root@localhost:/# cat /sys/kernel/debug/pwm
platform/39c0000.tachometer, 1 PWM device
pwm-0 (generic_pwm_tachomet): requested period: 0 ns duty: 0 ns polarity: normal
platform/32f0000.pwm, 1 PWM device
pwm-0 (user-pwm8 ): requested enabled period: 8000 ns duty: 0 ns polarity: normal
platform/32c0000.pwm, 1 PWM device
pwm-0 (user-pwm5 ): requested enabled period: 5000 ns duty: 0 ns polarity: normal
platform/c340000.pwm, 1 PWM device
pwm-0 (pwm-fan ): requested enabled period: 45334 ns duty: 0 ns polarity: normal
platform/3280000.pwm, 1 PWM device
pwm-0 (user-pwm1 ): requested enabled period: 10000 ns duty: 0 ns polarity: normal
cfg+设备树+驱动方式
cfg
cfg的配置过程
Jetson_AGX_Series_DevKit_Pinmux_Configuration_Template.xlsm
配置生成*.dtsi
–> Linux_for_Tegra/kernel/pinmux/t19x/pinmux-dts2cfg.py
生成 *.cfg配置文件
–> 复制配置文件到Linux_for_Tegra/bootloader/t186ref/BCT/tegra19x-mb1-pinmux-p2888-0000-a04-p2822-0000-b01.cfg
实际只需确认以下内容即可:
Linux_for_Tegra/bootloader/t186ref/BCT/tegra19x-mb1-pinmux-p2888-0000-a04-p2822-0000-b01.cfg
内容包含
pinmux.0x02440020 = 0x00000402; # soc_gpio54_pn1
pinmux.0x02434090 = 0x00000401; # soc_gpio12_ph0
pinmux.0x02430040 = 0x00000401; # soc_gpio44_pr0
设备树
Linux_for_Tegra/source/public/hardware/nvidia/platform/t19x/galen/kernel-dts/user/user-pwm.dtsi
/*
* Copyright (c) 2021- zwq. All rights reserved.
*
*
*/
#include <dt-bindings/gpio/tegra194-gpio.h>
#include <dt-bindings/pinctrl/pinctrl-tegra.h>
/ {
user-pwm {
compatible = "user,user-pwm";
status = "okay";
user-pwm1 {
pwms = <&tegra_pwm1 0 45334 PWM_POLARITY_NORMAL>;
pwm-id = <1>;
duty = <10>;
period = <10000>;
polarity = <0>;
enable = <1>;
};
user-pwm5 {
pwms = <&tegra_pwm5 0 20000>;
pwm-id = <5>;
duty = <50>;
period = <5000>;
polarity = <1>;
enable = <1>;
};
user-pwm8 {
pwms = <&tegra_pwm8 0 45334 PWM_POLARITY_NORMAL>;
pwm-id = <8>;
duty = <80>;
period = <8000>;
polarity = <1>;
enable = <1>;
};
};
tegra_pwm5: pwm@32c0000 {
status = "okay";
};
tegra_pwm1: pwm@3280000 {
status = "okay";
};
tegra_pwm8: pwm@32f0000 {
status = "okay";
};
};
Linux_for_Tegra/source/public/hardware/nvidia/platform/t19x/galen/kernel-dts/tegra194-p2888-0001-p2822-0000.dts
#include "common/tegra194-p2888-0001-p2822-0000-common.dtsi"
#include "common/tegra194-p2822-camera-modules.dtsi"
#include "t19x-common-modules/tegra194-camera-plugin-manager.dtsi"
#include "user/user-pwm.dtsi"
编译内核,烧写整个固件后引脚配置才能生效(cfg配置文件生效)。
驱动
Linux_for_Tegra/source/public/kernel/kernel-4.9/drivers/pwm/pwm-user.c
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/kthread.h>
#include <linux/pwm.h>
#include <dt-bindings/gpio/tegra194-gpio.h>
#define PWMX_MAX_NUM 10
struct pwmx_device_t {
int pwm_id;
struct pwm_device *pwm;
struct device *dev;
int enable;
enum pwm_polarity polarity;
int duty;
int period;
};
struct pwmx_class_t {
struct class *pwm_class;
struct pwmx_device_t pwm_devs[PWMX_MAX_NUM];
int pwm_cnt;
};
static ssize_t pwmx_enable_show(struct device * dev, struct device_attribute * attr, char * buf)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "enable")){
if(xdev->enable)
return strlcpy(buf, "1\n", 3);
else
return strlcpy(buf, "0\n", 3);
}
return strlcpy(buf, "0\n", 3);
}
static ssize_t pwmx_enable_store(struct device * dev, struct device_attribute * attr, const char * buf, size_t count)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
unsigned long on = simple_strtoul(buf, NULL, 10);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "enable")){
if(on){
pwm_enable(xdev->pwm);
}else{
pwm_disable(xdev->pwm);
}
xdev->enable = on ? 1 : 0;
}
return count;
}
static ssize_t pwmx_polarity_show(struct device * dev, struct device_attribute * attr, char * buf)
{
const struct pwmx_device_t * xdev = dev_get_drvdata(dev);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "polarity")){
if(xdev->polarity == PWM_POLARITY_NORMAL)
return sprintf(buf, "%s\n","normal");
else
return sprintf(buf, "%s\n", "inversed");
}
return sprintf(buf, "%s\n","normal");
}
static ssize_t pwmx_polarity_store(struct device * dev, struct device_attribute * attr, const char * buf, size_t count)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "polarity")){
if( !strcmp(buf,"normal")){
xdev->polarity = PWM_POLARITY_NORMAL;
}
else if( !strcmp(buf,"inversed")){
xdev->polarity = PWM_POLARITY_INVERSED;
}else{
return 0;
}
pwm_set_polarity(xdev->pwm,xdev->polarity);
}
return 0;
}
static ssize_t pwmx_duty_show(struct device * dev, struct device_attribute * attr, char * buf)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "duty")){
return sprintf(buf,"%u\n",xdev->duty);
}
return strlcpy(buf, "0\n", 3);
}
static ssize_t pwmx_duty_store(struct device * dev, struct device_attribute * attr, const char * buf, size_t count)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
unsigned long duty = simple_strtoul(buf, NULL, 10);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "duty")){
xdev->duty = duty>100?100:duty;
pwm_config(xdev->pwm,(xdev->duty*xdev->period/100),xdev->period);
}
return count;
}
static ssize_t pwmx_period_show(struct device * dev, struct device_attribute * attr, char * buf)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "period")){
return sprintf(buf,"%u\n",xdev->period);
}
return strlcpy(buf, "0\n", 3);
}
static ssize_t pwmx_period_store(struct device * dev, struct device_attribute * attr, const char * buf, size_t count)
{
struct pwmx_device_t * xdev = dev_get_drvdata(dev);
unsigned long period = simple_strtoul(buf, NULL, 10);
if(xdev == NULL)
return 0;
if(!strcmp(attr->attr.name, "period")){
xdev->period = period;
pwm_config(xdev->pwm,xdev->duty,xdev->period);
}
return count;
}
static DEVICE_ATTR(enable, 0664, pwmx_enable_show, pwmx_enable_store);
static DEVICE_ATTR(polarity, 0664, pwmx_polarity_show, pwmx_polarity_store);
static DEVICE_ATTR(duty, 0664, pwmx_duty_show, pwmx_duty_store);
static DEVICE_ATTR(period, 0664, pwmx_period_show, pwmx_period_store);
static struct attribute * pwmx_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_polarity.attr,
&dev_attr_duty.attr,
&dev_attr_period.attr,
NULL
};
static const struct attribute_group pwmx_group = {
.attrs = pwmx_attrs,
};
static int init_device_data(struct pwmx_device_t *dev,struct pwm_device *pwm,int pwm_id,int enable,int polarity,int duty,int period)
{
if(dev == NULL)
return -1;
dev->pwm = pwm;
dev->pwm_id = pwm_id;
dev->enable = enable;
dev->polarity = polarity;
dev->duty = duty;
dev->period = period;
pwm_config(dev->pwm, (duty*period/100), period);
if(dev->enable){
pwm_enable(dev->pwm);
}else{
pwm_disable(dev->pwm);
}
return 0;
}
static int create_device_nodes(struct pwmx_class_t *cls,struct pwm_device *pwm,int pwm_id,int enable,int polarity,int duty,int period)
{
int ret = 0;
if(cls == NULL)
return -1;
if(cls->pwm_cnt>=PWMX_MAX_NUM)
return -2;
cls->pwm_devs[cls->pwm_cnt].dev = device_create(cls->pwm_class, NULL,MKDEV(0, cls->pwm_cnt), NULL, "%s", name);
ret = sysfs_create_group(&cls->pwm_devs[cls->pwm_cnt].dev->kobj,&pwmx_group);
init_device_data(&cls->pwm_devs[cls->pwm_cnt],pwm,pwm_id,enable,polarity,duty,period);
dev_set_drvdata(cls->pwm_devs[cls->pwm_cnt].dev, &cls->pwm_devs[cls->pwm_cnt]);
cls->pwm_cnt++;
return 0;
}
static int user_pwm_probe(struct platform_device * pdev)
{
struct pwm_device * pwm_dev = NULL;
struct device_node * node = pdev->dev.of_node;
struct pwmx_class_t * pwmx_class;
struct device_node *child;
int ret;
int cnt = 0;
int duty = 0;
int enable = 0;
int polarity = 0;
int period = 0;
int pwm_id = 0;
char name[20];
if(!node)
return -ENODEV;
pwmx_class = kzalloc(sizeof(struct pwmx_class_t), GFP_KERNEL);
if (!pwmx_class){
printk("%s -ENOMEM\n",__func__);
return -ENOMEM;
}
pwmx_class->pwm_cnt = 0;
pwmx_class->pwm_class = class_create(THIS_MODULE, "pwm_usr");
for_each_child_of_node(node, child){
//*********************************************************
ret = of_property_read_u32(child, "duty",&cnt);
if (ret || !cnt ) {
pr_err("no duty\n");
goto INIT_ERR_FREE;
}
duty = cnt;
printk("get duty:%d\n",duty);
ret = of_property_read_u32(child,"enable",&cnt);
if (ret || !cnt ) {
goto INIT_ERR_FREE;
}
enable = cnt;
printk("get enable:%d\n",enable);
ret = of_property_read_u32(child,"pwm-id",&cnt);
if(ret || !cnt){
goto INIT_ERR_FREE;
}
pwm_id = cnt;
printk("get pwm_id %d\n",pwm_id);
ret = of_property_read_u32(child,"period",&cnt);
if (ret || !cnt ) {
goto INIT_ERR_FREE;
}
period = cnt;
printk("get period:%d\n",period);
ret = of_property_read_u32(child,"polarity",&cnt);
if (ret) {
goto INIT_ERR_FREE;
}
polarity = cnt;
printk("get polarity:%d\n",polarity);
sprintf(name,"pwm%d",pwm_id);
pwm_dev = of_pwm_get(child, NULL);
if (IS_ERR(pwm_dev)) {
dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
pwm_dev = pwm_request(pwm_id, name);
}
if (IS_ERR(pwm_dev)) {
ret = PTR_ERR(pwm_dev);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "unable to request PWM\n");
goto INIT_ERR_FREE;
}
printk("pwm%d duty:%d%% period:%d polarity:%d enable:%d\n",pwm_id,duty,period,polarity,enable);
create_device_nodes(pwmx_class,pwm_dev,pwm_id,enable,polarity,duty,period);
//********************************************************
}
dev_set_drvdata(&pdev->dev, pwmx_class);
pr_info("pwm_init finish\n");
return 0;
INIT_ERR_FREE:
pr_err("pwm_init err\n");
kfree(pwmx_class);
return -1;
}
static int user_pwm_remove(struct platform_device *pdev)
{
int i = 0;
struct pwmx_class_t * xdev = dev_get_drvdata(&pdev->dev);
if(xdev != NULL){
for(i=0;i<xdev->pwm_cnt;i++){
pwm_disable(xdev->pwm_devs[i].pwm);
pwm_free(xdev->pwm_devs[i].pwm);
printk("pwm%d free\n",xdev->pwm_devs[i].pwm_id);
sysfs_remove_group(&xdev->pwm_devs[i].dev->kobj, &pwmx_group);
device_destroy(xdev->pwm_class,MKDEV(0, i));
}
class_destroy(xdev->pwm_class);
}
return 0;
}
#ifdef CONFIG_PM
static int user_pwm_suspend(struct device *dev)
{
return 0;
}
static int user_pwm_resume(struct device *dev)
{
return 0;
}
#else
#define user_pwm_suspend NULL
#define user_pwm_resume NULL
#endif
static const struct dev_pm_ops user_pwm_pm_ops = {
.suspend = user_pwm_suspend,
.resume = user_pwm_resume,
};
static struct of_device_id user_pwm_of_match[] = {
{ .compatible = "user,user-pwm" },
{},
};
MODULE_DEVICE_TABLE(of, user_pwm_of_match);
static struct platform_driver user_pwm_driver = {
.driver = {
.name = "user-pwm",
.owner = THIS_MODULE,
.pm = &user_pwm_pm_ops,
.of_match_table = of_match_ptr(user_pwm_of_match),
},
.probe = user_pwm_probe,
.remove = user_pwm_remove,
};
module_platform_driver(user_pwm_driver);
MODULE_DESCRIPTION("user pwm driver");
MODULE_AUTHOR("zhengweiqing, 1548889230@qq.com");
MODULE_LICENSE("GPL");
Linux_for_Tegra/source/public/kernel/kernel-4.9/drivers/pwm/Makefile
obj-m += pwm-user.o
编译烧写
编译烧写
Linux_for_Tegra/source/public/kernel/kernel-4.9
下编译内核, Linux_for_Tegra/
目录下使用flash.sh
脚本烧写固件。
测试
驱动加载
sudo insmod pwm-user.ko
测试pwm
cat /sys/kernel/debug/pwm
root@localhost:/# cat /sys/kernel/debug/pwm
platform/39c0000.tachometer, 1 PWM device
pwm-0 (generic_pwm_tachomet): requested period: 0 ns duty: 0 ns polarity: normal
platform/32f0000.pwm, 1 PWM device
pwm-0 (user-pwm8 ): requested enabled period: 8000 ns duty: 6400 ns polarity: normal
platform/32c0000.pwm, 1 PWM device
pwm-0 (user-pwm5 ): requested enabled period: 5000 ns duty: 2500 ns polarity: normal
platform/c340000.pwm, 1 PWM device
pwm-0 (pwm-fan ): requested enabled period: 45334 ns duty: 0 ns polarity: normal
platform/3280000.pwm, 1 PWM device
pwm-0 (user-pwm1 ): requested enabled period: 10000 ns duty: 1000 ns polarity: normal
root@localhost:/#
root@localhost:/# cd /sys/class/pwm_usr/
root@localhost:/sys/class/pwm_usr# ls
pwm1 pwm5 pwm8
root@localhost:/sys/class/pwm_usr# cd pwm8/
root@localhost:/sys/class/pwm_usr/pwm8# ls
duty enable period polarity power subsystem uevent
root@localhost:/sys/class/pwm_usr/pwm8# echo 100 > duty
root@localhost:/sys/class/pwm_usr/pwm8# cd ../
root@localhost:/sys/class/pwm_usr# echo 70 > pwm1/duty
root@localhost:/sys/class/pwm_usr# echo 50 > pwm5/duty
root@localhost:/sys/class/pwm_usr# cat /sys/kernel/debug/pwm
platform/39c0000.tachometer, 1 PWM device
pwm-0 (generic_pwm_tachomet): requested period: 0 ns duty: 0 ns polarity: normal
platform/32f0000.pwm, 1 PWM device
pwm-0 (user-pwm8 ): requested enabled period: 8000 ns duty: 8000 ns polarity: normal
platform/32c0000.pwm, 1 PWM device
pwm-0 (user-pwm5 ): requested enabled period: 5000 ns duty: 2500 ns polarity: normal
platform/c340000.pwm, 1 PWM device
pwm-0 (pwm-fan ): requested enabled period: 45334 ns duty: 0 ns polarity: normal
platform/3280000.pwm, 1 PWM device
pwm-0 (user-pwm1 ): requested enabled period: 10000 ns duty: 7000 ns polarity: normal
root@localhost:/sys/class/pwm_usr#