/*
 * Carsten Langgaard, carstenl@mips.com
 * Copyright (C) 2002 MIPS Technologies, Inc.  All rights reserved.
 *
 *  This program is free software; you can distribute it and/or modify it
 *  under the terms of the GNU General Public License (Version 2) as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope 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.
 *
 * OHIO specific setup.
 */
#include <linux/config.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/tty.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/vmalloc.h>
#include <linux/proc_fs.h>
#include <linux/kallsyms.h>

#include <asm/cpu.h>
#include <asm/bootinfo.h>
#include <asm/irq.h>
#include <asm/mips-boards/generic.h>
#include <asm/mips-boards/prom.h>
#include <asm/mips-boards/ohio.h>
#include <asm/mips-boards/ohioint.h>
#include <asm/mach_avm.h>
#include <asm/mach-ohio/hw_boot.h>
#include <asm/mach-ohio/hw_emif.h>
#include <asm/time.h>


/*--- #define PRINTK      prom_printf ---*/
#define PRINTK      printk

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static spinlock_t ohio_clock_spinlock;
static unsigned int ohio_current_system_clock;
#define default_ohio_lan_xtal  25000000
const unsigned int ohio_lan_xtal = default_ohio_lan_xtal;
#define default_ohio_dsp_xtal 35328000
const unsigned int ohio_dsp_xtal = default_ohio_dsp_xtal;

#if defined(CONFIG_OHIO_CLOCK_SWITCH)
static spinlock_t ohio_clock_switch_spinlock;
static struct _ohio_clk_notify_system_clock_change *ohio_system_clock_notify_first;
static struct proc_dir_entry *ohio_change_clock_entry;
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/



/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int __init ohio_clk_switch_init(void);
static unsigned int ohio_call_clock_notifier(enum _avm_clock_id clock_id, unsigned int clk);
static unsigned int ohio_set_mips_clock(unsigned int clk);
static unsigned int ohio_set_system_clock(unsigned int clk);
static unsigned int ohio_set_usb_clock_notify(enum _avm_clock_id clock_id, unsigned int clk);
static unsigned int ohio_set_usb_clock(unsigned int clk);
static unsigned int ohio_set_dsp_clock(unsigned int clk);
static unsigned int ohio_get_system_clock(void);
static unsigned int ohio_get_mips_clock(void);
static unsigned int ohio_get_dsp_clock(void);
static unsigned int ohio_get_usb_clock(void);

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _valid_clk {
    unsigned int pre_div;
    unsigned int mult;
    unsigned int post_div;
    unsigned int post_div2;
    unsigned int freq;
    unsigned int startup;
};

struct _valid_clk valid_system_clk[] = {
    { freq: 25000000, pre_div: 1, mult:  10, post_div: 10, post_div2: 1 },
    { freq: 50000000, pre_div: 1, mult:  10, post_div: 5, post_div2: 1 },
    { freq: 75000000, pre_div: 1, mult:  6, post_div: 2, post_div2: 1 },
    { freq:100000000, pre_div: 1, mult:  8, post_div: 2, post_div2: 1 },
    { startup: 1, freq:125000000, pre_div: 1, mult: 10, post_div: 2, post_div2: 1 },
    { freq:137500000, pre_div: 1, mult: 11, post_div: 2, post_div2: 1 },
    { freq:150000000, pre_div: 1, mult: 12, post_div: 2, post_div2: 2 },
    { freq:162500000, pre_div: 2, mult: 13, post_div: 1, post_div2: 2 },
    { 0, 0, 0, 0 }
};

struct _valid_clk valid_mips_clk_with_dsp_xtal[] = {
    { freq: default_ohio_dsp_xtal * 1, pre_div: 1, mult:  1, post_div: 1 },
    { freq: default_ohio_dsp_xtal * 2, pre_div: 1, mult:  2, post_div: 1 },
    { freq: default_ohio_dsp_xtal * 3, pre_div: 1, mult:  3, post_div: 1 },
    { freq: default_ohio_dsp_xtal * 4, pre_div: 1, mult:  4, post_div: 1 },
    { freq: default_ohio_dsp_xtal * 5, pre_div: 1, mult:  5, post_div: 1 },
    { startup: 1, freq: default_ohio_dsp_xtal * 6, pre_div: 1, mult:  6, post_div: 1 }, /* Frequ: 211968000 Hz */
    { freq: default_ohio_dsp_xtal * 7, pre_div: 1, mult:  7, post_div: 1 },  /* 247296000 Hz */
    { 0, 0, 0, 0 }
};

