From edf17a0a57fbd35138eef33b5668984bd35ae71b Mon Sep 17 00:00:00 2001 From: olegart Date: Thu, 7 May 2015 11:56:00 +0300 Subject: [PATCH] Initial release --- Makefile | 45 +++++ src/Makefile | 1 + src/gpio-sqwave.c | 426 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 Makefile create mode 100644 src/Makefile create mode 100644 src/gpio-sqwave.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a763ab5 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# +# Copyright (C) 2008-2012 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=gpio-meander +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/gpio-sqwave + SUBMENU:=Other modules + DEPENDS:=@!LINUX_3_3 + TITLE:=Timer IRQ handler + FILES:=$(PKG_BUILD_DIR)/gpio-sqwave.ko + AUTOLOAD:=$(call AutoLoad,30,gpio-sqwave,1) + KCONFIG:= +endef + +define KernelPackage/gpio-sqwave/description + This is GPIO square wave generator for AR9331 devices. +endef + +MAKE_OPTS:= \ + ARCH="$(LINUX_KARCH)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + SUBDIRS="$(PKG_BUILD_DIR)" + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + $(MAKE_OPTS) \ + modules +endef + +$(eval $(call KernelPackage,gpio-sqwave)) diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..17a1a11 --- /dev/null +++ b/src/Makefile @@ -0,0 +1 @@ +obj-m += gpio-sqwave.o diff --git a/src/gpio-sqwave.c b/src/gpio-sqwave.c new file mode 100644 index 0000000..f6d6c46 --- /dev/null +++ b/src/gpio-sqwave.c @@ -0,0 +1,426 @@ +/* + * GPIO square wave generator for AR9331 + * + * Copyright (C) 2013-2015 Gerhard Bertelsmann + * Copyright (C) 2015 Dmitriy Zherebkov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////// + +#define DRV_NAME "gpio-sqwave" +#define FILE_NAME "sqwave" + +//////////////////////////////////////////////////////////////////////////////////////////// + +static int timer=3; +module_param(timer, int, 0); +MODULE_PARM_DESC(timer, "system timer number (0-3)"); + +//////////////////////////////////////////////////////////////////////////////////////////// + +//#define DEBUG_OUT + +#ifdef DEBUG_OUT +#define debug(fmt,args...) printk (KERN_INFO DRV_NAME ": " fmt ,##args) +#else +#define debug(fmt,args...) +#endif /* DEBUG_OUT */ + +static unsigned int _timer_frequency=100000000; +static spinlock_t _lock; +static unsigned int _gpio_prev=0; + +//////////////////////////////////////////////////////////////////////////////////////////// + +#define ATH79_TIMER0_IRQ ATH79_MISC_IRQ(0) +#define AR71XX_TIMER0_RELOAD 0x04 + +#define ATH79_TIMER1_IRQ ATH79_MISC_IRQ(8) +#define AR71XX_TIMER1_RELOAD 0x98 + +#define ATH79_TIMER2_IRQ ATH79_MISC_IRQ(9) +#define AR71XX_TIMER2_RELOAD 0xA0 + +#define ATH79_TIMER3_IRQ ATH79_MISC_IRQ(10) +#define AR71XX_TIMER3_RELOAD 0xA8 + +struct _timer_desc_struct +{ + unsigned int irq; + unsigned int reload_reg; +} _timers[4]= +{ + { ATH79_TIMER0_IRQ, AR71XX_TIMER0_RELOAD }, + { ATH79_TIMER1_IRQ, AR71XX_TIMER1_RELOAD }, + { ATH79_TIMER2_IRQ, AR71XX_TIMER2_RELOAD }, + { ATH79_TIMER3_IRQ, AR71XX_TIMER3_RELOAD } +}; + +//////////////////////////////////////////////////////////////////////////////////////////// + +#define GPIO_OFFS_READ 0x04 +#define GPIO_OFFS_SET 0x0C +#define GPIO_OFFS_CLEAR 0x10 + +//////////////////////////////////////////////////////////////////////////////////////////// + +void __iomem *ath79_timer_base=NULL; + +void __iomem *gpio_addr=NULL; +void __iomem *gpio_readdata_addr=NULL; +void __iomem *gpio_setdataout_addr=NULL; +void __iomem *gpio_cleardataout_addr=NULL; + +//////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int timer; + int irq; + int gpio; + unsigned int frequency; + int value; +} _timer_handler; + +static _timer_handler _handler; + +static struct dentry* in_file; + +//////////////////////////////////////////////////////////////////////////////////////////// + +static int is_space(char symbol) +{ + return (symbol == ' ') || (symbol == '\t'); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static int is_digit(char symbol) +{ + return (symbol >= '0') && (symbol <= '9'); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static irqreturn_t timer_interrupt(int irq, void* dev_id) +{ + _timer_handler* handler=(_timer_handler*)dev_id; + + if(handler && (handler->irq == irq) && (handler->gpio >= 0)) + { + if(handler->value) + { + __raw_writel(1 << handler->gpio, gpio_setdataout_addr); + } + else + { + __raw_writel(1 << handler->gpio, gpio_cleardataout_addr); + } + + handler->value=!handler->value; + } + + return(IRQ_HANDLED); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static int add_irq(void* data) +{ + int err=0; + int irq_number=_timers[timer].irq; + + debug("Adding IRQ %d handler\n",irq_number); + + err=request_irq(irq_number, timer_interrupt, 0, DRV_NAME, data); + + if(!err) + { + debug("Got IRQ %d.\n", irq_number); + return irq_number; + } + else + { + debug("Timer IRQ handler: trouble requesting IRQ %d error %d\n",irq_number, err); + } + + return -1; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static void stop(void) +{ + unsigned long flags=0; + + spin_lock_irqsave(&_lock,flags); + + if(_handler.irq >= 0) + { + free_irq(_handler.irq, (void*)&_handler); + _handler.irq=-1; + + _handler.timer=-1; + + 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); + } + + gpio_free(_handler.gpio); + _handler.gpio=-1; + } + + _handler.frequency=0; + _handler.value=0; + + debug("Timer stopped.\n"); + } + + spin_unlock_irqrestore(&_lock,flags); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static int start(int gpio,unsigned int frequency) +{ + unsigned int timeout=0; + int irq=-1; + unsigned long flags=0; + + stop(); + + 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); + + irq=add_irq(&_handler); + + if(irq >= 0) + { + _handler.timer=timer; + _handler.irq=irq; + + gpio_request(gpio, DRV_NAME); + if(gpio_direction_output(gpio,0) == 0) + { + _handler.gpio=gpio; + + // 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; + + debug("New frequency: %u.\n", frequency); + + spin_unlock_irqrestore(&_lock,flags); + return 0; + } + else + { + debug("Can't set GPIO %d as output.\n", gpio); + } + } + + spin_unlock_irqrestore(&_lock,flags); + + stop(); + return -1; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static ssize_t run_command(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char buffer[512]; + char line[20]; + char* in_pos=NULL; + char* end=NULL; + char* out_pos=NULL; + + if(count > 512) + return -EINVAL; // file is too big + + copy_from_user(buffer, buf, count); + buffer[count]=0; + + debug("Command is found (%u bytes length):\n%s\n",count,buffer); + + in_pos=buffer; + end=in_pos+count-1; + + while(in_pos < end) + { + unsigned int gpio=-1; + unsigned int frequency=0; + + while((in_pos < end) && is_space(*in_pos)) ++in_pos; // skip whitespace + if(in_pos >= end) break; + + if(*in_pos == '-') + { + stop(); + return count; + } + else if(*in_pos == '?') + { + if(_handler.frequency) + { + printk(KERN_INFO DRV_NAME " is running on GPIO %d with frequency %u Hz (system timer %d).\n", + _handler.gpio,_handler.frequency,_handler.timer); + } + else + { + printk(KERN_INFO DRV_NAME " is not running (timer %d selected).\n",timer); + } + + 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; + + if(is_digit(line[0])) + { + sscanf(line, "%u", &frequency); + } + else + { + printk(KERN_INFO DRV_NAME " can't read frequency.\n"); + break; + } + + if((gpio >= 0) && (frequency > 0)) + { + if(start(gpio,frequency) >= 0) + { + debug("Started!\n"); + break; + } + } + + printk(KERN_INFO DRV_NAME " can't start.\n"); + return 0; + } + + return count; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static const struct file_operations irq_fops = +{ + .write = run_command, +}; + +//////////////////////////////////////////////////////////////////////////////////////////// + +struct clk // defined in clock.c +{ + unsigned long rate; +}; + +//////////////////////////////////////////////////////////////////////////////////////////// + +static int __init mymodule_init(void) +{ + struct clk* ahb_clk=clk_get(NULL,"ahb"); + if(ahb_clk) + { + _timer_frequency=ahb_clk->rate; + } + + ath79_timer_base=ioremap_nocache(AR71XX_RESET_BASE, AR71XX_RESET_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; + + _handler.timer=-1; + _handler.irq=-1; + _handler.gpio=-1; + _handler.frequency=0; + _handler.value=0; + + in_file=debugfs_create_file(FILE_NAME, 0200, NULL, NULL, &irq_fops); + + debug("System timer #%d frequency is %d Hz.\n",timer,_timer_frequency); + printk(KERN_INFO DRV_NAME " is waiting for commands in file /sys/kernel/debug/" FILE_NAME ".\n"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static void __exit mymodule_exit(void) +{ + stop(); + + debugfs_remove(in_file); + + return; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +module_init(mymodule_init); +module_exit(mymodule_exit); + +//////////////////////////////////////////////////////////////////////////////////////////// + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dmitriy Zherebkov (Black Swift team)"); + +////////////////////////////////////////////////////////////////////////////////////////////