2009-06-24 13:12:57 +04:00
/*
* Copyright ( C ) 2007 Felix Fietkau < nbd @ openwrt . org >
* Copyright ( C ) 2007 Eugene Konev < ejka @ openwrt . org >
2010-01-27 11:10:06 +03:00
* Copyright ( C ) 2009 Florian Fainelli < florian @ openwrt . org >
2009-06-24 13:12:57 +04:00
*
* 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 . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/gcd.h>
# include <linux/io.h>
2010-01-27 11:10:06 +03:00
# include <linux/err.h>
# include <linux/clk.h>
2009-06-24 13:12:57 +04:00
# include <asm/addrspace.h>
# include <asm/mach-ar7/ar7.h>
# define BOOT_PLL_SOURCE_MASK 0x3
# define CPU_PLL_SOURCE_SHIFT 16
# define BUS_PLL_SOURCE_SHIFT 14
# define USB_PLL_SOURCE_SHIFT 18
# define DSP_PLL_SOURCE_SHIFT 22
# define BOOT_PLL_SOURCE_AFE 0
# define BOOT_PLL_SOURCE_BUS 0
# define BOOT_PLL_SOURCE_REF 1
# define BOOT_PLL_SOURCE_XTAL 2
# define BOOT_PLL_SOURCE_CPU 3
# define BOOT_PLL_BYPASS 0x00000020
# define BOOT_PLL_ASYNC_MODE 0x02000000
# define BOOT_PLL_2TO1_MODE 0x00008000
# define TNETD7200_CLOCK_ID_CPU 0
# define TNETD7200_CLOCK_ID_DSP 1
# define TNETD7200_CLOCK_ID_USB 2
# define TNETD7200_DEF_CPU_CLK 211000000
# define TNETD7200_DEF_DSP_CLK 125000000
# define TNETD7200_DEF_USB_CLK 48000000
struct tnetd7300_clock {
u32 ctrl ;
# define PREDIV_MASK 0x001f0000
# define PREDIV_SHIFT 16
# define POSTDIV_MASK 0x0000001f
u32 unused1 [ 3 ] ;
u32 pll ;
# define MUL_MASK 0x0000f000
# define MUL_SHIFT 12
# define PLL_MODE_MASK 0x00000001
# define PLL_NDIV 0x00000800
# define PLL_DIV 0x00000002
# define PLL_STATUS 0x00000001
u32 unused2 [ 3 ] ;
} ;
struct tnetd7300_clocks {
struct tnetd7300_clock bus ;
struct tnetd7300_clock cpu ;
struct tnetd7300_clock usb ;
struct tnetd7300_clock dsp ;
} ;
struct tnetd7200_clock {
u32 ctrl ;
u32 unused1 [ 3 ] ;
# define DIVISOR_ENABLE_MASK 0x00008000
u32 mul ;
u32 prediv ;
u32 postdiv ;
u32 postdiv2 ;
u32 unused2 [ 6 ] ;
u32 cmd ;
u32 status ;
u32 cmden ;
u32 padding [ 15 ] ;
} ;
struct tnetd7200_clocks {
struct tnetd7200_clock cpu ;
struct tnetd7200_clock dsp ;
struct tnetd7200_clock usb ;
} ;
2010-01-27 11:10:06 +03:00
static struct clk bus_clk = {
. rate = 125000000 ,
} ;
static struct clk cpu_clk = {
. rate = 150000000 ,
} ;
static struct clk dsp_clk ;
static struct clk vbus_clk ;
2009-06-24 13:12:57 +04:00
static void approximate ( int base , int target , int * prediv ,
int * postdiv , int * mul )
{
int i , j , k , freq , res = target ;
for ( i = 1 ; i < = 16 ; i + + )
for ( j = 1 ; j < = 32 ; j + + )
for ( k = 1 ; k < = 32 ; k + + ) {
freq = abs ( base / j * i / k - target ) ;
if ( freq < res ) {
res = freq ;
* mul = i ;
* prediv = j ;
* postdiv = k ;
}
}
}
static void calculate ( int base , int target , int * prediv , int * postdiv ,
int * mul )
{
int tmp_gcd , tmp_base , tmp_freq ;
for ( * prediv = 1 ; * prediv < = 32 ; ( * prediv ) + + ) {
tmp_base = base / * prediv ;
tmp_gcd = gcd ( target , tmp_base ) ;
* mul = target / tmp_gcd ;
* postdiv = tmp_base / tmp_gcd ;
if ( ( * mul < 1 ) | | ( * mul > = 16 ) )
continue ;
if ( ( * postdiv > 0 ) & ( * postdiv < = 32 ) )
break ;
}
if ( base / * prediv * * mul / * postdiv ! = target ) {
approximate ( base , target , prediv , postdiv , mul ) ;
tmp_freq = base / * prediv * * mul / * postdiv ;
printk ( KERN_WARNING
" Adjusted requested frequency %d to %d \n " ,
target , tmp_freq ) ;
}
printk ( KERN_DEBUG " Clocks: prediv: %d, postdiv: %d, mul: %d \n " ,
* prediv , * postdiv , * mul ) ;
}
static int tnetd7300_dsp_clock ( void )
{
u32 didr1 , didr2 ;
u8 rev = ar7_chip_rev ( ) ;
didr1 = readl ( ( void * ) KSEG1ADDR ( AR7_REGS_GPIO + 0x18 ) ) ;
didr2 = readl ( ( void * ) KSEG1ADDR ( AR7_REGS_GPIO + 0x1c ) ) ;
if ( didr2 & ( 1 < < 23 ) )
return 0 ;
if ( ( rev > = 0x23 ) & & ( rev ! = 0x57 ) )
return 250000000 ;
if ( ( ( ( didr2 & 0x1fff ) < < 10 ) | ( ( didr1 & 0xffc00000 ) > > 22 ) )
> 4208000 )
return 250000000 ;
return 0 ;
}
static int tnetd7300_get_clock ( u32 shift , struct tnetd7300_clock * clock ,
u32 * bootcr , u32 bus_clock )
{
int product ;
int base_clock = AR7_REF_CLOCK ;
u32 ctrl = readl ( & clock - > ctrl ) ;
u32 pll = readl ( & clock - > pll ) ;
int prediv = ( ( ctrl & PREDIV_MASK ) > > PREDIV_SHIFT ) + 1 ;
int postdiv = ( ctrl & POSTDIV_MASK ) + 1 ;
int divisor = prediv * postdiv ;
int mul = ( ( pll & MUL_MASK ) > > MUL_SHIFT ) + 1 ;
switch ( ( * bootcr & ( BOOT_PLL_SOURCE_MASK < < shift ) ) > > shift ) {
case BOOT_PLL_SOURCE_BUS :
base_clock = bus_clock ;
break ;
case BOOT_PLL_SOURCE_REF :
base_clock = AR7_REF_CLOCK ;
break ;
case BOOT_PLL_SOURCE_XTAL :
base_clock = AR7_XTAL_CLOCK ;
break ;
case BOOT_PLL_SOURCE_CPU :
2010-01-27 11:10:06 +03:00
base_clock = cpu_clk . rate ;
2009-06-24 13:12:57 +04:00
break ;
}
if ( * bootcr & BOOT_PLL_BYPASS )
return base_clock / divisor ;
if ( ( pll & PLL_MODE_MASK ) = = 0 )
return ( base_clock > > ( mul / 16 + 1 ) ) / divisor ;
if ( ( pll & ( PLL_NDIV | PLL_DIV ) ) = = ( PLL_NDIV | PLL_DIV ) ) {
product = ( mul & 1 ) ?
( base_clock * mul ) > > 1 :
( base_clock * ( mul - 1 ) ) > > 2 ;
return product / divisor ;
}
if ( mul = = 16 )
return base_clock / divisor ;
return base_clock * mul / divisor ;
}
static void tnetd7300_set_clock ( u32 shift , struct tnetd7300_clock * clock ,
u32 * bootcr , u32 frequency )
{
int prediv , postdiv , mul ;
2010-01-27 11:10:06 +03:00
int base_clock = bus_clk . rate ;
2009-06-24 13:12:57 +04:00
switch ( ( * bootcr & ( BOOT_PLL_SOURCE_MASK < < shift ) ) > > shift ) {
case BOOT_PLL_SOURCE_BUS :
2010-01-27 11:10:06 +03:00
base_clock = bus_clk . rate ;
2009-06-24 13:12:57 +04:00
break ;
case BOOT_PLL_SOURCE_REF :
base_clock = AR7_REF_CLOCK ;
break ;
case BOOT_PLL_SOURCE_XTAL :
base_clock = AR7_XTAL_CLOCK ;
break ;
case BOOT_PLL_SOURCE_CPU :
2010-01-27 11:10:06 +03:00
base_clock = cpu_clk . rate ;
2009-06-24 13:12:57 +04:00
break ;
}
calculate ( base_clock , frequency , & prediv , & postdiv , & mul ) ;
writel ( ( ( prediv - 1 ) < < PREDIV_SHIFT ) | ( postdiv - 1 ) , & clock - > ctrl ) ;
2010-11-01 01:49:58 +03:00
mdelay ( 1 ) ;
2009-06-24 13:12:57 +04:00
writel ( 4 , & clock - > pll ) ;
while ( readl ( & clock - > pll ) & PLL_STATUS )
;
writel ( ( ( mul - 1 ) < < MUL_SHIFT ) | ( 0xff < < 3 ) | 0x0e , & clock - > pll ) ;
2010-11-01 01:49:58 +03:00
mdelay ( 75 ) ;
2009-06-24 13:12:57 +04:00
}
static void __init tnetd7300_init_clocks ( void )
{
u32 * bootcr = ( u32 * ) ioremap_nocache ( AR7_REGS_DCL , 4 ) ;
struct tnetd7300_clocks * clocks =
ioremap_nocache ( UR8_REGS_CLOCKS ,
sizeof ( struct tnetd7300_clocks ) ) ;
2010-01-27 11:10:06 +03:00
bus_clk . rate = tnetd7300_get_clock ( BUS_PLL_SOURCE_SHIFT ,
2009-06-24 13:12:57 +04:00
& clocks - > bus , bootcr , AR7_AFE_CLOCK ) ;
if ( * bootcr & BOOT_PLL_ASYNC_MODE )
2010-01-27 11:10:06 +03:00
cpu_clk . rate = tnetd7300_get_clock ( CPU_PLL_SOURCE_SHIFT ,
2009-06-24 13:12:57 +04:00
& clocks - > cpu , bootcr , AR7_AFE_CLOCK ) ;
else
2010-01-27 11:10:06 +03:00
cpu_clk . rate = bus_clk . rate ;
2009-06-24 13:12:57 +04:00
2010-01-27 11:10:06 +03:00
if ( dsp_clk . rate = = 250000000 )
2009-06-24 13:12:57 +04:00
tnetd7300_set_clock ( DSP_PLL_SOURCE_SHIFT , & clocks - > dsp ,
2010-01-27 11:10:06 +03:00
bootcr , dsp_clk . rate ) ;
2009-06-24 13:12:57 +04:00
iounmap ( clocks ) ;
iounmap ( bootcr ) ;
}
static void tnetd7200_set_clock ( int base , struct tnetd7200_clock * clock ,
int prediv , int postdiv , int postdiv2 , int mul , u32 frequency )
{
printk ( KERN_INFO
" Clocks: base = %d, frequency = %u, prediv = %d, "
" postdiv = %d, postdiv2 = %d, mul = %d \n " ,
base , frequency , prediv , postdiv , postdiv2 , mul ) ;
writel ( 0 , & clock - > ctrl ) ;
writel ( DIVISOR_ENABLE_MASK | ( ( prediv - 1 ) & 0x1F ) , & clock - > prediv ) ;
writel ( ( mul - 1 ) & 0xF , & clock - > mul ) ;
while ( readl ( & clock - > status ) & 0x1 )
; /* nop */
writel ( DIVISOR_ENABLE_MASK | ( ( postdiv - 1 ) & 0x1F ) , & clock - > postdiv ) ;
writel ( readl ( & clock - > cmden ) | 1 , & clock - > cmden ) ;
writel ( readl ( & clock - > cmd ) | 1 , & clock - > cmd ) ;
while ( readl ( & clock - > status ) & 0x1 )
; /* nop */
writel ( DIVISOR_ENABLE_MASK | ( ( postdiv2 - 1 ) & 0x1F ) , & clock - > postdiv2 ) ;
writel ( readl ( & clock - > cmden ) | 1 , & clock - > cmden ) ;
writel ( readl ( & clock - > cmd ) | 1 , & clock - > cmd ) ;
while ( readl ( & clock - > status ) & 0x1 )
; /* nop */
writel ( readl ( & clock - > ctrl ) | 1 , & clock - > ctrl ) ;
}
static int tnetd7200_get_clock_base ( int clock_id , u32 * bootcr )
{
if ( * bootcr & BOOT_PLL_ASYNC_MODE )
/* Async */
switch ( clock_id ) {
case TNETD7200_CLOCK_ID_DSP :
return AR7_REF_CLOCK ;
default :
return AR7_AFE_CLOCK ;
}
else
/* Sync */
if ( * bootcr & BOOT_PLL_2TO1_MODE )
/* 2:1 */
switch ( clock_id ) {
case TNETD7200_CLOCK_ID_DSP :
return AR7_REF_CLOCK ;
default :
return AR7_AFE_CLOCK ;
}
else
/* 1:1 */
return AR7_REF_CLOCK ;
}
static void __init tnetd7200_init_clocks ( void )
{
u32 * bootcr = ( u32 * ) ioremap_nocache ( AR7_REGS_DCL , 4 ) ;
struct tnetd7200_clocks * clocks =
ioremap_nocache ( AR7_REGS_CLOCKS ,
sizeof ( struct tnetd7200_clocks ) ) ;
int cpu_base , cpu_mul , cpu_prediv , cpu_postdiv ;
int dsp_base , dsp_mul , dsp_prediv , dsp_postdiv ;
int usb_base , usb_mul , usb_prediv , usb_postdiv ;
cpu_base = tnetd7200_get_clock_base ( TNETD7200_CLOCK_ID_CPU , bootcr ) ;
dsp_base = tnetd7200_get_clock_base ( TNETD7200_CLOCK_ID_DSP , bootcr ) ;
if ( * bootcr & BOOT_PLL_ASYNC_MODE ) {
printk ( KERN_INFO " Clocks: Async mode \n " ) ;
printk ( KERN_INFO " Clocks: Setting DSP clock \n " ) ;
calculate ( dsp_base , TNETD7200_DEF_DSP_CLK ,
& dsp_prediv , & dsp_postdiv , & dsp_mul ) ;
2010-01-27 11:10:06 +03:00
bus_clk . rate =
2009-06-24 13:12:57 +04:00
( ( dsp_base / dsp_prediv ) * dsp_mul ) / dsp_postdiv ;
tnetd7200_set_clock ( dsp_base , & clocks - > dsp ,
dsp_prediv , dsp_postdiv * 2 , dsp_postdiv , dsp_mul * 2 ,
2010-01-27 11:10:06 +03:00
bus_clk . rate ) ;
2009-06-24 13:12:57 +04:00
printk ( KERN_INFO " Clocks: Setting CPU clock \n " ) ;
calculate ( cpu_base , TNETD7200_DEF_CPU_CLK , & cpu_prediv ,
& cpu_postdiv , & cpu_mul ) ;
2010-01-27 11:10:06 +03:00
cpu_clk . rate =
2009-06-24 13:12:57 +04:00
( ( cpu_base / cpu_prediv ) * cpu_mul ) / cpu_postdiv ;
tnetd7200_set_clock ( cpu_base , & clocks - > cpu ,
cpu_prediv , cpu_postdiv , - 1 , cpu_mul ,
2010-01-27 11:10:06 +03:00
cpu_clk . rate ) ;
2009-06-24 13:12:57 +04:00
} else
if ( * bootcr & BOOT_PLL_2TO1_MODE ) {
printk ( KERN_INFO " Clocks: Sync 2:1 mode \n " ) ;
printk ( KERN_INFO " Clocks: Setting CPU clock \n " ) ;
calculate ( cpu_base , TNETD7200_DEF_CPU_CLK , & cpu_prediv ,
& cpu_postdiv , & cpu_mul ) ;
2010-01-27 11:10:06 +03:00
cpu_clk . rate = ( ( cpu_base / cpu_prediv ) * cpu_mul )
2009-06-24 13:12:57 +04:00
/ cpu_postdiv ;
tnetd7200_set_clock ( cpu_base , & clocks - > cpu ,
cpu_prediv , cpu_postdiv , - 1 , cpu_mul ,
2010-01-27 11:10:06 +03:00
cpu_clk . rate ) ;
2009-06-24 13:12:57 +04:00
printk ( KERN_INFO " Clocks: Setting DSP clock \n " ) ;
calculate ( dsp_base , TNETD7200_DEF_DSP_CLK , & dsp_prediv ,
& dsp_postdiv , & dsp_mul ) ;
2010-01-27 11:10:06 +03:00
bus_clk . rate = cpu_clk . rate / 2 ;
2009-06-24 13:12:57 +04:00
tnetd7200_set_clock ( dsp_base , & clocks - > dsp ,
dsp_prediv , dsp_postdiv * 2 , dsp_postdiv ,
2010-01-27 11:10:06 +03:00
dsp_mul * 2 , bus_clk . rate ) ;
2009-06-24 13:12:57 +04:00
} else {
printk ( KERN_INFO " Clocks: Sync 1:1 mode \n " ) ;
printk ( KERN_INFO " Clocks: Setting DSP clock \n " ) ;
calculate ( dsp_base , TNETD7200_DEF_DSP_CLK , & dsp_prediv ,
& dsp_postdiv , & dsp_mul ) ;
2010-01-27 11:10:06 +03:00
bus_clk . rate = ( ( dsp_base / dsp_prediv ) * dsp_mul )
2009-06-24 13:12:57 +04:00
/ dsp_postdiv ;
tnetd7200_set_clock ( dsp_base , & clocks - > dsp ,
dsp_prediv , dsp_postdiv * 2 , dsp_postdiv ,
2010-01-27 11:10:06 +03:00
dsp_mul * 2 , bus_clk . rate ) ;
2009-06-24 13:12:57 +04:00
2010-01-27 11:10:06 +03:00
cpu_clk . rate = bus_clk . rate ;
2009-06-24 13:12:57 +04:00
}
printk ( KERN_INFO " Clocks: Setting USB clock \n " ) ;
2010-01-27 11:10:06 +03:00
usb_base = bus_clk . rate ;
2009-06-24 13:12:57 +04:00
calculate ( usb_base , TNETD7200_DEF_USB_CLK , & usb_prediv ,
& usb_postdiv , & usb_mul ) ;
tnetd7200_set_clock ( usb_base , & clocks - > usb ,
usb_prediv , usb_postdiv , - 1 , usb_mul ,
TNETD7200_DEF_USB_CLK ) ;
2010-01-27 11:10:06 +03:00
dsp_clk . rate = cpu_clk . rate ;
2009-06-24 13:12:57 +04:00
iounmap ( clocks ) ;
iounmap ( bootcr ) ;
}
2010-01-27 11:10:06 +03:00
/*
* Linux clock API
*/
int clk_enable ( struct clk * clk )
{
return 0 ;
}
EXPORT_SYMBOL ( clk_enable ) ;
void clk_disable ( struct clk * clk )
{
}
EXPORT_SYMBOL ( clk_disable ) ;
unsigned long clk_get_rate ( struct clk * clk )
{
return clk - > rate ;
}
EXPORT_SYMBOL ( clk_get_rate ) ;
struct clk * clk_get ( struct device * dev , const char * id )
{
if ( ! strcmp ( id , " bus " ) )
return & bus_clk ;
/* cpmac and vbus share the same rate */
if ( ! strcmp ( id , " cpmac " ) )
return & vbus_clk ;
if ( ! strcmp ( id , " cpu " ) )
return & cpu_clk ;
2011-06-12 22:57:17 +04:00
if ( ! strcmp ( id , " dsp " ) )
2010-01-27 11:10:06 +03:00
return & dsp_clk ;
if ( ! strcmp ( id , " vbus " ) )
return & vbus_clk ;
return ERR_PTR ( - ENOENT ) ;
}
EXPORT_SYMBOL ( clk_get ) ;
void clk_put ( struct clk * clk )
{
}
EXPORT_SYMBOL ( clk_put ) ;
2010-11-01 01:49:58 +03:00
void __init ar7_init_clocks ( void )
2009-06-24 13:12:57 +04:00
{
switch ( ar7_chip_id ( ) ) {
case AR7_CHIP_7100 :
case AR7_CHIP_7200 :
tnetd7200_init_clocks ( ) ;
break ;
case AR7_CHIP_7300 :
2010-01-27 11:10:06 +03:00
dsp_clk . rate = tnetd7300_dsp_clock ( ) ;
2009-06-24 13:12:57 +04:00
tnetd7300_init_clocks ( ) ;
break ;
default :
break ;
}
2010-01-27 11:10:06 +03:00
/* adjust vbus clock rate */
vbus_clk . rate = bus_clk . rate / 2 ;
2009-06-24 13:12:57 +04:00
}