轉載請注明出處:https://www.cnblogs.com/lialong1st/p/11436190.html
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; }