mirror of
https://github.com/Fishwaldo/build.git
synced 2025-03-21 22:31:51 +00:00
1664 lines
53 KiB
Diff
1664 lines
53 KiB
Diff
|
From 2920d2cfb3fed734404e4ace8d43c95846c06e77 Mon Sep 17 00:00:00 2001
|
||
|
From: Damien Pageot <damien.pageot78@gmail.com>
|
||
|
Date: Thu, 31 Mar 2016 16:38:56 +0200
|
||
|
Subject: [PATCH 15/19] rewrite function bestprescale
|
||
|
|
||
|
recode best prescale function and add non prescale
|
||
|
---
|
||
|
drivers/misc/pwm-sunxi.c | 1564 +++++++++++++++++++++++-----------------------
|
||
|
1 file changed, 791 insertions(+), 773 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/misc/pwm-sunxi.c b/drivers/misc/pwm-sunxi.c
|
||
|
index 310798e..6f1e498 100644
|
||
|
--- a/drivers/misc/pwm-sunxi.c
|
||
|
+++ b/drivers/misc/pwm-sunxi.c
|
||
|
@@ -1,10 +1,10 @@
|
||
|
-/* pwm-sunxi.c
|
||
|
- *
|
||
|
- * pwm module for sun4i (and others) like cubieboard and pcduino
|
||
|
- *
|
||
|
- * (C) Copyright 2013
|
||
|
- * David H. Wilkins <dwil...@conecuh.com>
|
||
|
- *
|
||
|
+/* pwm-sunxi.c
|
||
|
+ *
|
||
|
+ * pwm module for sun4i (and others) like cubieboard and pcduino
|
||
|
+ *
|
||
|
+ * (C) Copyright 2013
|
||
|
+ * David H. Wilkins <dwil...@conecuh.com>
|
||
|
+ *
|
||
|
* CHANGELOG:
|
||
|
* 10.08.2013 - Stefan Voit <stefan.voit@voit-consulting.com>
|
||
|
* - Added script.bin support for [pwm0_para] and [pwm1_para]: pwm_used, pwm_period, pwm_duty_percent
|
||
|
@@ -16,130 +16,130 @@
|
||
|
* - Implement duty_percent=0 to set pwm line to 0 - right now it goes to 100%
|
||
|
* - Change the script_bin settings loader for pwm_period to allow text based values (100ms, 10hz,...)
|
||
|
* - Merge h & c file
|
||
|
- * -
|
||
|
+ * -
|
||
|
*
|
||
|
- * This program is free software; you can redistribute it and/or
|
||
|
- * modify it under the terms of the GNU General Public License as
|
||
|
- * published by the Free Software Foundation; either version 2 of
|
||
|
- * the License, or (at your option) any later version.
|
||
|
- *
|
||
|
- * This program is distributed in the hope that it will be useful,
|
||
|
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
- * GNU General Public License for more details.
|
||
|
- *
|
||
|
- * You should have received a copy of the GNU General Public License
|
||
|
- * along with this program; if not, write to the Free Software
|
||
|
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||
|
- * MA 02111-1307 USA
|
||
|
- */
|
||
|
-
|
||
|
-#include <linux/kobject.h>
|
||
|
-#include <linux/slab.h>
|
||
|
-#include <linux/device.h>
|
||
|
-#include <linux/sysfs.h>
|
||
|
-#include <linux/module.h>
|
||
|
-#include <linux/kernel.h>
|
||
|
-#include <linux/platform_device.h>
|
||
|
-#include <linux/err.h>
|
||
|
-#include <asm/io.h>
|
||
|
-#include <asm/delay.h>
|
||
|
-#include <mach/platform.h>
|
||
|
-#include <linux/pwm.h>
|
||
|
-#include <linux/ctype.h>
|
||
|
-#include <linux/limits.h>
|
||
|
-#include <linux/pwm.h>
|
||
|
-#include <linux/kdev_t.h>
|
||
|
+ * This program is free software; you can redistribute it and/or
|
||
|
+ * modify it under the terms of the GNU General Public License as
|
||
|
+ * published by the Free Software Foundation; either version 2 of
|
||
|
+ * the License, or (at your option) any later version.
|
||
|
+ *
|
||
|
+ * This program is distributed in the hope that it will be useful,
|
||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
+ * GNU General Public License for more details.
|
||
|
+ *
|
||
|
+ * You should have received a copy of the GNU General Public License
|
||
|
+ * along with this program; if not, write to the Free Software
|
||
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||
|
+ * MA 02111-1307 USA
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/kobject.h>
|
||
|
+#include <linux/slab.h>
|
||
|
+#include <linux/device.h>
|
||
|
+#include <linux/sysfs.h>
|
||
|
+#include <linux/module.h>
|
||
|
+#include <linux/kernel.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/err.h>
|
||
|
+#include <asm/io.h>
|
||
|
+#include <asm/delay.h>
|
||
|
+#include <mach/platform.h>
|
||
|
+#include <linux/pwm.h>
|
||
|
+#include <linux/ctype.h>
|
||
|
+#include <linux/limits.h>
|
||
|
+#include <linux/pwm.h>
|
||
|
+#include <linux/kdev_t.h>
|
||
|
#include <plat/system.h>
|
||
|
#include <plat/sys_config.h>
|
||
|
|
||
|
-#include "pwm-sunxi.h"
|
||
|
-/*
|
||
|
- * Forward Declarations
|
||
|
- */
|
||
|
+#include "pwm-sunxi.h"
|
||
|
+/*
|
||
|
+ * Forward Declarations
|
||
|
+ */
|
||
|
|
||
|
|
||
|
#define SUNXI_PWM_DEBUG
|
||
|
|
||
|
//comment to get debug messages
|
||
|
#undef SUNXI_PWM_DEBUG
|
||
|
-
|
||
|
-void release_pwm_sunxi(struct kobject *kobj);
|
||
|
-void pwm_setup_available_channels(void );
|
||
|
-ssize_t pwm_set_mode(unsigned int enable, struct sun4i_pwm_available_channel *chan);
|
||
|
-enum sun4i_pwm_prescale pwm_get_best_prescale(unsigned long long period);
|
||
|
-unsigned int get_entire_cycles(struct sun4i_pwm_available_channel *chan);
|
||
|
-unsigned int get_active_cycles(struct sun4i_pwm_available_channel *chan);
|
||
|
-unsigned long convert_string_to_microseconds(const char *buf);
|
||
|
-int pwm_set_period_and_duty(struct sun4i_pwm_available_channel *chan);
|
||
|
-void fixup_duty(struct sun4i_pwm_available_channel *chan);
|
||
|
-
|
||
|
-
|
||
|
-static DEFINE_MUTEX(sysfs_lock);
|
||
|
-static struct class pwm_class;
|
||
|
-
|
||
|
-void *PWM_CTRL_REG_BASE = NULL;
|
||
|
-
|
||
|
-
|
||
|
-static struct class_attribute pwm_class_attrs[] = {
|
||
|
- __ATTR_NULL
|
||
|
-};
|
||
|
-
|
||
|
-
|
||
|
-static struct class pwm_class = {
|
||
|
- .name = "pwm-sunxi",
|
||
|
- .owner = THIS_MODULE,
|
||
|
- .class_attrs = pwm_class_attrs,
|
||
|
-};
|
||
|
-
|
||
|
-
|
||
|
-/*
|
||
|
- * sysfs store / show functions
|
||
|
- */
|
||
|
-
|
||
|
-static ssize_t pwm_polarity_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||
|
-static ssize_t pwm_period_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||
|
-static ssize_t pwm_duty_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||
|
-static ssize_t pwm_run_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
-static ssize_t pwm_duty_percent_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
-static ssize_t pwm_pulse_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
-static ssize_t pwm_pin_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
-
|
||
|
-static ssize_t pwm_polarity_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
-static ssize_t pwm_period_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
-static ssize_t pwm_duty_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
-static ssize_t pwm_run_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
-static ssize_t pwm_duty_percent_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
-static ssize_t pwm_pulse_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
-
|
||
|
-static DEVICE_ATTR(polarity, 0644,pwm_polarity_show, pwm_polarity_store);
|
||
|
-static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store);
|
||
|
-static DEVICE_ATTR(duty, 0644,pwm_duty_show, pwm_duty_store);
|
||
|
-static DEVICE_ATTR(run, 0644, pwm_run_show, pwm_run_store);
|
||
|
-static DEVICE_ATTR(duty_percent, 0644, pwm_duty_percent_show, pwm_duty_percent_store);
|
||
|
-static DEVICE_ATTR(pulse, 0644, pwm_pulse_show, pwm_pulse_store);
|
||
|
-static DEVICE_ATTR(pin, 0644, pwm_pin_show, NULL);
|
||
|
-
|
||
|
-static const struct attribute *pwm_attrs[] = {
|
||
|
- &dev_attr_polarity.attr,
|
||
|
- &dev_attr_period.attr,
|
||
|
- &dev_attr_duty.attr,
|
||
|
- &dev_attr_run.attr,
|
||
|
- &dev_attr_duty_percent.attr,
|
||
|
- &dev_attr_pulse.attr,
|
||
|
- &dev_attr_pin.attr,
|
||
|
- NULL,
|
||
|
-};
|
||
|
-
|
||
|
-static const struct attribute_group pwm_attr_group = {
|
||
|
- .attrs = (struct attribute **) pwm_attrs
|
||
|
-};
|
||
|
-
|
||
|
-struct device *pwm0;
|
||
|
-struct device *pwm1;
|
||
|
-
|
||
|
-
|
||
|
-static struct sun4i_pwm_available_channel pwm_available_chan[SUN4I_MAX_HARDWARE_PWM_CHANNELS];
|
||
|
+
|
||
|
+void release_pwm_sunxi(struct kobject *kobj);
|
||
|
+void pwm_setup_available_channels(void );
|
||
|
+ssize_t pwm_set_mode(unsigned int enable, struct sun4i_pwm_available_channel *chan);
|
||
|
+enum sun4i_pwm_prescale pwm_get_best_prescale(unsigned long long period);
|
||
|
+unsigned int get_entire_cycles(struct sun4i_pwm_available_channel *chan);
|
||
|
+unsigned int get_active_cycles(struct sun4i_pwm_available_channel *chan);
|
||
|
+unsigned long convert_string_to_microseconds(const char *buf);
|
||
|
+int pwm_set_period_and_duty(struct sun4i_pwm_available_channel *chan);
|
||
|
+void fixup_duty(struct sun4i_pwm_available_channel *chan);
|
||
|
+
|
||
|
+
|
||
|
+static DEFINE_MUTEX(sysfs_lock);
|
||
|
+static struct class pwm_class;
|
||
|
+
|
||
|
+void *PWM_CTRL_REG_BASE = NULL;
|
||
|
+
|
||
|
+
|
||
|
+static struct class_attribute pwm_class_attrs[] = {
|
||
|
+ __ATTR_NULL
|
||
|
+};
|
||
|
+
|
||
|
+
|
||
|
+static struct class pwm_class = {
|
||
|
+ .name = "pwm-sunxi",
|
||
|
+ .owner = THIS_MODULE,
|
||
|
+ .class_attrs = pwm_class_attrs,
|
||
|
+};
|
||
|
+
|
||
|
+
|
||
|
+/*
|
||
|
+ * sysfs store / show functions
|
||
|
+ */
|
||
|
+
|
||
|
+static ssize_t pwm_polarity_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||
|
+static ssize_t pwm_period_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||
|
+static ssize_t pwm_duty_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||
|
+static ssize_t pwm_run_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
+static ssize_t pwm_duty_percent_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
+static ssize_t pwm_pulse_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
+static ssize_t pwm_pin_show(struct device *dev,struct device_attribute *attr, char *buf);
|
||
|
+
|
||
|
+static ssize_t pwm_polarity_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
+static ssize_t pwm_period_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
+static ssize_t pwm_duty_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
+static ssize_t pwm_run_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
+static ssize_t pwm_duty_percent_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
+static ssize_t pwm_pulse_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size);
|
||
|
+
|
||
|
+static DEVICE_ATTR(polarity, 0644,pwm_polarity_show, pwm_polarity_store);
|
||
|
+static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store);
|
||
|
+static DEVICE_ATTR(duty, 0644,pwm_duty_show, pwm_duty_store);
|
||
|
+static DEVICE_ATTR(run, 0644, pwm_run_show, pwm_run_store);
|
||
|
+static DEVICE_ATTR(duty_percent, 0644, pwm_duty_percent_show, pwm_duty_percent_store);
|
||
|
+static DEVICE_ATTR(pulse, 0644, pwm_pulse_show, pwm_pulse_store);
|
||
|
+static DEVICE_ATTR(pin, 0644, pwm_pin_show, NULL);
|
||
|
+
|
||
|
+static const struct attribute *pwm_attrs[] = {
|
||
|
+ &dev_attr_polarity.attr,
|
||
|
+ &dev_attr_period.attr,
|
||
|
+ &dev_attr_duty.attr,
|
||
|
+ &dev_attr_run.attr,
|
||
|
+ &dev_attr_duty_percent.attr,
|
||
|
+ &dev_attr_pulse.attr,
|
||
|
+ &dev_attr_pin.attr,
|
||
|
+ NULL,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct attribute_group pwm_attr_group = {
|
||
|
+ .attrs = (struct attribute **) pwm_attrs
|
||
|
+};
|
||
|
+
|
||
|
+struct device *pwm0;
|
||
|
+struct device *pwm1;
|
||
|
+
|
||
|
+
|
||
|
+static struct sun4i_pwm_available_channel pwm_available_chan[SUN4I_MAX_HARDWARE_PWM_CHANNELS];
|
||
|
static int sunxi_pwm_class_registered = 0;
|
||
|
|
||
|
static void __init sunxi_pwm_register_class(void)
|
||
|
@@ -154,18 +154,18 @@ static void __init sunxi_pwm_register_class(void)
|
||
|
sunxi_pwm_class_registered = 1;
|
||
|
}
|
||
|
|
||
|
-static int __init sunxi_pwm_init(void)
|
||
|
-{
|
||
|
+static int __init sunxi_pwm_init(void)
|
||
|
+{
|
||
|
int init_enable, init_duty_percent, init_period;
|
||
|
struct sun4i_pwm_available_channel *chan;
|
||
|
int err = 0;
|
||
|
|
||
|
- pwm_setup_available_channels();
|
||
|
+ pwm_setup_available_channels();
|
||
|
|
||
|
//PWM 0
|
||
|
//printk("pwm-sunxi: configuring pwm0...\n");
|
||
|
chan = &pwm_available_chan[0];
|
||
|
-
|
||
|
+
|
||
|
init_enable=0;
|
||
|
init_period=0;
|
||
|
init_duty_percent=100;
|
||
|
@@ -193,13 +193,13 @@ static int __init sunxi_pwm_init(void)
|
||
|
}
|
||
|
|
||
|
chan->duty_percent=init_duty_percent;
|
||
|
- chan->period = init_period;
|
||
|
- chan->prescale = pwm_get_best_prescale(init_period);
|
||
|
- fixup_duty(chan);
|
||
|
+ chan->period = init_period;
|
||
|
+ chan->prescale = pwm_get_best_prescale(init_period);
|
||
|
+ fixup_duty(chan);
|
||
|
#ifdef SUNXI_PWM_DEBUG
|
||
|
printk("pwm-sunxi: pwm0 set initial values\n");
|
||
|
#endif
|
||
|
- pwm_set_mode(init_enable,chan);
|
||
|
+ pwm_set_mode(init_enable,chan);
|
||
|
printk("pwm-sunxi: pwm0 configured - period: %ld, duty_percent: %d, duty: %ld\n",
|
||
|
chan->period, chan->duty_percent, chan->duty);
|
||
|
}
|
||
|
@@ -210,7 +210,7 @@ static int __init sunxi_pwm_init(void)
|
||
|
//PWM 1
|
||
|
//printk("pwm-sunxi: configuring pwm1...\n");
|
||
|
chan = &pwm_available_chan[1];
|
||
|
-
|
||
|
+
|
||
|
init_enable=0;
|
||
|
init_period=0;
|
||
|
init_duty_percent=100;
|
||
|
@@ -238,23 +238,23 @@ static int __init sunxi_pwm_init(void)
|
||
|
}
|
||
|
|
||
|
chan->duty_percent=init_duty_percent;
|
||
|
- chan->period = init_period;
|
||
|
- chan->prescale = pwm_get_best_prescale(init_period);
|
||
|
- fixup_duty(chan);
|
||
|
+ chan->period = init_period;
|
||
|
+ chan->prescale = pwm_get_best_prescale(init_period);
|
||
|
+ fixup_duty(chan);
|
||
|
#ifdef SUNXI_PWM_DEBUG
|
||
|
printk("pwm-sunxi: pwm0 set initial values\n");
|
||
|
#endif
|
||
|
- pwm_set_mode(init_enable,chan);
|
||
|
+ pwm_set_mode(init_enable,chan);
|
||
|
printk("pwm-sunxi: pwm1 configured - period: %ld, duty_percent: %d, duty: %ld\n",
|
||
|
chan->period, chan->duty_percent, chan->duty);
|
||
|
}
|
||
|
return 0;
|
||
|
-}
|
||
|
+}
|
||
|
|
||
|
-void sunxi_pwm_exit(void)
|
||
|
-{
|
||
|
- void *timer_base = ioremap(SW_PA_TIMERC_IO_BASE, 0x400);
|
||
|
- void *PWM_CTRL_REG_BASE = timer_base + 0x200;
|
||
|
+void sunxi_pwm_exit(void)
|
||
|
+{
|
||
|
+ void *timer_base = ioremap(SW_PA_TIMERC_IO_BASE, 0x400);
|
||
|
+ void *PWM_CTRL_REG_BASE = timer_base + 0x200;
|
||
|
|
||
|
if (pwm0) {
|
||
|
device_destroy(&pwm_class, pwm0->devt);
|
||
|
@@ -268,647 +268,665 @@ void sunxi_pwm_exit(void)
|
||
|
}
|
||
|
|
||
|
if (sunxi_pwm_class_registered) {
|
||
|
- writel(0, PWM_CTRL_REG_BASE + 0);
|
||
|
+ writel(0, PWM_CTRL_REG_BASE + 0);
|
||
|
class_unregister(&pwm_class);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-/*
|
||
|
- * Functions to display the pwm variables currently set
|
||
|
- */
|
||
|
-
|
||
|
-static ssize_t pwm_polarity_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status;
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- status = scnprintf(buf,PAGE_SIZE,"%d",chan->ctrl_current.s.ch0_act_state);
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- status = scnprintf(buf,PAGE_SIZE,"%d",chan->ctrl_current.s.ch1_act_state);
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- return status;
|
||
|
-}
|
||
|
-static ssize_t pwm_period_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status;
|
||
|
- status = sprintf(buf,"%lu",chan->period);
|
||
|
- return status;
|
||
|
-}
|
||
|
-static ssize_t pwm_duty_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status;
|
||
|
- status = sprintf(buf,"%lu",chan->duty);
|
||
|
- return status;
|
||
|
-}
|
||
|
-static ssize_t pwm_run_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status;
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- status = sprintf(buf,"%d",chan->ctrl_current.s.ch0_en);
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- status = sprintf(buf,"%d",chan->ctrl_current.s.ch1_en);
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
-
|
||
|
- return status;
|
||
|
-}
|
||
|
-static ssize_t pwm_duty_percent_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- return sprintf(buf,"%u",chan->duty_percent);
|
||
|
-}
|
||
|
-static ssize_t pwm_pulse_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status;
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- status = sprintf(buf,"%d",chan->ctrl_current.s.ch0_pulse_start);
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- status = sprintf(buf,"%d",chan->ctrl_current.s.ch1_pulse_start);
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- return status;
|
||
|
-}
|
||
|
-
|
||
|
-static ssize_t pwm_pin_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
- const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status;
|
||
|
- status = sprintf(buf,"%s",chan->pin_name);
|
||
|
-
|
||
|
- return status;
|
||
|
-}
|
||
|
-
|
||
|
-/*
|
||
|
- * Functions to store values for pwm
|
||
|
- */
|
||
|
-
|
||
|
-static ssize_t pwm_polarity_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
- struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status = -EINVAL;
|
||
|
- int act_state = 0;
|
||
|
-
|
||
|
- sscanf(buf,"%d",&act_state);
|
||
|
- if(act_state < 2) {
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- chan->ctrl_current.s.ch0_act_state = act_state;
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- chan->ctrl_current.s.ch1_act_state = act_state;
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- status = size;
|
||
|
- }
|
||
|
- return status;
|
||
|
-}
|
||
|
-static ssize_t pwm_period_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
- unsigned long long period = 0;
|
||
|
- struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
-
|
||
|
- period = convert_string_to_microseconds(buf);
|
||
|
- if(!period || period > ULONG_MAX) {
|
||
|
- size = -EINVAL;
|
||
|
- } else {
|
||
|
- if(period <= chan->duty) {
|
||
|
- chan->duty = period;
|
||
|
- }
|
||
|
- chan->period = period;
|
||
|
- chan->prescale = pwm_get_best_prescale(period);
|
||
|
- fixup_duty(chan);
|
||
|
- if(chan->duty) {
|
||
|
- pwm_set_mode(NO_ENABLE_CHANGE,chan);
|
||
|
- }
|
||
|
- }
|
||
|
- return size;
|
||
|
-}
|
||
|
-static ssize_t pwm_duty_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
- unsigned long long duty = 0;
|
||
|
- struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
-
|
||
|
- /* sscanf(buf,"%Lu",&duty); */ /* L means long long pointer */
|
||
|
- duty = convert_string_to_microseconds(buf);
|
||
|
- duty = duty > ULONG_MAX ? ULONG_MAX : duty;
|
||
|
- duty = duty > chan->period ? chan->period : duty;
|
||
|
- chan->duty_percent = -1; /* disable duty_percent if duty is set by hand */
|
||
|
- chan->duty = duty;
|
||
|
- pwm_set_mode(NO_ENABLE_CHANGE,chan);
|
||
|
- return size;
|
||
|
-}
|
||
|
-
|
||
|
-struct time_suffix suffixes[] = {
|
||
|
- [0] = { .suffix = "hz", .multiplier = 1, .freq = true }, /* f = 1/T */
|
||
|
- [1] = { .suffix = "khz", .multiplier = 1000, .freq = true },
|
||
|
- [2] = { .suffix = "mhz", .multiplier = 1000000, .freq = true },
|
||
|
- [3] = { .suffix = "ghz", .multiplier = 1000000000, .freq = true },
|
||
|
- [4] = { .suffix = "ms", .multiplier = 1000, .freq = false }, /* T = 1/f */
|
||
|
- [5] = { .suffix = "us", .multiplier = 1, .freq = false },
|
||
|
- [6] = { .suffix = "ns", .multiplier = 1, .freq = false },
|
||
|
- [7] = { .suffix = NULL, .multiplier = 0, .freq = false },
|
||
|
-};
|
||
|
-
|
||
|
-
|
||
|
-unsigned long convert_string_to_microseconds(const char *buf) {
|
||
|
- unsigned char ch = 0;
|
||
|
- char numbers[10];
|
||
|
- char letters[4];
|
||
|
- const char *bufptr = buf;
|
||
|
- int i = 0;
|
||
|
- unsigned long microseconds = 0;
|
||
|
- unsigned long numeric_part = 0;
|
||
|
- int found_suffix = -1;
|
||
|
- int numbers_index = 0, letters_index = 0;
|
||
|
- while(bufptr && *bufptr && (ch = *bufptr) && isdigit(ch) && numbers_index < (sizeof(numbers)-1)) {
|
||
|
- numbers[numbers_index++] = *bufptr++;
|
||
|
- }
|
||
|
- numbers[numbers_index] = 0;
|
||
|
- while(bufptr && *bufptr && (ch = *bufptr) && strchr("usmhznhzkg",tolower(ch)) && letters_index < (sizeof(letters)-1)) {
|
||
|
- letters[letters_index++] = tolower(*bufptr);
|
||
|
- bufptr++;
|
||
|
- }
|
||
|
- letters[letters_index] = 0;
|
||
|
- sscanf(numbers,"%lu",&numeric_part);
|
||
|
- while(suffixes[i].suffix) {
|
||
|
- if(!strcmp(suffixes[i].suffix,letters)) {
|
||
|
- found_suffix = i;
|
||
|
- break;
|
||
|
- }
|
||
|
- i++;
|
||
|
- }
|
||
|
- if(found_suffix > -1) {
|
||
|
- if(suffixes[found_suffix].freq) {
|
||
|
- microseconds = 1000000 / (numeric_part * suffixes[found_suffix].multiplier);
|
||
|
- } else {
|
||
|
- microseconds = suffixes[found_suffix].multiplier * numeric_part;
|
||
|
- }
|
||
|
- }
|
||
|
- return microseconds;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-
|
||
|
-
|
||
|
-static const unsigned int prescale_divisor[13] = {120,
|
||
|
- 180,
|
||
|
- 240,
|
||
|
- 360,
|
||
|
- 480,
|
||
|
- 480, /* Invalid Option */
|
||
|
- 480, /* Invalid Option */
|
||
|
- 480, /* Invalid Option */
|
||
|
- 12000,
|
||
|
- 24000,
|
||
|
- 36000,
|
||
|
- 48000,
|
||
|
- 72000};
|
||
|
-
|
||
|
-/*
|
||
|
- * Find the best prescale value for the period
|
||
|
- * We want to get the highest period cycle count possible, so we look
|
||
|
- * make a run through the prescale values looking for numbers over
|
||
|
- * min_optimal_period_cycles. If none are found then root though again
|
||
|
- * taking anything that works
|
||
|
- */
|
||
|
-enum sun4i_pwm_prescale pwm_get_best_prescale(unsigned long long period_in) {
|
||
|
- int i;
|
||
|
- unsigned long period = period_in;
|
||
|
- const unsigned long min_optimal_period_cycles = MAX_CYCLES / 2;
|
||
|
- const unsigned long min_period_cycles = 0x02;
|
||
|
- enum sun4i_pwm_prescale best_prescale = 0;
|
||
|
-
|
||
|
- best_prescale = -1;
|
||
|
- for(i = 0 ; i < 13 ; i++) {
|
||
|
- unsigned long int check_value = (prescale_divisor[i] /24);
|
||
|
- if(check_value < 1 || check_value > period) {
|
||
|
- break;
|
||
|
- }
|
||
|
- if(((period / check_value) >= min_optimal_period_cycles) &&
|
||
|
- ((period / check_value) <= MAX_CYCLES)) {
|
||
|
- best_prescale = i;
|
||
|
- break;
|
||
|
- }
|
||
|
- }
|
||
|
-
|
||
|
- if(best_prescale > 13) {
|
||
|
- for(i = 0 ; i < 13 ; i++) {
|
||
|
- unsigned long int check_value = (prescale_divisor[i] /24);
|
||
|
- if(check_value < 1 || check_value > period) {
|
||
|
- break;
|
||
|
- }
|
||
|
- if(((period / check_value) >= min_period_cycles) &&
|
||
|
- ((period / check_value) <= MAX_CYCLES)) {
|
||
|
- best_prescale = i;
|
||
|
- break;
|
||
|
- }
|
||
|
- }
|
||
|
- }
|
||
|
- if(best_prescale > 13) {
|
||
|
- best_prescale = PRESCALE_DIV480; /* Something that's not zero - use invalid prescale value */
|
||
|
- }
|
||
|
-
|
||
|
- return best_prescale;
|
||
|
-}
|
||
|
-
|
||
|
-/*
|
||
|
- * return the number of cycles for the channel period computed from the microseconds
|
||
|
- * for the period. Allwinner docs call this "entire" cycles
|
||
|
- */
|
||
|
-unsigned int get_entire_cycles(struct sun4i_pwm_available_channel *chan) {
|
||
|
- unsigned int entire_cycles = 0x01;
|
||
|
- if ((2 * prescale_divisor[chan->prescale] * MAX_CYCLES) > 0) {
|
||
|
- entire_cycles = chan->period / (prescale_divisor[chan->prescale] /24);
|
||
|
- }
|
||
|
- if(entire_cycles == 0) {entire_cycles = MAX_CYCLES;}
|
||
|
- if(entire_cycles > MAX_CYCLES) {entire_cycles = MAX_CYCLES;}
|
||
|
+/*
|
||
|
+ * Functions to display the pwm variables currently set
|
||
|
+ */
|
||
|
+
|
||
|
+static ssize_t pwm_polarity_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status;
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ status = scnprintf(buf,PAGE_SIZE,"%d",chan->ctrl_current.s.ch0_act_state);
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ status = scnprintf(buf,PAGE_SIZE,"%d",chan->ctrl_current.s.ch1_act_state);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ return status;
|
||
|
+}
|
||
|
+static ssize_t pwm_period_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status;
|
||
|
+ status = sprintf(buf,"%lu",chan->period);
|
||
|
+ return status;
|
||
|
+}
|
||
|
+static ssize_t pwm_duty_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status;
|
||
|
+ status = sprintf(buf,"%lu",chan->duty);
|
||
|
+ return status;
|
||
|
+}
|
||
|
+static ssize_t pwm_run_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status;
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ status = sprintf(buf,"%d",chan->ctrl_current.s.ch0_en);
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ status = sprintf(buf,"%d",chan->ctrl_current.s.ch1_en);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ return status;
|
||
|
+}
|
||
|
+static ssize_t pwm_duty_percent_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ return sprintf(buf,"%u",chan->duty_percent);
|
||
|
+}
|
||
|
+static ssize_t pwm_pulse_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status;
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ status = sprintf(buf,"%d",chan->ctrl_current.s.ch0_pulse_start);
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ status = sprintf(buf,"%d",chan->ctrl_current.s.ch1_pulse_start);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+static ssize_t pwm_pin_show(struct device *dev,struct device_attribute *attr, char *buf) {
|
||
|
+ const struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status;
|
||
|
+ status = sprintf(buf,"%s",chan->pin_name);
|
||
|
+
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Functions to store values for pwm
|
||
|
+ */
|
||
|
+
|
||
|
+static ssize_t pwm_polarity_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
+ struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status = -EINVAL;
|
||
|
+ int act_state = 0;
|
||
|
+
|
||
|
+ sscanf(buf,"%d",&act_state);
|
||
|
+ if(act_state < 2) {
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ chan->ctrl_current.s.ch0_act_state = act_state;
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ chan->ctrl_current.s.ch1_act_state = act_state;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ status = size;
|
||
|
+ }
|
||
|
+ return status;
|
||
|
+}
|
||
|
+static ssize_t pwm_period_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
+ unsigned long long period = 0;
|
||
|
+ struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ period = convert_string_to_microseconds(buf);
|
||
|
+ if(!period || period > ULONG_MAX) {
|
||
|
+ size = -EINVAL;
|
||
|
+ } else {
|
||
|
+ if(period <= chan->duty) {
|
||
|
+ chan->duty = period;
|
||
|
+ }
|
||
|
+ chan->period = period;
|
||
|
+ chan->prescale = pwm_get_best_prescale(period);
|
||
|
+ fixup_duty(chan);
|
||
|
+ if(chan->duty) {
|
||
|
+ pwm_set_mode(NO_ENABLE_CHANGE,chan);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return size;
|
||
|
+}
|
||
|
+static ssize_t pwm_duty_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
+ unsigned long long duty = 0;
|
||
|
+ struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ /* sscanf(buf,"%Lu",&duty); */ /* L means long long pointer */
|
||
|
+ duty = convert_string_to_microseconds(buf);
|
||
|
+ duty = duty > ULONG_MAX ? ULONG_MAX : duty;
|
||
|
+ duty = duty > chan->period ? chan->period : duty;
|
||
|
+ chan->duty_percent = -1; /* disable duty_percent if duty is set by hand */
|
||
|
+ chan->duty = duty;
|
||
|
+ pwm_set_mode(NO_ENABLE_CHANGE,chan);
|
||
|
+ return size;
|
||
|
+}
|
||
|
+
|
||
|
+struct time_suffix suffixes[] = {
|
||
|
+ [0] = { .suffix = "hz", .multiplier = 1, .freq = true }, /* f = 1/T */
|
||
|
+ [1] = { .suffix = "khz", .multiplier = 1000, .freq = true },
|
||
|
+ [2] = { .suffix = "mhz", .multiplier = 1000000, .freq = true },
|
||
|
+ [3] = { .suffix = "ghz", .multiplier = 1000000000, .freq = true },
|
||
|
+ [4] = { .suffix = "ms", .multiplier = 1000, .freq = false }, /* T = 1/f */
|
||
|
+ [5] = { .suffix = "us", .multiplier = 1, .freq = false },
|
||
|
+ [6] = { .suffix = "ns", .multiplier = 1, .freq = false },
|
||
|
+ [7] = { .suffix = NULL, .multiplier = 0, .freq = false },
|
||
|
+};
|
||
|
+
|
||
|
+
|
||
|
+unsigned long convert_string_to_microseconds(const char *buf) {
|
||
|
+ unsigned char ch = 0;
|
||
|
+ char numbers[10];
|
||
|
+ char letters[4];
|
||
|
+ const char *bufptr = buf;
|
||
|
+ int i = 0;
|
||
|
+ unsigned long microseconds = 0;
|
||
|
+ unsigned long numeric_part = 0;
|
||
|
+ int found_suffix = -1;
|
||
|
+ int numbers_index = 0, letters_index = 0;
|
||
|
+ while(bufptr && *bufptr && (ch = *bufptr) && isdigit(ch) && numbers_index < (sizeof(numbers)-1)) {
|
||
|
+ numbers[numbers_index++] = *bufptr++;
|
||
|
+ }
|
||
|
+ numbers[numbers_index] = 0;
|
||
|
+ while(bufptr && *bufptr && (ch = *bufptr) && strchr("usmhznhzkg",tolower(ch)) && letters_index < (sizeof(letters)-1)) {
|
||
|
+ letters[letters_index++] = tolower(*bufptr);
|
||
|
+ bufptr++;
|
||
|
+ }
|
||
|
+ letters[letters_index] = 0;
|
||
|
+ sscanf(numbers,"%lu",&numeric_part);
|
||
|
+ while(suffixes[i].suffix) {
|
||
|
+ if(!strcmp(suffixes[i].suffix,letters)) {
|
||
|
+ found_suffix = i;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ i++;
|
||
|
+ }
|
||
|
+ if(found_suffix > -1) {
|
||
|
+ if(suffixes[found_suffix].freq) {
|
||
|
+ microseconds = 1000000 / (numeric_part * suffixes[found_suffix].multiplier);
|
||
|
+ } else {
|
||
|
+ microseconds = suffixes[found_suffix].multiplier * numeric_part;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return microseconds;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+
|
||
|
+
|
||
|
+static const unsigned int prescale_divisor[13] = {120,
|
||
|
+ 180,
|
||
|
+ 240,
|
||
|
+ 360,
|
||
|
+ 480,
|
||
|
+ 480, /* Invalid Option */
|
||
|
+ 480, /* Invalid Option */
|
||
|
+ 480, /* Invalid Option */
|
||
|
+ 12000,
|
||
|
+ 24000,
|
||
|
+ 36000,
|
||
|
+ 48000,
|
||
|
+ 72000};
|
||
|
+
|
||
|
+/*
|
||
|
+ * Find the best prescale value for the period
|
||
|
+ * We want to get the highest period cycle count possible, so we look
|
||
|
+ * make a run through the prescale values looking for numbers over
|
||
|
+ * min_optimal_period_cycles. If none are found then root though again
|
||
|
+ * taking anything that works
|
||
|
+ */
|
||
|
+enum sun4i_pwm_prescale pwm_get_best_prescale(unsigned long long period_in_us) {
|
||
|
+ int i;
|
||
|
+ unsigned long period_ticks_ns = 42; /* non exact value this introduce 0.8% per clock of error */
|
||
|
+ unsigned long period_ns = period_in_us *1000;
|
||
|
+ enum sun4i_pwm_prescale best_prescale = PRESCALE_DIV1;
|
||
|
+/* with this code the lowest prescaler is always selected */
|
||
|
+ if (period_in_us!= 0){
|
||
|
+
|
||
|
+ if (period_ns < MAX_CYCLES * period_ticks_ns) /* ~2.7ms */
|
||
|
+ goto finded;
|
||
|
+
|
||
|
+ if (period_ns < MAX_CYCLES * 120 * period_ticks_ns){ /*~327ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV120;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_ns < MAX_CYCLES * 180 * period_ticks_ns){ /*~491ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV180;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_ns < MAX_CYCLES * 240 * period_ticks_ns){ /*~655ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV240;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_ns < MAX_CYCLES * 360 * period_ticks_ns){ /*~983ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV360;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_ns < MAX_CYCLES * 480 * period_ticks_ns){ /*~1310ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV480;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_in_us < MAX_CYCLES * 12 * period_ticks_ns){ /*~32767.5ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV12k;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (period_in_us < MAX_CYCLES * 24 * period_ticks_ns){ /*~65535ms*/
|
||
|
+ best_prescale = PRESCALE_DIV24k;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_in_us < MAX_CYCLES * 36 * period_ticks_ns){ /*~98302ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV36k;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_in_us < MAX_CYCLES * 48 * period_ticks_ns){ /*~130070ms~*/
|
||
|
+ best_prescale = PRESCALE_DIV48k;
|
||
|
+ goto finded;
|
||
|
+ }
|
||
|
+ if (period_in_us < MAX_CYCLES * 72 * period_ticks_ns){ /*max absolute error ~~1.5ms */
|
||
|
+ best_prescale = PRESCALE_DIV72k;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else
|
||
|
+ best_prescale = PRESCALE_DIV480; /*default value when non found or input is 0*/
|
||
|
+ finded:
|
||
|
+ return best_prescale;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * return the number of cycles for the channel period computed from the microseconds
|
||
|
+ * for the period. Allwinner docs call this "entire" cycles
|
||
|
+ */
|
||
|
+unsigned int get_entire_cycles(struct sun4i_pwm_available_channel *chan) {
|
||
|
+ unsigned int entire_cycles = 0x01;
|
||
|
+ if ((2 * prescale_divisor[chan->prescale] * MAX_CYCLES) > 0) {
|
||
|
+ entire_cycles = chan->period / (prescale_divisor[chan->prescale] /24);
|
||
|
+ }
|
||
|
+ if(entire_cycles == 0) {entire_cycles = MAX_CYCLES;}
|
||
|
+ if(entire_cycles > MAX_CYCLES) {entire_cycles = MAX_CYCLES;}
|
||
|
#ifdef SUNXI_PWM_DEBUG
|
||
|
- printk("Best prescale was %d, entire cycles was %u\n",chan->prescale, entire_cycles);
|
||
|
+ printk("Best prescale was %d, entire cycles was %u\n",chan->prescale, entire_cycles);
|
||
|
#endif
|
||
|
-
|
||
|
- return entire_cycles;
|
||
|
-}
|
||
|
-
|
||
|
-/*
|
||
|
- * return the number of cycles for the channel duty computed from the microseconds
|
||
|
- * for the duty. Allwinner docs call this "active" cycles
|
||
|
- */
|
||
|
-unsigned int get_active_cycles(struct sun4i_pwm_available_channel *chan) {
|
||
|
- unsigned int active_cycles = 0x01;
|
||
|
- unsigned int entire_cycles = get_entire_cycles(chan);
|
||
|
- if(chan->duty < 0 && chan->period) {
|
||
|
- active_cycles = entire_cycles-1;
|
||
|
- } else if ((2 * prescale_divisor[chan->prescale] * MAX_CYCLES) > 0) {
|
||
|
- active_cycles = chan->duty / (prescale_divisor[chan->prescale] /24);
|
||
|
- }
|
||
|
-/* if(active_cycles == 0) {active_cycles = 0x0ff;} */
|
||
|
+
|
||
|
+ return entire_cycles;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * return the number of cycles for the channel duty computed from the microseconds
|
||
|
+ * for the duty. Allwinner docs call this "active" cycles
|
||
|
+ */
|
||
|
+unsigned int get_active_cycles(struct sun4i_pwm_available_channel *chan) {
|
||
|
+ unsigned int active_cycles = 0x01;
|
||
|
+ unsigned int entire_cycles = get_entire_cycles(chan);
|
||
|
+ if(chan->duty < 0 && chan->period) {
|
||
|
+ active_cycles = entire_cycles-1;
|
||
|
+ } else if ((2 * prescale_divisor[chan->prescale] * MAX_CYCLES) > 0) {
|
||
|
+ active_cycles = chan->duty / (prescale_divisor[chan->prescale] /24);
|
||
|
+ }
|
||
|
+/* if(active_cycles == 0) {active_cycles = 0x0ff;} */
|
||
|
#ifdef SUNXI_PWM_DEBUG
|
||
|
- printk("Best prescale was %d, active cycles was %u (before entire check)\n",chan->prescale, active_cycles);
|
||
|
+ printk("Best prescale was %d, active cycles was %u (before entire check)\n",chan->prescale, active_cycles);
|
||
|
#endif
|
||
|
- if(active_cycles > MAX_CYCLES) {active_cycles = entire_cycles-1;}
|
||
|
+ if(active_cycles > MAX_CYCLES) {active_cycles = entire_cycles-1;}
|
||
|
#ifdef SUNXI_PWM_DEBUG
|
||
|
- printk("Best prescale was %d, active cycles was %u (after entire check)\n",chan->prescale, active_cycles);
|
||
|
+ printk("Best prescale was %d, active cycles was %u (after entire check)\n",chan->prescale, active_cycles);
|
||
|
#endif
|
||
|
- return active_cycles;
|
||
|
-}
|
||
|
-
|
||
|
-/*
|
||
|
- * When the duty is set, compute the number of microseconds
|
||
|
- * based on the period.
|
||
|
- */
|
||
|
-
|
||
|
-void fixup_duty(struct sun4i_pwm_available_channel *chan) {
|
||
|
- if(chan->duty_percent >= 0) {
|
||
|
- chan->duty = chan->period * chan->duty_percent / 100;
|
||
|
- }
|
||
|
-}
|
||
|
-
|
||
|
-/*
|
||
|
- * Stores the run (enable) bit.
|
||
|
- */
|
||
|
-
|
||
|
-static ssize_t pwm_run_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
- struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status = -EINVAL;
|
||
|
- int enable = 0;
|
||
|
-
|
||
|
- sscanf(buf,"%d",&enable);
|
||
|
- if(enable < 2) {
|
||
|
- status = pwm_set_mode(enable, chan);
|
||
|
- }
|
||
|
- return size;
|
||
|
-}
|
||
|
-
|
||
|
-static ssize_t pwm_duty_percent_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
- unsigned int duty_percent = 0;
|
||
|
- struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
-
|
||
|
- sscanf(buf,"%u",&duty_percent);
|
||
|
- if(duty_percent > 100) {
|
||
|
- size = -EINVAL;
|
||
|
- } else {
|
||
|
- chan->duty_percent = duty_percent;
|
||
|
- if(chan->period) {
|
||
|
- fixup_duty(chan);
|
||
|
- pwm_set_mode(NO_ENABLE_CHANGE,chan);
|
||
|
- }
|
||
|
- }
|
||
|
-
|
||
|
- return size;
|
||
|
-}
|
||
|
-static ssize_t pwm_pulse_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
- struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
- ssize_t status = -EINVAL;
|
||
|
- int pulse = 0;
|
||
|
- sscanf(buf,"%d",&pulse);
|
||
|
- if(pulse < 2) {
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- chan->ctrl_current.s.ch0_pulse_start = pulse;
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- chan->ctrl_current.s.ch1_pulse_start = pulse;
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- status = size;
|
||
|
- }
|
||
|
- return status;
|
||
|
-}
|
||
|
-
|
||
|
-int pwm_set_period_and_duty(struct sun4i_pwm_available_channel *chan) {
|
||
|
- int return_val = -EINVAL;
|
||
|
- unsigned int entire_cycles = get_entire_cycles(chan);
|
||
|
- unsigned int active_cycles = get_active_cycles(chan);
|
||
|
- chan->period_reg.initializer = 0;
|
||
|
- if(entire_cycles >= active_cycles && active_cycles) {
|
||
|
- chan->period_reg.s.pwm_entire_cycles = entire_cycles;
|
||
|
- chan->period_reg.s.pwm_active_cycles = active_cycles;
|
||
|
- } else {
|
||
|
- chan->period_reg.s.pwm_entire_cycles = MAX_CYCLES;
|
||
|
- chan->period_reg.s.pwm_active_cycles = MAX_CYCLES;
|
||
|
- }
|
||
|
- writel(chan->period_reg.initializer, chan->period_reg_addr);
|
||
|
- return return_val;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-ssize_t pwm_set_mode(unsigned int enable, struct sun4i_pwm_available_channel *chan) {
|
||
|
- ssize_t status = 0;
|
||
|
- if(enable == NO_ENABLE_CHANGE) {
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- enable = chan->ctrl_current.s.ch0_en;
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- enable = chan->ctrl_current.s.ch1_en;
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- }
|
||
|
- chan->ctrl_current.initializer = readl(chan->ctrl_addr);
|
||
|
- if(enable == 1) {
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- chan->ctrl_current.s.ch0_prescaler = 0;
|
||
|
- chan->ctrl_current.s.ch0_act_state = 0;
|
||
|
- chan->ctrl_current.s.ch0_mode = 0;
|
||
|
- chan->ctrl_current.s.ch0_pulse_start = 0;
|
||
|
- chan->ctrl_current.s.ch0_en = 0;
|
||
|
- chan->ctrl_current.s.ch0_clk_gating = 0;
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- chan->ctrl_current.s.ch1_prescaler = 0;
|
||
|
- chan->ctrl_current.s.ch1_act_state = 0;
|
||
|
- chan->ctrl_current.s.ch1_mode = 0;
|
||
|
- chan->ctrl_current.s.ch1_pulse_start = 0;
|
||
|
- chan->ctrl_current.s.ch1_en = 1;
|
||
|
- chan->ctrl_current.s.ch1_clk_gating = 0;
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- if(status) {
|
||
|
- return status;
|
||
|
- }
|
||
|
- //writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
- chan->pin_current.initializer = readl(chan->pin_addr);
|
||
|
- if(chan->pin_mask.s0.pin0_select) {
|
||
|
- chan->pin_current.s0.pin0_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin1_select) {
|
||
|
- chan->pin_current.s0.pin1_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin2_select) {
|
||
|
- chan->pin_current.s0.pin2_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin3_select) {
|
||
|
- chan->pin_current.s0.pin3_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin4_select) {
|
||
|
- chan->pin_current.s0.pin4_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin5_select) {
|
||
|
- chan->pin_current.s0.pin5_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin6_select) {
|
||
|
- chan->pin_current.s0.pin6_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->pin_mask.s0.pin7_select) {
|
||
|
- chan->pin_current.s0.pin7_select = SELECT_PWM;
|
||
|
- }
|
||
|
- if(chan->channel == 0) {
|
||
|
- chan->ctrl_current.s.ch0_prescaler = chan->prescale;
|
||
|
- } else {
|
||
|
- chan->ctrl_current.s.ch1_prescaler = chan->prescale;
|
||
|
- }
|
||
|
- pwm_set_period_and_duty(chan);
|
||
|
-
|
||
|
- writel(chan->pin_current.initializer,chan->pin_addr);
|
||
|
- //writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- chan->ctrl_current.s.ch0_en = 1;
|
||
|
- chan->ctrl_current.s.ch0_clk_gating = 1;
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- chan->ctrl_current.s.ch1_en = 1;
|
||
|
- chan->ctrl_current.s.ch1_clk_gating = 1;
|
||
|
- break;
|
||
|
- }
|
||
|
- writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
-
|
||
|
- } else if (enable == 0) {
|
||
|
- switch (chan->channel) {
|
||
|
- case 0:
|
||
|
- chan->ctrl_current.s.ch0_clk_gating = 0;
|
||
|
- chan->ctrl_current.s.ch0_en = enable;
|
||
|
- break;
|
||
|
- case 1:
|
||
|
- chan->ctrl_current.s.ch1_clk_gating = 0;
|
||
|
- chan->ctrl_current.s.ch1_en = enable;
|
||
|
- break;
|
||
|
- default:
|
||
|
- status = -EINVAL;
|
||
|
- break;
|
||
|
- }
|
||
|
- if(!status) {
|
||
|
- chan->pin_current.initializer &= ~chan->pin_mask.initializer;
|
||
|
- chan->pin_current.initializer |= readl(chan->pin_addr) & chan->pin_mask.initializer;
|
||
|
- writel(chan->pin_current.initializer,chan->pin_addr);
|
||
|
- writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
- }
|
||
|
- }
|
||
|
- return status;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
-
|
||
|
-void pwm_setup_available_channels( void ) {
|
||
|
- void * timer_base = ioremap(SW_PA_TIMERC_IO_BASE, 0x400); /* 0x01c20c00 */
|
||
|
- void * PWM_CTRL_REG_BASE = timer_base + 0x200; /* 0x01c20e00 */
|
||
|
- void * portc_io_base = ioremap(SW_PA_PORTC_IO_BASE,0x400); /* 0x01c20800 */
|
||
|
- void * PB_CFG0_REG = (portc_io_base + 0x24); /* 0x01C20824 */
|
||
|
- void * PI_CFG0_REG = (portc_io_base + 0x120); /* 0x01c20920 */
|
||
|
-
|
||
|
- /*void * PB_PULL0_REG = (portc_io_base + 0x040);*/ /* 0x01c20840 */
|
||
|
- /*void * PI_PULL0_REG = (portc_io_base + 0x13c);*/ /* 0x01c2091c */
|
||
|
- /*void * PH_CFG0_REG = (portc_io_base + 0xfc);*/ /* 0x01c208fc */
|
||
|
- /*void * PH_CFG1_REG = (portc_io_base + 0x100);*/ /* 0x01c20900 */
|
||
|
- /*void * PH_PULL0_REG = (portc_io_base + 0x118);*/ /* 0x01c20918 */
|
||
|
-
|
||
|
- pwm_available_chan[0].use_count = 0;
|
||
|
- pwm_available_chan[0].ctrl_addr = PWM_CTRL_REG_BASE;
|
||
|
- pwm_available_chan[0].pin_addr = PB_CFG0_REG;
|
||
|
- pwm_available_chan[0].period_reg_addr = pwm_available_chan[0].ctrl_addr + 0x04;
|
||
|
- pwm_available_chan[0].channel = 0;
|
||
|
- pwm_available_chan[0].ctrl_backup.initializer = readl(pwm_available_chan[0].ctrl_addr);
|
||
|
- pwm_available_chan[0].ctrl_mask.initializer = 0;
|
||
|
- pwm_available_chan[0].ctrl_mask.s.ch0_prescaler = 0x0f;
|
||
|
- pwm_available_chan[0].ctrl_mask.s.ch0_en = 0x01;
|
||
|
- pwm_available_chan[0].ctrl_mask.s.ch0_act_state = 0x01;
|
||
|
- pwm_available_chan[0].ctrl_mask.s.ch0_clk_gating = 0x00;
|
||
|
- pwm_available_chan[0].ctrl_mask.s.ch0_mode = 0x01;
|
||
|
- pwm_available_chan[0].ctrl_mask.s.ch0_pulse_start = 0x01;
|
||
|
- pwm_available_chan[0].ctrl_current.initializer = 0;
|
||
|
- pwm_available_chan[0].pin_backup.initializer = readl(pwm_available_chan[0].pin_addr);
|
||
|
-/* pwm_available_chan[0].pin_mask.initializer = 0xffffffff; */
|
||
|
- pwm_available_chan[0].pin_mask.s0.pin2_select = 0x07;
|
||
|
- pwm_available_chan[0].pin_current.s0.pin2_select = 0x02;
|
||
|
-
|
||
|
- pwm_available_chan[0].pin_name = "PB2";
|
||
|
- pwm_available_chan[0].period = 10000;
|
||
|
- pwm_available_chan[0].duty_percent = 100;
|
||
|
- *(unsigned int *)&pwm_available_chan[0].period_reg = 0;
|
||
|
- pwm_available_chan[0].prescale = 0;
|
||
|
-
|
||
|
-
|
||
|
- pwm_available_chan[1].use_count = 0;
|
||
|
- pwm_available_chan[1].ctrl_addr = PWM_CTRL_REG_BASE;
|
||
|
- pwm_available_chan[1].pin_addr = PI_CFG0_REG;
|
||
|
- pwm_available_chan[1].period_reg_addr = pwm_available_chan[1].ctrl_addr + 0x08;
|
||
|
- pwm_available_chan[1].channel = 1;
|
||
|
- pwm_available_chan[1].ctrl_backup.initializer = readl(pwm_available_chan[1].ctrl_addr);
|
||
|
- pwm_available_chan[1].ctrl_mask.initializer = 0;
|
||
|
- pwm_available_chan[1].ctrl_mask.s.ch1_prescaler = 0x0f;
|
||
|
- pwm_available_chan[1].ctrl_mask.s.ch1_en = 0x01;
|
||
|
- pwm_available_chan[1].ctrl_mask.s.ch1_act_state = 0x01;
|
||
|
- pwm_available_chan[1].ctrl_mask.s.ch1_clk_gating = 0x00;
|
||
|
- pwm_available_chan[1].ctrl_mask.s.ch1_mode = 0x01;
|
||
|
- pwm_available_chan[1].ctrl_mask.s.ch1_pulse_start = 0x01;
|
||
|
- pwm_available_chan[1].ctrl_current.initializer = 0;
|
||
|
- pwm_available_chan[1].pin_backup.initializer = readl(pwm_available_chan[1].pin_addr);
|
||
|
- pwm_available_chan[1].pin_mask.initializer = 0;
|
||
|
- pwm_available_chan[1].pin_mask.s0.pin3_select = 0x07;
|
||
|
- pwm_available_chan[1].pin_current.s0.pin3_select = 0x02;
|
||
|
- pwm_available_chan[1].pin_name = "PI3";
|
||
|
- pwm_available_chan[1].period = 10000;
|
||
|
- pwm_available_chan[1].duty_percent = 50;
|
||
|
- *(unsigned int *)&pwm_available_chan[1].period_reg = 0;
|
||
|
- pwm_available_chan[1].prescale = 0;
|
||
|
-
|
||
|
-
|
||
|
-}
|
||
|
-
|
||
|
-struct pwm_device {
|
||
|
- struct sun4i_pwm_available_channel *chan;
|
||
|
-};
|
||
|
-
|
||
|
-struct pwm_device pwm_devices[2] = {
|
||
|
- [0] = {.chan = &pwm_available_chan[0]},
|
||
|
- [1] = {.chan = &pwm_available_chan[1]}
|
||
|
-};
|
||
|
-
|
||
|
-struct pwm_device *pwm_request(int pwm_id, const char *label)
|
||
|
-{
|
||
|
- struct pwm_device *pwm;
|
||
|
- int found = 0;
|
||
|
-
|
||
|
- if(pwm_id < 2 && pwm_id >= 0) {
|
||
|
- pwm = &pwm_devices[pwm_id];
|
||
|
- found = 1;
|
||
|
- }
|
||
|
- if (found) {
|
||
|
- if (pwm->chan->use_count == 0) {
|
||
|
- pwm->chan->use_count++;
|
||
|
- pwm->chan->name = label;
|
||
|
- } else
|
||
|
- pwm = ERR_PTR(-EBUSY);
|
||
|
- } else
|
||
|
- pwm = ERR_PTR(-ENOENT);
|
||
|
-
|
||
|
- return pwm;
|
||
|
-}
|
||
|
-EXPORT_SYMBOL(pwm_request);
|
||
|
-
|
||
|
-
|
||
|
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
|
||
|
-{
|
||
|
- if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
|
||
|
- return -EINVAL;
|
||
|
-
|
||
|
- pwm->chan->period = period_ns / 1000;
|
||
|
- pwm->chan->prescale = pwm_get_best_prescale(pwm->chan->period);
|
||
|
- pwm->chan->duty = duty_ns / 1000;
|
||
|
- fixup_duty(pwm->chan);
|
||
|
- pwm_set_mode(NO_ENABLE_CHANGE,pwm->chan);
|
||
|
- return 0;
|
||
|
-}
|
||
|
-EXPORT_SYMBOL(pwm_config);
|
||
|
-
|
||
|
-
|
||
|
-int pwm_enable(struct pwm_device *pwm)
|
||
|
-{
|
||
|
- if (pwm == NULL) {
|
||
|
- return -EINVAL;
|
||
|
- }
|
||
|
- pwm_set_mode(PWM_CTRL_ENABLE,pwm->chan);
|
||
|
- return 0;
|
||
|
-}
|
||
|
-EXPORT_SYMBOL(pwm_enable);
|
||
|
-
|
||
|
-void pwm_disable(struct pwm_device *pwm)
|
||
|
-{
|
||
|
- if (pwm == NULL) {
|
||
|
- return;
|
||
|
- }
|
||
|
- pwm_set_mode(PWM_CTRL_DISABLE,pwm->chan);
|
||
|
-}
|
||
|
-EXPORT_SYMBOL(pwm_disable);
|
||
|
-
|
||
|
-void pwm_free(struct pwm_device *pwm)
|
||
|
-{
|
||
|
- if (pwm->chan->use_count) {
|
||
|
- pwm->chan->use_count--;
|
||
|
- } else
|
||
|
- pr_warning("PWM device already freed\n");
|
||
|
-}
|
||
|
-EXPORT_SYMBOL(pwm_free);
|
||
|
-
|
||
|
-
|
||
|
-module_init(sunxi_pwm_init);
|
||
|
-module_exit(sunxi_pwm_exit);
|
||
|
-
|
||
|
-
|
||
|
-
|
||
|
-
|
||
|
-MODULE_LICENSE("GPL");
|
||
|
-MODULE_AUTHOR("David H. Wilkins <dwil...@conecuh.com>");
|
||
|
+ return active_cycles;
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * When the duty is set, compute the number of microseconds
|
||
|
+ * based on the period.
|
||
|
+ */
|
||
|
+
|
||
|
+void fixup_duty(struct sun4i_pwm_available_channel *chan) {
|
||
|
+ if(chan->duty_percent >= 0) {
|
||
|
+ chan->duty = chan->period * chan->duty_percent / 100;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ * Stores the run (enable) bit.
|
||
|
+ */
|
||
|
+
|
||
|
+static ssize_t pwm_run_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
+ struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status = -EINVAL;
|
||
|
+ int enable = 0;
|
||
|
+
|
||
|
+ sscanf(buf,"%d",&enable);
|
||
|
+ if(enable < 2) {
|
||
|
+ status = pwm_set_mode(enable, chan);
|
||
|
+ }
|
||
|
+ return size;
|
||
|
+}
|
||
|
+
|
||
|
+static ssize_t pwm_duty_percent_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
+ unsigned int duty_percent = 0;
|
||
|
+ struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ sscanf(buf,"%u",&duty_percent);
|
||
|
+ if(duty_percent > 100) {
|
||
|
+ size = -EINVAL;
|
||
|
+ } else {
|
||
|
+ chan->duty_percent = duty_percent;
|
||
|
+ if(chan->period) {
|
||
|
+ fixup_duty(chan);
|
||
|
+ pwm_set_mode(NO_ENABLE_CHANGE,chan);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ return size;
|
||
|
+}
|
||
|
+static ssize_t pwm_pulse_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t size) {
|
||
|
+ struct sun4i_pwm_available_channel *chan = dev_get_drvdata(dev);
|
||
|
+ ssize_t status = -EINVAL;
|
||
|
+ int pulse = 0;
|
||
|
+ sscanf(buf,"%d",&pulse);
|
||
|
+ if(pulse < 2) {
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ chan->ctrl_current.s.ch0_pulse_start = pulse;
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ chan->ctrl_current.s.ch1_pulse_start = pulse;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ status = size;
|
||
|
+ }
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+int pwm_set_period_and_duty(struct sun4i_pwm_available_channel *chan) {
|
||
|
+ int return_val = -EINVAL;
|
||
|
+ unsigned int entire_cycles = get_entire_cycles(chan);
|
||
|
+ unsigned int active_cycles = get_active_cycles(chan);
|
||
|
+ chan->period_reg.initializer = 0;
|
||
|
+ if(entire_cycles >= active_cycles && active_cycles) {
|
||
|
+ chan->period_reg.s.pwm_entire_cycles = entire_cycles;
|
||
|
+ chan->period_reg.s.pwm_active_cycles = active_cycles;
|
||
|
+ } else {
|
||
|
+ chan->period_reg.s.pwm_entire_cycles = MAX_CYCLES;
|
||
|
+ chan->period_reg.s.pwm_active_cycles = MAX_CYCLES;
|
||
|
+ }
|
||
|
+ writel(chan->period_reg.initializer, chan->period_reg_addr);
|
||
|
+ return return_val;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+ssize_t pwm_set_mode(unsigned int enable, struct sun4i_pwm_available_channel *chan) {
|
||
|
+ ssize_t status = 0;
|
||
|
+ if(enable == NO_ENABLE_CHANGE) {
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ enable = chan->ctrl_current.s.ch0_en;
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ enable = chan->ctrl_current.s.ch1_en;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ chan->ctrl_current.initializer = readl(chan->ctrl_addr);
|
||
|
+ if(enable == 1) {
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ chan->ctrl_current.s.ch0_prescaler = 0;
|
||
|
+ chan->ctrl_current.s.ch0_act_state = 0;
|
||
|
+ chan->ctrl_current.s.ch0_mode = 0;
|
||
|
+ chan->ctrl_current.s.ch0_pulse_start = 0;
|
||
|
+ chan->ctrl_current.s.ch0_en = 0;
|
||
|
+ chan->ctrl_current.s.ch0_clk_gating = 0;
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ chan->ctrl_current.s.ch1_prescaler = 0;
|
||
|
+ chan->ctrl_current.s.ch1_act_state = 0;
|
||
|
+ chan->ctrl_current.s.ch1_mode = 0;
|
||
|
+ chan->ctrl_current.s.ch1_pulse_start = 0;
|
||
|
+ chan->ctrl_current.s.ch1_en = 1;
|
||
|
+ chan->ctrl_current.s.ch1_clk_gating = 0;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ if(status) {
|
||
|
+ return status;
|
||
|
+ }
|
||
|
+ //writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
+ chan->pin_current.initializer = readl(chan->pin_addr);
|
||
|
+ if(chan->pin_mask.s0.pin0_select) {
|
||
|
+ chan->pin_current.s0.pin0_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin1_select) {
|
||
|
+ chan->pin_current.s0.pin1_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin2_select) {
|
||
|
+ chan->pin_current.s0.pin2_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin3_select) {
|
||
|
+ chan->pin_current.s0.pin3_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin4_select) {
|
||
|
+ chan->pin_current.s0.pin4_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin5_select) {
|
||
|
+ chan->pin_current.s0.pin5_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin6_select) {
|
||
|
+ chan->pin_current.s0.pin6_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->pin_mask.s0.pin7_select) {
|
||
|
+ chan->pin_current.s0.pin7_select = SELECT_PWM;
|
||
|
+ }
|
||
|
+ if(chan->channel == 0) {
|
||
|
+ chan->ctrl_current.s.ch0_prescaler = chan->prescale;
|
||
|
+ } else {
|
||
|
+ chan->ctrl_current.s.ch1_prescaler = chan->prescale;
|
||
|
+ }
|
||
|
+ pwm_set_period_and_duty(chan);
|
||
|
+
|
||
|
+ writel(chan->pin_current.initializer,chan->pin_addr);
|
||
|
+ //writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ chan->ctrl_current.s.ch0_en = 1;
|
||
|
+ chan->ctrl_current.s.ch0_clk_gating = 1;
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ chan->ctrl_current.s.ch1_en = 1;
|
||
|
+ chan->ctrl_current.s.ch1_clk_gating = 1;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
+
|
||
|
+ } else if (enable == 0) {
|
||
|
+ switch (chan->channel) {
|
||
|
+ case 0:
|
||
|
+ chan->ctrl_current.s.ch0_clk_gating = 0;
|
||
|
+ chan->ctrl_current.s.ch0_en = enable;
|
||
|
+ break;
|
||
|
+ case 1:
|
||
|
+ chan->ctrl_current.s.ch1_clk_gating = 0;
|
||
|
+ chan->ctrl_current.s.ch1_en = enable;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ status = -EINVAL;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ if(!status) {
|
||
|
+ chan->pin_current.initializer &= ~chan->pin_mask.initializer;
|
||
|
+ chan->pin_current.initializer |= readl(chan->pin_addr) & chan->pin_mask.initializer;
|
||
|
+ writel(chan->pin_current.initializer,chan->pin_addr);
|
||
|
+ writel(chan->ctrl_current.initializer,chan->ctrl_addr);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+
|
||
|
+void pwm_setup_available_channels( void ) {
|
||
|
+ void * timer_base = ioremap(SW_PA_TIMERC_IO_BASE, 0x400); /* 0x01c20c00 */
|
||
|
+ void * PWM_CTRL_REG_BASE = timer_base + 0x200; /* 0x01c20e00 */
|
||
|
+ void * portc_io_base = ioremap(SW_PA_PORTC_IO_BASE,0x400); /* 0x01c20800 */
|
||
|
+ void * PB_CFG0_REG = (portc_io_base + 0x24); /* 0x01C20824 */
|
||
|
+ void * PI_CFG0_REG = (portc_io_base + 0x120); /* 0x01c20920 */
|
||
|
+
|
||
|
+ /*void * PB_PULL0_REG = (portc_io_base + 0x040);*/ /* 0x01c20840 */
|
||
|
+ /*void * PI_PULL0_REG = (portc_io_base + 0x13c);*/ /* 0x01c2091c */
|
||
|
+ /*void * PH_CFG0_REG = (portc_io_base + 0xfc);*/ /* 0x01c208fc */
|
||
|
+ /*void * PH_CFG1_REG = (portc_io_base + 0x100);*/ /* 0x01c20900 */
|
||
|
+ /*void * PH_PULL0_REG = (portc_io_base + 0x118);*/ /* 0x01c20918 */
|
||
|
+
|
||
|
+ pwm_available_chan[0].use_count = 0;
|
||
|
+ pwm_available_chan[0].ctrl_addr = PWM_CTRL_REG_BASE;
|
||
|
+ pwm_available_chan[0].pin_addr = PB_CFG0_REG;
|
||
|
+ pwm_available_chan[0].period_reg_addr = pwm_available_chan[0].ctrl_addr + 0x04;
|
||
|
+ pwm_available_chan[0].channel = 0;
|
||
|
+ pwm_available_chan[0].ctrl_backup.initializer = readl(pwm_available_chan[0].ctrl_addr);
|
||
|
+ pwm_available_chan[0].ctrl_mask.initializer = 0;
|
||
|
+ pwm_available_chan[0].ctrl_mask.s.ch0_prescaler = 0x0f;
|
||
|
+ pwm_available_chan[0].ctrl_mask.s.ch0_en = 0x01;
|
||
|
+ pwm_available_chan[0].ctrl_mask.s.ch0_act_state = 0x01;
|
||
|
+ pwm_available_chan[0].ctrl_mask.s.ch0_clk_gating = 0x00;
|
||
|
+ pwm_available_chan[0].ctrl_mask.s.ch0_mode = 0x01;
|
||
|
+ pwm_available_chan[0].ctrl_mask.s.ch0_pulse_start = 0x01;
|
||
|
+ pwm_available_chan[0].ctrl_current.initializer = 0;
|
||
|
+ pwm_available_chan[0].pin_backup.initializer = readl(pwm_available_chan[0].pin_addr);
|
||
|
+/* pwm_available_chan[0].pin_mask.initializer = 0xffffffff; */
|
||
|
+ pwm_available_chan[0].pin_mask.s0.pin2_select = 0x07;
|
||
|
+ pwm_available_chan[0].pin_current.s0.pin2_select = 0x02;
|
||
|
+
|
||
|
+ pwm_available_chan[0].pin_name = "PB2";
|
||
|
+ pwm_available_chan[0].period = 10000;
|
||
|
+ pwm_available_chan[0].duty_percent = 100;
|
||
|
+ *(unsigned int *)&pwm_available_chan[0].period_reg = 0;
|
||
|
+ pwm_available_chan[0].prescale = 0;
|
||
|
+
|
||
|
+
|
||
|
+ pwm_available_chan[1].use_count = 0;
|
||
|
+ pwm_available_chan[1].ctrl_addr = PWM_CTRL_REG_BASE;
|
||
|
+ pwm_available_chan[1].pin_addr = PI_CFG0_REG;
|
||
|
+ pwm_available_chan[1].period_reg_addr = pwm_available_chan[1].ctrl_addr + 0x08;
|
||
|
+ pwm_available_chan[1].channel = 1;
|
||
|
+ pwm_available_chan[1].ctrl_backup.initializer = readl(pwm_available_chan[1].ctrl_addr);
|
||
|
+ pwm_available_chan[1].ctrl_mask.initializer = 0;
|
||
|
+ pwm_available_chan[1].ctrl_mask.s.ch1_prescaler = 0x0f;
|
||
|
+ pwm_available_chan[1].ctrl_mask.s.ch1_en = 0x01;
|
||
|
+ pwm_available_chan[1].ctrl_mask.s.ch1_act_state = 0x01;
|
||
|
+ pwm_available_chan[1].ctrl_mask.s.ch1_clk_gating = 0x00;
|
||
|
+ pwm_available_chan[1].ctrl_mask.s.ch1_mode = 0x01;
|
||
|
+ pwm_available_chan[1].ctrl_mask.s.ch1_pulse_start = 0x01;
|
||
|
+ pwm_available_chan[1].ctrl_current.initializer = 0;
|
||
|
+ pwm_available_chan[1].pin_backup.initializer = readl(pwm_available_chan[1].pin_addr);
|
||
|
+ pwm_available_chan[1].pin_mask.initializer = 0;
|
||
|
+ pwm_available_chan[1].pin_mask.s0.pin3_select = 0x07;
|
||
|
+ pwm_available_chan[1].pin_current.s0.pin3_select = 0x02;
|
||
|
+ pwm_available_chan[1].pin_name = "PI3";
|
||
|
+ pwm_available_chan[1].period = 10000;
|
||
|
+ pwm_available_chan[1].duty_percent = 50;
|
||
|
+ *(unsigned int *)&pwm_available_chan[1].period_reg = 0;
|
||
|
+ pwm_available_chan[1].prescale = 0;
|
||
|
+
|
||
|
+
|
||
|
+}
|
||
|
+
|
||
|
+struct pwm_device {
|
||
|
+ struct sun4i_pwm_available_channel *chan;
|
||
|
+};
|
||
|
+
|
||
|
+struct pwm_device pwm_devices[2] = {
|
||
|
+ [0] = {.chan = &pwm_available_chan[0]},
|
||
|
+ [1] = {.chan = &pwm_available_chan[1]}
|
||
|
+};
|
||
|
+
|
||
|
+struct pwm_device *pwm_request(int pwm_id, const char *label)
|
||
|
+{
|
||
|
+ struct pwm_device *pwm;
|
||
|
+ int found = 0;
|
||
|
+
|
||
|
+ if(pwm_id < 2 && pwm_id >= 0) {
|
||
|
+ pwm = &pwm_devices[pwm_id];
|
||
|
+ found = 1;
|
||
|
+ }
|
||
|
+ if (found) {
|
||
|
+ if (pwm->chan->use_count == 0) {
|
||
|
+ pwm->chan->use_count++;
|
||
|
+ pwm->chan->name = label;
|
||
|
+ } else
|
||
|
+ pwm = ERR_PTR(-EBUSY);
|
||
|
+ } else
|
||
|
+ pwm = ERR_PTR(-ENOENT);
|
||
|
+
|
||
|
+ return pwm;
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(pwm_request);
|
||
|
+
|
||
|
+
|
||
|
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
|
||
|
+{
|
||
|
+ if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ pwm->chan->period = period_ns / 1000;
|
||
|
+ pwm->chan->prescale = pwm_get_best_prescale(pwm->chan->period);
|
||
|
+ pwm->chan->duty = duty_ns / 1000;
|
||
|
+ fixup_duty(pwm->chan);
|
||
|
+ pwm_set_mode(NO_ENABLE_CHANGE,pwm->chan);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(pwm_config);
|
||
|
+
|
||
|
+
|
||
|
+int pwm_enable(struct pwm_device *pwm)
|
||
|
+{
|
||
|
+ if (pwm == NULL) {
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+ pwm_set_mode(PWM_CTRL_ENABLE,pwm->chan);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(pwm_enable);
|
||
|
+
|
||
|
+void pwm_disable(struct pwm_device *pwm)
|
||
|
+{
|
||
|
+ if (pwm == NULL) {
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ pwm_set_mode(PWM_CTRL_DISABLE,pwm->chan);
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(pwm_disable);
|
||
|
+
|
||
|
+void pwm_free(struct pwm_device *pwm)
|
||
|
+{
|
||
|
+ if (pwm->chan->use_count) {
|
||
|
+ pwm->chan->use_count--;
|
||
|
+ } else
|
||
|
+ pr_warning("PWM device already freed\n");
|
||
|
+}
|
||
|
+EXPORT_SYMBOL(pwm_free);
|
||
|
+
|
||
|
+
|
||
|
+module_init(sunxi_pwm_init);
|
||
|
+module_exit(sunxi_pwm_exit);
|
||
|
+
|
||
|
+
|
||
|
+
|
||
|
+
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
+MODULE_AUTHOR("David H. Wilkins <dwil...@conecuh.com>");
|
||
|
--
|
||
|
1.9.1
|
||
|
|