sunxi PWM driver with script.bin support

1,672 views
Skip to first unread message

Almo Nito

unread,
Aug 10, 2013, 3:22:06 PM8/10/13
to linux...@googlegroups.com

Hey Guys, Please find my modified version of David H. Wilkins (https://groups.google.com/forum/#!topic/linux-sunxi/I81t60tLgcA) PWM driver attached for Linux 3.4 branch

 

Changes are:

- Added script.bin support for [pwm0_para] and [pwm1_para]: pwm_used, pwm_period, pwm_duty_percent

- Added initial setup based on script.bin settings

- Removed bug that caused the PWM to pause quickly when changing parameters

- Dropped debug/dump functions

 

 

Still open tasks:

TODO:

- 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

 

 

Pleas note that when using this driver you need to remove EXPORT_SYMBOL(pwm_enable); from drivers/video/sunxi/disp/disp_lcd.c b/drivers/video/sunxi/disp/disp_lcd.c

 

Also you need to set set  lcd_pwm_not_used = 1 and lcd_pwm_used = 0 in the [lcd0_para] section

 

Example config for script.bin:

 

[pwm0_para]

pwm_used = 1

pwm_period = 10000

pwm_duty_percent = 100

 

[pwm0_para]

pwm_used = 0

pwm_period = 1000

pwm_duty_percent = 100

 

I also attached a fex file that works with this pwm driver on Olimex A10s with A13 7” LCD

 

pwm-sunxi.c
pwm-sunxi.h
a10s-olinuxino-m_pwm.fex

Almo Nito

unread,
Aug 10, 2013, 3:37:34 PM8/10/13
to linux...@googlegroups.com

Please find the patch attached

--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

patch_pwm.diff

Arokux X

unread,
Aug 10, 2013, 5:47:13 PM8/10/13
to linux...@googlegroups.com
Hi Almo,

please send your patches inline enclosed into an e-mail.

Thanks,
Arokux

Almo Nito

unread,
Aug 10, 2013, 6:53:03 PM8/10/13
to linux...@googlegroups.com

Here you go

 

 

-------------------------------------------------

 

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig

index f16c278..8c08120 100644

--- a/drivers/misc/Kconfig

+++ b/drivers/misc/Kconfig

@@ -85,6 +85,17 @@ config ATMEL_PWM

                 purposes including software controlled power-efficient backlights

                 on LCD displays, motor control, and waveform generation.

+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 AB8500_PWM

               bool "AB8500 PWM support"

               depends on AB8500_CORE && ARCH_U8500

diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile

index f49d878..24a9408 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..1b2fc65

--- /dev/null

+++ b/drivers/misc/pwm-sunxi.c

@@ -0,0 +1,928 @@

+/* 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 <stefa...@voit-consulting.com>

+ * - Added script.bin support for [pwm0_para] and [pwm1_para]: pwm_used, pwm_period, pwm_duty_percent

+ * - Added initial setup based on script.bin settings

+ * - Removed bug that caused the PWM to pause quickly when changing parameters

+ * - Dropped debug/dump functions

+ *

+ * TODO:

+ * - 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 <pwm-sunxi.h>

+#include <linux/pwm.h>

+#include <linux/kdev_t.h>

+#include <plat/sys_config.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;

+

+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,init_enable,init_duty_percent;

+             int init_period;

+             struct sun4i_pwm_available_channel *chan;

+             int err = 0;

+

+        pwm_setup_available_channels();

+

+        return_val = class_register(&pwm_class);

+        if(return_val) {

+                class_unregister(&pwm_class);

+        }

+#ifdef SUNXI_PWM_DEBUG

+             else {

+                printk("pwm-sunxi: pwm_class.dev_kobj = %p\n",pwm_class.dev_kobj);

+        }

+#endif

+/*

+        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("pwm-sunxi: return from sysfs_create_group(pwm0) was %d\n",return_val);

+        }

+

+        return_val = sysfs_create_group(pwm1_kobj,&pwm_attr_group);

+        if(return_val) {

+                printk("pwm-sunxi: return from sysfs_create_group(pwm1) was %d\n",return_val);

+        }

+

+

+             //PWM 0

+             //printk("pwm-sunxi: configuring pwm0...\n");

+             chan = &pwm_available_chan[0];

+            

+             init_enable=0;

+             init_period=0;

+             init_duty_percent=100;

+

+            

+

+             err = script_parser_fetch("pwm0_para", "pwm_used", &init_enable,sizeof(init_enable)/sizeof(int));

+             if (err) {

+                             pr_err("%s script_parser_fetch '[pwm0_para]' 'pwm_used' err - disabling pwm0\n",      __func__);

+             }

+

+             if(init_enable) {

+            

+                             err = script_parser_fetch("pwm0_para", "pwm_period", &init_period,sizeof(init_period)/sizeof(int));

+                             if (err) {

+                                             pr_err("%s script_parser_fetch '[pwm0_para]' 'pwm_period' err - using 10000\n",           __func__);

+                                             init_period=10000;

+                             }

+

+                             err = script_parser_fetch("pwm0_para", "pwm_duty_percent", &init_duty_percent,sizeof(init_duty_percent)/sizeof(int));

+                             if (err) {

+                                             pr_err("%s script_parser_fetch '[pwm0_para]' 'pwm_duty_percent' err - using 100\n", __func__);

+                                             init_duty_percent=100;

+                             }

+

+                             chan->duty_percent=init_duty_percent;

+                             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);

+             }

+

+        printk("pwm-sunxi: pwm0 configured - enable: %d, period: %ld, duty_percent: %d, duty: %ld\n",init_enable,chan->period,chan->duty_percent,chan->duty);

+

+

+

+             //PWM 1

+             //printk("pwm-sunxi: configuring pwm1...\n");

+             chan = &pwm_available_chan[1];

+                            

+            

+             init_enable=0;

+             init_period=0;

+             init_duty_percent=100;

+

+            

+

+             err = script_parser_fetch("pwm1_para", "pwm_used", &init_enable,sizeof(init_enable)/sizeof(int));

+             if (err) {

+                             pr_err("%s script_parser_fetch '[pwm1_para]' 'pwm_used' err - disabling pwm1\n",      __func__);

+             }

+

+             if(init_enable) {

+            

+                             err = script_parser_fetch("pwm1_para", "pwm_period", &init_period,sizeof(init_period)/sizeof(int));

+                             if (err) {

+                                             pr_err("%s script_parser_fetch '[pwm1_para]' 'pwm_period' err - using 10000\n",           __func__);

+                                             init_period=10000;

+                             }

+

+                             err = script_parser_fetch("pwm1_para", "pwm_duty_percent", &init_duty_percent,sizeof(init_duty_percent)/sizeof(int));

+                             if (err) {

+                                             pr_err("%s script_parser_fetch '[pwm1_para]' 'pwm_duty_percent' err - using 100\n", __func__);

+                                             init_duty_percent=100;

+                             }

+

+                             chan->duty_percent=init_duty_percent;

+                             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);

+             }

+

+        printk("pwm-sunxi: pwm1 configured - enable: %d, period: %ld, duty_percent: %d, duty: %ld\n",init_enable,chan->period,chan->duty_percent,chan->duty);

+

+

+

+

+             printk("pwm-sunxi: Initialized\n");

+        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;}

+#ifdef SUNXI_PWM_DEBUG

+        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;} */

+#ifdef SUNXI_PWM_DEBUG

+        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;}

