/*
* drivers/pwm/pwm-sunxi-dev.c
*
* Allwinnertech pulse-width-modulation controller driver
*
* Copyright (C) 2018 AllWinner
*
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/pinctrl/consumer.h>
#include <asm/io.h>
#include <linux/pwm.h>
static u32 debug_mask;
#define dprintk(level_mask, fmt, arg...) \
do { \
if (unlikely(debug_mask & level_mask)) \
pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg); \
} while (0)
#define IRTX_ERR(fmt, arg...) pr_err("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define PWM_IOCTL_BASE 'P'
#define PWM_CONFIG _IOW(PWM_IOCTL_BASE, 1 , struct pwm_config)
#define PWM_ENABLE _IOW(PWM_IOCTL_BASE, 2 , int)
#define PWM_DISABLE _IOW(PWM_IOCTL_BASE, 3 , int)
#define GROUP_PWM_CONFIG _IOW(PWM_IOCTL_BASE, 4, struct pwm_config)
struct pwm_config {
int pwm_id; //id,0对应pwm0,类推
int duty_ns; //占空比时间,单位ns
int period_ns; //周期时间,单位ns
int pwm_polarity; // 0表示 PWM_POLARITY_NORMAL, 1表示PWM_POLARITY_INVERSED
int group_channel; //组使用 0表示不使用,1表示使用第零组,2表示使用第一组
int group_run_count; //组脉冲数
};
/*
struct group_pwm_config {
int pwm_id; //id, 组中用到的一个pwm
int group_duty_ns; //组占空比
int group_period_ns; //组周期
int group_polarity; //组极性 0表示normal, 1表示inversed
int group_run_count; //组脉冲数
int group_channel; // 组使用 0表示不使用,1表示使用第零组,2表示使用第一组
};
*/
struct sunxi_pwm_group {
unsigned int group0_used;
unsigned int group0_mask;
unsigned int group1_used;
unsigned int group1_mask;
};
struct sunxi_pwm_chip {
struct pwm_chip chip;
void __iomem *base;
struct sunxi_pwm_config *config;
struct sunxi_pwm_group group;
#if defined(CLK_GATE_SUPPORT)
struct clk *pwm_clk;
#endif
unsigned int g_channel;
};
struct sunxi_pwm_dev {
struct device *dev;
struct cdev cdev;
dev_t chrdev;
};
static struct sunxi_pwm_dev *sunxi_pwm_dev;
static struct class *sunxi_pwm_class;
static int sunxi_pwm_open(struct inode *inode, struct file *filp)
{
filp->private_data = sunxi_pwm_dev;
return 0;
}
static int sunxi_pwm_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long sunxi_pwm_unlocked_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
unsigned int size;
struct pwm_config *code;
struct group_pwm_config *group_config;
int ret, i;
static struct pwm_device *pwm;
switch (cmd) {
case PWM_CONFIG:
size = _IOC_SIZE(cmd);
code = (struct pwm_config *)kzalloc(size, GFP_KERNEL);
if(IS_ERR_OR_NULL(code)){
IRTX_ERR("not enough memory\n");
return -ENOMEM;
}
if(copy_from_user(code, (void __user *)arg, size)){
IRTX_ERR("copy buffer err\n");
return -ENOMEM;
}
pwm = pwm_request(code->pwm_id, "sunxi_pwm0");
if(IS_ERR(pwm)){
IRTX_ERR("pwm err\n");
return -ENOMEM;
}else
IRTX_ERR("pwm success\n");
// pwm->chip_data = (struct pwm_config *)kzalloc(size, GFP_KERNEL);
pwm->chip_data = code;
ret = pwm_config(pwm, code->duty_ns, code->period_ns);
if(ret < 0){
IRTX_ERR("pwm ioctl err\n");
return -ENOMEM;
}
pwm_set_polarity(pwm,code->pwm_polarity);
kfree(code);
break;
case GROUP_PWM_CONFIG:
size = _IOC_SIZE(cmd);
// group_config = (struct group_pwm_config *)kzalloc(size, GFP_KERNEL);
code = (struct pwm_config *)kzalloc(size, GFP_KERNEL);
if(IS_ERR_OR_NULL(code)){
IRTX_ERR("not enough memory\n");
return -ENOMEM;
}
if(copy_from_user(code, (void __user *)arg, size)){
IRTX_ERR("copy buffer err\n");
return -ENOMEM;
}
pwm = pwm_request(0, "sunxi_group0"); //是否能重复request同一个id
if(IS_ERR(pwm)){
IRTX_ERR("pwm err\n");
return -ENOMEM;
}else
IRTX_ERR("pwm success\n");
// pwm->chip_data = (struct pwm_config *)kzalloc(size, GFP_KERNEL);
pwm->chip_data = code;
//struct pwm_chip *chip = to_sunxi_pwm_chip(pwm);
//struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip);
struct pwm_chip *chip = container_of(pwm, struct pwm_chip, pwms);
struct sunxi_pwm_chip *pc = container_of(chip, struct sunxi_pwm_chip, chip);
int group0_mask = pc->group.group0_mask;
for (i = 0; i < 10; i ++) {
if (group0_mask &= 1 << i) {
pwm = pwm_request(i, "sunxi_group0"); //是否能重复request同一个id
if(IS_ERR(pwm)){
IRTX_ERR("pwm err\n");
return -ENOMEM;
}else
IRTX_ERR("pwm success\n");
ret = pwm_config(pwm, code->duty_ns, code->period_ns);
if(ret < 0){
IRTX_ERR("pwm ioctl err\n");
return -ENOMEM;
}
}
}
pwm_set_polarity(pwm,code->pwm_polarity);
kfree(code);
break;
case PWM_ENABLE:
pwm_enable(pwm);
break;
case PWM_DISABLE:
pwm_disable(pwm);
break;
default:
IRTX_ERR("a err cmd");
return -ENOTTY;
}
return 0;
}
static long sunxi_pwm_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
unsigned long translated_arg = (unsigned long)compat_ptr(arg);
return sunxi_pwm_unlocked_ioctl(filp, cmd, translated_arg);
}
static const struct file_operations sunxi_pwm_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sunxi_pwm_unlocked_ioctl,
.compat_ioctl = sunxi_pwm_compat_ioctl,
.open = sunxi_pwm_open,
.release = sunxi_pwm_release,
};
static int __init sunxi_pwm_init(void)
{
int err = 0;
struct device *dev;
sunxi_pwm_dev= kzalloc(sizeof(struct sunxi_pwm_dev), GFP_KERNEL);
if (sunxi_pwm_dev == NULL) {
IRTX_ERR("kzalloc failed!\n");
return -ENOMEM;
}
err = alloc_chrdev_region(&sunxi_pwm_dev->chrdev, 0, 1, "sunxi-pwm-dev");
if (err) {
IRTX_ERR("alloc_chrdev_region failed!\n");
goto alloc_chrdev_err;
}
cdev_init(&(sunxi_pwm_dev->cdev), &sunxi_pwm_fops);
sunxi_pwm_dev->cdev.owner = THIS_MODULE;
err = cdev_add(&(sunxi_pwm_dev->cdev), sunxi_pwm_dev->chrdev, 1);
if (err) {
IRTX_ERR("cdev_add failed!\n");
goto cdev_add_err;
}
sunxi_pwm_class = class_create(THIS_MODULE, "sunxi_pwm_char_class");
if (IS_ERR(sunxi_pwm_class)) {
err = PTR_ERR(sunxi_pwm_class);
IRTX_ERR("class_create failed!\n");
goto class_err;
}
dev = device_create(sunxi_pwm_class, NULL, sunxi_pwm_dev->chrdev, NULL,
"sunxi_pwm%d", 0);
if (IS_ERR(dev)) {
err = PTR_ERR(dev);
IRTX_ERR("device_create failed!\n");
goto device_err;
}
return 0;
device_err:
device_destroy(sunxi_pwm_class, sunxi_pwm_dev->chrdev);
class_err:
cdev_del(&(sunxi_pwm_dev->cdev));
cdev_add_err:
unregister_chrdev_region(sunxi_pwm_dev->chrdev, 1);
alloc_chrdev_err:
kfree(sunxi_pwm_dev);
return err;
}
static void __exit sunxi_pwm_exit(void)
{
cdev_del(&(sunxi_pwm_dev->cdev));
unregister_chrdev_region(sunxi_pwm_dev->chrdev, 1);
device_destroy(sunxi_pwm_class, sunxi_pwm_dev->chrdev);
class_destroy(sunxi_pwm_class);
kfree(sunxi_pwm_dev);
}
module_init(sunxi_pwm_init);
module_exit(sunxi_pwm_exit);
MODULE_AUTHOR("Li huaxing");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SUNXI_PWM char");