2005-11-01 22:53:50 +03:00
/*
* Generic library functions for the microengines found on the Intel
* IXP2000 series of network processors .
*
* Copyright ( C ) 2004 , 2005 Lennert Buytenhek < buytenh @ wantstofly . org >
* Dedicated to Marija Kulikova .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation ; either version 2.1 of the
* License , or ( at your option ) any later version .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/string.h>
2008-08-05 19:14:15 +04:00
# include <mach/hardware.h>
2006-03-20 20:10:17 +03:00
# include <asm/hardware/uengine.h>
2005-11-01 22:53:50 +03:00
# include <asm/io.h>
2006-06-22 13:30:56 +04:00
# if defined(CONFIG_ARCH_IXP2000)
# define IXP_UENGINE_CSR_VIRT_BASE IXP2000_UENGINE_CSR_VIRT_BASE
# define IXP_PRODUCT_ID IXP2000_PRODUCT_ID
# define IXP_MISC_CONTROL IXP2000_MISC_CONTROL
# define IXP_RESET1 IXP2000_RESET1
# else
# if defined(CONFIG_ARCH_IXP23XX)
# define IXP_UENGINE_CSR_VIRT_BASE IXP23XX_UENGINE_CSR_VIRT_BASE
# define IXP_PRODUCT_ID IXP23XX_PRODUCT_ID
# define IXP_MISC_CONTROL IXP23XX_MISC_CONTROL
# define IXP_RESET1 IXP23XX_RESET1
# else
# error unknown platform
# endif
# endif
2005-11-01 22:53:50 +03:00
# define USTORE_ADDRESS 0x000
# define USTORE_DATA_LOWER 0x004
# define USTORE_DATA_UPPER 0x008
# define CTX_ENABLES 0x018
# define CC_ENABLE 0x01c
# define CSR_CTX_POINTER 0x020
# define INDIRECT_CTX_STS 0x040
# define ACTIVE_CTX_STS 0x044
# define INDIRECT_CTX_SIG_EVENTS 0x048
# define INDIRECT_CTX_WAKEUP_EVENTS 0x050
# define NN_PUT 0x080
# define NN_GET 0x084
# define TIMESTAMP_LOW 0x0c0
# define TIMESTAMP_HIGH 0x0c4
# define T_INDEX_BYTE_INDEX 0x0f4
# define LOCAL_CSR_STATUS 0x180
u32 ixp2000_uengine_mask ;
static void * ixp2000_uengine_csr_area ( int uengine )
{
2006-06-22 13:30:56 +04:00
return ( ( void * ) IXP_UENGINE_CSR_VIRT_BASE ) + ( uengine < < 10 ) ;
2005-11-01 22:53:50 +03:00
}
/*
* LOCAL_CSR_STATUS = 1 after a read or write to a microengine ' s CSR
* space means that the microengine we tried to access was also trying
* to access its own CSR space on the same clock cycle as we did . When
* this happens , we lose the arbitration process by default , and the
* read or write we tried to do was not actually performed , so we try
* again until it succeeds .
*/
u32 ixp2000_uengine_csr_read ( int uengine , int offset )
{
void * uebase ;
u32 * local_csr_status ;
u32 * reg ;
u32 value ;
uebase = ixp2000_uengine_csr_area ( uengine ) ;
local_csr_status = ( u32 * ) ( uebase + LOCAL_CSR_STATUS ) ;
reg = ( u32 * ) ( uebase + offset ) ;
do {
value = ixp2000_reg_read ( reg ) ;
} while ( ixp2000_reg_read ( local_csr_status ) & 1 ) ;
return value ;
}
EXPORT_SYMBOL ( ixp2000_uengine_csr_read ) ;
void ixp2000_uengine_csr_write ( int uengine , int offset , u32 value )
{
void * uebase ;
u32 * local_csr_status ;
u32 * reg ;
uebase = ixp2000_uengine_csr_area ( uengine ) ;
local_csr_status = ( u32 * ) ( uebase + LOCAL_CSR_STATUS ) ;
reg = ( u32 * ) ( uebase + offset ) ;
do {
ixp2000_reg_write ( reg , value ) ;
} while ( ixp2000_reg_read ( local_csr_status ) & 1 ) ;
}
EXPORT_SYMBOL ( ixp2000_uengine_csr_write ) ;
void ixp2000_uengine_reset ( u32 uengine_mask )
{
2006-06-22 13:30:56 +04:00
u32 value ;
value = ixp2000_reg_read ( IXP_RESET1 ) & ~ ixp2000_uengine_mask ;
uengine_mask & = ixp2000_uengine_mask ;
ixp2000_reg_wrb ( IXP_RESET1 , value | uengine_mask ) ;
ixp2000_reg_wrb ( IXP_RESET1 , value ) ;
2005-11-01 22:53:50 +03:00
}
EXPORT_SYMBOL ( ixp2000_uengine_reset ) ;
void ixp2000_uengine_set_mode ( int uengine , u32 mode )
{
/*
* CTL_STR_PAR_EN : unconditionally enable parity checking on
* control store .
*/
mode | = 0x10000000 ;
ixp2000_uengine_csr_write ( uengine , CTX_ENABLES , mode ) ;
/*
* Enable updating of condition codes .
*/
ixp2000_uengine_csr_write ( uengine , CC_ENABLE , 0x00002000 ) ;
/*
* Initialise other per - microengine registers .
*/
ixp2000_uengine_csr_write ( uengine , NN_PUT , 0x00 ) ;
ixp2000_uengine_csr_write ( uengine , NN_GET , 0x00 ) ;
ixp2000_uengine_csr_write ( uengine , T_INDEX_BYTE_INDEX , 0 ) ;
}
EXPORT_SYMBOL ( ixp2000_uengine_set_mode ) ;
static int make_even_parity ( u32 x )
{
return hweight32 ( x ) & 1 ;
}
static void ustore_write ( int uengine , u64 insn )
{
/*
* Generate even parity for top and bottom 20 bits .
*/
insn | = ( u64 ) make_even_parity ( ( insn > > 20 ) & 0x000fffff ) < < 41 ;
insn | = ( u64 ) make_even_parity ( insn & 0x000fffff ) < < 40 ;
/*
* Write to microstore . The second write auto - increments
* the USTORE_ADDRESS index register .
*/
ixp2000_uengine_csr_write ( uengine , USTORE_DATA_LOWER , ( u32 ) insn ) ;
ixp2000_uengine_csr_write ( uengine , USTORE_DATA_UPPER , ( u32 ) ( insn > > 32 ) ) ;
}
void ixp2000_uengine_load_microcode ( int uengine , u8 * ucode , int insns )
{
int i ;
/*
* Start writing to microstore at address 0.
*/
ixp2000_uengine_csr_write ( uengine , USTORE_ADDRESS , 0x80000000 ) ;
for ( i = 0 ; i < insns ; i + + ) {
u64 insn ;
insn = ( ( ( u64 ) ucode [ 0 ] ) < < 32 ) |
( ( ( u64 ) ucode [ 1 ] ) < < 24 ) |
( ( ( u64 ) ucode [ 2 ] ) < < 16 ) |
( ( ( u64 ) ucode [ 3 ] ) < < 8 ) |
( ( u64 ) ucode [ 4 ] ) ;
ucode + = 5 ;
ustore_write ( uengine , insn ) ;
}
/*
* Pad with a few NOPs at the end ( to avoid the microengine
* aborting as it prefetches beyond the last instruction ) , unless
* we run off the end of the instruction store first , at which
* point the address register will wrap back to zero .
*/
for ( i = 0 ; i < 4 ; i + + ) {
u32 addr ;
addr = ixp2000_uengine_csr_read ( uengine , USTORE_ADDRESS ) ;
if ( addr = = 0x80000000 )
break ;
ustore_write ( uengine , 0xf0000c0300ULL ) ;
}
/*
* End programming .
*/
ixp2000_uengine_csr_write ( uengine , USTORE_ADDRESS , 0x00000000 ) ;
}
EXPORT_SYMBOL ( ixp2000_uengine_load_microcode ) ;
void ixp2000_uengine_init_context ( int uengine , int context , int pc )
{
/*
* Select the right context for indirect access .
*/
ixp2000_uengine_csr_write ( uengine , CSR_CTX_POINTER , context ) ;
/*
* Initialise signal masks to immediately go to Ready state .
*/
ixp2000_uengine_csr_write ( uengine , INDIRECT_CTX_SIG_EVENTS , 1 ) ;
ixp2000_uengine_csr_write ( uengine , INDIRECT_CTX_WAKEUP_EVENTS , 1 ) ;
/*
* Set program counter .
*/
ixp2000_uengine_csr_write ( uengine , INDIRECT_CTX_STS , pc ) ;
}
EXPORT_SYMBOL ( ixp2000_uengine_init_context ) ;
void ixp2000_uengine_start_contexts ( int uengine , u8 ctx_mask )
{
u32 mask ;
/*
* Enable the specified context to go to Executing state .
*/
mask = ixp2000_uengine_csr_read ( uengine , CTX_ENABLES ) ;
mask | = ctx_mask < < 8 ;
ixp2000_uengine_csr_write ( uengine , CTX_ENABLES , mask ) ;
}
EXPORT_SYMBOL ( ixp2000_uengine_start_contexts ) ;
void ixp2000_uengine_stop_contexts ( int uengine , u8 ctx_mask )
{
u32 mask ;
/*
* Disable the Ready - > Executing transition . Note that this
* does not stop the context until it voluntarily yields .
*/
mask = ixp2000_uengine_csr_read ( uengine , CTX_ENABLES ) ;
mask & = ~ ( ctx_mask < < 8 ) ;
ixp2000_uengine_csr_write ( uengine , CTX_ENABLES , mask ) ;
}
EXPORT_SYMBOL ( ixp2000_uengine_stop_contexts ) ;
static int check_ixp_type ( struct ixp2000_uengine_code * c )
{
u32 product_id ;
u32 rev ;
2006-06-22 13:30:56 +04:00
product_id = ixp2000_reg_read ( IXP_PRODUCT_ID ) ;
2005-11-01 22:53:50 +03:00
if ( ( ( product_id > > 16 ) & 0x1f ) ! = 0 )
return 0 ;
switch ( ( product_id > > 8 ) & 0xff ) {
2006-06-22 13:30:56 +04:00
# ifdef CONFIG_ARCH_IXP2000
2005-11-01 22:53:50 +03:00
case 0 : /* IXP2800 */
if ( ! ( c - > cpu_model_bitmask & 4 ) )
return 0 ;
break ;
case 1 : /* IXP2850 */
if ( ! ( c - > cpu_model_bitmask & 8 ) )
return 0 ;
break ;
case 2 : /* IXP2400 */
if ( ! ( c - > cpu_model_bitmask & 2 ) )
return 0 ;
break ;
2006-06-22 13:30:56 +04:00
# endif
# ifdef CONFIG_ARCH_IXP23XX
case 4 : /* IXP23xx */
if ( ! ( c - > cpu_model_bitmask & 0x3f0 ) )
return 0 ;
break ;
# endif
2005-11-01 22:53:50 +03:00
default :
return 0 ;
}
rev = product_id & 0xff ;
if ( rev < c - > cpu_min_revision | | rev > c - > cpu_max_revision )
return 0 ;
return 1 ;
}
static void generate_ucode ( u8 * ucode , u32 * gpr_a , u32 * gpr_b )
{
int offset ;
int i ;
offset = 0 ;
for ( i = 0 ; i < 128 ; i + + ) {
u8 b3 ;
u8 b2 ;
u8 b1 ;
u8 b0 ;
b3 = ( gpr_a [ i ] > > 24 ) & 0xff ;
b2 = ( gpr_a [ i ] > > 16 ) & 0xff ;
b1 = ( gpr_a [ i ] > > 8 ) & 0xff ;
b0 = gpr_a [ i ] & 0xff ;
// immed[@ai, (b1 << 8) | b0]
// 11110000 0000VVVV VVVV11VV VVVVVV00 1IIIIIII
ucode [ offset + + ] = 0xf0 ;
ucode [ offset + + ] = ( b1 > > 4 ) ;
ucode [ offset + + ] = ( b1 < < 4 ) | 0x0c | ( b0 > > 6 ) ;
ucode [ offset + + ] = ( b0 < < 2 ) ;
ucode [ offset + + ] = 0x80 | i ;
// immed_w1[@ai, (b3 << 8) | b2]
// 11110100 0100VVVV VVVV11VV VVVVVV00 1IIIIIII
ucode [ offset + + ] = 0xf4 ;
ucode [ offset + + ] = 0x40 | ( b3 > > 4 ) ;
ucode [ offset + + ] = ( b3 < < 4 ) | 0x0c | ( b2 > > 6 ) ;
ucode [ offset + + ] = ( b2 < < 2 ) ;
ucode [ offset + + ] = 0x80 | i ;
}
for ( i = 0 ; i < 128 ; i + + ) {
u8 b3 ;
u8 b2 ;
u8 b1 ;
u8 b0 ;
b3 = ( gpr_b [ i ] > > 24 ) & 0xff ;
b2 = ( gpr_b [ i ] > > 16 ) & 0xff ;
b1 = ( gpr_b [ i ] > > 8 ) & 0xff ;
b0 = gpr_b [ i ] & 0xff ;
// immed[@bi, (b1 << 8) | b0]
// 11110000 0000VVVV VVVV001I IIIIII11 VVVVVVVV
ucode [ offset + + ] = 0xf0 ;
ucode [ offset + + ] = ( b1 > > 4 ) ;
ucode [ offset + + ] = ( b1 < < 4 ) | 0x02 | ( i > > 6 ) ;
ucode [ offset + + ] = ( i < < 2 ) | 0x03 ;
ucode [ offset + + ] = b0 ;
// immed_w1[@bi, (b3 << 8) | b2]
// 11110100 0100VVVV VVVV001I IIIIII11 VVVVVVVV
ucode [ offset + + ] = 0xf4 ;
ucode [ offset + + ] = 0x40 | ( b3 > > 4 ) ;
ucode [ offset + + ] = ( b3 < < 4 ) | 0x02 | ( i > > 6 ) ;
ucode [ offset + + ] = ( i < < 2 ) | 0x03 ;
ucode [ offset + + ] = b2 ;
}
// ctx_arb[kill]
ucode [ offset + + ] = 0xe0 ;
ucode [ offset + + ] = 0x00 ;
ucode [ offset + + ] = 0x01 ;
ucode [ offset + + ] = 0x00 ;
ucode [ offset + + ] = 0x00 ;
}
static int set_initial_registers ( int uengine , struct ixp2000_uengine_code * c )
{
int per_ctx_regs ;
u32 * gpr_a ;
u32 * gpr_b ;
u8 * ucode ;
int i ;
2007-11-22 01:51:50 +03:00
gpr_a = kzalloc ( 128 * sizeof ( u32 ) , GFP_KERNEL ) ;
gpr_b = kzalloc ( 128 * sizeof ( u32 ) , GFP_KERNEL ) ;
2005-11-01 22:53:50 +03:00
ucode = kmalloc ( 513 * 5 , GFP_KERNEL ) ;
if ( gpr_a = = NULL | | gpr_b = = NULL | | ucode = = NULL ) {
kfree ( ucode ) ;
kfree ( gpr_b ) ;
kfree ( gpr_a ) ;
return 1 ;
}
per_ctx_regs = 16 ;
if ( c - > uengine_parameters & IXP2000_UENGINE_4_CONTEXTS )
per_ctx_regs = 32 ;
for ( i = 0 ; i < 256 ; i + + ) {
struct ixp2000_reg_value * r = c - > initial_reg_values + i ;
u32 * bank ;
int inc ;
int j ;
if ( r - > reg = = - 1 )
break ;
bank = ( r - > reg & 0x400 ) ? gpr_b : gpr_a ;
inc = ( r - > reg & 0x80 ) ? 128 : per_ctx_regs ;
j = r - > reg & 0x7f ;
while ( j < 128 ) {
bank [ j ] = r - > value ;
j + = inc ;
}
}
generate_ucode ( ucode , gpr_a , gpr_b ) ;
ixp2000_uengine_load_microcode ( uengine , ucode , 513 ) ;
ixp2000_uengine_init_context ( uengine , 0 , 0 ) ;
ixp2000_uengine_start_contexts ( uengine , 0x01 ) ;
for ( i = 0 ; i < 100 ; i + + ) {
u32 status ;
status = ixp2000_uengine_csr_read ( uengine , ACTIVE_CTX_STS ) ;
if ( ! ( status & 0x80000000 ) )
break ;
}
ixp2000_uengine_stop_contexts ( uengine , 0x01 ) ;
kfree ( ucode ) ;
kfree ( gpr_b ) ;
kfree ( gpr_a ) ;
return ! ! ( i = = 100 ) ;
}
int ixp2000_uengine_load ( int uengine , struct ixp2000_uengine_code * c )
{
int ctx ;
if ( ! check_ixp_type ( c ) )
return 1 ;
if ( ! ( ixp2000_uengine_mask & ( 1 < < uengine ) ) )
return 1 ;
ixp2000_uengine_reset ( 1 < < uengine ) ;
ixp2000_uengine_set_mode ( uengine , c - > uengine_parameters ) ;
if ( set_initial_registers ( uengine , c ) )
return 1 ;
ixp2000_uengine_load_microcode ( uengine , c - > insns , c - > num_insns ) ;
for ( ctx = 0 ; ctx < 8 ; ctx + + )
ixp2000_uengine_init_context ( uengine , ctx , 0 ) ;
return 0 ;
}
EXPORT_SYMBOL ( ixp2000_uengine_load ) ;
static int __init ixp2000_uengine_init ( void )
{
int uengine ;
u32 value ;
/*
* Determine number of microengines present .
*/
2006-06-22 13:30:56 +04:00
switch ( ( ixp2000_reg_read ( IXP_PRODUCT_ID ) > > 8 ) & 0x1fff ) {
# ifdef CONFIG_ARCH_IXP2000
2005-11-01 22:53:50 +03:00
case 0 : /* IXP2800 */
case 1 : /* IXP2850 */
ixp2000_uengine_mask = 0x00ff00ff ;
break ;
case 2 : /* IXP2400 */
ixp2000_uengine_mask = 0x000f000f ;
break ;
2006-06-22 13:30:56 +04:00
# endif
# ifdef CONFIG_ARCH_IXP23XX
case 4 : /* IXP23xx */
ixp2000_uengine_mask = ( * IXP23XX_EXP_CFG_FUSE > > 8 ) & 0xf ;
break ;
# endif
2005-11-01 22:53:50 +03:00
default :
printk ( KERN_INFO " Detected unknown IXP2000 model (%.8x) \n " ,
2006-06-22 13:30:56 +04:00
( unsigned int ) ixp2000_reg_read ( IXP_PRODUCT_ID ) ) ;
2005-11-01 22:53:50 +03:00
ixp2000_uengine_mask = 0x00000000 ;
break ;
}
/*
* Reset microengines .
*/
2005-11-06 17:34:13 +03:00
ixp2000_uengine_reset ( ixp2000_uengine_mask ) ;
2005-11-01 22:53:50 +03:00
/*
* Synchronise timestamp counters across all microengines .
*/
2006-06-22 13:30:56 +04:00
value = ixp2000_reg_read ( IXP_MISC_CONTROL ) ;
ixp2000_reg_wrb ( IXP_MISC_CONTROL , value & ~ 0x80 ) ;
2005-11-01 22:53:50 +03:00
for ( uengine = 0 ; uengine < 32 ; uengine + + ) {
if ( ixp2000_uengine_mask & ( 1 < < uengine ) ) {
ixp2000_uengine_csr_write ( uengine , TIMESTAMP_LOW , 0 ) ;
ixp2000_uengine_csr_write ( uengine , TIMESTAMP_HIGH , 0 ) ;
}
}
2006-06-22 13:30:56 +04:00
ixp2000_reg_wrb ( IXP_MISC_CONTROL , value | 0x80 ) ;
2005-11-01 22:53:50 +03:00
return 0 ;
}
subsys_initcall ( ixp2000_uengine_init ) ;