8de163b8ae
Make frequency related comments accurate. Disable UART0 clock. Change-Id: I224a3d6656ad53165dcff68ed716fa2c6863240d
957 lines
24 KiB
C
957 lines
24 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id: $
|
|
*
|
|
* Copyright (C) 2011-2021 by Tomasz Moń
|
|
*
|
|
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include "config.h"
|
|
#include "file.h"
|
|
#include "system.h"
|
|
#include "time.h"
|
|
#include "power.h"
|
|
#include "kernel.h"
|
|
#include "panic.h"
|
|
/*#define LOGF_ENABLE*/
|
|
#include "logf.h"
|
|
#include "avr-sansaconnect.h"
|
|
#include "uart-target.h"
|
|
#include "usb.h"
|
|
#include "button.h"
|
|
#include "backlight.h"
|
|
#include "powermgmt.h"
|
|
#include "aic3x.h"
|
|
|
|
//#define BUTTON_DEBUG
|
|
|
|
#ifdef BUTTON_DEBUG
|
|
#include "lcd-target.h"
|
|
#include "lcd.h"
|
|
#include "font.h"
|
|
#include "common.h"
|
|
#endif
|
|
|
|
#ifdef BUTTON_DEBUG
|
|
#define dbgprintf DEBUGF
|
|
#else
|
|
#define dbgprintf(...)
|
|
#endif
|
|
|
|
#define AVR_DELAY_US 200
|
|
#define AVR_MAX_RETRIES 10
|
|
|
|
#define CMD_SYNC 0xAA
|
|
#define CMD_CLOSE 0xCC
|
|
#define CMD_FILL 0xFF
|
|
|
|
/* Actual command opcodes handled by AVR */
|
|
#define CMD_STATE 0xBB
|
|
#define CMD_VER 0xBC
|
|
#define CMD_MONOTIME 0xBD
|
|
#define CMD_PGMWAKE 0xBF
|
|
#define CMD_HDQ_READ 0xC0
|
|
#define CMD_HDQ_WRITE 0xC1
|
|
#define CMD_HDQ_STATUS 0xC2
|
|
#define CMD_GET_LAST_RESET_TYPE 0xC4
|
|
#define CMD_UNKNOWN_C5 0xC5
|
|
#define CMD_GET_BATTERY_TEMP 0xC8
|
|
#define CMD_LCM_POWER 0xC9
|
|
#define CMD_AMP_ENABLE 0xCA
|
|
#define CMD_WHEEL_EN 0xD0
|
|
#define CMD_SET_INTCHRG 0xD1
|
|
#define CMD_GET_INTCHRG 0xD2
|
|
#define CMD_WIFI_PD 0xD3
|
|
#define CMD_UNKNOWN_D4 0xD4
|
|
#define CMD_UNKNOWN_D5 0xD5
|
|
#define CMD_UNKNOWN_D6 0xD6
|
|
#define CMD_CODEC_RESET 0xD7
|
|
#define CMD_ADC_START 0xD8
|
|
#define CMD_ADC_RESULT 0xD9
|
|
#define CMD_SYS_CTRL 0xDA
|
|
#define CMD_SET_USBCHRG 0xE2
|
|
#define CMD_GET_USBCHRG 0xE3
|
|
#define CMD_MONORSTCNT 0xE4
|
|
|
|
/* CMD_LCM_POWER parameters */
|
|
#define LCM_POWER_OFF 0x00
|
|
#define LCM_POWER_ON 0x01
|
|
#define LCM_POWER_SLEEP 0x02
|
|
#define LCM_POWER_WAKE 0x03
|
|
#define LCM_REPOWER_ON 0x04
|
|
|
|
/* CMD_SYS_CTRL parameters */
|
|
#define SYS_CTRL_POWEROFF 0x00
|
|
#define SYS_CTRL_RESET 0x01
|
|
#define SYS_CTRL_SLEEP 0x02
|
|
#define SYS_CTRL_DISABLE_WD 0x03
|
|
#define SYS_CTRL_KICK_WD 0x04
|
|
#define SYS_CTRL_EN_HDQ_THERM 0x05
|
|
#define SYS_CTRL_EN_TS_THERM 0x06
|
|
#define SYS_CTRL_FRESET 0x80
|
|
|
|
/* HDQ status codes */
|
|
#define HDQ_STATUS_OK 0x00
|
|
#define HDQ_STATUS_NOT_READY 0x01
|
|
#define HDQ_STATUS_TIMEOUT 0x02
|
|
|
|
/* protects spi avr commands from concurrent access */
|
|
static struct mutex avr_mtx;
|
|
/* serializes hdq read/write and status retrieval */
|
|
static struct mutex hdq_mtx;
|
|
|
|
/* AVR thread events */
|
|
#define INPUT_INTERRUPT 1
|
|
#define MONOTIME_OFFSET_UPDATE 2
|
|
#define POWER_OFF_REQUEST 3
|
|
static int btn = 0;
|
|
static bool hold_switch;
|
|
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 1400 us so the tick counter is used together with
|
|
* last read monotime value to return current time.
|
|
*/
|
|
static bool monotime_available;
|
|
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 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.
|
|
*/
|
|
#define BATTERY_STATUS_LOW_POWER (1 << 2)
|
|
#define BATTERY_STATUS_CHARGER_CONNECTED (1 << 1)
|
|
#define BATTERY_STATUS_CHARGING (1 << 0)
|
|
static uint8_t avr_battery_status;
|
|
|
|
#define BATTERY_LEVEL_NOT_DETECTED (1 << 7)
|
|
#define BATTERY_LEVEL_PERCENTAGE_MASK 0x7F
|
|
static uint8_t avr_battery_level = 100;
|
|
|
|
static inline uint16_t be2short(uint8_t *buf)
|
|
{
|
|
return (uint16_t)((buf[0] << 8) | buf[1]);
|
|
}
|
|
|
|
#define BUTTON_DIRECT_MASK (BUTTON_LEFT | BUTTON_UP | BUTTON_RIGHT | BUTTON_DOWN | BUTTON_SELECT | BUTTON_VOL_UP | BUTTON_VOL_DOWN | BUTTON_NEXT | BUTTON_PREV)
|
|
|
|
static void handle_wheel(uint8_t wheel)
|
|
{
|
|
static int key = 0;
|
|
static uint8_t velocity = 0;
|
|
static uint32_t wheel_delta = 1ul << 24;
|
|
static uint8_t wheel_prev = 0;
|
|
static unsigned long nextbacklight_hw_on = 0;
|
|
static int prev_key = -1;
|
|
static int prev_key_post = 0;
|
|
|
|
if (TIME_AFTER(current_tick, nextbacklight_hw_on))
|
|
{
|
|
backlight_on();
|
|
reset_poweroff_timer();
|
|
nextbacklight_hw_on = current_tick + HZ/4;
|
|
}
|
|
|
|
if (wheel_prev < wheel)
|
|
{
|
|
key = BUTTON_SCROLL_FWD;
|
|
velocity = wheel - wheel_prev;
|
|
}
|
|
else if (wheel_prev > wheel)
|
|
{
|
|
key = BUTTON_SCROLL_BACK;
|
|
velocity = wheel_prev - wheel;
|
|
}
|
|
|
|
if (prev_key != key && velocity < 2 /* filter "rewinds" */)
|
|
{
|
|
/* direction reversal */
|
|
prev_key = key;
|
|
wheel_delta = 1ul << 24;
|
|
return;
|
|
}
|
|
|
|
/* TODO: take velocity into account */
|
|
if (queue_empty(&button_queue))
|
|
{
|
|
if (prev_key_post == key)
|
|
{
|
|
key |= BUTTON_REPEAT;
|
|
}
|
|
|
|
/* Post directly, don't update btn as avr doesn't give
|
|
interrupt on scroll stop */
|
|
queue_post(&button_queue, key, wheel_delta);
|
|
|
|
wheel_delta = 1ul << 24;
|
|
|
|
prev_key_post = key;
|
|
}
|
|
else
|
|
{
|
|
/* skipped post - increment delta and limit to 7 bits */
|
|
wheel_delta += 1ul << 24;
|
|
|
|
if (wheel_delta > (0x7ful << 24))
|
|
wheel_delta = 0x7ful << 24;
|
|
}
|
|
|
|
wheel_prev = wheel;
|
|
|
|
prev_key = key;
|
|
}
|
|
|
|
/* buf must be 8-byte state array (reply from avr_hid_get_state() */
|
|
static void parse_button_state(uint8_t *state)
|
|
{
|
|
uint16_t main_btns_state = be2short(&state[2]);
|
|
#ifdef BUTTON_DEBUG
|
|
uint16_t main_btns_changed = be2short(&state[4]);
|
|
#endif
|
|
|
|
/* make sure other bits doesn't conflict with our "free bits" buttons */
|
|
main_btns_state &= BUTTON_DIRECT_MASK;
|
|
|
|
if (state[1] & 0x01) /* is power button pressed? */
|
|
{
|
|
main_btns_state |= BUTTON_POWER;
|
|
}
|
|
|
|
btn = main_btns_state;
|
|
|
|
/* check if stored hold_switch state changed (prevents lost changes) */
|
|
if ((state[1] & 0x20) /* hold change notification */ ||
|
|
(hold_switch != ((state[1] & 0x02) >> 1)))
|
|
{
|
|
hold_switch = (state[1] & 0x02) >> 1;
|
|
#ifdef BUTTON_DEBUG
|
|
dbgprintf("HOLD changed (%d)", hold_switch);
|
|
#endif
|
|
#ifndef BOOTLOADER
|
|
backlight_hold_changed(hold_switch);
|
|
#endif
|
|
}
|
|
|
|
if ((hold_switch == false) && (state[1] & 0x80)) /* scrollwheel change */
|
|
{
|
|
handle_wheel(state[0]);
|
|
}
|
|
|
|
#ifdef BUTTON_DEBUG
|
|
if (state[1] & 0x10) /* power button change */
|
|
{
|
|
/* power button state has changed */
|
|
main_btns_changed |= BUTTON_POWER;
|
|
}
|
|
|
|
if (btn & BUTTON_LEFT) dbgprintf("LEFT");
|
|
if (btn & BUTTON_UP) dbgprintf("UP");
|
|
if (btn & BUTTON_RIGHT) dbgprintf("RIGHT");
|
|
if (btn & BUTTON_DOWN) dbgprintf("DOWN");
|
|
if (btn & BUTTON_SELECT) dbgprintf("SELECT");
|
|
if (btn & BUTTON_VOL_UP) dbgprintf("VOL UP");
|
|
if (btn & BUTTON_VOL_DOWN) dbgprintf("VOL DOWN");
|
|
if (btn & BUTTON_NEXT) dbgprintf("NEXT");
|
|
if (btn & BUTTON_PREV) dbgprintf("PREV");
|
|
if (btn & BUTTON_POWER) dbgprintf("POWER");
|
|
if (btn & BUTTON_HOLD) dbgprintf("HOLD");
|
|
if (btn & BUTTON_SCROLL_FWD) dbgprintf("SCROLL FWD");
|
|
if (btn & BUTTON_SCROLL_BACK) dbgprintf("SCROLL BACK");
|
|
#endif
|
|
}
|
|
|
|
static bool avr_command_reads_data(uint8_t opcode)
|
|
{
|
|
switch (opcode)
|
|
{
|
|
case CMD_STATE:
|
|
case CMD_VER:
|
|
case CMD_GET_LAST_RESET_TYPE:
|
|
case CMD_GET_INTCHRG:
|
|
case CMD_MONOTIME:
|
|
case CMD_UNKNOWN_C5:
|
|
case CMD_MONORSTCNT:
|
|
case CMD_HDQ_STATUS:
|
|
case CMD_GET_BATTERY_TEMP:
|
|
case CMD_GET_USBCHRG:
|
|
case CMD_ADC_RESULT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static size_t avr_command_data_size(uint8_t opcode)
|
|
{
|
|
switch (opcode)
|
|
{
|
|
case CMD_STATE: return 8;
|
|
case CMD_VER: return 1;
|
|
case CMD_MONOTIME: return 4;
|
|
case CMD_PGMWAKE: return 4;
|
|
case CMD_HDQ_READ: return 1;
|
|
case CMD_HDQ_WRITE: return 2;
|
|
case CMD_HDQ_STATUS: return 2;
|
|
case CMD_GET_LAST_RESET_TYPE: return 1;
|
|
case CMD_UNKNOWN_C5: return 1;
|
|
case CMD_GET_BATTERY_TEMP: return 2;
|
|
case CMD_LCM_POWER: return 1;
|
|
case CMD_AMP_ENABLE: return 1;
|
|
case CMD_WHEEL_EN: return 1;
|
|
case CMD_SET_INTCHRG: return 1;
|
|
case CMD_GET_INTCHRG: return 1;
|
|
case CMD_WIFI_PD: return 1;
|
|
case CMD_UNKNOWN_D4: return 1;
|
|
case CMD_UNKNOWN_D5: return 2;
|
|
case CMD_UNKNOWN_D6: return 2;
|
|
case CMD_CODEC_RESET: return 0;
|
|
case CMD_ADC_START: return 1;
|
|
case CMD_ADC_RESULT: return 2;
|
|
case CMD_SYS_CTRL: return 1;
|
|
case CMD_SET_USBCHRG: return 1;
|
|
case CMD_GET_USBCHRG: return 1;
|
|
case CMD_MONORSTCNT: return 2;
|
|
default:
|
|
panicf("Invalid AVR opcode %02X", opcode);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static uint8_t spi_read_byte(void)
|
|
{
|
|
uint16_t rxdata;
|
|
|
|
do
|
|
{
|
|
rxdata = IO_SERIAL1_RX_DATA;
|
|
}
|
|
while (rxdata & (1<<8));
|
|
|
|
return rxdata & 0xFF;
|
|
}
|
|
|
|
static void avr_hid_select(void)
|
|
{
|
|
/* Drive GIO29 (AVR SS) low */
|
|
IO_GIO_BITCLR1 = (1 << 13);
|
|
}
|
|
|
|
static void avr_hid_release(void)
|
|
{
|
|
/* Drive GIO29 (AVR SS) high */
|
|
IO_GIO_BITSET1 = (1 << 13);
|
|
}
|
|
|
|
static bool avr_run_command(uint8_t opcode, uint8_t *data, size_t data_length)
|
|
{
|
|
bool success = true;
|
|
const bool is_read = avr_command_reads_data(opcode);
|
|
size_t i;
|
|
uint8_t rx;
|
|
|
|
/* Verify command data size and also make sure command is valid */
|
|
if (avr_command_data_size(opcode) != data_length)
|
|
{
|
|
panicf("AVR %02x invalid data length", opcode);
|
|
}
|
|
|
|
mutex_lock(&avr_mtx);
|
|
|
|
bitset16(&IO_CLK_MOD2, CLK_MOD2_SIF1);
|
|
IO_SERIAL1_TX_ENABLE = 0x0001;
|
|
|
|
avr_hid_select();
|
|
udelay(10);
|
|
|
|
IO_SERIAL1_TX_DATA = CMD_SYNC;
|
|
spi_read_byte();
|
|
/* Allow AVR to process CMD_SYNC */
|
|
udelay(AVR_DELAY_US);
|
|
|
|
IO_SERIAL1_TX_DATA = opcode;
|
|
rx = spi_read_byte();
|
|
if (rx != CMD_SYNC)
|
|
{
|
|
/* AVR failed to register CMD_SYNC */
|
|
success = false;
|
|
}
|
|
/* Allow AVR to process opcode */
|
|
udelay(AVR_DELAY_US);
|
|
|
|
if (is_read)
|
|
{
|
|
for (i = 0; i < data_length; i++)
|
|
{
|
|
IO_SERIAL1_TX_DATA = CMD_FILL;
|
|
data[i] = spi_read_byte();
|
|
udelay(AVR_DELAY_US);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < data_length; i++)
|
|
{
|
|
IO_SERIAL1_TX_DATA = data[i];
|
|
spi_read_byte();
|
|
udelay(AVR_DELAY_US);
|
|
}
|
|
}
|
|
|
|
IO_SERIAL1_TX_DATA = CMD_CLOSE;
|
|
rx = spi_read_byte();
|
|
udelay(AVR_DELAY_US);
|
|
if (is_read)
|
|
{
|
|
success = success && (rx == CMD_CLOSE);
|
|
}
|
|
|
|
avr_hid_release();
|
|
|
|
IO_SERIAL1_TX_ENABLE = 0;
|
|
bitclr16(&IO_CLK_MOD2, CLK_MOD2_SIF1);
|
|
|
|
mutex_unlock(&avr_mtx);
|
|
|
|
return success;
|
|
}
|
|
|
|
static bool avr_hid_get_state(void)
|
|
{
|
|
uint8_t state[8];
|
|
if (avr_run_command(CMD_STATE, state, sizeof(state)))
|
|
{
|
|
avr_battery_status = state[6];
|
|
avr_battery_level = state[7];
|
|
parse_button_state(state);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool avr_hid_sync(void)
|
|
{
|
|
int retry;
|
|
for (retry = 0; retry < AVR_MAX_RETRIES; retry++)
|
|
{
|
|
if (avr_hid_get_state())
|
|
{
|
|
return true;
|
|
}
|
|
mdelay(100);
|
|
}
|
|
/* TODO: Program HID as it appears to be not programmed.
|
|
* To do so, unfortunately, AVR firmware would have to be written
|
|
* from scratch as OF blob cannot be used due to licensing.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
static bool avr_execute_command(uint8_t opcode, uint8_t *data, size_t data_length)
|
|
{
|
|
int retry;
|
|
for (retry = 0; retry < AVR_MAX_RETRIES; retry++)
|
|
{
|
|
if (avr_run_command(opcode, data, data_length))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* Resync and try again */
|
|
avr_hid_sync();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void avr_hid_init(void)
|
|
{
|
|
/*
|
|
setup alternate GIO functions:
|
|
GIO30 - SIF1 Clock
|
|
GIO31 - SIF1 Data In
|
|
GIO32 - SIF1 Data Out
|
|
Manually drive GIO29 output (directly connected to AVR's SS).
|
|
*/
|
|
IO_GIO_FSEL2 = (IO_GIO_FSEL2 & 0x00FF) | 0xA800;
|
|
/* GIO29, GIO30 - outputs, GIO31 - input */
|
|
IO_GIO_DIR1 = (IO_GIO_DIR1 & ~((1 << 13) | (1 << 14))) | (1 << 15);
|
|
/* GIO32 - output */
|
|
bitclr16(&IO_GIO_DIR2, (1 << 0));
|
|
avr_hid_release();
|
|
|
|
/* Master, MSB first, RATE = 219 (Bit rate = ARM clock / 2*(RATE + 1)))
|
|
* Boosted 148.5 MHz / 440 = 337.5 kHz
|
|
* Default 74.25 MHz / 440 = 168.75 kHz
|
|
*/
|
|
IO_SERIAL1_MODE = 0x6DB;
|
|
|
|
mutex_init(&avr_mtx);
|
|
mutex_init(&hdq_mtx);
|
|
}
|
|
|
|
int _battery_level(void)
|
|
{
|
|
/* OF still plays music when level is at 0 */
|
|
if (avr_battery_level & BATTERY_LEVEL_NOT_DETECTED)
|
|
{
|
|
return 0;
|
|
}
|
|
return avr_battery_level & BATTERY_LEVEL_PERCENTAGE_MASK;
|
|
}
|
|
|
|
int _battery_voltage(void)
|
|
{
|
|
return avr_hid_hdq_read_short(HDQ_REG_VOLT);
|
|
}
|
|
|
|
int _battery_time(void)
|
|
{
|
|
/* HDQ_REG_TTE reads as 65535 when charging */
|
|
return avr_hid_hdq_read_short(HDQ_REG_TTE);
|
|
}
|
|
|
|
unsigned int power_input_status(void)
|
|
{
|
|
if (avr_battery_status & BATTERY_STATUS_CHARGER_CONNECTED)
|
|
{
|
|
return POWER_INPUT_USB_CHARGER;
|
|
}
|
|
return POWER_INPUT_NONE;
|
|
}
|
|
|
|
bool charging_state(void)
|
|
{
|
|
return (avr_battery_status & BATTERY_STATUS_CHARGING) != 0;
|
|
}
|
|
|
|
static int avr_hid_hdq_read_byte_internal(uint8_t address)
|
|
{
|
|
uint8_t result[2];
|
|
|
|
if (!avr_execute_command(CMD_HDQ_READ, &address, sizeof(address)))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
do
|
|
{
|
|
mdelay(10);
|
|
if (!avr_execute_command(CMD_HDQ_STATUS, result, sizeof(result)))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
while (result[0] == HDQ_STATUS_NOT_READY);
|
|
|
|
if (result[0] != HDQ_STATUS_OK)
|
|
{
|
|
logf("HDQ read %d status %d", address, result[0]);
|
|
return -1;
|
|
}
|
|
|
|
return result[1];
|
|
}
|
|
|
|
int avr_hid_hdq_read_byte(uint8_t address)
|
|
{
|
|
int retry;
|
|
int value = -1;
|
|
for (retry = 0; (retry < 3) && (value < 0); retry++)
|
|
{
|
|
mutex_lock(&hdq_mtx);
|
|
value = avr_hid_hdq_read_byte_internal(address);
|
|
mutex_unlock(&hdq_mtx);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int avr_hid_hdq_read_short(uint8_t address)
|
|
{
|
|
int old_hi = -1, old_lo = -1, hi = -2, lo = -2;
|
|
/* Keep reading until we read the same value twice.
|
|
* There's no atomic 16-bit value retrieval, so keep reading
|
|
* until we read the same value twice. HDQ registers update
|
|
* no more than once per 2.56 seconds so usually there will
|
|
* be 4 reads and sometimes 6 reads.
|
|
*/
|
|
while ((old_hi != hi) || (old_lo != lo))
|
|
{
|
|
old_hi = hi;
|
|
old_lo = lo;
|
|
hi = avr_hid_hdq_read_byte(address + 1);
|
|
lo = avr_hid_hdq_read_byte(address);
|
|
}
|
|
if ((hi < 0) || (lo < 0))
|
|
{
|
|
return -1;
|
|
}
|
|
return (hi << 8) | lo;
|
|
}
|
|
|
|
static void avr_hid_enable_wheel(void)
|
|
{
|
|
uint8_t enable = 0x01;
|
|
avr_execute_command(CMD_WHEEL_EN, &enable, sizeof(enable));
|
|
}
|
|
|
|
/* command that is sent by "hidtool -J 1" issued on every OF boot */
|
|
void avr_hid_enable_charger(void)
|
|
{
|
|
uint8_t enable = 0x01;
|
|
avr_execute_command(CMD_SET_INTCHRG, &enable, sizeof(enable));
|
|
}
|
|
|
|
void avr_hid_wifi_pd(int high)
|
|
{
|
|
uint8_t state = high ? 0x01 : 0x00;
|
|
avr_execute_command(CMD_WIFI_PD, &state, sizeof(state));
|
|
}
|
|
|
|
static void avr_hid_lcm_power(uint8_t parameter)
|
|
{
|
|
avr_execute_command(CMD_LCM_POWER, ¶meter, sizeof(parameter));
|
|
}
|
|
|
|
void avr_hid_lcm_sleep(void)
|
|
{
|
|
avr_hid_lcm_power(LCM_POWER_SLEEP);
|
|
}
|
|
|
|
void avr_hid_lcm_wake(void)
|
|
{
|
|
avr_hid_lcm_power(LCM_POWER_WAKE);
|
|
}
|
|
|
|
void avr_hid_lcm_power_on(void)
|
|
{
|
|
avr_hid_lcm_power(LCM_POWER_ON);
|
|
}
|
|
|
|
void avr_hid_lcm_power_off(void)
|
|
{
|
|
avr_hid_lcm_power(LCM_POWER_OFF);
|
|
}
|
|
|
|
void avr_hid_reset_codec(void)
|
|
{
|
|
avr_execute_command(CMD_CODEC_RESET, NULL, 0);
|
|
}
|
|
|
|
void avr_hid_set_amp_enable(uint8_t enable)
|
|
{
|
|
avr_execute_command(CMD_AMP_ENABLE, &enable, sizeof(enable));
|
|
}
|
|
|
|
static void avr_hid_sys_ctrl(uint8_t parameter)
|
|
{
|
|
avr_execute_command(CMD_SYS_CTRL, ¶meter, sizeof(parameter));
|
|
}
|
|
|
|
void avr_hid_power_off(void)
|
|
{
|
|
/* Do not execute command directly here because we can get called inside
|
|
* tick task context that must not acquire mutex.
|
|
*/
|
|
queue_post(&avr_queue, POWER_OFF_REQUEST, 0);
|
|
}
|
|
|
|
static bool avr_state_changed(void)
|
|
{
|
|
return (IO_GIO_BITSET0 & 0x1) ? false : true;
|
|
}
|
|
|
|
static bool headphones_inserted(void)
|
|
{
|
|
return (IO_GIO_BITSET0 & 0x04) ? false : true;
|
|
}
|
|
|
|
static void set_audio_output(bool headphones)
|
|
{
|
|
#ifdef BOOTLOADER
|
|
(void)headphones;
|
|
#else
|
|
if (headphones)
|
|
{
|
|
/* Stereo output on headphones */
|
|
avr_hid_set_amp_enable(0);
|
|
aic3x_switch_output(true);
|
|
}
|
|
else
|
|
{
|
|
/* Mono output on built-in speaker */
|
|
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)
|
|
{
|
|
uint8_t tmp[4];
|
|
uint32_t t1, t2;
|
|
|
|
if (!avr_execute_command(CMD_MONOTIME, tmp, sizeof(tmp)))
|
|
{
|
|
return;
|
|
}
|
|
t1 = (tmp[0]) | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24);
|
|
|
|
if (!avr_execute_command(CMD_MONOTIME, tmp, sizeof(tmp)))
|
|
{
|
|
return;
|
|
}
|
|
t2 = (tmp[0]) | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24);
|
|
|
|
if ((t1 == t2) || (t1 + 1 == t2))
|
|
{
|
|
int flags = disable_irq_save();
|
|
monotime_value = t2;
|
|
monotime_value_tick = current_tick;
|
|
restore_irq(flags);
|
|
monotime_available = true;
|
|
}
|
|
}
|
|
|
|
static time_t get_timestamp(void)
|
|
{
|
|
time_t timestamp;
|
|
if (!monotime_available)
|
|
{
|
|
read_monotime();
|
|
}
|
|
if (monotime_available)
|
|
{
|
|
int flags = disable_irq_save();
|
|
timestamp = monotime_value;
|
|
timestamp += monotime_offset;
|
|
timestamp += ((current_tick - monotime_value_tick) / HZ);
|
|
restore_irq(flags);
|
|
return timestamp;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (avr_state_changed())
|
|
{
|
|
/* We have to read AVR state, simply check if there's any event
|
|
* pending but do not block. It is possible that AVR interrupt
|
|
* line is held active even though we read the state (change
|
|
* occured during read).
|
|
*/
|
|
queue_wait_w_tmo(&avr_queue, &ev, 0);
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
else if (ev.id == POWER_OFF_REQUEST)
|
|
{
|
|
avr_hid_reset_codec();
|
|
avr_hid_sys_ctrl(SYS_CTRL_POWEROFF);
|
|
}
|
|
|
|
input_interrupt_pending = false;
|
|
if (avr_state_changed())
|
|
{
|
|
/* Read buttons state */
|
|
avr_hid_get_state();
|
|
}
|
|
|
|
headphones_state = headphones_inserted();
|
|
if (headphones_state != headphones_active_state)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GIO0(void) __attribute__ ((section(".icode")));
|
|
void GIO0(void)
|
|
{
|
|
/* Clear interrupt */
|
|
IO_INTC_IRQ1 = (1 << 5);
|
|
|
|
if (!input_interrupt_pending)
|
|
{
|
|
input_interrupt_pending = true;
|
|
queue_post(&avr_queue, INPUT_INTERRUPT, 0);
|
|
}
|
|
}
|
|
|
|
void GIO2(void) __attribute__ ((section(".icode")));
|
|
void GIO2(void)
|
|
{
|
|
/* Clear interrupt */
|
|
IO_INTC_IRQ1 = (1 << 7);
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
void button_init_device(void)
|
|
{
|
|
btn = 0;
|
|
hold_switch = false;
|
|
|
|
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));
|
|
|
|
IO_GIO_DIR0 |= 0x01; /* Set GIO0 as input */
|
|
|
|
/* Get in sync with AVR */
|
|
avr_hid_sync();
|
|
|
|
/* Enable wheel */
|
|
avr_hid_enable_wheel();
|
|
/* Read button status and tell avr we want interrupt on next change */
|
|
avr_hid_get_state();
|
|
|
|
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 */
|
|
IO_GIO_IRQEDGE = (IO_GIO_IRQEDGE & ~0x01) | 0x04;
|
|
|
|
/* Enable GIO0 and GIO2 interrupts */
|
|
IO_INTC_EINT1 |= INTR_EINT1_EXT0 | INTR_EINT1_EXT2;
|
|
}
|
|
|
|
int button_read_device(void)
|
|
{
|
|
if(hold_switch)
|
|
return 0;
|
|
else
|
|
return btn;
|
|
}
|
|
|
|
bool button_hold(void)
|
|
{
|
|
return hold_switch;
|
|
}
|