From 373851095386dec79adff7927e9b23020a7fbf30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Wed, 23 Jun 2021 17:15:36 +0200 Subject: [PATCH] Sansa Connect: Implement RTC functionality Use 32-bit monotime AVR counter for time tracking. Set the time by adding fixed offset to the counter value. Store the offset in rockbox directory to make it persistent between reboots. Do not implement alarm functionality as wakeup is only possible from sleep and not from complete power off. Change-Id: I615c7eb4df8ab0619dcbfcff107bc7051a15aace --- apps/keymaps/keymap-sansa-connect.c | 16 +- firmware/export/config.h | 1 + firmware/export/config/sansaconnect.h | 2 +- .../sansa-connect/avr-sansaconnect.c | 182 +++++++++++++++--- 4 files changed, 176 insertions(+), 25 deletions(-) diff --git a/apps/keymaps/keymap-sansa-connect.c b/apps/keymaps/keymap-sansa-connect.c index 27790d2bfd..6a6d5de955 100644 --- a/apps/keymaps/keymap-sansa-connect.c +++ b/apps/keymaps/keymap-sansa-connect.c @@ -73,6 +73,18 @@ static const struct button_mapping button_context_yesno[] = { LAST_ITEM_IN_LIST }; /* button_context_yesno */ +static const struct button_mapping button_context_settings_time[] = { + {ACTION_STD_PREV, BUTTON_PREV|BUTTON_REL, BUTTON_PREV}, + {ACTION_STD_PREVREPEAT, BUTTON_PREV|BUTTON_REPEAT, BUTTON_PREV}, + {ACTION_STD_NEXT, BUTTON_NEXT|BUTTON_REL, BUTTON_NEXT}, + {ACTION_STD_NEXTREPEAT, BUTTON_NEXT|BUTTON_REPEAT, BUTTON_NEXT}, + {ACTION_STD_CANCEL, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT}, + {ACTION_STD_OK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT}, + {ACTION_SETTINGS_INC, BUTTON_SCROLL_FWD, BUTTON_NONE}, + {ACTION_SETTINGS_DEC, BUTTON_SCROLL_BACK, BUTTON_NONE}, + LAST_ITEM_IN_LIST +}; /* button_context_settings_time */ + static const struct button_mapping button_context_keyboard[] = { {ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE}, {ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, @@ -113,7 +125,9 @@ const struct button_mapping* get_context_mapping(int context) case CONTEXT_KEYBOARD: case CONTEXT_MORSE_INPUT: return button_context_keyboard; - + case CONTEXT_SETTINGS_TIME: + return button_context_settings_time; + case CONTEXT_TREE: case CONTEXT_LIST: case CONTEXT_MAINMENU: diff --git a/firmware/export/config.h b/firmware/export/config.h index 623ef8b4ff..fdf3bf420d 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -348,6 +348,7 @@ Lyre prototype 1 */ #define RTC_STM41T62 21 /* ST M41T62 */ #define RTC_JZ4760 22 /* Ingenic Jz4760 */ #define RTC_X1000 23 /* Ingenic X1000 */ +#define RTC_CONNECT 24 /* Sansa Connect AVR */ /* USB On-the-go */ #define USBOTG_M66591 6591 /* M:Robe 500 */ diff --git a/firmware/export/config/sansaconnect.h b/firmware/export/config/sansaconnect.h index 5909d3bf84..5fc91a2794 100644 --- a/firmware/export/config/sansaconnect.h +++ b/firmware/export/config/sansaconnect.h @@ -114,7 +114,7 @@ //#define HW_SAMPR_CAPS SAMPR_CAP_44 | SAMPR_CAP_22 | SAMPR_CAP_11 | SAMPR_CAP_8 /* define this if you have a real-time clock */ -//#define CONFIG_RTC RTC_STM41T62 +#define CONFIG_RTC RTC_CONNECT /* define this if the unit uses a scrollwheel for navigation */ #define HAVE_SCROLLWHEEL diff --git a/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c b/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c index ca76100e8b..66cbb1931b 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c @@ -21,7 +21,9 @@ #include #include "config.h" +#include "file.h" #include "system.h" +#include "time.h" #include "power.h" #include "kernel.h" #include "panic.h" @@ -104,15 +106,28 @@ /* protects spi avr commands from concurrent access */ static struct mutex avr_mtx; -/* buttons thread */ -#define BTN_INTERRUPT 1 +/* AVR thread events */ +#define INPUT_INTERRUPT 1 +#define MONOTIME_OFFSET_UPDATE 2 static int btn = 0; static bool hold_switch; -#ifndef BOOTLOADER +static bool input_interrupt_pending; +/* AVR implements 32-bit counter incremented every second. + * The counter value cannot be modified to arbitrary value, + * so the epoch offset needs to be stored in a file. + */ +#define MONOTIME_OFFSET_FILE ROCKBOX_DIR "/monotime_offset.dat" +static uint32_t monotime_offset; +/* Buffer last read monotime value. Reading monotime takes + * atleast 700 us so the tick counter is used together with + * last read monotime value to return current time. + */ +static uint32_t monotime_value; +static unsigned long monotime_value_tick; + static long avr_stack[DEFAULT_STACK_SIZE/sizeof(long)]; static const char avr_thread_name[] = "avr"; -static struct semaphore avr_thread_trigger; -#endif +static struct event_queue avr_queue; /* OF bootloader will refuse to start software if low power is set * Bits 3, 4, 5, 6 and 7 are unknown. @@ -133,7 +148,6 @@ static inline uint16_t be2short(uint8_t *buf) #define BUTTON_DIRECT_MASK (BUTTON_LEFT | BUTTON_UP | BUTTON_RIGHT | BUTTON_DOWN | BUTTON_SELECT | BUTTON_VOL_UP | BUTTON_VOL_DOWN | BUTTON_NEXT | BUTTON_PREV) -#ifndef BOOTLOADER static void handle_wheel(uint8_t wheel) { static int key = 0; @@ -199,7 +213,6 @@ static void handle_wheel(uint8_t wheel) prev_key = key; } -#endif /* buf must be 8-byte state array (reply from avr_hid_get_state() */ static void parse_button_state(uint8_t *state) @@ -219,26 +232,23 @@ static void parse_button_state(uint8_t *state) btn = main_btns_state; -#ifndef BOOTLOADER /* check if stored hold_switch state changed (prevents lost changes) */ if ((state[1] & 0x20) /* hold change notification */ || (hold_switch != ((state[1] & 0x02) >> 1))) { -#endif hold_switch = (state[1] & 0x02) >> 1; #ifdef BUTTON_DEBUG dbgprintf("HOLD changed (%d)", hold_switch); #endif #ifndef BOOTLOADER backlight_hold_changed(hold_switch); - } #endif -#ifndef BOOTLOADER + } + if ((hold_switch == false) && (state[1] & 0x80)) /* scrollwheel change */ { handle_wheel(state[0]); } -#endif #ifdef BUTTON_DEBUG if (state[1] & 0x10) /* power button change */ @@ -496,6 +506,13 @@ static void avr_hid_get_state(void) parse_button_state(state); } +static uint32_t avr_hid_get_monotime(void) +{ + uint8_t tmp[4]; + avr_execute_command(CMD_MONOTIME, tmp, sizeof(tmp)); + return (tmp[0]) | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24); +} + static void avr_hid_enable_wheel(void) { uint8_t enable = 0x01; @@ -554,7 +571,6 @@ void avr_hid_power_off(void) avr_hid_sys_ctrl(SYS_CTRL_POWEROFF); } -#ifndef BOOTLOADER static bool avr_state_changed(void) { return (IO_GIO_BITSET0 & 0x1) ? false : true; @@ -567,6 +583,9 @@ static bool headphones_inserted(void) static void set_audio_output(bool headphones) { +#ifdef BOOTLOADER + (void)headphones; +#else if (headphones) { /* Stereo output on headphones */ @@ -579,19 +598,115 @@ static void set_audio_output(bool headphones) aic3x_switch_output(false); avr_hid_set_amp_enable(1); } +#endif +} + +static void read_monotime_offset(void) +{ + int fd = open(MONOTIME_OFFSET_FILE, O_RDONLY); + if (fd >= 0) + { + uint32_t offset; + if (sizeof(offset) == read(fd, &offset, sizeof(offset))) + { + monotime_offset = offset; + } + close(fd); + } +} + +static bool write_monotime_offset(void) +{ + bool success = false; + int fd = open(MONOTIME_OFFSET_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd >= 0) + { + uint32_t offset = monotime_offset; + if (sizeof(monotime_offset) == write(fd, &offset, sizeof(offset))) + { + success = true; + } + close(fd); + } + return success; +} + +static void read_monotime(void) +{ + uint32_t value = avr_hid_get_monotime(); + int flags = disable_irq_save(); + monotime_value = value; + monotime_value_tick = current_tick; + restore_irq(flags); +} + +static time_t get_timestamp(void) +{ + time_t timestamp; + int flags = disable_irq_save(); + timestamp = monotime_value; + timestamp += monotime_offset; + timestamp += ((current_tick - monotime_value_tick) / HZ); + restore_irq(flags); + return timestamp; +} + +void rtc_init(void) +{ + /* This is called before disk is mounted */ +} + +int rtc_read_datetime(struct tm *tm) +{ + time_t time = get_timestamp(); + gmtime_r(&time, tm); + return 1; +} + +int rtc_write_datetime(const struct tm *tm) +{ + time_t offset = mktime((struct tm *)tm); + int flags = disable_irq_save(); + offset -= monotime_value; + offset -= ((current_tick - monotime_value_tick) / HZ); + monotime_offset = offset; + restore_irq(flags); + queue_post(&avr_queue, MONOTIME_OFFSET_UPDATE, 0); + return 1; } void avr_thread(void) { + struct queue_event ev; bool headphones_active_state = headphones_inserted(); bool headphones_state; + bool disk_access_available = true; + bool monotime_offset_update_pending = false; set_audio_output(headphones_active_state); + read_monotime_offset(); + read_monotime(); while (1) { - semaphore_wait(&avr_thread_trigger, TIMEOUT_BLOCK); + queue_wait(&avr_queue, &ev); + if (ev.id == SYS_USB_CONNECTED) + { + /* Allow USB to gain exclusive storage access */ + usb_acknowledge(SYS_USB_CONNECTED_ACK); + disk_access_available = false; + } + else if (ev.id == SYS_USB_DISCONNECTED) + { + disk_access_available = true; + } + else if (ev.id == MONOTIME_OFFSET_UPDATE) + { + monotime_offset_update_pending = true; + } + + input_interrupt_pending = false; if (avr_state_changed()) { /* Read buttons state */ @@ -604,6 +719,20 @@ void avr_thread(void) set_audio_output(headphones_state); headphones_active_state = headphones_state; } + + if (disk_access_available) + { + if (monotime_offset_update_pending && write_monotime_offset()) + { + monotime_offset_update_pending = false; + } + } + + /* Update buffered monotime value every hour */ + if (TIME_AFTER(current_tick, monotime_value_tick + 3600 * HZ)) + { + read_monotime(); + } } } @@ -613,7 +742,11 @@ void GIO0(void) /* Clear interrupt */ IO_INTC_IRQ1 = (1 << 5); - semaphore_release(&avr_thread_trigger); + if (!input_interrupt_pending) + { + input_interrupt_pending = true; + queue_post(&avr_queue, INPUT_INTERRUPT, 0); + } } void GIO2(void) __attribute__ ((section(".icode"))); @@ -622,20 +755,26 @@ void GIO2(void) /* Clear interrupt */ IO_INTC_IRQ1 = (1 << 7); - semaphore_release(&avr_thread_trigger); + /* Prevent event queue overflow by allowing just one pending event */ + if (!input_interrupt_pending) + { + input_interrupt_pending = true; + queue_post(&avr_queue, INPUT_INTERRUPT, 0); + } } -#endif void button_init_device(void) { btn = 0; hold_switch = false; -#ifndef BOOTLOADER - semaphore_init(&avr_thread_trigger, 1, 1); + + queue_init(&avr_queue, true); + input_interrupt_pending = true; + queue_post(&avr_queue, INPUT_INTERRUPT, 0); create_thread(avr_thread, avr_stack, sizeof(avr_stack), 0, avr_thread_name IF_PRIO(, PRIORITY_USER_INTERFACE) IF_COP(, CPU)); -#endif + IO_GIO_DIR0 |= 0x01; /* Set GIO0 as input */ /* Get in sync with AVR */ @@ -646,7 +785,6 @@ void button_init_device(void) /* Read button status and tell avr we want interrupt on next change */ avr_hid_get_state(); -#ifndef BOOTLOADER IO_GIO_IRQPORT |= 0x05; /* Enable GIO0/GIO2 external interrupt */ IO_GIO_INV0 &= ~0x05; /* Clear INV for GIO0/GIO2 */ /* falling edge detection on GIO0, any edge on GIO2 */ @@ -654,7 +792,6 @@ void button_init_device(void) /* Enable GIO0 and GIO2 interrupts */ IO_INTC_EINT1 |= INTR_EINT1_EXT0 | INTR_EINT1_EXT2; -#endif } int button_read_device(void) @@ -674,4 +811,3 @@ void lcd_enable(bool on) { (void)on; } -