|
/*
|
|
* PWM driver for Rockchip SoCs
|
|
*/
|
|
|
|
//original include file
|
|
#include <linux/module.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
//#include <dt-bindings/pwm/pwm.h>
|
|
#include <linux/platform_device.h>
|
|
#include <asm/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/init.h>
|
|
|
|
//creat proc directory and file
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/kernel.h>
|
|
|
|
//print driver information
|
|
#define PWM_INFO(fmt,arg...) // printk("<<-PWM-INFO->> "fmt"\n",##arg)
|
|
|
|
static unsigned int gval = 0;
|
|
struct rp_pwm_data {
|
|
int pwm_id;
|
|
struct pwm_device *pwm;
|
|
unsigned int period;
|
|
unsigned int pwm_period_ns;
|
|
unsigned int max_period;
|
|
unsigned int min_period;
|
|
unsigned int duty_ns;
|
|
bool enabled;
|
|
};
|
|
struct rp_pwm_data g_rpdzkj_pdata;
|
|
|
|
static int pwm_open(struct inode *inode, struct file *file)
|
|
{
|
|
PWM_INFO("here is %s", __func__);
|
|
|
|
return 0;
|
|
}
|
|
static ssize_t pwm_write(struct file *file, const char *buffer,size_t count, loff_t *data)
|
|
{
|
|
char buf[7] = {0}; //maxmum is 10000, and the str include a single newline before its terminating null, so be sizeof(10000)+2(\n\0)
|
|
unsigned long state;
|
|
int tmp_duty,ret;
|
|
|
|
PWM_INFO("here is %s", __func__);
|
|
|
|
if(copy_from_user(buf,buffer,count)){
|
|
printk("failed to copy data to kernel space\n");
|
|
return -EFAULT;
|
|
}
|
|
PWM_INFO("buf is %s, size is %ld", buf, sizeof(buf));
|
|
|
|
ret = kstrtoul(buf, 10, &state);
|
|
if (ret){
|
|
PWM_INFO("str_to_ul_faild!");
|
|
return ret;
|
|
}
|
|
if (state > g_rpdzkj_pdata.max_period)
|
|
state = g_rpdzkj_pdata.max_period;
|
|
else if (state < g_rpdzkj_pdata.min_period)
|
|
state = g_rpdzkj_pdata.min_period;
|
|
tmp_duty = state;
|
|
|
|
g_rpdzkj_pdata.enabled = true;
|
|
pwm_config(g_rpdzkj_pdata.pwm, tmp_duty, g_rpdzkj_pdata.pwm_period_ns);
|
|
pwm_enable(g_rpdzkj_pdata.pwm);
|
|
gval = tmp_duty;
|
|
|
|
return count;
|
|
|
|
}
|
|
static ssize_t pwm_read(struct file *file, char __user * buffer, size_t count, loff_t *data)
|
|
{
|
|
PWM_INFO("here is %s", __func__);
|
|
|
|
PWM_INFO(":\n\
|
|
duty: %d\n\
|
|
period: %d\n", gval, g_rpdzkj_pdata.pwm_period_ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations pwm_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = pwm_open,
|
|
.write = pwm_write,
|
|
.read = pwm_read,
|
|
};
|
|
|
|
static int rp_pwm_status_update(struct rp_pwm_data *pdata)
|
|
{
|
|
if (pdata->enabled)
|
|
return 0;
|
|
|
|
pwm_enable(pdata->pwm);
|
|
pwm_config(pdata->pwm, pdata->duty_ns, g_rpdzkj_pdata.pwm_period_ns);
|
|
pdata->enabled = true;
|
|
gval = pdata->duty_ns;
|
|
return 0;
|
|
}
|
|
|
|
ssize_t rp_pwm_parse_dt(struct rp_pwm_data *rp_pdata, struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
const __be32 *id, *min_period, *max_period, *duty_ns;
|
|
int len;
|
|
|
|
id = of_get_property(np, "pwm_id", &len);
|
|
if (id)
|
|
rp_pdata->pwm_id = be32_to_cpu(*id);
|
|
|
|
min_period = of_get_property(np, "min_period", &len);
|
|
if (min_period)
|
|
rp_pdata->min_period = be32_to_cpu(*min_period);
|
|
|
|
max_period = of_get_property(np, "max_period", &len);
|
|
if (max_period)
|
|
rp_pdata->max_period = be32_to_cpu(*max_period);
|
|
|
|
rp_pdata->pwm_period_ns = rp_pdata->max_period - rp_pdata->min_period;
|
|
|
|
duty_ns = of_get_property(np, "duty_ns", &len);
|
|
if (duty_ns)
|
|
rp_pdata->duty_ns = be32_to_cpu(*duty_ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int rp_pwm_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct rp_pwm_data *rp_pdata = pdev->dev.platform_data;
|
|
int ret;
|
|
static struct proc_dir_entry *root_entry_pwm;
|
|
|
|
if (!np) {
|
|
dev_err(&pdev->dev, "Device Tree node missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rp_pdata = devm_kzalloc(&pdev->dev, sizeof(*rp_pdata), GFP_KERNEL);
|
|
if (!rp_pdata)
|
|
return -ENOMEM;
|
|
|
|
if (np)
|
|
ret = rp_pwm_parse_dt(rp_pdata, pdev);
|
|
|
|
rp_pdata->enabled = false;
|
|
|
|
rp_pdata->pwm = pwm_request(rp_pdata->pwm_id, "rp-pwm");
|
|
if (IS_ERR(rp_pdata->pwm)) {
|
|
dev_err(&pdev->dev, "unable to request legacy PWM\n");
|
|
ret = PTR_ERR(rp_pdata->pwm);
|
|
goto err;
|
|
}
|
|
|
|
if (rp_pdata->pwm_period_ns > 0)
|
|
pwm_set_period(rp_pdata->pwm, rp_pdata->pwm_period_ns);
|
|
rp_pdata->period = pwm_get_period(rp_pdata->pwm);
|
|
|
|
g_rpdzkj_pdata = *rp_pdata;
|
|
rp_pwm_status_update(rp_pdata);
|
|
dev_info(&pdev->dev, "%s: rp PWM Demo !\n", __func__);
|
|
|
|
|
|
/*creat folder for operat pwn Conveniently*/
|
|
root_entry_pwm = proc_mkdir("rp_pwm", NULL);
|
|
|
|
proc_create("rp_pwm1", 0666 , root_entry_pwm , &pwm_ops);
|
|
|
|
return 0;
|
|
err:
|
|
//pwm_free(rp_pdata->pwm);
|
|
devm_kfree(&pdev->dev, rp_pdata);
|
|
return ret;
|
|
}
|
|
|
|
static int rp_pwm_remove(struct platform_device *pdev)
|
|
{
|
|
struct rp_pwm_data *rp_pdata = pdev->dev.platform_data;
|
|
|
|
pwm_free(rp_pdata->pwm);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id rp_pwm_dt_ids[] = {
|
|
{ .compatible = "rp_pwm"},
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rp_pwm_dt_ids);
|
|
|
|
static struct platform_driver rp_pwm_driver = {
|
|
.driver = {
|
|
.name = "rp_pwm",
|
|
.of_match_table = rp_pwm_dt_ids,
|
|
},
|
|
.probe = rp_pwm_probe,
|
|
.remove = rp_pwm_remove,
|
|
};
|
|
|
|
module_platform_driver(rp_pwm_driver);
|
|
|
|
MODULE_AUTHOR("rpdzkj Jsomeone");
|
|
MODULE_DESCRIPTION("rpdzkj pwm control");
|
|
MODULE_LICENSE("GPL v2");
|