struct _valid_clk valid_mips_clk_with_lan_xtal[] = {
    { freq: default_ohio_lan_xtal * 1, pre_div: 1, mult:  1, post_div: 1 },  /* 25 Mhz */
    { freq: default_ohio_lan_xtal * 2, pre_div: 1, mult:  2, post_div: 1 },
    { freq: default_ohio_lan_xtal * 3, pre_div: 1, mult:  3, post_div: 1 },
    { freq: default_ohio_lan_xtal * 4, pre_div: 1, mult:  4, post_div: 1 },
    { freq: default_ohio_lan_xtal * 5, pre_div: 1, mult:  5, post_div: 1 },
    { freq: default_ohio_lan_xtal * 6, pre_div: 1, mult:  6, post_div: 1 },  /* 150 MHz */
    { freq: default_ohio_lan_xtal * 7, pre_div: 1, mult:  7, post_div: 1 },  /* 175 MHz */
    { freq: default_ohio_lan_xtal * 8, pre_div: 1, mult:  8, post_div: 1 },  /* 200 MHz */
    { startup: 1, freq: default_ohio_lan_xtal * 9, pre_div: 1, mult:  9, post_div: 1 },  /* 225 MHz */
    { freq: default_ohio_lan_xtal * 10, pre_div: 1, mult:  10, post_div: 1 },  /* 250 MHz Hz */
    { 0, 0, 0, 0 }
};

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
union EMIF_SDRAMTimingReg ohio_SDRAM_config[3] = {
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#ifdef USE_CL2_RAMS
    { 
        /*--- 125 MHz CS2 ---*/
#define EMIF_SDRAM_TIMING_125MHZ    0
        Bits: {
            t_rfc: 10 - 1,
            t_rrd: 2  - 1,
            t_rc:  9  - 1,
            t_ras: 6  - 1,
            t_wr:  2  - 1,
            t_rcd: 3  - 1,
            t_rp:  3  - 1
        }
    },
#define EMIF_SDRAM_TIMING_137MHZ    1
    {
        /*--- 137,5 MHz CS2 ---*/
        Bits: {
            t_rfc: 11 - 1,
            t_rrd: 2  - 1,
            t_rc:  9  - 1,
            t_ras: 6  - 1,
            t_wr:  2  - 1,
            t_rcd: 3  - 1,
            t_rp:  3  - 1
        }
    },
#define EMIF_SDRAM_TIMING_150MHZ    2
    {
        /*--- 150 MHz CS2 ---*/
        Bits: {
            t_rfc: 12 - 1,
            t_rrd: 3  - 1,
            t_rc:  10 - 1,
            t_ras: 7  - 1,
            t_wr:  3  - 1,
            t_rcd: 3  - 1,
            t_rp:  3  - 1
        }
    }
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#else
    { 
        /*--- 125 MHz CS2 ---*/
#define EMIF_SDRAM_TIMING_125MHZ    0
        Bits: {
            t_rfc: 10 - 1,
            t_rrd: 2  - 1,
            t_rc:  9  - 1,
            t_ras: 6  - 1,
            t_wr:  2  - 1,
            t_rcd: 3  - 1,
            t_rp:  3  - 1
        }
    },
#define EMIF_SDRAM_TIMING_137MHZ    1
    {
        /*--- 137,5 MHz CS2 ---*/
        Bits: {
            t_rfc: 11 - 1,
            t_rrd: 2  - 1,
            t_rc:  9  - 1,
            t_ras: 6  - 1,
            t_wr:  2  - 1,
            t_rcd: 3  - 1,
            t_rp:  3  - 1
        }
    },
#define EMIF_SDRAM_TIMING_150MHZ    2
    {
        /*--- 150 MHz CS2 ---*/
        Bits: {
            t_rfc: 12 - 1,
            t_rrd: 3  - 1,
            t_rc:  10 - 1,
            t_ras: 7  - 1,
            t_wr:  3  - 1,
            t_rcd: 3  - 1,
            t_rp:  3  - 1
        }
    }
#endif
};

