1 /*
2  * libtilemcore - Graphing calculator emulation library
3  *
4  * Copyright (C) 2009 Benjamin Moody
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include "tilem.h"
27 
tilem_user_timers_reset(TilemCalc * calc)28 void tilem_user_timers_reset(TilemCalc* calc)
29 {
30 	int i;
31 
32 	for (i = 0; i < TILEM_MAX_USER_TIMERS; i++) {
33 		tilem_z80_set_timer(calc, TILEM_TIMER_USER1 + i, 0, 0, 0);
34 		calc->usertimers[i].frequency = 0;
35 		calc->usertimers[i].loopvalue = 0;
36 		calc->usertimers[i].status = 0;
37 	}
38 }
39 
get_duration(int fvalue,int nticks)40 static inline dword get_duration(int fvalue, int nticks)
41 {
42 	qword t=0;
43 
44 	if (fvalue & 0x80) {
45 		if (fvalue & 0x20)
46 			return 64 * nticks;
47 		else if (fvalue & 0x10)
48 			return 32 * nticks;
49 		else if (fvalue & 0x08)
50 			return 16 * nticks;
51 		else if (fvalue & 0x04)
52 			return 8 * nticks;
53 		else if (fvalue & 0x02)
54 			return 4 * nticks;
55 		else if (fvalue & 0x01)
56 			return 2 * nticks;
57 		else
58 			return nticks;
59 	}
60 	else if (fvalue & 0x40) {
61 		switch (fvalue & 7) {
62 		case 0:
63 			t = 3000000;
64 			break;
65 		case 1:
66 			t = 33000000;
67 			break;
68 		case 2:
69 			t = 328000000;
70 			break;
71 		case 3:
72 			t = 0xC3530D40; //3277000000;
73 			break;
74 		case 4:
75 			t = 1000000;
76 			break;
77 		case 5:
78 			t = 16000000;
79 			break;
80 		case 6:
81 			t = 256000000;
82 			break;
83 		case 7:
84 			t = 0xF4240000; //4096000000;
85 			break;
86 		}
87 
88 		return ((t * nticks + 16384) / 32768);
89 	}
90 
91 	return 0;
92 }
93 
get_normal_duration(const TilemUserTimer * tmr)94 static dword get_normal_duration(const TilemUserTimer* tmr)
95 {
96 	if (tmr->loopvalue)
97 		return get_duration(tmr->frequency, tmr->loopvalue);
98 	else
99 		return get_duration(tmr->frequency, 256);
100 }
101 
get_overflow_duration(const TilemUserTimer * tmr)102 static dword get_overflow_duration(const TilemUserTimer* tmr)
103 {
104 	return get_duration(tmr->frequency, 256);
105 }
106 
tilem_user_timer_set_frequency(TilemCalc * calc,int n,byte value)107 void tilem_user_timer_set_frequency(TilemCalc* calc, int n, byte value)
108 {
109 	TilemUserTimer* tmr = &calc->usertimers[n];
110 
111 	/* writing to frequency control port stops timer; set
112 	   loopvalue to the current value of the counter */
113 	tmr->loopvalue = tilem_user_timer_get_value(calc, n);
114 	tilem_z80_set_timer(calc, TILEM_TIMER_USER1 + n, 0, 0, 0);
115 	tmr->frequency = value;
116 }
117 
tilem_user_timer_set_mode(TilemCalc * calc,int n,byte mode)118 void tilem_user_timer_set_mode(TilemCalc* calc, int n, byte mode)
119 {
120 	TilemUserTimer* tmr = &calc->usertimers[n];
121 
122 	/* setting mode clears "finished" and "overflow" flags */
123 	tmr->status = ((tmr->status & TILEM_USER_TIMER_NO_HALT_INT)
124 		       | (mode & 3));
125 
126 	calc->z80.interrupts &= ~(TILEM_INTERRUPT_USER_TIMER1 << n);
127 
128 	/* if timer is currently running, it will not overflow the
129 	   next time it expires -> set period to normal */
130 	if ((mode & TILEM_USER_TIMER_LOOP) || tmr->loopvalue == 0) {
131 		tilem_z80_set_timer_period(calc, TILEM_TIMER_USER1 + n,
132 					   get_normal_duration(tmr));
133 	}
134 	else {
135 		tilem_z80_set_timer_period(calc, TILEM_TIMER_USER1 + n, 0);
136 	}
137 }
138 
tilem_user_timer_start(TilemCalc * calc,int n,byte value)139 void tilem_user_timer_start(TilemCalc* calc, int n, byte value)
140 {
141 	TilemUserTimer* tmr = &calc->usertimers[n];
142 	dword count, period;
143 
144 	tmr->loopvalue = value;
145 
146 	/* if a valid frequency is set, then writing to value port
147 	   starts timer */
148 
149 	count = get_normal_duration(tmr);
150 	if (!count)
151 		return;
152 
153 	if (!value) {
154 		/* input value 0 means loop indefinitely */
155 		period = get_overflow_duration(tmr);
156 	}
157 	else if (tmr->status & TILEM_USER_TIMER_FINISHED) {
158 		/* timer has already expired once -> it will overflow
159 		   the next time it expires (note that this happens
160 		   even if the loop flag isn't set) */
161 		period = get_overflow_duration(tmr);
162 	}
163 	else if (!(tmr->status & TILEM_USER_TIMER_LOOP)) {
164 		/* don't loop */
165 		period = 0;
166 	}
167 	else {
168 		/* timer hasn't expired yet; second iteration starts
169 		   from the same counter value as the first */
170 		period = count;
171 	}
172 
173 	tilem_z80_set_timer(calc, TILEM_TIMER_USER1 + n, count, period,
174 			    (calc->usertimers[n].frequency & 0x80 ? 0 : 1));
175 }
176 
tilem_user_timer_get_value(TilemCalc * calc,int n)177 byte tilem_user_timer_get_value(TilemCalc* calc, int n)
178 {
179 	TilemUserTimer* tmr = &calc->usertimers[n];
180 	dword period;
181 
182 	if (!tilem_z80_timer_running(calc, TILEM_TIMER_USER1 + n))
183 		return tmr->loopvalue;
184 
185 	period = get_overflow_duration(tmr);
186 
187 	if (tmr->frequency & 0x80) {
188 		dword t = tilem_z80_get_timer_clocks
189 			(calc, TILEM_TIMER_USER1 + n);
190 		return ((t * 256) / period) % 256;
191 	}
192 	else {
193 		qword t = tilem_z80_get_timer_microseconds
194 			(calc, TILEM_TIMER_USER1 + n);
195 		return ((t * 256) / period) % 256;
196 	}
197 }
198 
tilem_user_timer_expired(TilemCalc * calc,void * data)199 void tilem_user_timer_expired(TilemCalc* calc, void* data)
200 {
201 	int n = TILEM_PTR_TO_DWORD(data);
202 	TilemUserTimer* tmr = &calc->usertimers[n];
203 
204 	/* input value 0 means loop indefinitely (don't set flags) */
205 	if (!tmr->loopvalue) {
206 		return;
207 	}
208 
209 	/* if timer has already finished, set "overflow" flag */
210 	if (tmr->status & TILEM_USER_TIMER_FINISHED) {
211 		tmr->status |= TILEM_USER_TIMER_OVERFLOW;
212 	}
213 
214 	/* set "finished" flag */
215 	tmr->status |= TILEM_USER_TIMER_FINISHED;
216 
217 	/* generate interrupt if appropriate */
218 	if ((tmr->status & TILEM_USER_TIMER_INTERRUPT)
219 	    && (!(tmr->status & TILEM_USER_TIMER_NO_HALT_INT)
220 		|| !calc->z80.halted)) {
221 		calc->z80.interrupts |= (TILEM_INTERRUPT_USER_TIMER1 << n);
222 	}
223 
224 	if (tmr->status & TILEM_USER_TIMER_LOOP) {
225 		/* timer will overflow the next time it expires
226 		   (unless user writes to the mode port before
227 		   then) */
228 		tilem_z80_set_timer_period(calc, TILEM_TIMER_USER1 + n,
229 					   get_overflow_duration(tmr));
230 	}
231 }
232