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