Browse Source
I2C register layout changed a bit to make various operations easier to implement in FW and for the user. Flashing/debugging tools now share more code. Firmware is now more configurable (it's now possible to compile-out various features). Self-testing for column-shorts is implemented. Firmware is optimized for low power consumption.master 1.0-beta1

32 changed files with 3325 additions and 1527 deletions
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
VERSION := $(shell git describe) $(shell git log -1 --format=%cd --date=iso)
|
||||
|
||||
OUT ?= build/
|
||||
CFLAGS ?= -O2 -g0
|
||||
CFLAGS += -DVERSION="\"$(VERSION)\"" -I. -I$(OUT)
|
||||
|
||||
all: $(OUT)ppkb-i2c-inputd $(OUT)ppkb-usb-flasher $(OUT)ppkb-usb-debugger $(OUT)fw-stock.bin $(OUT)ppkb-i2c-debugger $(OUT)ppkb-i2c-charger-ctl $(OUT)ppkb-i2c-flasher $(OUT)ppkb-i2c-selftest |
||||
|
||||
$(OUT)ppkb-usb-flasher: usb-flasher.c common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)ppkb-usb-debugger: usb-debugger.c common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)kmap.h: keymaps/physical-map.txt keymaps/factory-keymap.txt |
||||
@mkdir -p $(OUT)
|
||||
php keymaps/map-to-c.php $^ > $@
|
||||
|
||||
$(OUT)ppkb-i2c-inputd: i2c-inputd.c $(OUT)kmap.h common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)ppkb-i2c-debugger: i2c-debugger.c common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)ppkb-i2c-charger-ctl: i2c-charger-ctl.c common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)ppkb-i2c-flasher: i2c-flasher.c common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)ppkb-i2c-selftest: i2c-selftest.c common.c |
||||
@mkdir -p $(OUT)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(OUT)fw-stock.bin $(OUT)fw-user.bin: firmware/em85f684a.h firmware/main.c firmware/build.sh firmware/bootloader.bin |
||||
@mkdir -p $(OUT)
|
||||
cd firmware && ./build.sh
|
||||
cp -f firmware/build/fw-stock.bin $(OUT)fw-stock.bin
|
||||
cp -f firmware/build/fw-user.bin $(OUT)fw-user.bin
|
Binary file not shown.
@ -1,3 +0,0 @@
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh |
||||
|
||||
gcc -o kbpower main.c || exit 1 |
@ -1,385 +0,0 @@
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* Pinephone keyboard power management daemon/tool. |
||||
* |
||||
* Copyright (C) 2021 Ondřej Jirman <megi@xff.cz> |
||||
* |
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
// {{{ includes
|
||||
|
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <inttypes.h> |
||||
#include <stdarg.h> |
||||
#include <stdbool.h> |
||||
#include <stdint.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <sys/stat.h> |
||||
#include <time.h> |
||||
#include <sys/ioctl.h> |
||||
#include <unistd.h> |
||||
#include <sys/types.h> |
||||
#include <dirent.h> |
||||
#include <poll.h> |
||||
|
||||
#include <linux/i2c.h> |
||||
#include <linux/i2c-dev.h> |
||||
#include <linux/gpio.h> |
||||
//#include <i2c/smbus.h>
|
||||
|
||||
#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; |
||||
} |
@ -0,0 +1,387 @@
@@ -0,0 +1,387 @@
|
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <inttypes.h> |
||||
#include <stdarg.h> |
||||
#include <stdbool.h> |
||||
#include <stdint.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <sys/stat.h> |
||||
#include <time.h> |
||||
#include <poll.h> |
||||
#include <sys/ioctl.h> |
||||
#include <unistd.h> |
||||
#include <sys/types.h> |
||||
#include <dirent.h> |
||||
#include <getopt.h> |
||||
|
||||
#include <linux/usbdevice_fs.h> |
||||
#include <linux/i2c.h> |
||||
#include <linux/i2c-dev.h> |
||||
#include <linux/gpio.h> |
||||
|
||||
#define BIT(n) (1u << (n)) |
||||
|
||||
#define KB_ADDR 0x15 |
||||
#define POWER_ADDR 0x75 |
||||
|
||||
static bool verbose; |
||||
#define debug(args...) { if (verbose) printf(args); } |
||||
|
||||
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); |
||||
} |
||||
|
||||
static 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; |
||||
} |
||||
} |
||||
|
||||
static int open_usb_dev(uint16_t vid, uint16_t pid) |
||||
{ |
||||
char path[256], buf[256]; |
||||
struct dirent *e; |
||||
unsigned e_vid, e_pid, bus, dev; |
||||
int fd = -1, ret; |
||||
DIR* d; |
||||
|
||||
d = opendir("/sys/bus/usb/devices"); |
||||
syscall_error(d == NULL, "opendir(/sys/bus/usb/devices) failed"); |
||||
|
||||
while (true) { |
||||
errno = 0; |
||||
e = readdir(d); |
||||
syscall_error(e == NULL && errno, "readdir(/sys/bus/usb/devices) failed"); |
||||
if (!e) |
||||
break; |
||||
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) |
||||
continue; |
||||
|
||||
snprintf(path, sizeof path, |
||||
"/sys/bus/usb/devices/%s/idVendor", e->d_name); |
||||
if (!read_file(path, buf, sizeof buf)) |
||||
continue; |
||||
|
||||
ret = sscanf(buf, "%x", &e_vid); |
||||
if (ret != 1) |
||||
error("Failed to parse %s", path); |
||||
|
||||
snprintf(path, sizeof path, |
||||
"/sys/bus/usb/devices/%s/idProduct", e->d_name); |
||||
if (!read_file(path, buf, sizeof buf)) |
||||
continue; |
||||
|
||||
ret = sscanf(buf, "%x", &e_pid); |
||||
if (ret != 1) |
||||
error("Failed to parse %s", path); |
||||
|
||||
if (e_vid == vid && e_pid == pid) { |
||||
snprintf(path, sizeof path, |
||||
"/sys/bus/usb/devices/%s/busnum", e->d_name); |
||||
if (!read_file(path, buf, sizeof buf)) |
||||
error("Failed to read %s", path); |
||||
|
||||
ret = sscanf(buf, "%u", &bus); |
||||
if (ret != 1) |
||||
error("Failed to parse %s", path); |
||||
|
||||
snprintf(path, sizeof path, |
||||
"/sys/bus/usb/devices/%s/devnum", e->d_name); |
||||
if (!read_file(path, buf, sizeof buf)) |
||||
error("Failed to read %s", path); |
||||
|
||||
ret = sscanf(buf, "%u", &dev); |
||||
if (ret != 1) |
||||
error("Failed to parse %s", path); |
||||
|
||||
snprintf(path, sizeof path, |
||||
"/dev/bus/usb/%03u/%03u", bus, dev); |
||||
|
||||
debug("Found %04x:%04x at %s\n", e_vid, e_pid, path); |
||||
|
||||
fd = open(path, O_RDWR); |
||||
syscall_error(fd < 0, "open(%s) failed", path); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
errno = ENOENT; |
||||
closedir(d); |
||||
return fd; |
||||
} |
||||
|
||||
static int handle_urb(int usb_fd, struct usbdevfs_urb* urb, int timeout) |
||||
{ |
||||
int ret; |
||||
struct usbdevfs_urb* reaped_urb; |
||||
int retries = 0; |
||||
|
||||
retry: |
||||
ret = ioctl(usb_fd, USBDEVFS_SUBMITURB, urb); |
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
struct pollfd fd = { |
||||
.fd = usb_fd, |
||||
.events = POLLOUT, |
||||
}; |
||||
|
||||
ret = poll(&fd, 1, timeout); |
||||
if (ret <= 0) { |
||||
if (ret == 0) |
||||
errno = ETIMEDOUT; |
||||
|
||||
int save_errno = errno; |
||||
|
||||
// on timeout or other poll error, we need to discard and reap the submitted URB
|
||||
ret = ioctl(usb_fd, USBDEVFS_DISCARDURB, urb); |
||||
|
||||
// even if discard fails, URB may still be reapable, we need to try reaping anyway
|
||||
ret = ioctl(usb_fd, USBDEVFS_REAPURBNDELAY, &reaped_urb); |
||||
|
||||
// reap must immediately succeed, otherwise this is fatal
|
||||
syscall_error(ret < 0, "USBDEVFS_REAPURBNDELAY failed"); |
||||
|
||||
errno = save_errno; |
||||
return -1; |
||||
} |
||||
|
||||
// hopefully POLLERR means we get some error immediately on reap
|
||||
|
||||
ret = ioctl(usb_fd, USBDEVFS_REAPURB, &reaped_urb); |
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
// EPROTO errors are recoverable
|
||||
if (urb->status == -71 && retries < 3) { |
||||
retries++; |
||||
goto retry; |
||||
} |
||||
|
||||
if (urb->status != 0) { |
||||
errno = -urb->status; |
||||
return -1; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
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"); |
||||
|
||||
return fd;
|
||||
} |
||||
|
||||
error("Can't find POGO I2C adapter"); |
||||
return -1; |
||||
} |
||||
|
||||
ssize_t xwrite(int fd, uint8_t* buf, size_t len) |
||||
{ |
||||
size_t off = 0; |
||||
|
||||
while (off < len) { |
||||
ssize_t ret = write(fd, buf + off, len - off); |
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
off += ret; |
||||
} |
||||
|
||||
return off; |
||||
} |
||||
|
||||
static const uint8_t crc8_0x7_table[] = { |
||||
0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, |
||||
0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, |
||||
0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, |
||||
0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, |
||||
0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, |
||||
0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, |
||||
0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, |
||||
0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, |
||||
0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, |
||||
0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, |
||||
0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, |
||||
0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, |
||||
0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, |
||||
0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, |
||||
0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, |
||||
0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, |
||||
0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, |
||||
0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, |
||||
0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, |
||||
0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, |
||||
0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, |
||||
0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, |
||||
0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, |
||||
0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, |
||||
0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, |
||||
0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, |
||||
0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, |
||||
0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, |
||||
0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, |
||||
0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, |
||||
0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, |
||||
0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 |
||||
}; |
||||
|
||||
static uint8_t crc8(const uint8_t *pdata, size_t nbytes) |
||||
{ |
||||
unsigned int idx; |
||||
uint8_t crc = 0; |
||||
|
||||
while (nbytes--) { |
||||
idx = (crc ^ *pdata); |
||||
crc = (crc8_0x7_table[idx]) & 0xff; |
||||
pdata++; |
||||
} |
||||
|
||||
return crc; |
||||
} |
||||
|
||||
uint64_t time_abs(void) |
||||
{ |
||||
struct timespec tmp; |
||||
int ret; |
||||
|
||||
ret = clock_gettime(CLOCK_MONOTONIC, &tmp); |
||||
if (ret < 0) |
||||
return 0; |
||||
|
||||
return tmp.tv_sec * 1000000000ull + tmp.tv_nsec; |
||||
} |
||||
|
||||
static int gpiochip_open(const char* match) |
||||
{ |
||||
int ret; |
||||
char path[256], buf[1024]; |
||||
int fd = -1; |
||||
|
||||
for (int i = 0; i < 8; i++) { |
||||
snprintf(path, sizeof path, "/sys/bus/gpio/devices/gpiochip%d/uevent", i); |
||||
if (!read_file(path, buf, sizeof buf)) |
||||
continue; |
||||
|
||||
if (!strstr(buf, match)) |
||||
continue; |
||||
|
||||
snprintf(path, sizeof path, "/dev/gpiochip%d", i); |
||||
|
||||
int fd = open(path, O_RDWR); |
||||
syscall_error(fd < 0, "open(%s) failed"); |
||||
|
||||
return fd; |
||||
} |
||||
|
||||
error("Can't find %s gpiochip", match); |
||||
return -1; |
||||
} |
||||
|
||||
static int gpio_setup_pl12(unsigned flags) |
||||
{ |
||||
int ret; |
||||
struct gpio_v2_line_request req = { |
||||
.num_lines = 1, |
||||
.offsets[0] = 12, |
||||
.config.flags = flags, |
||||
.consumer = "ppkbd", |
||||
}; |
||||
|
||||
int fd = gpiochip_open("OF_FULLNAME=/soc/pinctrl@1f02c00"); |
||||
|
||||
ret = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req); |
||||
syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); |
||||
|
||||
close(fd); |
||||
|
||||
return req.fd; |
||||
} |
||||
|
||||
static int gpio_get_value(int lfd) |
||||
{ |
||||
int ret; |
||||
struct gpio_v2_line_values vals = { |
||||
.mask = 1, |
||||
}; |
||||
|
||||
ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); |
||||
syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); |
||||
|
||||
return vals.bits & 0x1; |
||||
} |
||||
|
||||
static int gpio_set_value(int lfd, int val) |
||||
{ |
||||
int ret; |
||||
struct gpio_v2_line_values vals = { |
||||
.mask = 1, |
||||
}; |
||||
|
||||
ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); |
||||
syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); |
||||
|
||||
return vals.bits & 0x1; |
||||
} |
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Pinephone Keyboard Firmware |
||||
* |
||||
* Copyright (C) 2021 Ondřej Jirman <megi@xff.cz> |
||||
* |
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#ifndef __PPKB_I2C_REGISTERS__ |
||||
#define __PPKB_I2C_REGISTERS__ |
||||
|
||||
// defines register API in BCD format (currently 1.0)
|
||||
// on incompatible change this may need to be changed
|
||||
#define FW_REVISION 0x10 |
||||
|
||||
#define REG_DEVID_K 0x00 |
||||
#define REG_DEVID_B 0x01 |
||||
#define REG_FW_REVISION 0x02 |
||||
#define REG_FW_FEATURES 0x03 |
||||
#define REG_FW_FEATURES_USB_DEBUGGER BIT(0) |
||||
#define REG_FW_FEATURES_FLASHING_MODE BIT(1) |
||||
#define REG_FW_FEATURES_SELF_TEST BIT(2) |
||||
#define REG_FW_FEATURES_STOCK_FW BIT(3) |
||||
|
||||
#define REG_KEYMATRIX_SIZE 0x06 |
||||
#define REG_KEYMATRIX_STATE_CRC8 0x07 |
||||
#define REG_KEYMATRIX_STATE 0x08 |
||||
#define REG_KEYMATRIX_STATE_END 0x13 |
||||
|
||||
#define REG_SYS_CONFIG 0x20 |
||||
#define REG_SYS_CONFIG_SCAN_BLOCK BIT(0) |
||||
#define REG_SYS_CONFIG_POLL_MODE BIT(1) |
||||
#define REG_SYS_CONFIG_USB_DEBUG_EN BIT(2) |
||||
|
||||
#define REG_SYS_COMMAND 0x21 |
||||
#define REG_SYS_COMMAND_MCU_RESET 'r' |
||||
#define REG_SYS_COMMAND_SELFTEST 't' |
||||
#define REG_SYS_COMMAND_USB_IAP 'i' |
||||
|
||||
#define REG_SYS_USER_APP_BLOCK 0x22 |
||||
#define REG_SYS_USER_APP_BLOCK_MAGIC 0x53 |
||||
|
||||
#define REG_FLASH_DATA_START 0x70 |
||||
#define REG_FLASH_DATA_END 0xef |
||||
#define REG_FLASH_ADDR_L 0xf0 |
||||
#define REG_FLASH_ADDR_H 0xf1 |
||||
#define REG_FLASH_CRC8 0xf2 |
||||
|
||||
#define REG_FLASH_UNLOCK 0xf3 |
||||
#define REG_FLASH_UNLOCK_MAGIC 0x46 |
||||
|
||||
#define REG_FLASH_CMD 0xf4 |
||||
#define REG_FLASH_CMD_READ_ROM 0x52 |
||||
#define REG_FLASH_CMD_WRITE_ROM 0x57 |
||||
#define REG_FLASH_CMD_ERASE_ROM 0x45 |
||||
#define REG_FLASH_CMD_COMMIT 0x43 |
||||
|
||||
#define REG_DEBUG_LOG 0xff |
||||
|
||||
#endif |
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/** |
||||
* Pinephone Keyboard Firmware |
||||
* |
||||
* Copyright (C) 2021 Ondřej Jirman <megi@xff.cz> |
||||
* |
||||
* 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 <http://www.gnu.org/licenses/>. |
||||
*/ |
||||
|
||||
#include "em85f684a.h" |
||||
|
||||
.area RSEG (ABS,DATA) |
||||
.org 0x0000 |
||||
ar0 = 0x00 |
||||
psw0 = 0xd0 |
||||