--- linux-org/arch/mips/au1000/mtx-2/Makefile 2006-05-01 13:34:12.664217250 +0200 +++ linux/arch/mips/au1000/mtx-2/Makefile 2006-05-01 13:33:35.609901500 +0200 @@ -15,6 +15,6 @@ O_TARGET := mtx-2.o -obj-y := init.o board_setup.o irqmap.o +obj-y := init.o board_setup.o irqmap.o slic.o include $(TOPDIR)/Rules.make --- linux-org/arch/mips/au1000/mtx-2/slic.c 1970-01-01 01:00:00.000000000 +0100 +++ linux/arch/mips/au1000/mtx-2/slic.c 2006-06-20 13:46:05.321244750 +0200 @@ -0,0 +1,704 @@ +/* + * Driver for the SLIC + * + * After the module is loaded there is a device /dev/misc/slic + * that can be read. A read returns if the DETECT-line has been toggled, + * indicating an offhook-event. + * + * Commands to be written to the device: + * P1 - Power on + * P0 - Power off + * ID - Idle state + * AC - Active state + * AR - Active state with reverse polarity + * R1 - Ringing on + * R0 - Ringing off + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/version.h> +#include <asm/uaccess.h> +#include <asm/au1000.h> +#include <linux/slic.h> + +/* #define DEBUG */ +#ifdef DEBUG +#define debug(args...) printk(args) +#else /* DEBUG */ +#define debug(args...) +#endif /* DEBUG */ + +/*---------[ declarations ]-----------------*/ + +#ifdef CONFIG_MIPS_MTX1 + +//#define SLIC_D0 (AU1500_GPIO_205) /* D0 - D3 */ +//#define SLIC_D1 (AU1500_GPIO_206) +//#define SLIC_D2 (AU1500_GPIO_208) +//#define SLIC_PD (AU1500_GPIO_209) /* Power down */ +#define SLIC_D0_GPIO (5) +#define SLIC_D1_GPIO (6) +#define SLIC_D2_GPIO (8) +#define SLIC_PD_GPIO (9) +#define SLIC_OH (AU1000_GPIO_8) /* Off hook */ +#define SLIC_OH_GPIO (8) /* GPIO-pin */ +#define SLIC_D0 (AU1500_GPIO_205) /* D0 - D3 */ +#define SLIC_D1 (AU1500_GPIO_206) +#define SLIC_D2 (AU1500_GPIO_208) +#define SLIC_PD (AU1500_GPIO_209) /* Power down */ +#define SLIC_DP_DIR (GPIO2_DIR) /* Direction-register */ +#define SLIC_DP_OUT (GPIO2_OUTPUT) /* Output-register */ +#define SLIC_DP_PINSTATE (GPIO2_PINSTATE) /* Input-register */ + +inline void gpio_init(void) { + au_writel(au_readl(SYS_TRIOUTCLR) & ~SLIC_OH_GPIO, SYS_TRIOUTCLR); // SLIC_OH_GPIO as tristate + au_writel((au_readl(SLIC_DP_DIR) | ((1 << SLIC_PD_GPIO)|(1 << SLIC_D0_GPIO)|(1 << SLIC_D1_GPIO)|(1 << SLIC_D2_GPIO))), SLIC_DP_DIR); +} + +inline void gpio_write(unsigned short bitmask, unsigned short value) { + au_writel((bitmask << 16) | value, SLIC_DP_OUT); +} +inline short gpio_read(unsigned short pin) { + return (au_readl(SLIC_DP_PINSTATE) >> pin) & 1; +} + +inline short gpio2_read(unsigned short pin) { + return (au_readl(SYS_PINSTATERD) >> pin) & 1; +} +#elif defined CONFIG_MIPS_MTX2 + +//#define SLIC_D0 (AU1000_GPIO_10) /* D0 - D3 */ +//#define SLIC_D1 (AU1000_GPIO_11) +//#define SLIC_D2 (AU1000_GPIO_12) +//#define SLIC_PD (AU1000_GPIO_13) /* Power down */ +#define SLIC_D0_GPIO (10) /* GPIO-pins */ +#define SLIC_D1_GPIO (11) +#define SLIC_D2_GPIO (12) +#define SLIC_PD_GPIO (13) + +#define SLIC_OH (AU1000_GPIO_9) /* Off hook */ +#define SLIC_OH_GPIO (9) /* GPIO-pin */ + +inline void gpio_init(void) { + au_writel(au_readl(SYS_PINFUNC) & ~SYS_PF_U3, SYS_PINFUNC); // configure GPIO9-14 as GPIO + au_writel(1<<SLIC_OH_GPIO, SYS_TRIOUTCLR); /* SLIC_OH_GPIO as tristate */ +} + + +inline void gpio_write(unsigned int bitmask, unsigned int value) { + au_writel(bitmask & ~value, SYS_OUTPUTCLR); + au_writel(bitmask & value, SYS_OUTPUTSET); +} + +inline short gpio_read(unsigned short pin) { + return (au_readl(SYS_PINSTATERD)>>pin) & 1; +} + +inline short gpio2_read(unsigned short pin) { + return (au_readl(SYS_PINSTATERD)>>pin) & 1; +} +#else +#error Cannot find hardware platform +#endif + + +/* States of SLIC-device, set via PD- and Dx-Pins */ +/* ---------------------------------------------- */ +#define SLIC_BITMASK ((1 << SLIC_PD_GPIO)|(1 << SLIC_D0_GPIO)|(1 << SLIC_D1_GPIO)|(1 << SLIC_D2_GPIO)) +/* Hi-Z-state, for onhook-condition, only detects offhook-event, minimum power-usage */ +#define SLIC_STATE_IDLE (1 << SLIC_PD_GPIO) +/* Active-state, for offhook-condition and other states where phone needs to be powered */ +#define SLIC_STATE_ACTIVE ((1 << SLIC_PD_GPIO)|(1 << SLIC_D1_GPIO)) +/* Active-state with reverse polarity */ +#define SLIC_STATE_REVERSE ((1 << SLIC_PD_GPIO)|(1 << SLIC_D1_GPIO)|(1 << SLIC_D2_GPIO)) +/* Ringing-state */ +#define SLIC_STATE_RINGING ((1 << SLIC_PD_GPIO)|(1 << SLIC_D0_GPIO)) +/* Powerdown-state */ +#define SLIC_STATE_POWERDOWN (0) + +#define SLIC_SET_STATE(x) do { \ + current_state = x; \ + gpio_write(SLIC_BITMASK, x); \ + } while (0) + +enum Slic_State { ONHOOK, OFFHOOK }; + +enum Event { evONHOOK, evOFFHOOK, evFLASH }; + +static DECLARE_WAIT_QUEUE_HEAD(slic_wait_queue); +static int slic_minor = -1; +static char is_inuse = 0; +static char state_changed = 0; +static enum Event last_value = 0; +static unsigned long event_time = 0; +static enum Slic_State event_value = 0; +static u32 current_state = 0; +static char active_reverse = 0; + +/* Timer-stuff */ +static char restart_timer = 1; /* Bool, if true, timer will be restarted by timer-function. */ +static char timer_started = 0; /* Bool, if true, timer was added. */ +static struct timer_list slic_ring_timer; +static struct timer_list slic_timer; + +#define RING_TIMER_INTERVALL (HZ/50) /* 25 Hz */ +#define RINGOFF_TIME (4*HZ) +#define RINGON_TIME (1*HZ) + +static void irq_taskletFunc(unsigned long dummy); +DECLARE_TASKLET(irq_tasklet, irq_taskletFunc, (unsigned long)&state_changed); + +enum Timer_Task { DEBOUNCE_ONHOOK, DETECT_FLASH_MIN, DETECT_FLASH_MAX, POWERON }; + +inline enum Slic_State get_current_slic_state(void) { + return gpio2_read(SLIC_OH_GPIO) ? ONHOOK : OFFHOOK; +} + +#define DEBOUNCE_TIME 200 +#define FLASH_MIN_TIME 170 +#define FLASH_MAX_TIME 310 +#define POWERON_TIME 200 + +unsigned int flash_min_t = FLASH_MIN_TIME; +unsigned int flash_max_t = FLASH_MAX_TIME; + + +static void slic_start_timer (unsigned long time_base, unsigned int timeout, enum Timer_Task timer_task ); + +/*---------[ Timer functions ]--------------------*/ + +/* The timer is used to toggle the d2-pin with 25 Hz. This generates the + ring-tone in the phone. */ + +static unsigned long ringtone = 0; +static unsigned long ringpause = RINGOFF_TIME; +static unsigned long ringsignal = RINGON_TIME; + +static void slic_do_ring (unsigned long data) +{ + /* Debug-output */ + /* + static int count = 0; + if (((++count) % 50) == 0) + debug("."); + */ + + /* Toggle d2-pin */ + u32 tmp = gpio_read(SLIC_D2_GPIO); + gpio_write((1 << SLIC_D2_GPIO), (tmp ? 0 : 1) << SLIC_D2_GPIO); + + if (restart_timer) { + if ( jiffies<ringtone ) { + slic_ring_timer.expires += RING_TIMER_INTERVALL; + } else { + slic_ring_timer.expires += ringpause; + ringtone += ringpause + ringsignal; + } + add_timer(&slic_ring_timer); + } +} + +static void slic_start_ring_timer (void) +{ + if (timer_started) + return; + timer_started = 1; + restart_timer = 1; /* Reenable self-reschedule */ + ringtone = jiffies + ringsignal; + slic_ring_timer.expires = jiffies; + slic_do_ring(0); +} + +static void slic_stop_ring_timer (void) +{ + if (!timer_started) + return; + restart_timer = 0; /* Timer-func mustn't reschedule itself while being stopped */ + del_timer_sync(&slic_ring_timer); + timer_started = 0; +} + + +/*---------[ SLIC Functions ]-----------------*/ + +/* Sets the SLIC to Hi-Z-state for powersaving. */ +static void slic_idle (void) +{ + slic_stop_ring_timer(); + SLIC_SET_STATE(SLIC_STATE_IDLE); +} + +/* Sets slic to active state. */ +static void slic_active (void) +{ + slic_stop_ring_timer(); + if ( active_reverse ) + SLIC_SET_STATE(SLIC_STATE_REVERSE); + else + SLIC_SET_STATE(SLIC_STATE_ACTIVE); +} + +/* Starts the timer-controlled toggling of the d-pins, which generates the + ringtone on the analog phone. */ +static void slic_ring_on (void) +{ + if ( current_state != SLIC_STATE_RINGING ) { + SLIC_SET_STATE(SLIC_STATE_RINGING); + slic_start_ring_timer(); /* start ringing */ + } +} + +/* Stops the timer to pause the ringing. */ +static void slic_ring_off (void) +{ + if ( current_state == SLIC_STATE_RINGING ) { + slic_stop_ring_timer(); + //SLIC_SET_STATE(SLIC_STATE_IDLE); <-- generates an irq!? + gpio_write((1 << SLIC_D2_GPIO), 0); + current_state = SLIC_STATE_IDLE; + } +} + +/* Powers up the slic device */ +static void slic_power_on (void) +{ + // this can generate a wrong off-hook event, so we 'debounce' the power on + event_time = jiffies; // this stops irq from generating off-hook event + SLIC_SET_STATE(SLIC_STATE_IDLE); + slic_start_timer(event_time, POWERON_TIME, POWERON); +} + +/* Shuts down the slic-device */ +static void slic_power_off (void) +{ + slic_stop_ring_timer(); + SLIC_SET_STATE(SLIC_STATE_POWERDOWN); +} + +#ifdef DEBUG +/* Testing: Turn on single pin */ +static void slic_test (int pin) +{ + switch (pin) { + case '0': + SLIC_SET_STATE(1 << SLIC_PD_GPIO); + break; + case '1': + SLIC_SET_STATE(1 << SLIC_D0_GPIO); + break; + case '2': + SLIC_SET_STATE(1 << SLIC_D1_GPIO); + break; + case '3': + SLIC_SET_STATE(1 << SLIC_D2_GPIO); + break; + default: + break; + } +} +#endif + +/*---------[ Interrupt handling ]-----------------*/ + +static void generate_event(enum Event ev) { + // storing only the current event, can lead to a event loss if the application + // doesn't read an event before the next occurs -> use an event queue to deliver + // events in sequence !! + debug("event: %i\n", ev); + last_value = ev; + state_changed = 1; + wake_up (&slic_wait_queue); +} + +// timeout in ms ; if timebase is 0 timeout is increased, otherwise absolute time_base+timeout is used +static void slic_start_timer (unsigned long time_base, unsigned int timeout, enum Timer_Task timer_task ) +{ + if ( time_base == 0 ) + slic_timer.expires += (timeout * HZ)/ 1000; + else + slic_timer.expires = time_base + (timeout * HZ)/ 1000; + slic_timer.data = (unsigned long) timer_task; + add_timer(&slic_timer); +} + +// timer used for - debouncing offhook (setting slic active, results in a onhook offhook irq) +// - distinguish between onhook and flash +static void slic_do_timer (unsigned long timer_task) { + enum Slic_State current = get_current_slic_state(); + int done = 0; + + switch ( timer_task ) { + case DEBOUNCE_ONHOOK: + if ( current == OFFHOOK ) { + // done, stable onhook state reached + done = 1; + } else { // current == ONHOOK + slic_start_timer(0, flash_min_t, DETECT_FLASH_MIN); + } + break; + case DETECT_FLASH_MIN: + if ( current == ONHOOK ) { + slic_start_timer(0, flash_max_t - flash_min_t, DETECT_FLASH_MAX); + } else { // current == OFFHOOK + // offhook too short for flash, ignore + done = 1; + } + break; + case DETECT_FLASH_MAX: + if ( current == ONHOOK ) { + // off-hook state reached + slic_idle(); + generate_event(evONHOOK); + done = 1; + } else { // current == OFFHOOK + // flash signal detected + generate_event(evFLASH); + done = 1; + } + break; + case POWERON: + if ( current == ONHOOK ) { + done = 1; // power on event 'debounced' + } else if ( current == OFFHOOK ) { + // probably we were off-hook when activating the slic; go off-hook now + slic_active(); + generate_event(evOFFHOOK); + slic_start_timer(event_time, DEBOUNCE_TIME, DEBOUNCE_ONHOOK); + } + } + + if ( done ) { + //re-enable irq and be careful to not loose a state change + event_time = 0; + if ( current != get_current_slic_state() && event_time==0 ) { + event_time = jiffies; + event_value = get_current_slic_state(); + tasklet_schedule(&irq_tasklet); + } + } +} + +static void irq_taskletFunc(unsigned long dummy) +{ + if ( (current_state == SLIC_STATE_IDLE || current_state == SLIC_STATE_RINGING) && + event_value == OFFHOOK ) { + slic_active(); + generate_event(evOFFHOOK); + slic_start_timer(event_time, DEBOUNCE_TIME, DEBOUNCE_ONHOOK); + } else if ( ( current_state == SLIC_STATE_ACTIVE || current_state == SLIC_STATE_REVERSE ) && + event_value == ONHOOK ) { + slic_start_timer(event_time, flash_min_t, DETECT_FLASH_MIN); + } else { + // ignore event + event_time = 0; + } +} + +/* The interrupt is raised by a state-change on the DET-pin. This signals an + offhook-condition on the phone. */ +static void slic_irq (int irq, void *private, struct pt_regs *regs) +{ + if ( !event_time ) { + event_time = jiffies; + event_value = get_current_slic_state(); + tasklet_schedule(&irq_tasklet); + } +} + +static int slic_startirq (void) +{ + if (request_irq(SLIC_OH, slic_irq, SA_INTERRUPT, "slic", (void *) &state_changed) < 0) { + return -1; + } + + return 0; +} + +static int slic_stopirq (void) +{ + free_irq (SLIC_OH, (void *) &state_changed); + return 0; +} + +/*---------[ File Functions ]-----------------*/ + +static int slic_open (struct inode *inode, struct file *file) +{ + if (MINOR(inode->i_rdev) != slic_minor) + return -ENODEV; + if (is_inuse) + return -EBUSY; + is_inuse = 1; + state_changed = 0; + MOD_INC_USE_COUNT; + return 0; +} + +static int slic_release (struct inode *inode, struct file *file) +{ + if (MINOR(inode->i_rdev) == slic_minor) { + is_inuse = 0; + } + MOD_DEC_USE_COUNT; + return 0; +} + +static ssize_t slic_read (struct file *file, char *buf, size_t count, loff_t *ppos) +{ + if (count < 1) + return -EINVAL; + if (!state_changed) + interruptible_sleep_on (&slic_wait_queue); + + state_changed = 0; + if (copy_to_user(buf, &last_value, 1)) { + return -EFAULT; + } + return 1; +} + +/* The SLIC is controlled with 2-byte commands. The first byte selects the type + of the command, the second is an additional parameter or ignored. */ +static ssize_t slic_write (struct file *file, const char *buf, size_t count, loff_t *ppos) +{ + char localbuff[2]; + size_t bytes_read = 0; + if (count < 2 || (count % 2)) + return -EINVAL; + while (bytes_read < count) { + if (copy_from_user(localbuff, buf + bytes_read, 2)) + return -EFAULT; + bytes_read += 2; + debug("SLIC: "); + switch (localbuff[0]) { + case 'P': + /* Power-command */ + if (localbuff[1] == '0') { + debug("Power off\n"); + slic_power_off(); + } else { + debug("Power on\n"); + slic_power_on(); + } + break; + case 'R': + /* Ring-command */ + if (localbuff[1] == '0') { + debug("Ring off\n"); + slic_ring_off(); + } else { + debug("Ring on\n"); + slic_ring_on(); + } + break; + case 'M': + /* Mode-command: 0 - active standard mode, 1 - active reverse mode */ + if (localbuff[1] == '0') { + debug("Active default\n"); + active_reverse = 0; + if ( current_state == SLIC_STATE_REVERSE ) + slic_active(); + } else { + debug("Active reverse default\n"); + active_reverse = 1; + if ( current_state == SLIC_STATE_ACTIVE ) + slic_active(); + } + break; + +#ifdef DEBUG + case 'A': + /* Active-command */ + debug("Active\n"); + slic_active(); + break; + case 'I': + /* Idle-command */ + debug("Idle\n"); + slic_idle(); + break; + case 'T': + /* Test-command */ + debug("Test %c\n", localbuff[1]); + slic_test(localbuff[1]); + break; +#endif + default: + return -EFAULT; + } + } + return count; /* Everything read */ +} + +static int +slic_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + // don't using file information for now; only one slic supported + int ret = 0; + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != SLIC_IOC_MAGIC) return -ENOTTY; + if (_IOC_NR(cmd) > SLIC_IOC_MAXNR) return -ENOTTY; + + switch (cmd) { + case IOC_SLIC_POWER: + /* Power-command */ + if ( arg ) { + debug("Power on\n"); + slic_power_on(); + } else { + debug("Power off\n"); + slic_power_off(); + } + break; + case IOC_SLIC_RING: + /* Ring-command */ + if ( arg ) { + debug("Ring on\n"); + slic_ring_on(); + } else { + debug("Ring off\n"); + slic_ring_off(); + } + break; + case IOC_SLIC_MODE: + /* Mode-command: 0 - active standard mode, 1 - active reverse mode */ + if ( arg ) { + debug("Active reverse default\n"); + active_reverse = 1; + if ( current_state == SLIC_STATE_ACTIVE ) + slic_active(); + } else { + debug("Active default\n"); + active_reverse = 0; + if ( current_state == SLIC_STATE_REVERSE ) + slic_active(); + } + break; + case IOC_SLIC_HOOKFLASH: + /* set hook-flash time */ + { + int min = arg & 0xffff; + int max = arg>>16; + if ( min<max && min >= 20 && max <= 1000 ) { + flash_min_t = min; + flash_max_t = max; + } else { + ret = -EINVAL; + } + } + break; + +#ifdef DEBUG + case IOC_SLIC_TEST: + /* Test-command */ + debug("Test %li\n", arg & SLIC_BITMASK); + SLIC_SET_STATE(arg); + break; +#endif + default: + ret = -ENOTTY; + } + + return ret; +} + +static unsigned int slic_poll (struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + + poll_wait (file, &slic_wait_queue, wait); + if (state_changed) /* state changed since last time. */ + mask |= POLLIN | POLLRDNORM; + return mask; +} + +/*---------[ Module stuff ]-----------------*/ + +static struct file_operations slic_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .read = slic_read, + .write = slic_write, + .readdir = NULL, + .poll = slic_poll, + .ioctl = slic_ioctl, + .mmap = NULL, + .open = slic_open, + .flush = NULL, + .release = slic_release +}; + +static struct miscdevice slic_miscdev = { + MISC_DYNAMIC_MINOR, + "slic", + &slic_fops +}; + +void __exit slic_cleanup (void) +{ + is_inuse = 1; + slic_stop_ring_timer(); + slic_stopirq(); + misc_deregister(&slic_miscdev); +} + +int __init slic_init (void) +{ + printk("Surfbox2 SLIC driver\n"); + debug(" with debug\n"); + + is_inuse = 1; + + // prepare time for handling RING + init_timer(&slic_ring_timer); + slic_ring_timer.function = slic_do_ring; + slic_ring_timer.data = 0; + + // prepare time for onhook/offhook debounce and flash detection + init_timer(&slic_timer); + slic_timer.function = slic_do_timer; + slic_timer.data = 0; + + /* Set GPIOs for Dx- and PD-pins as output */ + gpio_init(); + + /* Set initial state */ + slic_power_off(); + + /* Register device */ + if (misc_register(&slic_miscdev) >= 0) { + slic_minor = slic_miscdev.minor; + if (slic_startirq () == 0) { + /* Everything fine */ + is_inuse = 0; + return 0; + } + /* Error */ + misc_deregister(&slic_miscdev); + } + return 1; +} + +module_init(slic_init); +module_exit(slic_cleanup); + +MODULE_AUTHOR("Ingo Salzmann"); +MODULE_DESCRIPTION("Driver for MTX SLIC"); +MODULE_LICENSE("GPL"); +EXPORT_NO_SYMBOLS; --- linux-org/include/linux/slic.h 1970-01-01 01:00:00.000000000 +0100 +++ linux/include/linux/slic.h 2006-06-20 13:46:05.321244750 +0200 @@ -0,0 +1,41 @@ +/** + * @file slic.h + * @author Thomas Geffert <geffert@4g-systems.com> + * @date Tue Jun 20 13:22:01 2006 + * + * @brief Some ioctl declarations for the mtx slic driver + * + * + */ + +/* + * (c) COPYRIGHT 2006 by 4G Systems GmbH Germany + * All rights reserved. + */ + + +#ifndef SLIC__H__ +#define SLIC__H__ + +#define SLIC_IOC_MAGIC 0xC1 /// an arbitrary number for identifying slic ioctls + +// ioctl argument: arg == 0 -> power off, arg == 1 -> power on +#define IOC_SLIC_POWER _IOW(SLIC_IOC_MAGIC, 0x00, unsigned int) + +// ioctl argument: arg == 0 -> ring off, arg == 1 -> ring on +#define IOC_SLIC_RING _IOW(SLIC_IOC_MAGIC, 0x01, unsigned int) + +// ioctl argument: arg == 0 -> active, arg == 1 -> active reverse +#define IOC_SLIC_MODE _IOW(SLIC_IOC_MAGIC, 0x02, unsigned int) + +// ioctl argument: set hook-flash time; arg&0xffff is min time in ms, arg>>16 is max time in ms +// 20 <= min < max <= 1000 +#define IOC_SLIC_HOOKFLASH _IOW(SLIC_IOC_MAGIC, 0x04, unsigned int) + +// ioctl argument: write arg to slic register ; only available if slic driver compiled with DEBUG +#define IOC_SLIC_TEST _IOW(SLIC_IOC_MAGIC, 0x03, unsigned int) + +#define SLIC_IOC_MAXNR 0x04 /// max number of supported ioctl calls + + +#endif /* SLIC__H__ */