+/*
+ * File: tmr.c
+ *
+ * Generic timer module. Currently uses Timer0 to generate ticks every
+ * 32 ms. Later will use Timer1, a crystal, and optionally a compare
+ * module to be able to generate ticks and wake up from sleep.
+ */
+
+
+#include <htc.h>
+#include "tmr.h"
+#include "isr.h"
+#include "bit.h"
+
+persistent tmr_time_t _tmr_ticks;
+static tmr_time_t _tmr_t0[TMR_COUNT];
+static tmr_time_t _tmr_elapsed[TMR_COUNT];
+static tmr_bitno_t _tmr_on;
+static tmr_bitno_t _tmr_periodic;
+static tmr_bitno_t _tmr_fired;
+
+/* Timer0 with 1:256 prescale given Fosc (_XTAL_FREQ).
+ * Timer0 is clocked by Fosc/4.
+ *
+ * Fosc Overflow, ms
+ * ------- ----------
+ * 32 MHz 8.192
+ * 16 MHz 16.384
+ * 8 MHz 32.768
+ * 4 MHz 65.536
+ * 2 MHz 131.072
+ * 1 MHz 262.144
+ * 500 KHz 524.288
+ * 250 KHz 1,048.576
+ * 125 KHz 2,097.152
+ */
+
+void tmr_init()
+{
+ /* Configure Timer0 to overflow every 32 msec. Adjust with
+ * Fosc as set in picinit.[ch]. At 4 MHz, prescale is 1:128.
+ * TMR0CS = 0, PSA = 0, PS = 0b110
+ */
+ OPTION_REG = (OPTION_REG & 0b11010000) + 0b0110;
+ TMR0IF = 0;
+ TMR0IE = 1;
+ GIE = 1;
+}
+
+void tmr_start(tmr_bitno_t t, tmr_time_t elapsed)
+{
+ ndi();
+ bit_set(_tmr_on, t);
+ bit_clr(_tmr_periodic, t);
+ bit_clr(_tmr_fired, t);
+ _tmr_t0[t] = _tmr_ticks;
+ _tmr_elapsed[t] = elapsed;
+ nei();
+}
+
+void tmr_startAt(tmr_bitno_t t, tmr_time_t t0, tmr_time_t elapsed)
+{
+ ndi();
+ bit_set(_tmr_on, t);
+ bit_clr(_tmr_periodic, t);
+ bit_clr(_tmr_fired, t);
+ _tmr_t0[t] = t0 + elapsed;
+ nei();
+}
+
+void tmr_startPeriodic(tmr_bitno_t t, tmr_time_t elapsed)
+{
+ ndi();
+ bit_set(_tmr_on, t);
+ bit_set(_tmr_periodic, t);
+ bit_clr(_tmr_fired, t);
+ _tmr_t0[t] = _tmr_ticks + elapsed;
+ _tmr_elapsed[t] = elapsed;
+ nei();
+}
+
+void tmr_startPeriodicAt(tmr_bitno_t t, tmr_time_t t0, tmr_time_t elapsed)
+{
+ ndi();
+ bit_set(_tmr_on, t);
+ bit_set(_tmr_periodic, t);
+ bit_clr(_tmr_fired, t);
+ _tmr_t0[t] = t0 + elapsed;
+ _tmr_elapsed[t] = elapsed;
+ nei();
+}
+
+bit tmr_fired(tmr_bitno_t t)
+{
+ /* FIXME: if called from ISR ndi()/nei() is not required */
+ static unsigned char last_fired;
+ unsigned char fired;
+
+ ndi();
+ fired = bit_get(_tmr_fired, t) != 0;
+ if (fired)
+ bit_clr(_tmr_fired, t);
+ nei();
+ return fired;
+}
+
+void tmr_isr()
+{
+ if (TMR0IF) {
+ TMR0IF = 0;
+ _tmr_ticks++;
+ for (tmr_bitno_t t = 0; t < TMR_COUNT; t++) {
+ if (_tmr_ticks - _tmr_t0[t] <= 0) {
+ bit_set(_tmr_fired, t);
+ if (bit_get(_tmr_periodic, t))
+ _tmr_t0[t] += _tmr_elapsed[t];
+ else
+ bit_clr(_tmr_on, t);
+ }
+ }
+ }
+}
+
+/* Wait for a specific timer value t */
+#define tmr_wait(t) while (TMR0 != t);
+
+void tmr_uwait(unsigned us)
+{
+ unsigned t0 = TMR0;
+
+ while (us >= 32768) {
+ tmr_wait(t0);
+ us -= 32768;
+ }
+ while (us >= 16384) {
+ tmr_cwait(128);
+ us -= 16384;
+ }
+ tmr_cwait(us / 128);
+}
+
+void tmr_mwait(unsigned ms)
+{
+ unsigned t0 = TMR0;
+
+ while (ms >= 32) {
+ tmr_wait(t0);
+ ms -= 32;
+ }
+ while (ms >= 16) {
+ tmr_cwait(128);
+ ms -= 16;
+ }
+ tmr_cwait(ms * 8);
+}