summaryrefslogtreecommitdiffstats
path: root/Src/osmoconbb/src/target/firmware/comm
diff options
context:
space:
mode:
Diffstat (limited to 'Src/osmoconbb/src/target/firmware/comm')
-rw-r--r--Src/osmoconbb/src/target/firmware/comm/Makefile5
-rw-r--r--Src/osmoconbb/src/target/firmware/comm/msgb.c78
-rw-r--r--Src/osmoconbb/src/target/firmware/comm/sercomm.c297
-rw-r--r--Src/osmoconbb/src/target/firmware/comm/sercomm_cons.c140
-rw-r--r--Src/osmoconbb/src/target/firmware/comm/timer.c217
5 files changed, 737 insertions, 0 deletions
diff --git a/Src/osmoconbb/src/target/firmware/comm/Makefile b/Src/osmoconbb/src/target/firmware/comm/Makefile
new file mode 100644
index 0000000..25fbb98
--- /dev/null
+++ b/Src/osmoconbb/src/target/firmware/comm/Makefile
@@ -0,0 +1,5 @@
+
+LIBRARIES+=comm
+comm_DIR=comm
+comm_SRCS=msgb.c sercomm.c sercomm_cons.c timer.c
+
diff --git a/Src/osmoconbb/src/target/firmware/comm/msgb.c b/Src/osmoconbb/src/target/firmware/comm/msgb.c
new file mode 100644
index 0000000..4dbc119
--- /dev/null
+++ b/Src/osmoconbb/src/target/firmware/comm/msgb.c
@@ -0,0 +1,78 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * 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
+ * (at your option) 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <asm/system.h>
+
+#include <debug.h>
+#include <delay.h>
+
+#include <osmocom/core/msgb.h>
+
+#define NO_TALLOC
+
+void *tall_msgb_ctx;
+
+#ifdef NO_TALLOC
+/* This is a poor mans static allocator for msgb objects */
+#define MSGB_DATA_SIZE 256+4
+#define MSGB_NUM 32
+struct supermsg {
+ uint8_t allocated;
+ struct msgb msg;
+ uint8_t buf[MSGB_DATA_SIZE];
+};
+static struct supermsg msgs[MSGB_NUM];
+void *_talloc_zero(void *ctx, unsigned int size, const char *name)
+{
+ unsigned long flags;
+ unsigned int i;
+
+ local_firq_save(flags);
+
+ if (size > sizeof(struct msgb) + MSGB_DATA_SIZE)
+ goto panic;
+
+ for (i = 0; i < ARRAY_SIZE(msgs); i++) {
+ if (!msgs[i].allocated) {
+ msgs[i].allocated = 1;
+ memset(&msgs[i].msg, 0, sizeof(&msgs[i].msg));
+ memset(&msgs[i].buf, 0, sizeof(&msgs[i].buf));
+ local_irq_restore(flags);
+ return &msgs[i].msg;
+ }
+ }
+
+panic:
+ cons_puts("unable to allocate msgb\n");
+ while (1);
+
+ return NULL; /* not reached */
+}
+void talloc_free(void *msg)
+{
+ struct supermsg *smsg = container_of(msg, struct supermsg, msg);
+ /* no locking required, since this is atomic */
+ smsg->allocated = 0;
+}
+#endif
diff --git a/Src/osmoconbb/src/target/firmware/comm/sercomm.c b/Src/osmoconbb/src/target/firmware/comm/sercomm.c
new file mode 100644
index 0000000..ddc852c
--- /dev/null
+++ b/Src/osmoconbb/src/target/firmware/comm/sercomm.c
@@ -0,0 +1,297 @@
+/* Serial communications layer, based on HDLC */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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
+ * (at your option) 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+
+#ifdef HOST_BUILD
+
+# define SERCOMM_RX_MSG_SIZE 2048
+# ifndef ARRAY_SIZE
+# define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+# endif
+# include <sercomm.h>
+
+static inline void sercomm_lock(unsigned long __attribute__((unused)) *flags) {}
+static inline void sercomm_unlock(unsigned long __attribute__((unused)) *flags) {}
+
+#else
+
+# define SERCOMM_RX_MSG_SIZE 256
+# include <debug.h>
+# include <osmocom/core/linuxlist.h>
+# include <asm/system.h>
+
+static inline void sercomm_lock(unsigned long *flags)
+{
+ local_firq_save(*flags);
+}
+
+static inline void sercomm_unlock(unsigned long *flags)
+{
+ local_irq_restore(*flags);
+}
+
+# include <comm/sercomm.h>
+# include <uart.h>
+
+#endif
+
+
+enum rx_state {
+ RX_ST_WAIT_START,
+ RX_ST_ADDR,
+ RX_ST_CTRL,
+ RX_ST_DATA,
+ RX_ST_ESCAPE,
+};
+
+static struct {
+ int initialized;
+
+ /* transmit side */
+ struct {
+ struct llist_head dlci_queues[_SC_DLCI_MAX];
+ struct msgb *msg;
+ enum rx_state state;
+ uint8_t *next_char;
+ } tx;
+
+ /* receive side */
+ struct {
+ dlci_cb_t dlci_handler[_SC_DLCI_MAX];
+ struct msgb *msg;
+ enum rx_state state;
+ uint8_t dlci;
+ uint8_t ctrl;
+ } rx;
+
+} sercomm;
+
+void sercomm_init(void)
+{
+ unsigned int i;
+ for (i = 0; i < ARRAY_SIZE(sercomm.tx.dlci_queues); i++)
+ INIT_LLIST_HEAD(&sercomm.tx.dlci_queues[i]);
+
+ sercomm.rx.msg = NULL;
+ sercomm.initialized = 1;
+
+ /* set up the echo dlci */
+ sercomm_register_rx_cb(SC_DLCI_ECHO, &sercomm_sendmsg);
+}
+
+int sercomm_initialized(void)
+{
+ return sercomm.initialized;
+}
+
+/* user interface for transmitting messages for a given DLCI */
+void sercomm_sendmsg(uint8_t dlci, struct msgb *msg)
+{
+ unsigned long flags;
+ uint8_t *hdr;
+
+ /* prepend address + control octet */
+ hdr = msgb_push(msg, 2);
+ hdr[0] = dlci;
+ hdr[1] = HDLC_C_UI;
+
+ /* This functiion can be called from any context: FIQ, IRQ
+ * and supervisor context. Proper locking is important! */
+ sercomm_lock(&flags);
+ msgb_enqueue(&sercomm.tx.dlci_queues[dlci], msg);
+ sercomm_unlock(&flags);
+
+#ifndef HOST_BUILD
+ /* tell UART that we have something to send */
+ uart_irq_enable(SERCOMM_UART_NR, UART_IRQ_TX_EMPTY, 1);
+#endif
+}
+
+/* how deep is the Tx queue for a given DLCI */
+unsigned int sercomm_tx_queue_depth(uint8_t dlci)
+{
+ struct llist_head *le;
+ unsigned int num = 0;
+
+ llist_for_each(le, &sercomm.tx.dlci_queues[dlci]) {
+ num++;
+ }
+
+ return num;
+}
+
+/* fetch one octet of to-be-transmitted serial data */
+int sercomm_drv_pull(uint8_t *ch)
+{
+ unsigned long flags;
+
+ /* we may be called from interrupt context, but we stiff need to lock
+ * because sercomm could be accessed from a FIQ context ... */
+
+ sercomm_lock(&flags);
+
+ if (!sercomm.tx.msg) {
+ unsigned int i;
+ /* dequeue a new message from the queues */
+ for (i = 0; i < ARRAY_SIZE(sercomm.tx.dlci_queues); i++) {
+ sercomm.tx.msg = msgb_dequeue(&sercomm.tx.dlci_queues[i]);
+ if (sercomm.tx.msg)
+ break;
+ }
+ if (sercomm.tx.msg) {
+ /* start of a new message, send start flag octet */
+ *ch = HDLC_FLAG;
+ sercomm.tx.next_char = sercomm.tx.msg->data;
+ sercomm_unlock(&flags);
+ return 1;
+ } else {
+ /* no more data avilable */
+ sercomm_unlock(&flags);
+ return 0;
+ }
+ }
+
+ if (sercomm.tx.state == RX_ST_ESCAPE) {
+ /* we've already transmitted the ESCAPE octet,
+ * we now need to transmit the escaped data */
+ *ch = *sercomm.tx.next_char++;
+ sercomm.tx.state = RX_ST_DATA;
+ } else if (sercomm.tx.next_char >= sercomm.tx.msg->tail) {
+ /* last character has already been transmitted,
+ * send end-of-message octet */
+ *ch = HDLC_FLAG;
+ /* we've reached the end of the message buffer */
+ msgb_free(sercomm.tx.msg);
+ sercomm.tx.msg = NULL;
+ sercomm.tx.next_char = NULL;
+ /* escaping for the two control octets */
+ } else if (*sercomm.tx.next_char == HDLC_FLAG ||
+ *sercomm.tx.next_char == HDLC_ESCAPE ||
+ *sercomm.tx.next_char == 0x00) {
+ /* send an escape octet */
+ *ch = HDLC_ESCAPE;
+ /* invert bit 5 of the next octet to be sent */
+ *sercomm.tx.next_char ^= (1 << 5);
+ sercomm.tx.state = RX_ST_ESCAPE;
+ } else {
+ /* standard case, simply send next octet */
+ *ch = *sercomm.tx.next_char++;
+ }
+
+ sercomm_unlock(&flags);
+ return 1;
+}
+
+/* register a handler for a given DLCI */
+int sercomm_register_rx_cb(uint8_t dlci, dlci_cb_t cb)
+{
+ if (dlci >= ARRAY_SIZE(sercomm.rx.dlci_handler))
+ return -EINVAL;
+
+ if (sercomm.rx.dlci_handler[dlci])
+ return -EBUSY;
+
+ sercomm.rx.dlci_handler[dlci] = cb;
+ return 0;
+}
+
+/* dispatch an incoming message once it is completely received */
+static void dispatch_rx_msg(uint8_t dlci, struct msgb *msg)
+{
+ if (dlci >= ARRAY_SIZE(sercomm.rx.dlci_handler) ||
+ !sercomm.rx.dlci_handler[dlci]) {
+ msgb_free(msg);
+ return;
+ }
+ sercomm.rx.dlci_handler[dlci](dlci, msg);
+}
+
+/* the driver has received one byte, pass it into sercomm layer */
+int sercomm_drv_rx_char(uint8_t ch)
+{
+ uint8_t *ptr;
+
+ /* we are always called from interrupt context in this function,
+ * which means that any data structures we use need to be for
+ * our exclusive access */
+ if (!sercomm.rx.msg)
+ sercomm.rx.msg = sercomm_alloc_msgb(SERCOMM_RX_MSG_SIZE);
+
+ if (msgb_tailroom(sercomm.rx.msg) == 0) {
+ //cons_puts("sercomm_drv_rx_char() overflow!\n");
+ msgb_free(sercomm.rx.msg);
+ sercomm.rx.msg = sercomm_alloc_msgb(SERCOMM_RX_MSG_SIZE);
+ sercomm.rx.state = RX_ST_WAIT_START;
+ return 0;
+ }
+
+ switch (sercomm.rx.state) {
+ case RX_ST_WAIT_START:
+ if (ch != HDLC_FLAG)
+ break;
+ sercomm.rx.state = RX_ST_ADDR;
+ break;
+ case RX_ST_ADDR:
+ sercomm.rx.dlci = ch;
+ sercomm.rx.state = RX_ST_CTRL;
+ break;
+ case RX_ST_CTRL:
+ sercomm.rx.ctrl = ch;
+ sercomm.rx.state = RX_ST_DATA;
+ break;
+ case RX_ST_DATA:
+ if (ch == HDLC_ESCAPE) {
+ /* drop the escape octet, but change state */
+ sercomm.rx.state = RX_ST_ESCAPE;
+ break;
+ } else if (ch == HDLC_FLAG) {
+ /* message is finished */
+ dispatch_rx_msg(sercomm.rx.dlci, sercomm.rx.msg);
+ /* allocate new buffer */
+ sercomm.rx.msg = NULL;
+ /* start all over again */
+ sercomm.rx.state = RX_ST_WAIT_START;
+
+ /* do not add the control char */
+ break;
+ }
+ /* default case: store the octet */
+ ptr = msgb_put(sercomm.rx.msg, 1);
+ *ptr = ch;
+ break;
+ case RX_ST_ESCAPE:
+ /* store bif-5-inverted octet in buffer */
+ ch ^= (1 << 5);
+ ptr = msgb_put(sercomm.rx.msg, 1);
+ *ptr = ch;
+ /* transition back to normal DATA state */
+ sercomm.rx.state = RX_ST_DATA;
+ break;
+ }
+
+ return 1;
+}
diff --git a/Src/osmoconbb/src/target/firmware/comm/sercomm_cons.c b/Src/osmoconbb/src/target/firmware/comm/sercomm_cons.c
new file mode 100644
index 0000000..a0dca40
--- /dev/null
+++ b/Src/osmoconbb/src/target/firmware/comm/sercomm_cons.c
@@ -0,0 +1,140 @@
+/* Serial console layer, layered on top of sercomm HDLC */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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
+ * (at your option) 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <asm/system.h>
+
+#include <uart.h>
+
+#include <console.h>
+#include <osmocom/core/msgb.h>
+#include <comm/sercomm.h>
+#include <comm/sercomm_cons.h>
+
+static struct {
+ struct msgb *cur_msg;
+} scons;
+
+static void raw_puts(const char *s)
+{
+ int i = strlen(s);
+ while (i--)
+ uart_putchar_wait(SERCOMM_UART_NR, *s++);
+}
+
+#ifdef DEBUG
+#define raw_putd(x) raw_puts(x)
+#else
+#define raw_putd(x)
+#endif
+
+int sercomm_puts(const char *s)
+{
+ unsigned long flags;
+ const int len = strlen(s);
+ unsigned int bytes_left = len;
+
+ if (!sercomm_initialized()) {
+ raw_putd("sercomm not initialized: ");
+ raw_puts(s);
+ return len - 1;
+ }
+
+ /* This function is called from any context: Supervisor, IRQ, FIQ, ...
+ * as such, we need to ensure re-entrant calls are either supported or
+ * avoided. */
+ local_irq_save(flags);
+ local_fiq_disable();
+
+ while (bytes_left > 0) {
+ unsigned int write_num, space_left, flush;
+ uint8_t *data;
+
+ if (!scons.cur_msg)
+ scons.cur_msg = sercomm_alloc_msgb(SERCOMM_CONS_ALLOC);
+
+ if (!scons.cur_msg) {
+ raw_putd("cannot allocate sercomm msgb: ");
+ raw_puts(s);
+ return -ENOMEM;
+ }
+
+ /* space left in the current msgb */
+ space_left = msgb_tailroom(scons.cur_msg);
+
+ if (space_left <= bytes_left) {
+ write_num = space_left;
+ /* flush buffer when it is full */
+ flush = 1;
+ } else {
+ write_num = bytes_left;
+ flush = 0;
+ }
+
+ /* obtain pointer where to copy the data */
+ data = msgb_put(scons.cur_msg, write_num);
+
+ /* copy data while looking for \n line termination */
+ {
+ unsigned int i;
+ for (i = 0; i < write_num; i++) {
+ /* flush buffer at end of line, but skip
+ * flushing if we have a backlog in order to
+ * increase efficiency of msgb filling */
+ if (*s == '\n' &&
+ sercomm_tx_queue_depth(SC_DLCI_CONSOLE) < 4)
+ flush = 1;
+ *data++ = *s++;
+ }
+ }
+ bytes_left -= write_num;
+
+ if (flush) {
+ sercomm_sendmsg(SC_DLCI_CONSOLE, scons.cur_msg);
+ /* reset scons.cur_msg pointer to ensure we allocate
+ * a new one next round */
+ scons.cur_msg = NULL;
+ }
+ }
+
+ local_irq_restore(flags);
+
+ return len - 1;
+}
+
+int sercomm_putchar(int c)
+{
+ char s[2];
+ int rc;
+
+ s[0] = c & 0xff;
+ s[1] = '\0';
+
+ rc = sercomm_puts(s);
+ if (rc < 0)
+ return rc;
+
+ return c;
+}
diff --git a/Src/osmoconbb/src/target/firmware/comm/timer.c b/Src/osmoconbb/src/target/firmware/comm/timer.c
new file mode 100644
index 0000000..6a649ae
--- /dev/null
+++ b/Src/osmoconbb/src/target/firmware/comm/timer.c
@@ -0,0 +1,217 @@
+/* (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * 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
+ * (at your option) 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <debug.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <comm/timer.h>
+
+#include <calypso/timer.h>
+#include <calypso/irq.h>
+
+#include <keypad.h>
+
+static LLIST_HEAD(timer_list);
+
+unsigned long volatile jiffies;
+
+#define TIMER_HZ 100
+
+#define time_after(a,b) \
+ (typecheck(unsigned long, a) && \
+ typecheck(unsigned long, b) && \
+ ((long)(b) - (long)(a) < 0))
+#define time_before(a,b) time_after(b,a)
+
+void add_timer(struct osmo_timer_list *timer)
+{
+ struct osmo_timer_list *list_timer;
+
+ /* TODO: Optimize and remember the closest item... */
+ timer->active = 1;
+
+ /* this might be called from within update_timers */
+ llist_for_each_entry(list_timer, &timer_list, entry)
+ if (timer == list_timer)
+ return;
+
+ timer->in_list = 1;
+ llist_add(&timer->entry, &timer_list);
+}
+
+void schedule_timer(struct osmo_timer_list *timer, int milliseconds)
+{
+ timer->expires = jiffies + ((milliseconds * TIMER_HZ) / 1000);
+ add_timer(timer);
+}
+
+void del_timer(struct osmo_timer_list *timer)
+{
+ if (timer->in_list) {
+ timer->active = 0;
+ timer->in_list = 0;
+ llist_del(&timer->entry);
+ }
+}
+
+int timer_pending(struct osmo_timer_list *timer)
+{
+ return timer->active;
+}
+
+#if 0
+/*
+ * if we have a nearest time return the delta between the current
+ * time and the time of the nearest timer.
+ * If the nearest timer timed out return NULL and then we will
+ * dispatch everything after the select
+ */
+struct timeval *nearest_timer()
+{
+ struct timeval current_time;
+
+ if (s_nearest_time.tv_sec == 0 && s_nearest_time.tv_usec == 0)
+ return NULL;
+
+ if (gettimeofday(&current_time, NULL) == -1)
+ return NULL;
+
+ unsigned long long nearestTime = s_nearest_time.tv_sec * MICRO_SECONDS + s_nearest_time.tv_usec;
+ unsigned long long currentTime = current_time.tv_sec * MICRO_SECONDS + current_time.tv_usec;
+
+ if (nearestTime < currentTime) {
+ s_select_time.tv_sec = 0;
+ s_select_time.tv_usec = 0;
+ } else {
+ s_select_time.tv_sec = (nearestTime - currentTime) / MICRO_SECONDS;
+ s_select_time.tv_usec = (nearestTime - currentTime) % MICRO_SECONDS;
+ }
+
+ return &s_select_time;
+}
+
+/*
+ * Find the nearest time and update s_nearest_time
+ */
+void prepare_timers()
+{
+ struct osmo_timer_list *timer, *nearest_timer = NULL;
+ llist_for_each_entry(timer, &timer_list, entry) {
+ if (!nearest_timer || time_before(timer->expires, nearest_timer->expires)) {
+ nearest_timer = timer;
+ }
+ }
+
+ if (nearest_timer) {
+ s_nearest_time = nearest_timer->timeout;
+ } else {
+ memset(&s_nearest_time, 0, sizeof(struct timeval));
+ }
+}
+#endif
+
+/*
+ * fire all timers... and remove them
+ */
+int update_timers(void)
+{
+ struct osmo_timer_list *timer, *tmp;
+ int work = 0;
+
+ /*
+ * The callbacks might mess with our list and in this case
+ * even llist_for_each_entry_safe is not safe to use. To allow
+ * del_timer, add_timer, schedule_timer to be called from within
+ * the callback we jump through some loops.
+ *
+ * First we set the handled flag of each active timer to zero,
+ * then we iterate over the list and execute the callbacks. As the
+ * list might have been changed (specially the next) from within
+ * the callback we have to start over again. Once every callback
+ * is dispatched we will remove the non-active from the list.
+ *
+ * TODO: If this is a performance issue we can poison a global
+ * variable in add_timer and del_timer and only then restart.
+ */
+ llist_for_each_entry(timer, &timer_list, entry) {
+ timer->handled = 0;
+ }
+
+restart:
+ llist_for_each_entry(timer, &timer_list, entry) {
+ if (!timer->handled && time_before(timer->expires, jiffies)) {
+ timer->handled = 1;
+ timer->active = 0;
+ (*timer->cb)(timer->data);
+ work = 1;
+ goto restart;
+ }
+ }
+
+ llist_for_each_entry_safe(timer, tmp, &timer_list, entry) {
+ timer->handled = 0;
+ if (!timer->active) {
+ del_timer(timer);
+ }
+ }
+
+ return work;
+}
+
+int timer_check(void)
+{
+ struct osmo_timer_list *timer;
+ int i = 0;
+
+ llist_for_each_entry(timer, &timer_list, entry) {
+ i++;
+ }
+ return i;
+}
+
+static void timer_irq(enum irq_nr irq)
+{
+ /* we only increment jiffies here. FIXME: does this need to be atomic? */
+ jiffies++;
+
+ keypad_poll();
+}
+
+void timer_init(void)
+{
+ /* configure TIMER2 for our purpose */
+ hwtimer_enable(2, 1);
+ /* The timer runs at 13MHz / 32, i.e. 406.25kHz */
+#if (TIMER_HZ == 100)
+ hwtimer_load(2, 4062);
+ hwtimer_config(2, 0, 1);
+#elif (TIMER_HZ == 10)
+ /* prescaler 4, 1015 ticks until expiry */
+ hwtimer_load(2, 1015);
+ hwtimer_config(2, 4, 1);
+#endif
+ hwtimer_enable(2, 1);
+
+ /* register interrupt handler with default priority, EDGE triggered */
+ irq_register_handler(IRQ_TIMER2, &timer_irq);
+ irq_config(IRQ_TIMER2, 0, 1, -1);
+ irq_enable(IRQ_TIMER2);
+}