/*
 * 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.
 *
 * AR7 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/ar7.h>
#include <asm/mips-boards/ar7int.h>
#include <asm/mach_avm.h>
#include <asm/mach-ar7/hw_boot.h>
#include <asm/mach-ar7/hw_emif.h>
#include <asm/time.h>

#ifdef CONFIG_AVM_POWERMETER
#include <linux/avm_power.h>
#endif/*--- #ifdef CONFIG_AVM_POWERMETER ---*/

/*--- #define AR7_CLK_DEBUG ---*/
#if defined(AR7_CLK_DEBUG)
#define DBG_TRC         printk
#else/*--- #if defined(AR7_CLK_DEBUG) ---*/
#define DBG_TRC(a,...)
#endif/*--- #else ---*//*--- #if defined(AR7_CLK_DEBUG) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static spinlock_t ar7_clock_spinlock;
static unsigned int ar7_current_system_clock;
#define default_ar7_lan_xtal  25000000
const unsigned int ar7_lan_xtal = default_ar7_lan_xtal;
#define default_ar7_dsp_xtal 35328000
const unsigned int ar7_dsp_xtal = default_ar7_dsp_xtal;

#if defined(CONFIG_AR7_CLOCK_SWITCH)
struct _ar7_clk_notify_system_clock_change {
    struct _ar7_clk_notify_system_clock_change *next;
    enum _avm_clock_id clock_id;
    unsigned int (*notify)(enum _avm_clock_id, unsigned int);
};
static spinlock_t ar7_clock_switch_spinlock;
static struct _ar7_clk_notify_system_clock_change *ar7_system_clock_notify_first;
static struct proc_dir_entry *ar7_change_clock_entry;

#define MAX_CLK_LISTENTRY       10
static struct _clk_listentry  {
    volatile unsigned int locked;
    struct _ar7_clk_notify_system_clock_change entry;
} clk_listentry[MAX_CLK_LISTENTRY];

/*--------------------------------------------------------------------------------*\
 * kmalloc darf hier nicht verwendet werden (zu frueh!)
\*--------------------------------------------------------------------------------*/
static struct _ar7_clk_notify_system_clock_change *alloc_clk_listentry(void) {
    unsigned int i;
    long flags;
    spin_lock_irqsave(&ar7_clock_spinlock, flags);
    for(i = 0; i < MAX_CLK_LISTENTRY; i++) {
        if(clk_listentry[i].locked == 0) {
            clk_listentry[i].locked = 1;
            spin_unlock_irqrestore(&ar7_clock_spinlock, flags);
            /*--- printk("[clk]alloc_clk_listentry: %d\n", i); ---*/
            return &clk_listentry[i].entry;
        }
    }
    spin_unlock_irqrestore(&ar7_clock_spinlock, flags);
    printk("[clk]alloc_clk_listentry: failed!\n");
    return NULL;
}
/*--------------------------------------------------------------------------------*\
\*--------------------------------------------------------------------------------*/
static void free_clk_listentry(struct _ar7_clk_notify_system_clock_change *entry) {
    unsigned int i;
    long flags;
    spin_lock_irqsave(&ar7_clock_spinlock, flags);
    for(i = 0; i < MAX_CLK_LISTENTRY; i++) {
        if(clk_listentry[i].locked && (&clk_listentry[i].entry == entry)) {
            clk_listentry[i].locked = 0;
            /*--- printk("[clk]free_clk_listentry: %d\n", i); ---*/
            spin_unlock_irqrestore(&ar7_clock_spinlock, flags);
            return;
        }
    }
    spin_unlock_irqrestore(&ar7_clock_spinlock, flags);
    printk("[clk]free_clk_listentry: failed!\n");
    return;
}
#endif /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int __init ar7_clk_switch_init(void);
static unsigned int ar7_call_clock_notifier(enum _avm_clock_id clock_id, unsigned int clk);
static unsigned int ar7_set_mips_clock(unsigned int clk);
static unsigned int ar7_set_system_clock(unsigned int clk);
static unsigned int ar7_set_usb_clock_notify(enum _avm_clock_id clock_id, unsigned int clk);
static unsigned int ar7_set_usb_clock(unsigned int clk);
static unsigned int ar7_set_dsp_clock(unsigned int clk);
static unsigned int ar7_get_system_clock(void);
static unsigned int ar7_get_mips_clock(void);
static unsigned int ar7_get_dsp_clock(void);
static unsigned int ar7_get_usb_clock(void);

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

