diff --git a/simio/simio_timer.c b/simio/simio_timer.c
index 2d304c3..bae2135 100644
--- a/simio/simio_timer.c
+++ b/simio/simio_timer.c
@@ -36,13 +36,18 @@
#define TACLR 0x0004 /* Timer A counter clear */
#define TAIE 0x0002 /* Timer A counter interrupt enable */
#define TAIFG 0x0001 /* Timer A counter interrupt flag */
+/* TBCTL bits */
+#define TBCLGRP1 0x4000 /* Timer B Compare latch load group 1 */
+#define TBCLGRP0 0x2000 /* Timer B Compare latch load group 0 */
+#define CNTL1 0x1000 /* Timer B Counter length 1 */
+#define CNTL0 0x0800 /* Timer B Counter length 0 */
/* TACCTLx flags (taken from mspgcc) */
#define CM1 0x8000 /* Capture mode 1 */
#define CM0 0x4000 /* Capture mode 0 */
#define CCIS1 0x2000 /* Capture input select 1 */
#define CCIS0 0x1000 /* Capture input select 0 */
-#define SCS 0x0800 /* Capture sychronize */
+#define SCS 0x0800 /* Capture synchronize */
#define SCCI 0x0400 /* Latched capture signal (read) */
#define CAP 0x0100 /* Capture mode: 1 /Compare mode : 0 */
#define OUTMOD2 0x0080 /* Output mode 2 */
@@ -53,9 +58,20 @@
/* #define OUT 0x0004 PWM Output signal if output mode 0 */
#define COV 0x0002 /* Capture/compare overflow flag */
#define CCIFG 0x0001 /* Capture/compare interrupt flag */
+/* TBCCTLx flags */
+#define CLLD1 0x0400 /* Compare latch load source 1 */
+#define CLLD0 0x0200 /* Compare latch load source 0 */
+/* Timer IV words */
+#define TAIV_TAIFG 0x000A /* Interrupt vector word for TAIFG */
+#define TBIV_TBIFG 0x000E /* Interrupt vector word for TBIFG */
#define MAX_CCRS 7
+typedef enum {
+ TIMER_TYPE_A,
+ TIMER_TYPE_B,
+} timer_type_t;
+
struct timer {
struct simio_device base;
@@ -67,6 +83,7 @@ struct timer {
address_t iv_addr;
int irq0;
int irq1;
+ timer_type_t timer_type;
/* IO registers */
uint16_t tactl;
@@ -110,6 +127,7 @@ static struct simio_device *timer_create(char **arg_text)
tr->iv_addr = 0x12e;
tr->irq0 = 9;
tr->irq1 = 8;
+ tr->timer_type = TIMER_TYPE_A;
return (struct simio_device *)tr;
}
@@ -149,6 +167,28 @@ static int config_addr(address_t *addr, char **arg_text)
return 0;
}
+static int config_type(timer_type_t *timer_type, char **arg_text)
+{
+ char *text = get_arg(arg_text);
+
+ if (!text) {
+ printc_err("timer: config: expected type\n");
+ return -1;
+ }
+
+ if (!strcasecmp(text, "A")) {
+ *timer_type = TIMER_TYPE_A;
+ return 0;
+ }
+ if (!strcasecmp(text, "B")) {
+ *timer_type = TIMER_TYPE_B;
+ return 0;
+ }
+
+ printc_err("timer: can't parse type: %s\n", text);
+ return -1;
+}
+
static int config_irq(int *irq, char **arg_text)
{
char *text = get_arg(arg_text);
@@ -237,6 +277,8 @@ static int timer_config(struct simio_device *dev,
if (!strcasecmp(param, "base"))
return config_addr(&tr->base_addr, arg_text);
+ if (!strcasecmp(param, "type"))
+ return config_type(&tr->timer_type, arg_text);
if (!strcasecmp(param, "iv"))
return config_addr(&tr->iv_addr, arg_text);
if (!strcasecmp(param, "irq0"))
@@ -266,7 +308,8 @@ static uint16_t calc_iv(struct timer *tr, int update)
if ((tr->tactl & (TAIFG | TAIE)) == (TAIFG | TAIE)) {
if (update)
tr->tactl &= ~TAIFG;
- return 0xa;
+ return (tr->timer_type == TIMER_TYPE_A) ?
+ TAIV_TAIFG : TBIV_TBIFG;
}
return 0;
@@ -276,26 +319,46 @@ static int timer_info(struct simio_device *dev)
{
struct timer *tr = (struct timer *)dev;
int i;
+ char timer_type = (tr->timer_type == TIMER_TYPE_A) ? 'A' : 'B';
printc("Base address: 0x%04x\n", tr->base_addr);
printc("IV address: 0x%04x\n", tr->iv_addr);
- printc("IRQ0: %d\n", tr->irq0);
- printc("IRQ1: %d\n", tr->irq1);
+ printc("IRQ0: %d\n", tr->irq0);
+ printc("IRQ1: %d\n", tr->irq1);
printc("\n");
- printc("TACTL: 0x%04x\n", tr->tactl);
- printc("TAR: 0x%04x\n", tr->tar);
- printc("TAIV: 0x%02x\n", calc_iv(tr, 0));
+ printc("T%cCTL: 0x%04x\n", timer_type, tr->tactl);
+ printc("T%cR: 0x%04x\n", timer_type, tr->tar);
+ printc("T%cIV: 0x%02x\n", timer_type, calc_iv(tr, 0));
printc("\n");
- for (i = 0; i < tr->size; i++)
- printc("Channel %2d, TACCTL = 0x%04x, TACCR = 0x%04x\n",
- i, tr->ctls[i], tr->ccrs[i]);
+ for (i = 0; i < tr->size; i++) {
+ printc("T%cCCTL%d = 0x%04x, T%cCCR%d = 0x%04x",
+ timer_type, i, tr->ctls[i], timer_type, i, tr->ccrs[i]);
+ printc("\n");
+ }
return 0;
}
+static uint16_t tar_mask(struct timer *tr)
+{
+ if (tr->timer_type == TIMER_TYPE_B) {
+ switch (tr->tactl & (CNTL1 | CNTL0)) {
+ case 0: /* 16 bits */
+ break;
+ case CNTL0: /* 12 bits */
+ return 0x0fff;
+ case CNTL1: /* 10 bits */
+ return 0x03ff;
+ case CNTL1 | CNTL0: /* 8 bits */
+ return 0x00ff;
+ }
+ }
+ return 0xffff;
+}
+
static int timer_write(struct simio_device *dev,
- address_t addr, uint16_t data)
+ address_t addr, uint16_t data)
{
struct timer *tr = (struct timer *)dev;
@@ -308,7 +371,7 @@ static int timer_write(struct simio_device *dev,
}
if (addr == tr->base_addr + 0x10) {
- tr->tar = data;
+ tr->tar = data & tar_mask(tr);
return 0;
}
@@ -316,8 +379,13 @@ static int timer_write(struct simio_device *dev,
addr < tr->base_addr + (tr->size << 1) + 2) {
int index = ((addr & 0xf) - 2) >> 1;
uint16_t oldval = tr->ctls[index];
+ uint16_t mask;
- tr->ctls[index] = (data & 0xf9f7) | (oldval & 0x0608);
+ if (tr->timer_type == TIMER_TYPE_A)
+ mask = 0x0608;
+ if (tr->timer_type == TIMER_TYPE_B)
+ mask = 0x0008;
+ tr->ctls[index] = (data & ~mask) | (oldval & mask);
/* Check capture initiated by Software */
if ((data & (CAP | CCIS1)) == (CAP | CCIS1))
trigger_capture(tr, index, oldval & CCI, data & CCIS0);
@@ -348,7 +416,7 @@ static int timer_write(struct simio_device *dev,
}
static int timer_read(struct simio_device *dev,
- address_t addr, uint16_t *data)
+ address_t addr, uint16_t *data)
{
struct timer *tr = (struct timer *)dev;
@@ -409,23 +477,37 @@ static void timer_ack_interrupt(struct simio_device *dev, int irq)
/* By design irq1 does not clear CCIFG or TAIFG automatically */
}
+static uint16_t tar_increment(struct timer *tr)
+{
+ tr->tar++;
+ tr->tar &= tar_mask(tr);
+ return tr->tar;
+}
+
+static uint16_t tar_decrement(struct timer *tr)
+{
+ tr->tar--;
+ tr->tar &= tar_mask(tr);
+ return tr->tar;
+}
+
static void tar_step(struct timer *tr)
{
switch ((tr->tactl >> 4) & 3) {
- case 0: break;
+ case 0:
+ break;
case 1:
if (tr->tar == tr->ccrs[0] || tr->go_down) {
tr->tar = 0;
tr->tactl |= TAIFG;
tr->go_down = false;
} else {
- tr->tar++;
+ tar_increment(tr);
}
break;
case 2:
- tr->tar++;
- if (!tr->tar)
+ if (tar_increment(tr) == 0)
tr->tactl |= TAIFG;
break;
@@ -436,16 +518,34 @@ static void tar_step(struct timer *tr)
tr->go_down = false;
if (tr->go_down) {
- tr->tar--;
- if (!tr->tar)
+ if (tar_decrement(tr) == 0)
tr->tactl |= TAIFG;
} else {
- tr->tar++;
+ tar_increment(tr);
}
break;
}
}
+static void comparator_step(struct timer *tr, int index)
+{
+ if (tr->timer_type == TIMER_TYPE_A) {
+ if (tr->tar == tr->ccrs[index]) {
+ tr->ctls[index] |= CCIFG;
+ if (tr->ctls[index] & CCI)
+ tr->ctls[index] |= SCCI;
+ else
+ tr->ctls[index] &= ~SCCI;
+ }
+ }
+
+ if (tr->timer_type == TIMER_TYPE_B) {
+ if (tr->tar == tr->ccrs[index]) {
+ tr->ctls[index] |= CCIFG;
+ }
+ }
+}
+
static void timer_step(struct simio_device *dev,
uint16_t status, const int *clocks)
{
@@ -471,16 +571,10 @@ static void timer_step(struct simio_device *dev,
for (i = 0; i < pulse_count; i++) {
int j;
- for (j = 0; j < tr->size; j++)
- if (!(tr->ctls[j] & CAP) && (tr->tar == tr->ccrs[j])) {
- if (tr->ctls[j] & CCI)
- tr->ctls[j] |= SCCI;
- else
- tr->ctls[j] &= ~SCCI;
-
- tr->ctls[j] |= CCIFG;
- }
-
+ for (j = 0; j < tr->size; j++) {
+ if (!(tr->ctls[j] & CAP))
+ comparator_step(tr, j);
+ }
tar_step(tr);
}
}
@@ -488,22 +582,24 @@ static void timer_step(struct simio_device *dev,
const struct simio_class simio_timer = {
.name = "timer",
.help =
-"This peripheral implements the Timer_A module.\n"
-"\n"
-"Constructor arguments: [size]\n"
-" Specify the number of capture/compare registers.\n"
-"\n"
-"Config arguments are:\n"
-" base
\n"
-" Set the peripheral base address.\n"
-" irq0 \n"
-" Set the interrupt vector for CCR0.\n"
-" irq1 \n"
-" Set the interrupt vector for CCR1.\n"
-" iv \n"
-" Set the interrupt vector register address.\n"
-" set <0|1>\n"
-" Set the capture input value on the given channel.\n",
+ "This peripheral implements the Timer_A and Timer_B module.\n"
+ "\n"
+ "Constructor arguments: [size]\n"
+ " Specify the number of capture/compare registers.\n"
+ "\n"
+ "Config arguments are:\n"
+ " base \n"
+ " Set the peripheral base address.\n"
+ " type \n"
+ " Set timer type.\n"
+ " irq0 \n"
+ " Set the interrupt vector for CCR0.\n"
+ " irq1 \n"
+ " Set the interrupt vector for CCR1.\n"
+ " iv \n"
+ " Set the interrupt vector register address.\n"
+ " set <0|1>\n"
+ " Set the capture input value on the given channel.\n",
.create = timer_create,
.destroy = timer_destroy,
diff --git a/simio/tests/test_timer.c b/simio/tests/test_timer.c
index 6d77589..ef85e84 100644
--- a/simio/tests/test_timer.c
+++ b/simio/tests/test_timer.c
@@ -71,7 +71,6 @@ static int config_timer(struct simio_device *dev, const char *param,
#define TxCCTL(index) (0x02 + (index) * 2)
#define TxCCR(index) (0x12 + (index) * 2)
#define TxIV_TxIFG(index) ((index) * 2)
-#define TAIV_TAIFG 0x0a
static uint16_t read_timer(struct simio_device *dev, int offset)
{
@@ -185,6 +184,7 @@ static void test_create_no_option()
// Check default values.
struct timer *tmr = (struct timer *)dev;
assert(tmr->size == 3);
+ assert(tmr->timer_type == TIMER_TYPE_A);
assert(tmr->base_addr == 0x0160);
assert(tmr->iv_addr == 0x012e);
assert(tmr->irq0 == 9);
@@ -226,6 +226,53 @@ static void test_create_with_size_1()
assert(dev == NULL);
}
+static void test_config_type_default()
+{
+ dev = create_timer("");
+
+ // Default timer type is A.
+ struct timer *tmr = (struct timer *)dev;
+ assert(tmr->timer_type == TIMER_TYPE_A);
+}
+
+static void test_config_type_A()
+{
+ dev = create_timer("");
+
+ // Timer can configured as type A.
+ assert(config_timer(dev, "type", "A") == 0);
+ struct timer *tmr = (struct timer *)dev;
+ assert(tmr->timer_type == TIMER_TYPE_A);
+}
+
+static void test_config_type_B()
+{
+ dev = create_timer("");
+
+ // Timer can configured as type B.
+ struct timer *tmr = (struct timer *)dev;
+ assert(config_timer(dev, "type", "B") == 0);
+ assert(tmr->timer_type == TIMER_TYPE_B);
+}
+
+static void test_config_type_bad()
+{
+ dev = create_timer("");
+
+ struct timer *tmr = (struct timer *)dev;
+ // Timer can't configured other than A/B.
+ assert(config_timer(dev, "type", "bad") != 0);
+}
+
+static void test_config_type_empty()
+{
+ dev = create_timer("");
+
+ struct timer *tmr = (struct timer *)dev;
+ // Timer type can't be empty.
+ assert(config_timer(dev, "type", "") != 0);
+}
+
static void test_address_space()
{
dev = create_timer("7");
@@ -319,7 +366,7 @@ static void test_timer_updown_stop()
assert(read_timer(dev, TxR) == 0);
}
-static void test_timer_up()
+static void test_timer_a_up()
{
dev = create_timer("");
@@ -348,7 +395,7 @@ static void test_timer_up()
assert(check_noirq(dev));
}
-static void test_timer_up_change_period()
+static void test_timer_a_up_change_period()
{
dev = create_timer("");
@@ -396,7 +443,7 @@ static void test_timer_up_change_period()
assert_not(read_timer(dev, TxCTL) & TAIFG);
}
-static void test_timer_updown_change_period()
+static void test_timer_a_updown_change_period()
{
dev = create_timer("");
@@ -657,7 +704,7 @@ static void test_timer_capture_by_signal()
assert(read_timer(dev, TxCCR(2)) == 50);
}
-static void test_timer_compare()
+static void test_timer_a_compare()
{
dev = create_timer("");
@@ -722,6 +769,142 @@ static void test_timer_compare()
assert_not(read_timer(dev, TxCCTL(1)) & SCCI);
}
+static void test_timer_b_compare()
+{
+ dev = create_timer("");
+ config_timer(dev, "type", "B");
+
+ /* Continuous mode, ACLK, interrupt enable, clear */
+ write_timer(dev, TxCTL, MC1 | TASSEL0 | TACLR | TAIE);
+ write_timer(dev, TxR, 0xffff);
+ /* Compare mode, enable interrupts */
+ write_timer(dev, TxCCTL(0), CCIE);
+ write_timer(dev, TxCCR(0), 200);
+ write_timer(dev, TxCCTL(1), CCIE);
+ write_timer(dev, TxCCR(1), 100);
+ write_timer(dev, TxCCTL(2), CCIE);
+ write_timer(dev, TxCCR(2), 200);
+
+ // Timer_B overflow interrupt should happen.
+ step_aclk(dev, 1);
+ assert(check_irq1(dev));
+ // Timer_B overflow vector is 0x0e.
+ assert(TBIV_TBIFG == 0x0e);
+ assert(read_iv(dev) == TBIV_TBIFG);
+ assert(check_noirq(dev));
+ assert(read_timer(dev, TxR) == 0);
+
+ step_aclk(dev, 100);
+ assert(check_noirq(dev));
+ // Set CCI of CCTL[1] to 1.
+ config_timer(dev, "set", "1 1");
+ // Timer_B comparator interrupt should happen.
+ step_aclk(dev, 1);
+ assert(check_irq1(dev));
+ assert(read_timer(dev, TxCCTL(1)) & CCIFG);
+ assert(read_iv(dev) == TxIV_TxIFG(1));
+ assert(check_noirq(dev));
+ assert_not(read_timer(dev, TxCCTL(1)) & CCIFG);
+
+ // Timer_B comparator interrupts should happen.
+ step_aclk(dev, 100);
+ assert(check_irq0(dev));
+ assert(read_timer(dev, TxCCTL(0)) & CCIFG);
+ ack_irq0(dev);
+ assert_not(check_noirq(dev));
+ assert_not(read_timer(dev, TxCCTL(0)) & CCIFG);
+ // Lower priority interrupt.
+ assert(check_irq1(dev));
+ assert(read_timer(dev, TxCCTL(2)) & CCIFG);
+ assert(read_iv(dev) == TxIV_TxIFG(2));
+ assert(check_noirq(dev));
+ assert_not(read_timer(dev, TxCCTL(2)) & CCIFG);
+
+ // Set CCI of CCTL[1] to 0.
+ config_timer(dev, "set", "1 0");
+ write_timer(dev, TxCCR(1), 300);
+ step_aclk(dev, 100);
+ // Timer_B comparator interrupt should happen.
+ assert(check_irq1(dev));
+ assert(read_timer(dev, TxCCTL(1)) & CCIFG);
+ assert(read_iv(dev) == TxIV_TxIFG(1));
+ assert(check_noirq(dev));
+ assert_not(read_timer(dev, TxCCTL(1)) & CCIFG);
+}
+
+static void test_timer_capture()
+{
+ dev = create_timer("");
+
+ /* Continuous mode, ACLK, clear */
+ write_timer(dev, TxCTL, MC1 | TASSEL0 | TACLR);
+ /* Capture mode, enable interrupts */
+}
+
+static void test_timer_b_length_8()
+{
+ dev = create_timer("");
+ config_timer(dev, "type", "B");
+
+ /* Continuous 8 bit, SMCLK, interrupt enable, clear */
+ write_timer(dev, TxCTL, MC1 | CNTL1 | CNTL0 | TACLR | TAIE | TASSEL1);
+ write_timer(dev, TxR, 0x00ff);
+ step_smclk(dev, 2);
+
+ // Timer B can configured as 8 bit length.
+ assert(check_irq1(dev));
+ assert(read_iv(dev) == TBIV_TBIFG);
+ assert(read_timer(dev, TxR) == 1);
+}
+
+static void test_timer_b_length_10()
+{
+ dev = create_timer("");
+ config_timer(dev, "type", "B");
+
+ /* Continuous 10 bit, ACLK, interrupt enable, clear */
+ write_timer(dev, TxCTL, MC1 | CNTL1 | TACLR | TAIE | TASSEL0);
+ write_timer(dev, TxR, 0x03ff);
+ step_aclk(dev, 3);
+
+ // Timer B can configured as 10 bit length.
+ assert(check_irq1(dev));
+ assert(read_iv(dev) == TBIV_TBIFG);
+ assert(read_timer(dev, TxR) == 2);
+}
+
+static void test_timer_b_length_12()
+{
+ dev = create_timer("");
+ config_timer(dev, "type", "B");
+
+ /* Continuous 12 bit, SMCLK, interrupt enable, clear */
+ write_timer(dev, TxCTL, MC1 | CNTL0 | TACLR | TAIE | TASSEL1);
+ write_timer(dev, TxR, 0x0fff);
+ step_smclk(dev, 4);
+
+ // Timer B can configured as 12 bit length.
+ assert(check_irq1(dev));
+ assert(read_iv(dev) == TBIV_TBIFG);
+ assert(read_timer(dev, TxR) == 3);
+}
+
+static void test_timer_b_length_16()
+{
+ dev = create_timer("");
+ config_timer(dev, "type", "B");
+
+ /* Continuous 16 bit, SMCLK, interrupt enable, clear */
+ write_timer(dev, TxCTL, MC1 | TACLR | TAIE | TASSEL1);
+ write_timer(dev, TxR, 0xffff);
+ step_smclk(dev, 5);
+
+ // Timer B can configured as 16 bit length.
+ assert(check_irq1(dev));
+ assert(read_iv(dev) == TBIV_TBIFG);
+ assert(read_timer(dev, TxR) == 4);
+}
+
/*
* Test runner.
@@ -748,16 +931,26 @@ int main(int argc, char **argv)
RUN_TEST(test_create_with_size_2);
RUN_TEST(test_create_with_size_8);
RUN_TEST(test_create_with_size_1);
+ RUN_TEST(test_config_type_default);
+ RUN_TEST(test_config_type_A);
+ RUN_TEST(test_config_type_B);
+ RUN_TEST(test_config_type_bad);
+ RUN_TEST(test_config_type_empty);
RUN_TEST(test_address_space);
RUN_TEST(test_timer_continuous);
RUN_TEST(test_timer_stop);
RUN_TEST(test_timer_up_stop);
RUN_TEST(test_timer_updown_stop);
- RUN_TEST(test_timer_up);
- RUN_TEST(test_timer_up_change_period);
- RUN_TEST(test_timer_updown_change_period);
+ RUN_TEST(test_timer_a_up);
+ RUN_TEST(test_timer_a_up_change_period);
+ RUN_TEST(test_timer_a_updown_change_period);
RUN_TEST(test_timer_divider);
RUN_TEST(test_timer_capture_by_software);
RUN_TEST(test_timer_capture_by_signal);
- RUN_TEST(test_timer_compare);
+ RUN_TEST(test_timer_a_compare);
+ RUN_TEST(test_timer_b_compare);
+ RUN_TEST(test_timer_b_length_8);
+ RUN_TEST(test_timer_b_length_10);
+ RUN_TEST(test_timer_b_length_12);
+ RUN_TEST(test_timer_b_length_16);
}