diff --git a/Makefile b/Makefile index a763ab5..9796ddf 100644 --- a/Makefile +++ b/Makefile @@ -8,22 +8,22 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk -PKG_NAME:=gpio-meander +PKG_NAME:=gpio-pwm-ar9331 PKG_RELEASE:=1 include $(INCLUDE_DIR)/package.mk -define KernelPackage/gpio-sqwave +define KernelPackage/gpio-pwm-ar9331 SUBMENU:=Other modules DEPENDS:=@!LINUX_3_3 - TITLE:=Timer IRQ handler - FILES:=$(PKG_BUILD_DIR)/gpio-sqwave.ko + TITLE:=PWM on gpio for AR9331 + FILES:=$(PKG_BUILD_DIR)/gpio-pwm-ar9331.ko AUTOLOAD:=$(call AutoLoad,30,gpio-sqwave,1) KCONFIG:= endef define KernelPackage/gpio-sqwave/description - This is GPIO square wave generator for AR9331 devices. + This is GPIO PWM generator for AR9331 devices. endef MAKE_OPTS:= \ @@ -42,4 +42,4 @@ define Build/Compile modules endef -$(eval $(call KernelPackage,gpio-sqwave)) +$(eval $(call KernelPackage,gpio-pwm-ar9331)) diff --git a/README.md b/README.md index b7c301b..7460dc7 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,27 @@ -# gpio-sqwave -Square wave generator using AR9331 hardware timer. +# gpio-pwm-ar9331 +PWM generator using AR9331 hardware timer. -gpio-sqwave is an OpenWRT kernel module which provides square wave output on any AR9331 SoC GPIO pin with up to 125 kHz frequency. +gpio-pwm-ar9331 is an OpenWRT kernel module which provides PWM output on any AR9331 SoC GPIO pin with up to 125 kHz frequency. -##Installation -*insmod gpio-sqwave [timer=0|1|2|3]*
+##Intallation +1. Download binary package from [releases section](https://github.com/ASMfreaK/gpio-pwm-ar9331/releases) or build it using OpenWRT Buildroot. +2. Upload package to device using your favorite method. +3. Run `opkg install kmod-gpio-pwm-ar9331_.ipk` where `` is version of a package. If it fails, rerun command with `--force-depends` flag i.e. `opkg install --force-depends kmod-gpio-pwm-ar9331_.ipk` + + +##Initialisation +`insmod gpio-sqwave [timer=0|1|2|3]`
Default is timer 3 ##Usage -**Start**: *echo "<GPIO> <freq>" > /sys/kernel/debug/sqwave*
-"*GPIO*" is GPIO number, "*freq*" is square wave frequency in Hz. Maximum frequency with 200 MHz system bus (default clock) is 125 kHz; high settings may result in performance penalty or watchdog reset. +**Start**: `echo " " > /sys/kernel/debug/pwm-ar9331`
+`GPIO` is GPIO number, `freq` is square wave frequency in Hz, `pos` is a number between 0 and 65536 representing duty cycle. At different frequencies it can generate up to 16 bit PWM Maximum frequency with 200 MHz system bus (default clock) is 125 kHz; high settings may result in performance penalty or watchdog reset. -**Example**: to blink Black Swift's system LED with 1 Hz frequency: *echo "27 1" > /sys/kernel/debug/sqwave* +**Example**: to blink Black Swift's system LED with 1 Hz frequency at around 1/2 duty cycle: `echo "27 1 3000" > /sys/kernel/debug/pwm-ar9331` -**Stop**: *echo - > /sys/kernel/debug/sqwave* +**Stop**: `echo - > /sys/kernel/debug/pwm-ar9331` -**Status**: *echo ? > /sys/kernel/debug/sqwave* +**Status**: `echo ? > /sys/kernel/debug/pwm-ar9331` -Check http://www.black-swift.com/wiki for more information. diff --git a/src/Makefile b/src/Makefile index 17a1a11..b6a2eb8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1 +1 @@ -obj-m += gpio-sqwave.o +obj-m += gpio-pwm-ar9331.o diff --git a/src/gpio-sqwave.c b/src/gpio-pwm-ar9331.c similarity index 69% rename from src/gpio-sqwave.c rename to src/gpio-pwm-ar9331.c index f6d6c46..d0bc319 100644 --- a/src/gpio-sqwave.c +++ b/src/gpio-pwm-ar9331.c @@ -29,8 +29,8 @@ //////////////////////////////////////////////////////////////////////////////////////////// -#define DRV_NAME "gpio-sqwave" -#define FILE_NAME "sqwave" +#define DRV_NAME "gpio-pwm-ar9331" +#define FILE_NAME "pwm-ar9331" //////////////////////////////////////////////////////////////////////////////////////////// @@ -48,7 +48,7 @@ MODULE_PARM_DESC(timer, "system timer number (0-3)"); #define debug(fmt,args...) #endif /* DEBUG_OUT */ -static unsigned int _timer_frequency=100000000; +static unsigned int _timer_frequency=200000000; static spinlock_t _lock; static unsigned int _gpio_prev=0; @@ -84,6 +84,8 @@ struct _timer_desc_struct #define GPIO_OFFS_SET 0x0C #define GPIO_OFFS_CLEAR 0x10 +#define PWM_MAX 65536 + //////////////////////////////////////////////////////////////////////////////////////////// void __iomem *ath79_timer_base=NULL; @@ -97,11 +99,16 @@ void __iomem *gpio_cleardataout_addr=NULL; typedef struct { - int timer; - int irq; - int gpio; + int timer; + int irq; + int gpio; unsigned int frequency; - int value; + unsigned int timeout_total; + unsigned int timeout_front; + unsigned int timeout_back; + unsigned int new_pos; + int update; + int value; } _timer_handler; static _timer_handler _handler; @@ -110,6 +117,41 @@ static struct dentry* in_file; //////////////////////////////////////////////////////////////////////////////////////////// +inline void set_timer_reload(int timer, unsigned int freq) +{ + __raw_writel(freq, ath79_timer_base+_timers[timer].reload_reg); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +inline void set_gpio_value(int gpio, int val) +{ + if(val) + { + __raw_writel(1 << gpio, gpio_setdataout_addr); + } + else + { + __raw_writel(1 << gpio, gpio_cleardataout_addr); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +void recalculate_timeouts(_timer_handler* handler){ + unsigned int flags = 0; + spin_lock_irqsave(&_lock,flags); + handler->timeout_total=_timer_frequency/handler->frequency; + debug("New timeout: %u\n",handler->timeout_total); + handler->timeout_front =(unsigned int)(((unsigned long long)handler->timeout_total * (unsigned long long)handler->new_pos) / PWM_MAX); + debug("New front timeout: %u\n",handler->timeout_front); + handler->timeout_back = handler->timeout_total - handler->timeout_front; + debug("New back timeout: %u\n",handler->timeout_back); + spin_unlock_irqrestore(&_lock,flags); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + static int is_space(char symbol) { return (symbol == ' ') || (symbol == '\t'); @@ -130,15 +172,21 @@ static irqreturn_t timer_interrupt(int irq, void* dev_id) if(handler && (handler->irq == irq) && (handler->gpio >= 0)) { + if(handler->update == 1) + { + handler->update = 0; + recalculate_timeouts(handler); + } if(handler->value) { __raw_writel(1 << handler->gpio, gpio_setdataout_addr); + set_timer_reload(handler->timer, handler->timeout_back); } else { __raw_writel(1 << handler->gpio, gpio_cleardataout_addr); + set_timer_reload(handler->timer, handler->timeout_front); } - handler->value=!handler->value; } @@ -186,16 +234,7 @@ static void stop(void) if(_handler.gpio >= 0) { - // restore previous GPIO state - if(_gpio_prev) - { - __raw_writel(1 << _handler.gpio,gpio_setdataout_addr); - } - else - { - __raw_writel(1 << _handler.gpio,gpio_cleardataout_addr); - } - + set_gpio_value(_handler.gpio, _gpio_prev); gpio_free(_handler.gpio); _handler.gpio=-1; } @@ -211,9 +250,8 @@ static void stop(void) //////////////////////////////////////////////////////////////////////////////////////////// -static int start(int gpio,unsigned int frequency) +static int start(int gpio,unsigned int frequency,unsigned int pos) { - unsigned int timeout=0; int irq=-1; unsigned long flags=0; @@ -221,7 +259,7 @@ static int start(int gpio,unsigned int frequency) spin_lock_irqsave(&_lock,flags); // need some time (10 ms) before first IRQ - even after "lock"?! - __raw_writel(_timer_frequency/100, ath79_timer_base+_timers[timer].reload_reg); + set_timer_reload(timer, _timer_frequency/100); irq=add_irq(&_handler); @@ -238,10 +276,11 @@ static int start(int gpio,unsigned int frequency) // save current GPIO state _gpio_prev=__raw_readl(gpio_readdata_addr+GPIO_OFFS_READ) & (1 << gpio); - timeout=_timer_frequency/frequency/2; - __raw_writel(timeout, ath79_timer_base+_timers[timer].reload_reg); - _handler.frequency=frequency; + _handler.frequency = frequency; + _handler.new_pos = pos; + recalculate_timeouts(&_handler); + set_timer_reload(timer, _handler.timeout_front); debug("New frequency: %u.\n", frequency); @@ -270,6 +309,7 @@ static ssize_t run_command(struct file *file, const char __user *buf, char* in_pos=NULL; char* end=NULL; char* out_pos=NULL; + unsigned int flags = 0; if(count > 512) return -EINVAL; // file is too big @@ -286,6 +326,7 @@ static ssize_t run_command(struct file *file, const char __user *buf, { unsigned int gpio=-1; unsigned int frequency=0; + unsigned int pos=0; while((in_pos < end) && is_space(*in_pos)) ++in_pos; // skip whitespace if(in_pos >= end) break; @@ -295,7 +336,7 @@ static ssize_t run_command(struct file *file, const char __user *buf, stop(); return count; } - else if(*in_pos == '?') + if(*in_pos == '?') { if(_handler.frequency) { @@ -309,46 +350,66 @@ static ssize_t run_command(struct file *file, const char __user *buf, break; } + if(*in_pos == '+'){ + ++in_pos; + while((in_pos < end) && is_space(*in_pos)) ++in_pos; // skip whitespace + if(in_pos >= end) break; - out_pos=line; - while((in_pos < end) && is_digit(*in_pos)) *out_pos++=*in_pos++; - *out_pos=0; - - if(is_digit(line[0])) - { - sscanf(line, "%d", &gpio); - } - else - { - printk(KERN_INFO DRV_NAME " can't read GPIO number.\n"); - break; - } - - while((in_pos < end) && is_space(*in_pos)) ++in_pos; // skip whitespace + out_pos=line; + while((in_pos < end) && is_digit(*in_pos)) *out_pos++=*in_pos++; + *out_pos=0; - out_pos=line; - while((in_pos < end) && is_digit(*in_pos)) *out_pos++=*in_pos++; - *out_pos=0; + if(is_digit(line[0])) + { + sscanf(line, "%d", &gpio); + } + else + { + printk(KERN_INFO DRV_NAME " can't read GPIO number.\n"); + break; + } + + while((in_pos < end) && is_space(*in_pos)) ++in_pos; // skip whitespace + + out_pos=line; + while((in_pos < end) && is_digit(*in_pos)) *out_pos++=*in_pos++; + *out_pos=0; + + if(is_digit(line[0])) + { + sscanf(line, "%u", &frequency); + } + else + { + printk(KERN_INFO DRV_NAME " can't read frequency.\n"); + break; + } + + while((in_pos < end) && is_space(*in_pos)) ++in_pos; // skip whitespace - if(is_digit(line[0])) - { - sscanf(line, "%u", &frequency); - } - else - { - printk(KERN_INFO DRV_NAME " can't read frequency.\n"); - break; - } + out_pos=line; + while((in_pos < end) && is_digit(*in_pos)) *out_pos++=*in_pos++; + *out_pos=0; - if((gpio >= 0) && (frequency > 0)) - { - if(start(gpio,frequency) >= 0) + if(is_digit(line[0])) + { + sscanf(line, "%u", &pos); + } + else { - debug("Started!\n"); + printk(KERN_INFO DRV_NAME " can't read pos.\n"); break; } - } + if((gpio >= 0) && (frequency > 0)) + { + if(start(gpio,frequency,pos) >= 0) + { + debug("Started!\n"); + break; + } + } + } printk(KERN_INFO DRV_NAME " can't start.\n"); return 0; } @@ -380,13 +441,13 @@ static int __init mymodule_init(void) _timer_frequency=ahb_clk->rate; } - ath79_timer_base=ioremap_nocache(AR71XX_RESET_BASE, AR71XX_RESET_SIZE); + ath79_timer_base=ioremap_nocache(AR71XX_RESET_BASE, AR71XX_RESET_SIZE); - gpio_addr=ioremap_nocache(AR71XX_GPIO_BASE, AR71XX_GPIO_SIZE); + gpio_addr=ioremap_nocache(AR71XX_GPIO_BASE, AR71XX_GPIO_SIZE); - gpio_readdata_addr = gpio_addr + GPIO_OFFS_READ; - gpio_setdataout_addr = gpio_addr + GPIO_OFFS_SET; - gpio_cleardataout_addr = gpio_addr + GPIO_OFFS_CLEAR; + gpio_readdata_addr = gpio_addr + GPIO_OFFS_READ; + gpio_setdataout_addr = gpio_addr + GPIO_OFFS_SET; + gpio_cleardataout_addr = gpio_addr + GPIO_OFFS_CLEAR; _handler.timer=-1; _handler.irq=-1;