Last active
August 29, 2015 13:57
-
-
Save BtbN/9665914 to your computer and use it in GitHub Desktop.
pwmdev kernel driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <linux/module.h> | |
#include <linux/moduleparam.h> | |
#include <linux/kernel.h> | |
#include <linux/init.h> | |
#include <linux/pwm.h> | |
#include <linux/stat.h> | |
#include <linux/fs.h> | |
#include <linux/device.h> | |
#include <linux/cdev.h> | |
#include <linux/types.h> | |
#include <linux/slab.h> | |
#include <asm/uaccess.h> | |
#include "pwmdev.h" | |
#define PWMDEV_MAX_DEVICES 8 | |
static int pwmCount = 4; | |
static int pwmNumbers[PWMDEV_MAX_DEVICES] = { 0, 1, 2, 3 }; | |
static int initPeriod = 20000000; | |
static int initDuty = 1409000; | |
module_param(initPeriod, int, S_IRUGO); | |
MODULE_PARM_DESC(initPeriod, "Initial PWM period on module load in ns"); | |
module_param(initDuty, int, S_IRUGO); | |
MODULE_PARM_DESC(initDuty, "Initial PWM duty cycle on module load in ns"); | |
module_param_array(pwmNumbers, int, &pwmCount, S_IRUGO); | |
MODULE_PARM_DESC(pwmNumbers, "PWM numbers to create devices for"); | |
static dev_t firstDev = MKDEV(0, 0); | |
static struct class *cl = NULL; | |
static dev_t pwmDevDevices[PWMDEV_MAX_DEVICES] = { 0 }; | |
static struct cdev *c_dev = NULL; | |
static DEFINE_MUTEX(pwm_open_close_lock); | |
static int pwmOpenCount[PWMDEV_MAX_DEVICES] = { 0 }; | |
static struct pwm_device *pwmData[PWMDEV_MAX_DEVICES] = { 0 }; | |
static pwmdev_arg_t pwmStates[PWMDEV_MAX_DEVICES]; | |
typedef struct | |
{ | |
int num; | |
char readData[256]; | |
size_t readPos; | |
size_t readLen; | |
} pwmdev_pdata_t; | |
static int pwmdev_open(struct inode *i, struct file *f) | |
{ | |
int num = iminor(i) - MINOR(firstDev); | |
int status = 0; | |
pwmdev_pdata_t *pdata = 0; | |
if(num < 0 || num >= pwmCount) | |
return -ENXIO; | |
mutex_lock(&pwm_open_close_lock); | |
if(pwmData[num] == NULL) | |
{ | |
pwmData[num] = pwm_request(pwmNumbers[num], "pwmdev"); | |
if(IS_ERR(pwmData[num])) | |
{ | |
printk(KERN_INFO "pwmdev: Failed requesting pwm %d\n", pwmNumbers[num]); | |
status = PTR_ERR(pwmData[num]); | |
pwmData[num] = NULL; | |
} | |
} | |
if(status == 0) | |
{ | |
pwmOpenCount[num] += 1; | |
pdata = kmalloc(sizeof(pwmdev_pdata_t), GFP_KERNEL); | |
pdata->num = num; | |
pdata->readData[0] = 0; | |
pdata->readPos = 0; | |
pdata->readLen = 0; | |
f->private_data = pdata; | |
} | |
mutex_unlock(&pwm_open_close_lock); | |
return status; | |
} | |
static int pwmdev_close(struct inode *i, struct file *f) | |
{ | |
pwmdev_pdata_t *pdata = f->private_data; | |
int num = pdata->num; | |
int status = 0; | |
mutex_lock(&pwm_open_close_lock); | |
if(pwmOpenCount[num] == 0) | |
{ | |
status = -EINVAL; | |
} | |
else | |
{ | |
pwmOpenCount[num] -= 1; | |
if(pwmOpenCount[num] == 0) | |
{ | |
pwm_free(pwmData[num]); | |
pwmData[num] = NULL; | |
} | |
} | |
mutex_unlock(&pwm_open_close_lock); | |
kfree(pdata); | |
return status; | |
} | |
static long pwmdev_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | |
{ | |
pwmdev_arg_t p; | |
pwmdev_pdata_t *pdata = f->private_data; | |
int num = pdata->num; | |
switch(cmd) | |
{ | |
case PWMDEV_ENABLE: | |
if(pwm_enable(pwmData[num]) < 0) | |
{ | |
printk(KERN_INFO "Enabling pwm %d failed", num); | |
return -EINVAL; | |
} | |
mutex_lock(&pwm_open_close_lock); | |
pwmStates[num].reenable = 1; | |
mutex_unlock(&pwm_open_close_lock); | |
break; | |
case PWMDEV_DISABLE: | |
pwm_disable(pwmData[num]); | |
mutex_lock(&pwm_open_close_lock); | |
pwmStates[num].reenable = 0; | |
mutex_unlock(&pwm_open_close_lock); | |
break; | |
case PWMDEV_SET_CONFIG: | |
if(copy_from_user(&p, (pwmdev_arg_t*)arg, sizeof(pwmdev_arg_t))) | |
{ | |
printk(KERN_INFO "pwmdev: invalid ioctl arg"); | |
return -EACCES; | |
} | |
if(p.reenable != 0) | |
pwm_disable(pwmData[num]); | |
if(pwm_config(pwmData[num], p.duty_ns, p.period_ns) < 0) | |
{ | |
printk(KERN_INFO "pwmdev: Configuring pwm %d failed", num); | |
return -EINVAL; | |
} | |
if(p.reenable != 0) | |
{ | |
if(pwm_enable(pwmData[num]) < 0) | |
{ | |
printk(KERN_INFO "pwmdev: Re-Enabling pwm %d failed", num); | |
return -EINVAL; | |
} | |
} | |
mutex_lock(&pwm_open_close_lock); | |
if(p.reenable == 0) | |
p.reenable = pwmStates[num].reenable; | |
pwmStates[num] = p; | |
mutex_unlock(&pwm_open_close_lock); | |
break; | |
case PWMDEV_GET_CONFIG: | |
mutex_lock(&pwm_open_close_lock); | |
p = pwmStates[num]; | |
mutex_unlock(&pwm_open_close_lock); | |
if(copy_to_user((pwmdev_arg_t*)arg, &p, sizeof(pwmdev_arg_t))) | |
{ | |
printk(KERN_INFO "pwmdev: Invalid user data for get config"); | |
return -EACCES; | |
} | |
break; | |
default: | |
printk(KERN_INFO "pwmdev: Unknown ioctl requested"); | |
return -EINVAL; | |
} | |
return 0; | |
} | |
static ssize_t pwmdev_read(struct file *f, char __user *buf, size_t len, loff_t *off) | |
{ | |
pwmdev_pdata_t *pdata = f->private_data; | |
int num = pdata->num; | |
int period_ns, duty_ns; | |
ssize_t tmplen; | |
if(len == 0) | |
return 0; | |
if(pdata->readLen == 0) | |
{ | |
mutex_lock(&pwm_open_close_lock); | |
period_ns = pwmStates[num].period_ns; | |
duty_ns = pwmStates[num].duty_ns; | |
mutex_unlock(&pwm_open_close_lock); | |
if((tmplen = snprintf(pdata->readData, 256, "duty_ms: %d.%06d, period_ms: %d.%06d\n", duty_ns / 1000000, duty_ns % 1000000, period_ns / 1000000, period_ns % 1000000)) <= 0) | |
return -EFAULT; | |
pdata->readLen = tmplen; | |
pdata->readPos = 0; | |
} | |
if(len > pdata->readLen - pdata->readPos) | |
tmplen = pdata->readLen - pdata->readPos; | |
else | |
tmplen = len; | |
if(tmplen <= 0) | |
return 0; | |
len = tmplen; | |
if(copy_to_user(buf, pdata->readData + pdata->readPos, len) != 0) | |
return -EFAULT; | |
pdata->readPos += len; | |
return len; | |
} | |
static struct file_operations pwmdev_fops = | |
{ | |
.owner = THIS_MODULE, | |
.open = pwmdev_open, | |
.release = pwmdev_close, | |
.unlocked_ioctl = pwmdev_ioctl, | |
.read = pwmdev_read, | |
}; | |
static void pwmdev_cleanup(void) | |
{ | |
int i; | |
if(c_dev != NULL) | |
cdev_del(c_dev); | |
c_dev = NULL; | |
for(i = 0; i < pwmCount; ++i) | |
{ | |
if(MAJOR(pwmDevDevices[i]) || MINOR(pwmDevDevices[i])) | |
device_destroy(cl, pwmDevDevices[i]); | |
pwmDevDevices[i] = MKDEV(0, 0); | |
if(pwmData[i] != NULL) | |
{ | |
pwm_free(pwmData[i]); | |
pwmData[i] = NULL; | |
} | |
} | |
if(cl != NULL) | |
class_destroy(cl); | |
cl = NULL; | |
if(MAJOR(firstDev) || MINOR(firstDev)) | |
unregister_chrdev_region(firstDev, pwmCount); | |
firstDev = MKDEV(0, 0); | |
} | |
static int __init pwmdev_init(void) | |
{ | |
int i; | |
if(alloc_chrdev_region(&firstDev, 0, pwmCount, "pwmdev") < 0) | |
goto error; | |
cl = class_create(THIS_MODULE, "pwmdev"); | |
if(cl == NULL) | |
goto error; | |
for(i = 0; i < pwmCount; ++i) | |
{ | |
dev_t ndev = MKDEV(MAJOR(firstDev), MINOR(firstDev) + i); | |
if(device_create(cl, NULL, ndev, NULL, "pwmdev%d", i) == NULL) | |
goto error; | |
pwmDevDevices[i] = ndev; | |
pwmData[i] = NULL; | |
} | |
c_dev = cdev_alloc(); | |
if(c_dev == NULL) | |
goto error; | |
cdev_init(c_dev, &pwmdev_fops); | |
if(cdev_add(c_dev, firstDev, pwmCount) < 0) | |
goto error; | |
if(initPeriod <= 0 || initDuty <= 0) | |
return 0; | |
for(i = 0; i < pwmCount; ++i) | |
{ | |
pwmData[i] = pwm_request(pwmNumbers[i], "pwmdev"); | |
if(IS_ERR(pwmData[i])) | |
{ | |
pwmData[i] = NULL; | |
printk(KERN_INFO "pwmdev: Failed requesting pwm %d", pwmNumbers[i]); | |
goto error; | |
} | |
if(pwm_config(pwmData[i], initDuty, initPeriod) < 0) | |
{ | |
printk(KERN_INFO "pwmdev: Failed configuring pwm %d", pwmNumbers[i]); | |
goto error; | |
} | |
if(pwm_enable(pwmData[i]) < 0) | |
{ | |
printk(KERN_INFO "pwmdev: Failed enabling pwm %d", pwmNumbers[i]); | |
goto error; | |
} | |
pwmStates[i].reenable = 1; | |
pwmStates[i].period_ns = initPeriod; | |
pwmStates[i].duty_ns = initDuty; | |
printk(KERN_INFO "pwmdev: Initialized pwm %d with period=%dns and duty=%dns", pwmNumbers[i], initPeriod, initDuty); | |
} | |
return 0; | |
error: | |
pwmdev_cleanup(); | |
return -EINVAL; | |
} | |
static void __exit pwmdev_exit(void) | |
{ | |
pwmdev_cleanup(); | |
} | |
module_init(pwmdev_init); | |
module_exit(pwmdev_exit); | |
MODULE_LICENSE("GPL"); | |
MODULE_AUTHOR("Timo R."); | |
MODULE_DESCRIPTION("Userspace PWM management device"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef PWMDEV_IOCTL_H | |
#define PWMDEV_IOCTL_H | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
typedef struct | |
{ | |
int reenable; | |
int duty_ns; | |
int period_ns; | |
} pwmdev_arg_t; | |
#define PWMDEV_ENABLE _IO('p', 1) | |
#define PWMDEV_DISABLE _IO('p', 2) | |
#define PWMDEV_SET_CONFIG _IOW('p', 3, pwmdev_arg_t *) | |
#define PWMDEV_GET_CONFIG _IOR('p', 4, pwmdev_arg_t *) | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment