/*------------------------------------------------------------------------------------------*\
 *   
 *   Copyright (C) 2006 AVM GmbH <fritzbox_info@avm.de>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
\*------------------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/fcntl.h>
#include <asm/ioctl.h>
#include <asm/errno.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/ar7wdt.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include "avm_sammel.h"
#include "ar7wdt.h"

/*------------------------------------------------------------------------------------------*\
 * 2.4 Kernel H-Files
\*------------------------------------------------------------------------------------------*/
#if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE 
#include <asm-mips/avalanche/sangam/sangam.h>
#include <asm-mips/avalanche/sangam/sangam_clk_cntl.h>
#include <linux/devfs_fs_kernel.h>
#endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ 

/*------------------------------------------------------------------------------------------*\
 * 2.6 Kernel H-Files
\*------------------------------------------------------------------------------------------*/
#if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE 
#define UINT32 unsigned int
#define INT32  int
#include <asm/mach_avm.h>
#include <asm/mips-boards/prom.h>
#include <asm/mach-ohio/wdtimer.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ 


/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
/*--- #define AR7WDT_DEBUG ---*/

#if defined(AR7WDT_DEBUG)
#define DBG(...)  printk(KERN_INFO __VA_ARGS__)
#else /*--- #if defined(AR7WDT_DEBUG) ---*/
#define DBG(...)  
#endif /*--- #else ---*/ /*--- #if defined(AR7WDT_DEBUG) ---*/


/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
/*--- #define MODULE_NAME     "ar7wdt" ---*/
/*--- MODULE_DESCRIPTION("AR7 Watchdog Timer"); ---*/
/*--- #define MODULE_VERSION "1.0" ---*/
/*--- MODULE_LICENSE("\n(C) Copyright 2003, Texas Instruments, Inc\n(C) Copyright 2004, 2005, AVM"); ---*/

/*--- int ar7wdt_no_reboot = 0; ---*/
extern int ar7wdt_no_reboot;
/*--- MODULE_PARM(ar7wdt_no_reboot, "i"); ---*/
/*--- MODULE_PARM_DESC(ar7wdt_no_reboot, "Watchdog no reboot"); ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _ar7wdt_cmd {
    char *cmd;
    int cmd_len;
    int (*funktion)(int, char *, int);
} ar7wdt_cmd[] = {
    { "start",      sizeof("start") - 1,      AR7WDT_register },
    { "register",   sizeof("register") - 1,   AR7WDT_register },
    { "release",    sizeof("release") - 1,    AR7WDT_release },
    { "timeout",    sizeof("timeout") - 1,    AR7WDT_set_timeout },
    { "time",       sizeof("time") - 1,       AR7WDT_set_timeout },
    { "trigger",    sizeof("trigger") - 1,    AR7WDT_trigger },
    { "disable",    sizeof("disable") - 1,    AR7WDT_disable },
    { "init-start", sizeof("init-start") - 1, AR7WDT_init_start },
    { "init-done",  sizeof("init-done") - 1,  AR7WDT_init_done },
    { NULL,       0,                      NULL }
};

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int ar7wdt_open(struct inode *, struct file *);
static int ar7wdt_release(struct inode *, struct file *);
static ssize_t ar7wdt_read(struct file *, char *, size_t, loff_t *);
static ssize_t ar7wdt_write(struct file *, const char *, size_t, loff_t *);
/*--- static int ar7wdt_ioctl(struct inode *, struct file *, unsigned int, unsigned long); ---*/
void ar7wdt_cleanup(void);
static int ar7wdt_fasync(int fd, struct file *filp, int mode);
static unsigned int ar7wdt_poll(struct file *filp, poll_table *wait);


struct file_operations ar7wdt_fops = {
    owner:   THIS_MODULE,
    open:    ar7wdt_open,
    release: ar7wdt_release,
    read:    ar7wdt_read,
    write:   ar7wdt_write,
    /*--- ioctl:   ar7wdt_ioctl, ---*/
    fasync:  ar7wdt_fasync,
    poll:    ar7wdt_poll,
};

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE 
static struct _avm_wdt {
	struct char_device_struct *device_region;
    dev_t                      device;
    struct cdev               *cdev;
} avmwdt;
#else /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int ar7wdt_major = 0;
devfs_handle_t ar7wdt_handle;
#endif /*--- #else ---*/ /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
int __init ar7wdt_init(void) {
    int reason;

    printk("AR7WDT: Watchdog Driver for AR7 Hardware (Version %s, build: %s %s)\n", "1.0", __DATE__, __TIME__);

#if defined(CONFIG_AVM_SAMMEL)
    if(ar7wdt_no_reboot == 0) {
        char *argptr;
        argptr = prom_getcmdline();
        /*--- __printk("kernel cmdline = %s\n", argptr); ---*/
        if(argptr) {
            argptr = strstr(argptr, "ar7wdt_no_reboot=");
            /*--- __printk("kernel: \"%s\"\n", argptr); ---*/
        }
        if(argptr) {
            /*--- __printk("kernel: switch: \"%c\"\n", argptr[sizeof("ar7wdt_no_reboot=") - 1]); ---*/
            switch(argptr[sizeof("ar7wdt_no_reboot=") - 1]) {
                default:
                case '0': break;
                case '1': ar7wdt_no_reboot = 1;
                          break;
                case '2': ar7wdt_no_reboot = 2;
                          break;
            }
        }
        /*--- __printk("kernel: ar7wdt_no_reboot = %u\n", ar7wdt_no_reboot ); ---*/
    }
#endif /*--- #if defined(CONFIG_AVM_SAMMEL) ---*/

    if(ar7wdt_no_reboot == 2) {
        printk("watchdog disabled\n");
        return 0;
    }
    if(ar7wdt_no_reboot) {
        printk("panic reboot disabled\n");
    }

    /*--------------------------------------------------------------------------------------*\
     * 2.6 Kernel init
    \*--------------------------------------------------------------------------------------*/
#if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE 
    DBG(KERN_INFO "[avmwdt] register_chrdev_region()\n");
    avmwdt.device = MKDEV(WATCHDOG_MAJOR,0);
    reason = register_chrdev_region(avmwdt.device, 1, "watchdog");
    if(reason) {
        DBG(KERN_ERR "[avmwdt] register_chrdev_region()\n");
        printk("[avmwdt] register_chrdev_region failed: reason %d!\n", reason);
        return -ERESTARTSYS;
    }

    avmwdt.cdev = cdev_alloc();
    if (!avmwdt.cdev) {
        unregister_chrdev_region(avmwdt.device, 1);
        printk("[avmwdt] cdev_alloc failed!\n");
        return -ERESTARTSYS;
    }

    avmwdt.cdev->owner = ar7wdt_fops.owner;
    avmwdt.cdev->ops = &ar7wdt_fops;
    kobject_set_name(&(avmwdt.cdev->kobj), "watchdog");

    if (cdev_add(avmwdt.cdev, avmwdt.device, 1)) { 
        kobject_put(&avmwdt.cdev->kobj);
        unregister_chrdev_region(avmwdt.device, 1);
        printk("[avmwdt] cdev_add failed!\n");
        return -ERESTARTSYS;
    }
    /*--------------------------------------------------------------------------------------*\
     * 2.4 Kernel init
    \*--------------------------------------------------------------------------------------*/
#else /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 
    ar7wdt_major = register_chrdev(0 /*--- dynamic major ---*/, "[ar7wdt]", &ar7wdt_fops);
    if(ar7wdt_major < 1) {
        DBG(KERN_ERR "%s: register_chrdrv failed: reason %d\n", "[ar7wdt]", ar7wdt_major);
        return -ERESTARTSYS;
    }
	ar7wdt_handle = devfs_register(NULL, "watchdog", DEVFS_FL_DEFAULT, ar7wdt_major, 0, S_IFCHR | S_IRUGO | S_IWUSR, &ar7wdt_fops, NULL);
#endif /*--- #else ---*/ /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 

    AR7WDT_init();
#if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE 
    ar7wdt_hw_init();
    ar7wdt_hw_trigger();
#else /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 
    wdtimer_init(AVALANCHE_WATCHDOG_TIMER_BASE, avalanche_clkc_get_freq(CLKC_SYS) / 2);

    if((reason = wdtimer_set_period( 42 * 1000 )) != WDTIMER_RET_OK) {
        printk("[ERROR wdtimer_set_period(42 * 1000) %d] \n", reason);
        return -1;
    }

    if((reason = wdtimer_ctrl(WDTIMER_CTRL_ENABLE)) != WDTIMER_RET_OK) {
        printk("[ERROR wdtimer_ctrl %d] \n", reason);
        return -1;
    }

    wdtimer_kick();
#endif /*--- #else ---*/ /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 
    return 0;
}

/*--- module_init(ar7wdt_init); ---*/

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
#if defined(CONFIG_AR7WDT_MODULE)
void ar7wdt_cleanup(void) {
    if(ar7wdt_no_reboot == 2) {
        printk("watchdog was disabled\n");
        return;
    }
    AR7WDT_deinit();
#if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE 
    if(avmwdt.cdev) {
        cdev_del(avmwdt.cdev); /* Delete char device */
        unregister_chrdev_region(avmwdt.device, 1);
    }
    ar7wdt_hw_deinit();
#else /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 
    if(ar7wdt_handle )
	    devfs_unregister(ar7wdt_handle);
    wdtimer_ctrl(WDTIMER_CTRL_DISABLE);
#endif /*--- #else ---*/ /*--- #if KERNEL_VERSION(2, 6, 0) < LINUX_VERSION_CODE ---*/ 
    return;
}

/*--- module_exit(ar7wdt_cleanup); ---*/
#endif /*--- #if defined(CONFIG_AR7WDT_MODULE) ---*/

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int ar7wdt_fasync(int fd, struct file *filp, int mode) {
    struct fasync_struct **fasync;

    DBG(KERN_INFO "ar7wdt_fasync: fd=%u\n", fd);
    fasync = AR7WDT_get_fasync_ptr((int)filp->private_data);
    if(fasync)
        return fasync_helper(fd, filp, mode, fasync);
    return -EPERM;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static unsigned int ar7wdt_poll(struct file *filp, poll_table *wait) {
	wait_queue_head_t *wait_queue;
    int poll;

	wait_queue = AR7WDT_get_wait_queue((int)filp->private_data);
    if (wait_queue == NULL) return 0;

	poll_wait (filp, wait_queue, wait);

	poll = AR7WDT_poll((int)filp->private_data);
    if(poll > 0) {
        /*--- DBG(KERN_INFO "ar7wdt_poll: data avail\n"); ---*/
	    return POLLIN | POLLRDNORM;
    }

    /*--- DBG(KERN_INFO "ar7wdt_poll: no data\n"); ---*/
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int ar7wdt_open(struct inode *inode, struct file *filp) {
    DBG(KERN_INFO "ar7wdt_open: always success\n");
#if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE 
    MOD_INC_USE_COUNT;
#endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ 
    filp->private_data = 0;
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int ar7wdt_release(struct inode *inode, struct file *filp) {
    DBG(KERN_INFO "ar7wdt_release: always success\n");
#if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE 
    MOD_DEC_USE_COUNT;
#endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ 
    if(filp->private_data)
    {
        AR7WDT_ungraceful_release((int)filp->private_data);
        /*--- AR7WDT_reboot((int)filp->private_data); ---*/
        return 0;
    }
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static ssize_t ar7wdt_read(struct file *filp, char *read_buffer, size_t max_read_length, loff_t *offp) {
    char Buffer[64];
    int len;

    if ((int)filp->private_data == 0) return -EINVAL;

    /*-------------------------------------------------------------------------------------------*\
    \*-------------------------------------------------------------------------------------------*/
ar7wdt_read_again:
    DBG(KERN_INFO "ar7wdt_read:\n");

    (void)AR7WDT_read((int)filp->private_data, Buffer, sizeof(Buffer));
    len = strlen(Buffer);
    if(len == 0) {
	    wait_queue_head_t *wait_queue;
        if(filp->f_flags & O_NONBLOCK) {
            DBG(" empty\n");
            return -EAGAIN;
        }

        DBG(" sleep on 'wait_queue'\n");
	    wait_queue = AR7WDT_get_wait_queue((int)filp->private_data);
        interruptible_sleep_on(wait_queue);

        DBG(" wake up from 'wait_queue'\n");
        goto ar7wdt_read_again;
    }
    /*-------------------------------------------------------------------------------------------*\
    \*-------------------------------------------------------------------------------------------*/

    len = min((int)max_read_length, len);

    if(len) {
        if(copy_to_user(read_buffer, Buffer, len)) {
            DBG(KERN_INFO "ar7wdt_read: copy_to_user failed len=%u\n", len);
            return (unsigned int)-EFAULT;
        }
    }
    DBG(KERN_INFO "ar7wdt_read: '%s' len=%u\n", Buffer, len);
    *offp += len;
    return len;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static ssize_t ar7wdt_write(struct file *filp, const char *write_buffer, size_t write_length, loff_t *offp) {
    char Buffer[128];
    unsigned int copy_count;
    struct _ar7wdt_cmd *C = &ar7wdt_cmd[0];

    copy_count = min(write_length, sizeof(Buffer) - 1);
    if(copy_from_user(Buffer, write_buffer, copy_count)) {
        return (unsigned int)-EFAULT;
    }
    Buffer[copy_count] = '\0';

    DBG(KERN_INFO "ar7wdt_write: '%s'\n", Buffer);
    while(C->cmd) {
        if(write_length >= C->cmd_len && !strncmp(C->cmd, Buffer, C->cmd_len) && C->funktion) {
            DBG(KERN_INFO "ar7wdt_write: call funktion for '%s'\n", C->cmd);
            filp->private_data = (void *)C->funktion((int)filp->private_data, Buffer + C->cmd_len, write_length - C->cmd_len);
            if((int)filp->private_data < 0) {
                /*--- io error ---*/
                DBG(KERN_INFO "ar7wdt_write: error %d\n", (int)filp->private_data);
                return (int)filp->private_data;
            }
            DBG(KERN_INFO "ar7wdt_write: success\n");
            break;
        } else {
            C++;
        }
    }
    if(C->cmd == NULL) {
        /*--- io error ---*/
        DBG(KERN_INFO "ar7wdt_write: no support funktion\n");
        return -EBADRQC; /*--- invallid request code ---*/
    }
    *offp += write_length;
    return write_length;
}

#if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE 
EXPORT_NO_SYMBOLS;
#endif /*--- #if KERNEL_VERSION(2, 6, 0) > LINUX_VERSION_CODE ---*/ 

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
