osmocom-bb/src/target/firmware/apps/rssi/main.c

775 lines
15 KiB
C

/* Cell Monitor of Free Software for Calypso Phone */
/* (C) 2012 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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 <errno.h>
#include <debug.h>
#include <memory.h>
#include <delay.h>
#include <byteorder.h>
#include <rffe.h>
#include <keypad.h>
#include <board.h>
#include <abb/twl3025.h>
#include <rf/trf6151.h>
#include <calypso/clock.h>
#include <calypso/tpu.h>
#include <calypso/tsp.h>
#include <calypso/dsp.h>
#include <calypso/irq.h>
#include <calypso/misc.h>
#include <calypso/buzzer.h>
#include <comm/sercomm.h>
#include <comm/timer.h>
#include <fb/framebuffer.h>
#include <layer1/sync.h>
#include <layer1/async.h>
#include <layer1/l23_api.h>
enum key_codes key_code = KEY_INV;
int key_pressed = 0;
enum key_codes key_pressed_code;
unsigned long key_pressed_when;
unsigned int key_pressed_delay;
enum mode {
MODE_MAIN,
MODE_SPECTRUM,
MODE_ARFCN,
} mode = MODE_MAIN;
enum mode last_mode; /* where to return after entering ARFCN */
static uint16_t arfcn = 0;
int pcs = 0;
int uplink = 0;
int max = 0;
uint8_t power, max_power;
char input[5];
int cursor;
static struct band {
int min, max, prev, next, freq_ul, freq_dl;
} bands[] = {
{ 128, 251, 124, 512, 8242, 8692 }, /* GSM 850 */
{ 955, 124, 885, 128, 8762, 9212 }, /* P,E,R GSM */
{ 512, 885, 251, 955, 17102, 18052 }, /* DCS 1800 */
{ 0, 0, 0, 0, 0, 0},
};
struct band *band;
#define PCS_MIN 512
#define PCS_MAX 810
#define DCS_MIN 512
#define DCS_MAX 885
#define PCS_UL 18502
#define PCS_DL 19302
enum pm_mode {
PM_IDLE,
PM_SENT,
PM_RANGE_SENT,
PM_RANGE_RESULT,
PM_RESULT,
} pm_mode = PM_IDLE;
#define NUM_PM_DL 2
#define NUM_PM_UL 10
int pm_meas[NUM_PM_UL];
int pm_count = 0;
int pm_max = 2;
uint8_t pm_spectrum[1024];
int pm_scale = 1; /* scale measured power level */
#define TONE_JIFFIES 4
int tone = 0;
unsigned long tone_time;
int tone_on = 0;
/* UI */
static void refresh_display(void)
{
char text[16];
fb_clear();
/* header */
fb_setbg(FB_COLOR_WHITE);
if (mode != MODE_SPECTRUM) {
fb_setfg(FB_COLOR_BLUE);
fb_setfont(FB_FONT_HELVR08);
fb_gotoxy(0,6);
fb_putstr("Osmocom Monitor Tool",-1);
fb_gotoxy(0,10);
fb_setfg(FB_COLOR_BLACK);
fb_boxto(framebuffer->width-1,10);
}
fb_setfg(FB_COLOR_BLACK);
fb_setfont(FB_FONT_C64);
/* ARFCN */
if (mode == MODE_MAIN || mode == MODE_ARFCN) {
fb_gotoxy(0,20);
if (mode == MODE_ARFCN)
sprintf(text, "ARFCN %s", input);
else if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX)
sprintf(text, "ARFCN %dPCS", arfcn);
else if (arfcn >= DCS_MIN && arfcn <= DCS_MAX)
sprintf(text, "ARFCN %dDCS", arfcn);
else
sprintf(text, "ARFCN %d", arfcn);
fb_putstr(text,framebuffer->width);
}
/* cursor */
if (mode == MODE_ARFCN) {
fb_setfg(FB_COLOR_WHITE);
fb_setbg(FB_COLOR_BLUE);
fb_putstr(" ", framebuffer->width);
fb_setfg(FB_COLOR_BLACK);
fb_setbg(FB_COLOR_WHITE);
}
/* Frequency / power */
if (mode == MODE_MAIN) {
int f;
if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) {
if (uplink)
f = PCS_UL;
else
f = PCS_DL;
} else if (uplink)
f = band->freq_ul;
else
f = band->freq_dl;
f += ((arfcn - band->min) & 1023) << 1;
fb_gotoxy(0,30);
sprintf(text, "Freq. %d.%d", f / 10, f % 10);
fb_putstr(text,framebuffer->width);
fb_gotoxy(0,40);
sprintf(text, "Power %d", ((max) ? max_power : power) - 110);
fb_putstr(text,framebuffer->width);
if (max) {
fb_setfont(FB_FONT_HELVR08);
fb_gotoxy(80,39);
fb_putstr("max",framebuffer->width);
fb_setfont(FB_FONT_C64);
}
fb_setbg(FB_COLOR_BLACK);
fb_gotoxy(0,45);
fb_boxto(framebuffer->width * power / 64, 50);
if (max) {
fb_gotoxy(framebuffer->width * max_power / 64 ,45);
fb_boxto(framebuffer->width * max_power / 64, 50);
}
fb_setbg(FB_COLOR_WHITE);
}
/* spectrum */
if (mode == MODE_SPECTRUM) {
int i;
uint16_t a, e, p;
fb_gotoxy(0,8);
if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX)
sprintf(text, "%4dP", arfcn);
else if (arfcn >= DCS_MIN && arfcn <= DCS_MAX)
sprintf(text, "%4dD", arfcn);
else
sprintf(text, "%4d ", arfcn);
sprintf(text + 5, " %d", pm_spectrum[arfcn & 1023] - 110);
fb_putstr(text,framebuffer->width);
if (max) {
fb_setfont(FB_FONT_HELVR08);
fb_gotoxy(80,15);
fb_putstr("max",framebuffer->width);
fb_setfont(FB_FONT_C64);
}
if (pm_scale != 1) {
fb_setfont(FB_FONT_HELVR08);
fb_gotoxy(1,15);
sprintf(text, "x%d", pm_scale);
fb_putstr(text,framebuffer->width);
fb_setfont(FB_FONT_C64);
}
if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) {
a = PCS_MIN;
e = PCS_MAX;
} else {
a = band->min;
e = band->max;
}
for (i = 0; i < framebuffer->width - 1; i++) {
p = (arfcn + i - (framebuffer->width >> 1)) & 1023;
if ((((p - a) & 1023) & 512))
continue;
if ((((e - p) & 1023) & 512))
continue;
p = (pm_spectrum[p] * pm_scale * 40 / 64);
if (p > 40)
p = 40;
fb_gotoxy(i, 50 - p);
fb_boxto(i, 50);
}
i = framebuffer->width >> 1;
fb_gotoxy(i, 0);
fb_boxto(i, 4);
fb_gotoxy(i, 50);
fb_boxto(i, 54);
}
/* footer */
fb_gotoxy(0,55);
fb_boxto(framebuffer->width-1,55);
fb_gotoxy(0,64);
if (mode == MODE_ARFCN)
sprintf(text, "%s %s", (cursor) ? "del " : "back",
(cursor) ? "enter" : " ");
else
sprintf(text, "%s %s", (pcs) ? "PCS" : "DCS",
(uplink) ? "UL" : "DL");
fb_putstr(text,framebuffer->width);
fb_setfont(FB_FONT_HELVR08);
fb_gotoxy(0,63);
sprintf(text, "%d", tone / 25);
fb_putstr(text,-1);
fb_flush();
}
static void exit_arfcn(void)
{
mode = last_mode;
refresh_display();
}
static void enter_arfcn(enum key_codes code)
{
/* enter mode */
if (mode != MODE_ARFCN) {
last_mode = mode;
mode = MODE_ARFCN;
input[0] = code - KEY_0 + '0';
input[1] = '\0';
cursor = 1;
refresh_display();
return;
}
if (code == KEY_LEFT_SB) {
/* back */
if (cursor == 0) {
exit_arfcn();
return;
}
/* delete */
cursor--;
input[cursor] = '\0';
refresh_display();
return;
}
if (code == KEY_RIGHT_SB) {
int check = 0;
int i;
struct band *temp = NULL;
/* nothing entered */
if (cursor == 0) {
return;
}
for (i = 0; i < cursor; i++)
check = (check << 3) + (check << 1) + input[i] - '0';
/* check */
for (i = 0; bands[i].max; i++) {
temp = &bands[i];
if (temp->min < temp->max) {
if (check >= temp->min && check <= temp->max)
break;
} else {
if (check >= temp->min || check <= temp->max)
break;
}
}
if (!bands[i].max)
return;
if (check > 1023)
return;
arfcn = check;
band = temp;
mode = last_mode;
refresh_display();
return;
}
if (cursor == 4)
return;
input[cursor] = code - KEY_0 + '0';
cursor++;
input[cursor] = '\0';
refresh_display();
}
static int inc_dec_arfcn(int inc)
{
int i;
/* select current band */
for (i = 0; bands[i].max; i++) {
band = &bands[i];
if (band->min < band->max) {
if (arfcn >= band->min && arfcn <= band->max)
break;
} else {
if (arfcn >= band->min || arfcn <= band->max)
break;
}
}
if (!bands[i].max)
return -EINVAL;
if (inc) {
if (arfcn == band->max)
arfcn = band->next;
else if (arfcn == 1023)
arfcn = 0;
else
arfcn++;
} else {
if (arfcn == band->min)
arfcn = band->prev;
else if (arfcn == 0)
arfcn = 1023;
else
arfcn--;
}
/* select next band */
for (i = 0; bands[i].max; i++) {
band = &bands[i];
if (band->min < band->max) {
if (arfcn >= band->min && arfcn <= band->max)
break;
} else {
if (arfcn >= band->min || arfcn <= band->max)
break;
}
}
if (!bands[i].max)
return -EINVAL;
refresh_display();
return 0;
}
static void toggle_dcs_pcs(void)
{
pcs = !pcs;
refresh_display();
}
static void toggle_up_down(void)
{
uplink = !uplink;
refresh_display();
}
static void toggle_spectrum(void)
{
if (mode == MODE_MAIN) {
mode = MODE_SPECTRUM;
pm_mode = PM_IDLE;
} else if (mode == MODE_SPECTRUM) {
mode = MODE_MAIN;
pm_mode = PM_IDLE;
}
l1s_reset();
l1s_reset_hw();
pm_count = 0;
refresh_display();
}
static void tone_inc_dec(int inc)
{
if (inc) {
if (tone + 25 <= 255)
tone += 25;
} else {
if (tone - 25 >= 0)
tone -= 25;
}
refresh_display();
}
static void hold_max(void)
{
max = !max;
max_power = power;
refresh_display();
}
static int inc_dec_spectrum(int inc)
{
if (inc) {
pm_scale <<= 1;
if (pm_scale > 8)
pm_scale = 8;
} else {
pm_scale >>= 1;
if (pm_scale < 1)
pm_scale = 1;
}
refresh_display();
return 0;
}
static void handle_key_code()
{
/* key repeat */
if (key_pressed) {
unsigned long elapsed = jiffies - key_pressed_when;
if (elapsed > key_pressed_delay) {
key_pressed_when = jiffies;
key_pressed_delay = 10;
/* only repeat these keys */
if (key_pressed_code == KEY_LEFT
|| key_pressed_code == KEY_RIGHT)
key_code = key_pressed_code;
}
}
if (key_code == KEY_INV)
return;
/* do later, do not disturb tone */
if (tone_on)
return;
switch (key_code) {
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9:
if (mode == MODE_MAIN || mode == MODE_SPECTRUM || mode == MODE_ARFCN)
enter_arfcn(key_code);
break;
case KEY_UP:
if (mode == MODE_MAIN)
tone_inc_dec(1);
else if (mode == MODE_SPECTRUM)
inc_dec_spectrum(1);
break;
case KEY_DOWN:
if (mode == MODE_MAIN)
tone_inc_dec(0);
else if (mode == MODE_SPECTRUM)
inc_dec_spectrum(0);
break;
case KEY_RIGHT:
if (mode == MODE_MAIN || mode == MODE_SPECTRUM)
inc_dec_arfcn(1);
break;
case KEY_LEFT:
if (mode == MODE_MAIN || mode == MODE_SPECTRUM)
inc_dec_arfcn(0);
break;
case KEY_LEFT_SB:
if (mode == MODE_MAIN || mode == MODE_SPECTRUM)
toggle_dcs_pcs();
else if (mode == MODE_ARFCN)
enter_arfcn(key_code);
break;
case KEY_RIGHT_SB:
if (mode == MODE_MAIN || mode == MODE_SPECTRUM)
toggle_up_down();
else if (mode == MODE_ARFCN)
enter_arfcn(key_code);
break;
case KEY_MENU:
hold_max();
break;
case KEY_POWER:
if (mode == MODE_ARFCN)
exit_arfcn();
else if (mode == MODE_SPECTRUM)
toggle_spectrum();
break;
case KEY_STAR:
if (mode == MODE_MAIN || mode == MODE_SPECTRUM)
toggle_spectrum();
break;
default:
break;
}
key_code = KEY_INV;
}
static void handle_tone(void)
{
unsigned long elapsed = jiffies - tone_time;
if (!tone_on) {
if (!tone || mode != MODE_MAIN)
return;
/* wait depending on power level */
if (elapsed < (uint8_t)(63-power))
return;
buzzer_volume(tone);
buzzer_note(NOTE(NOTE_C, OCTAVE_5));
tone_time = jiffies;
tone_on = 1;
return;
}
if (elapsed >= TONE_JIFFIES) {
tone_on = 0;
tone_time = jiffies;
buzzer_volume(0);
}
}
/* PM handling */
static void handle_pm(void)
{
/* start power measurement */
if (pm_mode == PM_IDLE && (mode == MODE_MAIN || mode == MODE_SPECTRUM)) {
struct msgb *msg = l1ctl_msgb_alloc(L1CTL_PM_REQ);
struct l1ctl_pm_req *pm;
uint16_t a, e;
pm = (struct l1ctl_pm_req *) msgb_put(msg, sizeof(*pm));
pm->type = 1;
if (mode == MODE_MAIN) {
a = arfcn;
if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX)
a |= ARFCN_PCS;
if (uplink)
a |= ARFCN_UPLINK;
e = a;
pm_mode = PM_SENT;
}
if (mode == MODE_SPECTRUM) {
if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) {
a = PCS_MIN | ARFCN_PCS;
e = PCS_MAX | ARFCN_PCS;
} else {
a = band->min;
e = band->max;
}
pm_mode = PM_RANGE_SENT;
}
if (uplink) {
a |= ARFCN_UPLINK;
e |= ARFCN_UPLINK;
}
pm->range.band_arfcn_from = htons(a);
pm->range.band_arfcn_to = htons(e);
l1a_l23_rx(SC_DLCI_L1A_L23, msg);
return;
}
if (pm_mode == PM_RESULT) {
pm_mode = PM_IDLE;
if (pm_count == pm_max) {
int i = 0;
int sum = 0;
if (uplink) {
/* find max */
for (i = 0; i < pm_count; i++) {
if (pm_meas[i] > sum)
sum = pm_meas[i];
}
power = sum;
} else {
for (i = 0; i < pm_count; i++)
sum += pm_meas[i];
power = sum / pm_count;
}
if (power > max_power)
max_power = power;
pm_count = 0;
pm_max = (uplink) ? NUM_PM_UL : NUM_PM_DL;
if (!tone_on)
refresh_display();
}
return;
}
if (pm_mode == PM_RANGE_RESULT) {
pm_mode = PM_IDLE;
refresh_display();
buzzer_volume(tone);
buzzer_note(NOTE(NOTE_C, OCTAVE_5));
tone_time = jiffies;
tone_on = 1;
return;
}
}
/* Main Program */
const char *hr = "======================================================================\n";
/* note: called from IRQ context */
static void l1a_l23_tx(struct msgb *msg)
{
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
struct l1ctl_pm_conf *pmr;
switch (l1h->msg_type) {
case L1CTL_PM_CONF:
if (pm_mode == PM_SENT) {
pmr = (struct l1ctl_pm_conf *) l1h->data;
pm_meas[pm_count] = pmr->pm[0];
pm_count++;
pm_mode = PM_RESULT;
}
if (pm_mode == PM_RANGE_SENT) {
for (pmr = (struct l1ctl_pm_conf *) l1h->data;
(uint8_t *) pmr < msg->tail; pmr++) {
if (!max || pm_spectrum[ntohs(pmr->band_arfcn) & 1023] < pmr->pm[0])
pm_spectrum[ntohs(pmr->band_arfcn) & 1023] = pmr->pm[0];
}
if ((l1h->flags & L1CTL_F_DONE))
pm_mode = PM_RANGE_RESULT;
}
l1s.tpu_offset_correction += 5000 / NUM_PM_UL;
break;
}
msgb_free(msg);
}
static void console_rx_cb(uint8_t dlci, struct msgb *msg)
{
if (dlci != SC_DLCI_CONSOLE) {
printf("Message for unknown DLCI %u\n", dlci);
return;
}
printf("Message on console DLCI: '%s'\n", msg->data);
msgb_free(msg);
}
static void l1a_l23_rx_cb(uint8_t dlci, struct msgb *msg)
{
int i;
printf("l1a_l23_rx_cb (DLCI %d): ", dlci);
for (i = 0; i < msg->len; i++)
printf("%02x ", msg->data[i]);
puts("\n");
}
static void key_handler(enum key_codes code, enum key_states state)
{
if (state != PRESSED) {
key_pressed = 0;
return;
}
/* key repeat */
if (!key_pressed) {
key_pressed = 1;
key_pressed_when = jiffies;
key_pressed_code = code;
key_pressed_delay = 60;
}
key_code = code;
}
int main(void)
{
board_init();
puts("\n\nOSMOCOM Monitor Tool (revision " GIT_REVISION ")\n");
puts(hr);
/* Dump device identification */
dump_dev_id();
puts(hr);
/* Dump clock config before PLL set */
calypso_clk_dump();
puts(hr);
keypad_set_handler(&key_handler);
/* Dump clock config after PLL set */
calypso_clk_dump();
puts(hr);
sercomm_register_rx_cb(SC_DLCI_CONSOLE, console_rx_cb);
sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx_cb);
layer1_init();
l1a_l23_tx_cb = l1a_l23_tx;
// display_unset_attr(DISP_ATTR_INVERT);
tpu_frame_irq_en(1, 1);
buzzer_mode_pwt(1);
buzzer_volume(0);
memset(pm_spectrum, 0, sizeof(pm_spectrum));
/* inc 0 to 1 and refresh */
inc_dec_arfcn(1);
while (1) {
l1a_compl_execute();
osmo_timers_update();
handle_key_code();
l1a_l23_handler();
handle_pm();
handle_tone();
}
/* NOT REACHED */
twl3025_power_off();
}