/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  time.S
 * Purpose: time related functions for DOS simulator
 * Entries: dos2A, dos2B, dos2C, dos2D
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: time.S86,v 1.5 2007/01/06 18:30:48 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Include assembler macros:
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <system/bios.i86>
#include "dossim.i86"

.file __FILE__
.line __LINE__




/*
 **************************************************************************
 *
 * BSS segment
 *
 */
	.bss

#ifdef NEEDTIME
	.lcomm	date,4			# current date
	.lcomm	time,4			# current time
#endif




/*
 **************************************************************************
 *
 * Start code segment.
 *
 */
	.text

#ifdef NEEDTIME
	.global	dos2A
	.global	dos2B
	.global	dos2C		# define entry points
	.global	dos2D
#endif



/*
 **************************************************************************
 *
 * Read current date. Note that the DOS simulator will not change the date
 * over midnight.
 * Input:  none
 * Output: AL  -  day of week (not used)
 *         CX  -  year
 *         DL  -  day of month
 *         DH  -  month
 * Registers changed: AL, CX, DX
 *
 */
#ifdef NEEDTIME
dos2A:

	push	ax
	push	bx
	mov	cx,[date + 0]
	mov	dx,[date + 2]
	mov	ax,cx
	or	ax,dx			# is the date already set?
	jnz	1f

	mov	ah,0x04
	int	INT_TIME		# get current date from BIOS
	mov	al,ch
	call	cvt2bin			# convert century number
	mov	bl,100
	mul	bl			# multiplicate by 100
	xchg	ax,cx
	xor	ah,ah
	call	cvt2bin			# convert year number
	add	cx,ax			# add to century number
	mov	al,dl
	call	cvt2bin			# convert day of month
	mov	dl,al
	mov	al,dh
	call 	cvt2bin			# convert month
	mov	dh,al
	mov	word ptr [date + 0],cx
	mov	word ptr [date + 2],dx	# save new date

1:	pop	bx
	pop	ax
	xor	al,al			# day of week not used
	ret
#endif



/*
 **************************************************************************
 *
 * Set current date. This routine does not check, if a date is correct.
 * Input:  CX  -  year
 *         DL  -  day of month
 *         DH  -  month
 * Output: AL  -  status: 00 = date is correct
 * Registers changed: AL
 *
 */
#ifdef NEEDTIME
dos2B:

	mov	word ptr [date + 0],cx
	mov	word ptr [date + 2],dx	# save new date
	xor	al,al			# return with success
	ret
#endif



/*
 **************************************************************************
 *
 * Read time. This routine uses the timer tick like the real DOS. To make
 * this possible, the current time is stored on the first call to this
 * routine, and then the BIOS timer tick count is reset to zero. Therefore,
 * the BIOS timer count is always relative to the time stored initially.
 * Input:  none
 * Output: CH  -  hour
 *         CL  -  minute
 *         DH  -  seconds
 *         DL  -  hundreds of seconds
 * Registers changed: CX, DX
 *
 */
#ifdef NEEDTIME
dos2C:

	push	ax
	cmp	dword ptr [time],0	# is this the first call?
	jne	1f

	call	getcmos			# read current time
	call	cvttime			# convert it into timer ticks
	mov	word ptr [time+0],dx	# and save it as the reference value
	mov	word ptr [time+2],cx
	xor	cx,cx
	xor	dx,dx			# reset the timer tick count to zero
	mov	ah,0x01
	int	INT_TIME

1:	xor	ah,ah
	int	INT_TIME		# get current timer tick count
	add	dx,word ptr [time + 0]	# add it to base count
	adc	cx,word ptr [time + 2]
	call	cvtticks		# and convert it into a time value
	pop	ax
	ret
#endif



/*
 **************************************************************************
 *
 * Set time. This routine does not check whether the time is valid or not.
 * If it gets an invalid time, the conversion routine might end up with
 * a divide error. To set the time, a tick count will be computed and
 * set as the future base value, with the BIOS timer reset to zero.
 * Input:  CH  -  hour
 *         CL  -  minutes
 *         DH  -  seconds
 *         DL  -  hundreds of seconds
 * Output: AL  -  status: 00 = time OK
 * Registers changed: AL
 *
 */
#ifdef NEEDTIME
dos2D:

	push	ax
	push	cx
	push	dx
	call	cvttime			# convert time into tick count
	mov	word ptr [time + 0],dx	# and save it as the base value
	mov	word ptr [time + 2],cx
	xor	cx,cx
	xor	dx,dx
	mov	ah,0x01
	int	INT_TIME		# reset BIOS counter to zero
	pop	dx
	pop	cx
	pop	ax
	xor	al,al			# always return with success
	ret
