gpio模拟pwm信号(风扇转速控制)

一、需求:  

 四路风扇分别通过PA6PG9PG11PG12四个脚输出pwm信号,控制风扇风速。但是芯片这4个脚没用硬件PWM功能,所以必须使用io口模拟pwm时序。 主要通过高精度定时器hrtimer去模拟pwm时序

二、功能实现

1、dts文件注册pwm设备

gpio-pwms {
 		compatible = "gpio-pwms";
 		pinctrl-names = "default";
 		pwm1 {
 			label = "pwm1";
 			gpios = <&pio 0 6 GPIO_ACTIVE_HIGH>;      //GPIO6 ---> PA6
 		};
 
 		pwm2 {
 			label = "pwm2";
 			gpios = <&pio 6 9 GPIO_ACTIVE_HIGH>;      //GPIO201 ---->PG9
 		};
		
		pwm3{
			label = "pwm3";
			gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>;	//GPIO203  ----->PG11
		};	
		
		pwm4{
			label = "pwm4";
			gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>;	//GPIO204  ----->PG12
		};	
 	};

2、驱动编写

(1)解析dts文件中的数据
static struct gpio_pwms_platform_data * gpio_pwms_get_devtree_pdata(struct device *dev)
{
	struct device_node *node, *pp;
	struct gpio_pwms_platform_data *pdata;
	struct pwm_chip *pwm;
	int error;
	int npwms;
	int i = 0;

	node = dev->of_node;
	if (!node)
		return NULL;

	npwms = of_get_child_count(node);     //获取dts文件中pwm结点的个数
	if (npwms == 0)
		return NULL;

       pdata = devm_kzalloc(dev, sizeof(*pdata) + npwms * (sizeof *pwm),GFP_KERNEL);
	if (!pdata) {
		error = -ENOMEM;
		goto err_out;
	}

	pdata->pwms = (struct pwm_chip *)(pdata + 1);
	pdata->npwms = npwms;   

	for_each_child_of_node(node, pp) 
	{
		enum of_gpio_flags flags;

		if (!of_find_property(pp, "gpios", NULL)) 
		{
			pdata->npwms--;
			printk( "Found pwm without gpios
");
			continue;
		}

		pwm = &pdata->pwms[i++];
		pwm->gpio = of_get_gpio_flags(pp, 0, &flags);       //获取每个pwm的gpio
		printk("pwm->gpio=%d,flags=%d",pwm->gpio,flags);
		if (pwm->gpio < 0)
		{
			error = pwm->gpio;
			if (error != -ENOENT) 
			{
				if (error != -EPROBE_DEFER)
					dev_err(dev,
						"Failed to get gpio flags, error: %d
",
						error);
				return ERR_PTR(error);
			}
		} 
		else
		{
			pwm->active_low = flags ;
		}
			pwm->desc = of_get_property(pp, "label", NULL);   //获取label的字串
	}

	
	if (pdata->npwms == 0) {
		error = -EINVAL;
		goto err_out;
	}
	return pdata;

	err_out:
		return ERR_PTR(error);
	
}
(2)gpio_demo_probe函数主要用于创建pwm设备和class,分别给四个pwm设备分配主设备和次设备号,并且设置io口的输入输出。
static int gpio_demo_probe(struct platform_device *pdev)
{


	
	struct device *dev = &pdev->dev;
	int error;
	int i,ret=0;
	unsigned int gpio;
   	 struct pwm_chip *gpwm = NULL;
       pdata = pdev->dev.platform_data;

	if (!pdata) {
		pdata = gpio_pwms_get_devtree_pdata(dev);    //获取dts中定义设备树的数据
		if (IS_ERR(pdata))
			return PTR_ERR(pdata);
		if (!pdata) {
			printk( "missing platform data
");
			return -EINVAL;
		}
	}

	gloabl_pwms_dev = devm_kzalloc(dev, pdata->npwms * sizeof(struct pwm_chip),
		       GFP_KERNEL);
	if (!gloabl_pwms_dev) {
		printk("no memory for gloabl_pwms_dev data
");
		return -ENOMEM;
	}
	memcpy(gloabl_pwms_dev, pdata->pwms, pdata->npwms * sizeof(struct pwm_chip));


	for(i=0;i<pdata->npwms;i++)
	{
          //申请主设备和此设备号,分配了cdev结构,注册了驱动的操作方法集 gpwm = &gloabl_pwms_dev[i]; gpwm->devno = MKDEV(pmajor, i); register_chrdev_region(gpwm->devno , 1, gpwm->desc); gpwm->cdev = cdev_alloc(); gpwm->cdev->owner = THIS_MODULE; cdev_init(gpwm->cdev,&pwm_fops); cdev_add(gpwm->cdev,gpwm->devno,1); } pwm_class = class_create(THIS_MODULE,PWM_CLASS_NAME); //创建class gpio-pwm if(IS_ERR(pwm_class)){ printk("debug:error class_create "); ret = PTR_ERR(pwm_class); goto err_class_error; } for (i = 0; i < pdata->npwms; i++) { gpwm = &gloabl_pwms_dev[i]; gpio = gpwm->gpio; gpwm->dev = device_create(pwm_class,NULL,gpwm->devno,NULL,"pwm%d",i+1); //创建pwm设备 if(IS_ERR(gpwm->dev)){ printk("debug:error device_create "); ret = PTR_ERR(gpwm); goto err_class_error; } else { printk("pwm_device_create "); } if(!gpio_is_valid(gpio)) printk("debug:invalid gpio,gpio=0x%x ", gpio); error = gpio_direction_output(gpio, !((gpwm->active_low == OF_GPIO_ACTIVE_LOW) ? 0 : 1)); //设置io口为输出即默认电平 if (error) { printk( "unable to set direction on gpio %u, err=%d ", gpio, error); return error; } //设置默认pwm周期和高电平时间 gpwm->period = 40000; gpwm->duty = 20000; //申请device error = devm_gpio_request(dev, gpio,gpwm->desc); if (error) { printk( "unable to request gpio %u, err=%d ", gpio, error); goto err_device_create; } else { printk("successed to request gpio "); gpwm->status = PWM_DISABLE; } } return 0; err_device_create: device_destroy(pwm_class,gpwm->devno); err_class_error: class_destroy(pwm_class); return ret; }     

注册了字符设备后,/dev/目录下会生成pwm1pwm2pwm3pwm4四个字符设备。可以在应用层去对设备进行读写操作,会调用到驱动下的这几个函数。

const struct file_operations pwm_fops = {
    .open = pwm_drv_open,
    .write = pwm_drv_write,
    .read = pwm_drv_read,
    .unlocked_ioctl = pwm_drv_ioctl, 
    .release = pwm_drv_close,
};
(3)pwm_drv_ioctl函数会对应用层发过来的指令进行响应.
//command
#define PWM_PERIOD_SET _IOW('A', 1, unsigned long) #define PWM_DUTY_SET _IOW('A', 2, unsigned long) #define PWM_START _IOW('A', 3, unsigned long) 
long pwm_drv_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
	int ret = 0,minornum=0;	
   struct inode * ginode = NULL;
  struct pwm_chip * pwm_dev = NULL;
   ginode = file_inode(filep); 
    minornum= iminor(ginode);
pwm_dev = &gloabl_pwms_dev[minornum];	 
	
printk("pwm_drv_ioctl.minornum=%d...gpio=%d.period=%ld..duty=%ld..
",minornum,pwm_dev->gpio,pwm_dev->period,pwm_dev->duty);	
	switch(minornum)
	{
		case 0:
			pwm1_dev =  &gloabl_pwms_dev[minornum];
			break;
		case 1:
			pwm2_dev =  &gloabl_pwms_dev[minornum];
			break;
		case 2: 
			pwm3_dev =  &gloabl_pwms_dev[minornum];
			break;
		case 3:
			pwm4_dev =  &gloabl_pwms_dev[minornum];
			break;
		default:
			break;	
	}
	
   switch(cmd)
    {
        case PWM_PERIOD_SET :
		if(0 == minornum)	
		{
            		pwm1_dev->period = arg;
		}
		else if(1 == minornum)
		{
		 	 pwm2_dev->period = arg;
		}
		else if(2 == minornum)
		{
		 	 pwm3_dev->period = arg;
		}
		else if(3 == minornum)
		{
		 	 pwm4_dev->period = arg;
		}
			
            break;
 
        case PWM_DUTY_SET :
		if(0 == minornum)	
		{	
           	 pwm1_dev->duty = arg;
		}
		else if(1 == minornum)
		{
			pwm2_dev->duty = arg;
		}
		else if(2 == minornum)
		{
			pwm3_dev->duty = arg;
		}
		else if(3 == minornum)
		{
			pwm4_dev->duty = arg;
		}
            break;
 
        case PWM_START :
	if(0 == minornum)	
	{			
            if(pwm1_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm1_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm1_gpio aready work
");
            }
	}
	else if(1 == minornum)
	{
		if(pwm2_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm2_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm2_gpio aready work
");
            }
	}
	else if(2 == minornum)
	{
		if(pwm3_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm3_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm3_gpio aready work
");
            }
	}
	else if(3 == minornum)
	{
		if(pwm4_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm4_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm4_gpio aready work
");
            }
	}
		
            break;
 
        default :
            ret = -1;
            break;
    }
    return 0;     
}
(4)hrtimer精准定时器模拟pwm信号
  • 初始化定时器,是指hrtimer1_handler为回调函数,hrtimer_start激活回调函数。  
 hrtimer_init(&pwm1_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
 pwm1_dev->mytimer.function = hrtimer1_handler;
 pwm1_dev->kt = ktime_set(0, pwm1_dev->period-pwm1_dev->duty);
 hrtimer_start(&pwm1_dev->mytimer,pwm1_dev->kt,HRTIMER_MODE_REL);
  • 初始化完之后会调用hrtimer1_handler函数,在回调函数会判断io口的电平高低,然后使用ktime_set设置到期时间,如果io为低电平,如果duty不为0,则拉高,ktime_set设置高电平的时间为duty,hrtimer_forward_now函数会等待duty纳秒,然后再执行回调函数hrtimer1_handler,再进行判断,如此循环。使用hrtimer_cancel函数去取消hrtimer.

我们需要四个定时器去模拟pwm,故要写四个回调函数模拟。

注意:根据实际测量,这里gpio_get_value获取到gpio的值,1为低电平,0为高电平。

static enum hrtimer_restart    hrtimer1_handler(struct hrtimer *timer)
{    
    if (gpio_get_value(pwm1_dev->gpio) == 1) {
	// There is no need to pull down when the duty cycle is 100% 
	 if (pwm1_dev->duty != 0) {  
     
            gpio_set_value(pwm1_dev->gpio, 0);
	    pwm1_dev->kt = ktime_set(0, pwm1_dev->duty);
            
        }
	// timer overflow 
        hrtimer_forward_now(&pwm1_dev->mytimer, pwm1_dev->kt); 
    } else {
	// There is no need to pull up when the duty cycle is 0 
                 if (pwm1_dev->duty != pwm1_dev->period) {
            gpio_set_value(pwm1_dev->gpio, 1);
	    pwm1_dev->kt = ktime_set(0, pwm1_dev->period-pwm1_dev->duty);
          
        }
	// timer overflow 
        hrtimer_forward_now(&pwm1_dev->mytimer, pwm1_dev->kt);
    }
 
    return HRTIMER_RESTART;    
} 
(5)设备注册
static struct of_device_id gpio_demo_of_match[] = {
    {.compatible = "gpio-pwms"},
    {},
}
 
MODULE_DEVICE_TABLE(of, gpio_demo_of_match);
 
static struct platform_driver gpio_demo_driver = {
    .probe = gpio_demo_probe,
    .driver = {
	.name = "gpio-pwms",
	.owner = THIS_MODULE,
	.of_match_table = of_match_ptr(gpio_demo_of_match),
    }
};
 
static int __init gpio_demo_init(void)
{	
    return platform_driver_register(&gpio_demo_driver);
}
 
static void __exit gpio_demo_exit(void)
{
	int i;
	 struct pwm_chip *gpwm = NULL;
	for(i=0;i<pdata->npwms;i++ )
	{	
		gpwm = &gloabl_pwms_dev[i];
		gpio_set_value(gpwm->gpio, 1);
		gpio_free(gpwm->gpio);
		device_destroy(pwm_class,gpwm->devno);
		cdev_del(gpwm->cdev);
		unregister_chrdev_region(gpwm->devno,1);
		hrtimer_cancel(&gpwm->mytimer);
		kfree(gpwm);
	}
	class_destroy(pwm_class);
	
    return platform_driver_unregister(&gpio_demo_driver);
}
 
late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);
// add by SouthLj 2019-0924 end
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SouthLj");
(6)注册成功 

注册完pwm设备和gpio_pwms class就可以看到如下目录:

image.png

可以看到设备的主、次设备号:

image.png

/dev目录下也可以看到四个设备,应用层就会通过ioctl对pwm设备写数据到驱动。

image.png

三、调用控制

 

 

image.png

 

  luci界面中设置风扇的数据后,会将数据更新到/etc/config/cgminer配置文件中,然后重新启动cgminer(即执行/etc/init.d/cgminer脚本),脚本中会将风扇风速最大最小值、风速默认值、风速自动控制、预启动时间以及预启动时间内风扇的风速,通过传参传给cgminer.

AVA9_OPTIONS=" --fan-limit $_fan_min-$_fan_max $VOLT_OFFSET"
	PARAMS=" --lowmem $AVA9_OPTIONS $POOL1 $POOL2 $POOL3 --api-allow $_aa --api-listen $_mo --fan-ctrl $_fan_ctrl $PRE_BOOT --pwm-default $_pwm_default"
	NTP_POOL="-p 0.openwrt.pool.ntp.org -p 1.openwrt.pool.ntp.org  -p 3.openwrt.pool.ntp.org -p 4.openwrt.pool.ntp.org"
	ASIA="-p 1.cn.pool.ntp.org -p 3.asia.pool.ntp.org -p 2.asia.pool.ntp.org"

	# _ntp_enable: openwrt, asia, globle
	if [ "$_ntp_enable" == "asia" ]; then
	    NTP_POOL="${ASIA}"
	fi

	if [ ! -f /tmp/cgminer-ntpd-done -a "$_ntp_enable" != "disable" ]; then
	    while [ "$NTPD_RET" != "0" ]; do
		ntpd -d -n -q -N ${NTP_POOL}
		NTPD_RET=$?
	    done

	    touch /tmp/cgminer-ntpd-done
	fi

        # Make sure udevd run before cgminer start
        UDEVDCNT=`pidof udevd | wc -w`
        if [ "$UDEVDCNT" == "0" ]; then
                mkdir -p /run
                udevd --daemon
        fi

	sleep 2
	start-stop-daemon -S -x $APP -p $PID_FILE -m -b -- $PARAMS

pwm占空比设到20%,pwm波形不稳定,导致风扇有顿挫感。nano给的软件默认最小占空比为30%.

cgminer.c中通过参数判断会执行相应的函数:

image.png

char *set_avalon9_fan_auto_control(char *arg)
{
	int ret=1,autocontrol=0;
	ret = sscanf(arg, "%d", &autocontrol);
	printf("autocontrol=%d
",autocontrol);
	if (ret < 1)
		return "No value passed to avalon9-fan-auto-control";	
	opt_avalon9_fan_auto_control = autocontrol;
	
	

	return NULL;
}
//风扇预启动设置逻辑是,如果风扇自动控制开关为enable,控制板上电开机后,则会根据
预启动速度跑,跑的时间是预启动时间,时间过后,风扇风速会按照默认风速转。预启动
设置只在上电第一次有效。
char *set_avalon9_fan_pre_boot_setup(char *arg)
{
	int ret=1,prebootime =1,prebootfan =100;
	ret = sscanf(arg, "%d-%d", &prebootfan,&prebootime);
	printf("prebootime=%d,prebootfan=%d
",prebootime,prebootfan);
	if (prebootfan < 0 || prebootfan> 100 || prebootime < 0 || prebootime > 10)
		return "Invalid value passed to boot_setup";
	if (ret < 1)
		return "No value passed to avalon9-fan-pre-boot-setup";
	prebootflag = 1;
	opt_avalon9_fan_pre_boottim = prebootime;
	opt_avalon9_fan_pre_bootfan = prebootfan;
	return NULL;
	
}

void open_pwm_device(float pwmval)
{
	int fd;
	fd = open("/dev/pwm1",O_RDWR );	   
	 if(fd < 0)
	 {			
	 	printf("failed to open pwm1 failed!
");	    
	 }		   
	 ioctl(fd,PWM_PERIOD_SET,40000);   // 10 000 00ns = 1ms     10 00ns = 1us    
	 ioctl(fd,PWM_DUTY_SET,(int)(40000*((float)pwmval/100)));  
	 ioctl(fd,PWM_START,1);   
	 close(fd);
		 
		
	 fd = open("/dev/pwm2",O_RDWR);  
	 
	 if(fd < 0) 
	 {		
		 printf("failed to open pwm2!
");        
	 }    
	 ioctl(fd,PWM_PERIOD_SET,40000);   // 10 000 000ns = 10ms    
	 ioctl(fd,PWM_DUTY_SET,(int)(40000*((float)pwmval/100)));   
	 ioctl(fd,PWM_START,1);    
	 close(fd);
		
	fd = open("/dev/pwm3",O_RDWR);   
	if(fd < 0) 
	{		
	printf("failed to open pwm3 failed!
");         
	}    
	ioctl(fd,PWM_PERIOD_SET,40000);   // 10 000 000ns = 10ms   
	ioctl(fd,PWM_DUTY_SET,(int)(40000*((float)pwmval/100)));   
	ioctl(fd,PWM_START,1);  
	close(fd);		
	
	fd = open("/dev/pwm4",O_RDWR);  
	if(fd < 0) 
	{		
		printf("failed to open pwm4! 
");    
	}	   
	ioctl(fd,PWM_PERIOD_SET,40000);   // 10 000 000ns = 10ms    
	ioctl(fd,PWM_DUTY_SET,(int)(40000*((float)pwmval/100)));   
	ioctl(fd,PWM_START,1);   
	close(fd);	 
	
}

char *set_avalon9_fan_pwm_default(char *arg)
{
    
	int pwmval;
      int ret=1;
//	int  delaytime  =  90000000;
	ret = sscanf(arg, "%d", &pwmval);
 	printf("
...........set_avalon9_fan_default_pwm....pwmval=%d..opt_avalon9_fan_auto_control=%d...
",pwmval,opt_avalon9_fan_auto_control);
	if (ret < 1)
		return "No value passed to avalon9-fan-default-pwm";
		
      if(pwmval<opt_avalon9_fan_min)
      	{
      	  pwmval =opt_avalon9_fan_min;
      	}
	else if(pwmval>opt_avalon9_fan_max)
	 {
	  	pwmval =opt_avalon9_fan_max;
	 }

	if(opt_avalon9_fan_auto_control == 1)
	{
	      
		 if(opt_avalon9_fan_pre_bootfan<opt_avalon9_fan_min)
	      	{
	      	  opt_avalon9_fan_pre_bootfan =opt_avalon9_fan_min;
	      	}
		else if(opt_avalon9_fan_pre_bootfan>opt_avalon9_fan_max)
		 {
		  	opt_avalon9_fan_pre_bootfan =opt_avalon9_fan_max;
		 }
		if(prebootflag==1)
		{
		      prebootflag==0;
			open_pwm_device(opt_avalon9_fan_pre_bootfan);
			cgsleep_ms(opt_avalon9_fan_pre_boottim*60000);
		}
		open_pwm_device(pwmval);
	}
	else
	{
		open_pwm_device(pwmval);
	}
	
	return NULL;
}

 通过cat /sys/class/kernel/gpio命令查看四个pwm口的电平高低。

四、源码:

dts文件:

	gpio-pwms {
 		compatible = "gpio-pwms";
 		pinctrl-names = "default";
 		pwm1 {
 			label = "pwm1";
 			gpios = <&pio 0 6 GPIO_ACTIVE_HIGH>;
 		};
 
 		pwm2 {
 			label = "pwm2";
 			gpios = <&pio 6 9 GPIO_ACTIVE_HIGH>;
 		};
		
		pwm3{
			label = "pwm3";
			gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>;	
		};	
		
		pwm4{
			label = "pwm4";
			gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>;	
		};	
 	};

pwm_gpio.h:

/**
 * pwm_gpio.h create by yuan
*/
 
#ifndef __PWM_GPIO_H__
#define __PWM_GPIO_H__
 
#include <linux/ioctl.h>
 
#define PWM_PERIOD_SET _IOW('A', 1, unsigned long)
#define PWM_DUTY_SET _IOW('A', 2, unsigned long)
#define PWM_START _IOW('A', 3, unsigned long)

 
#endif /* pwm-gpio.h */

pwm_gpio.c:

/**
 * pwm_gpio.c create by yuanqiangfei
*/
 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
 
 #include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/io.h>
 
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
 
#include <linux/pwm-gpio.h>
   
#define PWM_CLASS_NAME   "gpio-pwms"    //产生sys/class/gpio-pwms
#define PWM_DEVICE_NUM     0         //产生/dev/pwm-0
 
typedef enum {
    PWM_DISABLE = 0,
    PWM_ENABLE,
}PWM_STATUS_t;
 
//pwm的设备对象
struct pwm_chip{
    dev_t devno;				
    struct cdev *cdev;
    struct device *dev;
    unsigned long period;
    unsigned long duty;
    struct hrtimer mytimer;
    ktime_t kt; 
    PWM_STATUS_t status;
    char *desc;	
   int gpio;
   int active_low;
   
};

struct gpio_pwms_platform_data {
	 struct pwm_chip  *pwms;
	int npwms;
};
 
static int pmajor = 247;
struct pwm_chip *pwm1_dev = NULL;
struct pwm_chip *pwm2_dev = NULL;
struct pwm_chip *pwm3_dev = NULL;
struct pwm_chip *pwm4_dev = NULL;
static struct class *pwm_class = NULL;
static struct pwm_chip *gloabl_pwms_dev = NULL;
static struct gpio_pwms_platform_data *pdata = NULL;
static void pwm_gpio_start(int minor);
static enum hrtimer_restart    hrtimer1_handler(struct hrtimer *timer); 
static enum hrtimer_restart    hrtimer2_handler(struct hrtimer *timer); 
static enum hrtimer_restart    hrtimer3_handler(struct hrtimer *timer); 
static enum hrtimer_restart    hrtimer4_handler(struct hrtimer *timer); 
 
int pwm_drv_open (struct inode * inode, struct file *filp)
{
    return 0;
}
 
ssize_t pwm_drv_read (struct file *filp, char __user *userbuf, size_t count, loff_t *fpos)
{
    return 0;
}
ssize_t pwm_drv_write (struct file *filp, const char __user *userbuf, size_t count, loff_t *fpos)
{
    return 0;
}
 
long pwm_drv_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
	int ret = 0,minornum=0;	
   struct inode * ginode = NULL;
  struct pwm_chip * pwm_dev = NULL;
   ginode = file_inode(filep); 
    minornum= iminor(ginode);
pwm_dev = &gloabl_pwms_dev[minornum];	 
	
printk("pwm_drv_ioctl.minornum=%d...gpio=%d.period=%ld..duty=%ld..
",minornum,pwm_dev->gpio,pwm_dev->period,pwm_dev->duty);	
	switch(minornum)
	{
		case 0:
			pwm1_dev =  &gloabl_pwms_dev[minornum];
			break;
		case 1:
			pwm2_dev =  &gloabl_pwms_dev[minornum];
			break;
		case 2: 
			pwm3_dev =  &gloabl_pwms_dev[minornum];
			break;
		case 3:
			pwm4_dev =  &gloabl_pwms_dev[minornum];
			break;
		default:
			break;	
	}
	
   switch(cmd)
    {
        case PWM_PERIOD_SET :
		if(0 == minornum)	
		{
            		pwm1_dev->period = arg;
		}
		else if(1 == minornum)
		{
		 	 pwm2_dev->period = arg;
		}
		else if(2 == minornum)
		{
		 	 pwm3_dev->period = arg;
		}
		else if(3 == minornum)
		{
		 	 pwm4_dev->period = arg;
		}
			
            break;
 
        case PWM_DUTY_SET :
		if(0 == minornum)	
		{	
           	 pwm1_dev->duty = arg;
		}
		else if(1 == minornum)
		{
			pwm2_dev->duty = arg;
		}
		else if(2 == minornum)
		{
			pwm3_dev->duty = arg;
		}
		else if(3 == minornum)
		{
			pwm4_dev->duty = arg;
		}
            break;
 
        case PWM_START :
	if(0 == minornum)	
	{			
            if(pwm1_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm1_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm1_gpio aready work
");
            }
	}
	else if(1 == minornum)
	{
		if(pwm2_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm2_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm2_gpio aready work
");
            }
	}
	else if(2 == minornum)
	{
		if(pwm3_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm3_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm3_gpio aready work
");
            }
	}
	else if(3 == minornum)
	{
		if(pwm4_dev->status == PWM_DISABLE){
                // start timer 
                pwm_gpio_start(minornum);
                pwm4_dev->status = PWM_ENABLE;
              
            }else{
                printk("debug:pwm4_gpio aready work
");
            }
	}
		
            break;
 
        default :
            ret = -1;
            break;
    }
    return 0;     
}
 
int pwm_drv_close (struct inode *inode, struct file *filp)
{
  printk("pwm_drv_close...
");
    return 0;
}
 
 
static void pwm_gpio_start(int minor)
{    
    printk("pwm_gpio_start...minor=%d..
",minor);
	switch(minor)
	{
		case 0:
			 hrtimer_init(&pwm1_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
			 pwm1_dev->mytimer.function = hrtimer1_handler;
			 pwm1_dev->kt = ktime_set(0, pwm1_dev->period-pwm1_dev->duty);
		         hrtimer_start(&pwm1_dev->mytimer,pwm1_dev->kt,HRTIMER_MODE_REL); 
			break;

		case 1:
			 hrtimer_init(&pwm2_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
			 pwm2_dev->mytimer.function = hrtimer2_handler;
			 pwm2_dev->kt = ktime_set(0, pwm2_dev->period-pwm2_dev->duty);
			 hrtimer_start(&pwm2_dev->mytimer,pwm2_dev->kt,HRTIMER_MODE_REL); 
			break;

		case 2:
			 hrtimer_init(&pwm3_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
			 pwm3_dev->mytimer.function = hrtimer3_handler;
			 pwm3_dev->kt = ktime_set(0,pwm3_dev->period -pwm3_dev->duty);
			 hrtimer_start(&pwm3_dev->mytimer,pwm3_dev->kt,HRTIMER_MODE_REL); 
			break;

		case 3:
			 hrtimer_init(&pwm4_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
			 pwm4_dev->mytimer.function = hrtimer4_handler;
			 pwm4_dev->kt = ktime_set(0, pwm4_dev->period-pwm4_dev->duty);
			 hrtimer_start(&pwm4_dev->mytimer,pwm4_dev->kt,HRTIMER_MODE_REL); 
			break;	
		default:
			break;	
	}

}
 
static enum hrtimer_restart    hrtimer1_handler(struct hrtimer *timer)
{    
    if (gpio_get_value(pwm1_dev->gpio) == 1) {
	// There is no need to pull down when the duty cycle is 100% 
	 if (pwm1_dev->duty != 0) {  
     
            gpio_set_value(pwm1_dev->gpio, 0);
	    pwm1_dev->kt = ktime_set(0, pwm1_dev->duty);
            
        }
	// timer overflow 
        hrtimer_forward_now(&pwm1_dev->mytimer, pwm1_dev->kt); 
    } else {
	// There is no need to pull up when the duty cycle is 0 
                 if (pwm1_dev->duty != pwm1_dev->period) {
            gpio_set_value(pwm1_dev->gpio, 1);
	    pwm1_dev->kt = ktime_set(0, pwm1_dev->period-pwm1_dev->duty);
          
        }
	// timer overflow 
        hrtimer_forward_now(&pwm1_dev->mytimer, pwm1_dev->kt);
    }
 
    return HRTIMER_RESTART;    
}

static enum hrtimer_restart    hrtimer2_handler(struct hrtimer *timer)
{    
    if (gpio_get_value(pwm2_dev->gpio) == 1) {
	// There is no need to pull down when the duty cycle is 100% 
	 if (pwm2_dev->duty != 0) {   
            gpio_set_value(pwm2_dev->gpio, 0);
             pwm2_dev->kt = ktime_set(0, pwm2_dev->duty);
        }
	// timer overflow 
        hrtimer_forward_now(&pwm2_dev->mytimer, pwm2_dev->kt); 
    } else {
	// There is no need to pull up when the duty cycle is 0 
             if (pwm2_dev->duty != pwm2_dev->period) {  
            gpio_set_value(pwm2_dev->gpio, 1);
	    pwm2_dev->kt = ktime_set(0, pwm2_dev->period-pwm2_dev->duty);	
        }
	// timer overflow 
        hrtimer_forward_now(&pwm2_dev->mytimer, pwm2_dev->kt);
    }
 
    return HRTIMER_RESTART;    
}

static enum hrtimer_restart    hrtimer3_handler(struct hrtimer *timer)
{    
    if (gpio_get_value(pwm3_dev->gpio) == 1) {
	// There is no need to pull down when the duty cycle is 100% 
          if (pwm3_dev->duty != 0) { 
            gpio_set_value(pwm3_dev->gpio, 0);
            pwm3_dev->kt = ktime_set(0, pwm3_dev->duty);
        }
	// timer overflow 
        hrtimer_forward_now(&pwm3_dev->mytimer, pwm3_dev->kt); 
    } else {
	// There is no need to pull up when the duty cycle is 0 
        
	    if (pwm3_dev->duty != pwm3_dev->period) {  		
            gpio_set_value(pwm3_dev->gpio, 1);
	    pwm3_dev->kt = ktime_set(0, pwm3_dev->period-pwm3_dev->duty);		
        }
	// timer overflow 
        hrtimer_forward_now(&pwm3_dev->mytimer, pwm3_dev->kt);
    }
 
    return HRTIMER_RESTART;    
}

 static enum hrtimer_restart    hrtimer4_handler(struct hrtimer *timer)
{  
    if (gpio_get_value(pwm4_dev->gpio) == 1) {
	// There is no need to pull down when the duty cycle is 100% 
        if (pwm4_dev->duty != 0) {     
            gpio_set_value(pwm4_dev->gpio, 0);
	   pwm4_dev->kt = ktime_set(0, pwm4_dev->duty);		
        }
	// timer overflow 
        hrtimer_forward_now(&pwm4_dev->mytimer, pwm4_dev->kt); 
    } else {
	// There is no need to pull up when the duty cycle is 0 
        if (pwm4_dev->duty != pwm4_dev->period) {    
            gpio_set_value(pwm4_dev->gpio, 1);
             pwm4_dev->kt = ktime_set(0, pwm4_dev->period-pwm4_dev->duty);
        }
	// timer overflow 
        hrtimer_forward_now(&pwm4_dev->mytimer, pwm4_dev->kt);
    }
 
    return HRTIMER_RESTART;    
}
 
 
const struct file_operations pwm_fops = {
    .open = pwm_drv_open,
    .write = pwm_drv_write,
    .read = pwm_drv_read,
    .unlocked_ioctl = pwm_drv_ioctl, 
    .release = pwm_drv_close,
};
 
static struct gpio_pwms_platform_data * gpio_pwms_get_devtree_pdata(struct device *dev)
{
	struct device_node *node, *pp;
	struct gpio_pwms_platform_data *pdata;
	struct pwm_chip *pwm;
	int error;
	int npwms;
	int i = 0;

	node = dev->of_node;
	if (!node)
		return NULL;

	npwms = of_get_child_count(node);
	if (npwms == 0)
		return NULL;

       pdata = devm_kzalloc(dev, sizeof(*pdata) + npwms * (sizeof *pwm),GFP_KERNEL);
	if (!pdata) {
		error = -ENOMEM;
		goto err_out;
	}

	pdata->pwms = (struct pwm_chip *)(pdata + 1);
	pdata->npwms = npwms;   

	for_each_child_of_node(node, pp) 
	{
		enum of_gpio_flags flags;

		if (!of_find_property(pp, "gpios", NULL)) 
		{
			pdata->npwms--;
			printk( "Found pwm without gpios
");
			continue;
		}

		pwm = &pdata->pwms[i++];
		pwm->gpio = of_get_gpio_flags(pp, 0, &flags);
		printk("pwm->gpio=%d,flags=%d",pwm->gpio,flags);
		if (pwm->gpio < 0)
		{
			error = pwm->gpio;
			if (error != -ENOENT) 
			{
				if (error != -EPROBE_DEFER)
					dev_err(dev,
						"Failed to get gpio flags, error: %d
",
						error);
				return ERR_PTR(error);
			}
		} 
		else
		{
			pwm->active_low = flags ;
		}
			pwm->desc = of_get_property(pp, "label", NULL);
	}

	
	if (pdata->npwms == 0) {
		error = -EINVAL;
		goto err_out;
	}
	return pdata;

	err_out:
		return ERR_PTR(error);
	
}
 
static int gpio_demo_probe(struct platform_device *pdev)
{


	
	struct device *dev = &pdev->dev;
	int error;
	int i,ret=0;
	unsigned int gpio;
   	 struct pwm_chip *gpwm = NULL;
       pdata = pdev->dev.platform_data;

	if (!pdata) {
		pdata = gpio_pwms_get_devtree_pdata(dev);
		if (IS_ERR(pdata))
			return PTR_ERR(pdata);
		if (!pdata) {
			printk( "missing platform data
");
			return -EINVAL;
		}
	}

	gloabl_pwms_dev = devm_kzalloc(dev, pdata->npwms * sizeof(struct pwm_chip),
		       GFP_KERNEL);
	if (!gloabl_pwms_dev) {
		printk("no memory for gloabl_pwms_dev data
");
		return -ENOMEM;
	}
	memcpy(gloabl_pwms_dev, pdata->pwms, pdata->npwms * sizeof(struct pwm_chip));


	for(i=0;i<pdata->npwms;i++)
	{
		gpwm = &gloabl_pwms_dev[i];
		gpwm->devno = MKDEV(pmajor, i);
		register_chrdev_region(gpwm->devno , 1, gpwm->desc);
		gpwm->cdev = cdev_alloc();
		gpwm->cdev->owner = THIS_MODULE;
		cdev_init(gpwm->cdev,&pwm_fops);
		cdev_add(gpwm->cdev,gpwm->devno,1);
	}
	

    pwm_class = class_create(THIS_MODULE,PWM_CLASS_NAME);
	  if(IS_ERR(pwm_class)){
        printk("debug:error class_create
");
        ret = PTR_ERR(pwm_class);
        goto err_class_error;
    }
	

	for (i = 0; i < pdata->npwms; i++) 
	{
		gpwm = &gloabl_pwms_dev[i];
		 gpio = gpwm->gpio;
		
		gpwm->dev = device_create(pwm_class,NULL,gpwm->devno,NULL,"pwm%d",i+1);
		if(IS_ERR(gpwm->dev)){
			printk("debug:error device_create
");
			ret = PTR_ERR(gpwm);
			goto err_class_error;
		}
	   else
	   {
		  printk("pwm_device_create
");
	   }
		
		
	    if(!gpio_is_valid(gpio))
			printk("debug:invalid gpio,gpio=0x%x
", gpio);
 
		error = gpio_direction_output(gpio, !((gpwm->active_low == OF_GPIO_ACTIVE_LOW) ? 0 : 1));
		if (error) {
			printk(
				"unable to set direction on gpio %u, err=%d
",
				gpio, error);
			return error;
		}
 
		gpwm->period = 40000;
		gpwm->duty = 20000;
 
		error = devm_gpio_request(dev, gpio,gpwm->desc);
		if (error) {
			printk( "unable to request gpio %u, err=%d
",
				gpio, error);
			  goto err_device_create;	
		}
		else
		{
			printk("successed to request gpio
");
			gpwm->status = PWM_DISABLE;
		}
		
	}
	
		 return 0;

 
err_device_create:
    device_destroy(pwm_class,gpwm->devno);
 
err_class_error:
    class_destroy(pwm_class);
 
    return ret;
}
 

static struct of_device_id gpio_demo_of_match[] = {
    {.compatible = "gpio-pwms"},
    {},
}
 
MODULE_DEVICE_TABLE(of, gpio_demo_of_match);
 
static struct platform_driver gpio_demo_driver = {
    .probe = gpio_demo_probe,
    .driver = {
	.name = "gpio-pwms",
	.owner = THIS_MODULE,
	.of_match_table = of_match_ptr(gpio_demo_of_match),
    }
};
 
static int __init gpio_demo_init(void)
{	
    return platform_driver_register(&gpio_demo_driver);
}
 
static void __exit gpio_demo_exit(void)
{
	int i;
	 struct pwm_chip *gpwm = NULL;
	for(i=0;i<pdata->npwms;i++ )
	{	
		gpwm = &gloabl_pwms_dev[i];
		gpio_set_value(gpwm->gpio, 1);
		gpio_free(gpwm->gpio);
		device_destroy(pwm_class,gpwm->devno);
		cdev_del(gpwm->cdev);
		unregister_chrdev_region(gpwm->devno,1);
		hrtimer_cancel(&gpwm->mytimer);
		kfree(gpwm);
	}
	class_destroy(pwm_class);
	
    return platform_driver_unregister(&gpio_demo_driver);
}
 
late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);
// add by SouthLj 2019-0924 end
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SouthLj");
/* end pwm_gpio.c */

  

  

原文地址:https://www.cnblogs.com/yuanqiangfei/p/14914905.html