summaryrefslogtreecommitdiff
path: root/firmware/drivers
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-05-23 17:30:58 +0100
committerAidan MacDonald <amachronic@protonmail.com>2021-07-13 22:01:33 +0100
commit4c60bc9e681865fcfc149775a1ed7ccd2613d5bf (patch)
tree99f8d91af2c171cf3843f0c14d41a20d9dc29c4f /firmware/drivers
parent3abb7c5dd5be2ec6744bfc0a80967b20f1b59e30 (diff)
downloadrockbox-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.c226
-rw-r--r--firmware/drivers/cw2015.c191
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
26struct es9218_state {
27 enum es9218_clock_gear clk_gear;
28 uint32_t fsr;
29 uint32_t nco;
30};
31
32static struct es9218_state es9218;
33
34void 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
55void es9218_close(void)
56{
57 /* Turn off power supply */
58 es9218_set_power_pin(0);
59 es9218_set_reset_pin(0);
60}
61
62static 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
88void 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
97void es9218_set_nco_frequency(uint32_t fsr)
98{
99 if(fsr != es9218.fsr) {
100 es9218.fsr = fsr;
101 recalc_nco();
102 }
103}
104
105void es9218_recompute_nco(void)
106{
107 recalc_nco();
108}
109
110void es9218_set_amp_mode(enum es9218_amp_mode mode)
111{
112 es9218_update(ES9218_REG_AMP_CONFIG, 0x03, (uint8_t)mode & 3);
113}
114
115void 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
121void 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
133void 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
143static 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
150static 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
157void 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
163void es9218_set_amp_volume(int vol)
164{
165 es9218_update(ES9218_REG_ANALOG_VOL, 0x1f, amp_vol_to_hw(vol));
166}
167
168void es9218_mute(bool en)
169{
170 es9218_update(ES9218_REG_FILTER_SYS_MUTE, 1, en ? 1 : 0);
171}
172
173void es9218_set_filter(enum es9218_filter_type filt)
174{
175 es9218_update(ES9218_REG_FILTER_SYS_MUTE, 0xe0, ((int)filt & 7) << 5);
176}
177
178void 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
185void es9218_set_automute_level(int dB)
186{
187 es9218_update(ES9218_REG_AUTOMUTE_LEVEL, 0x7f, dB);
188}
189
190void es9218_set_automute_fast_mode(bool en)
191{
192 es9218_update(ES9218_REG_MIX_AUTOMUTE, 0x10, en ? 0x10 : 0x00);
193}
194
195void es9218_set_dpll_bandwidth(int knob)
196{
197 es9218_update(ES9218_REG_ASRC_DPLL_BANDWIDTH, 0xf0, (knob & 0xf) << 4);
198}
199
200void es9218_set_thd_compensation(bool en)
201{
202 es9218_update(ES9218_REG_THD_COMP_BYPASS, 0x40, en ? 0x40 : 0);
203}
204
205void 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
213int es9218_read(int reg)
214{
215 return i2c_reg_read1(ES9218_BUS, ES9218_ADDR, reg);
216}
217
218void es9218_write(int reg, uint8_t val)
219{
220 i2c_reg_write1(ES9218_BUS, ES9218_ADDR, reg, val);
221}
222
223void 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)
46static 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
60static 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
70static 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
83void 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
91int 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
104int 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
116int 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
128const uint8_t* cw2015_get_bat_info(void)
129{
130 return &chip_batinfo[0];
131}
132
133#ifndef BOOTLOADER
134enum {
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
143static 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
153static 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
183bool 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