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