From 8a6af7acf702a6ee763409a491eacf39b17dd951 Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Tue, 15 Jun 2021 09:20:43 +0200 Subject: [PATCH] Add tool for communicating with the charger --- charger/build.sh | 3 + charger/main.c | 385 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100755 charger/build.sh create mode 100644 charger/main.c diff --git a/charger/build.sh b/charger/build.sh new file mode 100755 index 0000000..be4a5f2 --- /dev/null +++ b/charger/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +gcc -o kbpower main.c || exit 1 diff --git a/charger/main.c b/charger/main.c new file mode 100644 index 0000000..454aea8 --- /dev/null +++ b/charger/main.c @@ -0,0 +1,385 @@ +/* + * Pinephone keyboard power management daemon/tool. + * + * Copyright (C) 2021 OndÅ™ej Jirman + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// {{{ includes + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +//#include + +#define DEBUG 1 + +#if DEBUG +#define debug(args...) printf(args) +#else +#define debug(args...) +#endif + +// }}} +// {{{ utils + +static void syscall_error(int is_err, const char* fmt, ...) +{ + va_list ap; + + if (!is_err) + return; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errno)); + + exit(1); +} + +static void error(const char* fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + + exit(1); +} + +bool read_file(const char* path, char* buf, size_t size) +{ + int fd; + ssize_t ret; + + fd = open(path, O_RDONLY); + if (fd < 0) + return false; + + ret = read(fd, buf, size); + close(fd); + if (ret < 0) + return false; + + if (ret < size) { + buf[ret] = 0; + return true; + } else { + buf[size - 1] = 0; + return false; + } +} + +#define BIT(n) (1u << (n)) + +#define KB_ADDR 0x15 +#define POWER_ADDR 0x75 + +static int pogo_i2c_open(void) +{ + int ret; + char path[256], buf[1024]; + int fd = -1; + + for (int i = 0; i < 8; i++) { + snprintf(path, sizeof path, "/sys/class/i2c-adapter/i2c-%d/uevent", i); + if (!read_file(path, buf, sizeof buf)) + continue; + + if (!strstr(buf, "OF_FULLNAME=/soc/i2c@1c2b400")) + continue; + + snprintf(path, sizeof path, "/dev/i2c-%d", i); + + int fd = open(path, O_RDWR); + syscall_error(fd < 0, "open(%s) failed"); + + //ret = ioctl(fd, I2C_SLAVE, addr); + //syscall_error(ret < 0, "I2C_SLAVE failed"); + + return fd; + } + + error("Can't find POGO I2C adapter"); + return -1; +} + +uint8_t read_power(int fd, uint8_t reg) +{ + int ret; + uint8_t val; + struct i2c_msg msgs[] = { + { POWER_ADDR, 0, 1, ® }, // address + { POWER_ADDR, I2C_M_RD, 1, &val }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + return val; +} + +void write_power(int fd, uint8_t reg, uint8_t val) +{ + int ret; + uint8_t buf[] = {reg, val}; + + struct i2c_msg msgs[] = { + { POWER_ADDR, 0, 2, buf }, + }; + +// printf("wr 0x%02hhx: %02hhx\n", reg, val); + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); +} + +void update_power(int fd, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t tmp; + + tmp = read_power(fd, reg); + tmp &= ~mask; + tmp |= val & mask; + write_power(fd, reg, tmp); +} + +// bits 4-0 are mapped to gpio4 - gpio0 +//#define MFP_CTL0 0x51 +//#define MFP_CTL1 0x52 +//#define GPIO_INEN 0x53 +//#define GPIO_OUTEN 0x54 +//#define GPIO_DATA 0x55 + +#define BATVADC_DAT_L 0xa2 +#define BATVADC_DAT_H 0xa3 +#define BATIADC_DAT_L 0xa4 +#define BATIADC_DAT_H 0xa5 +#define BATOCV_DAT_L 0xa8 +#define BATOCV_DAT_H 0xa9 + +// in mV +unsigned get_bat_voltage(int fd) +{ + unsigned l = read_power(fd, BATVADC_DAT_L); + unsigned h = read_power(fd, BATVADC_DAT_H); + + if (h & 0x20) + return 2600 - ((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 3724; + + return 2600 + (l + (h << 8)) * 1000 / 3724; +} + +int get_bat_current(int fd) +{ + unsigned l = read_power(fd, BATIADC_DAT_L); + unsigned h = read_power(fd, BATIADC_DAT_H); + + if (h & 0x20) + return - (int)((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 1341; + + return (l + (h << 8)) * 1000 / 1341; +} + +unsigned get_bat_oc_voltage(int fd) +{ + unsigned l = read_power(fd, BATOCV_DAT_L); + unsigned h = read_power(fd, BATOCV_DAT_H); + + if (h & 0x20) + return 2600 - ((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 3724; + + return 2600 + (l + (h << 8)) * 1000 / 3724; +} + +enum { + POWER_CHARGER_ENABLED, + POWER_VOUT_ENABLED, + POWER_VOUT_AUTO, +}; + +#define SYS_CTL0 0x01 +#define SYS_CTL1 0x02 +#define SYS_CTL2 0x0c +#define SYS_CTL3 0x03 +#define SYS_CTL4 0x04 +#define SYS_CTL5 0x07 + +#define Charger_CTL1 0x22 +#define Charger_CTL2 0x24 +#define CHG_DIG_CTL4 0x25 + +void power_setup(int fd, unsigned flags) +{ + update_power(fd, SYS_CTL1, 0x03, 0x00); // disable automatic control based on load detection + update_power(fd, SYS_CTL0, 0x1e, BIT(1) | BIT(2)); // 2=boost 1=charger enable + update_power(fd, SYS_CTL3, BIT(5), 0); // disable "2x key press = shutdown" function + update_power(fd, SYS_CTL4, BIT(5), 0); // disable "VIN pull out -> VOUT auto-enable" function + + update_power(fd, CHG_DIG_CTL4, 0x1f, 15); // set charging current (in 100mA steps) +} + +#define READ0 0x71 +#define READ1 0x72 +#define READ2 0x77 + +const char* get_chg_status_text(uint8_t s) +{ + switch (s) { + case 0: return "Idle"; + case 1: return "Trickle charge"; + case 2: return "Constant current phase"; + case 3: return "Constant voltage phase"; + case 4: return "Constant voltage stop"; + case 5: return "Full"; + case 6: return "Timeout"; + default: return "Unknown"; + } +} + +void power_status(int fd) +{ + uint8_t r0 = read_power(fd, READ0); + uint8_t r1 = read_power(fd, READ1); + uint8_t r2 = read_power(fd, READ2); + + printf("Charger: %s (%s%s%s%s%s%s%s)\n", get_chg_status_text((r0 >> 5) & 0x7), + r0 & BIT(4) ? " chg_op" : "", + r0 & BIT(3) ? " chg_end" : "", + r0 & BIT(2) ? " cv_timeout" : "", + r0 & BIT(1) ? " chg_timeout" : "", + r0 & BIT(0) ? " trickle_timeout" : "", + r1 & BIT(6) ? " VIN overvoltage" : "", + r1 & BIT(5) ? " <= 75mA load" : "" + ); + + printf("Button: %02hhx (%s%s%s%s)\n", r2, + r2 & BIT(3) ? " btn_press" : " btn_not_press", + r2 & BIT(2) ? " double_press" : "", + r2 & BIT(1) ? " long_press" : "", + r2 & BIT(0) ? " short_press" : "" + ); + + printf("0x70: %02hhx\n", read_power(fd, 0x70)); + + update_power(fd, READ2, 0x7, 0x7); +} + +/* +- Independent control of + - Boost (5V VOUT to power Pinephone) + - Battery Charger + +- Optional automatic power on when load is inserted +- Optional auto enable of VOUT when disconnecting VIN (reg 0x04) + +- Optiobal automatic shutdown when VOUT has light load (customizable via reg + 0x0c, min. is 100mA, shutdown lastsa 8-64s (see reg 0x04)) + +- Charger_CTL1 0x22 + - Control of charging current based on VOUT undervoltage (it tries + to keep VOUT in a certain range by reducing load on VIN by + decreasing charging current?) + +- Battery type selection 4.2/4.3/4.35V + - + extra margin 0-42mV during constant voltage phase? + - External (via VSET pin) or internal setting (via reg 0x24) + +- Charging current selection (100mA - 2.3A ?) + +- Charging status register + - charging state - idle, trickle, constant voltage/current phase, full, + timeout + - LED heavey load indication + - VIN overvoltage indication (> 5.6V) + +- Button press status + - current state: UP/DOWN + - long press + - short press + +GPIO: + +- KEY input + - Long press button time selection 1-4s + - Enable/disable 2x short press shutdown function +- L3/L4 function selection: + - GPIO0/1 + - normal function +- LIGHT pin function selection: + - GPIO2 + - VREF + - WLED +- VSET + - VSET (normal function to select battery voltage via PIN setting) + - GPIO4 +- RSET + - GPIO3 + - battery internal resistance selection via resistor on the RSET pin + +- separate input/output enable register for all 5 GPIOs +- GPIO data register to read/write values to pins + +ADC: + +- 14 bit two register VBAT, IBAT, VBAT_OCV readings +*/ + +int main(int ac, char* av[]) +{ + int fd, ret; + + fd = pogo_i2c_open(); + + printf("V=%u mV (OCV %u mV) I=%d mA\n", get_bat_voltage(fd), get_bat_oc_voltage(fd), get_bat_current(fd)); + +// uint8_t v = read_power(fd, 2); +// printf("%02hhx\n", v); + + return 0; +}