/*************************************************************************** * __________ __ ___. * 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 #include "system.h" /* Headers for the debug menu */ #ifndef BOOTLOADER # include "action.h" # include "list.h" # include #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