| /* head.S: kernel entry point for FR-V kernel | |
| * | |
| * Copyright (C) 2003, 2004 Red Hat, Inc. All Rights Reserved. | |
| * Written by David Howells (dhowells@redhat.com) | |
| * | |
| * 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. | |
| */ | |
| #include <linux/threads.h> | |
| #include <linux/linkage.h> | |
| #include <asm/thread_info.h> | |
| #include <asm/ptrace.h> | |
| #include <asm/page.h> | |
| #include <asm/spr-regs.h> | |
| #include <asm/mb86943a.h> | |
| #include <asm/cache.h> | |
| #include "head.inc" | |
| ############################################################################### | |
| # | |
| # void _boot(unsigned long magic, char *command_line) __attribute__((noreturn)) | |
| # | |
| # - if magic is 0xdead1eaf, then command_line is assumed to point to the kernel | |
| # command line string | |
| # | |
| ############################################################################### | |
| .section .text.head,"ax" | |
| .balign 4 | |
| .globl _boot, __head_reference | |
| .type _boot,@function | |
| _boot: | |
| __head_reference: | |
| sethi.p %hi(LED_ADDR),gr30 | |
| setlo %lo(LED_ADDR),gr30 | |
| LEDS 0x0000 | |
| # calculate reference address for PC-relative stuff | |
| call 0f | |
| 0: movsg lr,gr26 | |
| addi gr26,#__head_reference-0b,gr26 | |
| # invalidate and disable both of the caches and turn off the memory access checking | |
| dcef @(gr0,gr0),1 | |
| bar | |
| sethi.p %hi(~(HSR0_ICE|HSR0_DCE|HSR0_CBM|HSR0_EIMMU|HSR0_EDMMU)),gr4 | |
| setlo %lo(~(HSR0_ICE|HSR0_DCE|HSR0_CBM|HSR0_EIMMU|HSR0_EDMMU)),gr4 | |
| movsg hsr0,gr5 | |
| and gr4,gr5,gr5 | |
| movgs gr5,hsr0 | |
| movsg hsr0,gr5 | |
| LEDS 0x0001 | |
| icei @(gr0,gr0),1 | |
| dcei @(gr0,gr0),1 | |
| bar | |
| # turn the instruction cache back on | |
| sethi.p %hi(HSR0_ICE),gr4 | |
| setlo %lo(HSR0_ICE),gr4 | |
| movsg hsr0,gr5 | |
| or gr4,gr5,gr5 | |
| movgs gr5,hsr0 | |
| movsg hsr0,gr5 | |
| bar | |
| LEDS 0x0002 | |
| # retrieve the parameters (including command line) before we overwrite them | |
| sethi.p %hi(0xdead1eaf),gr7 | |
| setlo %lo(0xdead1eaf),gr7 | |
| subcc gr7,gr8,gr0,icc0 | |
| bne icc0,#0,__head_no_parameters | |
| sethi.p %hi(redboot_command_line-1),gr6 | |
| setlo %lo(redboot_command_line-1),gr6 | |
| sethi.p %hi(__head_reference),gr4 | |
| setlo %lo(__head_reference),gr4 | |
| sub gr6,gr4,gr6 | |
| add.p gr6,gr26,gr6 | |
| subi gr9,#1,gr9 | |
| setlos.p #511,gr4 | |
| setlos #1,gr5 | |
| __head_copy_cmdline: | |
| ldubu.p @(gr9,gr5),gr16 | |
| subicc gr4,#1,gr4,icc0 | |
| stbu.p gr16,@(gr6,gr5) | |
| subicc gr16,#0,gr0,icc1 | |
| bls icc0,#0,__head_end_cmdline | |
| bne icc1,#1,__head_copy_cmdline | |
| __head_end_cmdline: | |
| stbu gr0,@(gr6,gr5) | |
| __head_no_parameters: | |
| ############################################################################### | |
| # | |
| # we need to relocate the SDRAM to 0x00000000 (linux) or 0xC0000000 (uClinux) | |
| # - note that we're going to have to run entirely out of the icache whilst | |
| # fiddling with the SDRAM controller registers | |
| # | |
| ############################################################################### | |
| #ifdef CONFIG_MMU | |
| call __head_fr451_describe_sdram | |
| #else | |
| movsg psr,gr5 | |
| srli gr5,#28,gr5 | |
| subicc gr5,#3,gr0,icc0 | |
| beq icc0,#0,__head_fr551_sdram | |
| call __head_fr401_describe_sdram | |
| bra __head_do_sdram | |
| __head_fr551_sdram: | |
| call __head_fr555_describe_sdram | |
| LEDS 0x000d | |
| __head_do_sdram: | |
| #endif | |
| # preload the registers with invalid values in case any DBR/DARS are marked not present | |
| sethi.p %hi(0xfe000000),gr17 ; unused SDRAM DBR value | |
| setlo %lo(0xfe000000),gr17 | |
| or.p gr17,gr0,gr20 | |
| or gr17,gr0,gr21 | |
| or.p gr17,gr0,gr22 | |
| or gr17,gr0,gr23 | |
| # consult the SDRAM controller CS address registers | |
| cld @(gr14,gr0 ),gr20, cc0,#1 ; DBR0 / DARS0 | |
| cld @(gr14,gr11),gr21, cc1,#1 ; DBR1 / DARS1 | |
| cld @(gr14,gr12),gr22, cc2,#1 ; DBR2 / DARS2 | |
| cld.p @(gr14,gr13),gr23, cc3,#1 ; DBR3 / DARS3 | |
| sll gr20,gr15,gr20 ; shift values up for FR551 | |
| sll gr21,gr15,gr21 | |
| sll gr22,gr15,gr22 | |
| sll gr23,gr15,gr23 | |
| LEDS 0x0003 | |
| # assume the lowest valid CS line to be the SDRAM base and get its address | |
| subcc gr20,gr17,gr0,icc0 | |
| subcc.p gr21,gr17,gr0,icc1 | |
| subcc gr22,gr17,gr0,icc2 | |
| subcc.p gr23,gr17,gr0,icc3 | |
| ckne icc0,cc4 ; T if DBR0 != 0xfe000000 | |
| ckne icc1,cc5 | |
| ckne icc2,cc6 | |
| ckne icc3,cc7 | |
| cor gr23,gr0,gr24, cc7,#1 ; GR24 = SDRAM base | |
| cor gr22,gr0,gr24, cc6,#1 | |
| cor gr21,gr0,gr24, cc5,#1 | |
| cor gr20,gr0,gr24, cc4,#1 | |
| # calculate the displacement required to get the SDRAM into the right place in memory | |
| sethi.p %hi(__sdram_base),gr16 | |
| setlo %lo(__sdram_base),gr16 | |
| sub gr16,gr24,gr16 ; delta = __sdram_base - DBRx | |
| # calculate the new values to go in the controller regs | |
| cadd.p gr20,gr16,gr20, cc4,#1 ; DCS#0 (new) = DCS#0 (old) + delta | |
| cadd gr21,gr16,gr21, cc5,#1 | |
| cadd.p gr22,gr16,gr22, cc6,#1 | |
| cadd gr23,gr16,gr23, cc7,#1 | |
| srl gr20,gr15,gr20 ; shift values down for FR551 | |
| srl gr21,gr15,gr21 | |
| srl gr22,gr15,gr22 | |
| srl gr23,gr15,gr23 | |
| # work out the address at which the reg updater resides and lock it into icache | |
| # also work out the address the updater will jump to when finished | |
| sethi.p %hi(__head_move_sdram-__head_reference),gr18 | |
| setlo %lo(__head_move_sdram-__head_reference),gr18 | |
| sethi.p %hi(__head_sdram_moved-__head_reference),gr19 | |
| setlo %lo(__head_sdram_moved-__head_reference),gr19 | |
| add.p gr18,gr26,gr18 | |
| add gr19,gr26,gr19 | |
| add.p gr19,gr16,gr19 ; moved = addr + (__sdram_base - DBRx) | |
| add gr18,gr5,gr4 ; two cachelines probably required | |
| icpl gr18,gr0,#1 ; load and lock the cachelines | |
| icpl gr4,gr0,#1 | |
| LEDS 0x0004 | |
| membar | |
| bar | |
| jmpl @(gr18,gr0) | |
| .balign L1_CACHE_BYTES | |
| __head_move_sdram: | |
| cst gr20,@(gr14,gr0 ), cc4,#1 | |
| cst gr21,@(gr14,gr11), cc5,#1 | |
| cst gr22,@(gr14,gr12), cc6,#1 | |
| cst gr23,@(gr14,gr13), cc7,#1 | |
| cld @(gr14,gr0 ),gr20, cc4,#1 | |
| cld @(gr14,gr11),gr21, cc5,#1 | |
| cld @(gr14,gr12),gr22, cc4,#1 | |
| cld @(gr14,gr13),gr23, cc7,#1 | |
| bar | |
| membar | |
| jmpl @(gr19,gr0) | |
| .balign L1_CACHE_BYTES | |
| __head_sdram_moved: | |
| icul gr18 | |
| add gr18,gr5,gr4 | |
| icul gr4 | |
| icei @(gr0,gr0),1 | |
| dcei @(gr0,gr0),1 | |
| LEDS 0x0005 | |
| # recalculate reference address | |
| call 0f | |
| 0: movsg lr,gr26 | |
| addi gr26,#__head_reference-0b,gr26 | |
| ############################################################################### | |
| # | |
| # move the kernel image down to the bottom of the SDRAM | |
| # | |
| ############################################################################### | |
| sethi.p %hi(__kernel_image_size_no_bss+15),gr4 | |
| setlo %lo(__kernel_image_size_no_bss+15),gr4 | |
| srli.p gr4,#4,gr4 ; count | |
| or gr26,gr26,gr16 ; source | |
| sethi.p %hi(__sdram_base),gr17 ; destination | |
| setlo %lo(__sdram_base),gr17 | |
| setlos #8,gr5 | |
| sub.p gr16,gr5,gr16 ; adjust src for LDDU | |
| sub gr17,gr5,gr17 ; adjust dst for LDDU | |
| sethi.p %hi(__head_move_kernel-__head_reference),gr18 | |
| setlo %lo(__head_move_kernel-__head_reference),gr18 | |
| sethi.p %hi(__head_kernel_moved-__head_reference+__sdram_base),gr19 | |
| setlo %lo(__head_kernel_moved-__head_reference+__sdram_base),gr19 | |
| add gr18,gr26,gr18 | |
| icpl gr18,gr0,#1 | |
| jmpl @(gr18,gr0) | |
| .balign 32 | |
| __head_move_kernel: | |
| lddu @(gr16,gr5),gr10 | |
| lddu @(gr16,gr5),gr12 | |
| stdu.p gr10,@(gr17,gr5) | |
| subicc gr4,#1,gr4,icc0 | |
| stdu.p gr12,@(gr17,gr5) | |
| bhi icc0,#0,__head_move_kernel | |
| jmpl @(gr19,gr0) | |
| .balign 32 | |
| __head_kernel_moved: | |
| icul gr18 | |
| icei @(gr0,gr0),1 | |
| dcei @(gr0,gr0),1 | |
| LEDS 0x0006 | |
| # recalculate reference address | |
| call 0f | |
| 0: movsg lr,gr26 | |
| addi gr26,#__head_reference-0b,gr26 | |
| ############################################################################### | |
| # | |
| # rearrange the iomem map and set the protection registers | |
| # | |
| ############################################################################### | |
| #ifdef CONFIG_MMU | |
| LEDS 0x3301 | |
| call __head_fr451_set_busctl | |
| LEDS 0x3303 | |
| call __head_fr451_survey_sdram | |
| LEDS 0x3305 | |
| call __head_fr451_set_protection | |
| #else | |
| movsg psr,gr5 | |
| srli gr5,#PSR_IMPLE_SHIFT,gr5 | |
| subicc gr5,#PSR_IMPLE_FR551,gr0,icc0 | |
| beq icc0,#0,__head_fr555_memmap | |
| subicc gr5,#PSR_IMPLE_FR451,gr0,icc0 | |
| beq icc0,#0,__head_fr451_memmap | |
| LEDS 0x3101 | |
| call __head_fr401_set_busctl | |
| LEDS 0x3103 | |
| call __head_fr401_survey_sdram | |
| LEDS 0x3105 | |
| call __head_fr401_set_protection | |
| bra __head_done_memmap | |
| __head_fr451_memmap: | |
| LEDS 0x3301 | |
| call __head_fr401_set_busctl | |
| LEDS 0x3303 | |
| call __head_fr401_survey_sdram | |
| LEDS 0x3305 | |
| call __head_fr451_set_protection | |
| bra __head_done_memmap | |
| __head_fr555_memmap: | |
| LEDS 0x3501 | |
| call __head_fr555_set_busctl | |
| LEDS 0x3503 | |
| call __head_fr555_survey_sdram | |
| LEDS 0x3505 | |
| call __head_fr555_set_protection | |
| __head_done_memmap: | |
| #endif | |
| LEDS 0x0007 | |
| ############################################################################### | |
| # | |
| # turn the data cache and MMU on | |
| # - for the FR451 this'll mean that the window through which the kernel is | |
| # viewed will change | |
| # | |
| ############################################################################### | |
| #ifdef CONFIG_MMU | |
| #define MMUMODE HSR0_EIMMU|HSR0_EDMMU|HSR0_EXMMU|HSR0_EDAT|HSR0_XEDAT | |
| #else | |
| #define MMUMODE HSR0_EIMMU|HSR0_EDMMU | |
| #endif | |
| movsg hsr0,gr5 | |
| sethi.p %hi(MMUMODE),gr4 | |
| setlo %lo(MMUMODE),gr4 | |
| or gr4,gr5,gr5 | |
| #if defined(CONFIG_FRV_DEFL_CACHE_WTHRU) | |
| sethi.p %hi(HSR0_DCE|HSR0_CBM_WRITE_THRU),gr4 | |
| setlo %lo(HSR0_DCE|HSR0_CBM_WRITE_THRU),gr4 | |
| #elif defined(CONFIG_FRV_DEFL_CACHE_WBACK) | |
| sethi.p %hi(HSR0_DCE|HSR0_CBM_COPY_BACK),gr4 | |
| setlo %lo(HSR0_DCE|HSR0_CBM_COPY_BACK),gr4 | |
| #elif defined(CONFIG_FRV_DEFL_CACHE_WBEHIND) | |
| sethi.p %hi(HSR0_DCE|HSR0_CBM_COPY_BACK),gr4 | |
| setlo %lo(HSR0_DCE|HSR0_CBM_COPY_BACK),gr4 | |
| movsg psr,gr6 | |
| srli gr6,#24,gr6 | |
| cmpi gr6,#0x50,icc0 // FR451 | |
| beq icc0,#0,0f | |
| cmpi gr6,#0x40,icc0 // FR405 | |
| bne icc0,#0,1f | |
| 0: | |
| # turn off write-allocate | |
| sethi.p %hi(HSR0_NWA),gr6 | |
| setlo %lo(HSR0_NWA),gr6 | |
| or gr4,gr6,gr4 | |
| 1: | |
| #else | |
| #error No default cache configuration set | |
| #endif | |
| or gr4,gr5,gr5 | |
| movgs gr5,hsr0 | |
| bar | |
| LEDS 0x0008 | |
| sethi.p %hi(__head_mmu_enabled),gr19 | |
| setlo %lo(__head_mmu_enabled),gr19 | |
| jmpl @(gr19,gr0) | |
| __head_mmu_enabled: | |
| icei @(gr0,gr0),#1 | |
| dcei @(gr0,gr0),#1 | |
| LEDS 0x0009 | |
| #ifdef CONFIG_MMU | |
| call __head_fr451_finalise_protection | |
| #endif | |
| LEDS 0x000a | |
| ############################################################################### | |
| # | |
| # set up the runtime environment | |
| # | |
| ############################################################################### | |
| # clear the BSS area | |
| sethi.p %hi(__bss_start),gr4 | |
| setlo %lo(__bss_start),gr4 | |
| sethi.p %hi(_end),gr5 | |
| setlo %lo(_end),gr5 | |
| or.p gr0,gr0,gr18 | |
| or gr0,gr0,gr19 | |
| 0: | |
| stdi gr18,@(gr4,#0) | |
| stdi gr18,@(gr4,#8) | |
| stdi gr18,@(gr4,#16) | |
| stdi.p gr18,@(gr4,#24) | |
| addi gr4,#24,gr4 | |
| subcc gr5,gr4,gr0,icc0 | |
| bhi icc0,#2,0b | |
| LEDS 0x000b | |
| # save the SDRAM details | |
| sethi.p %hi(__sdram_old_base),gr4 | |
| setlo %lo(__sdram_old_base),gr4 | |
| st gr24,@(gr4,gr0) | |
| sethi.p %hi(__sdram_base),gr5 | |
| setlo %lo(__sdram_base),gr5 | |
| sethi.p %hi(memory_start),gr4 | |
| setlo %lo(memory_start),gr4 | |
| st gr5,@(gr4,gr0) | |
| add gr25,gr5,gr25 | |
| sethi.p %hi(memory_end),gr4 | |
| setlo %lo(memory_end),gr4 | |
| st gr25,@(gr4,gr0) | |
| # point the TBR at the kernel trap table | |
| sethi.p %hi(__entry_kerneltrap_table),gr4 | |
| setlo %lo(__entry_kerneltrap_table),gr4 | |
| movgs gr4,tbr | |
| # set up the exception frame for init | |
| sethi.p %hi(__kernel_frame0_ptr),gr28 | |
| setlo %lo(__kernel_frame0_ptr),gr28 | |
| sethi.p %hi(_gp),gr16 | |
| setlo %lo(_gp),gr16 | |
| sethi.p %hi(__entry_usertrap_table),gr4 | |
| setlo %lo(__entry_usertrap_table),gr4 | |
| lddi @(gr28,#0),gr28 ; load __frame & current | |
| ldi.p @(gr29,#4),gr15 ; set current_thread | |
| or gr0,gr0,fp | |
| or gr28,gr0,sp | |
| sti.p gr4,@(gr28,REG_TBR) | |
| setlos #ISR_EDE|ISR_DTT_DIVBYZERO|ISR_EMAM_EXCEPTION,gr5 | |
| movgs gr5,isr | |
| # turn on and off various CPU services | |
| movsg psr,gr22 | |
| sethi.p %hi(#PSR_EM|PSR_EF|PSR_CM|PSR_NEM),gr4 | |
| setlo %lo(#PSR_EM|PSR_EF|PSR_CM|PSR_NEM),gr4 | |
| or gr22,gr4,gr22 | |
| movgs gr22,psr | |
| andi gr22,#~(PSR_PIL|PSR_PS|PSR_S),gr22 | |
| ori gr22,#PSR_ET,gr22 | |
| sti gr22,@(gr28,REG_PSR) | |
| ############################################################################### | |
| # | |
| # set up the registers and jump into the kernel | |
| # | |
| ############################################################################### | |
| LEDS 0x000c | |
| # initialise the processor and the peripherals | |
| #call SYMBOL_NAME(processor_init) | |
| #call SYMBOL_NAME(unit_init) | |
| #LEDS 0x0aff | |
| sethi.p #0xe5e5,gr3 | |
| setlo #0xe5e5,gr3 | |
| or.p gr3,gr0,gr4 | |
| or gr3,gr0,gr5 | |
| or.p gr3,gr0,gr6 | |
| or gr3,gr0,gr7 | |
| or.p gr3,gr0,gr8 | |
| or gr3,gr0,gr9 | |
| or.p gr3,gr0,gr10 | |
| or gr3,gr0,gr11 | |
| or.p gr3,gr0,gr12 | |
| or gr3,gr0,gr13 | |
| or.p gr3,gr0,gr14 | |
| or gr3,gr0,gr17 | |
| or.p gr3,gr0,gr18 | |
| or gr3,gr0,gr19 | |
| or.p gr3,gr0,gr20 | |
| or gr3,gr0,gr21 | |
| or.p gr3,gr0,gr23 | |
| or gr3,gr0,gr24 | |
| or.p gr3,gr0,gr25 | |
| or gr3,gr0,gr26 | |
| or.p gr3,gr0,gr27 | |
| # or gr3,gr0,gr30 | |
| or gr3,gr0,gr31 | |
| movgs gr0,lr | |
| movgs gr0,lcr | |
| movgs gr0,ccr | |
| movgs gr0,cccr | |
| # initialise the virtual interrupt handling | |
| subcc gr0,gr0,gr0,icc2 /* set Z, clear C */ | |
| #ifdef CONFIG_MMU | |
| movgs gr3,scr2 | |
| movgs gr3,scr3 | |
| #endif | |
| LEDS 0x0fff | |
| # invoke the debugging stub if present | |
| # - arch/frv/kernel/debug-stub.c will shift control directly to init/main.c | |
| # (it will not return here) | |
| break | |
| .globl __debug_stub_init_break | |
| __debug_stub_init_break: | |
| # however, if you need to use an ICE, and don't care about using any userspace | |
| # debugging tools (such as the ptrace syscall), you can just step over the break | |
| # above and get to the kernel this way | |
| # look at arch/frv/kernel/debug-stub.c: debug_stub_init() to see what you've missed | |
| call start_kernel | |
| .globl __head_end | |
| __head_end: | |
| .size _boot, .-_boot | |
| # provide a point for GDB to place a break | |
| .section .text.start,"ax" | |
| .globl _start | |
| .balign 4 | |
| _start: | |
| call _boot | |
| .previous | |
| ############################################################################### | |
| # | |
| # split a tile off of the region defined by GR8-GR9 | |
| # | |
| # ENTRY: EXIT: | |
| # GR4 - IAMPR value representing tile | |
| # GR5 - DAMPR value representing tile | |
| # GR6 - IAMLR value representing tile | |
| # GR7 - DAMLR value representing tile | |
| # GR8 region base pointer [saved] | |
| # GR9 region top pointer updated to exclude new tile | |
| # GR11 xAMLR mask [saved] | |
| # GR25 SDRAM size [saved] | |
| # GR30 LED address [saved] | |
| # | |
| # - GR8 and GR9 should be rounded up/down to the nearest megabyte before calling | |
| # | |
| ############################################################################### | |
| .globl __head_split_region | |
| .type __head_split_region,@function | |
| __head_split_region: | |
| subcc.p gr9,gr8,gr4,icc0 | |
| setlos #31,gr5 | |
| scan.p gr4,gr0,gr6 | |
| beq icc0,#0,__head_region_empty | |
| sub.p gr5,gr6,gr6 ; bit number of highest set bit (1MB=>20) | |
| setlos #1,gr4 | |
| sll.p gr4,gr6,gr4 ; size of region (1 << bitno) | |
| subi gr6,#17,gr6 ; 1MB => 0x03 | |
| slli.p gr6,#4,gr6 ; 1MB => 0x30 | |
| sub gr9,gr4,gr9 ; move uncovered top down | |
| or gr9,gr6,gr4 | |
| ori gr4,#xAMPRx_S_USER|xAMPRx_C_CACHED|xAMPRx_V,gr4 | |
| or.p gr4,gr0,gr5 | |
| and gr4,gr11,gr6 | |
| and.p gr5,gr11,gr7 | |
| bralr | |
| __head_region_empty: | |
| or.p gr0,gr0,gr4 | |
| or gr0,gr0,gr5 | |
| or.p gr0,gr0,gr6 | |
| or gr0,gr0,gr7 | |
| bralr | |
| .size __head_split_region, .-__head_split_region | |
| ############################################################################### | |
| # | |
| # write the 32-bit hex number in GR8 to ttyS0 | |
| # | |
| ############################################################################### | |
| #if 0 | |
| .globl __head_write_to_ttyS0 | |
| .type __head_write_to_ttyS0,@function | |
| __head_write_to_ttyS0: | |
| sethi.p %hi(0xfeff9c00),gr31 | |
| setlo %lo(0xfeff9c00),gr31 | |
| setlos #8,gr20 | |
| 0: ldubi @(gr31,#5*8),gr21 | |
| andi gr21,#0x60,gr21 | |
| subicc gr21,#0x60,gr21,icc0 | |
| bne icc0,#0,0b | |
| 1: srli gr8,#28,gr21 | |
| slli gr8,#4,gr8 | |
| addi gr21,#'0',gr21 | |
| subicc gr21,#'9',gr0,icc0 | |
| bls icc0,#2,2f | |
| addi gr21,#'A'-'0'-10,gr21 | |
| 2: | |
| stbi gr21,@(gr31,#0*8) | |
| subicc gr20,#1,gr20,icc0 | |
| bhi icc0,#2,1b | |
| setlos #'\r',gr21 | |
| stbi gr21,@(gr31,#0*8) | |
| setlos #'\n',gr21 | |
| stbi gr21,@(gr31,#0*8) | |
| 3: ldubi @(gr31,#5*8),gr21 | |
| andi gr21,#0x60,gr21 | |
| subicc gr21,#0x60,gr21,icc0 | |
| bne icc0,#0,3b | |
| bralr | |
| .size __head_write_to_ttyS0, .-__head_write_to_ttyS0 | |
| #endif |