struct _valid_clk valid_system_clk[] = {
    { freq: 25000000, pre_div: 1, mult:  10, post_div: 10, clk_sel: 1 },
    { freq: 50000000, pre_div: 1, mult:  10, post_div: 5, clk_sel: 1 },
    { freq: 75000000, pre_div: 1, mult:  6, post_div: 2, clk_sel: 1 },
    { freq: 62500000, pre_div: 1, mult: 10, post_div: 4, clk_sel: 1 },
    { freq:100000000, pre_div: 1, mult:  8, post_div: 2, clk_sel: 1 },
    { startup: 1, freq:125000000, pre_div: 1, mult: 5, post_div: 1, clk_sel: 1 },
    { 0, 0, 0, 0 }
};
#if 0
struct _valid_clk valid_mips_clk_with_dsp_xtal[] = {
    { freq: default_ar7_dsp_xtal * 1, pre_div: 1, mult:  1, post_div: 1, clk_sel: 0 },
    { freq: default_ar7_dsp_xtal * 2, pre_div: 1, mult:  2, post_div: 1, clk_sel: 0 },
    { freq: default_ar7_dsp_xtal * 3, pre_div: 1, mult:  3, post_div: 1, clk_sel: 0 },
    { freq: default_ar7_dsp_xtal * 4, pre_div: 1, mult:  4, post_div: 1, clk_sel: 0 },
    { freq: default_ar7_dsp_xtal * 5, pre_div: 1, mult:  5, post_div: 1, clk_sel: 0 },
    { startup: 1, freq: default_ar7_dsp_xtal * 6, pre_div: 1, mult:  6, post_div: 1, clk_sel: 0}, /* Frequ: 211968000 Hz */
    { freq: default_ar7_dsp_xtal * 7, pre_div: 1, mult:  7, post_div: 1, clk_sel: 0},  /* 247296000 Hz */
    { 0, 0, 0, 0 }
};
#endif

struct _valid_clk valid_mips_clk_with_lan_xtal[] = {
    { freq: default_ar7_lan_xtal * 1, pre_div: 1, mult:  1, post_div: 1, clk_sel: 1},  /* 25 Mhz */
    { freq: default_ar7_lan_xtal * 2, pre_div: 1, mult:  2, post_div: 1, clk_sel: 1 },
    { freq: default_ar7_lan_xtal * 3, pre_div: 1, mult:  3, post_div: 1, clk_sel: 1 },
    { freq: default_ar7_lan_xtal * 4, pre_div: 1, mult:  4, post_div: 1, clk_sel: 1 },
    { freq: default_ar7_lan_xtal * 5, pre_div: 1, mult:  5, post_div: 1, clk_sel: 1 },
    { startup: 1, freq: default_ar7_lan_xtal * 6, pre_div: 1, mult:  6, post_div: 1, clk_sel: 1 },  /* 150 MHz */
    { freq: default_ar7_lan_xtal * 13 / 2, pre_div: 2, mult: 13, post_div: 1, clk_sel: 1 },  /* 162.5 MHz */
    { freq: default_ar7_lan_xtal * 7, pre_div: 1, mult:  7, post_div: 1, clk_sel: 1 },  /* 175 MHz */
    { freq: default_ar7_lan_xtal * 8, pre_div: 1, mult:  8, post_div: 1, clk_sel: 1 },  /* 200 MHz */
    { 0, 0, 0, 0 }
};

struct _valid_clk valid_dsp_clk[] = {
    { startup: 1, freq: default_ar7_lan_xtal * 8, pre_div: 1, mult:  8, post_div: 1, clk_sel: 1}, /* Freq: 200000000 Hz */
    { freq: default_ar7_dsp_xtal * 6, pre_div: 1, mult:  6, post_div: 1, clk_sel: 0},   /* Freq: 211968000 Hz */
    { freq: default_ar7_lan_xtal * 10, pre_div: 1, mult:  10, post_div: 1, clk_sel: 1}, /* Freq: 250000000 Hz */
    { 0, 0, 0, 0 }
};

/*--- Abgeleitet von System oder MIPS-Clock (je nach clk_sel) - freq gibt hier dies Referenz-Frequenz an ---*/
struct _valid_clk valid_usb_clk_48Mhz[] = {
    /*--- abgleitet von System-Clock ---*/
    { freq:150000000, pre_div: 25, mult: 8,  post_div: 1, clk_sel: 0 },
    { freq:125000000, pre_div: 26, mult: 10, post_div: 1, clk_sel: 0 },
    { freq:62500000,  pre_div: 13, mult: 10, post_div: 1, clk_sel: 0 },
    /*--- abgleitet von MIPS-Clock ---*/
    { freq:150000000, pre_div: 25, mult: 8,  post_div: 1,  clk_sel: 3 },
    { 0, 0, 0, 0 }
};

struct _valid_clk valid_usb_clk_25Mhz[] = {
    /*--- abgleitet von System-Clock ---*/
    { freq:150000000, pre_div: 6, mult: 1, post_div: 1, clk_sel: 0 },
    { freq:125000000, pre_div: 5, mult: 1, post_div: 1, clk_sel: 0 },
    { freq:62500000,  pre_div: 1, mult: 2, post_div: 2, clk_sel: 0 },
    /*--- abgleitet von MIPS-Clock ---*/
    { freq:162500000, pre_div: 26, mult: 8,  post_div: 2, clk_sel: 3 },
    { 0, 0, 0, 0 }
};

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
union EMIF_SDRAMTimingReg ar7_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 CS3 ---*/
#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 CS3 ---*/
        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 CS3 ---*/
        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
};

