一、需求:
四路風扇分別通過PA6\PG9\PG11\PG12四個腳輸出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\n");
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\n",
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\n"); 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\n"); 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\n"); 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\n"); ret = PTR_ERR(gpwm); goto err_class_error; } else { printk("pwm_device_create\n"); } if(!gpio_is_valid(gpio)) printk("debug:invalid gpio,gpio=0x%x\n", 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\n", 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\n", gpio, error); goto err_device_create; } else { printk("successed to request gpio\n"); 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/目錄下會生成pwm1\pwm2\pwm3\pwm4四個字符設備。可以在應用層去對設備進行讀寫操作,會調用到驅動下的這幾個函數。
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..\n",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\n");
}
}
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\n");
}
}
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\n");
}
}
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\n");
}
}
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就可以看到如下目錄:
可以看到設備的主、次設備號:
/dev目錄下也可以看到四個設備,應用層就會通過ioctl對pwm設備寫數據到驅動。
三、調用控制
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中通過參數判斷會執行相應的函數:
char *set_avalon9_fan_auto_control(char *arg)
{
int ret=1,autocontrol=0;
ret = sscanf(arg, "%d", &autocontrol);
printf("autocontrol=%d\n",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\n",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!\n");
}
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!\n");
}
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!\n");
}
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! \n");
}
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("\n...........set_avalon9_fan_default_pwm....pwmval=%d..opt_avalon9_fan_auto_control=%d...\n",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..\n",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\n"); } } 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\n"); } } 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\n"); } } 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\n"); } } break; default : ret = -1; break; } return 0; } int pwm_drv_close (struct inode *inode, struct file *filp) { printk("pwm_drv_close...\n"); return 0; } static void pwm_gpio_start(int minor) { printk("pwm_gpio_start...minor=%d..\n",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\n"); 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\n", 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\n"); 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\n"); 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\n"); 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\n"); ret = PTR_ERR(gpwm); goto err_class_error; } else { printk("pwm_device_create\n"); } if(!gpio_is_valid(gpio)) printk("debug:invalid gpio,gpio=0x%x\n", 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\n", 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\n", gpio, error); goto err_device_create; } else { printk("successed to request gpio\n"); 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 */