From: "David H. Wilkins" <
dwil...@conecuh.com>
The file "drivers/video/sunxi/disp/disp_lcd.c" exported the symbol "pwm_enable" which
conflicted with the "pwm_enable" symbol mandated for the 3.4 series kernel interface
files: drivers/misc/pwm-sunxi.[ch]
Implements a kernel pwm driver and a sysfs interface for the pwm, loosely based
on the gpio sysfs interface:
-- /sys/class/pwm-sunxi
|
+----pwmX
|
+---duty
+---duty_percent
+---period
+---polarity
+---pulse
+---pin
+---run
* period (r/w)
period that makes up a cycle. Can be expressed as hz, khz, ms, or us. Whole numbers only. Examples:
echo 10hz > /sys/class/pwm-sunxi/pwm0/period
echo 1khz > /sys/class/pwm-sunxi/pwm0/period
echo 100ms > /sys/class/pwm-sunxi/pwm0/period
echo 100us > /sys/class/pwm-sunxi/pwm0/period
echo 150khz > /sys/class/pwm-sunxi/pwm0/period
* duty (r/w)
portion of the period above that is "active" or on. Same units as above.
Ex: echo 1hz > /sys/class/pwm-sunxi/pwm0/period
Ex: echo 2hz > /sys/class/pwm-sunxi/pwm0/duty
* duty_percent (r/w)
duty (above) expressed as a percentage. Whole numbers only
Ex: echo 50 > /sys/class/pwm-sunxi/pwm0/duty_percent
* polarity(r/w)
polarity of the pin during the duty portion. 1 = high, 0 = low
* pulse (r/w)
Output one pulse at the specified period and duty
* pin (ro)
Name of the A10 pin this pwm outputs on. This is hardwired and informational only. Example: PB2
* run (r/w) Enable/Disable the PWM with the previously set parameters. Examples:
echo 1 > /sys/class/pwm-sunxi/pwm0/run
echo 0 > /sys/class/pwm-sunxi/pwm0/run
Signed-off-by: David H. Wilkins <
dwil...@conecuh.com>
---
drivers/misc/Kconfig | 10 +
drivers/misc/Makefile | 1 +
drivers/misc/pwm-sunxi.c | 1015 +++++++++++++++++++++++++++++++++++
drivers/misc/pwm-sunxi.h | 198 +++++++
drivers/video/sunxi/disp/disp_lcd.c | 1 -
5 files changed, 1224 insertions(+), 1 deletion(-)
create mode 100644 drivers/misc/pwm-sunxi.c
create mode 100644 drivers/misc/pwm-sunxi.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f16c278..8a98b73 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -76,6 +76,16 @@ config SUNXI_DBGREG
To compile this driver as a module, choose M here: the
module will be called sun4i-dbgreg.
+config SUNXI_PWM
+ tristate "Sunxi PWM Driver (pwm-sunxi)"
+ help
+ Say Y here if you want Hardware PWM Support
+
+ To compile this driver as a module, choose M here: the
+ module will be called pwm-sunxi. This driver supports
+ a sysfs interface at /sys/class/pwm-sunxi as well as the
+ kernel pwm interface.
+
config ATMEL_PWM
tristate "Atmel AT32/AT91 PWM support"
depends on HAVE_CLK
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f49d878..82b4db5 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -55,3 +55,4 @@ obj-$(CONFIG_SENSORS_AK8975) += akm8975.o
obj-$(CONFIG_SUN4I_VIBRATOR) += sun4i-vibrator.o
obj-$(CONFIG_SUN4I_GPIO_UGLY) += sun4i-gpio.o
obj-$(CONFIG_SUNXI_DBGREG) += sunxi-dbgreg.o
+obj-$(CONFIG_SUNXI_PWM) += pwm-sunxi.o
diff --git a/drivers/misc/pwm-sunxi.c b/drivers/misc/pwm-sunxi.c
new file mode 100644
index 0000000..104c471
--- /dev/null
+++ b/drivers/misc/pwm-sunxi.c
@@ -0,0 +1,1015 @@
+/* pwm-sunxi.c
+ *
+ * pwm module for sun4i (and others) like cubieboard and pcduino
+ *
+ * (C) Copyright 2013
+ * David H. Wilkins <
dwil...@conecuh.com>
+ *
+ * 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 <pwm-sunxi.h>
+#include <linux/pwm.h>
+#include <linux/kdev_t.h>
+/*
+ * Forward Declarations
+ */
+
+void pwm_dump_pwm_ctrl_reg(struct sun4i_pwm_ctrl *pwm, struct sun4i_pwm_ctrl *pwm_compare,const char *name);
+void pwm_dump_pwm_period_reg(struct sun4i_pwm_period *period, struct sun4i_pwm_period *period_compare,const char *name);
+void pwm_dump_ioreg_cfg(struct sun4i_ioreg_cfg0 *cfg,struct sun4i_ioreg_cfg0 *cfg_compare, const char *name);
+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;
+
+struct kobject *pwm0_kobj;
+struct kobject *pwm1_kobj;
+
+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 __init sunxi_pwm_init(void)
+{
+ int return_val;
+ pwm_setup_available_channels();
+
+ return_val = class_register(&pwm_class);
+ if(return_val) {
+ class_unregister(&pwm_class);
+ } else {
+ printk(KERN_INFO "pwm_class.dev_kobj = %p",pwm_class.dev_kobj);
+ }
+/*
+ platform_device_register(&sun4i_pwm_device);
+ platform_driver_register(&sun4i_pwm_driver);
+*/
+
+ pwm0 = device_create(&pwm_class,NULL,MKDEV(0,0),&pwm_available_chan[0],"pwm0");
+ pwm1 = device_create(&pwm_class,NULL,MKDEV(0,0),&pwm_available_chan[1],"pwm1");
+
+ pwm0_kobj = &pwm0->kobj;
+ pwm1_kobj = &pwm1->kobj;
+ return_val = sysfs_create_group(pwm0_kobj,&pwm_attr_group);
+ if(return_val) {
+ printk(KERN_INFO "pwm-sunxi: return from sysfs_create_group(pwm0) was %d",return_val);
+ }
+
+ return_val = sysfs_create_group(pwm1_kobj,&pwm_attr_group);
+ if(return_val) {
+ printk(KERN_INFO "pwm-sunxi: return from sysfs_create_group(pwm1) was %d",return_val);
+ }
+
+
+ printk(KERN_INFO "pwm-sunxi: Initialized...");
+ return return_val;
+}
+
+void sunxi_pwm_exit(void)
+{
+ void *timer_base = ioremap(SW_PA_TIMERC_IO_BASE, 0x400);
+ void *PWM_CTRL_REG_BASE = timer_base + 0x200;
+
+ device_destroy(&pwm_class,pwm0->devt);
+ device_destroy(&pwm_class,pwm1->devt);
+ writel(0, PWM_CTRL_REG_BASE + 0);
+ writel(pwm_available_chan[0].pin_backup.initializer, pwm_available_chan[0].pin_addr);
+ writel(pwm_available_chan[1].pin_backup.initializer, pwm_available_chan[1].pin_addr);
+
+ 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;}
+
+ printk(KERN_INFO "Best prescale was %d, entire cycles was %u",chan->prescale, entire_cycles);
+
+ 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 && 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;} */
+ printk(KERN_INFO "Best prescale was %d, active cycles was %u (before entire check)",chan->prescale, active_cycles);
+ if(active_cycles > MAX_CYCLES) {active_cycles = entire_cycles-1;}
+ printk(KERN_INFO "Best prescale was %d, active cycles was %u (after entire check)x",chan->prescale, active_cycles);
+ 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_dump_ioreg_cfg(struct sun4i_ioreg_cfg0 *cfg,
+ struct sun4i_ioreg_cfg0 *cfg_compare,
+ const char *name) {
+
+ const char * cfg_pin0_select_diff = "";
+ const char * cfg_pin1_select_diff = "";
+ const char * cfg_pin2_select_diff = "";
+ const char * cfg_pin3_select_diff = "";
+ const char * cfg_pin4_select_diff = "";
+ const char * cfg_pin5_select_diff = "";
+ const char * cfg_pin6_select_diff = "";
+ const char * cfg_pin7_select_diff = "";
+
+
+ if(!cfg || !name) {
+ return;
+ }
+
+
+ if(cfg && cfg_compare) {
+ cfg_pin0_select_diff = (const char *)(cfg->pin0_select != cfg_compare->pin0_select ? " ******" : "");
+ cfg_pin1_select_diff = (const char *)(cfg->pin1_select != cfg_compare->pin1_select ? " ******" : "");
+ cfg_pin2_select_diff = (const char *)(cfg->pin2_select != cfg_compare->pin2_select ? " ******" : "");
+ cfg_pin3_select_diff = (const char *)(cfg->pin3_select != cfg_compare->pin3_select ? " ******" : "");
+ cfg_pin4_select_diff = (const char *)(cfg->pin4_select != cfg_compare->pin4_select ? " ******" : "");
+ cfg_pin5_select_diff = (const char *)(cfg->pin5_select != cfg_compare->pin5_select ? " ******" : "");
+ cfg_pin6_select_diff = (const char *)(cfg->pin6_select != cfg_compare->pin6_select ? " ******" : "");
+ cfg_pin7_select_diff = (const char *)(cfg->pin7_select != cfg_compare->pin7_select ? " ******" : "");
+ }
+
+ printk(KERN_INFO "%s: pin0( 8)_select : 0x%x %s",name,cfg->pin0_select,cfg_pin0_select_diff);
+ printk(KERN_INFO "%s: pin1( 9)_select : 0x%x %s",name,cfg->pin1_select,cfg_pin1_select_diff);
+ printk(KERN_INFO "%s: pin2(10)_select : 0x%x %s",name,cfg->pin2_select,cfg_pin2_select_diff);
+ printk(KERN_INFO "%s: pin3(11)_select : 0x%x %s",name,cfg->pin3_select,cfg_pin3_select_diff);
+ printk(KERN_INFO "%s: pin4(12)_select : 0x%x %s",name,cfg->pin4_select,cfg_pin4_select_diff);
+ printk(KERN_INFO "%s: pin5(13)_select : 0x%x %s",name,cfg->pin5_select,cfg_pin5_select_diff);
+ printk(KERN_INFO "%s: pin6(14)_select : 0x%x %s",name,cfg->pin6_select,cfg_pin6_select_diff);
+ printk(KERN_INFO "%s: pin7(15)_select : 0x%x %s",name,cfg->pin7_select,cfg_pin7_select_diff);
+ printk(KERN_INFO "%s: pin_ctl: 0x%x",name,*(unsigned int *)cfg);
+
+}
+
+
+void pwm_dump_ioreg_pull(struct ioreg_pull *cfg,
+ struct ioreg_pull *cfg_compare,
+ const char *name) {
+
+ char *pin_state[4] = {
+ "Disable",
+ "Pull Up",
+ "Pull Down",
+ "Reserved"
+ };
+
+ const char * cfg_pin0_diff = "";
+ const char * cfg_pin1_diff = "";
+ const char * cfg_pin2_diff = "";
+ const char * cfg_pin3_diff = "";
+ const char * cfg_pin4_diff = "";
+ const char * cfg_pin5_diff = "";
+ const char * cfg_pin6_diff = "";
+ const char * cfg_pin7_diff = "";
+ const char * cfg_pin8_diff = "";
+ const char * cfg_pin9_diff = "";
+ const char * cfg_pin10_diff = "";
+ const char * cfg_pin11_diff = "";
+ const char * cfg_pin12_diff = "";
+ const char * cfg_pin13_diff = "";
+ const char * cfg_pin14_diff = "";
+ const char * cfg_pin15_diff = "";
+
+
+ if(!cfg || !name) {
+ return;
+ }
+
+
+ if(cfg && cfg_compare) {
+ cfg_pin0_diff = (const char *)(cfg->pin0 != cfg_compare->pin0 ? " ******" : "");
+ cfg_pin1_diff = (const char *)(cfg->pin1 != cfg_compare->pin1 ? " ******" : "");
+ cfg_pin2_diff = (const char *)(cfg->pin2 != cfg_compare->pin2 ? " ******" : "");
+ cfg_pin3_diff = (const char *)(cfg->pin3 != cfg_compare->pin3 ? " ******" : "");
+ cfg_pin4_diff = (const char *)(cfg->pin4 != cfg_compare->pin4 ? " ******" : "");
+ cfg_pin5_diff = (const char *)(cfg->pin5 != cfg_compare->pin5 ? " ******" : "");
+ cfg_pin6_diff = (const char *)(cfg->pin6 != cfg_compare->pin6 ? " ******" : "");
+ cfg_pin7_diff = (const char *)(cfg->pin7 != cfg_compare->pin7 ? " ******" : "");
+ cfg_pin8_diff = (const char *)(cfg->pin8 != cfg_compare->pin8 ? " ******" : "");
+ cfg_pin9_diff = (const char *)(cfg->pin9 != cfg_compare->pin9 ? " ******" : "");
+ cfg_pin10_diff = (const char *)(cfg->pin10 != cfg_compare->pin10 ? " ******" : "");
+ cfg_pin11_diff = (const char *)(cfg->pin11 != cfg_compare->pin11 ? " ******" : "");
+ cfg_pin12_diff = (const char *)(cfg->pin12 != cfg_compare->pin12 ? " ******" : "");
+ cfg_pin13_diff = (const char *)(cfg->pin13 != cfg_compare->pin13 ? " ******" : "");
+ cfg_pin14_diff = (const char *)(cfg->pin14 != cfg_compare->pin14 ? " ******" : "");
+ cfg_pin15_diff = (const char *)(cfg->pin15 != cfg_compare->pin15 ? " ******" : "");
+ }
+
+ printk(KERN_INFO "%s: pin0 : 0x%x (%s) %s",name,cfg->pin0,pin_state[cfg->pin0],cfg_pin0_diff);
+ printk(KERN_INFO "%s: pin1 : 0x%x (%s) %s",name,cfg->pin1,pin_state[cfg->pin1],cfg_pin1_diff);
+ printk(KERN_INFO "%s: pin2 : 0x%x (%s) %s",name,cfg->pin2,pin_state[cfg->pin2],cfg_pin2_diff);
+ printk(KERN_INFO "%s: pin3 : 0x%x (%s) %s",name,cfg->pin3,pin_state[cfg->pin3],cfg_pin3_diff);
+ printk(KERN_INFO "%s: pin4 : 0x%x (%s) %s",name,cfg->pin4,pin_state[cfg->pin4],cfg_pin4_diff);
+ printk(KERN_INFO "%s: pin5 : 0x%x (%s) %s",name,cfg->pin5,pin_state[cfg->pin5],cfg_pin5_diff);
+ printk(KERN_INFO "%s: pin6 : 0x%x (%s) %s",name,cfg->pin6,pin_state[cfg->pin6],cfg_pin6_diff);
+ printk(KERN_INFO "%s: pin7 : 0x%x (%s) %s",name,cfg->pin7,pin_state[cfg->pin7],cfg_pin7_diff);
+ printk(KERN_INFO "%s: pin8 : 0x%x (%s) %s",name,cfg->pin8,pin_state[cfg->pin8],cfg_pin8_diff);
+ printk(KERN_INFO "%s: pin9 : 0x%x (%s) %s",name,cfg->pin9,pin_state[cfg->pin9],cfg_pin9_diff);
+ printk(KERN_INFO "%s: pin10 : 0x%x (%s) %s",name,cfg->pin10,pin_state[cfg->pin10],cfg_pin10_diff);
+ printk(KERN_INFO "%s: pin11 : 0x%x (%s) %s",name,cfg->pin11,pin_state[cfg->pin11],cfg_pin11_diff);
+ printk(KERN_INFO "%s: pin12 : 0x%x (%s) %s",name,cfg->pin12,pin_state[cfg->pin12],cfg_pin12_diff);
+ printk(KERN_INFO "%s: pin13 : 0x%x (%s) %s",name,cfg->pin13,pin_state[cfg->pin13],cfg_pin13_diff);
+ printk(KERN_INFO "%s: pin14 : 0x%x (%s) %s",name,cfg->pin14,pin_state[cfg->pin14],cfg_pin14_diff);
+ printk(KERN_INFO "%s: pin15 : 0x%x (%s) %s",name,cfg->pin15,pin_state[cfg->pin15],cfg_pin15_diff);
+
+}
+
+
+
+void pwm_dump_pwm_period_reg(struct sun4i_pwm_period *period,
+ struct sun4i_pwm_period *period_compare,const char *name) {
+
+ char period_entire_cycles_diff[25] = {0};
+ char period_active_cycles_diff[25] = {0};
+
+ if(!period || !name) {
+ return;
+ }
+
+
+ if(period && period_compare) {
+ if(period->pwm_entire_cycles != period_compare->pwm_entire_cycles) {
+ sprintf(period_entire_cycles_diff," *** was **** 0x%x",period_compare->pwm_entire_cycles);
+ }
+ if(period->pwm_active_cycles != period_compare->pwm_active_cycles) {
+ sprintf(period_active_cycles_diff," *** was **** 0x%x",period_compare->pwm_active_cycles);
+ }
+ }
+
+ printk(KERN_INFO "%s: entire_cycles: 0x%x %s",name,period->pwm_entire_cycles,period_entire_cycles_diff);
+ printk(KERN_INFO "%s: active_cycles: 0x%x %s",name,period->pwm_active_cycles,period_active_cycles_diff);
+ printk(KERN_INFO "%s: period_reg: 0x%x",name,*(unsigned int *)period);
+
+}
+
+
+void pwm_dump_pwm_ctrl_reg(struct sun4i_pwm_ctrl *pwm,
+ struct sun4i_pwm_ctrl *pwm_compare,const char *name) {
+ const char * ch0_prescaler_diff = "";
+ const char * ch0_en_diff = "";
+ const char * ch0_act_state_diff = "";
+ const char * ch0_clk_gating_diff = "";
+ const char * ch0_mode_diff = "";
+ const char * ch0_pulse_start_diff = "";
+
+ const char * ch1_prescaler_diff = "";
+ const char * ch1_en_diff = "";
+ const char * ch1_act_state_diff = "";
+ const char * ch1_clk_gating_diff = "";
+ const char * ch1_mode_diff = "";
+ const char * ch1_pulse_start_diff = "";
+
+ if(!pwm || !name) {
+ return;
+ }
+
+
+ if(pwm && pwm_compare) {
+ ch0_prescaler_diff = (const char *)(pwm->ch0_prescaler != pwm_compare->ch0_prescaler ? " ******" : "");
+ ch0_en_diff = (const char *)(pwm->ch0_en != pwm_compare->ch0_en ? " ******" : "");
+ ch0_act_state_diff = (const char *)(pwm->ch0_act_state != pwm_compare->ch0_act_state ? " ******" : "");
+ ch0_clk_gating_diff = (const char *)(pwm->ch0_clk_gating != pwm_compare->ch0_clk_gating ? " ******" : "");
+ ch0_mode_diff = (const char *)(pwm->ch0_mode != pwm_compare->ch0_mode ? " ******" : "");
+ ch0_pulse_start_diff = (const char *)(pwm->ch0_pulse_start != pwm_compare->ch0_pulse_start ? " ******" : "");
+
+ ch1_prescaler_diff = (const char *)(pwm->ch1_prescaler != pwm_compare->ch1_prescaler ? " ******" : "");
+ ch1_en_diff = (const char *)(pwm->ch1_en != pwm_compare->ch1_en ? " ******" : "");
+ ch1_act_state_diff = (const char *)(pwm->ch1_act_state != pwm_compare->ch1_act_state ? " ******" : "");
+ ch1_clk_gating_diff = (const char *)(pwm->ch1_clk_gating != pwm_compare->ch1_clk_gating ? " ******" : "");
+ ch1_mode_diff = (const char *)(pwm->ch1_mode != pwm_compare->ch1_mode ? " ******" : "");
+ ch1_pulse_start_diff = (const char *)(pwm->ch1_pulse_start != pwm_compare->ch1_pulse_start ? " ******" : "");
+ }
+
+
+ printk(KERN_INFO "%s: ch0_prescaler: 0x%x %s",name,pwm->ch0_prescaler,ch0_prescaler_diff);
+ printk(KERN_INFO "%s: ch0_en: 0x%x %s",name,pwm->ch0_en,ch0_en_diff);
+ printk(KERN_INFO "%s: ch0_act_state: 0x%x %s",name,pwm->ch0_act_state,ch0_act_state_diff);
+ printk(KERN_INFO "%s: ch0_clk_gating: 0x%x %s",name,pwm->ch0_clk_gating,ch0_clk_gating_diff);
+ printk(KERN_INFO "%s: ch0_mode: 0x%x %s",name,pwm->ch0_mode,ch0_mode_diff);
+ printk(KERN_INFO "%s: ch0_pulse_start: 0x%x %s",name,pwm->ch0_pulse_start,ch0_pulse_start_diff);
+
+
+ printk(KERN_INFO "%s: ch1_prescaler: 0x%x %s",name,pwm->ch1_prescaler,ch1_prescaler_diff);
+ printk(KERN_INFO "%s: ch1_en: 0x%x %s",name,pwm->ch1_en,ch1_en_diff);
+ printk(KERN_INFO "%s: ch1_act_state: 0x%x %s",name,pwm->ch1_act_state,ch1_act_state_diff);
+ printk(KERN_INFO "%s: ch1_clk_gating: 0x%x %s",name,pwm->ch1_clk_gating,ch1_clk_gating_diff);
+ printk(KERN_INFO "%s: ch1_mode: 0x%x %s",name,pwm->ch1_mode,ch1_mode_diff);
+ printk(KERN_INFO "%s: ch1_pulse_start: 0x%x %s",name,pwm->ch1_pulse_start,ch1_pulse_start_diff);
+
+
+ printk(KERN_INFO "%s: ch1_ctrl_reg: 0x%x",name,*(unsigned int *)pwm);
+ printk(KERN_INFO "%s: sizeof(*pwm) = %d",name,sizeof(*pwm));
+}
+
+
+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 = 50;
+ *(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>");
diff --git a/drivers/misc/pwm-sunxi.h b/drivers/misc/pwm-sunxi.h
new file mode 100644
index 0000000..4f24c13
--- /dev/null
+++ b/drivers/misc/pwm-sunxi.h
@@ -0,0 +1,198 @@
+/*
+ * pwm-sunxi.h
+ *
+ * (C) Copyright 2013
+ * David H. Wilkins <
dwil...@conecuh.com>
+ *
+ * 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
+ */
+
+
+#define SUN4I_PWM_IOREG_MAX 10
+#define SUN4I_MAX_HARDWARE_PWM_CHANNELS 2
+
+/*
+ * structure that defines the pwm control register
+ */
+
+enum sun4i_pwm_prescale {
+ PRESCALE_DIV120 = 0x00, /* Divide 24mhz clock by 120 */
+ PRESCALE_DIV180 = 0x01,
+ PRESCALE_DIV240 = 0x02,
+ PRESCALE_DIV360 = 0x03,
+ PRESCALE_DIV480 = 0x04,
+ PRESCALE_INVx05 = 0x05,
+ PRESCALE_INVx06 = 0x06,
+ PRESCALE_INVx07 = 0x07,
+ PRESCALE_DIV12k = 0x08,
+ PRESCALE_DIV24k = 0x09,
+ PRESCALE_DIV36k = 0x0a,
+ PRESCALE_DIV48k = 0x0b,
+ PRESCALE_DIV72k = 0x0c
+};
+
+
+
+
+struct sun4i_pwm_ctrl {
+ enum sun4i_pwm_prescale ch0_prescaler:4; /* ch0 Prescale register - values above */
+ unsigned int ch0_en:1; /* chan 0 enable */
+ unsigned int ch0_act_state:1; /* chan 0 polarity 0=low, 1=high */
+ unsigned int ch0_clk_gating:1; /* Allow clock to run for chan 0 */
+ unsigned int ch0_mode:1; /* Mode - 0 = cycle(running), 1=only 1 pulse */
+ unsigned int ch0_pulse_start:1; /* Write 1 for mode pulse above to start */
+ unsigned int unused1:6; /* The bit skip count is 6 */
+ enum sun4i_pwm_prescale ch1_prescaler:4; /* ch1 Prescale register - values above*/
+ unsigned int ch1_en:1; /* chan 1 enable */
+ unsigned int ch1_act_state:1; /* chan 1 polarity 0=low, 1=high */
+ unsigned int ch1_clk_gating:1; /* Allow clock to run for chan 1 */
+ unsigned int ch1_mode:1; /* Mode - 0 = cycle(running), 1=only 1 pulse */
+ unsigned int ch1_pulse_start:1; /* Write 1 for mode pulse above to start */
+ unsigned int unused2:6; /* The bit skip count is 6 */
+};
+
+
+#define A10CLK 24000000 /* Speed of the clock - 24mhz */
+
+#define NO_ENABLE_CHANGE 2 /* Signal to set_pwm_mode to keep the same chan enable bit */
+
+#define PWM_CTRL_ENABLE 1
+#define PWM_CTRL_DISABLE 0
+
+#define MAX_CYCLES 0x0ffff /* max cycle count possible for period active and entire */
+struct sun4i_pwm_period {
+#if MAX_CYCLES > 0x0ff
+ unsigned int pwm_active_cycles:16; /* duty cycle */
+ unsigned int pwm_entire_cycles:16; /* period */
+#else
+ unsigned int pwm_active_cycles:8; /* duty cycle */
+ unsigned int unused1:8;
+ unsigned int pwm_entire_cycles:8; /* period */
+ unsigned int unused2:8;
+#endif
+};
+
+
+enum sun4i_ioreg_pin_select {
+ SELECT_INPUT = 0x00, /* bits for the config registers */
+ SELECT_OUTPUT = 0x01,
+ SELECT_PWM = 0x02,
+ SELECT_SPI2_CLK = 0x02,
+ SELECT_I2S_LRCK = 0x02,
+ SELECT_I2S_BCLK = 0x02
+};
+
+
+struct sun4i_ioreg_cfg0 {
+ enum sun4i_ioreg_pin_select pin0_select:4;
+ enum sun4i_ioreg_pin_select pin1_select:4;
+ enum sun4i_ioreg_pin_select pin2_select:4;
+ enum sun4i_ioreg_pin_select pin3_select:4;
+ enum sun4i_ioreg_pin_select pin4_select:4;
+ enum sun4i_ioreg_pin_select pin5_select:4;
+ enum sun4i_ioreg_pin_select pin6_select:4;
+ enum sun4i_ioreg_pin_select pin7_select:4;
+};
+
+/*
+ * another duplicate struct to make the pin names
+ * look right
+ */
+struct sun4i_ioreg_cfg1 {
+ enum sun4i_ioreg_pin_select pin8_select:4;
+ enum sun4i_ioreg_pin_select pin9_select:4;
+ enum sun4i_ioreg_pin_select pin10_select:4;
+ enum sun4i_ioreg_pin_select pin11_select:4;
+ enum sun4i_ioreg_pin_select pin12_select:4;
+ enum sun4i_ioreg_pin_select pin13_select:4;
+ enum sun4i_ioreg_pin_select pin14_select:4;
+ enum sun4i_ioreg_pin_select pin15_select:4;
+};
+
+
+struct ioreg_pull {
+ unsigned int pin0:2;
+ unsigned int pin1:2;
+ unsigned int pin2:2;
+ unsigned int pin3:2;
+ unsigned int pin4:2;
+ unsigned int pin5:2;
+ unsigned int pin6:2;
+ unsigned int pin7:2;
+ unsigned int pin8:2;
+ unsigned int pin9:2;
+ unsigned int pin10:2;
+ unsigned int pin11:2;
+ unsigned int pin12:2;
+ unsigned int pin13:2;
+ unsigned int pin14:2;
+ unsigned int pin15:2;
+};
+
+union ioreg_pull_u {
+ struct ioreg_pull s;
+ unsigned int initializer;
+};
+
+
+union sun4i_pwm_ctrl_u {
+ struct sun4i_pwm_ctrl s;
+ unsigned int initializer;
+};
+
+union sun4i_pwm_period_u {
+ struct sun4i_pwm_period s;
+ unsigned int initializer;
+};
+
+union sun4i_ioreg_cfg_u {
+ struct sun4i_ioreg_cfg0 s0; /* io register config 0 */
+ struct sun4i_ioreg_cfg1 s1; /* io register config 1 (just to make pin names look nice) */
+ unsigned int initializer;
+};
+
+
+
+struct sun4i_pwm_available_channel{
+ unsigned int use_count;
+ void *ctrl_addr; /* Address of the control register */
+ void *pin_addr; /* Address of the pin register to change to PWM mode */
+ void *period_reg_addr; /* Address of the period register for this chan */
+ unsigned int channel; /* Channel number */
+ unsigned long period; /* Period in microseconds */
+ unsigned long duty; /* duty cycle in microseconds */
+ unsigned int duty_percent; /* percentage (drives duty microseconds if set) */
+ enum sun4i_pwm_prescale prescale; /* best prescale value computed for period */
+ union sun4i_pwm_period_u period_reg; /* period register */
+ union sun4i_pwm_ctrl_u ctrl_backup; /* control register backup at init */
+ union sun4i_pwm_ctrl_u ctrl_mask; /* mask for ctrl register bit we can change */
+ union sun4i_pwm_ctrl_u ctrl_current; /* current control register settings */
+ union sun4i_ioreg_cfg_u pin_backup; /* pin backup at init */
+ union sun4i_ioreg_cfg_u pin_mask; /* mask of pin settings we can change */
+ union sun4i_ioreg_cfg_u pin_current; /* current pin register */
+ const char *pin_name; /* name of the pin */
+ const char *name; /* name of the pwm device from the pwm i/f */
+};
+
+/*
+ * struct used to implement the hz/khz/ms/us etc for period and duty
+ */
+struct time_suffix {
+ char * suffix; /* text suffix */
+ unsigned long multiplier; /* multiplier for the entered value */
+ bool freq; /* true if a frequency, otherwise a time */
+ /* T = 1/f and f = 1/T */
+};
diff --git a/drivers/video/sunxi/disp/disp_lcd.c b/drivers/video/sunxi/disp/disp_lcd.c
index ddf1de9..2c3f659 100644
--- a/drivers/video/sunxi/disp/disp_lcd.c
+++ b/drivers/video/sunxi/disp/disp_lcd.c
@@ -770,7 +770,6 @@ pwm_enable(__u32 channel, __bool b_en)
return 0;
}
-EXPORT_SYMBOL(pwm_enable);
#ifdef CONFIG_ARCH_SUN4I
/*
--
1.7.11.7