#endif



/*
 **************************************************************************
 *
 * Convert time into a number of timer ticks.
 * Input:  CH  -  hour
 *         CL  -  minutes
 *         DH  -  seconds
 *         DL  -  hundreds of seconds
 * Output: CX:DX  -  timer value
 * Registers changed: AX, CX, DX
 *
 */
#ifdef NEEDTIME
cvttime:

	push	bx
	mov	al,60
	mul	ch
	xor	ch,ch			# compute number of minutes since
	add	cx,ax			# midnight -> CX
	mov	al,100
	mul	dh			# compute number of hundred-seconds
	xor	dh,dh			# since last minute -> DX
	add	dx,ax
	xchg	cx,dx
	mov	ax,6000		# combine both values in CX and DX
	mul	dx			# by first converting the number
	add	ax,cx			# of minutes into hundred-seconds,
	adc	dx,0			# and then adding both values
	mov	cx,dx

/*
 * The BIOS generates 18.2 timer ticks per second. To convert the number
 * of hundreds of seconds since midnight into the number of timer ticks,
 * multiply the number by 233, and then divide by 2^8 and then by 5.
 */

	mov	bx,233
	mul	bx			# first multiply the lower word
	xchg	cx,ax
	mul	bl			# then the upper byte
	add	dx,ax			# add carry from lower to upper
	mov	bx,5
	mov	al,dh			# "divide" high word by 2^8 and move
	xor	ah,ah			# the result into AX
	mov	dh,dl			# "divide" low word by 2^8 and move
	mov	dl,ch			# the result into DX
	div	bl			# divide high word by 5
	mov	cl,al
	xor	ch,ch			# CX now contains the high word of the
	xchg	ax,dx			# end result
	mov	dl,dh
	xor	dh,dh
	div	bx			# now divide the low word by 5
	mov	dx,ax			# put the result into CX:DX
	pop	bx
	ret
#endif



/*
 **************************************************************************
 *
 * Convert number of timer ticks into time value.
 * Input:  CX:DX  -  number of timer ticks
 * Output: CH  -  hour
 *         CL  -  minutes
 *         DH  -  seconds
 *         DL  -  hundreds of seconds
 * Registers changed: AX, CX, DX
 *
 */
#ifdef NEEDTIME
cvtticks:

	push	bx
	xchg	cx,dx
	mov	ax,cx
	mov	bx,65520		# divide tick count by number of
	div	bx			# ticks per hour
	mov	ch,al			# save hour
	mov	ax,dx
	xor	dx,dx
	mov	bx,1092			# divide remaining tick count by
	div	bx			# number of ticks per minute
	mov	cl,al			# save minute
	mov	ax,dx
	mov	bx,500
	mul	bx			# multiply remaining tick count by
	mov	bx,91			# 500 and divide by 91 to get number
	div	bx			# of hundreds of a second
	xor	dx,dx
	mov	bx,100			# divide by 100 to get number of
	div	bx			# seconds and number of hundreds
	mov	dh,al
	pop	bx
	ret
#endif



/*
 **************************************************************************
 *
 * Read the current time from the CMOS clock using the BIOS.
 * Input:  none
 * Output: CH  -  hour
 *         CL  -  minute
 *         DH  -  seconds
 *         DL  -  hundreds of seconds
 * Registers changed: AX, CX, DX
 *
 */
#ifdef NEEDTIME
getcmos:

	mov	ah,0x02
	int	INT_TIME		# get current time from BIOS
	mov	al,ch
	call	cvt2bin			# convert hours
	mov	ch,al
	mov	al,cl
	call	cvt2bin			# convert minutes
	mov	cl,al
	mov	al,dh
	call	cvt2bin			# convert seconds
	mov	dh,al
	xor	dl,dl			# hundreds of seconds is always zero
	ret
#endif



/*
 **************************************************************************
 *
 * Routine to convert BCD value into binary value
 * Input:  AL  -  BCD value
 * Output: AL  -  Binary value
 * Registers changed: AL
 *
 */
#ifdef NEEDTIME
cvt2bin:

	push	cx
	mov	ch,al
	shr	ch,4			# separate upper nibble
	xchg	ch,ah
	and	ax,0x0F0F
	aad				# convert into binary
	mov	ah,ch
	pop	cx
	ret
#endif




/*
 **************************************************************************
 *
 */
	.end

