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
This commit is contained in:
Tomasz Moń 2021-06-23 17:15:36 +02:00
parent 635ec5bbbd
commit 3738510953
No known key found for this signature in database
GPG key ID: 92BA8820D4D517C8
4 changed files with 176 additions and 25 deletions

View file

@ -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:

View file

@ -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 */

View file

@ -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

View file

@ -21,7 +21,9 @@
#include <stdio.h>
#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;
}