238 lines
6.7 KiB
C
238 lines
6.7 KiB
C
|
/*
|
||
|
* SMP/VPE-safe functions to access "registers" (see note).
|
||
|
*
|
||
|
* NOTES:
|
||
|
* - These macros use ll/sc instructions, so it is your responsibility to
|
||
|
* ensure these are available on your platform before including this file.
|
||
|
* - The MIPS32 spec states that ll/sc results are undefined for uncached
|
||
|
* accesses. This means they can't be used on HW registers accessed
|
||
|
* through kseg1. Code which requires these macros for this purpose must
|
||
|
* front-end the registers with cached memory "registers" and have a single
|
||
|
* thread update the actual HW registers.
|
||
|
* - A maximum of 2k of code can be inserted between ll and sc. Every
|
||
|
* memory accesses between the instructions will increase the chance of
|
||
|
* sc failing and having to loop.
|
||
|
* - When using custom_read_reg32/custom_write_reg32 only perform the
|
||
|
* necessary logical operations on the register value in between these
|
||
|
* two calls. All other logic should be performed before the first call.
|
||
|
* - There is a bug on the R10000 chips which has a workaround. If you
|
||
|
* are affected by this bug, make sure to define the symbol 'R10000_LLSC_WAR'
|
||
|
* to be non-zero. If you are using this header from within linux, you may
|
||
|
* include <asm/war.h> before including this file to have this defined
|
||
|
* appropriately for you.
|
||
|
*
|
||
|
* Copyright 2005-2007 PMC-Sierra, Inc.
|
||
|
*
|
||
|
* 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* 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., 675
|
||
|
* Mass Ave, Cambridge, MA 02139, USA.
|
||
|
*/
|
||
|
|
||
|
#ifndef __ASM_REGOPS_H__
|
||
|
#define __ASM_REGOPS_H__
|
||
|
|
||
|
#include <linux/types.h>
|
||
|
|
||
|
#include <asm/compiler.h>
|
||
|
#include <asm/war.h>
|
||
|
|
||
|
#ifndef R10000_LLSC_WAR
|
||
|
#define R10000_LLSC_WAR 0
|
||
|
#endif
|
||
|
|
||
|
#if R10000_LLSC_WAR == 1
|
||
|
#define __beqz "beqzl "
|
||
|
#else
|
||
|
#define __beqz "beqz "
|
||
|
#endif
|
||
|
|
||
|
#ifndef _LINUX_TYPES_H
|
||
|
typedef unsigned int u32;
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Sets all the masked bits to the corresponding value bits
|
||
|
*/
|
||
|
static inline void set_value_reg32(volatile u32 *const addr,
|
||
|
u32 const mask,
|
||
|
u32 const value)
|
||
|
{
|
||
|
u32 temp;
|
||
|
|
||
|
__asm__ __volatile__(
|
||
|
" .set push \n"
|
||
|
" .set arch=r4000 \n"
|
||
|
"1: ll %0, %1 # set_value_reg32 \n"
|
||
|
" and %0, %2 \n"
|
||
|
" or %0, %3 \n"
|
||
|
" sc %0, %1 \n"
|
||
|
" "__beqz"%0, 1b \n"
|
||
|
" nop \n"
|
||
|
" .set pop \n"
|
||
|
: "=&r" (temp), "=" GCC_OFF_SMALL_ASM() (*addr)
|
||
|
: "ir" (~mask), "ir" (value), GCC_OFF_SMALL_ASM() (*addr));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Sets all the masked bits to '1'
|
||
|
*/
|
||
|
static inline void set_reg32(volatile u32 *const addr,
|
||
|
u32 const mask)
|
||
|
{
|
||
|
u32 temp;
|
||
|
|
||
|
__asm__ __volatile__(
|
||
|
" .set push \n"
|
||
|
" .set arch=r4000 \n"
|
||
|
"1: ll %0, %1 # set_reg32 \n"
|
||
|
" or %0, %2 \n"
|
||
|
" sc %0, %1 \n"
|
||
|
" "__beqz"%0, 1b \n"
|
||
|
" nop \n"
|
||
|
" .set pop \n"
|
||
|
: "=&r" (temp), "=" GCC_OFF_SMALL_ASM() (*addr)
|
||
|
: "ir" (mask), GCC_OFF_SMALL_ASM() (*addr));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Sets all the masked bits to '0'
|
||
|
*/
|
||
|
static inline void clear_reg32(volatile u32 *const addr,
|
||
|
u32 const mask)
|
||
|
{
|
||
|
u32 temp;
|
||
|
|
||
|
__asm__ __volatile__(
|
||
|
" .set push \n"
|
||
|
" .set arch=r4000 \n"
|
||
|
"1: ll %0, %1 # clear_reg32 \n"
|
||
|
" and %0, %2 \n"
|
||
|
" sc %0, %1 \n"
|
||
|
" "__beqz"%0, 1b \n"
|
||
|
" nop \n"
|
||
|
" .set pop \n"
|
||
|
: "=&r" (temp), "=" GCC_OFF_SMALL_ASM() (*addr)
|
||
|
: "ir" (~mask), GCC_OFF_SMALL_ASM() (*addr));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Toggles all masked bits from '0' to '1' and '1' to '0'
|
||
|
*/
|
||
|
static inline void toggle_reg32(volatile u32 *const addr,
|
||
|
u32 const mask)
|
||
|
{
|
||
|
u32 temp;
|
||
|
|
||
|
__asm__ __volatile__(
|
||
|
" .set push \n"
|
||
|
" .set arch=r4000 \n"
|
||
|
"1: ll %0, %1 # toggle_reg32 \n"
|
||
|
" xor %0, %2 \n"
|
||
|
" sc %0, %1 \n"
|
||
|
" "__beqz"%0, 1b \n"
|
||
|
" nop \n"
|
||
|
" .set pop \n"
|
||
|
: "=&r" (temp), "=" GCC_OFF_SMALL_ASM() (*addr)
|
||
|
: "ir" (mask), GCC_OFF_SMALL_ASM() (*addr));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read all masked bits others are returned as '0'
|
||
|
*/
|
||
|
static inline u32 read_reg32(volatile u32 *const addr,
|
||
|
u32 const mask)
|
||
|
{
|
||
|
u32 temp;
|
||
|
|
||
|
__asm__ __volatile__(
|
||
|
" .set push \n"
|
||
|
" .set noreorder \n"
|
||
|
" lw %0, %1 # read \n"
|
||
|
" and %0, %2 # mask \n"
|
||
|
" .set pop \n"
|
||
|
: "=&r" (temp)
|
||
|
: "m" (*addr), "ir" (mask));
|
||
|
|
||
|
return temp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* blocking_read_reg32 - Read address with blocking load
|
||
|
*
|
||
|
* Uncached writes need to be read back to ensure they reach RAM.
|
||
|
* The returned value must be 'used' to prevent from becoming a
|
||
|
* non-blocking load.
|
||
|
*/
|
||
|
static inline u32 blocking_read_reg32(volatile u32 *const addr)
|
||
|
{
|
||
|
u32 temp;
|
||
|
|
||
|
__asm__ __volatile__(
|
||
|
" .set push \n"
|
||
|
" .set noreorder \n"
|
||
|
" lw %0, %1 # read \n"
|
||
|
" move %0, %0 # block \n"
|
||
|
" .set pop \n"
|
||
|
: "=&r" (temp)
|
||
|
: "m" (*addr));
|
||
|
|
||
|
return temp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* For special strange cases only:
|
||
|
*
|
||
|
* If you need custom processing within a ll/sc loop, use the following macros
|
||
|
* VERY CAREFULLY:
|
||
|
*
|
||
|
* u32 tmp; <-- Define a variable to hold the data
|
||
|
*
|
||
|
* custom_read_reg32(address, tmp); <-- Reads the address and put the value
|
||
|
* in the 'tmp' variable given
|
||
|
*
|
||
|
* From here on out, you are (basically) atomic, so don't do anything too
|
||
|
* fancy!
|
||
|
* Also, this code may loop if the end of this block fails to write
|
||
|
* everything back safely due do the other CPU, so do NOT do anything
|
||
|
* with side-effects!
|
||
|
*
|
||
|
* custom_write_reg32(address, tmp); <-- Writes back 'tmp' safely.
|
||
|
*/
|
||
|
#define custom_read_reg32(address, tmp) \
|
||
|
__asm__ __volatile__( \
|
||
|
" .set push \n" \
|
||
|
" .set arch=r4000 \n" \
|
||
|
"1: ll %0, %1 #custom_read_reg32 \n" \
|
||
|
" .set pop \n" \
|
||
|
: "=r" (tmp), "=" GCC_OFF_SMALL_ASM() (*address) \
|
||
|
: GCC_OFF_SMALL_ASM() (*address))
|
||
|
|
||
|
#define custom_write_reg32(address, tmp) \
|
||
|
__asm__ __volatile__( \
|
||
|
" .set push \n" \
|
||
|
" .set arch=r4000 \n" \
|
||
|
" sc %0, %1 #custom_write_reg32 \n" \
|
||
|
" "__beqz"%0, 1b \n" \
|
||
|
" nop \n" \
|
||
|
" .set pop \n" \
|
||
|
: "=&r" (tmp), "=" GCC_OFF_SMALL_ASM() (*address) \
|
||
|
: "0" (tmp), GCC_OFF_SMALL_ASM() (*address))
|
||
|
|
||
|
#endif /* __ASM_REGOPS_H__ */
|