/* Copyright (c) 2005,2007  Dmitry Xmelkov
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.
   * Neither the name of the copyright holders nor the names of
     contributors may be used to endorse or promote products derived
     from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. */

/* $Id: ultoa_invert.S 2191 2010-11-05 13:45:57Z arcanum $	*/

#if !defined(__AVR_TINY__)

#ifndef	__DOXYGEN__

#include "macros.inc"
#include "ntz.h"
#include "xtoa_fast.h"

/* --------------------------------------------------------------------
   char * __ultoa_invert (unsigned long val, char * str, int base)

   This function is intended for usage as internal printf's one.
   It differs from others of `xtoa_fast' family:
       * srt[] will NOT 0 terminated.
       * Sequence of digits is inverted.
       * It returns pointer to first byte after a string.
       * Only `XTOA_UPPER' flag is operated.
   Notes:
       * base: check only 8 and 16, all others are treated as 10.
       (internal printf's function).
*/

    /* Input	*/
#define v_lo	r22
#define	v_hi	r23
#define	v_hlo	r24
#define	v_hhi	r25
#define	str_lo	r20
#define	str_hi	r21
#define	base	r18
#define	flags	r19

    /* Used	*/
#define	v_fifth	r26	/* val: bits 39..32			*/
#define	t_lo	r18	/* temporary for shifted `val'		*/
#define	t_hi	r19
#define	t_hlo	r20
#define	t_hhi	r21
#define	symb	r20	/* write to string			*/
#define	cnt	r27	/* shift loop counter, local arg	*/

    /* Fixed	*/
#define	rzero	r1

	ASSEMBLY_CLIB_SECTION
	.global	__ultoa_invert
	.type	__ultoa_invert, "function"

__ultoa_invert:
	X_movw	ZL, str_lo
	clr	v_fifth			; needed for all (ultoa_lsr)
	cpi	base, 8
	breq	.L_oct
	cpi	base, 16
	breq	.L_hex

  ; decimal format
	clt				; flag of val == 0
.L_dec_loop:
	push	v_lo			; to calculate remander
  ; val &= ~1
	andi	v_lo, ~1
  ; val += 2
	subi	v_lo, lo8(-2)
	sbci	v_hi, hi8(-2)
	sbci	v_hlo, hlo8(-2)
	sbci	v_hhi, hhi8(-2)
	sbci	v_fifth, hhi8(-2)
  ; val += val/2
	ldi	cnt, 1
	rcall	.L_div_add
  ; val += val/16
	ldi	cnt, 4
	rcall	.L_div_add
  ; val += val/256
	add	v_lo, v_hi
	adc	v_hi, v_hlo
	adc	v_hlo, v_hhi
	adc	v_hhi, v_fifth
	adc	v_fifth, rzero
  ; val += val/65536
	add	v_lo, v_hlo
	adc	v_hi, v_hhi
	adc	v_hlo, v_fifth
	adc	v_hhi, rzero
	adc	v_fifth, rzero
  ; val += val >> 32
	add	v_lo, v_fifth
	adc	v_hi, rzero
	adc	v_hlo, rzero
	adc	v_hhi, rzero
	adc	v_fifth, rzero
  ; division result:  val /= 16
	rcall	.L_lsr_4		; v_fitth := 0
	brne	1f
	set				; T := Z flag
1:
  ; rem:  val_original - 10*val
	pop	t_hi
#if  defined(__AVR_ENHANCED__) && __AVR_ENHANCED__
	ldi	t_lo, 10
	mul	t_lo, v_lo
	clr	r1
#else
	mov	r0, v_lo
	lsl	r0
	sub	t_hi, r0
	lsl	r0
	lsl	r0
#endif
	sub	t_hi, r0
  ; output digit
	subi	t_hi, lo8(-'0')
	st	Z+, t_hi
  ; quotient == 0 ?
	brtc	.L_dec_loop
  ; end of string
.L_eos:
	X_movw	r24, ZL
	ret

  ; octal format
.L_oct:
	mov	symb, v_lo
	andi	symb, 7
	subi	symb, lo8(-'0')
	st	Z+, symb
	ldi	cnt, 3
	rcall	.L_lsr
	brne	.L_oct
	rjmp	.L_eos

  ; hex format
.L_hex:
	mov	symb, v_lo
	andi	symb, 0x0f
	subi	symb, lo8(-'0')
	cpi	symb, '9' + 1
	brlo	3f
	subi	symb, lo8('9' + 1 - 'a')
	sbrc	flags, ntz(XTOA_UPPER) - 8
	subi	symb, lo8('a' - 'A')
3:	st	Z+, symb
	rcall	.L_lsr_4
	brne	.L_hex
	rjmp	.L_eos
	
.L_lsr_4:
	ldi	cnt, 4
.L_lsr:
	lsr	v_fifth
	ror	v_hhi
	ror	v_hlo
	ror	v_hi
	ror	v_lo
	dec	cnt
	brne	.L_lsr
  ; tst
	sbiw	v_hlo, 0		; only Z flag is needed
	cpc	v_lo, rzero
	cpc	v_hi, rzero
	ret

.L_div_add:
  ; copy to temporary
	X_movw	t_lo, v_lo
	X_movw	t_hlo, v_hlo
	mov	r0, v_fifth
  ; lsr temporary
7:	lsr	r0
	ror	t_hhi
	ror	t_hlo
	ror	t_hi
	ror	t_lo
	dec	cnt
	brne	7b
  ; add
	add	v_lo, t_lo
	adc	v_hi, t_hi
	adc	v_hlo, t_hlo
	adc	v_hhi, t_hhi
	adc	v_fifth, r0		; here r0 == 0
	ret

	.size	__ultoa_invert, . - __ultoa_invert
	.end

#endif	/* !__DOXYGEN__ */

#endif /* !defined(__AVR_TINY__) */