+#ifdef SUNXI_PWM_DEBUG

+        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>");

diff --git a/drivers/misc/pwm-sunxi.h b/drivers/misc/pwm-sunxi.h

new file mode 100644

index 0000000..0a65606

--- /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 31e69bb..8040fb9 100644

--- a/drivers/video/sunxi/disp/disp_lcd.c

+++ b/drivers/video/sunxi/disp/disp_lcd.c

@@ -769,7 +769,6 @@ pwm_enable(__u32 channel, __bool b_en)

                return 0;

}

-EXPORT_SYMBOL(pwm_enable);

 #ifdef CONFIG_ARCH_SUN4I

/*

 

-------------------------------------------------

--

Hans de Goede

unread,
Aug 12, 2013, 9:17:56 AM8/12/13
to linux...@googlegroups.com, Almo Nito
Hi,

On 08/10/2013 09:22 PM, Almo Nito wrote:
> Hey Guys, Please find my modified version of David H. Wilkins (https://groups.google.com/forum/#!topic/linux-sunxi/I81t60tLgcA <https://groups.google.com/forum/#%21topic/linux-sunxi/I81t60tLgcA>) PWM driver attached for Linux 3.4 branch

Hmm, not the prettiest code, lots of coding style issues, next time please
run scripts/checkpatch.pl on the diff before submitting.

Anyways I've replaced all the 8 space tabs with real tabs, and added this
to my tree. I've also added a patch on top to only register the devices when
there actually is a pwmX_para sectoion in the fex file, as I don't think
it is a good idea to blindly always register these.

> I also attached a fex file that works with this pwm driver on Olimex A10s with A13 7� LCD

Why would you do that, the lcd / disp driver has its own builtin pwm code. Does that
not allow setting the brightness, or ... ?

Regards,

Hans

Almo Nito

unread,
Aug 12, 2013, 9:31:18 AM8/12/13
to linux...@googlegroups.com
> Why would you do that, the lcd / disp driver has its own builtin pwm code.
Does that not allow setting the brightness, or ... ?

I have not find it, all I was able to is on and off

Are you sure 3.4 kernel has that?

-----Ursprüngliche Nachricht-----
Auftrag von Hans de Goede
Gesendet: Montag, 12. August 2013 15:18
An: linux...@googlegroups.com
Cc: Almo Nito
Betreff: Re: [linux-sunxi] sunxi PWM driver with script.bin support

Hi,

On 08/10/2013 09:22 PM, Almo Nito wrote:
> Hey Guys, Please find my modified version of David H. Wilkins
> (https://groups.google.com/forum/#!topic/linux-sunxi/I81t60tLgcA
> <https://groups.google.com/forum/#%21topic/linux-sunxi/I81t60tLgcA>)
> PWM driver attached for Linux 3.4 branch

Hmm, not the prettiest code, lots of coding style issues, next time please
run scripts/checkpatch.pl on the diff before submitting.

Anyways I've replaced all the 8 space tabs with real tabs, and added this to
my tree. I've also added a patch on top to only register the devices when
there actually is a pwmX_para sectoion in the fex file, as I don't think it
is a good idea to blindly always register these.

> I also attached a fex file that works with this pwm driver on Olimex
> A10s with A13 7” LCD

Why would you do that, the lcd / disp driver has its own builtin pwm code.
Does that not allow setting the brightness, or ... ?

Regards,

Hans

Hans de Goede

unread,
Aug 12, 2013, 9:35:43 AM8/12/13
to linux...@googlegroups.com, Almo Nito
Hi,

On 08/12/2013 03:31 PM, Almo Nito wrote:
>> Why would you do that, the lcd / disp driver has its own builtin pwm code.
> Does that not allow setting the brightness, or ... ?
>
> I have not find it, all I was able to is on and off
>
> Are you sure 3.4 kernel has that?

Hmm, yes and no, I just checked the code, and you should be
able to add a "lcd0_brightness = 255" to the [disp_init]
section of the fex, and then by changing the 255 you can
change the brightness, but there is no runtime control...

Regards,

Hans

Almo Nito

unread,
Aug 12, 2013, 9:41:17 AM8/12/13
to linux...@googlegroups.com
Then it does make sense to use the pwm driver I guess.
Unless someone wants to add runtime support to the lcd one

-----Ursprüngliche Nachricht-----
Von: linux...@googlegroups.com [mailto:linux...@googlegroups.com] Im
Auftrag von Hans de Goede
Gesendet: Montag, 12. August 2013 15:36
An: linux...@googlegroups.com
Cc: Almo Nito
Betreff: Re: AW: [linux-sunxi] sunxi PWM driver with script.bin support

Hans de Goede

unread,
Aug 12, 2013, 9:47:23 AM8/12/13
to linux...@googlegroups.com, Almo Nito
Hi,

On 08/12/2013 03:41 PM, Almo Nito wrote:
> Then it does make sense to use the pwm driver I guess.
> Unless someone wants to add runtime support to the lcd one

Agreed, can you please create an a10s-olinuxino-m-lcd7.fex, based on
the latest a10s-olinuxino-m.fex from sunxi-boards (I pushed some
changes yesterday), test this with my latest sunxi-3.4 tree (which has
the only load the pwm driver if it is in the fex changes) and then send
it to me. Then I'll this to sunxi-boards, so that people who want to use
lcd will have a ready to use fex.

A patch to select-boards.sh to be able to add multiple entries for the
same board with different fex files would be welcome too, so that with
the next Fedora images users can simply select "A10S-OLinuXino-MICRO with
A13-LCD7 module" :)

Regards,

Hans

Almo Nito

unread,
Aug 12, 2013, 10:49:55 AM8/12/13
to linux...@googlegroups.com
Will create the fex and send it asap

-----Ursprüngliche Nachricht-----
Von: linux...@googlegroups.com [mailto:linux...@googlegroups.com] Im
Auftrag von Hans de Goede
Gesendet: Montag, 12. August 2013 15:47
An: linux...@googlegroups.com
Cc: Almo Nito
Betreff: Re: AW: AW: [linux-sunxi] sunxi PWM driver with script.bin support

Almo Nito

unread,
Aug 12, 2013, 10:50:50 AM8/12/13
to linux...@googlegroups.com
> A patch to select-boards.sh to be able to add multiple entries for the
same board with different fex files would be welcome too, so that with the
next Fedora images users can simply select "A10S-OLinuXino-MICRO with
A13-LCD7 module" :)

Can you please descruot what you mean by that?

-----Ursprüngliche Nachricht-----
Von: linux...@googlegroups.com [mailto:linux...@googlegroups.com] Im
Auftrag von Hans de Goede
Gesendet: Montag, 12. August 2013 15:47
An: linux...@googlegroups.com
Cc: Almo Nito
Betreff: Re: AW: AW: [linux-sunxi] sunxi PWM driver with script.bin support

Jari Helaakoski

unread,
Aug 12, 2013, 12:38:39 PM8/12/13
to linux...@googlegroups.com



2013/8/12 Hans de Goede <hdeg...@redhat.com>
I was planning some-kind generic PWM LCD driver which implements lcd sysfs inteface for lcd backlight. It should be mainlinable (if mainline has similar PWM framework).


-Jari

Jari Helaakoski

unread,
Aug 12, 2013, 1:48:32 PM8/12/13
to linux...@googlegroups.com


I was planning some-kind generic PWM LCD driver which implements lcd sysfs inteface for lcd backlight. It should be mainlinable (if mainline has similar PWM framework).

Hans de Goede

unread,
Aug 13, 2013, 3:46:34 AM8/13/13
to linux...@googlegroups.com, Almo Nito
Hi,

On 08/12/2013 04:50 PM, Almo Nito wrote:
>> A patch to select-boards.sh to be able to add multiple entries for the
> same board with different fex files would be welcome too, so that with the
> next Fedora images users can simply select "A10S-OLinuXino-MICRO with
> A13-LCD7 module" :)
>
> Can you please descruot what you mean by that?

Sure, if you look at the Fedora images, there is a directory hierarchy under
the uboot partition like this:
boards
boards/sun4i
boards/sun5i
boards/sun7i
...
boards/sun5i/a10s-olinuxino-m

And currently under boards/sun5i/a10s-olinuxino-m there are:

a10s-olinuxino-m.fex script.bin sunxi-spl.bin u-boot.bin

There is a script in the root of the boot partition called
select-board.sh :
https://github.com/jwrdegoede/sunxi-fedora-scripts/blob/master/select-board.sh

This uses the dialog command to show an ncurses menu to the user allowing
the user to select which board he has, and then automatically installs
the u-boot + script.bin + right kernel (sun4i, sun5i or sun7i) for the user.

Currently this script supports only 1 menu entry per board, it would be
nice to extend it so that there can be multiple entries for a single
board, selecting between different script.bin files, while using the same
u-boot, etc.

Then the menu for board selecting can list things like:

A10S-OLinuXino-MICRO
A10S-OLinuXino-MICRO with A13-LCD7 module
A10S-OLinuXino-MICRO with A13-LCD7TS module
A10S-OLinuXino-MICRO with A13-LCD10 module

Instead oh having just a single A10S-OLinuXino-MICRO entry, which defaults
to hdmi out.

And I was wondering if you would be willing to hack a bit on select-board.sh
to make this possible :)

Regards,

Hans

Hans de Goede

unread,
Aug 13, 2013, 3:47:18 AM8/13/13
to linux...@googlegroups.com, Jari Helaakoski
Hi,

On 08/12/2013 06:38 PM, Jari Helaakoski wrote:
>
>
>
> 2013/8/12 Hans de Goede <hdeg...@redhat.com <mailto:hdeg...@redhat.com>>
That indeed would be much better. Once you've patches for this I'll happily
merge them.

Regards,

Hans

Henrik Nordström

unread,
Aug 14, 2013, 4:25:12 AM8/14/13
to linux...@googlegroups.com
mån 2013-08-12 klockan 15:41 +0200 skrev Almo Nito:
> Then it does make sense to use the pwm driver I guess.
> Unless someone wants to add runtime support to the lcd one

Yes, when this was discussed previously the plans was to replace the LCD
specific one with the actual PWM driver.

Regards
Henrik

Oliver Schinagl

unread,
Aug 21, 2013, 4:12:18 AM8/21/13
to linux...@googlegroups.com
For mainline it's really easy and have it almost working, just need some
spare time now.

With mainline you write a PWM driver, and only that. In the DT you then
say if it's a backlight or not and the pwm/backlight framework sort it,
so no backlight driver needs to be written. IT's pretty neat ;)

oliver
>
>
> -Jari
Reply all
Reply to author
Forward
0 new messages