mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-09 13:12:37 -05:00
New port: FiiO M3K on bare metal
Change-Id: I7517e7d5459e129dcfc9465c6fbd708619888fbe
This commit is contained in:
parent
83fcbedc65
commit
3ec66893e3
143 changed files with 16585 additions and 24 deletions
274
firmware/drivers/audio/ak4376.c
Normal file
274
firmware/drivers/audio/ak4376.c
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* 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 "audiohw.h"
|
||||
#include "sound.h"
|
||||
#include "panic.h"
|
||||
#include "pcm_sampr.h"
|
||||
#include "pcm_sw_volume.h"
|
||||
#include "system.h"
|
||||
#include "i2c-async.h"
|
||||
|
||||
#ifndef HAVE_SW_VOLUME_CONTROL
|
||||
# error "AK4376 requires HAVE_SW_VOLUME_CONTROL!"
|
||||
#endif
|
||||
|
||||
/* NOTE: At present, only the FiiO M3K uses this driver so the handling of
|
||||
* the clock / audio interface is limited to I2S slave, 16-bit samples, with
|
||||
* DAC master clock provided directly on the MCLK input pin, fitting the
|
||||
* clock setup of the M3K.
|
||||
*
|
||||
* Feel free to expand upon this if another target ever needs this driver.
|
||||
*/
|
||||
|
||||
/* Converts HW_FREQ_XX constants to register values */
|
||||
static const int ak4376_fsel_to_hw[] = {
|
||||
HW_HAVE_192_(AK4376_FS_192,)
|
||||
HW_HAVE_176_(AK4376_FS_176,)
|
||||
HW_HAVE_96_(AK4376_FS_96,)
|
||||
HW_HAVE_88_(AK4376_FS_88,)
|
||||
HW_HAVE_64_(AK4376_FS_64,)
|
||||
HW_HAVE_48_(AK4376_FS_48,)
|
||||
HW_HAVE_44_(AK4376_FS_44,)
|
||||
HW_HAVE_32_(AK4376_FS_32,)
|
||||
HW_HAVE_24_(AK4376_FS_24,)
|
||||
HW_HAVE_22_(AK4376_FS_22,)
|
||||
HW_HAVE_16_(AK4376_FS_16,)
|
||||
HW_HAVE_12_(AK4376_FS_12,)
|
||||
HW_HAVE_11_(AK4376_FS_11,)
|
||||
HW_HAVE_8_(AK4376_FS_8,)
|
||||
};
|
||||
|
||||
static struct ak4376 {
|
||||
int fsel;
|
||||
int low_mode;
|
||||
int regs[AK4376_NUM_REGS];
|
||||
} ak4376;
|
||||
|
||||
void ak4376_init(void)
|
||||
{
|
||||
/* Initialize DAC state */
|
||||
ak4376.fsel = HW_FREQ_48;
|
||||
ak4376.low_mode = 0;
|
||||
for(int i = 0; i < AK4376_NUM_REGS; ++i)
|
||||
ak4376.regs[i] = -1;
|
||||
|
||||
/* Initial reset after power-on */
|
||||
ak4376_set_pdn_pin(0);
|
||||
mdelay(1);
|
||||
ak4376_set_pdn_pin(1);
|
||||
mdelay(1);
|
||||
|
||||
static const int init_config[] = {
|
||||
/* Ensure HPRHZ, HPLHZ are 0 */
|
||||
AK4376_REG_OUTPUT_MODE, 0x00,
|
||||
/* Mute all volume controls */
|
||||
AK4376_REG_MIXER, 0x00,
|
||||
AK4376_REG_LCH_VOLUME, 0x80,
|
||||
AK4376_REG_RCH_VOLUME, 0x00,
|
||||
AK4376_REG_AMP_VOLUME, 0x00,
|
||||
/* Clock source = MCLK, divider = 1 */
|
||||
AK4376_REG_DAC_CLK_SRC, 0x00,
|
||||
AK4376_REG_DAC_CLK_DIV, 0x00,
|
||||
/* I2S slave mode, 16-bit samples */
|
||||
AK4376_REG_AUDIO_IF_FMT, 0x03,
|
||||
/* Recommended by datasheet */
|
||||
AK4376_REG_ADJUST1, 0x20,
|
||||
AK4376_REG_ADJUST2, 0x05,
|
||||
/* Power controls */
|
||||
AK4376_REG_PWR2, 0x33,
|
||||
AK4376_REG_PWR3, 0x01,
|
||||
AK4376_REG_PWR4, 0x03,
|
||||
};
|
||||
|
||||
/* Write initial configuration prior to power-up */
|
||||
for(size_t i = 0; i < ARRAYLEN(init_config); i += 2)
|
||||
ak4376_write(init_config[i], init_config[i+1]);
|
||||
|
||||
/* Initial frequency setting, also handles DAC/amp power-up */
|
||||
audiohw_set_frequency(HW_FREQ_48);
|
||||
}
|
||||
|
||||
void ak4376_close(void)
|
||||
{
|
||||
/* Shut off power */
|
||||
ak4376_write(AK4376_REG_PWR3, 0x00);
|
||||
ak4376_write(AK4376_REG_PWR4, 0x00);
|
||||
ak4376_write(AK4376_REG_PWR2, 0x00);
|
||||
|
||||
/* PDN pin low */
|
||||
ak4376_set_pdn_pin(0);
|
||||
}
|
||||
|
||||
void ak4376_write(int reg, int value)
|
||||
{
|
||||
/* Ensure value is sensible and differs from the last set value */
|
||||
if((value & 0xff) == value && ak4376.regs[reg] != value) {
|
||||
int r = i2c_reg_write1(AK4376_BUS, AK4376_ADDR, reg, value);
|
||||
if(r == I2C_STATUS_OK)
|
||||
ak4376.regs[reg] = value;
|
||||
else
|
||||
ak4376.regs[reg] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int ak4376_read(int reg)
|
||||
{
|
||||
/* Only read from I2C if we don't already know the value */
|
||||
if(ak4376.regs[reg] < 0)
|
||||
ak4376.regs[reg] = i2c_reg_read1(AK4376_BUS, AK4376_ADDR, reg);
|
||||
|
||||
return ak4376.regs[reg];
|
||||
}
|
||||
|
||||
static int round_step_up(int x, int step)
|
||||
{
|
||||
int rem = x % step;
|
||||
if(rem > 0)
|
||||
rem -= step;
|
||||
return x - rem;
|
||||
}
|
||||
|
||||
static void calc_volumes(int vol, int* mix, int* dig, int* sw)
|
||||
{
|
||||
/* Mixer can divide by 2, which gives an extra -6 dB adjustment */
|
||||
if(vol < AK4376_DIG_VOLUME_MIN) {
|
||||
*mix |= AK4376_MIX_HALF;
|
||||
vol += 60;
|
||||
}
|
||||
|
||||
*dig = round_step_up(vol, AK4376_DIG_VOLUME_STEP);
|
||||
*dig = MIN(*dig, AK4376_DIG_VOLUME_MAX);
|
||||
*dig = MAX(*dig, AK4376_DIG_VOLUME_MIN);
|
||||
vol -= *dig;
|
||||
|
||||
/* Seems that this is the allowable range for software volume */
|
||||
*sw = MIN(vol, 60);
|
||||
*sw = MAX(*sw, -730);
|
||||
vol -= *sw;
|
||||
}
|
||||
|
||||
static int dig_vol_to_hw(int vol)
|
||||
{
|
||||
if(vol < AK4376_DIG_VOLUME_MIN) return 0;
|
||||
if(vol > AK4376_DIG_VOLUME_MAX) return 31;
|
||||
return (vol - AK4376_DIG_VOLUME_MIN) / AK4376_DIG_VOLUME_STEP + 1;
|
||||
}
|
||||
|
||||
static int amp_vol_to_hw(int vol)
|
||||
{
|
||||
if(vol < AK4376_AMP_VOLUME_MIN) return 0;
|
||||
if(vol > AK4376_AMP_VOLUME_MAX) return 14;
|
||||
return (vol - AK4376_AMP_VOLUME_MIN) / AK4376_AMP_VOLUME_STEP + 1;
|
||||
}
|
||||
|
||||
void audiohw_set_volume(int vol_l, int vol_r)
|
||||
{
|
||||
int amp;
|
||||
int mix_l = AK4376_MIX_LCH, dig_l, sw_l;
|
||||
int mix_r = AK4376_MIX_RCH, dig_r, sw_r;
|
||||
|
||||
if(vol_l <= AK4376_MIN_VOLUME && vol_r <= AK4376_MIN_VOLUME) {
|
||||
/* Special case for full mute */
|
||||
amp = AK4376_AMP_VOLUME_MUTE;
|
||||
dig_l = dig_r = AK4376_DIG_VOLUME_MUTE;
|
||||
sw_l = sw_r = PCM_MUTE_LEVEL;
|
||||
} else {
|
||||
/* Amp is a mono control -- calculate based on the loudest channel.
|
||||
* The quieter channel then gets reduced more by digital controls. */
|
||||
amp = round_step_up(MAX(vol_l, vol_r), AK4376_AMP_VOLUME_STEP);
|
||||
amp = MIN(amp, AK4376_AMP_VOLUME_MAX);
|
||||
amp = MAX(amp, AK4376_AMP_VOLUME_MIN);
|
||||
|
||||
/* Other controls are stereo */
|
||||
calc_volumes(vol_l - amp, &mix_l, &dig_l, &sw_l);
|
||||
calc_volumes(vol_r - amp, &mix_r, &dig_r, &sw_r);
|
||||
}
|
||||
|
||||
ak4376_write(AK4376_REG_MIXER, (mix_l & 0xf) | ((mix_r & 0xf) << 4));
|
||||
ak4376_write(AK4376_REG_LCH_VOLUME, dig_vol_to_hw(dig_l) | (1 << 7));
|
||||
ak4376_write(AK4376_REG_RCH_VOLUME, dig_vol_to_hw(dig_r));
|
||||
ak4376_write(AK4376_REG_AMP_VOLUME, amp_vol_to_hw(amp));
|
||||
pcm_set_master_volume(sw_l, sw_r);
|
||||
}
|
||||
|
||||
void audiohw_set_filter_roll_off(int val)
|
||||
{
|
||||
int reg = ak4376_read(AK4376_REG_FILTER);
|
||||
reg &= ~0xc0;
|
||||
reg |= (val & 3) << 6;
|
||||
ak4376_write(AK4376_REG_FILTER, reg);
|
||||
}
|
||||
|
||||
void audiohw_set_frequency(int fsel)
|
||||
{
|
||||
/* Determine master clock multiplier */
|
||||
int mult = ak4376_set_mclk_freq(fsel, false);
|
||||
|
||||
/* Calculate clock mode for frequency. Multipliers of 32/64 are only
|
||||
* for rates >= 256 KHz which are not supported by Rockbox, so they
|
||||
* are commented out -- but they're in the correct place. */
|
||||
int clock_mode = ak4376_fsel_to_hw[fsel];
|
||||
switch(mult) {
|
||||
/* case 32: */
|
||||
case 256:
|
||||
break;
|
||||
/* case 64: */
|
||||
case 512:
|
||||
clock_mode |= 0x20;
|
||||
break;
|
||||
case 1024:
|
||||
clock_mode |= 0x40;
|
||||
break;
|
||||
case 128:
|
||||
clock_mode |= 0x60;
|
||||
break;
|
||||
default:
|
||||
panicf("ak4376: bad master clock multiple %d", mult);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle the DSMLP bit in the MODE_CTRL register */
|
||||
int mode_ctrl = 0x00;
|
||||
if(ak4376.low_mode || hw_freq_sampr[fsel] <= SAMPR_12)
|
||||
mode_ctrl |= 0x40;
|
||||
|
||||
/* Program the new settings */
|
||||
ak4376_write(AK4376_REG_CLOCK_MODE, clock_mode);
|
||||
ak4376_write(AK4376_REG_MODE_CTRL, mode_ctrl);
|
||||
ak4376_write(AK4376_REG_PWR3, ak4376.low_mode ? 0x11 : 0x01);
|
||||
|
||||
/* Enable the master clock */
|
||||
ak4376_set_mclk_freq(fsel, true);
|
||||
|
||||
/* Remember the frequency */
|
||||
ak4376.fsel = fsel;
|
||||
}
|
||||
|
||||
void audiohw_set_power_mode(int mode)
|
||||
{
|
||||
/* This is handled via audiohw_set_frequency() since changing LPMODE
|
||||
* bit requires power-down/power-up & changing other bits as well */
|
||||
if(ak4376.low_mode != mode) {
|
||||
ak4376.low_mode = mode;
|
||||
audiohw_set_frequency(ak4376.fsel);
|
||||
}
|
||||
}
|
||||
419
firmware/drivers/axp173.c
Normal file
419
firmware/drivers/axp173.c
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* 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 "axp173.h"
|
||||
#include "power.h"
|
||||
#include "i2c-async.h"
|
||||
|
||||
/* Headers for the debug menu */
|
||||
#ifndef BOOTLOADER
|
||||
# include "action.h"
|
||||
# include "list.h"
|
||||
# include <stdio.h>
|
||||
#endif
|
||||
|
||||
static const struct axp173_adc_info {
|
||||
uint8_t reg;
|
||||
uint8_t en_reg;
|
||||
uint8_t en_bit;
|
||||
} axp173_adc_info[NUM_ADC_CHANNELS] = {
|
||||
{0x56, 0x82, 5}, /* ACIN_VOLTAGE */
|
||||
{0x58, 0x82, 4}, /* ACIN_CURRENT */
|
||||
{0x5a, 0x82, 3}, /* VBUS_VOLTAGE */
|
||||
{0x5c, 0x82, 2}, /* VBUS_CURRENT */
|
||||
{0x5e, 0x83, 7}, /* INTERNAL_TEMP */
|
||||
{0x62, 0x82, 1}, /* TS_INPUT */
|
||||
{0x78, 0x82, 7}, /* BATTERY_VOLTAGE */
|
||||
{0x7a, 0x82, 6}, /* CHARGE_CURRENT */
|
||||
{0x7c, 0x82, 6}, /* DISCHARGE_CURRENT */
|
||||
{0x7e, 0x82, 1}, /* APS_VOLTAGE */
|
||||
{0x70, 0xff, 0}, /* BATTERY_POWER */
|
||||
};
|
||||
|
||||
static struct axp173 {
|
||||
int adc_enable;
|
||||
} axp173;
|
||||
|
||||
static void axp173_init_enabled_adcs(void)
|
||||
{
|
||||
axp173.adc_enable = 0;
|
||||
|
||||
/* Read enabled ADCs from the hardware */
|
||||
uint8_t regs[2];
|
||||
int rc = i2c_reg_read(AXP173_BUS, AXP173_ADDR, 0x82, 2, ®s[0]);
|
||||
if(rc != I2C_STATUS_OK)
|
||||
return;
|
||||
|
||||
/* Parse registers to set ADC enable bits */
|
||||
const struct axp173_adc_info* info = axp173_adc_info;
|
||||
for(int i = 0; i < NUM_ADC_CHANNELS; ++i) {
|
||||
if(info[i].en_reg == 0xff)
|
||||
continue;
|
||||
|
||||
if(regs[info[i].en_reg - 0x82] & info[i].en_bit)
|
||||
axp173.adc_enable |= 1 << i;
|
||||
}
|
||||
|
||||
/* Handle battery power ADC */
|
||||
if((axp173.adc_enable & (1 << ADC_BATTERY_VOLTAGE)) &&
|
||||
(axp173.adc_enable & (1 << ADC_DISCHARGE_CURRENT))) {
|
||||
axp173.adc_enable |= (1 << ADC_BATTERY_POWER);
|
||||
}
|
||||
}
|
||||
|
||||
void axp173_init(void)
|
||||
{
|
||||
axp173_init_enabled_adcs();
|
||||
|
||||
/* We need discharge current ADC to reliably poll for a full battery */
|
||||
int bits = axp173.adc_enable;
|
||||
bits |= (1 << ADC_DISCHARGE_CURRENT);
|
||||
axp173_adc_set_enabled(bits);
|
||||
}
|
||||
|
||||
/* TODO: this can STILL indicate some false positives! */
|
||||
int axp173_battery_status(void)
|
||||
{
|
||||
int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, 0x00);
|
||||
if(r >= 0) {
|
||||
/* Charging bit indicates we're currently charging */
|
||||
if((r & 0x04) != 0)
|
||||
return AXP173_BATT_CHARGING;
|
||||
|
||||
/* Not plugged in means we're discharging */
|
||||
if((r & 0xf0) == 0)
|
||||
return AXP173_BATT_DISCHARGING;
|
||||
} else {
|
||||
/* Report discharging if we can't find out power status */
|
||||
return AXP173_BATT_DISCHARGING;
|
||||
}
|
||||
|
||||
/* If the battery is full and not in use, the charging bit will be 0,
|
||||
* there will be an external power source, AND the discharge current
|
||||
* will be zero. Seems to rule out all false positives. */
|
||||
int d = axp173_adc_read_raw(ADC_DISCHARGE_CURRENT);
|
||||
if(d == 0)
|
||||
return AXP173_BATT_FULL;
|
||||
|
||||
return AXP173_BATT_DISCHARGING;
|
||||
}
|
||||
|
||||
int axp173_input_status(void)
|
||||
{
|
||||
#ifdef HAVE_BATTERY_SWITCH
|
||||
int input_status = 0;
|
||||
#else
|
||||
int input_status = AXP173_INPUT_BATTERY;
|
||||
#endif
|
||||
|
||||
int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, 0x00);
|
||||
if(r < 0)
|
||||
return input_status;
|
||||
|
||||
/* Check for AC input */
|
||||
if(r & 0x80)
|
||||
input_status |= AXP173_INPUT_AC;
|
||||
|
||||
/* Only report USB if ACIN and VBUS are not shorted */
|
||||
if((r & 0x20) != 0 && (r & 0x02) == 0)
|
||||
input_status |= AXP173_INPUT_USB;
|
||||
|
||||
#ifdef HAVE_BATTERY_SWITCH
|
||||
/* Check for battery presence if target defines it as removable */
|
||||
r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, 0x01);
|
||||
if(r >= 0 && (r & 0x20) != 0)
|
||||
input_status |= AXP173_INPUT_BATTERY;
|
||||
#endif
|
||||
|
||||
return input_status;
|
||||
}
|
||||
|
||||
int axp173_adc_read(int adc)
|
||||
{
|
||||
int value = axp173_adc_read_raw(adc);
|
||||
if(value == INT_MIN)
|
||||
return INT_MIN;
|
||||
|
||||
return axp173_adc_conv_raw(adc, value);
|
||||
}
|
||||
|
||||
int axp173_adc_read_raw(int adc)
|
||||
{
|
||||
/* Don't give a reading if the ADC is not enabled */
|
||||
if((axp173.adc_enable & (1 << adc)) == 0)
|
||||
return INT_MIN;
|
||||
|
||||
/* Read the ADC */
|
||||
uint8_t buf[3];
|
||||
int count = (adc == ADC_BATTERY_POWER) ? 3 : 2;
|
||||
uint8_t reg = axp173_adc_info[adc].reg;
|
||||
int rc = i2c_reg_read(AXP173_BUS, AXP173_ADDR, reg, count, &buf[0]);
|
||||
if(rc != I2C_STATUS_OK)
|
||||
return INT_MIN;
|
||||
|
||||
/* Parse the value */
|
||||
if(adc == ADC_BATTERY_POWER)
|
||||
return (buf[0] << 16) | (buf[1] << 8) | buf[2];
|
||||
else if(adc == ADC_CHARGE_CURRENT || adc == ADC_DISCHARGE_CURRENT)
|
||||
return (buf[0] << 5) | (buf[1] & 0x1f);
|
||||
else
|
||||
return (buf[0] << 4) | (buf[1] & 0xf);
|
||||
}
|
||||
|
||||
int axp173_adc_conv_raw(int adc, int value)
|
||||
{
|
||||
switch(adc) {
|
||||
case ADC_ACIN_VOLTAGE:
|
||||
case ADC_VBUS_VOLTAGE:
|
||||
/* 0 mV ... 6.9615 mV, step 1.7 mV */
|
||||
return value * 17 / 10;
|
||||
case ADC_ACIN_CURRENT:
|
||||
/* 0 mA ... 2.5594 A, step 0.625 mA */
|
||||
return value * 5 / 8;
|
||||
case ADC_VBUS_CURRENT:
|
||||
/* 0 mA ... 1.5356 A, step 0.375 mA */
|
||||
return value * 3 / 8;
|
||||
case ADC_INTERNAL_TEMP:
|
||||
/* -144.7 C ... 264.8 C, step 0.1 C */
|
||||
return value - 1447;
|
||||
case ADC_TS_INPUT:
|
||||
/* 0 mV ... 3.276 V, step 0.8 mV */
|
||||
return value * 4 / 5;
|
||||
case ADC_BATTERY_VOLTAGE:
|
||||
/* 0 mV ... 4.5045 V, step 1.1 mV */
|
||||
return value * 11 / 10;
|
||||
case ADC_CHARGE_CURRENT:
|
||||
case ADC_DISCHARGE_CURRENT:
|
||||
/* 0 mA to 4.095 A, step 0.5 mA */
|
||||
return value / 2;
|
||||
case ADC_APS_VOLTAGE:
|
||||
/* 0 mV to 5.733 V, step 1.4 mV */
|
||||
return value * 7 / 5;
|
||||
case ADC_BATTERY_POWER:
|
||||
/* 0 uW to 23.6404 W, step 0.55 uW */
|
||||
return value * 11 / 20;
|
||||
default:
|
||||
/* Shouldn't happen */
|
||||
return INT_MIN;
|
||||
}
|
||||
}
|
||||
|
||||
int axp173_adc_get_enabled(void)
|
||||
{
|
||||
return axp173.adc_enable;
|
||||
}
|
||||
|
||||
void axp173_adc_set_enabled(int adc_bits)
|
||||
{
|
||||
/* Ignore no-op */
|
||||
if(adc_bits == axp173.adc_enable)
|
||||
return;
|
||||
|
||||
/* Compute the new register values */
|
||||
const struct axp173_adc_info* info = axp173_adc_info;
|
||||
uint8_t regs[2] = {0, 0};
|
||||
for(int i = 0; i < NUM_ADC_CHANNELS; ++i) {
|
||||
if(info[i].en_reg == 0xff)
|
||||
continue;
|
||||
|
||||
if(adc_bits & (1 << i))
|
||||
regs[info[i].en_reg - 0x82] |= 1 << info[i].en_bit;
|
||||
}
|
||||
|
||||
/* These ADCs share an enable bit */
|
||||
if(adc_bits & ((1 << ADC_CHARGE_CURRENT)|(1 << ADC_DISCHARGE_CURRENT))) {
|
||||
adc_bits |= (1 << ADC_CHARGE_CURRENT);
|
||||
adc_bits |= (1 << ADC_DISCHARGE_CURRENT);
|
||||
}
|
||||
|
||||
/* Enable required bits for battery power ADC */
|
||||
if(adc_bits & (1 << ADC_BATTERY_POWER)) {
|
||||
regs[0] |= 1 << info[ADC_DISCHARGE_CURRENT].en_bit;
|
||||
regs[0] |= 1 << info[ADC_BATTERY_VOLTAGE].en_bit;
|
||||
}
|
||||
|
||||
/* Update the configuration */
|
||||
i2c_reg_write(AXP173_BUS, AXP173_ADDR, 0x82, 2, ®s[0]);
|
||||
axp173.adc_enable = adc_bits;
|
||||
}
|
||||
|
||||
int axp173_adc_get_rate(void)
|
||||
{
|
||||
int r = i2c_reg_read1(AXP173_BUS, AXP173_ADDR, 0x84);
|
||||
if(r < 0)
|
||||
return AXP173_ADC_RATE_100HZ; /* an arbitrary value */
|
||||
|
||||
return (r >> 6) & 3;
|
||||
}
|
||||
|
||||
void axp173_adc_set_rate(int rate)
|
||||
{
|
||||
i2c_reg_modify1(AXP173_BUS, AXP173_ADDR, 0x84,
|
||||
0xc0, (rate & 3) << 6, NULL);
|
||||
}
|
||||
|
||||
static uint32_t axp173_cc_parse(const uint8_t* buf)
|
||||
{
|
||||
return ((uint32_t)buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
||||
}
|
||||
|
||||
void axp173_cc_read(uint32_t* charge, uint32_t* discharge)
|
||||
{
|
||||
uint8_t buf[8];
|
||||
int rc = i2c_reg_read(AXP173_BUS, AXP173_ADDR, 0xb0, 8, &buf[0]);
|
||||
if(rc != I2C_STATUS_OK) {
|
||||
if(charge)
|
||||
*charge = 0;
|
||||
if(discharge)
|
||||
*discharge = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(charge)
|
||||
*charge = axp173_cc_parse(&buf[0]);
|
||||
if(discharge)
|
||||
*discharge = axp173_cc_parse(&buf[4]);
|
||||
}
|
||||
|
||||
void axp173_cc_clear(void)
|
||||
{
|
||||
i2c_reg_setbit1(AXP173_BUS, AXP173_ADDR, 0xb8, 5, 1, NULL);
|
||||
}
|
||||
|
||||
void axp173_cc_enable(bool en)
|
||||
{
|
||||
i2c_reg_setbit1(AXP173_BUS, AXP173_ADDR, 0xb8, 7, en ? 1 : 0, NULL);
|
||||
}
|
||||
|
||||
#ifndef BOOTLOADER
|
||||
#define AXP173_DEBUG_BATTERY_STATUS 0
|
||||
#define AXP173_DEBUG_INPUT_STATUS 1
|
||||
#define AXP173_DEBUG_ADC_RATE 2
|
||||
#define AXP173_DEBUG_FIRST_ADC 3
|
||||
#define AXP173_DEBUG_ENTRIES (AXP173_DEBUG_FIRST_ADC + NUM_ADC_CHANNELS)
|
||||
|
||||
static int axp173_debug_menu_cb(int action, struct gui_synclist* lists)
|
||||
{
|
||||
(void)lists;
|
||||
|
||||
if(action == ACTION_NONE)
|
||||
action = ACTION_REDRAW;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
static const char* axp173_debug_menu_get_name(int item, void* data,
|
||||
char* buf, size_t buflen)
|
||||
{
|
||||
(void)data;
|
||||
|
||||
static const char* const adc_names[] = {
|
||||
"V_acin", "I_acin", "V_vbus", "I_vbus", "T_int",
|
||||
"V_ts", "V_batt", "I_chrg", "I_dchg", "V_aps", "P_batt"
|
||||
};
|
||||
|
||||
static const char* const adc_units[] = {
|
||||
"mV", "mA", "mV", "mA", "C", "mV", "mV", "mA", "mA", "mV", "uW",
|
||||
};
|
||||
|
||||
int adc = item - AXP173_DEBUG_FIRST_ADC;
|
||||
if(item >= AXP173_DEBUG_FIRST_ADC && adc < NUM_ADC_CHANNELS) {
|
||||
int raw_value = axp173_adc_read_raw(adc);
|
||||
if(raw_value == INT_MIN) {
|
||||
snprintf(buf, buflen, "%s: [Disabled]", adc_names[adc]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int value = axp173_adc_conv_raw(adc, raw_value);
|
||||
if(adc == ADC_INTERNAL_TEMP) {
|
||||
snprintf(buf, buflen, "%s: %d.%d %s", adc_names[adc],
|
||||
value/10, value%10, adc_units[adc]);
|
||||
} else {
|
||||
snprintf(buf, buflen, "%s: %d %s", adc_names[adc],
|
||||
value, adc_units[adc]);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
switch(item) {
|
||||
case AXP173_DEBUG_BATTERY_STATUS: {
|
||||
switch(axp173_battery_status()) {
|
||||
case AXP173_BATT_FULL:
|
||||
return "Battery: Full";
|
||||
case AXP173_BATT_CHARGING:
|
||||
return "Battery: Charging";
|
||||
case AXP173_BATT_DISCHARGING:
|
||||
return "Battery: Discharging";
|
||||
default:
|
||||
return "Battery: Unknown";
|
||||
}
|
||||
} break;
|
||||
|
||||
case AXP173_DEBUG_INPUT_STATUS: {
|
||||
int s = axp173_input_status();
|
||||
const char* ac = (s & AXP173_INPUT_AC) ? " AC" : "";
|
||||
const char* usb = (s & AXP173_INPUT_USB) ? " USB" : "";
|
||||
const char* batt = (s & AXP173_INPUT_BATTERY) ? " Battery" : "";
|
||||
snprintf(buf, buflen, "Inputs:%s%s%s", ac, usb, batt);
|
||||
return buf;
|
||||
} break;
|
||||
|
||||
case AXP173_DEBUG_ADC_RATE: {
|
||||
int rate = 25 << axp173_adc_get_rate();
|
||||
snprintf(buf, buflen, "ADC sample rate: %d Hz", rate);
|
||||
return buf;
|
||||
} break;
|
||||
|
||||
default:
|
||||
return "---";
|
||||
}
|
||||
}
|
||||
|
||||
bool axp173_debug_menu(void)
|
||||
{
|
||||
struct simplelist_info info;
|
||||
simplelist_info_init(&info, "AXP173 debug", AXP173_DEBUG_ENTRIES, NULL);
|
||||
info.action_callback = axp173_debug_menu_cb;
|
||||
info.get_name = axp173_debug_menu_get_name;
|
||||
return simplelist_show_list(&info);
|
||||
}
|
||||
#endif /* !BOOTLOADER */
|
||||
|
||||
/* This is basically the only valid implementation, so define it here */
|
||||
unsigned int power_input_status(void)
|
||||
{
|
||||
unsigned int state = 0;
|
||||
int input_status = axp173_input_status();
|
||||
|
||||
if(input_status & AXP173_INPUT_AC)
|
||||
state |= POWER_INPUT_MAIN_CHARGER;
|
||||
|
||||
if(input_status & AXP173_INPUT_USB)
|
||||
state |= POWER_INPUT_USB_CHARGER;
|
||||
|
||||
#ifdef HAVE_BATTERY_SWITCH
|
||||
if(input_status & AXP173_INPUT_BATTERY)
|
||||
state |= POWER_INPUT_BATTERY;
|
||||
#endif
|
||||
|
||||
return state;
|
||||
}
|
||||
104
firmware/drivers/rtc/rtc_x1000.c
Normal file
104
firmware/drivers/rtc/rtc_x1000.c
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* Based mainly on rtc_jz4760.c,
|
||||
* Copyright (C) 2016 by Roman Stolyarov
|
||||
*
|
||||
* 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 "rtc.h"
|
||||
#include "x1000/rtc.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* 4 byte magic number 'RTCV' */
|
||||
#define RTCV 0x52544356
|
||||
|
||||
/* expected RTC clock frequency */
|
||||
#define NC1HZ_EXPECTED (32768 - 1)
|
||||
|
||||
static void rtc_write_reg(uint32_t addr, uint32_t value)
|
||||
{
|
||||
while(jz_readf(RTC_CR, WRDY) == 0);
|
||||
REG_RTC_WENR = 0xa55a;
|
||||
while(jz_readf(RTC_WENR, WEN) == 0);
|
||||
while(jz_readf(RTC_CR, WRDY) == 0);
|
||||
(*(volatile uint32_t*)addr) = value;
|
||||
while(jz_readf(RTC_CR, WRDY) == 0);
|
||||
}
|
||||
|
||||
void rtc_init(void)
|
||||
{
|
||||
/* Check if we totally lost power and need to reset the RTC */
|
||||
if(REG_RTC_HSPR != RTCV || jz_readf(RTC_GR, NC1HZ) != NC1HZ_EXPECTED) {
|
||||
rtc_write_reg(JA_RTC_GR, NC1HZ_EXPECTED);
|
||||
rtc_write_reg(JA_RTC_HWFCR, 3200);
|
||||
rtc_write_reg(JA_RTC_HRCR, 2048);
|
||||
rtc_write_reg(JA_RTC_SR, 1546300800); /* 01/01/2019 */
|
||||
rtc_write_reg(JA_RTC_CR, 1);
|
||||
rtc_write_reg(JA_RTC_HSPR, RTCV);
|
||||
}
|
||||
|
||||
rtc_write_reg(JA_RTC_HWRSR, 0);
|
||||
}
|
||||
|
||||
int rtc_read_datetime(struct tm* tm)
|
||||
{
|
||||
time_t time = REG_RTC_SR;
|
||||
gmtime_r(&time, tm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rtc_write_datetime(const struct tm* tm)
|
||||
{
|
||||
time_t time = mktime((struct tm*)tm);
|
||||
rtc_write_reg(JA_RTC_SR, time);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_RTC_ALARM
|
||||
/* TODO: implement the RTC alarm */
|
||||
|
||||
void rtc_set_alarm(int h, int m)
|
||||
{
|
||||
(void)h;
|
||||
(void)m;
|
||||
}
|
||||
|
||||
void rtc_get_alarm(int* h, int* m)
|
||||
{
|
||||
(void)h;
|
||||
(void)m;
|
||||
}
|
||||
|
||||
void rtc_enable_alarm(bool enable)
|
||||
{
|
||||
(void)enable;
|
||||
}
|
||||
|
||||
bool rtc_check_alarm_started(bool release_alarm)
|
||||
{
|
||||
(void)release_alarm;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rtc_check_alarm_flag(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue