mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-09 13:12:37 -05:00
New port: Shanling Q1 native
- Audio playback works - Touchscreen and buttons work - Bootloader works and is capable of dual boot - Plugins are working - Cabbiev2 theme has been ported - Stable for general usage Thanks to Marc Aarts for porting Cabbiev2 and plugin bitmaps. There's a few minor known issues: - Bootloader must be installed manually using 'usbboot' as there is no support in jztool yet. - Keymaps may be lacking, need further testing and feedback. - Some plugins may not be fully adapted to the screen size and could benefit from further tweaking. - LCD shows abnormal effects under some circumstances: for example, after viewing a mostly black screen an afterimage appears briefly when going back to a brightly-lit screen. Sudden power-off without proper shutdown of the backlight causes a "dissolving" effect. - CW2015 battery reporting driver is buggy, and disabled for now. Battery reporting is currently voltage-based using the AXP192. Change-Id: I635e83f02a880192c5a82cb0861ad3a61c137c3a
This commit is contained in:
parent
3abb7c5dd5
commit
4c60bc9e68
110 changed files with 2843 additions and 15 deletions
226
firmware/drivers/audio/es9218.c
Normal file
226
firmware/drivers/audio/es9218.c
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "i2c-async.h"
|
||||
|
||||
struct es9218_state {
|
||||
enum es9218_clock_gear clk_gear;
|
||||
uint32_t fsr;
|
||||
uint32_t nco;
|
||||
};
|
||||
|
||||
static struct es9218_state es9218;
|
||||
|
||||
void es9218_open(void)
|
||||
{
|
||||
/* Enable power supply */
|
||||
es9218_set_power_pin(1);
|
||||
|
||||
/* "Wiggle" reset pin to get the internal oscillator to stabilize.
|
||||
* This should also work if using an external powered oscillator,
|
||||
* although in that case it's unnecessary to do this dance. */
|
||||
es9218_set_reset_pin(1);
|
||||
udelay(75);
|
||||
es9218_set_reset_pin(0);
|
||||
udelay(50);
|
||||
es9218_set_reset_pin(1);
|
||||
mdelay(2);
|
||||
|
||||
/* Initialize driver state */
|
||||
es9218.clk_gear = ES9218_CLK_GEAR_1;
|
||||
es9218.fsr = 0;
|
||||
es9218.nco = 0;
|
||||
}
|
||||
|
||||
void es9218_close(void)
|
||||
{
|
||||
/* Turn off power supply */
|
||||
es9218_set_power_pin(0);
|
||||
es9218_set_reset_pin(0);
|
||||
}
|
||||
|
||||
static void recalc_nco(void)
|
||||
{
|
||||
/* nco * CLK *
|
||||
* fsr = --------- *
|
||||
* 2**32 */
|
||||
|
||||
uint32_t clk = es9218_get_mclk();
|
||||
clk >>= (int)es9218.clk_gear;
|
||||
|
||||
uint64_t nco64 = es9218.fsr;
|
||||
nco64 <<= 32;
|
||||
nco64 /= clk;
|
||||
|
||||
/* let's just ignore overflow... */
|
||||
uint32_t nco = nco64;
|
||||
if(nco != es9218.nco) {
|
||||
es9218.nco = nco;
|
||||
|
||||
/* registers must be written in this order */
|
||||
es9218_write(ES9218_REG_PROG_NCO_BIT0_7, (nco >> 0) & 0xff);
|
||||
es9218_write(ES9218_REG_PROG_NCO_BIT8_15, (nco >> 8) & 0xff);
|
||||
es9218_write(ES9218_REG_PROG_NCO_BIT16_23, (nco >> 16) & 0xff);
|
||||
es9218_write(ES9218_REG_PROG_NCO_BIT24_31, (nco >> 24) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
void es9218_set_clock_gear(enum es9218_clock_gear gear)
|
||||
{
|
||||
if(gear != es9218.clk_gear) {
|
||||
es9218.clk_gear = gear;
|
||||
es9218_update(ES9218_REG_SYSTEM, 0x0c, (uint8_t)(gear & 3) << 2);
|
||||
recalc_nco();
|
||||
}
|
||||
}
|
||||
|
||||
void es9218_set_nco_frequency(uint32_t fsr)
|
||||
{
|
||||
if(fsr != es9218.fsr) {
|
||||
es9218.fsr = fsr;
|
||||
recalc_nco();
|
||||
}
|
||||
}
|
||||
|
||||
void es9218_recompute_nco(void)
|
||||
{
|
||||
recalc_nco();
|
||||
}
|
||||
|
||||
void es9218_set_amp_mode(enum es9218_amp_mode mode)
|
||||
{
|
||||
es9218_update(ES9218_REG_AMP_CONFIG, 0x03, (uint8_t)mode & 3);
|
||||
}
|
||||
|
||||
void es9218_set_amp_powered(bool en)
|
||||
{
|
||||
/* this doesn't seem to be necessary..? */
|
||||
es9218_update(ES9218_REG_ANALOG_CTRL, 0x40, en ? 0x40 : 0x00);
|
||||
}
|
||||
|
||||
void es9218_set_iface_role(enum es9218_iface_role role)
|
||||
{
|
||||
/* asrc is used to lock onto the incoming audio frequency and is
|
||||
* only used in aysnchronous slave mode. In synchronous operation,
|
||||
* including master mode, it can be disabled to save power. */
|
||||
int asrc_en = (role == ES9218_IFACE_ROLE_SLAVE ? 1 : 0);
|
||||
int master_mode = (role == ES9218_IFACE_ROLE_MASTER ? 1 : 0);
|
||||
|
||||
es9218_update(ES9218_REG_MASTER_MODE_CONFIG, 1 << 7, master_mode << 7);
|
||||
es9218_update(ES9218_REG_GENERAL_CONFIG, 1 << 7, asrc_en << 7);
|
||||
}
|
||||
|
||||
void es9218_set_iface_format(enum es9218_iface_format fmt,
|
||||
enum es9218_iface_bits bits)
|
||||
{
|
||||
uint8_t val = 0;
|
||||
val |= ((uint8_t)bits & 3) << 6;
|
||||
val |= ((uint8_t)fmt & 3) << 4;
|
||||
/* keep low 4 bits zero -> use normal I2S mode, disable DSD mode */
|
||||
es9218_write(ES9218_REG_INPUT_SEL, val);
|
||||
}
|
||||
|
||||
static int dig_vol_to_hw(int x)
|
||||
{
|
||||
x = MIN(x, ES9218_DIG_VOLUME_MAX);
|
||||
x = MAX(x, ES9218_DIG_VOLUME_MIN);
|
||||
return 0xff - (x - ES9218_DIG_VOLUME_MIN) / ES9218_DIG_VOLUME_STEP;
|
||||
}
|
||||
|
||||
static int amp_vol_to_hw(int x)
|
||||
{
|
||||
x = MIN(x, ES9218_AMP_VOLUME_MAX);
|
||||
x = MAX(x, ES9218_AMP_VOLUME_MIN);
|
||||
return 24 - (x - ES9218_AMP_VOLUME_MIN) / ES9218_AMP_VOLUME_STEP;
|
||||
}
|
||||
|
||||
void es9218_set_dig_volume(int vol_l, int vol_r)
|
||||
{
|
||||
es9218_write(ES9218_REG_VOLUME_LEFT, dig_vol_to_hw(vol_l));
|
||||
es9218_write(ES9218_REG_VOLUME_RIGHT, dig_vol_to_hw(vol_r));
|
||||
}
|
||||
|
||||
void es9218_set_amp_volume(int vol)
|
||||
{
|
||||
es9218_update(ES9218_REG_ANALOG_VOL, 0x1f, amp_vol_to_hw(vol));
|
||||
}
|
||||
|
||||
void es9218_mute(bool en)
|
||||
{
|
||||
es9218_update(ES9218_REG_FILTER_SYS_MUTE, 1, en ? 1 : 0);
|
||||
}
|
||||
|
||||
void es9218_set_filter(enum es9218_filter_type filt)
|
||||
{
|
||||
es9218_update(ES9218_REG_FILTER_SYS_MUTE, 0xe0, ((int)filt & 7) << 5);
|
||||
}
|
||||
|
||||
void es9218_set_automute_time(int time)
|
||||
{
|
||||
if(time < 0) time = 0;
|
||||
if(time > 255) time = 255;
|
||||
es9218_write(ES9218_REG_AUTOMUTE_TIME, time);
|
||||
}
|
||||
|
||||
void es9218_set_automute_level(int dB)
|
||||
{
|
||||
es9218_update(ES9218_REG_AUTOMUTE_LEVEL, 0x7f, dB);
|
||||
}
|
||||
|
||||
void es9218_set_automute_fast_mode(bool en)
|
||||
{
|
||||
es9218_update(ES9218_REG_MIX_AUTOMUTE, 0x10, en ? 0x10 : 0x00);
|
||||
}
|
||||
|
||||
void es9218_set_dpll_bandwidth(int knob)
|
||||
{
|
||||
es9218_update(ES9218_REG_ASRC_DPLL_BANDWIDTH, 0xf0, (knob & 0xf) << 4);
|
||||
}
|
||||
|
||||
void es9218_set_thd_compensation(bool en)
|
||||
{
|
||||
es9218_update(ES9218_REG_THD_COMP_BYPASS, 0x40, en ? 0x40 : 0);
|
||||
}
|
||||
|
||||
void es9218_set_thd_coeffs(uint16_t c2, uint16_t c3)
|
||||
{
|
||||
es9218_write(ES9218_REG_THD_COMP_C2_LO, c2 & 0xff);
|
||||
es9218_write(ES9218_REG_THD_COMP_C2_HI, (c2 >> 8) & 0xff);
|
||||
es9218_write(ES9218_REG_THD_COMP_C3_LO, c3 & 0xff);
|
||||
es9218_write(ES9218_REG_THD_COMP_C3_HI, (c3 >> 8) & 0xff);
|
||||
}
|
||||
|
||||
int es9218_read(int reg)
|
||||
{
|
||||
return i2c_reg_read1(ES9218_BUS, ES9218_ADDR, reg);
|
||||
}
|
||||
|
||||
void es9218_write(int reg, uint8_t val)
|
||||
{
|
||||
i2c_reg_write1(ES9218_BUS, ES9218_ADDR, reg, val);
|
||||
}
|
||||
|
||||
void es9218_update(int reg, uint8_t msk, uint8_t val)
|
||||
{
|
||||
i2c_reg_modify1(ES9218_BUS, ES9218_ADDR, reg, msk, val, NULL);
|
||||
}
|
||||
191
firmware/drivers/cw2015.c
Normal file
191
firmware/drivers/cw2015.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "cw2015.h"
|
||||
#include "i2c-async.h"
|
||||
#include <string.h>
|
||||
#include "system.h"
|
||||
|
||||
/* Headers for the debug menu */
|
||||
#ifndef BOOTLOADER
|
||||
# include "action.h"
|
||||
# include "list.h"
|
||||
# include <stdio.h>
|
||||
#endif
|
||||
|
||||
/* Battery profile info is an opaque blob. According to this,
|
||||
* https://lore.kernel.org/linux-pm/20200503154855.duwj2djgqfiyleq5@earth.universe/T/#u
|
||||
* the blob only comes from Cellwise testing a physical battery and cannot be
|
||||
* obtained any other way. It's specific to a given battery so each target has
|
||||
* its own profile.
|
||||
*
|
||||
* Profile data seems to be retained on the chip so it's not a hard requirement
|
||||
* to define this. Provided you don't lose power in the meantime, it should be
|
||||
* enough to just boot the OF, then boot Rockbox and read out the battery info
|
||||
* from the CW2015 debug screen.
|
||||
*/
|
||||
#if defined(SHANLING_Q1)
|
||||
static const uint8_t device_batinfo[CW2015_SIZE_BATINFO] = {
|
||||
0x15, 0x7E, 0x61, 0x59, 0x57, 0x55, 0x56, 0x4C,
|
||||
0x4E, 0x4D, 0x50, 0x4C, 0x45, 0x3A, 0x2D, 0x27,
|
||||
0x22, 0x1E, 0x19, 0x1E, 0x2A, 0x3C, 0x48, 0x45,
|
||||
0x1D, 0x94, 0x08, 0xF6, 0x15, 0x29, 0x48, 0x51,
|
||||
0x5D, 0x60, 0x63, 0x66, 0x45, 0x1D, 0x83, 0x38,
|
||||
0x09, 0x43, 0x16, 0x42, 0x76, 0x98, 0xA5, 0x1B,
|
||||
0x41, 0x76, 0x99, 0xBF, 0x80, 0xC0, 0xEF, 0xCB,
|
||||
0x2F, 0x00, 0x64, 0xA5, 0xB5, 0x0E, 0x30, 0x29,
|
||||
};
|
||||
#else
|
||||
# define NO_BATINFO
|
||||
#endif
|
||||
|
||||
static uint8_t chip_batinfo[CW2015_SIZE_BATINFO];
|
||||
|
||||
/* TODO: Finish implementing this
|
||||
*
|
||||
* Although this chip might give a better battery estimate than voltage does,
|
||||
* the mainline linux driver has a lot of weird hacks due to oddities like the
|
||||
* SoC getting stuck during charging, and from limited testing it seems this
|
||||
* may occur for the Q1 too.
|
||||
*/
|
||||
|
||||
static int cw2015_read_bat_info(uint8_t* data)
|
||||
{
|
||||
for(int i = 0; i < CW2015_SIZE_BATINFO; ++i) {
|
||||
int r = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_BATINFO + i);
|
||||
if(r < 0)
|
||||
return r;
|
||||
|
||||
data[i] = r & 0xff;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cw2015_init(void)
|
||||
{
|
||||
/* mdelay(100); */
|
||||
int rc = cw2015_read_bat_info(&chip_batinfo[0]);
|
||||
if(rc < 0)
|
||||
memset(chip_batinfo, 0, sizeof(chip_batinfo));
|
||||
}
|
||||
|
||||
int cw2015_get_vcell(void)
|
||||
{
|
||||
int vcell_msb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_VCELL);
|
||||
int vcell_lsb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_VCELL+1);
|
||||
|
||||
if(vcell_msb < 0 || vcell_lsb < 0)
|
||||
return -1;
|
||||
|
||||
/* 14 bits, resolution 305 uV */
|
||||
int v_raw = ((vcell_msb & 0x3f) << 8) | vcell_lsb;
|
||||
return v_raw * 61 / 200;
|
||||
}
|
||||
|
||||
int cw2015_get_soc(void)
|
||||
{
|
||||
int soc_msb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_SOC);
|
||||
|
||||
if(soc_msb < 0)
|
||||
return -1;
|
||||
|
||||
/* MSB is the state of charge in percentage.
|
||||
* the LSB contains fractional information not useful to Rockbox. */
|
||||
return soc_msb & 0xff;
|
||||
}
|
||||
|
||||
int cw2015_get_rrt(void)
|
||||
{
|
||||
int rrt_msb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_RRT_ALERT);
|
||||
int rrt_lsb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_RRT_ALERT+1);
|
||||
|
||||
if(rrt_msb < 0 || rrt_lsb < 0)
|
||||
return -1;
|
||||
|
||||
/* 13 bits, resolution 1 minute */
|
||||
return ((rrt_msb & 0x1f) << 8) | rrt_lsb;
|
||||
}
|
||||
|
||||
const uint8_t* cw2015_get_bat_info(void)
|
||||
{
|
||||
return &chip_batinfo[0];
|
||||
}
|
||||
|
||||
#ifndef BOOTLOADER
|
||||
enum {
|
||||
CW2015_DEBUG_VCELL = 0,
|
||||
CW2015_DEBUG_SOC,
|
||||
CW2015_DEBUG_RRT,
|
||||
CW2015_DEBUG_BATINFO,
|
||||
CW2015_DEBUG_BATINFO_LAST = CW2015_DEBUG_BATINFO + 7,
|
||||
CW2015_DEBUG_NUM_ENTRIES,
|
||||
};
|
||||
|
||||
static int cw2015_debug_menu_cb(int action, struct gui_synclist* lists)
|
||||
{
|
||||
(void)lists;
|
||||
|
||||
if(action == ACTION_NONE)
|
||||
action = ACTION_REDRAW;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
static const char* cw2015_debug_menu_get_name(int item, void* data,
|
||||
char* buf, size_t buflen)
|
||||
{
|
||||
(void)data;
|
||||
|
||||
/* hexdump of battery info */
|
||||
if(item >= CW2015_DEBUG_BATINFO && item <= CW2015_DEBUG_BATINFO_LAST) {
|
||||
int i = item - CW2015_DEBUG_BATINFO;
|
||||
const uint8_t* batinfo = cw2015_get_bat_info();
|
||||
snprintf(buf, buflen, "BatInfo%d: %02x %02x %02x %02x %02x %02x %02x %02x", i,
|
||||
batinfo[8*i + 0], batinfo[8*i + 1], batinfo[8*i + 2], batinfo[8*i + 3],
|
||||
batinfo[8*i + 4], batinfo[8*i + 5], batinfo[8*i + 6], batinfo[8*i + 7]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
switch(item) {
|
||||
case CW2015_DEBUG_VCELL:
|
||||
snprintf(buf, buflen, "VCell: %d mV", cw2015_get_vcell());
|
||||
return buf;
|
||||
case CW2015_DEBUG_SOC:
|
||||
snprintf(buf, buflen, "SOC: %d%%", cw2015_get_soc());
|
||||
return buf;
|
||||
case CW2015_DEBUG_RRT:
|
||||
snprintf(buf, buflen, "Runtime: %d min", cw2015_get_rrt());
|
||||
return buf;
|
||||
default:
|
||||
return "---";
|
||||
}
|
||||
}
|
||||
|
||||
bool cw2015_debug_menu(void)
|
||||
{
|
||||
struct simplelist_info info;
|
||||
simplelist_info_init(&info, "CW2015 debug", CW2015_DEBUG_NUM_ENTRIES, NULL);
|
||||
info.action_callback = cw2015_debug_menu_cb;
|
||||
info.get_name = cw2015_debug_menu_get_name;
|
||||
return simplelist_show_list(&info);
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue