zoukankan      html  css  js  c++  java
  • 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 */
    

      

      

  • 相关阅读:
    VS2010程序打包
    Extjs布局
    Extjs4.x完美treepanel checkbox无限级选中与取消
    extjs 4.2 日期控件 选择时分秒功能
    extjs4.0下的日期控件的星期显示为y的解决办法
    linux下mysql 配置
    坐标轴笔记
    cpp 内嵌函数(lambda,struct)
    ue4 笔记
    ue4 蓝图方法备份
  • 原文地址:https://www.cnblogs.com/yuanqiangfei/p/14914905.html
Copyright © 2011-2022 走看看