summaryrefslogtreecommitdiff
path: root/firmware/drivers/cw2015.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/drivers/cw2015.c')
-rw-r--r--firmware/drivers/cw2015.c191
1 files changed, 191 insertions, 0 deletions
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