blogernice

导航

[Linux] pwm设备驱动调试

CPU:RK3288

系统:Linux

 

客户需求是通过 pwm 控制激光的强弱,写驱动前,需要先了解几个相关的概念和相关函数

 

概念:

PWM:脉冲宽度调试(Pulse width modulation),最典型的应用是调节 LED 的亮度

占空比:高电平或者低电平时间占一个周期时间的比例

 

驱动中 pwm 相关函数:

复制代码
/*
 * 功能:申请pwm设备
 * pwm_id:pwm编号
 * label:自定义名称
 */
struct pwm_device *pwm_request(int pwm_id, const char *label);

/*
 * 功能:释放pwm设备
 * pwm:pwm申请到的设备
 */
void pwm_free(struct pwm_device *pwm);

/*
 * 功能:使能pwm
 * pwm:pwm申请到的设备
 */
int pwm_enable(struct pwm_device *pwm);

/*
 * 功能:失能pwm
 * pwm:pwm申请到的设备
 */
void pwm_disable(struct pwm_device *pwm);

/*
 * 功能:设置pwm周期
 * pwm:pwm申请到的设备
 * period:pwm周期时间,单位为ns
 */
void pwm_set_period(struct pwm_device *pwm, unsigned int period);

/*
 * 功能:获取pwm周期
 * pwm:pwm申请到的设备
 * 返回值:pwm周期时间,单位为ns
 */
unsigned int pwm_get_period(struct pwm_device *pwm);

/*
 * 功能:设置pwm有效极性
 * pwm:pwm申请到的设备
 * polarity:pwm有效极性 PWM_POLARITY_NORMAL / PWM_POLARITY_INVERSED
 */
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);

/*
 * 功能:配置pwm
 * pwm:pwm申请到的设备
 * duty_ns:pwm占空比时间,单位为ns
 * period_ns:pwm周期时间,单位为ns
 */
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
复制代码

 

根据需求,底层给用户提供四个功能接口,包括设置周期时间、获取当前周期时间、设置占空比、获取当前占空比

1、在 dts 中配置 pwm 默认参数

 

复制代码
pwm-laser {
    compatible = "pwm-laser";
    status = "okay";
    /* pwm1 */
    pwm-id = <1>;
    /* pwm1周期为1ms */
    period = <1000000>;
    /* 占空比默认为0 */
    default-level = <0>;
};

&pwm1 {
    status = "okay";
};
复制代码

 

2、编写 pwm 驱动,按照需求为用户提供接口

复制代码
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>

#define PWM_LASER_SET_PERIOD        _IOW('A', 0x11, unsigned int)
#define PWM_LASER_GET_PERIOD        _IOR('A', 0x12, unsigned int)
#define PWM_LASER_SET_DUTY          _IOW('A', 0x13, unsigned int)
#define PWM_LASER_GET_DUTY          _IOR('A', 0x14, unsigned int)
#define PWM_LASER_ENABLE            _IO('A', 0x15)
#define PWM_LASER_DISABLE           _IO('A', 0x16)

struct pwm_para {
    int pwm_id;
    unsigned int period;
    unsigned int level;
    unsigned int duty;
    struct pwm_device *pwm;
};

struct pwm_para laser;

static void pwm_laser_update(void)
{
    laser.duty = laser.level * laser.period / 100;

    pwm_config(laser.pwm, laser.duty, laser.period);
}

static void pwm_laser_enable(void)
{
    pwm_enable(laser.pwm);
}

static void pwm_laser_disable(void)
{
    // 关闭pwm之前将占空比设置为0
    laser.level = 0;
    pwm_laser_update();
    pwm_disable(laser.pwm);
}

static void pwm_laser_set_period(unsigned int period)
{
    laser.period = period * 1000000;
    
    pwm_set_period(laser.pwm, laser.period);
}

static unsigned int pwm_laser_get_period(void)
{
    return (laser.period / 1000000);
}

static int pwm_laser_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int pwm_laser_release(struct inode *inode, struct file *filp)
{
    pwm_laser_disable();
    
    return 0;
}