/*--------------------------------------------------------------------------------*\
\*--------------------------------------------------------------------------------*/
static inline unsigned int ar7_get_clockregister(enum _avm_clock_id id, struct _hw_clock *hw_clock, struct _hw_boot *hw_boot, 
                                                 union __clock_CR0 **CLKCR, union __clock_PLLCR **CLKPLLCR, 
                                                 union __clock_ACLKPLLCR1 **CLKPLLCR1, unsigned int *clk_sel) {
    *CLKPLLCR1 = NULL;
    switch(id) {
        case  avm_clock_id_cpu:
            *CLKCR     = &hw_clock->MCLKCR;
            *CLKPLLCR  = &hw_clock->MCLKPLLCR;
            if(clk_sel) *clk_sel = hw_boot->hw_boot_config.Bits.mipspllsel;
            break;
        case  avm_clock_id_system:
            *CLKCR     = &hw_clock->SCLKCR;
            *CLKPLLCR  = &hw_clock->SCLKPLLCR;
            if(clk_sel) *clk_sel = hw_boot->hw_boot_config.Bits.syspllsel;
            break;
        case  avm_clock_id_usb:
            *CLKCR     = &hw_clock->UCLKCR;
            *CLKPLLCR  = &hw_clock->UCLKPLLCR;
            if(clk_sel) *clk_sel = hw_boot->hw_boot_config.Bits.usbpllsel;
            break;
        case  avm_clock_id_dsp:
            if(hw_boot->hw_boot_adsl_pll_select.Bits.pll_0_select) {  
                /*--- verwende APLL0 ---*/
                *CLKCR     = &hw_clock->ACLKCR0;
                *CLKPLLCR  = &hw_clock->ACLKPLLCR0;
            } else {
                /*--- verwende APLL1 ---*/
                *CLKCR     = &hw_clock->ACLKCR1;
                *CLKPLLCR  = NULL;
                *CLKPLLCR1 = &hw_clock->ACLKPLLCR1; /*--- Achtung ! anderer Registeraufbau! ---*/
            }
            if(clk_sel) *clk_sel = hw_boot->hw_boot_config.Bits.adslpllsel;

            break;
        case  avm_clock_id_vbus:
            /*--- kein break; ---*/
        default:
            return -1;
            break;

    }
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_clk_get_pll_factor(enum _avm_clock_id id, struct _hw_clock *hw_clock, unsigned int count){
    union __clock_CR0 *CLKCR;
    union __clock_PLLCR *CLKPLLCR;
    union __clock_ACLKPLLCR1 *CLKPLLCR1;
    unsigned int  clk_predivider;
    unsigned int  clk_postdivider;
    unsigned int  k, m, d, clk_sel;
    struct _hw_boot *AR7_hw_boot  = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;

    if(ar7_get_clockregister(id, hw_clock, AR7_hw_boot, &CLKCR, &CLKPLLCR, &CLKPLLCR1, &clk_sel)) {
        return 0;
    }
    /* Pre-Divider and post-divider are 5 bit N+1 dividers */
    clk_postdivider = CLKCR->Bits.postclkdiv + 1;
    clk_predivider  = CLKCR->Bits.preclkdiv  + 1;
    k               = CLKPLLCR->Bits.pllmul + 1;

    DBG_TRC("[ar7_clk_get_pll_factor] %s basefreq %u prediv %u faktor %u postdiv %u clk_sel=%u plonoff=%u\n",
        id == avm_clock_id_non ? "avm_clock_id_non" :
        id == avm_clock_id_cpu ? "avm_clock_id_cpu" :
        id == avm_clock_id_system ? "avm_clock_id_system" :
        id == avm_clock_id_usb ? "avm_clock_id_usb" :
        id == avm_clock_id_dsp ? "avm_clock_id_dsp" :
        id == avm_clock_id_vbus ? "avm_clock_id_vbus" : "unknown", count,
                                                                   clk_predivider,
                                                                   k,
                                                                   clk_postdivider,
                                                                   clk_sel,
                                                                   CLKPLLCR->Bits.plonoff 
                                                                   );

    if(CLKPLLCR) {
        /*--- see TNETD7300 Device table section 1.3.3.5 page 1-37 ---*/
        if(CLKPLLCR->Bits.pllndiv == 0) {
            m = 1;
            if(k == 16) {
                d = 4;
            } else {
                d = 2;
            }
        } else {
            if(CLKPLLCR->Bits.pldiv == 0) {
                d = 1;
                if(k == 16) {
                    m = 1;
                } else {
                    m = k;
                }
            } else {
                if((k & 1) == 0) {
                    /*--- even ---*/
                    m = k - 1;
                    d = 4;
                } else {
                    /*--- odd ---*/
                    m = k;
                    d = 2;
                }
            }
        }
    } else {
        printk("[ar7_clk_get_pll_factor] ADSLSS PLL1 ??? no calculate on ACLKPLLCR1 ???\n");
        m = 1;
        d = 1;
    }
    count /= clk_predivider;
    if(AR7_hw_boot->hw_boot_config.Bits.pll_byp) {
        count /= clk_postdivider;
        return count;
    }
    count = (count * m) / d;
    count /= clk_postdivider;
    DBG_TRC("[ar7_clk_get_pll_factor] pllfreq = %u\n", count);
    return count;
}

#define  DIVIDER_LOCK_TIME      (10100 * 10)
#define  PLL_LOCK_TIME          (10100 * 75)
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void ar7_clk_set_pll(enum _avm_clock_id id, struct _hw_clock *hw_clock,
                             unsigned int enable, 
                             unsigned int mult, 
                             unsigned int pre_div, 
                             unsigned int post_div
                             ) {

    int flags;
    volatile unsigned int temp;
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    union __clock_CR0 *CLKCR;
    union __clock_PLLCR *CLKPLLCR;
    union __clock_ACLKPLLCR1 *CLKPLLCR1;

    DBG_TRC("[ar7_clk_set_pll] %s enable %u prediv %u faktor=%u postdiv %u\n",
        id == avm_clock_id_non ? "avm_clock_id_non" :
        id == avm_clock_id_cpu ? "avm_clock_id_cpu" :
        id == avm_clock_id_system ? "avm_clock_id_system" :
        id == avm_clock_id_usb ? "avm_clock_id_usb" :
        id == avm_clock_id_dsp ? "avm_clock_id_dsp" :
        id == avm_clock_id_vbus ? "avm_clock_id_vbus" : "unknown", enable,
                                                                   pre_div,
                                                                   mult,
                                                                   post_div);

    if(ar7_get_clockregister(id, hw_clock, BOOT, &CLKCR, &CLKPLLCR, &CLKPLLCR1, NULL)) {
        return;
    }
    spin_lock_irqsave(&ar7_clock_spinlock, flags);
    if(enable == 0)  {
        CLKPLLCR->Bits.plonoff = 0; /*--- PLL Off ---*/
        spin_unlock_irqrestore(&ar7_clock_spinlock, flags);
        return;
    }

    CLKCR->Bits.postclkdiv = post_div - 1;
    CLKCR->Bits.preclkdiv  = pre_div  - 1;
    
    if(CLKPLLCR) {
        CLKPLLCR->Bits.pllndiv = 0;
        CLKPLLCR->Bits.plonoff = 1; /*--- PLL On ---*/

        for(temp =0; temp < DIVIDER_LOCK_TIME; temp++);
        while (CLKPLLCR->Bits.status);          /*--- wait for divider output to stabilise ---*/ 
#if 0
        /*write to PLL clock register*/
        if(id == avm_clock_id_system) {
            /* Bring DRAM out of hold */
            EMIF->SDRAMBankCR.Bits.sr = 1; 
        }
#endif

        CLKPLLCR->Bits.pllmul = mult - 1;
        CLKPLLCR->Bits.pllndiv = 1;

        for(temp =0; temp < DIVIDER_LOCK_TIME; temp++);
        while (!CLKPLLCR->Bits.status);         /*--- wait for divider output to stabilise ---*/
#if 0
        /* Bring DRAM out of hold */
        if(id == avm_clock_id_system) {
            /* Bring DRAM out of hold */
            EMIF->SDRAMBankCR.Bits.sr = 0; 
        }
#endif
    } else {
        printk("[ar7_clk_set_pll] ADSLSS PLL1 ??? no change on ACLKPLLCR1 ???\n");
    }
    spin_unlock_irqrestore(&ar7_clock_spinlock, flags);
    return;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_AR7_CLOCK_SWITCH)
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int ar7_change_clock_read(char* buf, char **start, off_t offset, int count, int *eof, void *data) {
    unsigned int mips_clk   = ar7_get_mips_clock();
    unsigned int system_clk = ar7_get_system_clock();
    unsigned int usb_clk    = ar7_get_usb_clock();
    unsigned int dsp_clk    = ar7_get_dsp_clock();
    unsigned int len;

    sprintf(buf, "AR7 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 ar7_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("[ar7_change_clock_write] faktor is %u\n", clk); ---*/

    if((p = strstr(buff, "CPU:"))) {
        p += sizeof("CPU:") - 1;
        while(*p && (*p == ' ' || *p == '\t')) p++; 
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ar7_change_clock_write] CPU: p=\"%s\" set to %uHz ", p, clk); ---*/
        if(ar7_set_mips_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else if((p = strstr(buff, "System:"))) {
        p += sizeof("System:") - 1;
        while(*p && (*p == ' ' || *p == '\t')) p++; 
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ar7_change_clock_write] System: p=\"%s\" set to %uHz ", p, clk); ---*/
        if(ar7_set_system_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else if((p = strstr(buff, "USB:"))) {
        p += sizeof("USB:") - 1;
        while(*p && (*p == ' ' || *p == '\t')) p++; 
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ar7_change_clock_write] USB: p=\"%s\" set to %uHz (%u) ", p, clk, (int)simple_strtol(p, NULL, 0)); ---*/
        if(ar7_set_usb_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else if((p = strstr(buff, "DSP:"))) {
        p += sizeof("DSP:") - 1;
        while(*p && (*p == ' ' || *p == '\t')) p++; 
		clk *= simple_strtol(p, NULL, 0);
        /*--- printk("[ar7_change_clock_write] DSP: p=\"%s\" set to %uHz ", p, clk); ---*/
        if(ar7_set_dsp_clock(clk))
            printk(" failed\n");
        /*--- else ---*/
            /*--- printk(" success\n"); ---*/
    } else {
        printk("[ar7_change_clock_write] <unknown> buf=\"%s\"\n", buff);
    }
    return count;
}
#endif /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int __init ar7_clk_init(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _valid_clk *V;

    spin_lock_init(&ar7_clock_spinlock);
#if defined(CONFIG_AR7_CLOCK_SWITCH)
    spin_lock_init(&ar7_clock_switch_spinlock);
    ar7_system_clock_notify_first = NULL;
#endif /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/
    V = valid_mips_clk_with_lan_xtal;
    printk("[ar7_clk_init]: dsl xtal %uHz lan xtal %uHz\n", V->freq, ar7_lan_xtal);
    while(V) {
        if(V->startup) {
            ar7_set_mips_clock(V->freq);
            break;
        }
        V++;
    }
    /* Initialisiere Systemclock auf 125MHz */
    DBG_TRC("[ar7_clk_init]: clockmode=%s %s\n", BOOT->hw_boot_config.Bits.mips_async ? "async":"sync", BOOT->hw_boot_config.Bits.pll_byp ? "pll bypassed" : "pll on");


    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) {
            ar7_set_system_clock(V->freq);
            ar7_set_usb_clock(48000000);
            return 0;
        }
        V++;
    }
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_AR7_CLOCK_SWITCH)
static int __init ar7_clk_switch_init(void) {
    ar7_change_clock_entry = create_proc_entry("clocks", 06666, NULL);
    if(ar7_change_clock_entry) {
        ar7_change_clock_entry->read_proc = ar7_change_clock_read;
        ar7_change_clock_entry->write_proc = ar7_change_clock_write;
        ar7_change_clock_entry->data = NULL;
    }
    return 0;
};

late_initcall(ar7_clk_switch_init);

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_set_clock_notify(enum _avm_clock_id clock_id, unsigned int (*notify)(enum _avm_clock_id, unsigned int new_clk), int set_function) {
    struct _ar7_clk_notify_system_clock_change *aktuell = ar7_system_clock_notify_first;
    struct _ar7_clk_notify_system_clock_change *N;
    int flags;

    DBG_TRC("[ar7_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 = alloc_clk_listentry();
        if(N == NULL)
            return -1;

        spin_lock_irqsave(&ar7_clock_switch_spinlock, flags);
        N->next     = NULL;
        N->clock_id = clock_id;
        N->notify   = notify;
        /*----------------------------------------------------------------------------------*\
         * nicht der erste, es gibt schon einen
        \*----------------------------------------------------------------------------------*/
        if(ar7_system_clock_notify_first) {
            while(aktuell->next) {
                aktuell = aktuell->next;
            }
            aktuell->next = N;
        } else {
            ar7_system_clock_notify_first = N;
        }
        spin_unlock_irqrestore(&ar7_clock_switch_spinlock, flags);
        return 0;
    } else {
        /*----------------------------------------------------------------------------------*\
         * der erste und einzige
        \*----------------------------------------------------------------------------------*/
        spin_lock_irqsave(&ar7_clock_switch_spinlock, flags);
        if(ar7_system_clock_notify_first == NULL) {
            spin_unlock_irqrestore(&ar7_clock_switch_spinlock, flags);
            return -1;
        }
        if((ar7_system_clock_notify_first->notify ==  notify) &&
           (ar7_system_clock_notify_first->clock_id == clock_id)) {
            N = ar7_system_clock_notify_first;
            ar7_system_clock_notify_first = ar7_system_clock_notify_first->next;
            spin_unlock_irqrestore(&ar7_clock_switch_spinlock, flags);
            free_clk_listentry(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(&ar7_clock_switch_spinlock, flags);
                free_clk_listentry(N);
                return 0;
            }
            aktuell = aktuell->next;
        }
        spin_unlock_irqrestore(&ar7_clock_switch_spinlock, flags);
    }
    return -1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_call_clock_notifier(enum _avm_clock_id clock_id, unsigned int clk) {
    struct _ar7_clk_notify_system_clock_change *aktuell = ar7_system_clock_notify_first;
    unsigned int ret = 0;
    while(aktuell) {
        if((aktuell->notify) && (aktuell->clock_id == clock_id)) {
            DBG_TRC("[] 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_AR7_CLOCK_SWITCH) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_set_mips_clock(unsigned int clk) {
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    unsigned int mult, post_div, pre_div, clk_sel;
    struct _valid_clk *V, *VStart;
    
    VStart = V = valid_mips_clk_with_lan_xtal;
    clk_sel = BOOT->hw_boot_config.Bits.mipspllsel;
    while(V->freq) {
        if((V->freq == clk) && (V->clk_sel == clk_sel))
            break;
        V++;
    }
    if(V->freq) {
        DBG_TRC("[ar7_set_mips_clock] set freq %u Hz pre_div=%u post_div=%u mult=%u clk_sel=%u\n", V->freq, V->pre_div, V->post_div, V->mult, V->clk_sel);

        mult     = V->mult;
        pre_div  = V->pre_div;
        post_div = V->post_div;
    } else {
        printk(KERN_ERR "[ar7_set_mips_clock]: possible value are:");
        while(VStart->freq) {
            printk(KERN_ERR" %u.%u KHz", VStart->freq / 1000, VStart->freq % 1000);
            VStart++;
        }
        printk(KERN_ERR"\n");
        return 1;
    }
    ar7_clk_set_pll(avm_clock_id_cpu, ar7_clock, 1 /* enable */, mult, pre_div, post_div) ;
#ifdef CONFIG_AVM_POWERMETER
    PowerManagmentRessourceInfo(powerdevice_cpuclock, FREQUENZ_TO_PERCENT(V->freq, ar7_get_mips_clock()));
#endif/*--- #ifdef CONFIG_AVM_POWERMETER ---*/
#if defined(CONFIG_AR7_CLOCK_SWITCH)
    return ar7_call_clock_notifier(avm_clock_id_cpu, clk);
#else /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/
    return 0;
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_set_system_clock(unsigned int clk) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    unsigned int mult = 0;
    unsigned int pre_div = 0;
    unsigned int post_div = 0;
    unsigned int i;
    unsigned int sdram_timing = 0, clk_sel;

    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        if(clk != (ar7_get_mips_clock() / 2)) {
            return 1;
        }
        return 0;
    }
    clk_sel = BOOT->hw_boot_config.Bits.syspllsel;
    for(i = 0 ; valid_system_clk[i].freq ; i++) {
        if((valid_system_clk[i].freq == clk) && (valid_system_clk[i].clk_sel == clk_sel)){
            break;
        }
    }
    switch(clk) {
        default:
        case 125000000:
            sdram_timing = ar7_SDRAM_config[EMIF_SDRAM_TIMING_125MHZ].i;
            break;
        case 137500000:
            sdram_timing = ar7_SDRAM_config[EMIF_SDRAM_TIMING_137MHZ].i;
            break;
        case 150000000:
            sdram_timing = ar7_SDRAM_config[EMIF_SDRAM_TIMING_150MHZ].i;
            break;
    }
    if(ar7_current_system_clock == clk) {
#ifdef USE_CL2_RAMS
        struct EMIF_register_memory_map *EMIF = (struct EMIF_register_memory_map *)AR7_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(ar7_current_system_clock < clk) {
        struct EMIF_register_memory_map *EMIF = (struct EMIF_register_memory_map *)AR7_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;
#ifdef CONFIG_AVM_POWERMETER
        PowerManagmentRessourceInfo(powerdevice_systemclock, FREQUENZ_TO_PERCENT(valid_system_clk[i].freq, 150000000));
#endif/*--- #ifdef CONFIG_AVM_POWERMETER ---*/
    } else {
        printk(KERN_ERR "[ar7_set_system_clock]: possible value are:");
        for(i = 0 ; valid_system_clk[i].freq ; i++) {
            printk(KERN_ERR" %u.%u KHz", valid_system_clk[i].freq / 1000, valid_system_clk[i].freq % 1000);
        }
        printk(KERN_ERR"\n");
        return 1;
    }
    ar7_clk_set_pll(avm_clock_id_system, ar7_clock, 1 /* enable */, mult, pre_div, post_div);
    if(sdram_timing) {  
        struct EMIF_register_memory_map *EMIF = (struct EMIF_register_memory_map *)AR7_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;
    }
    ar7_current_system_clock = clk;

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

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

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_set_usb_clock(unsigned int clk) {
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    unsigned int clk_sel, refclk = 0;
    struct _valid_clk *pvalid;

#if defined(CONFIG_AR7_CLOCK_SWITCH)
    ar7_set_clock_notify(avm_clock_id_system, ar7_set_usb_clock_notify, 0); /*--- deinstall ---*/
#endif /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/

    switch(clk) {
        case 25000000:
            pvalid =  valid_usb_clk_25Mhz;
            break;
        case 48000000:
            pvalid =  valid_usb_clk_48Mhz;
            break;
        default:
            printk(KERN_ERR"[ar7_set_usb_clock] invalid input-clock %d\n", clk);
            return 1;
    }
    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        ar7_clk_set_pll(avm_clock_id_system, ar7_clock, 1, 5, 1, 1);
    }
    clk_sel = BOOT->hw_boot_config.Bits.usbpllsel;  
    switch(clk_sel) {
        case 0: /*--- USB_SYSPLL_SELECT ---*/
            refclk = ar7_get_system_clock();
            break;                                              
        case 1: /*--- USB_REFCLKI_SELECT ---*/
            refclk = ar7_lan_xtal;                 
            break;
        case 2:/*--- USB_XTAL3IN_SELECT ---*/
            printk(KERN_ERR"[ar7_get_usb_clock]not supported clock-input\n");
            break;                                              
        case 3:/*--- USB_MIPSPLL_SELECT ---*/
            refclk =  ar7_get_mips_clock();
            break;                                              
    }
    while(pvalid->freq) {
        if((pvalid->freq == refclk) && (pvalid->clk_sel == clk_sel)) {
            break;
        }
        pvalid++;
    }
    if(pvalid->freq) {
        DBG_TRC("[ar7_set_usb_clock] usb pll input %u Hz\n", refclk);
        ar7_clk_set_pll(avm_clock_id_usb, ar7_clock, 1 /* enable */, pvalid->mult, pvalid->pre_div, pvalid->post_div);
#if defined(CONFIG_AR7_CLOCK_SWITCH)
        current_usb_clock = clk;
        ar7_set_clock_notify(avm_clock_id_system, ar7_set_usb_clock_notify, 1); /*--- install ---*/
        return ar7_call_clock_notifier(avm_clock_id_usb, clk);
#else /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/
        return 0;
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/
    }
    printk(KERN_ERR"[ar7_set_usb_clock] can't set usb pll input %u Hz\n", refclk);
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_set_dsp_clock(unsigned int clk) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    unsigned int clk_sel;
    struct _valid_clk *pvalid = valid_dsp_clk;

    clk_sel = BOOT->hw_boot_config.Bits.adslpllsel;  
    while(pvalid->freq) {
        if((pvalid->freq == clk) && (pvalid->clk_sel == clk_sel)) {
            break;
        }
        pvalid++;
    }
    if(pvalid->freq) {
        BOOT->hw_boot_adsl_pll_select.Bits.pll_0_select = 1;  /*--- verwende APLL0 ---*/
        ar7_clk_set_pll(avm_clock_id_dsp, ar7_clock, 1, pvalid->mult, pvalid->pre_div, pvalid->post_div);
#ifdef CONFIG_AVM_POWERMETER
        PowerManagmentRessourceInfo(powerdevice_dspclock, FREQUENZ_TO_PERCENT(clk, 250000000));
#endif/*--- #ifdef CONFIG_AVM_POWERMETER ---*/
        DBG_TRC("[ar7_set_dsp_clock]: clk = %u done\n", clk);
        return 0;
    }
    printk(KERN_ERR"[ar7_set_dsp_clock]: failed setting clk = %u\n", clk);
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_get_system_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    unsigned int refclk = 0, clk;
    unsigned int clk_sel;

    if(BOOT->hw_boot_config.Bits.mips_async == 0) { /*--- hardware is in synchrone mode ---*/
        /*--- printk(KERN_ERR "[ar7_get_system_clock]: ar7 is synchron\n"); ---*/
        clk = ar7_get_mips_clock() / 2;
    } else {
        clk_sel = BOOT->hw_boot_config.Bits.syspllsel;  
        switch(clk_sel) {
            case 0: /*--- AFE_CLKl input ---*/
                refclk = ar7_dsp_xtal;
                break;
            case 1: /*--- REFCLCKl input ---*/
                refclk = ar7_lan_xtal;
                break;
            case 2: /*--- XTAL3IN input ---*/
                printk(KERN_ERR"[ar7_get_system_clock]not supported clock-input\n");
                break;
            case 3: /*--- MIPS-Pll output ---*/
                refclk = ar7_get_mips_clock();
                break;                                              
        }
        clk = ar7_clk_get_pll_factor(avm_clock_id_system, ar7_clock, refclk);
        DBG_TRC("[ar7_get_system_clock]: clk_sel=%d clk = %u\n", clk_sel, clk);
    }
    return clk;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_get_mips_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    unsigned int clk, refclk = 0, clk_sel;
    clk_sel = BOOT->hw_boot_config.Bits.mipspllsel;  
    switch(clk_sel) {
        case 0: /*--- AFE_CLKl input ---*/
            refclk = ar7_dsp_xtal;
            break;
        case 1: /*--- REFCLCKl input ---*/
            refclk = ar7_lan_xtal;
            break;
        case 2: /*--- XTAL3IN input ---*/
        case 3: /*--- MIPS-Pll output ---*/
            printk(KERN_ERR"[ar7_get_mips_clock]no supported clock-input\n");
            break;                                              
    }

    clk = ar7_clk_get_pll_factor(avm_clock_id_cpu, ar7_clock, refclk);
    DBG_TRC("[ar7_get_mips_clock]: clk = %u\n", clk);
    return clk;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_get_dsp_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    unsigned int clk, refclk = 0;
    unsigned int clk_sel;
    clk_sel = BOOT->hw_boot_config.Bits.adslpllsel;  
    switch(clk_sel) {
        case 0: /*--- AFE_CLKl input ---*/
            refclk = ar7_dsp_xtal;
            break;
        case 1: /*--- REFCLCKl input ---*/
            refclk = ar7_lan_xtal;
            break;
        case 2: /*--- XTAL3IN input ---*/
            printk(KERN_ERR"[ar7_get_dsp_clock]not supported clock-input\n");
            break;
        case 3: /*--- MIPS-Pll output ---*/
            refclk =  ar7_get_mips_clock();
            break;                                              
    }
    clk = ar7_clk_get_pll_factor(avm_clock_id_dsp, ar7_clock, refclk);
    DBG_TRC("[ar7_get_dsp_clock]: clk_sel=%d clk=%d\n", clk_sel, clk);
    return clk;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int ar7_get_usb_clock(void) {
    struct _hw_boot *BOOT = (struct _hw_boot *)AR7_DEVICE_CONFIG_BASE;
    struct _hw_clock *ar7_clock = (struct _hw_clock *)AR7_CLOCK_BASE;
    unsigned int clk, refclk = 0;
    unsigned int clk_sel;

    clk_sel = BOOT->hw_boot_config.Bits.usbpllsel;  
    switch(clk_sel) {                                                            
        case 0: /*--- USB_SYSPLL_SELECT ---*/
            refclk = ar7_get_system_clock();
            break;                                              
        case 1: /*--- USB_REFCLKI_SELECT ---*/
            refclk = ar7_lan_xtal;                 
            break;
        case 2:/*--- USB_XTAL3IN_SELECT ---*/
            printk(KERN_ERR"[ar7_get_usb_clock]not supported clock-input\n");
            break;                                              
        case 3:/*--- USB_MIPSPLL_SELECT ---*/
            refclk =  ar7_get_mips_clock();
            break;                                              
    }
    clk = ar7_clk_get_pll_factor(avm_clock_id_usb, ar7_clock, refclk);
    DBG_TRC("[ar7_get_usb_clock]: clk_sel=%d clk = %u\n", clk_sel, clk);
    return clk;
}
            
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_get_clock(enum _avm_clock_id clock_id) {
    switch(clock_id) {
        case avm_clock_id_cpu:
            return ar7_get_mips_clock();
        case avm_clock_id_peripheral:
            return ar7_get_system_clock() / 2;
        case avm_clock_id_system:
            return ar7_get_system_clock();
        case avm_clock_id_vbus:
            return ar7_get_system_clock() / 2;
        case avm_clock_id_usb:
            return ar7_get_usb_clock();
        case avm_clock_id_dsp:
            return ar7_get_dsp_clock();
        default:
            break;
    }
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int ar7_set_clock(enum _avm_clock_id clock_id, unsigned int clk) {
    switch(clock_id) {
        case avm_clock_id_cpu:
            return ar7_set_mips_clock(clk);
        case avm_clock_id_system:
            return ar7_set_system_clock(clk);
        case avm_clock_id_usb:
            return ar7_set_usb_clock(clk);
        case avm_clock_id_dsp:
            return ar7_set_dsp_clock(clk);
        case avm_clock_id_vbus:
        default:
            break;
    }
    return 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_AR7_CLOCK_SWITCH)
unsigned int ar7_get_clock_notify(enum _avm_clock_id clock_id, 
                                   unsigned int (*handler)(enum _avm_clock_id, unsigned int new_clk)) {
    ar7_set_clock_notify(clock_id, handler, 0); /*--- deinstall ---*/
    ar7_set_clock_notify(clock_id, handler, 1); /*--- install ---*/
    return ar7_get_clock(clock_id);
}
EXPORT_SYMBOL(ar7_get_clock_notify);
#endif /*--- #if defined(CONFIG_AR7_CLOCK_SWITCH) ---*/

EXPORT_SYMBOL(ar7_get_clock);
EXPORT_SYMBOL(ar7_set_clock);

