Skip to content

Instantly share code, notes, and snippets.

@BtbN
Last active August 29, 2015 13:57
Show Gist options
  • Save BtbN/9665914 to your computer and use it in GitHub Desktop.
Save BtbN/9665914 to your computer and use it in GitHub Desktop.
pwmdev kernel driver
#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");
#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