/*------------------------------------------------------------------------------------------*\
 * HWRevision die keinen DSP Quarz besetzen mit Space getrennt
\*------------------------------------------------------------------------------------------*/
char *AVM_LAN_CLK_HWREV_LIST = " 105 ";

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static struct _valid_clk *ohio_get_valid_mips_clk_table(void) {
    char *hwrev;
    static struct _valid_clk *V = NULL;

    if(V) return V;

    prom_init();
    /*--- printk("HWRevision=\"%s\"\n", hwrev); ---*/
    /*--- prom_printf("[ohio_get_valid_mips_clk_table]\n"); ---*/
    hwrev = prom_getenv("HWRevision");
    if(hwrev) {
        char buff[20];
        char *p;

        /*--- prom_printf("[ohio_get_valid_mips_clk_table] hwrev=%s\n", hwrev); ---*/
        strcpy(buff, hwrev);
        strcat(buff, " ");
        /*----------------------------------------------------------------------------------*\
         * aus "94.0.0.1 "  -->   "94 " erzeugen
        \*----------------------------------------------------------------------------------*/
        p = strchr(buff, '.');
        if(p) {
            *p++ = ' ';
            *p++ = '\0';
        }
        if(strstr(AVM_LAN_CLK_HWREV_LIST, buff)) {
            /*--- prom_printf("[ohio_get_valid_mips_clk_table] has no dsp xtal\n", hwrev); ---*/
            return V = valid_mips_clk_with_lan_xtal;
        } else {
            /*--- prom_printf("[ohio_get_valid_mips_clk_table] has dsp xtal\n", hwrev); ---*/
            return V = valid_mips_clk_with_dsp_xtal;
        }
    }
    /*--- prom_printf("[ohio_get_valid_mips_clk_table] no HWRevision\n"); ---*/
    return NULL;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ohio_clk_get_pll_factor2(struct _ohio_clock_pll *pll, unsigned int count) {
    if(pll->PLL_ctrl.Bits.pllen == 1) {
        if(pll->PLL_pre_div.Bits.diven == 1) {
            count /= pll->PLL_pre_div.Bits.ratio + 1;
        }
        
        count *= pll->PLL_mult.Bits.pllm + 1;
        
        if(pll->PLL_post_div2.Bits.diven == 1) {
            count /= pll->PLL_post_div2.Bits.ratio + 1;
        }
    } 
    return count;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ohio_clk_get_pll_factor(struct _ohio_clock_pll *pll, unsigned int count) {
    if(pll->PLL_ctrl.Bits.pllen == 1) {
        if(pll->PLL_pre_div.Bits.diven == 1) {
            count /= pll->PLL_pre_div.Bits.ratio + 1;
        }
        count *= pll->PLL_mult.Bits.pllm + 1;
        if(pll->PLL_post_div.Bits.diven == 1) {
            count /= pll->PLL_post_div.Bits.ratio + 1;
        }
    } 
    return count;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void ohio_clk_set_pll(struct _ohio_clock_pll *PLL, 
                             unsigned int enable, 
                             unsigned int mult, 
                             unsigned int pre_div, 
                             unsigned int post_div,
                             unsigned int post_div2) {

    int flags;
    spin_lock_irqsave(&ohio_clock_spinlock, flags);
    PLL->PLL_ctrl.Bits.pllen = 0; /*--- PLL Off ---*/
    if(enable == 0)  {
        spin_unlock_irqrestore(&ohio_clock_spinlock, flags);
        return;
    }

    PLL->PLL_ctrl.Bits.pllpwdn = 0; /*--- Power up ---*/
    PLL->PLL_mult.Bits.pllm = mult - 1;
    if(pre_div) {
        PLL->PLL_pre_div.Bits.ratio = pre_div - 1;
        PLL->PLL_pre_div.Bits.diven = 1;
    } else {
        PLL->PLL_pre_div.Bits.diven = 1;
    }
    if(post_div) {
        PLL->PLL_cmd_en.Bits.goset = 1;
        PLL->PLL_post_div.Bits.ratio = post_div - 1;
        PLL->PLL_post_div.Bits.diven = 1;
        PLL->PLL_cmd.Bits.goset = 1;
    } else {
        PLL->PLL_pre_div.Bits.diven = 1;
    }
    if(post_div2) {
        PLL->PLL_cmd_en.Bits.goset = 1;
        PLL->PLL_post_div2.Bits.ratio = post_div2 - 1;
        PLL->PLL_post_div2.Bits.diven = 1;
        PLL->PLL_cmd.Bits.goset = 1;
    }
    PLL->PLL_ctrl.Bits.pllen = 1; /*--- PLL On ---*/
    spin_unlock_irqrestore(&ohio_clock_spinlock, flags);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
struct _ohio_clk_notify_system_clock_change {
    struct _ohio_clk_notify_system_clock_change *next;
    enum _avm_clock_id clock_id;
    unsigned int (*notify)(enum _avm_clock_id, unsigned int);
};


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int ohio_change_clock_read(char* buf, char **start, off_t offset, int count, int *eof, void *data) {
    unsigned int mips_clk   = ohio_get_mips_clock();
    unsigned int system_clk = ohio_get_system_clock();
    unsigned int usb_clk    = ohio_get_usb_clock();
    unsigned int dsp_clk    = ohio_get_dsp_clock();
    unsigned int len;

    sprintf(buf, "OHIO Clock: CPU: %u.%03ukHz System: %u.%03ukHz USB: %u.%03ukHz DSP: %u.%03ukHz\n",
            mips_clk / 1000000, (mips_clk % 1000000) / 1000,
            system_clk / 1000000, (system_clk % 1000000) / 1000,
            usb_clk / 1000000, (usb_clk % 1000000) / 1000,
            dsp_clk / 1000000, (dsp_clk % 1000000) / 1000);
        
    len = strlen(buf);
    return len;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int ohio_change_clock_write(struct file *file, const char __user *buff, unsigned long count, void *data) {
    char *p;
    unsigned int clk = 1;
    if(strstr(buff, "kHz")) clk = 1000;
    if(strstr(buff, "KHz")) clk = 1000;
    if(strstr(buff, "mHz")) clk = 1000 * 1000;
    if(strstr(buff, "MHz")) clk = 1000 * 1000;

    /*--- if(clk) ---*/
        /*--- printk("[ohio_change_clock_write] faktor is %u\n", clk); ---*/

    if((p = strstr(buff, "CPU:"))) {
        p += sizeof("CPU:");
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ohio_change_clock_write] CPU: p=\"%s\" set to %uHz ", p, clk); ---*/
        if(ohio_set_mips_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else if((p = strstr(buff, "System:"))) {
        p += sizeof("System:");
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ohio_change_clock_write] System: p=\"%s\" set to %uHz ", p, clk); ---*/
        if(ohio_set_system_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else if((p = strstr(buff, "USB:"))) {
        p += sizeof("USB:");
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ohio_change_clock_write] USB: p=\"%s\" set to %uHz (%u) ", p, clk, (int)simple_strtol(p, NULL, 0)); ---*/
        if(ohio_set_usb_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else if((p = strstr(buff, "DSP:"))) {
        p += sizeof("DSP:");
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ohio_change_clock_write] DSP: p=\"%s\" set to %uHz ", p, clk); ---*/
        if(ohio_set_dsp_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else {
        printk("[ohio_change_clock_write] <unknown> buf=\"%s\"\n", buff);
    }
    return count;
}
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int __init ohio_clk_init(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    struct _valid_clk *V = ohio_get_valid_mips_clk_table();
    PRINTK("[ohio_clk_init]: dsl xtal %uHz lan xtal %uHz\n", V->freq, ohio_lan_xtal);

    spin_lock_init(&ohio_clock_spinlock);
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    spin_lock_init(&ohio_clock_switch_spinlock);
    ohio_system_clock_notify_first = NULL;
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
    while(V) {
        if(V->startup) {
            if(ohio_set_mips_clock(V->freq))
              return 1;
            else
                break;
        }
        V++;
    }
    /* Initialisiere Systemclock auf 125MHz mit 250MHz fuer DSP */

    /*--- PRINTK("[ohio_clk_init]: clockmode=%s", BOOT->hw_boot_config.Bits.mips_async ? "async":"sync"); ---*/
    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        return 0; /* system clock nicht setzen */
    }
    V = valid_system_clk;
    while(V) {
        if(V->startup) {
            ohio_set_system_clock(V->freq);
            return 0;
        }
        V++;
    }
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
static int __init ohio_clk_switch_init(void) {
    ohio_change_clock_entry = create_proc_entry("clocks", 06666, NULL);
    if(ohio_change_clock_entry) {
        ohio_change_clock_entry->read_proc = ohio_change_clock_read;
        ohio_change_clock_entry->write_proc = ohio_change_clock_write;
        ohio_change_clock_entry->data = NULL;
    }
    return 0;
};

late_initcall(ohio_clk_switch_init);

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ohio_set_clock_notify(enum _avm_clock_id clock_id, unsigned int (*notify)(enum _avm_clock_id, unsigned int new_clk), int set_function) {
    struct _ohio_clk_notify_system_clock_change *aktuell = ohio_system_clock_notify_first;
    struct _ohio_clk_notify_system_clock_change *N;
    int flags;

    printk("[ohio_set_clock_notify] %s notify %s 0x%p ",
        clock_id == avm_clock_id_non ? "avm_clock_id_non" :
        clock_id == avm_clock_id_cpu ? "avm_clock_id_cpu" :
        clock_id == avm_clock_id_system ? "avm_clock_id_system" :
        clock_id == avm_clock_id_usb ? "avm_clock_id_usb" :
        clock_id == avm_clock_id_dsp ? "avm_clock_id_dsp" :
        clock_id == avm_clock_id_vbus ? "avm_clock_id_vbus" : "unknown",
        set_function ? "enable" : "disable",
        notify);
    __print_symbol("0x%x\n", (unsigned long) notify);

    if(set_function) {
        N = vmalloc(sizeof(struct _ohio_clk_notify_system_clock_change));
        if(N == NULL)
            return -1;

        spin_lock_irqsave(&ohio_clock_switch_spinlock, flags);
        N->next     = NULL;
        N->clock_id = clock_id;
        N->notify   = notify;
        /*----------------------------------------------------------------------------------*\
         * nicht der erste, es gibt schon einen
        \*----------------------------------------------------------------------------------*/
        if(ohio_system_clock_notify_first) {
            while(aktuell->next) {
                aktuell = aktuell->next;
            }
            aktuell->next = N;
        } else {
            ohio_system_clock_notify_first = N;
        }
        spin_unlock_irqrestore(&ohio_clock_switch_spinlock, flags);
        return 0;
    } else {
        if(ohio_system_clock_notify_first == NULL) {
            return -1;
        }
        /*----------------------------------------------------------------------------------*\
         * der erste und einzige
        \*----------------------------------------------------------------------------------*/
        spin_lock_irqsave(&ohio_clock_switch_spinlock, flags);
        if((ohio_system_clock_notify_first->notify ==  notify) &&
           (ohio_system_clock_notify_first->clock_id == clock_id)) {
            N = ohio_system_clock_notify_first;
            ohio_system_clock_notify_first = ohio_system_clock_notify_first->next;
            spin_unlock_irqrestore(&ohio_clock_switch_spinlock, flags);
            vfree((void *)N);
            return 0;
        }

        /*----------------------------------------------------------------------------------*\
         * der erste ist es nicht, gibt es noch weitere
        \*----------------------------------------------------------------------------------*/
        while(aktuell->next) {
            if(aktuell->next->notify ==  notify) {
                N = aktuell->next;
                aktuell->next = aktuell->next->next;
                spin_unlock_irqrestore(&ohio_clock_switch_spinlock, flags);
                vfree((void *)N);
                return 0;
            }
            aktuell = aktuell->next;
        }
        spin_unlock_irqrestore(&ohio_clock_switch_spinlock, flags);
    }
    return -1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_call_clock_notifier(enum _avm_clock_id clock_id, unsigned int clk) {
    struct _ohio_clk_notify_system_clock_change *aktuell = ohio_system_clock_notify_first;
    unsigned int ret = 0;
    while(aktuell) {
        if((aktuell->notify) && (aktuell->clock_id == clock_id)) {
            printk("[] call 0x%p ", aktuell->notify);
            __print_symbol("0x%x\n", (unsigned long) aktuell->notify);
            ret |= (aktuell->notify)(clock_id, clk);
        }
        aktuell = aktuell->next;
    }
    return ret;
}
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_set_mips_clock(unsigned int clk) {
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    unsigned int mult, post_div, pre_div;
    struct _valid_clk *V, *VStart;
    
    VStart = V = ohio_get_valid_mips_clk_table();

    while(V->freq) {
        if(V->freq == clk)
            break;
        V++;
    }
    if(V->freq) {
        /*--- PRINTK("[ohio_set_mips_clock] set freq %u Hz pre_div=%u post_div=%u mult=%u\n", V->freq, V->pre_div, V->post_div, V->mult); ---*/

        mult     = V->mult;
        pre_div  = V->pre_div;
        post_div = V->post_div;
    } else {
        printk(KERN_ERR "[ohio_set_mips_clock]: possible value are:");
        while(VStart->freq) {
            printk(" %u.%u KHz", VStart->freq / 1000, VStart->freq % 1000);
            VStart++;
        }
        printk("\n");
        return 1;
    }

    ohio_clk_set_pll(&(ohio_clock->MIPS_PLL), 1 /* enable */, mult, pre_div /* pre div */, post_div /* post div */, 0 /* post div2 */);
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    return ohio_call_clock_notifier(avm_clock_id_cpu, clk);
#else /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
    return 0;
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_set_system_clock(unsigned int clk) {
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    unsigned int mult = 0;
    unsigned int pre_div = 0;
    unsigned int post_div = 0;
    unsigned int post_div2 = 0;
    unsigned int i;
    unsigned int sdram_timing = 0;

    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        if(clk != (ohio_get_mips_clock() / 2)) {
            return 1;
        }
        return 0;
    }

    for(i = 0 ; valid_system_clk[i].freq ; i++) {
        if(valid_system_clk[i].freq == clk)
            break;
    }

    switch(clk) {
        default:
        case 125000000:
            sdram_timing = ohio_SDRAM_config[EMIF_SDRAM_TIMING_125MHZ].i;
            break;
        case 137500000:
            sdram_timing = ohio_SDRAM_config[EMIF_SDRAM_TIMING_137MHZ].i;
            break;
        case 150000000:
            sdram_timing = ohio_SDRAM_config[EMIF_SDRAM_TIMING_150MHZ].i;
            break;
    }
    if(ohio_current_system_clock == clk) {
#ifdef USE_CL2_RAMS
        struct EMIF_register_memory_map *EMIF = (struct EMIF_register_memory_map *)OHIO_EMIF_BASE;
        /*--- printk("[speedup] CL%d -> CL2 \n", EMIF->SDRAMBankCR.Bits.cl ? 3 : 2); ---*/
        EMIF->SDRAMBankCR.i = EMIF->SDRAMBankCR.i & ~(1 << 13);
#endif /*--- #ifdef USE_CL2_RAMS ---*/
        sdram_timing = 0;
    } else if(ohio_current_system_clock < clk) {
        struct EMIF_register_memory_map *EMIF = (struct EMIF_register_memory_map *)OHIO_EMIF_BASE;
#ifdef USE_CL2_RAMS
        /*--- printk("[speedup] (pre) (0x%x) CL%d -> CL2 Timing 0x%x -> 0x%x\n", ---*/ 
                /*--- EMIF->SDRAMBankCR.i, ---*/
                /*--- EMIF->SDRAMBankCR.Bits.cl ? 3 : 2, EMIF->SDRAMTimingReg.i, sdram_timing); ---*/
        EMIF->SDRAMBankCR.i = EMIF->SDRAMBankCR.i & ~(1 << 13);
#else /*--- #ifdef USE_CL2_RAMS ---*/
        /*--- printk("[speedup] (pre) Timing 0x%x -> 0x%x\n", EMIF->SDRAMTimingReg.i, sdram_timing); ---*/
#endif /*--- #else ---*/ /*--- #ifdef USE_CL2_RAMS ---*/
        EMIF->SDRAMTimingReg.i    = sdram_timing;
        sdram_timing = 0;
    }
    
    if(valid_system_clk[i].freq) {
        mult      = valid_system_clk[i].mult;
        pre_div   = valid_system_clk[i].pre_div;
        post_div  = valid_system_clk[i].post_div;
        post_div2 = valid_system_clk[i].post_div2;
    } else {
        printk(KERN_ERR "[ohio_set_system_clock]: possible value are:");
        for(i = 0 ; valid_system_clk[i].freq ; i++) {
            printk(" %u.%u KHz", valid_system_clk[i].freq / 1000, valid_system_clk[i].freq % 1000);
        }
        printk("\n");
        return 1;
    }
    
    /*--- PRINTK("[ohio_set_system_clock] clk=%d, multi=%d, pre_div=%d, post_div=%d, post_div2=%d\n", clk, mult, pre_div, post_div, post_div2); ---*/
    ohio_clk_set_pll(&(ohio_clock->SYSTEM_PLL), 1 /* enable */, mult, pre_div /* pre div */, post_div /* post div */, post_div2 /* post div2 */);

    if(sdram_timing) {  
        struct EMIF_register_memory_map *EMIF = (struct EMIF_register_memory_map *)OHIO_EMIF_BASE;
#ifdef USE_CL2_RAMS
        /*--- printk("[speedup] (post) (0x%x) CL%d -> CL2 Timing 0x%x -> 0x%x\n", ---*/ 
                /*--- EMIF->SDRAMBankCR.i, ---*/
                /*--- EMIF->SDRAMBankCR.Bits.cl ? 3 : 2, EMIF->SDRAMTimingReg.i, sdram_timing); ---*/
        EMIF->SDRAMBankCR.i = EMIF->SDRAMBankCR.i & ~(1 << 13);
#else /*--- #ifdef USE_CL2_RAMS ---*/
        /*--- printk("[speedup] (post) Timing 0x%x -> 0x%x\n", EMIF->SDRAMTimingReg.i, sdram_timing); ---*/
#endif /*--- #else ---*/ /*--- #ifdef USE_CL2_RAMS ---*/
        EMIF->SDRAMTimingReg.i    = sdram_timing;
    }
    ohio_current_system_clock = clk;

#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    return ohio_call_clock_notifier(avm_clock_id_system, clk);
#else /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
    return 0;
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
static unsigned int current_usb_clock = 48000000;
static unsigned int ohio_set_usb_clock_notify(enum _avm_clock_id clock_id, unsigned int clk) {
    if(clock_id != avm_clock_id_system)
        return 1;
    return ohio_set_usb_clock(current_usb_clock);
}
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_set_usb_clock(unsigned int clk) {
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    unsigned int pre_div = 1, post_div = 1, mul = 1, system;

#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    ohio_set_clock_notify(avm_clock_id_system, ohio_set_usb_clock_notify, 0); /*--- deinstall ---*/
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/

    if(clk != 48000000)
        return 1;

    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        ohio_clk_set_pll(&(ohio_clock->SYSTEM_PLL), 1, 5, 1, 1, 0 /* post div2 */);
    }
    system = ohio_clk_get_pll_factor(&(ohio_clock->SYSTEM_PLL), ohio_lan_xtal);
    /*--- PRINTK("[ohio_set_usb_clock] usb pll input %u Hz\n", system); ---*/

    switch(system) {
        case  25000000:  /*--- kein USB mehr ---*/
        case  50000000:
        case  75000000:
        case 100000000:
        case 137500000:
        default:
            return 1;
        case 150000000:
            pre_div = 25;
            post_div = 1;
            mul = 8;
            break;
        case 125000000:
            pre_div = 26;
            post_div = 1;
            mul = 10;
            break;
        case 162500000:
            break;
    }
    ohio_clk_set_pll(&(ohio_clock->USB_PLL), 1, mul, pre_div, post_div, 0 /* post div2 */);

#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    current_usb_clock = clk;
    ohio_set_clock_notify(avm_clock_id_system, ohio_set_usb_clock_notify, 1); /*--- install ---*/
    return ohio_call_clock_notifier(avm_clock_id_usb, clk);
#else /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
    return 0;
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_set_dsp_clock(unsigned int clk) {
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    unsigned int test_clk;

    test_clk = ohio_clk_get_pll_factor2(&(ohio_clock->SYSTEM_PLL), ohio_lan_xtal);

    /*--- printk("###CLKDEBUG: test_clk = %d, clk = %d\n", test_clk, clk); ---*/
    if(test_clk == clk) {
        if(BOOT->hw_boot_config.Bits.pll_byp == 0) {
            BOOT->hw_boot_adsl_vserclksel.Bits.ohio_250_mode = 1;
            goto good;
        }
    }
    test_clk = ohio_get_mips_clock();
    
    if(test_clk == clk) {
        BOOT->hw_boot_adsl_vserclksel.Bits.ohio_250_mode = 0;
        goto good;
    }
    return 1;
good:
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    return ohio_call_clock_notifier(avm_clock_id_dsp, clk);
#else /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
    return 0;
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_get_system_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    unsigned int clk = 0;

    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        /*--- PRINTK(KERN_ERR "[ohio_get_system_clock]: ohio is synchon\n"); ---*/
        clk = ohio_get_mips_clock() / 2;
    } else {
        clk = ohio_clk_get_pll_factor(&(ohio_clock->SYSTEM_PLL), ohio_lan_xtal);
    }
    /*--- PRINTK(KERN_ERR "[ohio_get_system_clock]: clk = %u\n", clk); ---*/
    return clk;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_get_mips_clock(void) {
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    struct _valid_clk *V = ohio_get_valid_mips_clk_table();
    unsigned int clk;

    clk = ohio_clk_get_pll_factor(&(ohio_clock->MIPS_PLL), V->freq);
    /*--- PRINTK("[ohio_get_mips_clock]: clk = %u\n", clk); ---*/
    return clk;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_get_dsp_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    unsigned int clk;

    if((BOOT->hw_boot_config.Bits.pll_byp == 0) && (BOOT->hw_boot_adsl_vserclksel.Bits.ohio_250_mode == 1)) {
        clk = ohio_clk_get_pll_factor(&(ohio_clock->SYSTEM_PLL), ohio_lan_xtal);
        clk *= ohio_clock->SYSTEM_PLL.PLL_post_div.Bits.ratio + 1;
        clk /= ohio_clock->SYSTEM_PLL.PLL_post_div2.Bits.ratio + 1;
    } else {
        clk = ohio_get_mips_clock();
    }
    /*--- PRINTK("[ohio_get_dsp_clock]: clk = %u\n", clk); ---*/
    return clk;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ohio_get_usb_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)OHIO_DEVICE_CONFIG_BASE;
    struct _hw_clock *ohio_clock = (struct _hw_clock *)OHIO_CLOCK_BASE;
    unsigned int clk;

    if(BOOT->hw_boot_config.Bits.pll_byp == 0) { 
        clk = ohio_clk_get_pll_factor(&(ohio_clock->SYSTEM_PLL), ohio_lan_xtal);
    } else {
        clk = ohio_lan_xtal;
    }
    clk = ohio_clk_get_pll_factor(&(ohio_clock->USB_PLL), clk);
    /*--- PRINTK("[ohio_get_usb_clock]: clk = %u\n", clk); ---*/

    return clk;
}
            
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ohio_get_clock(enum _avm_clock_id clock_id) {
    switch(clock_id) {
        case avm_clock_id_cpu:
            return ohio_get_mips_clock();
        case avm_clock_id_peripheral:
            return ohio_get_system_clock() / 2;
        case avm_clock_id_system:
            return ohio_get_system_clock();
        case avm_clock_id_vbus:
            return ohio_get_system_clock() / 2;
        case avm_clock_id_usb:
            return ohio_get_usb_clock();
        case avm_clock_id_dsp:
            return ohio_get_dsp_clock();
        default:
            break;
    }
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ohio_set_clock(enum _avm_clock_id clock_id, unsigned int clk) {
    switch(clock_id) {
        case avm_clock_id_cpu:
            return ohio_set_mips_clock(clk);
        case avm_clock_id_system:
            return ohio_set_system_clock(clk);
        case avm_clock_id_usb:
            return ohio_set_usb_clock(clk);
        case avm_clock_id_dsp:
            return ohio_set_dsp_clock(clk);
        case avm_clock_id_vbus:
        default:
            break;
    }
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
unsigned int ohio_get_clock_notify(enum _avm_clock_id clock_id, 
                                   unsigned int (*handler)(enum _avm_clock_id, unsigned int new_clk)) {
    ohio_set_clock_notify(clock_id, handler, 0); /*--- deinstall ---*/
    ohio_set_clock_notify(clock_id, handler, 1); /*--- install ---*/
    return ohio_get_clock(clock_id);
}
EXPORT_SYMBOL(ohio_get_clock_notify);
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/

EXPORT_SYMBOL(ohio_get_clock);
EXPORT_SYMBOL(ohio_set_clock);

