diff options
author | Aidan MacDonald <amachronic@protonmail.com> | 2021-05-23 17:30:58 +0100 |
---|---|---|
committer | Aidan MacDonald <amachronic@protonmail.com> | 2021-07-13 22:01:33 +0100 |
commit | 4c60bc9e681865fcfc149775a1ed7ccd2613d5bf (patch) | |
tree | 99f8d91af2c171cf3843f0c14d41a20d9dc29c4f /firmware/drivers | |
parent | 3abb7c5dd5be2ec6744bfc0a80967b20f1b59e30 (diff) | |
download | rockbox-4c60bc9e681865fcfc149775a1ed7ccd2613d5bf.tar.gz rockbox-4c60bc9e681865fcfc149775a1ed7ccd2613d5bf.zip |
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
Diffstat (limited to 'firmware/drivers')
-rw-r--r-- | firmware/drivers/audio/es9218.c | 226 | ||||
-rw-r--r-- | firmware/drivers/cw2015.c | 191 |
2 files changed, 417 insertions, 0 deletions
diff --git a/firmware/drivers/audio/es9218.c b/firmware/drivers/audio/es9218.c new file mode 100644 index 0000000000..76d387221a --- /dev/null +++ b/firmware/drivers/audio/es9218.c | |||
@@ -0,0 +1,226 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "audiohw.h" | ||
23 | #include "system.h" | ||
24 | #include "i2c-async.h" | ||
25 | |||
26 | struct es9218_state { | ||
27 | enum es9218_clock_gear clk_gear; | ||
28 | uint32_t fsr; | ||
29 | uint32_t nco; | ||
30 | }; | ||
31 | |||
32 | static struct es9218_state es9218; | ||
33 | |||
34 | void es9218_open(void) | ||
35 | { | ||
36 | /* Enable power supply */ | ||
37 | es9218_set_power_pin(1); | ||
38 | |||
39 | /* "Wiggle" reset pin to get the internal oscillator to stabilize. | ||
40 | * This should also work if using an external powered oscillator, | ||
41 | * although in that case it's unnecessary to do this dance. */ | ||
42 | es9218_set_reset_pin(1); | ||
43 | udelay(75); | ||
44 | es9218_set_reset_pin(0); | ||
45 | udelay(50); | ||
46 | es9218_set_reset_pin(1); | ||
47 | mdelay(2); | ||
48 | |||
49 | /* Initialize driver state */ | ||
50 | es9218.clk_gear = ES9218_CLK_GEAR_1; | ||
51 | es9218.fsr = 0; | ||
52 | es9218.nco = 0; | ||
53 | } | ||
54 | |||
55 | void es9218_close(void) | ||
56 | { | ||
57 | /* Turn off power supply */ | ||
58 | es9218_set_power_pin(0); | ||
59 | es9218_set_reset_pin(0); | ||
60 | } | ||
61 | |||
62 | static void recalc_nco(void) | ||
63 | { | ||
64 | /* nco * CLK * | ||
65 | * fsr = --------- * | ||
66 | * 2**32 */ | ||
67 | |||
68 | uint32_t clk = es9218_get_mclk(); | ||
69 | clk >>= (int)es9218.clk_gear; | ||
70 | |||
71 | uint64_t nco64 = es9218.fsr; | ||
72 | nco64 <<= 32; | ||
73 | nco64 /= clk; | ||
74 | |||
75 | /* let's just ignore overflow... */ | ||
76 | uint32_t nco = nco64; | ||
77 | if(nco != es9218.nco) { | ||
78 | es9218.nco = nco; | ||
79 | |||
80 | /* registers must be written in this order */ | ||
81 | es9218_write(ES9218_REG_PROG_NCO_BIT0_7, (nco >> 0) & 0xff); | ||
82 | es9218_write(ES9218_REG_PROG_NCO_BIT8_15, (nco >> 8) & 0xff); | ||
83 | es9218_write(ES9218_REG_PROG_NCO_BIT16_23, (nco >> 16) & 0xff); | ||
84 | es9218_write(ES9218_REG_PROG_NCO_BIT24_31, (nco >> 24) & 0xff); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | void es9218_set_clock_gear(enum es9218_clock_gear gear) | ||
89 | { | ||
90 | if(gear != es9218.clk_gear) { | ||
91 | es9218.clk_gear = gear; | ||
92 | es9218_update(ES9218_REG_SYSTEM, 0x0c, (uint8_t)(gear & 3) << 2); | ||
93 | recalc_nco(); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | void es9218_set_nco_frequency(uint32_t fsr) | ||
98 | { | ||
99 | if(fsr != es9218.fsr) { | ||
100 | es9218.fsr = fsr; | ||
101 | recalc_nco(); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | void es9218_recompute_nco(void) | ||
106 | { | ||
107 | recalc_nco(); | ||
108 | } | ||
109 | |||
110 | void es9218_set_amp_mode(enum es9218_amp_mode mode) | ||
111 | { | ||
112 | es9218_update(ES9218_REG_AMP_CONFIG, 0x03, (uint8_t)mode & 3); | ||
113 | } | ||
114 | |||
115 | void es9218_set_amp_powered(bool en) | ||
116 | { | ||
117 | /* this doesn't seem to be necessary..? */ | ||
118 | es9218_update(ES9218_REG_ANALOG_CTRL, 0x40, en ? 0x40 : 0x00); | ||
119 | } | ||
120 | |||
121 | void es9218_set_iface_role(enum es9218_iface_role role) | ||
122 | { | ||
123 | /* asrc is used to lock onto the incoming audio frequency and is | ||
124 | * only used in aysnchronous slave mode. In synchronous operation, | ||
125 | * including master mode, it can be disabled to save power. */ | ||
126 | int asrc_en = (role == ES9218_IFACE_ROLE_SLAVE ? 1 : 0); | ||
127 | int master_mode = (role == ES9218_IFACE_ROLE_MASTER ? 1 : 0); | ||
128 | |||
129 | es9218_update(ES9218_REG_MASTER_MODE_CONFIG, 1 << 7, master_mode << 7); | ||
130 | es9218_update(ES9218_REG_GENERAL_CONFIG, 1 << 7, asrc_en << 7); | ||
131 | } | ||
132 | |||
133 | void es9218_set_iface_format(enum es9218_iface_format fmt, | ||
134 | enum es9218_iface_bits bits) | ||
135 | { | ||
136 | uint8_t val = 0; | ||
137 | val |= ((uint8_t)bits & 3) << 6; | ||
138 | val |= ((uint8_t)fmt & 3) << 4; | ||
139 | /* keep low 4 bits zero -> use normal I2S mode, disable DSD mode */ | ||
140 | es9218_write(ES9218_REG_INPUT_SEL, val); | ||
141 | } | ||
142 | |||
143 | static int dig_vol_to_hw(int x) | ||
144 | { | ||
145 | x = MIN(x, ES9218_DIG_VOLUME_MAX); | ||
146 | x = MAX(x, ES9218_DIG_VOLUME_MIN); | ||
147 | return 0xff - (x - ES9218_DIG_VOLUME_MIN) / ES9218_DIG_VOLUME_STEP; | ||
148 | } | ||
149 | |||
150 | static int amp_vol_to_hw(int x) | ||
151 | { | ||
152 | x = MIN(x, ES9218_AMP_VOLUME_MAX); | ||
153 | x = MAX(x, ES9218_AMP_VOLUME_MIN); | ||
154 | return 24 - (x - ES9218_AMP_VOLUME_MIN) / ES9218_AMP_VOLUME_STEP; | ||
155 | } | ||
156 | |||
157 | void es9218_set_dig_volume(int vol_l, int vol_r) | ||
158 | { | ||
159 | es9218_write(ES9218_REG_VOLUME_LEFT, dig_vol_to_hw(vol_l)); | ||
160 | es9218_write(ES9218_REG_VOLUME_RIGHT, dig_vol_to_hw(vol_r)); | ||
161 | } | ||
162 | |||
163 | void es9218_set_amp_volume(int vol) | ||
164 | { | ||
165 | es9218_update(ES9218_REG_ANALOG_VOL, 0x1f, amp_vol_to_hw(vol)); | ||
166 | } | ||
167 | |||
168 | void es9218_mute(bool en) | ||
169 | { | ||
170 | es9218_update(ES9218_REG_FILTER_SYS_MUTE, 1, en ? 1 : 0); | ||
171 | } | ||
172 | |||
173 | void es9218_set_filter(enum es9218_filter_type filt) | ||
174 | { | ||
175 | es9218_update(ES9218_REG_FILTER_SYS_MUTE, 0xe0, ((int)filt & 7) << 5); | ||
176 | } | ||
177 | |||
178 | void es9218_set_automute_time(int time) | ||
179 | { | ||
180 | if(time < 0) time = 0; | ||
181 | if(time > 255) time = 255; | ||
182 | es9218_write(ES9218_REG_AUTOMUTE_TIME, time); | ||
183 | } | ||
184 | |||
185 | void es9218_set_automute_level(int dB) | ||
186 | { | ||
187 | es9218_update(ES9218_REG_AUTOMUTE_LEVEL, 0x7f, dB); | ||
188 | } | ||
189 | |||
190 | void es9218_set_automute_fast_mode(bool en) | ||
191 | { | ||
192 | es9218_update(ES9218_REG_MIX_AUTOMUTE, 0x10, en ? 0x10 : 0x00); | ||
193 | } | ||
194 | |||
195 | void es9218_set_dpll_bandwidth(int knob) | ||
196 | { | ||
197 | es9218_update(ES9218_REG_ASRC_DPLL_BANDWIDTH, 0xf0, (knob & 0xf) << 4); | ||
198 | } | ||
199 | |||
200 | void es9218_set_thd_compensation(bool en) | ||
201 | { | ||
202 | es9218_update(ES9218_REG_THD_COMP_BYPASS, 0x40, en ? 0x40 : 0); | ||
203 | } | ||
204 | |||
205 | void es9218_set_thd_coeffs(uint16_t c2, uint16_t c3) | ||
206 | { | ||
207 | es9218_write(ES9218_REG_THD_COMP_C2_LO, c2 & 0xff); | ||
208 | es9218_write(ES9218_REG_THD_COMP_C2_HI, (c2 >> 8) & 0xff); | ||
209 | es9218_write(ES9218_REG_THD_COMP_C3_LO, c3 & 0xff); | ||
210 | es9218_write(ES9218_REG_THD_COMP_C3_HI, (c3 >> 8) & 0xff); | ||
211 | } | ||
212 | |||
213 | int es9218_read(int reg) | ||
214 | { | ||
215 | return i2c_reg_read1(ES9218_BUS, ES9218_ADDR, reg); | ||
216 | } | ||
217 | |||
218 | void es9218_write(int reg, uint8_t val) | ||
219 | { | ||
220 | i2c_reg_write1(ES9218_BUS, ES9218_ADDR, reg, val); | ||
221 | } | ||
222 | |||
223 | void es9218_update(int reg, uint8_t msk, uint8_t val) | ||
224 | { | ||
225 | i2c_reg_modify1(ES9218_BUS, ES9218_ADDR, reg, msk, val, NULL); | ||
226 | } | ||
diff --git a/firmware/drivers/cw2015.c b/firmware/drivers/cw2015.c new file mode 100644 index 0000000000..705ca16e22 --- /dev/null +++ b/firmware/drivers/cw2015.c | |||
@@ -0,0 +1,191 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2021 Aidan MacDonald | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "cw2015.h" | ||
23 | #include "i2c-async.h" | ||
24 | #include <string.h> | ||
25 | #include "system.h" | ||
26 | |||
27 | /* Headers for the debug menu */ | ||
28 | #ifndef BOOTLOADER | ||
29 | # include "action.h" | ||
30 | # include "list.h" | ||
31 | # include <stdio.h> | ||
32 | #endif | ||
33 | |||
34 | /* Battery profile info is an opaque blob. According to this, | ||
35 | * https://lore.kernel.org/linux-pm/20200503154855.duwj2djgqfiyleq5@earth.universe/T/#u | ||
36 | * the blob only comes from Cellwise testing a physical battery and cannot be | ||
37 | * obtained any other way. It's specific to a given battery so each target has | ||
38 | * its own profile. | ||
39 | * | ||
40 | * Profile data seems to be retained on the chip so it's not a hard requirement | ||
41 | * to define this. Provided you don't lose power in the meantime, it should be | ||
42 | * enough to just boot the OF, then boot Rockbox and read out the battery info | ||
43 | * from the CW2015 debug screen. | ||
44 | */ | ||
45 | #if defined(SHANLING_Q1) | ||
46 | static const uint8_t device_batinfo[CW2015_SIZE_BATINFO] = { | ||
47 | 0x15, 0x7E, 0x61, 0x59, 0x57, 0x55, 0x56, 0x4C, | ||
48 | 0x4E, 0x4D, 0x50, 0x4C, 0x45, 0x3A, 0x2D, 0x27, | ||
49 | 0x22, 0x1E, 0x19, 0x1E, 0x2A, 0x3C, 0x48, 0x45, | ||
50 | 0x1D, 0x94, 0x08, 0xF6, 0x15, 0x29, 0x48, 0x51, | ||
51 | 0x5D, 0x60, 0x63, 0x66, 0x45, 0x1D, 0x83, 0x38, | ||
52 | 0x09, 0x43, 0x16, 0x42, 0x76, 0x98, 0xA5, 0x1B, | ||
53 | 0x41, 0x76, 0x99, 0xBF, 0x80, 0xC0, 0xEF, 0xCB, | ||
54 | 0x2F, 0x00, 0x64, 0xA5, 0xB5, 0x0E, 0x30, 0x29, | ||
55 | }; | ||
56 | #else | ||
57 | # define NO_BATINFO | ||
58 | #endif | ||
59 | |||
60 | static uint8_t chip_batinfo[CW2015_SIZE_BATINFO]; | ||
61 | |||
62 | /* TODO: Finish implementing this | ||
63 | * | ||
64 | * Although this chip might give a better battery estimate than voltage does, | ||
65 | * the mainline linux driver has a lot of weird hacks due to oddities like the | ||
66 | * SoC getting stuck during charging, and from limited testing it seems this | ||
67 | * may occur for the Q1 too. | ||
68 | */ | ||
69 | |||
70 | static int cw2015_read_bat_info(uint8_t* data) | ||
71 | { | ||
72 | for(int i = 0; i < CW2015_SIZE_BATINFO; ++i) { | ||
73 | int r = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_BATINFO + i); | ||
74 | if(r < 0) | ||
75 | return r; | ||
76 | |||
77 | data[i] = r & 0xff; | ||
78 | } | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | void cw2015_init(void) | ||
84 | { | ||
85 | /* mdelay(100); */ | ||
86 | int rc = cw2015_read_bat_info(&chip_batinfo[0]); | ||
87 | if(rc < 0) | ||
88 | memset(chip_batinfo, 0, sizeof(chip_batinfo)); | ||
89 | } | ||
90 | |||
91 | int cw2015_get_vcell(void) | ||
92 | { | ||
93 | int vcell_msb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_VCELL); | ||
94 | int vcell_lsb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_VCELL+1); | ||
95 | |||
96 | if(vcell_msb < 0 || vcell_lsb < 0) | ||
97 | return -1; | ||
98 | |||
99 | /* 14 bits, resolution 305 uV */ | ||
100 | int v_raw = ((vcell_msb & 0x3f) << 8) | vcell_lsb; | ||
101 | return v_raw * 61 / 200; | ||
102 | } | ||
103 | |||
104 | int cw2015_get_soc(void) | ||
105 | { | ||
106 | int soc_msb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_SOC); | ||
107 | |||
108 | if(soc_msb < 0) | ||
109 | return -1; | ||
110 | |||
111 | /* MSB is the state of charge in percentage. | ||
112 | * the LSB contains fractional information not useful to Rockbox. */ | ||
113 | return soc_msb & 0xff; | ||
114 | } | ||
115 | |||
116 | int cw2015_get_rrt(void) | ||
117 | { | ||
118 | int rrt_msb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_RRT_ALERT); | ||
119 | int rrt_lsb = i2c_reg_read1(CW2015_BUS, CW2015_ADDR, CW2015_REG_RRT_ALERT+1); | ||
120 | |||
121 | if(rrt_msb < 0 || rrt_lsb < 0) | ||
122 | return -1; | ||
123 | |||
124 | /* 13 bits, resolution 1 minute */ | ||
125 | return ((rrt_msb & 0x1f) << 8) | rrt_lsb; | ||
126 | } | ||
127 | |||
128 | const uint8_t* cw2015_get_bat_info(void) | ||
129 | { | ||
130 | return &chip_batinfo[0]; | ||
131 | } | ||
132 | |||
133 | #ifndef BOOTLOADER | ||
134 | enum { | ||
135 | CW2015_DEBUG_VCELL = 0, | ||
136 | CW2015_DEBUG_SOC, | ||
137 | CW2015_DEBUG_RRT, | ||
138 | CW2015_DEBUG_BATINFO, | ||
139 | CW2015_DEBUG_BATINFO_LAST = CW2015_DEBUG_BATINFO + 7, | ||
140 | CW2015_DEBUG_NUM_ENTRIES, | ||
141 | }; | ||
142 | |||
143 | static int cw2015_debug_menu_cb(int action, struct gui_synclist* lists) | ||
144 | { | ||
145 | (void)lists; | ||
146 | |||
147 | if(action == ACTION_NONE) | ||
148 | action = ACTION_REDRAW; | ||
149 | |||
150 | return action; | ||
151 | } | ||
152 | |||
153 | static const char* cw2015_debug_menu_get_name(int item, void* data, | ||
154 | char* buf, size_t buflen) | ||
155 | { | ||
156 | (void)data; | ||
157 | |||
158 | /* hexdump of battery info */ | ||
159 | if(item >= CW2015_DEBUG_BATINFO && item <= CW2015_DEBUG_BATINFO_LAST) { | ||
160 | int i = item - CW2015_DEBUG_BATINFO; | ||
161 | const uint8_t* batinfo = cw2015_get_bat_info(); | ||
162 | snprintf(buf, buflen, "BatInfo%d: %02x %02x %02x %02x %02x %02x %02x %02x", i, | ||
163 | batinfo[8*i + 0], batinfo[8*i + 1], batinfo[8*i + 2], batinfo[8*i + 3], | ||
164 | batinfo[8*i + 4], batinfo[8*i + 5], batinfo[8*i + 6], batinfo[8*i + 7]); | ||
165 | return buf; | ||
166 | } | ||
167 | |||
168 | switch(item) { | ||
169 | case CW2015_DEBUG_VCELL: | ||
170 | snprintf(buf, buflen, "VCell: %d mV", cw2015_get_vcell()); | ||
171 | return buf; | ||
172 | case CW2015_DEBUG_SOC: | ||
173 | snprintf(buf, buflen, "SOC: %d%%", cw2015_get_soc()); | ||
174 | return buf; | ||
175 | case CW2015_DEBUG_RRT: | ||
176 | snprintf(buf, buflen, "Runtime: %d min", cw2015_get_rrt()); | ||
177 | return buf; | ||
178 | default: | ||
179 | return "---"; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | bool cw2015_debug_menu(void) | ||
184 | { | ||
185 | struct simplelist_info info; | ||
186 | simplelist_info_init(&info, "CW2015 debug", CW2015_DEBUG_NUM_ENTRIES, NULL); | ||
187 | info.action_callback = cw2015_debug_menu_cb; | ||
188 | info.get_name = cw2015_debug_menu_get_name; | ||
189 | return simplelist_show_list(&info); | ||
190 | } | ||
191 | #endif | ||