static ssize_t pwm_laser_read(struct file *filp, char __user *buf, size_t len, loff_t *pos)
{ 
    return 0;
}

static ssize_t pwm_laser_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{
    return 0;
}

static long pwm_laser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    unsigned int period;
    unsigned int level;
    
    void __user *argp = (void __user *)arg;
    
    switch (cmd)
    {
        case PWM_LASER_SET_PERIOD:
            if (argp == NULL) {
                printk("laser: invalid argument.");
                return -EINVAL;
            }

            if (copy_from_user(&period, argp, sizeof(unsigned int))) {
                printk("copy_from_user failed.");
                return -EFAULT;
            }
            
            pwm_laser_set_period(period);
            break;
            
        case PWM_LASER_GET_PERIOD:
            period = pwm_laser_get_period();
        
            if (copy_to_user(argp, &period, sizeof(unsigned int))) {
                printk("copy_to_user failed.");
                return -EFAULT;
            }
            break;
            
        case PWM_LASER_SET_DUTY    :
            if (argp == NULL) {
                printk("laser: invalid argument.");
                return -EINVAL;
            }

            if (copy_from_user(&level, argp, sizeof(unsigned int))) {
                printk("copy_from_user failed.");
                return -EFAULT;
            }

            if ((level < 0) || (level > 100)) {
                printk("laser: invalid argument.");
                return -EINVAL;
            }

            laser.level = level;
            pwm_laser_update();
            break;
            
        case PWM_LASER_GET_DUTY    :
            if (copy_to_user(argp, &laser.level, sizeof(unsigned int))) {
                printk("copy_to_user failed.");
                return -EFAULT;
            }
            break;
            
        case PWM_LASER_ENABLE:
            pwm_laser_update();
            pwm_laser_enable();
            break;
            
        case PWM_LASER_DISABLE:
            pwm_laser_disable();
            break;
            
        default:
            printk("laser: cmd error!\n");
            return -EFAULT;
    }
    
    return 0;
}

struct file_operations pwm_laser_fops = {
    .owner = THIS_MODULE,
    .open = pwm_laser_open,
    .release = pwm_laser_release,
    .write = pwm_laser_write,
    .read = pwm_laser_read,
    .unlocked_ioctl = pwm_laser_ioctl,
};

struct miscdevice pwm_laser_dev =
{  
    .minor  =   MISC_DYNAMIC_MINOR,
    .fops   =   &pwm_laser_fops,
    .name   =   "pwm-laser",
};

static ssize_t pwm_laser_parse_dt(struct platform_device *pdev)
{
    struct device_node *node = pdev->dev.of_node;
    int ret;

    if (!node) {
        dev_err(&pdev->dev, "laser: Device Tree node missing\n");
        return -EINVAL;
    }
    
    ret = of_property_read_u32(node, "pwm-id", &laser.pwm_id);
    if (ret < 0) {
        dev_err(&pdev->dev, "laser: pwm-id missing\n");
        return ret;
    }

    ret = of_property_read_u32(node, "period", &laser.period);
    if (ret < 0) {
        dev_err(&pdev->dev, "laser: period missing\n");
        return ret;
    }
    
    ret = of_property_read_u32(node, "default-level", &laser.level);
    if (ret < 0) {
        dev_err(&pdev->dev, "laser: default-level missing\n");
        return ret;
    }

    return 0;
}

static void pwm_laser_test(void)
{
    int i = 0, dir = 0;
    
    pwm_laser_enable();
    
    while (1)
    {
        if (dir == 0) {
            laser.level = i++;
            if (i == 100)
                dir = 1;
        } else {
            laser.level = i--;
            if (i == 0)
                dir = 0;
        }

        pwm_laser_update();
        msleep(100);
    }
    
    pwm_laser_disable();
}

static int pwm_laser_probe(struct platform_device *pdev)
{
    int ret;

    ret = pwm_laser_parse_dt(pdev);
    if (ret < 0) {
        dev_err(&pdev->dev, "laser: Device not found\n");
        return -ENODEV;
    }
    
    laser.pwm = pwm_request(laser.pwm_id, "pwm-laser");
    if (IS_ERR(laser.pwm)) {
        dev_err(&pdev->dev, "laser: unable to request legacy PWM\n");
        ret = PTR_ERR(laser.pwm);
        return ret;
    }

    // 设置pwm参数
    pwm_laser_update();

    // 设置极性必须在设置参数之后,pwm使能之前,否则会设置失败
    ret = pwm_set_polarity(laser.pwm, PWM_POLARITY_NORMAL);
    if (ret < 0)
        printk("pwm set polarity fail, ret = %d\n", ret);

    // 调试完屏蔽使能函数,交由app控制
//  pwm_laser_enable();

    misc_register(&pwm_laser_dev);

    // 调试完屏蔽测试函数
//  pwm_laser_test();

    return 0;
}

static int pwm_laser_remove(struct platform_device *pdev)
{
    pwm_disable(laser.pwm);
    pwm_free(laser.pwm);
    
    misc_deregister(&pwm_laser_dev);
    
    return 0;
}

int pwm_laser_suspend(struct device *dev)
{
    return 0;
}

int pwm_laser_resume(struct device *dev)
{
    return 0;
}

const struct dev_pm_ops pwm_laser_pm_ops = {
    .suspend = pwm_laser_suspend,
    .resume = pwm_laser_resume,
};

static const struct of_device_id of_pwm_laser_match[] = {
    {.compatible = "pwm-laser", },
    {},
};

static struct platform_driver pwm_laser = {
    .probe = pwm_laser_probe,
    .remove = pwm_laser_remove,
    .driver = {
        .name = "pwm-laser",
        .owner = THIS_MODULE,
        .pm = &pwm_laser_pm_ops,
        .of_match_table = of_match_ptr(of_pwm_laser_match),
    },
};

module_platform_driver(pwm_laser);

MODULE_DESCRIPTION("pwm control driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AaronLee");
复制代码

 

3、编写用户测试 demo,测试需求中的所有接口

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>

#define PWM_LASER_SET_PERIOD            _IOW('A', 0x11, unsigned int)
#define PWM_LASER_GET_PERIOD            _IOR('A', 0x12, unsigned int)
#define PWM_LASER_SET_DUTY              _IOW('A', 0x13, unsigned int)
#define PWM_LASER_GET_DUTY              _IOR('A', 0x14, unsigned int)
#define PWM_LASER_ENABLE                _IO('A', 0x15)
#define PWM_LASER_DISABLE               _IO('A', 0x16)

int main(void)
{
    unsigned int i = 0;
    int dir = 0;
    int duty, period;
    int fd;

    fd = open("/dev/pwm-laser", O_RDWR);
    if (fd < 0)
    {
            perror("open pwm-laser fail");
            exit(1);
    }
    
    /* 获取pwm周期 */
    ioctl(fd, PWM_LASER_GET_PERIOD, &period);
    printf("pwm: period = %d\n", period);
    
    /* 设置pwm周期为2ms */
    period = 2;
    ioctl(fd, PWM_LASER_SET_PERIOD, &period);

    /* 获取pwm占空比 */
    ioctl(fd, PWM_LASER_GET_DUTY, &duty);
    printf("pwm: duty = %d\n", duty);

    /* 使能pwm */
    ioctl(fd, PWM_LASER_ENABLE);

    while(1)
    {
        /* 设置pwm占空比 */
        ioctl(fd, PWM_LASER_SET_DUTY, &i);

        if (dir == 0) {
            i++;
            if (i == 100)
                dir = 1;
        } else {
            i--;
            if (i == 0)
                dir = 0;
        }

        sleep(1);
    }

    /* 关闭pwm */
    ioctl(fd, PWM_LASER_DISABLE);

    close(fd);

    return 0;
}
复制代码

 

posted on 2019-11-16 11:56  blogernice  阅读(649)  评论(0编辑  收藏  举报