summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/bluetooth/bluefruit_le.cpp (renamed from drivers/bluetooth/adafruit_ble.cpp)64
-rw-r--r--drivers/bluetooth/bluefruit_le.h (renamed from drivers/bluetooth/adafruit_ble.h)20
-rw-r--r--drivers/bluetooth/outputselect.c21
-rw-r--r--drivers/bluetooth/rn42.c99
-rw-r--r--drivers/bluetooth/rn42.h25
-rw-r--r--drivers/flash/flash_spi.c372
-rw-r--r--drivers/flash/flash_spi.h136
-rw-r--r--drivers/gpio/mcp23018.c108
-rw-r--r--drivers/gpio/mcp23018.h65
-rw-r--r--drivers/gpio/pca9555.c61
-rw-r--r--drivers/gpio/pca9555.h81
-rw-r--r--drivers/gpio/sn74x138.c65
-rw-r--r--drivers/gpio/sn74x138.h48
-rw-r--r--drivers/led/ckled2001.c11
-rw-r--r--drivers/led/issi/is31fl3733-simple.c248
-rw-r--r--drivers/led/issi/is31fl3733-simple.h260
-rw-r--r--drivers/led/issi/is31fl3742.h299
-rw-r--r--drivers/led/issi/is31fl3743.h327
-rw-r--r--drivers/led/issi/is31fl3745.h270
-rw-r--r--drivers/led/issi/is31fl3746.h198
-rw-r--r--drivers/led/issi/is31flcommon.c230
-rw-r--r--drivers/led/issi/is31flcommon.h78
-rw-r--r--drivers/sensors/adns9800.c16
-rw-r--r--drivers/sensors/adns9800_srom_A6.h2
-rw-r--r--drivers/sensors/pimoroni_trackball.c22
-rw-r--r--drivers/sensors/pimoroni_trackball.h9
-rw-r--r--drivers/sensors/pmw3360.c283
-rw-r--r--drivers/sensors/pmw3360.h35
-rw-r--r--drivers/sensors/pmw3389.c292
-rw-r--r--drivers/sensors/pmw3389.h76
-rw-r--r--drivers/sensors/pmw3389_firmware.h558
-rw-r--r--drivers/serial.h19
32 files changed, 3808 insertions, 590 deletions
diff --git a/drivers/bluetooth/adafruit_ble.cpp b/drivers/bluetooth/bluefruit_le.cpp
index 34a780e9a5..86581a1a48 100644
--- a/drivers/bluetooth/adafruit_ble.cpp
+++ b/drivers/bluetooth/bluefruit_le.cpp
@@ -1,4 +1,4 @@
-#include "adafruit_ble.h"
+#include "bluefruit_le.h"
#include <stdio.h>
#include <stdlib.h>
@@ -16,20 +16,20 @@
// These are the pin assignments for the 32u4 boards.
// You may define them to something else in your config.h
// if yours is wired up differently.
-#ifndef ADAFRUIT_BLE_RST_PIN
-# define ADAFRUIT_BLE_RST_PIN D4
+#ifndef BLUEFRUIT_LE_RST_PIN
+# define BLUEFRUIT_LE_RST_PIN D4
#endif
-#ifndef ADAFRUIT_BLE_CS_PIN
-# define ADAFRUIT_BLE_CS_PIN B4
+#ifndef BLUEFRUIT_LE_CS_PIN
+# define BLUEFRUIT_LE_CS_PIN B4
#endif
-#ifndef ADAFRUIT_BLE_IRQ_PIN
-# define ADAFRUIT_BLE_IRQ_PIN E6
+#ifndef BLUEFRUIT_LE_IRQ_PIN
+# define BLUEFRUIT_LE_IRQ_PIN E6
#endif
-#ifndef ADAFRUIT_BLE_SCK_DIVISOR
-# define ADAFRUIT_BLE_SCK_DIVISOR 2 // 4MHz SCK/8MHz CPU, calculated for Feather 32U4 BLE
+#ifndef BLUEFRUIT_LE_SCK_DIVISOR
+# define BLUEFRUIT_LE_SCK_DIVISOR 2 // 4MHz SCK/8MHz CPU, calculated for Feather 32U4 BLE
#endif
#define SAMPLE_BATTERY
@@ -143,7 +143,7 @@ static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool ver
// Send a single SDEP packet
static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) {
- spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR);
+ spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR);
uint16_t timerStart = timer_read();
bool success = false;
bool ready = false;
@@ -157,7 +157,7 @@ static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) {
// Release it and let it initialize
spi_stop();
wait_us(SdepBackOff);
- spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR);
+ spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR);
} while (timer_elapsed(timerStart) < timeout);
if (ready) {
@@ -190,7 +190,7 @@ static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) {
bool ready = false;
do {
- ready = readPin(ADAFRUIT_BLE_IRQ_PIN);
+ ready = readPin(BLUEFRUIT_LE_IRQ_PIN);
if (ready) {
break;
}
@@ -198,7 +198,7 @@ static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) {
} while (timer_elapsed(timerStart) < timeout);
if (ready) {
- spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR);
+ spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR);
do {
// Read the command type, waiting for the data to be ready
@@ -207,7 +207,7 @@ static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) {
// Release it and let it initialize
spi_stop();
wait_us(SdepBackOff);
- spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR);
+ spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR);
continue;
}
@@ -233,7 +233,7 @@ static void resp_buf_read_one(bool greedy) {
return;
}
- if (readPin(ADAFRUIT_BLE_IRQ_PIN)) {
+ if (readPin(BLUEFRUIT_LE_IRQ_PIN)) {
struct sdep_msg msg;
again:
@@ -244,7 +244,7 @@ static void resp_buf_read_one(bool greedy) {
dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send));
}
- if (greedy && resp_buf.peek(last_send) && readPin(ADAFRUIT_BLE_IRQ_PIN)) {
+ if (greedy && resp_buf.peek(last_send) && readPin(BLUEFRUIT_LE_IRQ_PIN)) {
goto again;
}
}
@@ -295,16 +295,16 @@ static bool ble_init(void) {
state.configured = false;
state.is_connected = false;
- setPinInput(ADAFRUIT_BLE_IRQ_PIN);
+ setPinInput(BLUEFRUIT_LE_IRQ_PIN);
spi_init();
// Perform a hardware reset
- setPinOutput(ADAFRUIT_BLE_RST_PIN);
- writePinHigh(ADAFRUIT_BLE_RST_PIN);
- writePinLow(ADAFRUIT_BLE_RST_PIN);
+ setPinOutput(BLUEFRUIT_LE_RST_PIN);
+ writePinHigh(BLUEFRUIT_LE_RST_PIN);
+ writePinLow(BLUEFRUIT_LE_RST_PIN);
wait_ms(10);
- writePinHigh(ADAFRUIT_BLE_RST_PIN);
+ writePinHigh(BLUEFRUIT_LE_RST_PIN);
wait_ms(1000); // Give it a second to initialize
@@ -424,9 +424,9 @@ bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) {
return at_command(cmdbuf, resp, resplen, verbose);
}
-bool adafruit_ble_is_connected(void) { return state.is_connected; }
+bool bluefruit_le_is_connected(void) { return state.is_connected; }
-bool adafruit_ble_enable_keyboard(void) {
+bool bluefruit_le_enable_keyboard(void) {
char resbuf[128];
if (!state.initialized && !ble_init()) {
@@ -498,16 +498,16 @@ static void set_connected(bool connected) {
}
}
-void adafruit_ble_task(void) {
+void bluefruit_le_task(void) {
char resbuf[48];
- if (!state.configured && !adafruit_ble_enable_keyboard()) {
+ if (!state.configured && !bluefruit_le_enable_keyboard()) {
return;
}
resp_buf_read_one(true);
send_buf_send_one(SdepShortTimeout);
- if (resp_buf.empty() && (state.event_flags & UsingEvents) && readPin(ADAFRUIT_BLE_IRQ_PIN)) {
+ if (resp_buf.empty() && (state.event_flags & UsingEvents) && readPin(BLUEFRUIT_LE_IRQ_PIN)) {
// Must be an event update
if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) {
uint32_t mask = strtoul(resbuf, NULL, 16);
@@ -609,7 +609,7 @@ static bool process_queue_item(struct queue_item *item, uint16_t timeout) {
}
}
-void adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys) {
+void bluefruit_le_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys) {
struct queue_item item;
bool didWait = false;
@@ -643,7 +643,7 @@ void adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nk
}
}
-void adafruit_ble_send_consumer_key(uint16_t usage) {
+void bluefruit_le_send_consumer_key(uint16_t usage) {
struct queue_item item;
item.queue_type = QTConsumer;
@@ -655,7 +655,7 @@ void adafruit_ble_send_consumer_key(uint16_t usage) {
}
#ifdef MOUSE_ENABLE
-void adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons) {
+void bluefruit_le_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons) {
struct queue_item item;
item.queue_type = QTMouseMove;
@@ -671,9 +671,9 @@ void adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan,
}
#endif
-uint32_t adafruit_ble_read_battery_voltage(void) { return state.vbat; }
+uint32_t bluefruit_le_read_battery_voltage(void) { return state.vbat; }
-bool adafruit_ble_set_mode_leds(bool on) {
+bool bluefruit_le_set_mode_leds(bool on) {
if (!state.configured) {
return false;
}
@@ -689,7 +689,7 @@ bool adafruit_ble_set_mode_leds(bool on) {
}
// https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel
-bool adafruit_ble_set_power_level(int8_t level) {
+bool bluefruit_le_set_power_level(int8_t level) {
char cmd[46];
if (!state.configured) {
return false;
diff --git a/drivers/bluetooth/adafruit_ble.h b/drivers/bluetooth/bluefruit_le.h
index b43e0771d9..de301c6167 100644
--- a/drivers/bluetooth/adafruit_ble.h
+++ b/drivers/bluetooth/bluefruit_le.h
@@ -16,43 +16,43 @@ extern "C" {
#endif
/* Instruct the module to enable HID keyboard support and reset */
-extern bool adafruit_ble_enable_keyboard(void);
+extern bool bluefruit_le_enable_keyboard(void);
/* Query to see if the BLE module is connected */
-extern bool adafruit_ble_query_is_connected(void);
+extern bool bluefruit_le_query_is_connected(void);
/* Returns true if we believe that the BLE module is connected.
* This uses our cached understanding that is maintained by
* calling ble_task() periodically. */
-extern bool adafruit_ble_is_connected(void);
+extern bool bluefruit_le_is_connected(void);
/* Call this periodically to process BLE-originated things */
-extern void adafruit_ble_task(void);
+extern void bluefruit_le_task(void);
/* Generates keypress events for a set of keys.
* The hid modifier mask specifies the state of the modifier keys for
* this set of keys.
* Also sends a key release indicator, so that the keys do not remain
* held down. */
-extern void adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys);
+extern void bluefruit_le_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys);
/* Send a consumer usage.
* (milliseconds) */
-extern void adafruit_ble_send_consumer_key(uint16_t usage);
+extern void bluefruit_le_send_consumer_key(uint16_t usage);
#ifdef MOUSE_ENABLE
/* Send a mouse/wheel movement report.
* The parameters are signed and indicate positive or negative direction
* change. */
-extern void adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons);
+extern void bluefruit_le_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons);
#endif
/* Compute battery voltage by reading an analog pin.
* Returns the integer number of millivolts */
-extern uint32_t adafruit_ble_read_battery_voltage(void);
+extern uint32_t bluefruit_le_read_battery_voltage(void);
-extern bool adafruit_ble_set_mode_leds(bool on);
-extern bool adafruit_ble_set_power_level(int8_t level);
+extern bool bluefruit_le_set_mode_leds(bool on);
+extern bool bluefruit_le_set_power_level(int8_t level);
#ifdef __cplusplus
}
diff --git a/drivers/bluetooth/outputselect.c b/drivers/bluetooth/outputselect.c
index f758c65280..44bc4a9aa3 100644
--- a/drivers/bluetooth/outputselect.c
+++ b/drivers/bluetooth/outputselect.c
@@ -13,13 +13,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "outputselect.h"
+#include "usb_util.h"
-#if defined(PROTOCOL_LUFA)
-# include "lufa.h"
-#endif
-
-#ifdef MODULE_ADAFRUIT_BLE
-# include "adafruit_ble.h"
+#ifdef BLUETOOTH_BLUEFRUIT_LE
+# include "bluefruit_le.h"
#endif
uint8_t desired_output = OUTPUT_DEFAULT;
@@ -39,23 +36,17 @@ void set_output(uint8_t output) {
*/
__attribute__((weak)) void set_output_user(uint8_t output) {}
-static bool is_usb_configured(void) {
-#if defined(PROTOCOL_LUFA)
- return USB_DeviceState == DEVICE_STATE_Configured;
-#endif
-}
-
/** \brief Auto Detect Output
*
* FIXME: Needs doc
*/
uint8_t auto_detect_output(void) {
- if (is_usb_configured()) {
+ if (usb_connected_state()) {
return OUTPUT_USB;
}
-#ifdef MODULE_ADAFRUIT_BLE
- if (adafruit_ble_is_connected()) {
+#ifdef BLUETOOTH_BLUEFRUIT_LE
+ if (bluefruit_le_is_connected()) {
return OUTPUT_BLUETOOTH;
}
#endif
diff --git a/drivers/bluetooth/rn42.c b/drivers/bluetooth/rn42.c
new file mode 100644
index 0000000000..2ef40bb7e0
--- /dev/null
+++ b/drivers/bluetooth/rn42.c
@@ -0,0 +1,99 @@
+/* Copyright 2021
+ *
+ * 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 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 "report.h"
+#include "uart.h"
+
+#ifndef RN42_BAUD_RATE
+# define RN42_BAUD_RATE 115200
+#endif
+
+// https://cdn.sparkfun.com/datasheets/Wireless/Bluetooth/bluetooth_cr_UG-v1.0r.pdf#G7.663734
+static inline uint16_t rn42_consumer_usage_to_bitmap(uint16_t usage) {
+ switch (usage) {
+ case AC_HOME:
+ return 0x0001;
+ case AL_EMAIL:
+ return 0x0002;
+ case AC_SEARCH:
+ return 0x0004;
+ case AL_KEYBOARD_LAYOUT:
+ return 0x0008;
+ case AUDIO_VOL_UP:
+ return 0x0010;
+ case AUDIO_VOL_DOWN:
+ return 0x0020;
+ case AUDIO_MUTE:
+ return 0x0040;
+ case TRANSPORT_PLAY_PAUSE:
+ return 0x0080;
+ case TRANSPORT_NEXT_TRACK:
+ return 0x0100;
+ case TRANSPORT_PREV_TRACK:
+ return 0x0200;
+ case TRANSPORT_STOP:
+ return 0x0400;
+ case TRANSPORT_EJECT:
+ return 0x0800;
+ case TRANSPORT_FAST_FORWARD:
+ return 0x1000;
+ case TRANSPORT_REWIND:
+ return 0x2000;
+ case TRANSPORT_STOP_EJECT:
+ return 0x4000;
+ case AL_LOCAL_BROWSER:
+ return 0x8000;
+ default:
+ return 0;
+ }
+}
+
+void rn42_init(void) { uart_init(RN42_BAUD_RATE); }
+
+void rn42_send_keyboard(report_keyboard_t *report) {
+ uart_write(0xFD);
+ uart_write(0x09);
+ uart_write(0x01);
+ uart_write(report->mods);
+ uart_write(0x00);
+ for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) {
+ uart_write(report->keys[i]);
+ }
+}
+
+void rn42_send_mouse(report_mouse_t *report) {
+ uart_write(0xFD);
+ uart_write(0x00);
+ uart_write(0x03);
+ uart_write(report->buttons);
+ uart_write(report->x);
+ uart_write(report->y);
+ uart_write(report->v); // should try sending the wheel v here
+ uart_write(report->h); // should try sending the wheel h here
+ uart_write(0x00);
+}
+
+void rn42_send_consumer(uint16_t data) {
+ static uint16_t last_data = 0;
+ if (data == last_data) return;
+ last_data = data;
+ uint16_t bitmap = rn42_consumer_usage_to_bitmap(data);
+ uart_write(0xFD);
+ uart_write(0x03);
+ uart_write(0x03);
+ uart_write(bitmap & 0xFF);
+ uart_write((bitmap >> 8) & 0xFF);
+}
diff --git a/drivers/bluetooth/rn42.h b/drivers/bluetooth/rn42.h
new file mode 100644
index 0000000000..4747759111
--- /dev/null
+++ b/drivers/bluetooth/rn42.h
@@ -0,0 +1,25 @@
+/* Copyright 2021
+ *
+ * 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 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 "report.h"
+
+void rn42_init(void);
+
+void rn42_send_keyboard(report_keyboard_t *report);
+
+void rn42_send_mouse(report_mouse_t *report);
+
+void rn42_send_consumer(uint16_t data);
diff --git a/drivers/flash/flash_spi.c b/drivers/flash/flash_spi.c
new file mode 100644
index 0000000000..2fa8891e38
--- /dev/null
+++ b/drivers/flash/flash_spi.c
@@ -0,0 +1,372 @@
+/*
+Copyright (C) 2021 Westberry Technology (ChangZhou) Corp., Ltd
+
+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 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 <string.h>
+
+#include "util.h"
+#include "wait.h"
+#include "debug.h"
+#include "timer.h"
+#include "flash_spi.h"
+#include "spi_master.h"
+
+/*
+ The time-out time of spi flash transmission.
+*/
+#ifndef EXTERNAL_FLASH_SPI_TIMEOUT
+# define EXTERNAL_FLASH_SPI_TIMEOUT 1000
+#endif
+
+/* ID comands */
+#define FLASH_CMD_RDID 0x9F /* RDID (Read Identification) */
+#define FLASH_CMD_RES 0xAB /* RES (Read Electronic ID) */
+#define FLASH_CMD_REMS 0x90 /* REMS (Read Electronic & Device ID) */
+
+/* register comands */
+#define FLASH_CMD_WRSR 0x01 /* WRSR (Write Status register) */
+#define FLASH_CMD_RDSR 0x05 /* RDSR (Read Status register) */
+
+/* READ comands */
+#define FLASH_CMD_READ 0x03 /* READ (1 x I/O) */
+#define FLASH_CMD_FASTREAD 0x0B /* FAST READ (Fast read data) */
+#define FLASH_CMD_DREAD 0x3B /* DREAD (1In/2 Out fast read) */
+
+/* Program comands */
+#define FLASH_CMD_WREN 0x06 /* WREN (Write Enable) */
+#define FLASH_CMD_WRDI 0x04 /* WRDI (Write Disable) */
+#define FLASH_CMD_PP 0x02 /* PP (page program) */
+
+/* Erase comands */
+#define FLASH_CMD_SE 0x20 /* SE (Sector Erase) */
+#define FLASH_CMD_BE 0xD8 /* BE (Block Erase) */
+#define FLASH_CMD_CE 0x60 /* CE (Chip Erase) hex code: 60 or C7 */
+
+/* Mode setting comands */
+#define FLASH_CMD_DP 0xB9 /* DP (Deep Power Down) */
+#define FLASH_CMD_RDP 0xAB /* RDP (Release form Deep Power Down) */
+
+/* Status register */
+#define FLASH_FLAG_WIP 0x01 /* Write in progress bit */
+#define FLASH_FLAG_WEL 0x02 /* Write enable latch bit */
+
+// #define DEBUG_FLASH_SPI_OUTPUT
+
+static bool spi_flash_start(void) { return spi_start(EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN, EXTERNAL_FLASH_SPI_LSBFIRST, EXTERNAL_FLASH_SPI_MODE, EXTERNAL_FLASH_SPI_CLOCK_DIVISOR); }
+
+static flash_status_t spi_flash_wait_while_busy(void) {
+ uint32_t deadline = timer_read32() + EXTERNAL_FLASH_SPI_TIMEOUT;
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+ uint8_t retval;
+
+ do {
+ bool res = spi_flash_start();
+ if (!res) {
+ dprint("Failed to start SPI! [spi flash wait while busy]\n");
+ return FLASH_STATUS_ERROR;
+ }
+
+ spi_write(FLASH_CMD_RDSR);
+
+ retval = (uint8_t)spi_read();
+
+ spi_stop();
+
+ if (timer_read32() >= deadline) {
+ response = FLASH_STATUS_TIMEOUT;
+ break;
+ }
+ } while (retval & FLASH_FLAG_WIP);
+
+ return response;
+}
+
+static flash_status_t spi_flash_write_enable(void) {
+ bool res = spi_flash_start();
+ if (!res) {
+ dprint("Failed to start SPI! [spi flash write enable]\n");
+ return FLASH_STATUS_ERROR;
+ }
+
+ spi_write(FLASH_CMD_WREN);
+
+ spi_stop();
+
+ return FLASH_STATUS_SUCCESS;
+}
+
+static flash_status_t spi_flash_write_disable(void) {
+ bool res = spi_flash_start();
+ if (!res) {
+ dprint("Failed to start SPI! [spi flash write disable]\n");
+ return FLASH_STATUS_ERROR;
+ }
+
+ spi_write(FLASH_CMD_WRDI);
+
+ spi_stop();
+
+ return FLASH_STATUS_SUCCESS;
+}
+
+/* This function is used for read transfer, write transfer and erase transfer. */
+static flash_status_t spi_flash_transaction(uint8_t cmd, uint32_t addr, uint8_t *data, size_t len) {
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+ uint8_t buffer[EXTERNAL_FLASH_ADDRESS_SIZE + 1];
+
+ buffer[0] = cmd;
+ for (int i = 0; i < EXTERNAL_FLASH_ADDRESS_SIZE; ++i) {
+ buffer[EXTERNAL_FLASH_ADDRESS_SIZE - i] = addr & 0xFF;
+ addr >>= 8;
+ }
+
+ bool res = spi_flash_start();
+ if (!res) {
+ dprint("Failed to start SPI! [spi flash transmit]\n");
+ return FLASH_STATUS_ERROR;
+ }
+
+ response = spi_transmit(buffer, sizeof(buffer));
+
+ if ((!response) && (data != NULL)) {
+ switch (cmd) {
+ case FLASH_CMD_READ:
+ response = spi_receive(data, len);
+ break;
+ case FLASH_CMD_PP:
+ response = spi_transmit(data, len);
+ break;
+ default:
+ response = FLASH_STATUS_ERROR;
+ break;
+ }
+ }
+
+ spi_stop();
+
+ return response;
+}
+
+void flash_init(void) { spi_init(); }
+
+flash_status_t flash_erase_chip(void) {
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+
+ /* Wait for the write-in-progress bit to be cleared. */
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash erase chip]\n");
+ return response;
+ }
+
+ /* Enable writes. */
+ response = spi_flash_write_enable();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to write-enable! [spi flash erase chip]\n");
+ return response;
+ }
+
+ /* Erase Chip. */
+ bool res = spi_flash_start();
+ if (!res) {
+ dprint("Failed to start SPI! [spi flash erase chip]\n");
+ return FLASH_STATUS_ERROR;
+ }
+ spi_write(FLASH_CMD_CE);
+ spi_stop();
+
+ /* Wait for the write-in-progress bit to be cleared.*/
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash erase chip]\n");
+ return response;
+ }
+
+ return response;
+}
+
+flash_status_t flash_erase_sector(uint32_t addr) {
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+
+ /* Check that the address exceeds the limit. */
+ if ((addr + (EXTERNAL_FLASH_SECTOR_SIZE)) >= (EXTERNAL_FLASH_SIZE) || ((addr % (EXTERNAL_FLASH_SECTOR_SIZE)) != 0)) {
+ dprintf("Flash erase sector address over limit! [addr:0x%x]\n", (uint32_t)addr);
+ return FLASH_STATUS_ERROR;
+ }
+
+ /* Wait for the write-in-progress bit to be cleared. */
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash erase sector]\n");
+ return response;
+ }
+
+ /* Enable writes. */
+ response = spi_flash_write_enable();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to write-enable! [spi flash erase sector]\n");
+ return response;
+ }
+
+ /* Erase Sector. */
+ response = spi_flash_transaction(FLASH_CMD_SE, addr, NULL, 0);
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to erase sector! [spi flash erase sector]\n");
+ return response;
+ }
+
+ /* Wait for the write-in-progress bit to be cleared.*/
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash erase sector]\n");
+ return response;
+ }
+
+ return response;
+}
+
+flash_status_t flash_erase_block(uint32_t addr) {
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+
+ /* Check that the address exceeds the limit. */
+ if ((addr + (EXTERNAL_FLASH_BLOCK_SIZE)) >= (EXTERNAL_FLASH_SIZE) || ((addr % (EXTERNAL_FLASH_BLOCK_SIZE)) != 0)) {
+ dprintf("Flash erase block address over limit! [addr:0x%x]\n", (uint32_t)addr);
+ return FLASH_STATUS_ERROR;
+ }
+
+ /* Wait for the write-in-progress bit to be cleared. */
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash erase block]\n");
+ return response;
+ }
+
+ /* Enable writes. */
+ response = spi_flash_write_enable();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to write-enable! [spi flash erase block]\n");
+ return response;
+ }
+
+ /* Erase Block. */
+ response = spi_flash_transaction(FLASH_CMD_BE, addr, NULL, 0);
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to erase block! [spi flash erase block]\n");
+ return response;
+ }
+
+ /* Wait for the write-in-progress bit to be cleared.*/
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash erase block]\n");
+ return response;
+ }
+
+ return response;
+}
+
+flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len) {
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+ uint8_t * read_buf = (uint8_t *)buf;
+
+ /* Wait for the write-in-progress bit to be cleared. */
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash read block]\n");
+ memset(read_buf, 0, len);
+ return response;
+ }
+
+ /* Perform read. */
+ response = spi_flash_transaction(FLASH_CMD_READ, addr, read_buf, len);
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to read block! [spi flash read block]\n");
+ memset(read_buf, 0, len);
+ return response;
+ }
+
+#if defined(CONSOLE_ENABLE) && defined(DEBUG_FLASH_SPI_OUTPUT)
+ dprintf("[SPI FLASH R] 0x%08lX: ", addr);
+ for (size_t i = 0; i < len; ++i) {
+ dprintf(" %02X", (int)(((uint8_t *)read_buf)[i]));
+ }
+ dprintf("\n");
+#endif // DEBUG_FLASH_SPI_OUTPUT
+
+ return response;
+}
+
+flash_status_t flash_write_block(uint32_t addr, const void *buf, size_t len) {
+ flash_status_t response = FLASH_STATUS_SUCCESS;
+ uint8_t * write_buf = (uint8_t *)buf;
+
+ while (len > 0) {
+ uint32_t page_offset = addr % EXTERNAL_FLASH_PAGE_SIZE;
+ size_t write_length = EXTERNAL_FLASH_PAGE_SIZE - page_offset;
+ if (write_length > len) {
+ write_length = len;
+ }
+
+ /* Wait for the write-in-progress bit to be cleared. */
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash write block]\n");
+ return response;
+ }
+
+ /* Enable writes. */
+ response = spi_flash_write_enable();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to write-enable! [spi flash write block]\n");
+ return response;
+ }
+
+#if defined(CONSOLE_ENABLE) && defined(DEBUG_FLASH_SPI_OUTPUT)
+ dprintf("[SPI FLASH W] 0x%08lX: ", addr);
+ for (size_t i = 0; i < write_length; i++) {
+ dprintf(" %02X", (int)(uint8_t)(write_buf[i]));
+ }
+ dprintf("\n");
+#endif // DEBUG_FLASH_SPI_OUTPUT
+
+ /* Perform the write. */
+ response = spi_flash_transaction(FLASH_CMD_PP, addr, write_buf, write_length);
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to write block! [spi flash write block]\n");
+ return response;
+ }
+
+ write_buf += write_length;
+ addr += write_length;
+ len -= write_length;
+ }
+
+ /* Wait for the write-in-progress bit to be cleared. */
+ response = spi_flash_wait_while_busy();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to check WIP flag! [spi flash write block]\n");
+ return response;
+ }
+
+ /* Disable writes. */
+ response = spi_flash_write_disable();
+ if (response != FLASH_STATUS_SUCCESS) {
+ dprint("Failed to write-disable! [spi flash write block]\n");
+ return response;
+ }
+
+ return response;
+}
diff --git a/drivers/flash/flash_spi.h b/drivers/flash/flash_spi.h
new file mode 100644
index 0000000000..abe95e955e
--- /dev/null
+++ b/drivers/flash/flash_spi.h
@@ -0,0 +1,136 @@
+/*
+Copyright (C) 2021 Westberry Technology (ChangZhou) Corp., Ltd
+
+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 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/>.
+*/
+
+#pragma once
+
+/* All the following default configurations are based on MX25L4006E Nor FLASH. */
+
+/*
+ The slave select pin of the FLASH.
+ This needs to be a normal GPIO pin_t value, such as B14.
+*/
+#ifndef EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN
+# error "No chip select pin defined -- missing EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN"
+#endif
+
+/*
+ The clock divisor for SPI to ensure that the MCU is within the
+ specifications of the FLASH chip. Generally this will be PCLK divided by
+ the intended divisor -- check your clock settings and the datasheet of
+ your FLASH.
+*/
+#ifndef EXTERNAL_FLASH_SPI_CLOCK_DIVISOR
+# ifdef __AVR__
+# define EXTERNAL_FLASH_SPI_CLOCK_DIVISOR 4
+# else
+# define EXTERNAL_FLASH_SPI_CLOCK_DIVISOR 8
+# endif
+#endif
+
+/*
+ The SPI mode to communicate with the FLASH.
+*/
+#ifndef EXTERNAL_FLASH_SPI_MODE
+# define EXTERNAL_FLASH_SPI_MODE 0
+#endif
+
+/*
+ Whether or not the SPI communication between the MCU and FLASH should be
+ LSB-first.
+*/
+#ifndef EXTERNAL_FLASH_SPI_LSBFIRST
+# define EXTERNAL_FLASH_SPI_LSBFIRST false
+#endif
+
+/*
+ The Flash address size in bytes, as specified in datasheet.
+*/
+#ifndef EXTERNAL_FLASH_ADDRESS_SIZE
+# define EXTERNAL_FLASH_ADDRESS_SIZE 3
+#endif
+
+/*
+ The page size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_PAGE_SIZE
+# define EXTERNAL_FLASH_PAGE_SIZE 256
+#endif
+
+/*
+ The sector size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_SECTOR_SIZE
+# define EXTERNAL_FLASH_SECTOR_SIZE (4 * 1024)
+#endif
+
+/*
+ The block size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_BLOCK_SIZE
+# define EXTERNAL_FLASH_BLOCK_SIZE (64 * 1024)
+#endif
+
+/*
+ The total size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_SIZE
+# define EXTERNAL_FLASH_SIZE (512 * 1024)
+#endif
+
+/*
+ The block count of the FLASH, calculated by total FLASH size and block size.
+*/
+#define EXTERNAL_FLASH_BLOCK_COUNT ((EXTERNAL_FLASH_SIZE) / (EXTERNAL_FLASH_BLOCK_SIZE))
+
+/*
+ The sector count of the FLASH, calculated by total FLASH size and sector size.
+*/
+#define EXTERNAL_FLASH_SECTOR_COUNT ((EXTERNAL_FLASH_SIZE) / (EXTERNAL_FLASH_SECTOR_SIZE))
+
+/*
+ The page count of the FLASH, calculated by total FLASH size and page size.
+*/
+#define EXTERNAL_FLASH_PAGE_COUNT ((EXTERNAL_FLASH_SIZE) / (EXTERNAL_FLASH_PAGE_SIZE))
+
+typedef int16_t flash_status_t;
+
+#define FLASH_STATUS_SUCCESS (0)
+#define FLASH_STATUS_ERROR (-1)
+#define FLASH_STATUS_TIMEOUT (-2)
+#define FLASH_STATUS_BAD_ADDRESS (-3)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+void flash_init(void);
+
+flash_status_t flash_erase_chip(void);
+
+flash_status_t flash_erase_block(uint32_t addr);
+
+flash_status_t flash_erase_sector(uint32_t addr);
+
+flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len);
+
+flash_status_t flash_write_block(uint32_t addr, const void *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/drivers/gpio/mcp23018.c b/drivers/gpio/mcp23018.c
new file mode 100644
index 0000000000..dc8ab03c50
--- /dev/null
+++ b/drivers/gpio/mcp23018.c
@@ -0,0 +1,108 @@
+// Copyright 2022 zvecr<git@zvecr.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "mcp23018.h"
+#include "i2c_master.h"
+#include "wait.h"
+#include "debug.h"
+
+#define SLAVE_TO_ADDR(n) (n << 1)
+#define TIMEOUT 100
+
+enum {
+ CMD_IODIRA = 0x00, // i/o direction register
+ CMD_IODIRB = 0x01,
+ CMD_GPPUA = 0x0C, // GPIO pull-up resistor register
+ CMD_GPPUB = 0x0D,
+ CMD_GPIOA = 0x12, // general purpose i/o port register (write modifies OLAT)
+ CMD_GPIOB = 0x13,
+};
+
+void mcp23018_init(uint8_t addr) {
+ static uint8_t s_init = 0;
+ if (!s_init) {
+ i2c_init();
+ wait_ms(1000);
+
+ s_init = 1;
+ }
+}
+
+bool mcp23018_set_config(uint8_t slave_addr, mcp23018_port_t port, uint8_t conf) {
+ uint8_t addr = SLAVE_TO_ADDR(slave_addr);
+ uint8_t cmdDirection = port ? CMD_IODIRB : CMD_IODIRA;
+ uint8_t cmdPullup = port ? CMD_GPPUB : CMD_GPPUA;
+
+ i2c_status_t ret = i2c_writeReg(addr, cmdDirection, &conf, sizeof(conf), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("mcp23018_set_config::directionFAILED::%u\n", ret);
+ return false;
+ }
+
+ ret = i2c_writeReg(addr, cmdPullup, &conf, sizeof(conf), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("mcp23018_set_config::pullupFAILED::%u\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+bool mcp23018_set_output(uint8_t slave_addr, mcp23018_port_t port, uint8_t conf) {
+ uint8_t addr = SLAVE_TO_ADDR(slave_addr);
+ uint8_t cmd = port ? CMD_GPIOB : CMD_GPIOA;
+
+ i2c_status_t ret = i2c_writeReg(addr, cmd, &conf, sizeof(conf), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("mcp23018_set_output::FAILED::%u\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+bool mcp23018_set_output_all(uint8_t slave_addr, uint8_t confA, uint8_t confB) {
+ uint8_t addr = SLAVE_TO_ADDR(slave_addr);
+ uint8_t conf[2] = {confA, confB};
+
+ i2c_status_t ret = i2c_writeReg(addr, CMD_GPIOA, &conf[0], sizeof(conf), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("mcp23018_set_output::FAILED::%u\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+bool mcp23018_readPins(uint8_t slave_addr, mcp23018_port_t port, uint8_t* out) {
+ uint8_t addr = SLAVE_TO_ADDR(slave_addr);
+ uint8_t cmd = port ? CMD_GPIOB : CMD_GPIOA;
+
+ i2c_status_t ret = i2c_readReg(addr, cmd, out, sizeof(uint8_t), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("mcp23018_readPins::FAILED::%u\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+bool mcp23018_readPins_all(uint8_t slave_addr, uint16_t* out) {
+ uint8_t addr = SLAVE_TO_ADDR(slave_addr);
+
+ typedef union {
+ uint8_t u8[2];
+ uint16_t u16;
+ } data16;
+
+ data16 data = {.u16 = 0};
+
+ i2c_status_t ret = i2c_readReg(addr, CMD_GPIOA, &data.u8[0], sizeof(data), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("mcp23018_readPins::FAILED::%u\n", ret);
+ return false;
+ }
+
+ *out = data.u16;
+ return true;
+}
diff --git a/drivers/gpio/mcp23018.h b/drivers/gpio/mcp23018.h
new file mode 100644
index 0000000000..e7c2730dd1
--- /dev/null
+++ b/drivers/gpio/mcp23018.h
@@ -0,0 +1,65 @@
+// Copyright 2022 zvecr<git@zvecr.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Port ID
+ */
+typedef enum {
+ mcp23018_PORTA,
+ mcp23018_PORTB,
+} mcp23018_port_t;
+
+/**
+ * Helpers for set_config
+ */
+enum {
+ ALL_OUTPUT = 0,
+ ALL_INPUT = 0xFF,
+};
+
+/**
+ * Helpers for set_output
+ */
+enum {
+ ALL_LOW = 0,
+ ALL_HIGH = 0xFF,
+};
+
+/**
+ * Init expander and any other dependent drivers
+ */
+void mcp23018_init(uint8_t slave_addr);
+
+/**
+ * Configure input/output to a given port
+ */
+bool mcp23018_set_config(uint8_t slave_addr, mcp23018_port_t port, uint8_t conf);
+
+/**
+ * Write high/low to a given port
+ */
+bool mcp23018_set_output(uint8_t slave_addr, mcp23018_port_t port, uint8_t conf);
+
+/**
+ * Write high/low to both ports sequentially
+ *
+ * - slightly faster than multiple set_output
+ */
+bool mcp23018_set_output_all(uint8_t slave_addr, uint8_t confA, uint8_t confB);
+
+/**
+ * Read state of a given port
+ */
+bool mcp23018_readPins(uint8_t slave_addr, mcp23018_port_t port, uint8_t* ret);
+
+/**
+ * Read state of both ports sequentially
+ *
+ * - slightly faster than multiple readPins
+ */
+bool mcp23018_readPins_all(uint8_t slave_addr, uint16_t* ret);
diff --git a/drivers/gpio/pca9555.c b/drivers/gpio/pca9555.c
index 02b5abbdde..adcd040083 100644
--- a/drivers/gpio/pca9555.c
+++ b/drivers/gpio/pca9555.c
@@ -1,18 +1,6 @@
-/* Copyright 2019
- *
- * 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 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/>.
- */
+// Copyright 2020 zvecr<git@zvecr.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#include "i2c_master.h"
#include "pca9555.h"
@@ -45,39 +33,59 @@ void pca9555_init(uint8_t slave_addr) {
// i2c_stop();
}
-void pca9555_set_config(uint8_t slave_addr, uint8_t port, uint8_t conf) {
+bool pca9555_set_config(uint8_t slave_addr, pca9555_port_t port, uint8_t conf) {
uint8_t addr = SLAVE_TO_ADDR(slave_addr);
uint8_t cmd = port ? CMD_CONFIG_1 : CMD_CONFIG_0;
i2c_status_t ret = i2c_writeReg(addr, cmd, &conf, sizeof(conf), TIMEOUT);
if (ret != I2C_STATUS_SUCCESS) {
print("pca9555_set_config::FAILED\n");
+ return false;
}
+
+ return true;
}
-void pca9555_set_output(uint8_t slave_addr, uint8_t port, uint8_t conf) {
+bool pca9555_set_output(uint8_t slave_addr, pca9555_port_t port, uint8_t conf) {
uint8_t addr = SLAVE_TO_ADDR(slave_addr);
uint8_t cmd = port ? CMD_OUTPUT_1 : CMD_OUTPUT_0;
i2c_status_t ret = i2c_writeReg(addr, cmd, &conf, sizeof(conf), TIMEOUT);
if (ret != I2C_STATUS_SUCCESS) {
print("pca9555_set_output::FAILED\n");
+ return false;
}
+
+ return true;
}
-uint8_t pca9555_readPins(uint8_t slave_addr, uint8_t port) {
+bool pca9555_set_output_all(uint8_t slave_addr, uint8_t confA, uint8_t confB) {
+ uint8_t addr = SLAVE_TO_ADDR(slave_addr);
+ uint8_t conf[2] = {confA, confB};
+
+ i2c_status_t ret = i2c_writeReg(addr, CMD_OUTPUT_0, &conf[0], sizeof(conf), TIMEOUT);
+ if (ret != I2C_STATUS_SUCCESS) {
+ dprintf("pca9555_set_output::FAILED::%u\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+bool pca9555_readPins(uint8_t slave_addr, pca9555_port_t port, uint8_t* out) {
uint8_t addr = SLAVE_TO_ADDR(slave_addr);
uint8_t cmd = port ? CMD_INPUT_1 : CMD_INPUT_0;
- uint8_t data = 0;
- i2c_status_t ret = i2c_readReg(addr, cmd, &data, sizeof(data), TIMEOUT);
+ i2c_status_t ret = i2c_readReg(addr, cmd, out, sizeof(uint8_t), TIMEOUT);
if (ret != I2C_STATUS_SUCCESS) {
print("pca9555_readPins::FAILED\n");
+ return false;
}
- return data;
+
+ return true;
}
-uint16_t pca9555_readAllPins(uint8_t slave_addr) {
+bool pca9555_readPins_all(uint8_t slave_addr, uint16_t* out) {
uint8_t addr = SLAVE_TO_ADDR(slave_addr);
typedef union {
@@ -85,11 +93,14 @@ uint16_t pca9555_readAllPins(uint8_t slave_addr) {
uint16_t u16;
} data16;
- data16 data;
+ data16 data = {.u16 = 0};
i2c_status_t ret = i2c_readReg(addr, CMD_INPUT_0, &data.u8[0], sizeof(data), TIMEOUT);
if (ret != I2C_STATUS_SUCCESS) {
- print("pca9555_readAllPins::FAILED\n");
+ print("pca9555_readPins_all::FAILED\n");
+ return false;
}
- return data.u16;
+
+ *out = data.u16;
+ return true;
}
diff --git a/drivers/gpio/pca9555.h b/drivers/gpio/pca9555.h
index 3341ec3eb5..6362ab68ae 100644
--- a/drivers/gpio/pca9555.h
+++ b/drivers/gpio/pca9555.h
@@ -1,20 +1,11 @@
-/* Copyright 2019
- *
- * 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 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/>.
- */
+// Copyright 2020 zvecr<git@zvecr.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
#pragma once
+#include <stdint.h>
+#include <stdbool.h>
+
/*
PCA9555
,----------.
@@ -38,20 +29,60 @@
`----------'
*/
-#define PCA9555_PORT0 0
-#define PCA9555_PORT1 1
+/**
+ * Port ID
+ */
+typedef enum {
+ PCA9555_PORT0,
+ PCA9555_PORT1,
+} pca9555_port_t;
-#define ALL_OUTPUT 0
-#define ALL_INPUT 0xFF
-#define ALL_LOW 0
-#define ALL_HIGH 0xFF
+/**
+ * Helpers for set_config
+ */
+enum {
+ ALL_OUTPUT = 0,
+ ALL_INPUT = 0xFF,
+};
+
+/**
+ * Helpers for set_output
+ */
+enum {
+ ALL_LOW = 0,
+ ALL_HIGH = 0xFF,
+};
+/**
+ * Init expander and any other dependent drivers
+ */
void pca9555_init(uint8_t slave_addr);
-void pca9555_set_config(uint8_t slave_addr, uint8_t port, uint8_t conf);
+/**
+ * Configure input/output to a given port
+ */
+bool pca9555_set_config(uint8_t slave_addr, pca9555_port_t port, uint8_t conf);
-void pca9555_set_output(uint8_t slave_addr, uint8_t port, uint8_t conf);
+/**
+ * Write high/low to a given port
+ */
+bool pca9555_set_output(uint8_t slave_addr, pca9555_port_t port, uint8_t conf);
-uint8_t pca9555_readPins(uint8_t slave_addr, uint8_t port);
+/**
+ * Write high/low to both ports sequentially
+ *
+ * - slightly faster than multiple set_output
+ */
+bool pca9555_set_output_all(uint8_t slave_addr, uint8_t confA, uint8_t confB);
-uint16_t pca9555_readAllPins(uint8_t slave_addr);
+/**
+ * Read state of a given port
+ */
+bool pca9555_readPins(uint8_t slave_addr, pca9555_port_t port, uint8_t* ret);
+
+/**
+ * Read state of both ports sequentially
+ *
+ * - slightly faster than multiple readPins
+ */
+bool pca9555_readPins_all(uint8_t slave_addr, uint16_t* ret);
diff --git a/drivers/gpio/sn74x138.c b/drivers/gpio/sn74x138.c
new file mode 100644
index 0000000000..222e5db56c
--- /dev/null
+++ b/drivers/gpio/sn74x138.c
@@ -0,0 +1,65 @@
+/* Copyright 2022
+ *
+ * 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 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 "sn74x138.h"
+#include "gpio.h"
+
+#define ADDRESS_PIN_COUNT 3
+
+#ifndef SN74X138_ADDRESS_PINS
+# error sn74x138: no address pins defined!
+#endif
+
+static const pin_t address_pins[ADDRESS_PIN_COUNT] = SN74X138_ADDRESS_PINS;
+
+void sn74x138_init(void) {
+ for (int i = 0; i < ADDRESS_PIN_COUNT; i++) {
+ setPinOutput(address_pins[i]);
+ writePinLow(address_pins[i]);
+ }
+
+#if defined(SN74X138_E1_PIN)
+ setPinOutput(SN74X138_E1_PIN);
+ writePinHigh(SN74X138_E1_PIN);
+#endif
+
+#if defined(SN74X138_E2_PIN)
+ setPinOutput(SN74X138_E2_PIN);
+ writePinHigh(SN74X138_E2_PIN);
+#endif
+#if defined(SN74X138_E3_PIN)
+ setPinOutput(SN74X138_E3_PIN);
+ writePinLow(SN74X138_E3_PIN);
+#endif
+}
+
+void sn74x138_set_enabled(bool enabled) {
+#if defined(SN74X138_E1_PIN)
+ writePin(SN74X138_E1_PIN, !enabled);
+#endif
+#if defined(SN74X138_E2_PIN)
+ writePin(SN74X138_E2_PIN, !enabled);
+#endif
+#if defined(SN74X138_E3_PIN)
+ writePin(SN74X138_E3_PIN, enabled);
+#endif
+}
+
+void sn74x138_set_addr(uint8_t address) {
+ for (int i = 0; i < ADDRESS_PIN_COUNT; i++) {
+ writePin(address_pins[i], address & (1 << i));
+ }
+}
diff --git a/drivers/gpio/sn74x138.h b/drivers/gpio/sn74x138.h
new file mode 100644
index 0000000000..6f1f20e618
--- /dev/null
+++ b/drivers/gpio/sn74x138.h
@@ -0,0 +1,48 @@
+/* Copyright 2022
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Driver for 74x138 3-to-8 decoder/demultiplexer with inverting outputs
+ * https://assets.nexperia.com/documents/data-sheet/74HC_HCT138.pdf
+ */
+
+/**
+ * Initialize the address and output enable pins.
+ */
+void sn74x138_init(void);
+
+/**
+ * Set the enabled state.
+ *
+ * When enabled is true, pulls the E1 and E2 pins low, and the E3 pin high.
+ *
+ * \param enabled The enable state to set.
+ */
+void sn74x138_set_enabled(bool enabled);
+
+/**
+ * Set the output pin address.
+ *
+ * The selected output pin will be pulled low, while the remaining output pins will be high.
+ *
+ * \param address The address to set, from 0 to 7.
+ */
+void sn74x138_set_addr(uint8_t address);
diff --git a/drivers/led/ckled2001.c b/drivers/led/ckled2001.c
index 990e50cb60..8d71805a24 100644
--- a/drivers/led/ckled2001.c
+++ b/drivers/led/ckled2001.c
@@ -125,7 +125,16 @@ void CKLED2001_init(uint8_t addr) {
// Set CURRENT PAGE (Page 4)
CKLED2001_write_register(addr, CONFIGURE_CMD_PAGE, CURRENT_TUNE_PAGE);
for (int i = 0; i < LED_CURRENT_TUNE_LENGTH; i++) {
- CKLED2001_write_register(addr, i, 0xFF);
+ switch (i) {
+ case 2:
+ case 5:
+ case 8:
+ case 11:
+ CKLED2001_write_register(addr, i, 0xA0);
+ break;
+ default:
+ CKLED2001_write_register(addr, i, 0xFF);
+ }
}
// Enable LEDs ON/OFF
diff --git a/drivers/led/issi/is31fl3733-simple.c b/drivers/led/issi/is31fl3733-simple.c
new file mode 100644
index 0000000000..777895bf89
--- /dev/null
+++ b/drivers/led/issi/is31fl3733-simple.c
@@ -0,0 +1,248 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2021 Doni Crosby
+ * Copyright 2021 Leo Deng
+ *
+ * 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 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 "is31fl3733-simple.h"
+#include "i2c_master.h"
+#include "wait.h"
+
+// This is a 7-bit address, that gets left-shifted and bit 0
+// set to 0 for write, 1 for read (as per I2C protocol)
+// The address will vary depending on your wiring:
+// 00 <-> GND
+// 01 <-> SCL
+// 10 <-> SDA
+// 11 <-> VCC
+// ADDR1 represents A1:A0 of the 7-bit address.
+// ADDR2 represents A3:A2 of the 7-bit address.
+// The result is: 0b101(ADDR2)(ADDR1)
+#define ISSI_ADDR_DEFAULT 0x50
+
+#define ISSI_COMMANDREGISTER 0xFD
+#define ISSI_COMMANDREGISTER_WRITELOCK 0xFE
+#define ISSI_INTERRUPTMASKREGISTER 0xF0
+#define ISSI_INTERRUPTSTATUSREGISTER 0xF1
+
+#define ISSI_PAGE_LEDCONTROL 0x00 // PG0
+#define ISSI_PAGE_PWM 0x01 // PG1
+#define ISSI_PAGE_AUTOBREATH 0x02 // PG2
+#define ISSI_PAGE_FUNCTION 0x03 // PG3
+
+#define ISSI_REG_CONFIGURATION 0x00 // PG3
+#define ISSI_REG_GLOBALCURRENT 0x01 // PG3
+#define ISSI_REG_RESET 0x11 // PG3
+#define ISSI_REG_SWPULLUP 0x0F // PG3
+#define ISSI_REG_CSPULLUP 0x10 // PG3
+
+#ifndef ISSI_TIMEOUT
+# define ISSI_TIMEOUT 100
+#endif
+
+#ifndef ISSI_PERSISTENCE
+# define ISSI_PERSISTENCE 0
+#endif
+
+#ifndef ISSI_PWM_FREQUENCY
+# define ISSI_PWM_FREQUENCY 0b000 // PFS - IS31FL3733B only
+#endif
+
+#ifndef ISSI_SWPULLUP
+# define ISSI_SWPULLUP PUR_0R
+#endif
+
+#ifndef ISSI_CSPULLUP
+# define ISSI_CSPULLUP PUR_0R
+#endif
+
+// Transfer buffer for TWITransmitData()
+uint8_t g_twi_transfer_buffer[20];
+
+// These buffers match the IS31FL3733 PWM registers.
+// The control buffers match the PG0 LED On/Off registers.
+// Storing them like this is optimal for I2C transfers to the registers.
+// We could optimize this and take out the unused registers from these
+// buffers and the transfers in IS31FL3733_write_pwm_buffer() but it's
+// probably not worth the extra complexity.
+uint8_t g_pwm_buffer[LED_DRIVER_COUNT][192];
+bool g_pwm_buffer_update_required[LED_DRIVER_COUNT] = {false};
+
+/* There's probably a better way to init this... */
+#if LED_DRIVER_COUNT == 1
+uint8_t g_led_control_registers[LED_DRIVER_COUNT][24] = {{0}};
+#elif LED_DRIVER_COUNT == 2
+uint8_t g_led_control_registers[LED_DRIVER_COUNT][24] = {{0}, {0}};
+#elif LED_DRIVER_COUNT == 3
+uint8_t g_led_control_registers[LED_DRIVER_COUNT][24] = {{0}, {0}, {0}};
+#elif LED_DRIVER_COUNT == 4
+uint8_t g_led_control_registers[LED_DRIVER_COUNT][24] = {{0}, {0}, {0}, {0}};
+#endif
+bool g_led_control_registers_update_required[LED_DRIVER_COUNT] = {false};
+
+bool IS31FL3733_write_register(uint8_t addr, uint8_t reg, uint8_t data) {
+ // If the transaction fails function returns false.
+ g_twi_transfer_buffer[0] = reg;
+ g_twi_transfer_buffer[1] = data;
+
+#if ISSI_PERSISTENCE > 0
+ for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT) != 0) {
+ return false;
+ }
+ }
+#else
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT) != 0) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+bool IS31FL3733_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) {
+ // Assumes PG1 is already selected.
+ // If any of the transactions fails function returns false.
+ // Transmit PWM registers in 12 transfers of 16 bytes.
+ // g_twi_transfer_buffer[] is 20 bytes
+
+ // Iterate over the pwm_buffer contents at 16 byte intervals.
+ for (int i = 0; i < 192; i += 16) {
+ g_twi_transfer_buffer[0] = i;
+ // Copy the data from i to i+15.
+ // Device will auto-increment register for data after the first byte
+ // Thus this sets registers 0x00-0x0F, 0x10-0x1F, etc. in one transfer.
+ for (int j = 0; j < 16; j++) {
+ g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j];
+ }
+
+#if ISSI_PERSISTENCE > 0
+ for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT) != 0) {
+ return false;
+ }
+ }
+#else
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT) != 0) {
+ return false;
+ }
+#endif
+ }
+ return true;
+}
+
+void IS31FL3733_init(uint8_t addr, uint8_t sync) {
+ // In order to avoid the LEDs being driven with garbage data
+ // in the LED driver's PWM registers, shutdown is enabled last.
+ // Set up the mode and other settings, clear the PWM registers,
+ // then disable software shutdown.
+ // Sync is passed so set it according to the datasheet.
+
+ // Unlock the command register.
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+
+ // Select PG0
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_LEDCONTROL);
+ // Turn off all LEDs.
+ for (int i = 0x00; i <= 0x17; i++) {
+ IS31FL3733_write_register(addr, i, 0x00);
+ }
+
+ // Unlock the command register.
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+
+ // Select PG1
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM);
+ // Set PWM on all LEDs to 0
+ // No need to setup Breath registers to PWM as that is the default.
+ for (int i = 0x00; i <= 0xBF; i++) {
+ IS31FL3733_write_register(addr, i, 0x00);
+ }
+
+ // Unlock the command register.
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+
+ // Select PG3
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_FUNCTION);
+ // Set de-ghost pull-up resistors (SWx)
+ IS31FL3733_write_register(addr, ISSI_REG_SWPULLUP, ISSI_SWPULLUP);
+ // Set de-ghost pull-down resistors (CSx)
+ IS31FL3733_write_register(addr, ISSI_REG_CSPULLUP, ISSI_CSPULLUP);
+ // Set global current to maximum.
+ IS31FL3733_write_register(addr, ISSI_REG_GLOBALCURRENT, 0xFF);
+ // Disable software shutdown.
+ IS31FL3733_write_register(addr, ISSI_REG_CONFIGURATION, ((sync & 0b11) << 6) | ((ISSI_PWM_FREQUENCY & 0b111) << 3) | 0x01);
+
+ // Wait 10ms to ensure the device has woken up.
+ wait_ms(10);
+}
+
+void IS31FL3733_set_value(int index, uint8_t value) {
+ if (index >= 0 && index < DRIVER_LED_TOTAL) {
+ is31_led led = g_is31_leds[index];
+
+ g_pwm_buffer[led.driver][led.v] = value;
+ g_pwm_buffer_update_required[led.driver] = true;
+ }
+}
+
+void IS31FL3733_set_value_all(uint8_t value) {
+ for (int i = 0; i < DRIVER_LED_TOTAL; i++) {
+ IS31FL3733_set_value(i, value);
+ }
+}
+
+void IS31FL3733_set_led_control_register(uint8_t index, bool value) {
+ is31_led led = g_is31_leds[index];
+
+ uint8_t control_register = led.v / 8;
+ uint8_t bit_value = led.v % 8;
+
+ if (value) {
+ g_led_control_registers[led.driver][control_register] |= (1 << bit_value);
+ } else {
+ g_led_control_registers[led.driver][control_register] &= ~(1 << bit_value);
+ }
+
+ g_led_control_registers_update_required[led.driver] = true;
+}
+
+void IS31FL3733_update_pwm_buffers(uint8_t addr, uint8_t index) {
+ if (g_pwm_buffer_update_required[index]) {
+ // Firstly we need to unlock the command register and select PG1.
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM);
+
+ // If any of the transactions fail we risk writing dirty PG0,
+ // refresh page 0 just in case.
+ if (!IS31FL3733_write_pwm_buffer(addr, g_pwm_buffer[index])) {
+ g_led_control_registers_update_required[index] = true;
+ }
+ g_pwm_buffer_update_required[index] = false;
+ }
+}
+
+void IS31FL3733_update_led_control_registers(uint8_t addr, uint8_t index) {
+ if (g_led_control_registers_update_required[index]) {
+ // Firstly we need to unlock the command register and select PG0
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+ IS31FL3733_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_LEDCONTROL);
+ for (int i = 0; i < 24; i++) {
+ IS31FL3733_write_register(addr, i, g_led_control_registers[index][i]);
+ }
+ g_led_control_registers_update_required[index] = false;
+ }
+}
diff --git a/drivers/led/issi/is31fl3733-simple.h b/drivers/led/issi/is31fl3733-simple.h
new file mode 100644
index 0000000000..f071db5e40
--- /dev/null
+++ b/drivers/led/issi/is31fl3733-simple.h
@@ -0,0 +1,260 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2021 Doni Crosby
+ * Copyright 2021 Leo Deng
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "progmem.h"
+
+typedef struct is31_led {
+ uint8_t driver : 2;
+ uint8_t v;
+} __attribute__((packed)) is31_led;
+
+extern const is31_led __flash g_is31_leds[DRIVER_LED_TOTAL];
+
+void IS31FL3733_init(uint8_t addr, uint8_t sync);
+bool IS31FL3733_write_register(uint8_t addr, uint8_t reg, uint8_t data);
+bool IS31FL3733_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer);
+
+void IS31FL3733_set_value(int index, uint8_t value);
+void IS31FL3733_set_value_all(uint8_t value);
+
+void IS31FL3733_set_led_control_register(uint8_t index, bool value);
+
+// This should not be called from an interrupt
+// (eg. from a timer interrupt).
+// Call this while idle (in between matrix scans).
+// If the buffer is dirty, it will update the driver with the buffer.
+void IS31FL3733_update_pwm_buffers(uint8_t addr, uint8_t index);
+void IS31FL3733_update_led_control_registers(uint8_t addr, uint8_t index);
+
+#define PUR_0R 0x00 // No PUR resistor
+#define PUR_05KR 0x02 // 0.5k Ohm resistor in t_NOL
+#define PUR_3KR 0x03 // 3.0k Ohm resistor on all the time
+#define PUR_4KR 0x04 // 4.0k Ohm resistor on all the time
+#define PUR_8KR 0x05 // 8.0k Ohm resistor on all the time
+#define PUR_16KR 0x06 // 16k Ohm resistor on all the time
+#define PUR_32KR 0x07 // 32k Ohm resistor in t_NOL
+
+#define A_1 0x00
+#define A_2 0x01
+#define A_3 0x02
+#define A_4 0x03
+#define A_5 0x04
+#define A_6 0x05
+#define A_7 0x06
+#define A_8 0x07
+#define A_9 0x08
+#define A_10 0x09
+#define A_11 0x0A
+#define A_12 0x0B
+#define A_13 0x0C
+#define A_14 0x0D
+#define A_15 0x0E
+#define A_16 0x0F
+
+#define B_1 0x10
+#define B_2 0x11
+#define B_3 0x12
+#define B_4 0x13
+#define B_5 0x14
+#define B_6 0x15
+#define B_7 0x16
+#define B_8 0x17
+#define B_9 0x18
+#define B_10 0x19
+#define B_11 0x1A
+#define B_12 0x1B
+#define B_13 0x1C
+#define B_14 0x1D
+#define B_15 0x1E
+#define B_16 0x1F
+
+#define C_1 0x20
+#define C_2 0x21
+#define C_3 0x22
+#define C_4 0x23
+#define C_5 0x24
+#define C_6 0x25
+#define C_7 0x26
+#define C_8 0x27
+#define C_9 0x28
+#define C_10 0x29
+#define C_11 0x2A
+#define C_12 0x2B
+#define C_13 0x2C
+#define C_14 0x2D
+#define C_15 0x2E
+#define C_16 0x2F
+
+#define D_1 0x30
+#define D_2 0x31
+#define D_3 0x32
+#define D_4 0x33
+#define D_5 0x34
+#define D_6 0x35
+#define D_7 0x36
+#define D_8 0x37
+#define D_9 0x38
+#define D_10 0x39
+#define D_11 0x3A
+#define D_12 0x3B
+#define D_13 0x3C
+#define D_14 0x3D
+#define D_15 0x3E
+#define D_16 0x3F
+
+#define E_1 0x40
+#define E_2 0x41
+#define E_3 0x42
+#define E_4 0x43
+#define E_5 0x44
+#define E_6 0x45
+#define E_7 0x46
+#define E_8 0x47
+#define E_9 0x48
+#define E_10 0x49
+#define E_11 0x4A
+#define E_12 0x4B
+#define E_13 0x4C
+#define E_14 0x4D
+#define E_15 0x4E
+#define E_16 0x4F
+
+#define F_1 0x50
+#define F_2 0x51
+#define F_3 0x52
+#define F_4 0x53
+#define F_5 0x54
+#define F_6 0x55
+#define F_7 0x56
+#define F_8 0x57
+#define F_9 0x58
+#define F_10 0x59
+#define F_11 0x5A
+#define F_12 0x5B
+#define F_13 0x5C
+#define F_14 0x5D
+#define F_15 0x5E
+#define F_16 0x5F
+
+#define G_1 0x60
+#define G_2 0x61
+#define G_3 0x62
+#define G_4 0x63
+#define G_5 0x64
+#define G_6 0x65
+#define G_7 0x66
+#define G_8 0x67
+#define G_9 0x68
+#define G_10 0x69
+#define G_11 0x6A
+#define G_12 0x6B
+#define G_13 0x6C
+#define G_14 0x6D
+#define G_15 0x6E
+#define G_16 0x6F
+
+#define H_1 0x70
+#define H_2 0x71
+#define H_3 0x72
+#define H_4 0x73
+#define H_5 0x74
+#define H_6 0x75
+#define H_7 0x76
+#define H_8 0x77
+#define H_9 0x78
+#define H_10 0x79
+#define H_11 0x7A
+#define H_12 0x7B
+#define H_13 0x7C
+#define H_14 0x7D
+#define H_15 0x7E
+#define H_16 0x7F
+
+#define I_1 0x80
+#define I_2 0x81
+#define I_3 0x82
+#define I_4 0x83
+#define I_5 0x84
+#define I_6 0x85
+#define I_7 0x86
+#define I_8 0x87
+#define I_9 0x88
+#define I_10 0x89
+#define I_11 0x8A
+#define I_12 0x8B
+#define I_13 0x8C
+#define I_14 0x8D
+#define I_15 0x8E
+#define I_16 0x8F
+
+#define J_1 0x90
+#define J_2 0x91
+#define J_3 0x92
+#define J_4 0x93
+#define J_5 0x94
+#define J_6 0x95
+#define J_7 0x96
+#define J_8 0x97
+#define J_9 0x98
+#define J_10 0x99
+#define J_11 0x9A
+#define J_12 0x9B
+#define J_13 0x9C
+#define J_14 0x9D
+#define J_15 0x9E
+#define J_16 0x9F
+
+#define K_1 0xA0
+#define K_2 0xA1
+#define K_3 0xA2
+#define K_4 0xA3
+#define K_5 0xA4
+#define K_6 0xA5
+#define K_7 0xA6
+#define K_8 0xA7
+#define K_9 0xA8
+#define K_10 0xA9
+#define K_11 0xAA
+#define K_12 0xAB
+#define K_13 0xAC
+#define K_14 0xAD
+#define K_15 0xAE
+#define K_16 0xAF
+
+#define L_1 0xB0
+#define L_2 0xB1
+#define L_3 0xB2
+#define L_4 0xB3
+#define L_5 0xB4
+#define L_6 0xB5
+#define L_7 0xB6
+#define L_8 0xB7
+#define L_9 0xB8
+#define L_10 0xB9
+#define L_11 0xBA
+#define L_12 0xBB
+#define L_13 0xBC
+#define L_14 0xBD
+#define L_15 0xBE
+#define L_16 0xBF
diff --git a/drivers/led/issi/is31fl3742.h b/drivers/led/issi/is31fl3742.h
new file mode 100644
index 0000000000..c96f12d0f1
--- /dev/null
+++ b/drivers/led/issi/is31fl3742.h
@@ -0,0 +1,299 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2020 MelGeek
+ * Copyright 2021 MasterSpoon
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+// This is a 7-bit address, that gets left-shifted and bit 0
+// set to 0 for write, 1 for read (as per I2C protocol)
+// The address will vary depending on your wiring:
+// 00 <-> GND
+// 01 <-> SCL
+// 10 <-> SDA
+// 11 <-> VCC
+// ADDR represents A1:A0 of the 7-bit address.
+// The result is: 0b01100(ADDR)
+#ifndef DRIVER_ADDR_1
+# define DRIVER_ADDR_1 0b0110000
+#endif
+
+// Command Registers
+#define ISSI_COMMANDREGISTER_WRITELOCK 0xFE
+#define ISSI_COMMANDREGISTER 0xFD
+#define ISSI_IDREGISTER 0xFC
+#define ISSI_REGISTER_UNLOCK 0xC5
+
+// Response Registers
+#define ISSI_PAGE_PWM 0x00
+#define ISSI_PAGE_SCALING 0x02
+#define ISSI_PAGE_FUNCTION 0x04
+
+// Registers under Function Register
+#define ISSI_REG_CONFIGURATION 0x00
+#define ISSI_REG_GLOBALCURRENT 0x01
+#define ISSI_REG_PULLDOWNUP 0x02
+#define ISSI_REG_SSR 0x41
+#define ISSI_REG_RESET 0x3F
+#define ISSI_REG_PWM_SET 0x36
+
+// Set defaults for Function Registers
+#ifndef ISSI_CONFIGURATION
+# define ISSI_CONFIGURATION 0x31
+#endif
+#ifndef ISSI_GLOBALCURRENT
+# define ISSI_GLOBALCURRENT 0xFF
+#endif
+#ifndef ISSI_PULLDOWNUP
+# define ISSI_PULLDOWNUP 0x55
+#endif
+#ifndef ISSI_PWM_SET
+# define ISSI_PWM_SET 0x00
+#endif
+
+// Set defaults for Spread Spectrum Register
+#ifndef ISSI_SSR_1
+# define ISSI_SSR_1 0x00
+#endif
+#ifndef ISSI_SSR_2
+# define ISSI_SSR_2 0x00
+#endif
+#ifndef ISSI_SSR_3
+# define ISSI_SSR_3 0x00
+#endif
+#ifndef ISSI_SSR_4
+# define ISSI_SSR_4 0x00
+#endif
+
+// Set defaults for Scaling registers
+#ifndef ISSI_SCAL_RED
+# define ISSI_SCAL_RED 0xFF
+#endif
+#ifndef ISSI_SCAL_BLUE
+# define ISSI_SCAL_BLUE 0xFF
+#endif
+#ifndef ISSI_SCAL_GREEN
+# define ISSI_SCAL_GREEN 0xFF
+#endif
+#define ISSI_SCAL_RED_OFF 0x00
+#define ISSI_SCAL_GREEN_OFF 0x00
+#define ISSI_SCAL_BLUE_OFF 0x00
+
+#ifndef ISSI_SCAL_LED
+# define ISSI_SCAL_LED 0xFF
+#endif
+#define ISSI_SCAL_LED_OFF 0x00
+
+// Set buffer sizes
+#define ISSI_MAX_LEDS 180
+#define ISSI_SCALING_SIZE 180
+#define ISSI_PWM_TRF_SIZE 18
+#define ISSI_SCALING_TRF_SIZE 18
+
+// Location of 1st bit for PWM and Scaling registers
+#define ISSI_PWM_REG_1ST 0x00
+#define ISSI_SCL_REG_1ST 0x00
+
+// Map CS SW locations to order in PWM / Scaling buffers
+// This matches the ORDER in the Datasheet Register not the POSITION
+// It will always count from 0x00 to (ISSI_MAX_LEDS - 1)
+#define CS1_SW1 0x00
+#define CS2_SW1 0x01
+#define CS3_SW1 0x02
+#define CS4_SW1 0x03
+#define CS5_SW1 0x04
+#define CS6_SW1 0x05
+#define CS7_SW1 0x06
+#define CS8_SW1 0x07
+#define CS9_SW1 0x08
+#define CS10_SW1 0x09
+#define CS11_SW1 0x0A
+#define CS12_SW1 0x0B
+#define CS13_SW1 0x0C
+#define CS14_SW1 0x0D
+#define CS15_SW1 0x0E
+#define CS16_SW1 0x0F
+#define CS17_SW1 0x10
+#define CS18_SW1 0x11
+#define CS19_SW1 0x12
+#define CS20_SW1 0x13
+#define CS21_SW1 0x14
+#define CS22_SW1 0x15
+#define CS23_SW1 0x16
+#define CS24_SW1 0x17
+#define CS25_SW1 0x18
+#define CS26_SW1 0x19
+#define CS27_SW1 0x1A
+#define CS28_SW1 0x1B
+#define CS29_SW1 0x1C
+#define CS30_SW1 0x1D
+
+#define CS1_SW2 0x1E
+#define CS2_SW2 0x1F
+#define CS3_SW2 0x20
+#define CS4_SW2 0x21
+#define CS5_SW2 0x22
+#define CS6_SW2 0x23
+#define CS7_SW2 0x24
+#define CS8_SW2 0x25
+#define CS9_SW2 0x26
+#define CS10_SW2 0x27
+#define CS11_SW2 0x28
+#define CS12_SW2 0x29
+#define CS13_SW2 0x2A
+#define CS14_SW2 0x2B
+#define CS15_SW2 0x2C
+#define CS16_SW2 0x2D
+#define CS17_SW2 0x2E
+#define CS18_SW2 0x2F
+#define CS19_SW2 0x30
+#define CS20_SW2 0x31
+#define CS21_SW2 0x32
+#define CS22_SW2 0x33
+#define CS23_SW2 0x34
+#define CS24_SW2 0x35
+#define CS25_SW2 0x36
+#define CS26_SW2 0x37
+#define CS27_SW2 0x38
+#define CS28_SW2 0x39
+#define CS29_SW2 0x3A
+#define CS30_SW2 0x3B
+
+#define CS1_SW3 0x3C
+#define CS2_SW3 0x3D
+#define CS3_SW3 0x3E
+#define CS4_SW3 0x3F
+#define CS5_SW3 0x40
+#define CS6_SW3 0x41
+#define CS7_SW3 0x42
+#define CS8_SW3 0x43
+#define CS9_SW3 0x44
+#define CS10_SW3 0x45
+#define CS11_SW3 0x46
+#define CS12_SW3 0x47
+#define CS13_SW3 0x48
+#define CS14_SW3 0x49
+#define CS15_SW3 0x4A
+#define CS16_SW3 0x4B
+#define CS17_SW3 0x4C
+#define CS18_SW3 0x4D
+#define CS19_SW3 0x4E
+#define CS20_SW3 0x4F
+#define CS21_SW3 0x50
+#define CS22_SW3 0x51
+#define CS23_SW3 0x52
+#define CS24_SW3 0x53
+#define CS25_SW3 0x54
+#define CS26_SW3 0x55
+#define CS27_SW3 0x56
+#define CS28_SW3 0x57
+#define CS29_SW3 0x58
+#define CS30_SW3 0x59
+
+#define CS1_SW4 0x5A
+#define CS2_SW4 0x5B
+#define CS3_SW4 0x5C
+#define CS4_SW4 0x5D
+#define CS5_SW4 0x5E
+#define CS6_SW4 0x5F
+#define CS7_SW4 0x60
+#define CS8_SW4 0x61
+#define CS9_SW4 0x62
+#define CS10_SW4 0x63
+#define CS11_SW4 0x64
+#define CS12_SW4 0x65
+#define CS13_SW4 0x66
+#define CS14_SW4 0x67
+#define CS15_SW4 0x68
+#define CS16_SW4 0x69
+#define CS17_SW4 0x6A
+#define CS18_SW4 0x6B
+#define CS19_SW4 0x6C
+#define CS20_SW4 0x6D
+#define CS21_SW4 0x6E
+#define CS22_SW4 0x6F
+#define CS23_SW4 0x70
+#define CS24_SW4 0x71
+#define CS25_SW4 0x72
+#define CS26_SW4 0x73
+#define CS27_SW4 0x74
+#define CS28_SW4 0x75
+#define CS29_SW4 0x76
+#define CS30_SW4 0x77
+
+#define CS1_SW5 0x78
+#define CS2_SW5 0x79
+#define CS3_SW5 0x7A
+#define CS4_SW5 0x7B
+#define CS5_SW5 0x7C
+#define CS6_SW5 0x7D
+#define CS7_SW5 0x7E
+#define CS8_SW5 0x7F
+#define CS9_SW5 0x80
+#define CS10_SW5 0x81
+#define CS11_SW5 0x82
+#define CS12_SW5 0x83
+#define CS13_SW5 0x84
+#define CS14_SW5 0x85
+#define CS15_SW5 0x86
+#define CS16_SW5 0x87
+#define CS17_SW5 0x88
+#define CS18_SW5 0x89
+#define CS19_SW5 0x8A
+#define CS20_SW5 0x8B
+#define CS21_SW5 0x8C
+#define CS22_SW5 0x8D
+#define CS23_SW5 0x8E
+#define CS24_SW5 0x8F
+#define CS25_SW5 0x90
+#define CS26_SW5 0x91
+#define CS27_SW5 0x92
+#define CS28_SW5 0x93
+#define CS29_SW5 0x94
+#define CS30_SW5 0x95
+
+#define CS1_SW6 0x96
+#define CS2_SW6 0x97
+#define CS3_SW6 0x98
+#define CS4_SW6 0x99
+#define CS5_SW6 0x9A
+#define CS6_SW6 0x9B
+#define CS7_SW6 0x9C
+#define CS8_SW6 0x9D
+#define CS9_SW6 0x9E
+#define CS10_SW6 0x9F
+#define CS11_SW6 0xA0
+#define CS12_SW6 0xA1
+#define CS13_SW6 0xA2
+#define CS14_SW6 0xA3
+#define CS15_SW6 0xA4
+#define CS16_SW6 0xA5
+#define CS17_SW6 0xA6
+#define CS18_SW6 0xA7
+#define CS19_SW6 0xA8
+#define CS20_SW6 0xA9
+#define CS21_SW6 0xAA
+#define CS22_SW6 0xAB
+#define CS23_SW6 0xAC
+#define CS24_SW6 0xAD
+#define CS25_SW6 0xAE
+#define CS26_SW6 0xAF
+#define CS27_SW6 0xB0
+#define CS28_SW6 0xB1
+#define CS29_SW6 0xB2
+#define CS30_SW6 0xB3
diff --git a/drivers/led/issi/is31fl3743.h b/drivers/led/issi/is31fl3743.h
new file mode 100644
index 0000000000..d8fcd79096
--- /dev/null
+++ b/drivers/led/issi/is31fl3743.h
@@ -0,0 +1,327 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2020 MelGeek
+ * Copyright 2021 MasterSpoon
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+// This is a 7-bit address, that gets left-shifted and bit 0
+// set to 0 for write, 1 for read (as per I2C protocol)
+// The address will vary depending on your wiring:
+// 00 <-> GND
+// 01 <-> SCL
+// 10 <-> SDA
+// 11 <-> VCC
+// ADDR1 represents A1:A0 of the 7-bit address.
+// ADDR2 represents A3:A2 of the 7-bit address.
+// The result is: 0b010(ADDR2)(ADDR1)
+#ifndef DRIVER_ADDR_1
+# define DRIVER_ADDR_1 0b0100000
+#endif
+
+// Set defaults for Spread Spectrum Register
+#ifndef ISSI_SSR_1
+# if DRIVER_COUNT == 1
+# define ISSI_SSR_1 0x00
+# else
+# define ISSI_SSR_1 0xC0
+# endif
+#endif
+#ifndef ISSI_SSR_2
+# define ISSI_SSR_2 0x80
+#endif
+#ifndef ISSI_SSR_3
+# define ISSI_SSR_3 0x80
+#endif
+#ifndef ISSI_SSR_4
+# define ISSI_SSR_4 0x80
+#endif
+
+// Command Registers
+#define ISSI_COMMANDREGISTER_WRITELOCK 0xFE
+#define ISSI_COMMANDREGISTER 0xFD
+#define ISSI_IDREGISTER 0xFC
+#define ISSI_REGISTER_UNLOCK 0xC5
+
+// Response Registers
+#define ISSI_PAGE_PWM 0x00
+#define ISSI_PAGE_SCALING 0x01
+#define ISSI_PAGE_FUNCTION 0x02
+
+// Registers under Function Register
+#define ISSI_REG_CONFIGURATION 0x00
+#define ISSI_REG_GLOBALCURRENT 0x01
+#define ISSI_REG_PULLDOWNUP 0x02
+#define ISSI_REG_TEMP 0x24
+#define ISSI_REG_SSR 0x25
+#define ISSI_REG_RESET 0x2F
+
+// Set defaults for Function Registers
+#ifndef ISSI_CONFIGURATION
+# define ISSI_CONFIGURATION 0x01
+#endif
+#ifndef ISSI_GLOBALCURRENT
+# define ISSI_GLOBALCURRENT 0xFF
+#endif
+#ifndef ISSI_PULLDOWNUP
+# define ISSI_PULLDOWNUP 0x33
+#endif
+#ifndef ISSI_TEMP
+# define ISSI_TEMP 0x00
+#endif
+
+// Set defaults for Scaling registers
+#ifndef ISSI_SCAL_RED
+# define ISSI_SCAL_RED 0xFF
+#endif
+#ifndef ISSI_SCAL_BLUE
+# define ISSI_SCAL_BLUE 0xFF
+#endif
+#ifndef ISSI_SCAL_GREEN
+# define ISSI_SCAL_GREEN 0xFF
+#endif
+#define ISSI_SCAL_RED_OFF 0x00
+#define ISSI_SCAL_GREEN_OFF 0x00
+#define ISSI_SCAL_BLUE_OFF 0x00
+
+#ifndef ISSI_SCAL_LED
+# define ISSI_SCAL_LED 0xFF
+#endif
+#define ISSI_SCAL_LED_OFF 0x00
+
+// Set buffer sizes
+#define ISSI_MAX_LEDS 198
+#define ISSI_SCALING_SIZE 198
+#define ISSI_PWM_TRF_SIZE 18
+#define ISSI_SCALING_TRF_SIZE 18
+
+// Location of 1st bit for PWM and Scaling registers
+#define ISSI_PWM_REG_1ST 0x01
+#define ISSI_SCL_REG_1ST 0x01
+
+// Map CS SW locations to order in PWM / Scaling buffers
+// This matches the ORDER in the Datasheet Register not the POSITION
+// It will always count from 0x00 to (ISSI_MAX_LEDS - 1)
+#define CS1_SW1 0x00
+#define CS2_SW1 0x01
+#define CS3_SW1 0x02
+#define CS4_SW1 0x03
+#define CS5_SW1 0x04
+#define CS6_SW1 0x05
+#define CS7_SW1 0x06
+#define CS8_SW1 0x07
+#define CS9_SW1 0x08
+#define CS10_SW1 0x09
+#define CS11_SW1 0x0A
+#define CS12_SW1 0x0B
+#define CS13_SW1 0x0C
+#define CS14_SW1 0x0D
+#define CS15_SW1 0x0E
+#define CS16_SW1 0x0F
+#define CS17_SW1 0x10
+#define CS18_SW1 0x11
+
+#define CS1_SW2 0x12
+#define CS2_SW2 0x13
+#define CS3_SW2 0x14
+#define CS4_SW2 0x15
+#define CS5_SW2 0x16
+#define CS6_SW2 0x17
+#define CS7_SW2 0x18
+#define CS8_SW2 0x19
+#define CS9_SW2 0x1A
+#define CS10_SW2 0x1B
+#define CS11_SW2 0x1C
+#define CS12_SW2 0x1D
+#define CS13_SW2 0x1E
+#define CS14_SW2 0x1F
+#define CS15_SW2 0x20
+#define CS16_SW2 0x21
+#define CS17_SW2 0x22
+#define CS18_SW2 0x23
+
+#define CS1_SW3 0x24
+#define CS2_SW3 0x25
+#define CS3_SW3 0x26
+#define CS4_SW3 0x27
+#define CS5_SW3 0x28
+#define CS6_SW3 0x29
+#define CS7_SW3 0x2A
+#define CS8_SW3 0x2B
+#define CS9_SW3 0x2C
+#define CS10_SW3 0x2D
+#define CS11_SW3 0x2E
+#define CS12_SW3 0x2F
+#define CS13_SW3 0x30
+#define CS14_SW3 0x31
+#define CS15_SW3 0x32
+#define CS16_SW3 0x33
+#define CS17_SW3 0x34
+#define CS18_SW3 0x35
+
+#define CS1_SW4 0x36
+#define CS2_SW4 0x37
+#define CS3_SW4 0x38
+#define CS4_SW4 0x39
+#define CS5_SW4 0x3A
+#define CS6_SW4 0x3B
+#define CS7_SW4 0x3C
+#define CS8_SW4 0x3D
+#define CS9_SW4 0x3E
+#define CS10_SW4 0x3F
+#define CS11_SW4 0x40
+#define CS12_SW4 0x41
+#define CS13_SW4 0x42
+#define CS14_SW4 0x43
+#define CS15_SW4 0x44
+#define CS16_SW4 0x45
+#define CS17_SW4 0x46
+#define CS18_SW4 0x47
+
+#define CS1_SW5 0x48
+#define CS2_SW5 0x49
+#define CS3_SW5 0x4A
+#define CS4_SW5 0x4B
+#define CS5_SW5 0x4C
+#define CS6_SW5 0x4D
+#define CS7_SW5 0x4E
+#define CS8_SW5 0x4F
+#define CS9_SW5 0x50
+#define CS10_SW5 0x51
+#define CS11_SW5 0x52
+#define CS12_SW5 0x53
+#define CS13_SW5 0x54
+#define CS14_SW5 0x55
+#define CS15_SW5 0x56
+#define CS16_SW5 0x57
+#define CS17_SW5 0x58
+#define CS18_SW5 0x59
+
+#define CS1_SW6 0x5A
+#define CS2_SW6 0x5B
+#define CS3_SW6 0x5C
+#define CS4_SW6 0x5D
+#define CS5_SW6 0x5E
+#define CS6_SW6 0x5F
+#define CS7_SW6 0x60
+#define CS8_SW6 0x61
+#define CS9_SW6 0x62
+#define CS10_SW6 0x63
+#define CS11_SW6 0x64
+#define CS12_SW6 0x65
+#define CS13_SW6 0x66
+#define CS14_SW6 0x67
+#define CS15_SW6 0x68
+#define CS16_SW6 0x69
+#define CS17_SW6 0x6A
+#define CS18_SW6 0x6B
+
+#define CS1_SW7 0x6C
+#define CS2_SW7 0x6D
+#define CS3_SW7 0x6E
+#define CS4_SW7 0x6F
+#define CS5_SW7 0x70
+#define CS6_SW7 0x71
+#define CS7_SW7 0x72
+#define CS8_SW7 0x73
+#define CS9_SW7 0x74
+#define CS10_SW7 0x75
+#define CS11_SW7 0x76
+#define CS12_SW7 0x77
+#define CS13_SW7 0x78
+#define CS14_SW7 0x79
+#define CS15_SW7 0x7A
+#define CS16_SW7 0x7B
+#define CS17_SW7 0x7C
+#define CS18_SW7 0x7D
+
+#define CS1_SW8 0x7E
+#define CS2_SW8 0x7F
+#define CS3_SW8 0x80
+#define CS4_SW8 0x81
+#define CS5_SW8 0x82
+#define CS6_SW8 0x83
+#define CS7_SW8 0x84
+#define CS8_SW8 0x85
+#define CS9_SW8 0x86
+#define CS10_SW8 0x87
+#define CS11_SW8 0x88
+#define CS12_SW8 0x89
+#define CS13_SW8 0x8A
+#define CS14_SW8 0x8B
+#define CS15_SW8 0x8C
+#define CS16_SW8 0x8D
+#define CS17_SW8 0x8E
+#define CS18_SW8 0x8F
+
+#define CS1_SW9 0x90
+#define CS2_SW9 0x91
+#define CS3_SW9 0x92
+#define CS4_SW9 0x93
+#define CS5_SW9 0x94
+#define CS6_SW9 0x95
+#define CS7_SW9 0x96
+#define CS8_SW9 0x97
+#define CS9_SW9 0x98
+#define CS10_SW9 0x99
+#define CS11_SW9 0x9A
+#define CS12_SW9 0x9B
+#define CS13_SW9 0x9C
+#define CS14_SW9 0x9D
+#define CS15_SW9 0x9E
+#define CS16_SW9 0x9F
+#define CS17_SW9 0xA0
+#define CS18_SW9 0xA1
+
+#define CS1_SW10 0xA2
+#define CS2_SW10 0xA3
+#define CS3_SW10 0xA4
+#define CS4_SW10 0xA5
+#define CS5_SW10 0xA6
+#define CS6_SW10 0xA7
+#define CS7_SW10 0xA8
+#define CS8_SW10 0xA9
+#define CS9_SW10 0xAA
+#define CS10_SW10 0xAB
+#define CS11_SW10 0xAC
+#define CS12_SW10 0xAD
+#define CS13_SW10 0xAE
+#define CS14_SW10 0xAF
+#define CS15_SW10 0xB0
+#define CS16_SW10 0xB1
+#define CS17_SW10 0xB2
+#define CS18_SW10 0xB3
+
+#define CS1_SW11 0xB4
+#define CS2_SW11 0xB5
+#define CS3_SW11 0xB6
+#define CS4_SW11 0xB7
+#define CS5_SW11 0xB8
+#define CS6_SW11 0xB9
+#define CS7_SW11 0xBA
+#define CS8_SW11 0xBB
+#define CS9_SW11 0xBC
+#define CS10_SW11 0xBD
+#define CS11_SW11 0xBE
+#define CS12_SW11 0xBF
+#define CS13_SW11 0xC0
+#define CS14_SW11 0xC1
+#define CS15_SW11 0xC2
+#define CS16_SW11 0xC3
+#define CS17_SW11 0xC4
+#define CS18_SW11 0xC5
diff --git a/drivers/led/issi/is31fl3745.h b/drivers/led/issi/is31fl3745.h
new file mode 100644
index 0000000000..ca5dd4a986
--- /dev/null
+++ b/drivers/led/issi/is31fl3745.h
@@ -0,0 +1,270 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2020 MelGeek
+ * Copyright 2021 MasterSpoon
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+// This is a 7-bit address, that gets left-shifted and bit 0
+// set to 0 for write, 1 for read (as per I2C protocol)
+// The address will vary depending on your wiring:
+// 00 <-> GND
+// 01 <-> SCL
+// 10 <-> SDA
+// 11 <-> VCC
+// ADDR1 represents A1:A0 of the 7-bit address.
+// ADDR2 represents A3:A2 of the 7-bit address.
+// The result is: 0b010(ADDR2)(ADDR1)
+#ifndef DRIVER_ADDR_1
+# define DRIVER_ADDR_1 0b0100000
+#endif
+
+// Set defaults for Spread Spectrum Register
+#ifndef ISSI_SSR_1
+# if DRIVER_COUNT == 1
+# define ISSI_SSR_1 0x00
+# else
+# define ISSI_SSR_1 0xC0
+# endif
+#endif
+#ifndef ISSI_SSR_2
+# define ISSI_SSR_2 0x80
+#endif
+#ifndef ISSI_SSR_3
+# define ISSI_SSR_3 0x80
+#endif
+#ifndef ISSI_SSR_4
+# define ISSI_SSR_4 0x80
+#endif
+
+// Command Registers
+#define ISSI_COMMANDREGISTER_WRITELOCK 0xFE
+#define ISSI_COMMANDREGISTER 0xFD
+#define ISSI_IDREGISTER 0xFC
+#define ISSI_REGISTER_UNLOCK 0xC5
+
+// Response Registers
+#define ISSI_PAGE_PWM 0x00
+#define ISSI_PAGE_SCALING 0x01
+#define ISSI_PAGE_FUNCTION 0x02
+
+// Registers under Function Register
+#define ISSI_REG_CONFIGURATION 0x00
+#define ISSI_REG_GLOBALCURRENT 0x01
+#define ISSI_REG_PULLDOWNUP 0x02
+#define ISSI_REG_TEMP 0x24
+#define ISSI_REG_SSR 0x25
+#define ISSI_REG_RESET 0x2F
+
+// Set defaults for Function Registers
+#ifndef ISSI_CONFIGURATION
+# define ISSI_CONFIGURATION 0x31
+#endif
+#ifndef ISSI_GLOBALCURRENT
+# define ISSI_GLOBALCURRENT 0xFF
+#endif
+#ifndef ISSI_PULLDOWNUP
+# define ISSI_PULLDOWNUP 0x33
+#endif
+#ifndef ISSI_TEMP
+# define ISSI_TEMP 0x00
+#endif
+
+// Set defaults for Scaling registers
+#ifndef ISSI_SCAL_RED
+# define ISSI_SCAL_RED 0xFF
+#endif
+#ifndef ISSI_SCAL_BLUE
+# define ISSI_SCAL_BLUE 0xFF
+#endif
+#ifndef ISSI_SCAL_GREEN
+# define ISSI_SCAL_GREEN 0xFF
+#endif
+#define ISSI_SCAL_RED_OFF 0x00
+#define ISSI_SCAL_GREEN_OFF 0x00
+#define ISSI_SCAL_BLUE_OFF 0x00
+
+#ifndef ISSI_SCAL_LED
+# define ISSI_SCAL_LED 0xFF
+#endif
+#define ISSI_SCAL_LED_OFF 0x00
+
+// Set buffer sizes
+#define ISSI_MAX_LEDS 144
+#define ISSI_SCALING_SIZE 144
+#define ISSI_PWM_TRF_SIZE 18
+#define ISSI_SCALING_TRF_SIZE 18
+
+// Location of 1st bit for PWM and Scaling registers
+#define ISSI_PWM_REG_1ST 0x01
+#define ISSI_SCL_REG_1ST 0x01
+
+// Map CS SW locations to order in PWM / Scaling buffers
+// This matches the ORDER in the Datasheet Register not the POSITION
+// It will always count from 0x00 to (ISSI_MAX_LEDS - 1)
+#define CS1_SW1 0x00
+#define CS2_SW1 0x01
+#define CS3_SW1 0x02
+#define CS4_SW1 0x03
+#define CS5_SW1 0x04
+#define CS6_SW1 0x05
+#define CS7_SW1 0x06
+#define CS8_SW1 0x07
+#define CS9_SW1 0x08
+#define CS10_SW1 0x09
+#define CS11_SW1 0x0A
+#define CS12_SW1 0x0B
+#define CS13_SW1 0x0C
+#define CS14_SW1 0x0D
+#define CS15_SW1 0x0E
+#define CS16_SW1 0x0F
+#define CS17_SW1 0x10
+#define CS18_SW1 0x11
+
+#define CS1_SW2 0x12
+#define CS2_SW2 0x13
+#define CS3_SW2 0x14
+#define CS4_SW2 0x15
+#define CS5_SW2 0x16
+#define CS6_SW2 0x17
+#define CS7_SW2 0x18
+#define CS8_SW2 0x19
+#define CS9_SW2 0x1A
+#define CS10_SW2 0x1B
+#define CS11_SW2 0x1C
+#define CS12_SW2 0x1D
+#define CS13_SW2 0x1E
+#define CS14_SW2 0x1F
+#define CS15_SW2 0x20
+#define CS16_SW2 0x21
+#define CS17_SW2 0x22
+#define CS18_SW2 0x23
+
+#define CS1_SW3 0x24
+#define CS2_SW3 0x25
+#define CS3_SW3 0x26
+#define CS4_SW3 0x27
+#define CS5_SW3 0x28
+#define CS6_SW3 0x29
+#define CS7_SW3 0x2A
+#define CS8_SW3 0x2B
+#define CS9_SW3 0x2C
+#define CS10_SW3 0x2D
+#define CS11_SW3 0x2E
+#define CS12_SW3 0x2F
+#define CS13_SW3 0x30
+#define CS14_SW3 0x31
+#define CS15_SW3 0x32
+#define CS16_SW3 0x33
+#define CS17_SW3 0x34
+#define CS18_SW3 0x35
+
+#define CS1_SW4 0x36
+#define CS2_SW4 0x37
+#define CS3_SW4 0x38
+#define CS4_SW4 0x39
+#define CS5_SW4 0x3A
+#define CS6_SW4 0x3B
+#define CS7_SW4 0x3C
+#define CS8_SW4 0x3D
+#define CS9_SW4 0x3E
+#define CS10_SW4 0x3F
+#define CS11_SW4 0x40
+#define CS12_SW4 0x41
+#define CS13_SW4 0x42
+#define CS14_SW4 0x43
+#define CS15_SW4 0x44
+#define CS16_SW4 0x45
+#define CS17_SW4 0x46
+#define CS18_SW4 0x47
+
+#define CS1_SW5 0x48
+#define CS2_SW5 0x49
+#define CS3_SW5 0x4A
+#define CS4_SW5 0x4B
+#define CS5_SW5 0x4C
+#define CS6_SW5 0x4D
+#define CS7_SW5 0x4E
+#define CS8_SW5 0x4F
+#define CS9_SW5 0x50
+#define CS10_SW5 0x51
+#define CS11_SW5 0x52
+#define CS12_SW5 0x53
+#define CS13_SW5 0x54
+#define CS14_SW5 0x55
+#define CS15_SW5 0x56
+#define CS16_SW5 0x57
+#define CS17_SW5 0x58
+#define CS18_SW5 0x59
+
+#define CS1_SW6 0x5A
+#define CS2_SW6 0x5B
+#define CS3_SW6 0x5C
+#define CS4_SW6 0x5D
+#define CS5_SW6 0x5E
+#define CS6_SW6 0x5F
+#define CS7_SW6 0x60
+#define CS8_SW6 0x61
+#define CS9_SW6 0x62
+#define CS10_SW6 0x63
+#define CS11_SW6 0x64
+#define CS12_SW6 0x65
+#define CS13_SW6 0x66
+#define CS14_SW6 0x67
+#define CS15_SW6 0x68
+#define CS16_SW6 0x69
+#define CS17_SW6 0x6A
+#define CS18_SW6 0x6B
+
+#define CS1_SW7 0x6C
+#define CS2_SW7 0x6D
+#define CS3_SW7 0x6E
+#define CS4_SW7 0x6F
+#define CS5_SW7 0x70
+#define CS6_SW7 0x71
+#define CS7_SW7 0x72
+#define CS8_SW7 0x73
+#define CS9_SW7 0x74
+#define CS10_SW7 0x75
+#define CS11_SW7 0x76
+#define CS12_SW7 0x77
+#define CS13_SW7 0x78
+#define CS14_SW7 0x79
+#define CS15_SW7 0x7A
+#define CS16_SW7 0x7B
+#define CS17_SW7 0x7C
+#define CS18_SW7 0x7D
+
+#define CS1_SW8 0x7E
+#define CS2_SW8 0x7F
+#define CS3_SW8 0x80
+#define CS4_SW8 0x81
+#define CS5_SW8 0x82
+#define CS6_SW8 0x83
+#define CS7_SW8 0x84
+#define CS8_SW8 0x85
+#define CS9_SW8 0x86
+#define CS10_SW8 0x87
+#define CS11_SW8 0x88
+#define CS12_SW8 0x89
+#define CS13_SW8 0x8A
+#define CS14_SW8 0x8B
+#define CS15_SW8 0x8C
+#define CS16_SW8 0x8D
+#define CS17_SW8 0x8E
+#define CS18_SW8 0x8F
diff --git a/drivers/led/issi/is31fl3746.h b/drivers/led/issi/is31fl3746.h
new file mode 100644
index 0000000000..f89f281533
--- /dev/null
+++ b/drivers/led/issi/is31fl3746.h
@@ -0,0 +1,198 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2020 MelGeek
+ * Copyright 2021 MasterSpoon
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+// This is a 7-bit address, that gets left-shifted and bit 0
+// set to 0 for write, 1 for read (as per I2C protocol)
+// The address will vary depending on your wiring:
+// 00 <-> GND
+// 01 <-> SCL
+// 10 <-> SDA
+// 11 <-> VCC
+// ADDR1 represents A1:A0 of the 7-bit address.
+// ADDR2 represents A3:A2 of the 7-bit address.
+// The result is: 0b110(ADDR2)(ADDR1)
+#ifndef DRIVER_ADDR_1
+# define DRIVER_ADDR_1 0b1100000
+#endif
+
+// Set defaults for Spread Spectrum Register
+#ifndef ISSI_SSR_1
+# define ISSI_SSR_1 0x00
+#endif
+#ifndef ISSI_SSR_2
+# define ISSI_SSR_2 0x00
+#endif
+#ifndef ISSI_SSR_3
+# define ISSI_SSR_3 0x00
+#endif
+#ifndef ISSI_SSR_4
+# define ISSI_SSR_4 0x00
+#endif
+
+// Command Registers
+#define ISSI_COMMANDREGISTER_WRITELOCK 0xFE
+#define ISSI_COMMANDREGISTER 0xFD
+#define ISSI_IDREGISTER 0xFC
+#define ISSI_REGISTER_UNLOCK 0xC5
+
+// Response Registers
+#define ISSI_PAGE_PWM 0x00
+#define ISSI_PAGE_SCALING 0x01
+#define ISSI_PAGE_FUNCTION 0x01
+
+// Registers under Function Register
+#define ISSI_REG_CONFIGURATION 0x50
+#define ISSI_REG_GLOBALCURRENT 0x51
+#define ISSI_REG_PULLDOWNUP 0x52
+#define ISSI_REG_TEMP 0x5F
+#define ISSI_REG_SSR 0x60
+#define ISSI_REG_RESET 0x8F
+#define ISSI_REG_PWM_ENABLE 0xE0
+#define ISSI_REG_PWM_SET 0xE2
+
+// Set defaults for Function Registers
+#ifndef ISSI_CONFIGURATION
+# define ISSI_CONFIGURATION 0x01
+#endif
+#ifndef ISSI_GLOBALCURRENT
+# define ISSI_GLOBALCURRENT 0xFF
+#endif
+#ifndef ISSI_PULLDOWNUP
+# define ISSI_PULLDOWNUP 0x33
+#endif
+#ifndef ISSI_TEMP
+# define ISSI_TEMP 0x00
+#endif
+#ifndef ISSI_PWM_ENABLE
+# define ISSI_PWM_ENABLE 0x00
+#endif
+#ifndef ISSI_PWM_SET
+# define ISSI_PWM_SET 0x00
+#endif
+
+// Set defaults for Scaling registers
+#ifndef ISSI_SCAL_RED
+# define ISSI_SCAL_RED 0xFF
+#endif
+#ifndef ISSI_SCAL_BLUE
+# define ISSI_SCAL_BLUE 0xFF
+#endif
+#ifndef ISSI_SCAL_GREEN
+# define ISSI_SCAL_GREEN 0xFF
+#endif
+#define ISSI_SCAL_RED_OFF 0x00
+#define ISSI_SCAL_GREEN_OFF 0x00
+#define ISSI_SCAL_BLUE_OFF 0x00
+
+#ifndef ISSI_SCAL_LED
+# define ISSI_SCAL_LED 0xFF
+#endif
+#define ISSI_SCAL_LED_OFF 0x00
+
+// Set buffer sizes
+#define ISSI_MAX_LEDS 72
+#define ISSI_SCALING_SIZE 72
+#define ISSI_PWM_TRF_SIZE 18
+#define ISSI_SCALING_TRF_SIZE 18
+
+// Location of 1st bit for PWM and Scaling registers
+#define ISSI_PWM_REG_1ST 0x01
+#define ISSI_SCL_REG_1ST 0x01
+
+// Map CS SW locations to order in PWM / Scaling buffers
+// This matches the ORDER in the Datasheet Register not the POSITION
+// It will always count from 0x00 to (ISSI_MAX_LEDS - 1)
+#define CS1_SW1 0x00
+#define CS2_SW1 0x01
+#define CS3_SW1 0x02
+#define CS4_SW1 0x03
+#define CS5_SW1 0x04
+#define CS6_SW1 0x05
+#define CS7_SW1 0x06
+#define CS8_SW1 0x07
+#define CS9_SW1 0x08
+#define CS10_SW1 0x09
+#define CS11_SW1 0x0A
+#define CS12_SW1 0x0B
+#define CS13_SW1 0x0C
+#define CS14_SW1 0x0D
+#define CS15_SW1 0x0E
+#define CS16_SW1 0x0F
+#define CS17_SW1 0x10
+#define CS18_SW1 0x11
+
+#define CS1_SW2 0x12
+#define CS2_SW2 0x13
+#define CS3_SW2 0x14
+#define CS4_SW2 0x15
+#define CS5_SW2 0x16
+#define CS6_SW2 0x17
+#define CS7_SW2 0x18
+#define CS8_SW2 0x19
+#define CS9_SW2 0x1A
+#define CS10_SW2 0x1B
+#define CS11_SW2 0x1C
+#define CS12_SW2 0x1D
+#define CS13_SW2 0x1E
+#define CS14_SW2 0x1F
+#define CS15_SW2 0x20
+#define CS16_SW2 0x21
+#define CS17_SW2 0x22
+#define CS18_SW2 0x23
+
+#define CS1_SW3 0x24
+#define CS2_SW3 0x25
+#define CS3_SW3 0x26
+#define CS4_SW3 0x27
+#define CS5_SW3 0x28
+#define CS6_SW3 0x29
+#define CS7_SW3 0x2A
+#define CS8_SW3 0x2B
+#define CS9_SW3 0x2C
+#define CS10_SW3 0x2D
+#define CS11_SW3 0x2E
+#define CS12_SW3 0x2F
+#define CS13_SW3 0x30
+#define CS14_SW3 0x31
+#define CS15_SW3 0x32
+#define CS16_SW3 0x33
+#define CS17_SW3 0x34
+#define CS18_SW3 0x35
+
+#define CS1_SW4 0x36
+#define CS2_SW4 0x37
+#define CS3_SW4 0x38
+#define CS4_SW4 0x39
+#define CS5_SW4 0x3A
+#define CS6_SW4 0x3B
+#define CS7_SW4 0x3C
+#define CS8_SW4 0x3D
+#define CS9_SW4 0x3E
+#define CS10_SW4 0x3F
+#define CS11_SW4 0x40
+#define CS12_SW4 0x41
+#define CS13_SW4 0x42
+#define CS14_SW4 0x43
+#define CS15_SW4 0x44
+#define CS16_SW4 0x45
+#define CS17_SW4 0x46
+#define CS18_SW4 0x47
diff --git a/drivers/led/issi/is31flcommon.c b/drivers/led/issi/is31flcommon.c
new file mode 100644
index 0000000000..9f4b2123ff
--- /dev/null
+++ b/drivers/led/issi/is31flcommon.c
@@ -0,0 +1,230 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2020 MelGeek
+ * Copyright 2021 MasterSpoon
+ *
+ * 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 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 "is31flcommon.h"
+#include "i2c_master.h"
+#include "wait.h"
+#include <string.h>
+
+// Set defaults for Timeout and Persistence
+#ifndef ISSI_TIMEOUT
+# define ISSI_TIMEOUT 100
+#endif
+#ifndef ISSI_PERSISTENCE
+# define ISSI_PERSISTENCE 0
+#endif
+
+// Transfer buffer for TWITransmitData()
+uint8_t g_twi_transfer_buffer[20];
+
+// These buffers match the PWM & scaling registers.
+// Storing them like this is optimal for I2C transfers to the registers.
+uint8_t g_pwm_buffer[DRIVER_COUNT][ISSI_MAX_LEDS];
+bool g_pwm_buffer_update_required[DRIVER_COUNT] = {false};
+
+uint8_t g_scaling_buffer[DRIVER_COUNT][ISSI_SCALING_SIZE];
+bool g_scaling_buffer_update_required[DRIVER_COUNT] = {false};
+
+// For writing of single register entry
+void IS31FL_write_single_register(uint8_t addr, uint8_t reg, uint8_t data) {
+ // Set register address and register data ready to write
+ g_twi_transfer_buffer[0] = reg;
+ g_twi_transfer_buffer[1] = data;
+
+#if ISSI_PERSISTENCE > 0
+ for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT) == 0) break;
+ }
+#else
+ i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT);
+#endif
+}
+
+// For writing of mulitple register entries to make use of address auto increment
+// Once the controller has been called and we have written the first bit of data
+// the controller will move to the next register meaning we can write sequential blocks.
+bool IS31FL_write_multi_registers(uint8_t addr, uint8_t *source_buffer, uint8_t buffer_size, uint8_t transfer_size, uint8_t start_reg_addr) {
+ // Split the buffer into chunks to transfer
+ for (int i = 0; i < buffer_size; i += transfer_size) {
+ // Set the first entry of transfer buffer to the first register we want to write
+ g_twi_transfer_buffer[0] = i + start_reg_addr;
+ // Copy the section of our source buffer into the transfer buffer after first register address
+ memcpy(g_twi_transfer_buffer + 1, source_buffer + i, transfer_size);
+
+#if ISSI_PERSISTENCE > 0
+ for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) {
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, transfer_size + 1, ISSI_TIMEOUT) != 0) {
+ return false;
+ }
+ }
+#else
+ if (i2c_transmit(addr << 1, g_twi_transfer_buffer, transfer_size + 1, ISSI_TIMEOUT) != 0) {
+ return false;
+ }
+#endif
+ }
+ return true;
+}
+
+void IS31FL_unlock_register(uint8_t addr, uint8_t page) {
+ // unlock the command register and select Page to write
+ IS31FL_write_single_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, ISSI_REGISTER_UNLOCK);
+ IS31FL_write_single_register(addr, ISSI_COMMANDREGISTER, page);
+}
+
+void IS31FL_common_init(uint8_t addr, uint8_t ssr) {
+ // Setup phase, need to take out of software shutdown and configure
+ // ISSI_SSR_x is passed to allow Master / Slave setting where applicable
+
+ // Unlock the command register & select Function Register
+ IS31FL_unlock_register(addr, ISSI_PAGE_FUNCTION);
+ // Set Configuration Register to remove Software shutdown
+ IS31FL_write_single_register(addr, ISSI_REG_CONFIGURATION, ISSI_CONFIGURATION);
+ // Set Golbal Current Control Register
+ IS31FL_write_single_register(addr, ISSI_REG_GLOBALCURRENT, ISSI_GLOBALCURRENT);
+ // Set Pull up & Down for SWx CSy
+ IS31FL_write_single_register(addr, ISSI_REG_PULLDOWNUP, ISSI_PULLDOWNUP);
+// Set Tempature Status
+#ifdef ISSI_REG_TEMP
+ IS31FL_write_single_register(addr, ISSI_REG_TEMP, ISSI_TEMP);
+#endif
+ // Set Spread Spectrum Register, passed through as sets SYNC function
+ IS31FL_write_single_register(addr, ISSI_REG_SSR, ssr);
+// Set PWM Frequency Enable Register if applicable
+#ifdef ISSI_REG_PWM_ENABLE
+ IS31FL_write_single_register(addr, ISSI_REG_PWM_ENABLE, ISSI_PWM_ENABLE);
+#endif
+// Set PWM Frequency Register if applicable
+#ifdef ISSI_REG_PWM_SET
+ IS31FL_write_single_register(addr, ISSI_REG_PWM_SET, ISSI_PWM_SET);
+#endif
+
+ // Wait 10ms to ensure the device has woken up.
+ wait_ms(10);
+}
+
+void IS31FL_common_update_pwm_register(uint8_t addr, uint8_t index) {
+ if (g_pwm_buffer_update_required[index]) {
+ // Queue up the correct page
+ IS31FL_unlock_register(addr, ISSI_PAGE_PWM);
+ // Hand off the update to IS31FL_write_multi_registers
+ IS31FL_write_multi_registers(addr, g_pwm_buffer[index], ISSI_MAX_LEDS, ISSI_PWM_TRF_SIZE, ISSI_PWM_REG_1ST);
+ // Update flags that pwm_buffer has been updated
+ g_pwm_buffer_update_required[index] = false;
+ }
+}
+
+#ifdef ISSI_MANUAL_SCALING
+void IS31FL_set_manual_scaling_buffer(void) {
+ for (int i = 0; i < ISSI_MANUAL_SCALING; i++) {
+ is31_led scale = g_is31_scaling[i];
+ if (scale.driver >= 0 && scale.driver < DRIVER_LED_TOTAL) {
+ is31_led led = g_is31_leds[scale.driver];
+
+# ifdef RGB_MATRIX_ENABLE
+ g_scaling_buffer[led.driver][led.r] = scale.r;
+ g_scaling_buffer[led.driver][led.g] = scale.g;
+ g_scaling_buffer[led.driver][led.b] = scale.b;
+# elif defined(LED_MATRIX_ENABLE)
+ g_scaling_buffer[led.driver][led.v] = scale.v;
+# endif
+ g_scaling_buffer_update_required[led.driver] = true;
+ }
+ }
+}
+#endif
+
+void IS31FL_common_update_scaling_register(uint8_t addr, uint8_t index) {
+ if (g_scaling_buffer_update_required[index]) {
+ // Queue up the correct page
+ IS31FL_unlock_register(addr, ISSI_PAGE_SCALING);
+ // Hand off the update to IS31FL_write_multi_registers
+ IS31FL_write_multi_registers(addr, g_scaling_buffer[index], ISSI_SCALING_SIZE, ISSI_SCALING_TRF_SIZE, ISSI_SCL_REG_1ST);
+ // Update flags that scaling_buffer has been updated
+ g_scaling_buffer_update_required[index] = false;
+ }
+}
+
+#ifdef RGB_MATRIX_ENABLE
+// Colour is set by adjusting PWM register
+void IS31FL_RGB_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) {
+ if (index >= 0 && index < DRIVER_LED_TOTAL) {
+ is31_led led = g_is31_leds[index];
+
+ g_pwm_buffer[led.driver][led.r] = red;
+ g_pwm_buffer[led.driver][led.g] = green;
+ g_pwm_buffer[led.driver][led.b] = blue;
+ g_pwm_buffer_update_required[led.driver] = true;
+ }
+}
+
+void IS31FL_RGB_set_color_all(uint8_t red, uint8_t green, uint8_t blue) {
+ for (int i = 0; i < DRIVER_LED_TOTAL; i++) {
+ IS31FL_RGB_set_color(i, red, green, blue);
+ }
+}
+
+// Setup Scaling register that decides the peak current of each LED
+void IS31FL_RGB_set_scaling_buffer(uint8_t index, bool red, bool green, bool blue) {
+ is31_led led = g_is31_leds[index];
+ if (red) {
+ g_scaling_buffer[led.driver][led.r] = ISSI_SCAL_RED;
+ } else {
+ g_scaling_buffer[led.driver][led.r] = ISSI_SCAL_RED_OFF;
+ }
+ if (green) {
+ g_scaling_buffer[led.driver][led.g] = ISSI_SCAL_GREEN;
+ } else {
+ g_scaling_buffer[led.driver][led.g] = ISSI_SCAL_GREEN_OFF;
+ }
+ if (blue) {
+ g_scaling_buffer[led.driver][led.b] = ISSI_SCAL_BLUE;
+ } else {
+ g_scaling_buffer[led.driver][led.b] = ISSI_SCAL_BLUE_OFF;
+ }
+ g_scaling_buffer_update_required[led.driver] = true;
+}
+
+#elif defined(LED_MATRIX_ENABLE)
+// LED Matrix Specific scripts
+void IS31FL_simple_set_scaling_buffer(uint8_t index, bool value) {
+ is31_led led = g_is31_leds[index];
+ if (value) {
+ g_scaling_buffer[led.driver][led.v] = ISSI_SCAL_LED;
+ } else {
+ g_scaling_buffer[led.driver][led.v] = ISSI_SCAL_LED_OFF;
+ }
+ g_scaling_buffer_update_required[led.driver] = true;
+}
+
+void IS31FL_simple_set_brightness(int index, uint8_t value) {
+ if (index >= 0 && index < DRIVER_LED_TOTAL) {
+ is31_led led = g_is31_leds[index];
+ g_pwm_buffer[led.driver][led.v] = value;
+ g_pwm_buffer_update_required[led.driver] = true;
+ }
+}
+
+void IS31FL_simple_set_brigntness_all(uint8_t value) {
+ for (int i = 0; i < DRIVER_LED_TOTAL; i++) {
+ IS31FL_simple_set_brightness(i, value);
+ }
+}
+#endif
diff --git a/drivers/led/issi/is31flcommon.h b/drivers/led/issi/is31flcommon.h
new file mode 100644
index 0000000000..77e9665e32
--- /dev/null
+++ b/drivers/led/issi/is31flcommon.h
@@ -0,0 +1,78 @@
+/* Copyright 2017 Jason Williams
+ * Copyright 2018 Jack Humbert
+ * Copyright 2018 Yiancar
+ * Copyright 2020 MelGeek
+ * Copyright 2021 MasterSpoon
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "progmem.h"
+
+// Which variant header file to use
+#ifdef IS31FL3742A
+# include "is31fl3742.h"
+#elif defined(IS31FL3743A)
+# include "is31fl3743.h"
+#elif defined(IS31FL3745)
+# include "is31fl3745.h"
+#elif defined(IS31FL3746A)
+# include "is31fl3746.h"
+#endif
+
+#ifdef RGB_MATRIX_ENABLE
+typedef struct is31_led {
+ uint8_t driver;
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+} __attribute__((packed)) is31_led;
+
+#elif defined(LED_MATRIX_ENABLE)
+typedef struct is31_led {
+ uint8_t driver;
+ uint8_t v;
+} __attribute__((packed)) is31_led;
+#endif
+
+#ifdef ISSI_MANUAL_SCALING
+extern const is31_led __flash g_is31_scaling[];
+void IS31FL_set_manual_scaling_buffer(void);
+#endif
+
+extern const is31_led __flash g_is31_leds[DRIVER_LED_TOTAL];
+
+void IS31FL_write_single_register(uint8_t addr, uint8_t reg, uint8_t data);
+bool IS31FL_write_multi_registers(uint8_t addr, uint8_t *source_buffer, uint8_t buffer_size, uint8_t transfer_size, uint8_t start_reg_addr);
+void IS31FL_unlock_register(uint8_t addr, uint8_t page);
+void IS31FL_common_init(uint8_t addr, uint8_t ssr);
+
+void IS31FL_common_update_pwm_register(uint8_t addr, uint8_t index);
+void IS31FL_common_update_scaling_register(uint8_t addr, uint8_t index);
+
+#ifdef RGB_MATRIX_ENABLE
+// RGB Matrix Specific scripts
+void IS31FL_RGB_set_color(int index, uint8_t red, uint8_t green, uint8_t blue);
+void IS31FL_RGB_set_color_all(uint8_t red, uint8_t green, uint8_t blue);
+void IS31FL_RGB_set_scaling_buffer(uint8_t index, bool red, bool green, bool blue);
+#elif defined(LED_MATRIX_ENABLE)
+// LED Matrix Specific scripts
+void IS31FL_simple_set_scaling_buffer(uint8_t index, bool value);
+void IS31FL_simple_set_brightness(int index, uint8_t value);
+void IS31FL_simple_set_brigntness_all(uint8_t value);
+#endif
diff --git a/drivers/sensors/adns9800.c b/drivers/sensors/adns9800.c
index c52f991804..d167841968 100644
--- a/drivers/sensors/adns9800.c
+++ b/drivers/sensors/adns9800.c
@@ -135,10 +135,8 @@ void adns9800_init() {
wait_us(15);
// send all bytes of the firmware
- unsigned char c;
- for (int i = 0; i < FIRMWARE_LENGTH; i++) {
- c = (unsigned char)pgm_read_byte(adns9800_firmware_data + i);
- spi_write(c);
+ for (uint16_t i = 0; i < FIRMWARE_LENGTH; i++) {
+ spi_write(pgm_read_byte(firmware_data + i));
wait_us(15);
}
@@ -154,8 +152,8 @@ void adns9800_init() {
}
config_adns9800_t adns9800_get_config(void) {
- uint8_t config_1 = adns9800_read(REG_Configuration_I);
- return (config_adns9800_t){(config_1 & 0xFF) * CPI_STEP};
+ uint8_t cpival = adns9800_read(REG_Configuration_I);
+ return (config_adns9800_t){(cpival & 0xFF) * CPI_STEP};
}
void adns9800_set_config(config_adns9800_t config) {
@@ -164,8 +162,8 @@ void adns9800_set_config(config_adns9800_t config) {
}
uint16_t adns9800_get_cpi(void) {
- uint8_t config_1 = adns9800_read(REG_Configuration_I);
- return (uint16_t){(config_1 & 0xFF) * CPI_STEP};
+ uint8_t cpival = adns9800_read(REG_Configuration_I);
+ return (uint16_t)(cpival & 0xFF) * CPI_STEP;
}
void adns9800_set_cpi(uint16_t cpi) {
@@ -184,7 +182,7 @@ static int16_t convertDeltaToInt(uint8_t high, uint8_t low) {
}
report_adns9800_t adns9800_get_report(void) {
- report_adns9800_t report = {0, 0};
+ report_adns9800_t report = {0};
adns9800_spi_start();
diff --git a/drivers/sensors/adns9800_srom_A6.h b/drivers/sensors/adns9800_srom_A6.h
index d86ecbbd9e..e698a401b9 100644
--- a/drivers/sensors/adns9800_srom_A6.h
+++ b/drivers/sensors/adns9800_srom_A6.h
@@ -6,7 +6,7 @@
// clang-format off
-const uint8_t adns9800_firmware_data[FIRMWARE_LENGTH] PROGMEM = {
+const uint8_t firmware_data[FIRMWARE_LENGTH] PROGMEM = {
0x03, 0xA6, 0x68, 0x1E, 0x7D, 0x10, 0x7E, 0x7E, 0x5F, 0x1C, 0xB8, 0xF2, 0x47, 0x0C, 0x7B, 0x74,
0x4B, 0x14, 0x8B, 0x75, 0x66, 0x51, 0x0B, 0x8C, 0x76, 0x74, 0x4B, 0x14, 0xAA, 0xD6, 0x0F, 0x9C,
0xBA, 0xF6, 0x6E, 0x3F, 0xDD, 0x38, 0xD5, 0x02, 0x80, 0x9B, 0x82, 0x6D, 0x58, 0x13, 0xA4, 0xAB,
diff --git a/drivers/sensors/pimoroni_trackball.c b/drivers/sensors/pimoroni_trackball.c
index 7d390056ea..2867e763bc 100644
--- a/drivers/sensors/pimoroni_trackball.c
+++ b/drivers/sensors/pimoroni_trackball.c
@@ -33,8 +33,24 @@
static uint16_t precision = 128;
-float pimoroni_trackball_get_precision(void) { return ((float)precision / 128); }
-void pimoroni_trackball_set_precision(float floatprecision) { precision = (floatprecision * 128); }
+uint16_t pimoroni_trackball_get_cpi(void) { return (precision * 125); }
+/**
+ * @brief Sets the scaling value for pimoroni trackball
+ *
+ * Sets a scaling value for pimoroni trackball to allow runtime adjustment. This isn't used by the sensor and is an
+ * approximation so the functions are consistent across drivers.
+ *
+ * NOTE: This rounds down to the nearest number divisable by 125 that's a positive integer, values below 125 are clamped to 125.
+ *
+ * @param cpi uint16_t
+ */
+void pimoroni_trackball_set_cpi(uint16_t cpi) {
+ if (cpi < 249) {
+ precision = 1;
+ } else {
+ precision = (cpi - (cpi % 125)) / 125;
+ }
+}
void pimoroni_trackball_set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
uint8_t data[4] = {r, g, b, w};
@@ -60,7 +76,7 @@ i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data) {
return status;
}
-__attribute__((weak)) void pimironi_trackball_device_init(void) {
+__attribute__((weak)) void pimoroni_trackball_device_init(void) {
i2c_init();
pimoroni_trackball_set_rgbw(0x00, 0x00, 0x00, 0x00);
}
diff --git a/drivers/sensors/pimoroni_trackball.h b/drivers/sensors/pimoroni_trackball.h
index 59ee8724ba..e20ee748a7 100644
--- a/drivers/sensors/pimoroni_trackball.h
+++ b/drivers/sensors/pimoroni_trackball.h
@@ -23,9 +23,6 @@
#ifndef PIMORONI_TRACKBALL_ADDRESS
# define PIMORONI_TRACKBALL_ADDRESS 0x0A
#endif
-#ifndef PIMORONI_TRACKBALL_INTERVAL_MS
-# define PIMORONI_TRACKBALL_INTERVAL_MS 8
-#endif
#ifndef PIMORONI_TRACKBALL_SCALE
# define PIMORONI_TRACKBALL_SCALE 5
#endif
@@ -52,10 +49,10 @@ typedef struct {
uint8_t click;
} pimoroni_data_t;
-void pimironi_trackball_device_init(void);
+void pimoroni_trackball_device_init(void);
void pimoroni_trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white);
int16_t pimoroni_trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale);
void pimoroni_trackball_adapt_values(int8_t* mouse, int16_t* offset);
-float pimoroni_trackball_get_precision(void);
-void pimoroni_trackball_set_precision(float precision);
+uint16_t pimoroni_trackball_get_cpi(void);
+void pimoroni_trackball_set_cpi(uint16_t cpi);
i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data);
diff --git a/drivers/sensors/pmw3360.c b/drivers/sensors/pmw3360.c
index 50d1c35801..8d1b08e22b 100644
--- a/drivers/sensors/pmw3360.c
+++ b/drivers/sensors/pmw3360.c
@@ -16,67 +16,71 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "spi_master.h"
#include "pmw3360.h"
#include "wait.h"
#include "debug.h"
#include "print.h"
-#include PMW3360_FIRMWARE_H
+#include "pmw3360_firmware.h"
// Registers
// clang-format off
-#define REG_Product_ID 0x00
-#define REG_Revision_ID 0x01
-#define REG_Motion 0x02
-#define REG_Delta_X_L 0x03
-#define REG_Delta_X_H 0x04
-#define REG_Delta_Y_L 0x05
-#define REG_Delta_Y_H 0x06
-#define REG_SQUAL 0x07
-#define REG_Raw_Data_Sum 0x08
-#define REG_Maximum_Raw_data 0x09
-#define REG_Minimum_Raw_data 0x0A
-#define REG_Shutter_Lower 0x0B
-#define REG_Shutter_Upper 0x0C
-#define REG_Control 0x0D
-#define REG_Config1 0x0F
-#define REG_Config2 0x10
-#define REG_Angle_Tune 0x11
-#define REG_Frame_Capture 0x12
-#define REG_SROM_Enable 0x13
-#define REG_Run_Downshift 0x14
-#define REG_Rest1_Rate_Lower 0x15
-#define REG_Rest1_Rate_Upper 0x16
-#define REG_Rest1_Downshift 0x17
-#define REG_Rest2_Rate_Lower 0x18
-#define REG_Rest2_Rate_Upper 0x19
-#define REG_Rest2_Downshift 0x1A
-#define REG_Rest3_Rate_Lower 0x1B
-#define REG_Rest3_Rate_Upper 0x1C
-#define REG_Observation 0x24
-#define REG_Data_Out_Lower 0x25
-#define REG_Data_Out_Upper 0x26
-#define REG_Raw_Data_Dump 0x29
-#define REG_SROM_ID 0x2A
-#define REG_Min_SQ_Run 0x2B
-#define REG_Raw_Data_Threshold 0x2C
-#define REG_Config5 0x2F
-#define REG_Power_Up_Reset 0x3A
-#define REG_Shutdown 0x3B
-#define REG_Inverse_Product_ID 0x3F
-#define REG_LiftCutoff_Tune3 0x41
-#define REG_Angle_Snap 0x42
-#define REG_LiftCutoff_Tune1 0x4A
-#define REG_Motion_Burst 0x50
-#define REG_LiftCutoff_Tune_Timeout 0x58
-#define REG_LiftCutoff_Tune_Min_Length 0x5A
-#define REG_SROM_Load_Burst 0x62
-#define REG_Lift_Config 0x63
-#define REG_Raw_Data_Burst 0x64
-#define REG_LiftCutoff_Tune2 0x65
+#define REG_Product_ID 0x00
+#define REG_Revision_ID 0x01
+#define REG_Motion 0x02
+#define REG_Delta_X_L 0x03
+#define REG_Delta_X_H 0x04
+#define REG_Delta_Y_L 0x05
+#define REG_Delta_Y_H 0x06
+#define REG_SQUAL 0x07
+#define REG_Raw_Data_Sum 0x08
+#define REG_Maximum_Raw_data 0x09
+#define REG_Minimum_Raw_data 0x0a
+#define REG_Shutter_Lower 0x0b
+#define REG_Shutter_Upper 0x0c
+#define REG_Control 0x0d
+#define REG_Config1 0x0f
+#define REG_Config2 0x10
+#define REG_Angle_Tune 0x11
+#define REG_Frame_Capture 0x12
+#define REG_SROM_Enable 0x13
+#define REG_Run_Downshift 0x14
+#define REG_Rest1_Rate_Lower 0x15
+#define REG_Rest1_Rate_Upper 0x16
+#define REG_Rest1_Downshift 0x17
+#define REG_Rest2_Rate_Lower 0x18
+#define REG_Rest2_Rate_Upper 0x19
+#define REG_Rest2_Downshift 0x1a
+#define REG_Rest3_Rate_Lower 0x1b
+#define REG_Rest3_Rate_Upper 0x1c
+#define REG_Observation 0x24
+#define REG_Data_Out_Lower 0x25
+#define REG_Data_Out_Upper 0x26
+#define REG_Raw_Data_Dump 0x29
+#define REG_SROM_ID 0x2a
+#define REG_Min_SQ_Run 0x2b
+#define REG_Raw_Data_Threshold 0x2c
+#define REG_Config5 0x2f
+#define REG_Power_Up_Reset 0x3a
+#define REG_Shutdown 0x3b
+#define REG_Inverse_Product_ID 0x3f
+#define REG_LiftCutoff_Tune3 0x41
+#define REG_Angle_Snap 0x42
+#define REG_LiftCutoff_Tune1 0x4a
+#define REG_Motion_Burst 0x50
+#define REG_LiftCutoff_Tune_Timeout 0x58
+#define REG_LiftCutoff_Tune_Min_Length 0x5a
+#define REG_SROM_Load_Burst 0x62
+#define REG_Lift_Config 0x63
+#define REG_Raw_Data_Burst 0x64
+#define REG_LiftCutoff_Tune2 0x65
+
+#define CPI_STEP 100
// clang-format on
+// limits to 0--119, resulting in a CPI range of 100 -- 12000 (as only steps of 100 are possible).
#ifndef MAX_CPI
-# define MAX_CPI 0x77 // limits to 0--119, should be max cpi/100
+# define MAX_CPI 0x77
#endif
bool _inBurst = false;
@@ -86,66 +90,50 @@ void print_byte(uint8_t byte) { dprintf("%c%c%c%c%c%c%c%c|", (byte & 0x80 ? '1'
#endif
#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
-bool spi_start_adv(void) {
+bool pmw3360_spi_start(void) {
bool status = spi_start(PMW3360_CS_PIN, PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR);
+ // tNCS-SCLK, 120ns
wait_us(1);
return status;
}
-void spi_stop_adv(void) {
- wait_us(1);
- spi_stop();
-}
+spi_status_t pmw3360_write(uint8_t reg_addr, uint8_t data) {
+ pmw3360_spi_start();
-spi_status_t spi_write_adv(uint8_t reg_addr, uint8_t data) {
if (reg_addr != REG_Motion_Burst) {
_inBurst = false;
}
- spi_start_adv();
// send address of the register, with MSBit = 1 to indicate it's a write
spi_status_t status = spi_write(reg_addr | 0x80);
status = spi_write(data);
- // tSCLK-NCS for write operation
- wait_us(20);
-
- // tSWW/tSWR (=120us) minus tSCLK-NCS. Could be shortened, but is looks like a safe lower bound
- wait_us(100);
+ // tSCLK-NCS for write operation is 35us
+ wait_us(35);
spi_stop();
+
+ // tSWW/tSWR (=180us) minus tSCLK-NCS. Could be shortened, but is looks like a safe lower bound
+ wait_us(145);
return status;
}
-uint8_t spi_read_adv(uint8_t reg_addr) {
- spi_start_adv();
+uint8_t pmw3360_read(uint8_t reg_addr) {
+ pmw3360_spi_start();
// send adress of the register, with MSBit = 0 to indicate it's a read
spi_write(reg_addr & 0x7f);
-
+ // tSRAD (=160us)
+ wait_us(160);
uint8_t data = spi_read();
// tSCLK-NCS for read operation is 120ns
wait_us(1);
+ spi_stop();
// tSRW/tSRR (=20us) minus tSCLK-NCS
wait_us(19);
-
- spi_stop();
return data;
}
-void pmw3360_set_cpi(uint16_t cpi) {
- uint8_t cpival = constrain((cpi / 100) - 1, 0, MAX_CPI);
-
- spi_start_adv();
- spi_write_adv(REG_Config1, cpival);
- spi_stop();
-}
-
-uint16_t pmw3360_get_cpi(void) {
- uint8_t cpival = spi_read_adv(REG_Config1);
- return (uint16_t)((cpival + 1) & 0xFF) * 100;
-}
-
bool pmw3360_init(void) {
setPinOutput(PMW3360_CS_PIN);
@@ -153,42 +141,51 @@ bool pmw3360_init(void) {
_inBurst = false;
spi_stop();
- spi_start_adv();
+ pmw3360_spi_start();
spi_stop();
- spi_write_adv(REG_Shutdown, 0xb6); // Shutdown first
+ pmw3360_write(REG_Shutdown, 0xb6); // Shutdown first
wait_ms(300);
- spi_start_adv();
+ pmw3360_spi_start();
wait_us(40);
- spi_stop_adv();
+ spi_stop();
wait_us(40);
- spi_write_adv(REG_Power_Up_Reset, 0x5a);
+ // power up, need to first drive NCS high then low, see above.
+ pmw3360_write(REG_Power_Up_Reset, 0x5a);
wait_ms(50);
- spi_read_adv(REG_Motion);
- spi_read_adv(REG_Delta_X_L);
- spi_read_adv(REG_Delta_X_H);
- spi_read_adv(REG_Delta_Y_L);
- spi_read_adv(REG_Delta_Y_H);
+ // read registers and discard
+ pmw3360_read(REG_Motion);
+ pmw3360_read(REG_Delta_X_L);
+ pmw3360_read(REG_Delta_X_H);
+ pmw3360_read(REG_Delta_Y_L);
+ pmw3360_read(REG_Delta_Y_H);
pmw3360_upload_firmware();
- spi_stop_adv();
+ spi_stop();
wait_ms(10);
pmw3360_set_cpi(PMW3360_CPI);
wait_ms(1);
- spi_write_adv(REG_Config2, 0x00);
+ pmw3360_write(REG_Config2, 0x00);
- spi_write_adv(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -30, 30));
+ pmw3360_write(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -127, 127));
- spi_write_adv(REG_Lift_Config, PMW3360_LIFTOFF_DISTANCE);
+ pmw3360_write(REG_Lift_Config, PMW3360_LIFTOFF_DISTANCE);
bool init_success = pmw3360_check_signature();
+#ifdef CONSOLE_ENABLE
+ if (init_success) {
+ dprintf("pmw3360 signature verified");
+ } else {
+ dprintf("pmw3360 signature verification failed!");
+ }
+#endif
writePinLow(PMW3360_CS_PIN);
@@ -196,86 +193,94 @@ bool pmw3360_init(void) {
}
void pmw3360_upload_firmware(void) {
- spi_write_adv(REG_SROM_Enable, 0x1d);
+ // Datasheet claims we need to disable REST mode first, but during startup
+ // it's already disabled and we're not turning it on ...
+ // pmw3360_write(REG_Config2, 0x00); // disable REST mode
+ pmw3360_write(REG_SROM_Enable, 0x1d);
wait_ms(10);
- spi_write_adv(REG_SROM_Enable, 0x18);
+ pmw3360_write(REG_SROM_Enable, 0x18);
- spi_start_adv();
+ pmw3360_spi_start();
spi_write(REG_SROM_Load_Burst | 0x80);
wait_us(15);
- unsigned char c;
- for (int i = 0; i < FIRMWARE_LENGTH; i++) {
- c = (unsigned char)pgm_read_byte(firmware_data + i);
- spi_write(c);
+ for (uint16_t i = 0; i < FIRMWARE_LENGTH; i++) {
+ spi_write(pgm_read_byte(firmware_data + i));
+#ifndef PMW3360_FIRMWARE_UPLOAD_FAST
wait_us(15);
+#endif
}
wait_us(200);
- spi_read_adv(REG_SROM_ID);
-
- spi_write_adv(REG_Config2, 0x00);
-
- spi_stop();
- wait_ms(10);
+ pmw3360_read(REG_SROM_ID);
+ pmw3360_write(REG_Config2, 0x00);
}
bool pmw3360_check_signature(void) {
- uint8_t pid = spi_read_adv(REG_Product_ID);
- uint8_t iv_pid = spi_read_adv(REG_Inverse_Product_ID);
- uint8_t SROM_ver = spi_read_adv(REG_SROM_ID);
+ uint8_t pid = pmw3360_read(REG_Product_ID);
+ uint8_t iv_pid = pmw3360_read(REG_Inverse_Product_ID);
+ uint8_t SROM_ver = pmw3360_read(REG_SROM_ID);
return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04
}
+uint16_t pmw3360_get_cpi(void) {
+ uint8_t cpival = pmw3360_read(REG_Config1);
+ return (uint16_t)((cpival + 1) & 0xFF) * CPI_STEP;
+}
+
+void pmw3360_set_cpi(uint16_t cpi) {
+ uint8_t cpival = constrain((cpi / CPI_STEP) - 1, 0, MAX_CPI);
+ pmw3360_write(REG_Config1, cpival);
+}
+
report_pmw3360_t pmw3360_read_burst(void) {
+ report_pmw3360_t report = {0};
+
if (!_inBurst) {
#ifdef CONSOLE_ENABLE
dprintf("burst on");
#endif
- spi_write_adv(REG_Motion_Burst, 0x00);
+ pmw3360_write(REG_Motion_Burst, 0x00);
_inBurst = true;
}
- spi_start_adv();
+ pmw3360_spi_start();
spi_write(REG_Motion_Burst);
- wait_us(35); // waits for tSRAD
+ wait_us(35); // waits for tSRAD_MOTBR
- report_pmw3360_t data = {0};
+ report.motion = spi_read();
+ spi_read(); // skip Observation
+ // delta registers
+ report.dx = spi_read();
+ report.mdx = spi_read();
+ report.dy = spi_read();
+ report.mdy = spi_read();
- data.motion = spi_read();
- spi_write(0x00); // skip Observation
- data.dx = spi_read();
- data.mdx = spi_read();
- data.dy = spi_read();
- data.mdy = spi_read();
+ if (report.motion & 0b111) { // panic recovery, sometimes burst mode works weird.
+ _inBurst = false;
+ }
spi_stop();
#ifdef CONSOLE_ENABLE
if (debug_mouse) {
- print_byte(data.motion);
- print_byte(data.dx);
- print_byte(data.mdx);
- print_byte(data.dy);
- print_byte(data.mdy);
+ print_byte(report.motion);
+ print_byte(report.dx);
+ print_byte(report.mdx);
+ print_byte(report.dy);
+ print_byte(report.mdy);
dprintf("\n");
}
#endif
- data.isMotion = (data.motion & 0x80) != 0;
- data.isOnSurface = (data.motion & 0x08) == 0;
- data.dx |= (data.mdx << 8);
- data.dx = data.dx * -1;
- data.dy |= (data.mdy << 8);
- data.dy = data.dy * -1;
-
- spi_stop();
+ report.isMotion = (report.motion & 0x80) != 0;
+ report.isOnSurface = (report.motion & 0x08) == 0;
+ report.dx |= (report.mdx << 8);
+ report.dx = report.dx * -1;
+ report.dy |= (report.mdy << 8);
+ report.dy = report.dy * -1;
- if (data.motion & 0b111) { // panic recovery, sometimes burst mode works weird.
- _inBurst = false;
- }
-
- return data;
+ return report;
}
diff --git a/drivers/sensors/pmw3360.h b/drivers/sensors/pmw3360.h
index 9aa8e13f8e..4e17aa0c46 100644
--- a/drivers/sensors/pmw3360.h
+++ b/drivers/sensors/pmw3360.h
@@ -19,8 +19,6 @@
#pragma once
#include <stdint.h>
-#include "report.h"
-#include "spi_master.h"
#ifndef PMW3360_CPI
# define PMW3360_CPI 1600
@@ -58,21 +56,6 @@
# error "No chip select pin defined -- missing PMW3360_CS_PIN"
#endif
-/*
-The pmw33660 and pmw3389 use the same registers and timing and such.
-The only differences between the two is the firmware used, and the
-range for the DPI. So add a semi-secret hack to allow use of the
-pmw3389's firmware blob. Also, can set the max cpi range too.
-This should work for the 3390 and 3391 too, in theory.
-*/
-#ifndef PMW3360_FIRMWARE_H
-# define PMW3360_FIRMWARE_H "pmw3360_firmware.h"
-#endif
-
-#ifdef CONSOLE_ENABLE
-void print_byte(uint8_t byte);
-#endif
-
typedef struct {
int8_t motion;
bool isMotion; // True if a motion is detected.
@@ -83,16 +66,10 @@ typedef struct {
int8_t mdy;
} report_pmw3360_t;
-bool spi_start_adv(void);
-void spi_stop_adv(void);
-spi_status_t spi_write_adv(uint8_t reg_addr, uint8_t data);
-uint8_t spi_read_adv(uint8_t reg_addr);
-bool pmw3360_init(void);
-void pmw3360_set_cpi(uint16_t cpi);
-uint16_t pmw3360_get_cpi(void);
-void pmw3360_upload_firmware(void);
-bool pmw3360_check_signature(void);
+bool pmw3360_init(void);
+void pmw3360_upload_firmware(void);
+bool pmw3360_check_signature(void);
+uint16_t pmw3360_get_cpi(void);
+void pmw3360_set_cpi(uint16_t cpi);
+/* Reads and clears the current delta values on the sensor */
report_pmw3360_t pmw3360_read_burst(void);
-
-#define degToRad(angleInDegrees) ((angleInDegrees)*M_PI / 180.0)
-#define radToDeg(angleInRadians) ((angleInRadians)*180.0 / M_PI)
diff --git a/drivers/sensors/pmw3389.c b/drivers/sensors/pmw3389.c
new file mode 100644
index 0000000000..963925bd38
--- /dev/null
+++ b/drivers/sensors/pmw3389.c
@@ -0,0 +1,292 @@
+/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+ * Copyright 2019 Sunjun Kim
+ * Copyright 2020 Ploopy Corporation
+ *
+ * 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 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 "spi_master.h"
+#include "pmw3389.h"
+#include "wait.h"
+#include "debug.h"
+#include "print.h"
+#include "pmw3389_firmware.h"
+
+// Registers
+// clang-format off
+#define REG_Product_ID 0x00
+#define REG_Revision_ID 0x01
+#define REG_Motion 0x02
+#define REG_Delta_X_L 0x03
+#define REG_Delta_X_H 0x04
+#define REG_Delta_Y_L 0x05
+#define REG_Delta_Y_H 0x06
+#define REG_SQUAL 0x07
+#define REG_RawData_Sum 0x08
+#define REG_Maximum_RawData 0x09
+#define REG_Minimum_RawData 0x0a
+#define REG_Shutter_Lower 0x0b
+#define REG_Shutter_Upper 0x0c
+#define REG_Ripple_Control 0x0d
+#define REG_Resolution_L 0x0e
+#define REG_Resolution_H 0x0f
+#define REG_Config2 0x10
+#define REG_Angle_Tune 0x11
+#define REG_Frame_Capture 0x12
+#define REG_SROM_Enable 0x13
+#define REG_Run_Downshift 0x14
+#define REG_Rest1_Rate_Lower 0x15
+#define REG_Rest1_Rate_Upper 0x16
+#define REG_Rest1_Downshift 0x17
+#define REG_Rest2_Rate_Lower 0x18
+#define REG_Rest2_Rate_Upper 0x19
+#define REG_Rest2_Downshift 0x1a
+#define REG_Rest3_Rate_Lower 0x1b
+#define REG_Rest3_Rate_Upper 0x1c
+#define REG_Observation 0x24
+#define REG_Data_Out_Lower 0x25
+#define REG_Data_Out_Upper 0x26
+#define REG_SROM_ID 0x2a
+#define REG_Min_SQ_Run 0x2b
+#define REG_RawData_Threshold 0x2c
+#define REG_Control2 0x2d
+#define REG_Config5_L 0x2e
+#define REG_Config5_H 0x2f
+#define REG_Power_Up_Reset 0X3a
+#define REG_Shutdown 0x3b
+#define REG_Inverse_Product_ID 0x3f
+#define REG_LiftCutoff_Cal3 0x41
+#define REG_Angle_Snap 0x42
+#define REG_LiftCutoff_Cal1 0x4a
+#define REG_Motion_Burst 0x50
+#define REG_SROM_Load_Burst 0x62
+#define REG_Lift_Config 0x63
+#define REG_RawData_Burst 0x64
+#define REG_LiftCutoff_Cal2 0x65
+#define REG_LiftCutoff_Cal_Timeout 0x71
+#define REG_LiftCutoff_Cal_Min_Length 0x72
+#define REG_PWM_Period_Cnt 0x73
+#define REG_PWM_Width_Cnt 0x74
+
+#define CPI_STEP 50
+// clang-format on
+
+// limits to 0--319, resulting in a CPI range of 50 -- 16000 (as only steps of 50 are possible).
+#ifndef MAX_CPI
+# define MAX_CPI 0x013f
+#endif
+
+bool _inBurst = false;
+
+#ifdef CONSOLE_ENABLE
+void print_byte(uint8_t byte) { dprintf("%c%c%c%c%c%c%c%c|", (byte & 0x80 ? '1' : '0'), (byte & 0x40 ? '1' : '0'), (byte & 0x20 ? '1' : '0'), (byte & 0x10 ? '1' : '0'), (byte & 0x08 ? '1' : '0'), (byte & 0x04 ? '1' : '0'), (byte & 0x02 ? '1' : '0'), (byte & 0x01 ? '1' : '0')); }
+#endif
+#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
+
+bool pmw3389_spi_start(void) {
+ bool status = spi_start(PMW3389_CS_PIN, PMW3389_SPI_LSBFIRST, PMW3389_SPI_MODE, PMW3389_SPI_DIVISOR);
+ // tNCS-SCLK, 120ns
+ wait_us(1);
+ return status;
+}
+
+spi_status_t pmw3389_write(uint8_t reg_addr, uint8_t data) {
+ pmw3389_spi_start();
+
+ if (reg_addr != REG_Motion_Burst) {
+ _inBurst = false;
+ }
+
+ // send address of the register, with MSBit = 1 to indicate it's a write
+ spi_status_t status = spi_write(reg_addr | 0x80);
+ status = spi_write(data);
+
+ // tSCLK-NCS for write operation is 35 us
+ wait_us(35);
+ spi_stop();
+
+ // tSWW/tSWR (=180us) minus tSCLK-NCS. Could be shortened, but is looks like a safe lower bound
+ wait_us(145);
+ return status;
+}
+
+uint8_t pmw3389_read(uint8_t reg_addr) {
+ pmw3389_spi_start();
+ // send adress of the register, with MSBit = 0 to indicate it's a read
+ spi_write(reg_addr & 0x7f);
+ // tSRAD (=160us)
+ wait_us(160);
+ uint8_t data = spi_read();
+
+ // tSCLK-NCS, 120ns
+ wait_us(1);
+ spi_stop();
+
+ // tSRW/tSRR (=20us) minus tSCLK-NCS
+ wait_us(19);
+ return data;
+}
+
+bool pmw3389_init(void) {
+ setPinOutput(PMW3389_CS_PIN);
+
+ spi_init();
+ _inBurst = false;
+
+ spi_stop();
+ pmw3389_spi_start();
+ spi_stop();
+
+ pmw3389_write(REG_Shutdown, 0xb6); // Shutdown first
+ wait_ms(300);
+
+ pmw3389_spi_start();
+ wait_us(40);
+ spi_stop();
+ wait_us(40);
+
+ // power up, need to first drive NCS high then low, see above.
+ pmw3389_write(REG_Power_Up_Reset, 0x5a);
+ wait_ms(50);
+
+ // read registers and discard
+ pmw3389_read(REG_Motion);
+ pmw3389_read(REG_Delta_X_L);
+ pmw3389_read(REG_Delta_X_H);
+ pmw3389_read(REG_Delta_Y_L);
+ pmw3389_read(REG_Delta_Y_H);
+
+ pmw3389_upload_firmware();
+
+ spi_stop();
+
+ wait_ms(10);
+ pmw3389_set_cpi(PMW3389_CPI);
+
+ wait_ms(1);
+
+ pmw3389_write(REG_Config2, 0x00);
+
+ pmw3389_write(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -127, 127));
+
+ pmw3389_write(REG_Lift_Config, PMW3389_LIFTOFF_DISTANCE);
+
+ bool init_success = pmw3389_check_signature();
+#ifdef CONSOLE_ENABLE
+ if (init_success) {
+ dprintf("pmw3389 signature verified");
+ } else {
+ dprintf("pmw3389 signature verification failed!");
+ }
+#endif
+
+ writePinLow(PMW3389_CS_PIN);
+
+ return init_success;
+}
+
+void pmw3389_upload_firmware(void) {
+ // Datasheet claims we need to disable REST mode first, but during startup
+ // it's already disabled and we're not turning it on ...
+ // pmw3389_write(REG_Config2, 0x00); // disable REST mode
+ pmw3389_write(REG_SROM_Enable, 0x1d);
+
+ wait_ms(10);
+
+ pmw3389_write(REG_SROM_Enable, 0x18);
+
+ pmw3389_spi_start();
+ spi_write(REG_SROM_Load_Burst | 0x80);
+ wait_us(15);
+
+ for (uint16_t i = 0; i < FIRMWARE_LENGTH; i++) {
+ spi_write(pgm_read_byte(firmware_data + i));
+#ifndef PMW3389_FIRMWARE_UPLOAD_FAST
+ wait_us(15);
+#endif
+ }
+ wait_us(200);
+
+ pmw3389_read(REG_SROM_ID);
+ pmw3389_write(REG_Config2, 0x00);
+}
+
+bool pmw3389_check_signature(void) {
+ uint8_t pid = pmw3389_read(REG_Product_ID);
+ uint8_t iv_pid = pmw3389_read(REG_Inverse_Product_ID);
+ uint8_t SROM_ver = pmw3389_read(REG_SROM_ID);
+ return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04
+}
+
+uint16_t pmw3389_get_cpi(void) {
+ uint16_t cpival = (pmw3389_read(REG_Resolution_H) << 8) | pmw3389_read(REG_Resolution_L);
+ return (uint16_t)((cpival + 1) & 0xffff) * CPI_STEP;
+}
+
+void pmw3389_set_cpi(uint16_t cpi) {
+ uint16_t cpival = constrain((cpi / CPI_STEP) - 1, 0, MAX_CPI);
+ // Sets upper byte first for more consistent setting of cpi
+ pmw3389_write(REG_Resolution_H, (cpival >> 8) & 0xff);
+ pmw3389_write(REG_Resolution_L, cpival & 0xff);
+}
+
+report_pmw3389_t pmw3389_read_burst(void) {
+ report_pmw3389_t report = {0};
+
+ if (!_inBurst) {
+#ifdef CONSOLE_ENABLE
+ dprintf("burst on");
+#endif
+ pmw3389_write(REG_Motion_Burst, 0x00);
+ _inBurst = true;
+ }
+
+ pmw3389_spi_start();
+ spi_write(REG_Motion_Burst);
+ wait_us(35); // waits for tSRAD_MOTBR
+
+ report.motion = spi_read();
+ spi_read(); // skip Observation
+ // delta registers
+ report.dx = spi_read();
+ report.mdx = spi_read();
+ report.dy = spi_read();
+ report.mdy = spi_read();
+
+ if (report.motion & 0b111) { // panic recovery, sometimes burst mode works weird.
+ _inBurst = false;
+ }
+
+ spi_stop();
+
+#ifdef CONSOLE_ENABLE
+ if (debug_mouse) {
+ print_byte(report.motion);
+ print_byte(report.dx);
+ print_byte(report.mdx);
+ print_byte(report.dy);
+ print_byte(report.mdy);
+ dprintf("\n");
+ }
+#endif
+
+ report.isMotion = (report.motion & 0x80) != 0;
+ report.isOnSurface = (report.motion & 0x08) == 0;
+ report.dx |= (report.mdx << 8);
+ report.dx = report.dx * -1;
+ report.dy |= (report.mdy << 8);
+ report.dy = report.dy * -1;
+
+ return report;
+}
diff --git a/drivers/sensors/pmw3389.h b/drivers/sensors/pmw3389.h
new file mode 100644
index 0000000000..a7c95e6076
--- /dev/null
+++ b/drivers/sensors/pmw3389.h
@@ -0,0 +1,76 @@
+/* Copyright 2021 Alabastard (@Alabastard-64)
+ * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+ * Copyright 2019 Sunjun Kim
+ * Copyright 2020 Ploopy Corporation
+ *
+ * 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 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/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#ifndef PMW3389_CPI
+# define PMW3389_CPI 2000
+#endif
+
+#ifndef PMW3389_CLOCK_SPEED
+# define PMW3389_CLOCK_SPEED 2000000
+#endif
+
+#ifndef PMW3389_SPI_LSBFIRST
+# define PMW3389_SPI_LSBFIRST false
+#endif
+
+#ifndef PMW3389_SPI_MODE
+# define PMW3389_SPI_MODE 3
+#endif
+
+#ifndef PMW3389_SPI_DIVISOR
+# ifdef __AVR__
+# define PMW3389_SPI_DIVISOR (F_CPU / PMW3389_CLOCK_SPEED)
+# else
+# define PMW3389_SPI_DIVISOR 64
+# endif
+#endif
+
+#ifndef PMW3389_LIFTOFF_DISTANCE
+# define PMW3389_LIFTOFF_DISTANCE 0x02
+#endif
+
+#ifndef ROTATIONAL_TRANSFORM_ANGLE
+# define ROTATIONAL_TRANSFORM_ANGLE 0x00
+#endif
+
+#ifndef PMW3389_CS_PIN
+# error "No chip select pin defined -- missing PMW3389_CS_PIN"
+#endif
+
+typedef struct {
+ int8_t motion;
+ bool isMotion; // True if a motion is detected.
+ bool isOnSurface; // True when a chip is on a surface
+ int16_t dx; // displacement on x directions. Unit: Count. (CPI * Count = Inch value)
+ int8_t mdx;
+ int16_t dy; // displacement on y directions.
+ int8_t mdy;
+} report_pmw3389_t;
+
+bool pmw3389_init(void);
+void pmw3389_upload_firmware(void);
+bool pmw3389_check_signature(void);
+uint16_t pmw3389_get_cpi(void);
+void pmw3389_set_cpi(uint16_t cpi);
+/* Reads and clears the current delta values on the sensor */
+report_pmw3389_t pmw3389_read_burst(void);
diff --git a/drivers/sensors/pmw3389_firmware.h b/drivers/sensors/pmw3389_firmware.h
index 0564dab73a..cd9638b605 100644
--- a/drivers/sensors/pmw3389_firmware.h
+++ b/drivers/sensors/pmw3389_firmware.h
@@ -18,286 +18,290 @@
#pragma once
+#include "progmem.h"
+
// PID, Inverse PID, SROM version
const uint8_t firmware_signature[] PROGMEM = {0x42, 0xBD, 0x04};
+#define FIRMWARE_LENGTH 4094
+
+// Firmware Blob for PMW3389
+
// clang-format off
-// Firmware Blob foor PMW3389
-const uint16_t firmware_length = 4094;
-// clang-format off
-const uint8_t firmware_data[] PROGMEM = { // SROM 0x04
-0x01, 0xe8, 0xba, 0x26, 0x0b, 0xb2, 0xbe, 0xfe, 0x7e, 0x5f, 0x3c, 0xdb, 0x15, 0xa8, 0xb3,
-0xe4, 0x2b, 0xb5, 0xe8, 0x53, 0x07, 0x6d, 0x3b, 0xd1, 0x20, 0xc2, 0x06, 0x6f, 0x3d, 0xd9,
-0x11, 0xa0, 0xc2, 0xe7, 0x2d, 0xb9, 0xd1, 0x20, 0xa3, 0xa5, 0xc8, 0xf3, 0x64, 0x4a, 0xf7,
-0x4d, 0x18, 0x93, 0xa4, 0xca, 0xf7, 0x6c, 0x5a, 0x36, 0xee, 0x5e, 0x3e, 0xfe, 0x7e, 0x7e,
-0x5f, 0x1d, 0x99, 0xb0, 0xc3, 0xe5, 0x29, 0xd3, 0x03, 0x65, 0x48, 0x12, 0x87, 0x6d, 0x58,
-0x32, 0xe6, 0x2f, 0xdc, 0x3a, 0xf2, 0x4f, 0xfd, 0x59, 0x11, 0x81, 0x61, 0x21, 0xc0, 0x02,
-0x86, 0x8e, 0x7f, 0x5d, 0x38, 0xf2, 0x47, 0x0c, 0x7b, 0x55, 0x28, 0xb3, 0xe4, 0x4a, 0x16,
-0xab, 0xbf, 0xdd, 0x38, 0xf2, 0x66, 0x4e, 0xff, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xa5, 0xc8,
-0x12, 0xa6, 0xaf, 0xdc, 0x3a, 0xd1, 0x41, 0x60, 0x75, 0x58, 0x24, 0x92, 0xd4, 0x72, 0x6c,
-0xe0, 0x2f, 0xfd, 0x23, 0x8d, 0x1c, 0x5b, 0xb2, 0x97, 0x36, 0x3d, 0x0b, 0xa2, 0x49, 0xb1,
-0x58, 0xf2, 0x1f, 0xc0, 0xcb, 0xf8, 0x41, 0x4f, 0xcd, 0x1e, 0x6b, 0x39, 0xa7, 0x2b, 0xe9,
-0x30, 0x16, 0x83, 0xd2, 0x0e, 0x47, 0x8f, 0xe3, 0xb1, 0xdf, 0xa2, 0x15, 0xdb, 0x5d, 0x30,
-0xc5, 0x1a, 0xab, 0x31, 0x99, 0xf3, 0xfa, 0xb2, 0x86, 0x69, 0xad, 0x7a, 0xe8, 0xa7, 0x18,
-0x6a, 0xcc, 0xc8, 0x65, 0x23, 0x87, 0xa8, 0x5f, 0xf5, 0x21, 0x59, 0x75, 0x09, 0x71, 0x45,
-0x55, 0x25, 0x4b, 0xda, 0xa1, 0xc3, 0xf7, 0x41, 0xab, 0x59, 0xd9, 0x74, 0x12, 0x55, 0x5f,
-0xbc, 0xaf, 0xd9, 0xfd, 0xb0, 0x1e, 0xa3, 0x0f, 0xff, 0xde, 0x11, 0x16, 0x6a, 0xae, 0x0e,
-0xe1, 0x5d, 0x3c, 0x10, 0x43, 0x9a, 0xa1, 0x0b, 0x24, 0x8f, 0x0d, 0x7f, 0x0b, 0x5e, 0x4c,
-0x42, 0xa4, 0x84, 0x2c, 0x40, 0xd0, 0x55, 0x39, 0xe6, 0x4b, 0xf8, 0x9b, 0x2f, 0xdc, 0x28,
-0xff, 0xfa, 0xb5, 0x85, 0x19, 0xe5, 0x28, 0xa1, 0x77, 0xaa, 0x73, 0xf3, 0x03, 0xc7, 0x62,
-0xa6, 0x91, 0x18, 0xc9, 0xb0, 0xcd, 0x05, 0xdc, 0xca, 0x81, 0x26, 0x1a, 0x47, 0x40, 0xda,
-0x36, 0x7d, 0x6a, 0x53, 0xc8, 0x5a, 0x77, 0x5d, 0x19, 0xa4, 0x1b, 0x23, 0x83, 0xd0, 0xb2,
-0xaa, 0x0e, 0xbf, 0x77, 0x4e, 0x3a, 0x3b, 0x59, 0x00, 0x31, 0x0d, 0x02, 0x1b, 0x88, 0x7a,
-0xd4, 0xbd, 0x9d, 0xcc, 0x58, 0x04, 0x69, 0xf6, 0x3b, 0xca, 0x42, 0xe2, 0xfd, 0xc3, 0x3d,
-0x39, 0xc5, 0xd0, 0x71, 0xe4, 0xc8, 0xb7, 0x3e, 0x3f, 0xc8, 0xe9, 0xca, 0xc9, 0x3f, 0x04,
-0x4e, 0x1b, 0x79, 0xca, 0xa5, 0x61, 0xc2, 0xed, 0x1d, 0xa6, 0xda, 0x5a, 0xe9, 0x7f, 0x65,
-0x8c, 0xbe, 0x12, 0x6e, 0xa4, 0x5b, 0x33, 0x2f, 0x84, 0x28, 0x9c, 0x1c, 0x88, 0x2d, 0xff,
-0x07, 0xbf, 0xa6, 0xd7, 0x5a, 0x88, 0x86, 0xb0, 0x3f, 0xf6, 0x31, 0x5b, 0x11, 0x6d, 0xf5,
-0x58, 0xeb, 0x58, 0x02, 0x9e, 0xb5, 0x9a, 0xb1, 0xff, 0x25, 0x9d, 0x8b, 0x4f, 0xb6, 0x0a,
-0xf9, 0xea, 0x3e, 0x3f, 0x21, 0x09, 0x65, 0x21, 0x22, 0xfe, 0x3d, 0x4e, 0x11, 0x5b, 0x9e,
-0x5a, 0x59, 0x8b, 0xdd, 0xd8, 0xce, 0xd6, 0xd9, 0x59, 0xd2, 0x1e, 0xfd, 0xef, 0x0d, 0x1b,
-0xd9, 0x61, 0x7f, 0xd7, 0x2d, 0xad, 0x62, 0x09, 0xe5, 0x22, 0x63, 0xea, 0xc7, 0x31, 0xd9,
-0xa1, 0x38, 0x80, 0x5c, 0xa7, 0x32, 0x82, 0xec, 0x1b, 0xa2, 0x49, 0x5a, 0x06, 0xd2, 0x7c,
-0xc9, 0x96, 0x57, 0xbb, 0x17, 0x75, 0xfc, 0x7a, 0x8f, 0x0d, 0x77, 0xb5, 0x7a, 0x8e, 0x3e,
-0xf4, 0xba, 0x2f, 0x69, 0x13, 0x26, 0xd6, 0xd9, 0x21, 0x60, 0x2f, 0x21, 0x3e, 0x87, 0xee,
-0xfd, 0x87, 0x16, 0x0d, 0xc8, 0x08, 0x00, 0x25, 0x71, 0xac, 0x2c, 0x03, 0x2a, 0x37, 0x2d,
-0xb3, 0x34, 0x09, 0x91, 0xe3, 0x06, 0x2c, 0x38, 0x37, 0x95, 0x3b, 0x17, 0x7a, 0xaf, 0xac,
-0x99, 0x55, 0xab, 0x41, 0x39, 0x5f, 0x8e, 0xa6, 0x43, 0x80, 0x03, 0x88, 0x6f, 0x7d, 0xbd,
-0x5a, 0xb4, 0x2b, 0x32, 0x23, 0x5a, 0xa9, 0x31, 0x32, 0x39, 0x4c, 0x5b, 0xf4, 0x6b, 0xaf,
-0x66, 0x6f, 0x3c, 0x8e, 0x2d, 0x82, 0x97, 0x9f, 0x4a, 0x01, 0xdc, 0x99, 0x98, 0x00, 0xec,
-0x38, 0x7a, 0x79, 0x70, 0xa6, 0x85, 0xd6, 0x21, 0x63, 0x0d, 0x45, 0x9a, 0x2e, 0x5e, 0xa7,
-0xb1, 0xea, 0x66, 0x6a, 0xbc, 0x62, 0x2d, 0x7b, 0x7d, 0x85, 0xea, 0x95, 0x2f, 0xc0, 0xe8,
-0x6f, 0x35, 0xa0, 0x3a, 0x02, 0x25, 0xbc, 0xb2, 0x5f, 0x5c, 0x43, 0x96, 0xcc, 0x26, 0xd2,
-0x16, 0xb4, 0x96, 0x73, 0xd7, 0x13, 0xc7, 0xae, 0x53, 0x15, 0x31, 0x89, 0x68, 0x66, 0x6d,
-0x2c, 0x92, 0x1f, 0xcc, 0x5b, 0xa7, 0x8f, 0x5d, 0xbb, 0xc9, 0xdb, 0xe8, 0x3b, 0x9d, 0x61,
-0x74, 0x8b, 0x05, 0xa1, 0x58, 0x52, 0x68, 0xee, 0x3d, 0x39, 0x79, 0xa0, 0x9b, 0xdd, 0xe1,
-0x55, 0xc9, 0x60, 0xeb, 0xad, 0xb8, 0x5b, 0xc2, 0x5a, 0xb5, 0x2c, 0x18, 0x55, 0xa9, 0x50,
-0xc3, 0xf6, 0x72, 0x5f, 0xcc, 0xe2, 0xf4, 0x55, 0xb5, 0xd6, 0xb5, 0x4a, 0x99, 0xa5, 0x28,
-0x74, 0x97, 0x18, 0xe8, 0xc0, 0x84, 0x89, 0x50, 0x03, 0x86, 0x4d, 0x1a, 0xb7, 0x09, 0x90,
-0xa2, 0x01, 0x04, 0xbb, 0x73, 0x62, 0xcb, 0x97, 0x22, 0x70, 0x5d, 0x52, 0x41, 0x8e, 0xd9,
-0x90, 0x15, 0xaa, 0xab, 0x0a, 0x31, 0x65, 0xb4, 0xda, 0xd0, 0xee, 0x24, 0xc9, 0x41, 0x91,
-0x1e, 0xbc, 0x46, 0x70, 0x40, 0x9d, 0xda, 0x0e, 0x2a, 0xe4, 0xb2, 0x4c, 0x9f, 0xf2, 0xfc,
-0xf3, 0x84, 0x17, 0x44, 0x1e, 0xd7, 0xca, 0x23, 0x1f, 0x3f, 0x5a, 0x22, 0x3d, 0xaf, 0x9b,
-0x2d, 0xfc, 0x41, 0xad, 0x26, 0xb4, 0x45, 0x67, 0x0b, 0x80, 0x0e, 0xf9, 0x61, 0x37, 0xec,
-0x3b, 0xf4, 0x4b, 0x14, 0xdf, 0x5a, 0x0c, 0x3a, 0x50, 0x0b, 0x14, 0x0c, 0x72, 0xae, 0xc6,
-0xc5, 0xec, 0x35, 0x53, 0x2d, 0x59, 0xed, 0x91, 0x74, 0xe2, 0xc4, 0xc8, 0xf2, 0x25, 0x6b,
-0x97, 0x6f, 0xc9, 0x76, 0xce, 0xa9, 0xb1, 0x99, 0x8f, 0x5a, 0x92, 0x3b, 0xc4, 0x8d, 0x54,
-0x50, 0x40, 0x72, 0xd6, 0x90, 0x83, 0xfc, 0xe5, 0x49, 0x8b, 0x17, 0xf5, 0xfd, 0x6b, 0x8d,
-0x32, 0x02, 0xe9, 0x0a, 0xfe, 0xbf, 0x00, 0x6b, 0xa3, 0xad, 0x5f, 0x09, 0x4b, 0x97, 0x2b,
-0x00, 0x58, 0x65, 0x2e, 0x07, 0x49, 0x0a, 0x3b, 0x6b, 0x2e, 0x50, 0x6c, 0x1d, 0xac, 0xb7,
-0x6a, 0x26, 0xd8, 0x13, 0xa4, 0xca, 0x16, 0xae, 0xab, 0x93, 0xb9, 0x1c, 0x1c, 0xb4, 0x47,
-0x6a, 0x38, 0x36, 0x17, 0x27, 0xc9, 0x7f, 0xc7, 0x64, 0xcb, 0x89, 0x58, 0xc5, 0x61, 0xc2,
-0xc6, 0xea, 0x15, 0x0b, 0x34, 0x0c, 0x5d, 0x61, 0x76, 0x6e, 0x2b, 0x62, 0x40, 0x92, 0xa3,
-0x6c, 0xef, 0xf4, 0xe4, 0xc3, 0xa1, 0xa8, 0xf5, 0x94, 0x79, 0x0d, 0xd1, 0x3d, 0xcb, 0x3d,
-0x40, 0xb6, 0xd0, 0xf0, 0x10, 0x54, 0xd8, 0x47, 0x25, 0x51, 0xc5, 0x41, 0x79, 0x00, 0xe5,
-0xa0, 0x72, 0xde, 0xbb, 0x3b, 0x62, 0x17, 0xf6, 0xbc, 0x5d, 0x00, 0x76, 0x2e, 0xa7, 0x3b,
-0xb6, 0xf1, 0x98, 0x72, 0x59, 0x2a, 0x73, 0xb0, 0x21, 0xd6, 0x49, 0xe0, 0xc0, 0xd5, 0xeb,
-0x02, 0x7d, 0x4b, 0x41, 0x28, 0x70, 0x2d, 0xec, 0x2b, 0x71, 0x1f, 0x0b, 0xb9, 0x71, 0x63,
-0x06, 0xe6, 0xbc, 0x60, 0xbb, 0xf4, 0x9a, 0x62, 0x43, 0x09, 0x18, 0x4e, 0x93, 0x06, 0x4d,
-0x76, 0xfa, 0x7f, 0xbd, 0x02, 0xe4, 0x50, 0x91, 0x12, 0xe5, 0x86, 0xff, 0x64, 0x1e, 0xaf,
-0x7e, 0xb3, 0xb2, 0xde, 0x89, 0xc1, 0xa2, 0x6f, 0x40, 0x7b, 0x41, 0x51, 0x63, 0xea, 0x25,
-0xd1, 0x97, 0x57, 0x92, 0xa8, 0x45, 0xa1, 0xa5, 0x45, 0x21, 0x43, 0x7f, 0x83, 0x15, 0x29,
-0xd0, 0x30, 0x53, 0x32, 0xb4, 0x5a, 0x17, 0x96, 0xbc, 0xc2, 0x68, 0xa9, 0xb7, 0xaf, 0xac,
-0xdf, 0xf1, 0xe3, 0x89, 0xba, 0x24, 0x79, 0x54, 0xc6, 0x14, 0x07, 0x1c, 0x1e, 0x0d, 0x3a,
-0x6b, 0xe5, 0x3d, 0x4e, 0x10, 0x60, 0x96, 0xec, 0x6c, 0xda, 0x47, 0xae, 0x03, 0x25, 0x39,
-0x1d, 0x74, 0xc8, 0xac, 0x6a, 0xf2, 0x6b, 0x05, 0x2a, 0x9a, 0xe7, 0xe8, 0x92, 0xd6, 0xc2,
-0x6d, 0xfa, 0xe8, 0xa7, 0x9d, 0x5f, 0x48, 0xc9, 0x75, 0xf1, 0x66, 0x6a, 0xdb, 0x5d, 0x9a,
-0xcd, 0x27, 0xdd, 0xb9, 0x24, 0x04, 0x9c, 0x18, 0xc2, 0x6d, 0x0c, 0x91, 0x34, 0x48, 0x42,
-0x6f, 0xe9, 0x59, 0x70, 0xc4, 0x7e, 0x81, 0x0e, 0x32, 0x0a, 0x93, 0x48, 0xb0, 0xc0, 0x15,
-0x9e, 0x05, 0xac, 0x36, 0x16, 0xcb, 0x59, 0x65, 0xa0, 0x83, 0xdf, 0x3e, 0xda, 0xfb, 0x1d,
-0x1a, 0xdb, 0x65, 0xec, 0x9a, 0xc6, 0xc3, 0x8e, 0x3c, 0x45, 0xfd, 0xc8, 0xf5, 0x1c, 0x6a,
-0x67, 0x0d, 0x8f, 0x99, 0x7d, 0x30, 0x21, 0x8c, 0xea, 0x22, 0x87, 0x65, 0xc9, 0xb2, 0x4c,
-0xe4, 0x1b, 0x46, 0xba, 0x54, 0xbd, 0x7c, 0xca, 0xd5, 0x8f, 0x5b, 0xa5, 0x01, 0x04, 0xd8,
-0x0a, 0x16, 0xbf, 0xb9, 0x50, 0x2e, 0x37, 0x2f, 0x64, 0xf3, 0x70, 0x11, 0x02, 0x05, 0x31,
-0x9b, 0xa0, 0xb2, 0x01, 0x5e, 0x4f, 0x19, 0xc9, 0xd4, 0xea, 0xa1, 0x79, 0x54, 0x53, 0xa7,
-0xde, 0x2f, 0x49, 0xd3, 0xd1, 0x63, 0xb5, 0x03, 0x15, 0x4e, 0xbf, 0x04, 0xb3, 0x26, 0x8b,
-0x20, 0xb2, 0x45, 0xcf, 0xcd, 0x5b, 0x82, 0x32, 0x88, 0x61, 0xa7, 0xa8, 0xb2, 0xa0, 0x72,
-0x96, 0xc0, 0xdb, 0x2b, 0xe2, 0x5f, 0xba, 0xe3, 0xf5, 0x8a, 0xde, 0xf1, 0x18, 0x01, 0x16,
-0x40, 0xd9, 0x86, 0x12, 0x09, 0x18, 0x1b, 0x05, 0x0c, 0xb1, 0xb5, 0x47, 0xe2, 0x43, 0xab,
-0xfe, 0x92, 0x63, 0x7e, 0x95, 0x2b, 0xf0, 0xaf, 0xe1, 0xf1, 0xc3, 0x4a, 0xff, 0x2b, 0x09,
-0xbb, 0x4a, 0x0e, 0x9a, 0xc4, 0xd8, 0x64, 0x7d, 0x83, 0xa0, 0x4f, 0x44, 0xdb, 0xc4, 0xa8,
-0x58, 0xef, 0xfc, 0x9e, 0x77, 0xf9, 0xa6, 0x8f, 0x58, 0x8b, 0x12, 0xf4, 0xe9, 0x81, 0x12,
-0x47, 0x51, 0x41, 0x83, 0xef, 0xf6, 0x73, 0xbc, 0x8e, 0x0f, 0x4c, 0x8f, 0x4e, 0x69, 0x90,
-0x77, 0x29, 0x5d, 0x92, 0xb0, 0x6d, 0x06, 0x67, 0x29, 0x60, 0xbd, 0x4b, 0x17, 0xc8, 0x89,
-0x69, 0x28, 0x29, 0xd6, 0x78, 0xcb, 0x11, 0x4c, 0xba, 0x8b, 0x68, 0xae, 0x7e, 0x9f, 0xef,
-0x95, 0xda, 0xe2, 0x9e, 0x7f, 0xe9, 0x55, 0xe5, 0xe1, 0xe2, 0xb7, 0xe6, 0x5f, 0xbb, 0x2c,
-0xa2, 0xe6, 0xee, 0xc7, 0x0a, 0x60, 0xa9, 0xd1, 0x80, 0xdf, 0x7f, 0xd6, 0x97, 0xab, 0x1d,
-0x22, 0x25, 0xfc, 0x79, 0x23, 0xe0, 0xae, 0xc5, 0xef, 0x16, 0xa4, 0xa1, 0x0f, 0x92, 0xa9,
-0xc7, 0xe3, 0x3a, 0x55, 0xdf, 0x62, 0x49, 0xd9, 0xf5, 0x84, 0x49, 0xc5, 0x90, 0x34, 0xd3,
-0xe1, 0xac, 0x99, 0x21, 0xb1, 0x02, 0x76, 0x4a, 0xfa, 0xd4, 0xbb, 0xa4, 0x9c, 0xa2, 0xe2,
-0xcb, 0x3d, 0x3b, 0x14, 0x75, 0x60, 0xd1, 0x02, 0xb4, 0xa3, 0xb4, 0x72, 0x06, 0xf9, 0x19,
-0x9c, 0xe2, 0xe4, 0xa7, 0x0f, 0x25, 0x88, 0xc6, 0x86, 0xd6, 0x8c, 0x74, 0x4e, 0x6e, 0xfc,
-0xa8, 0x48, 0x9e, 0xa7, 0x9d, 0x1a, 0x4b, 0x37, 0x09, 0xc8, 0xb0, 0x10, 0xbe, 0x6f, 0xfe,
-0xa3, 0xc4, 0x7a, 0xb5, 0x3d, 0xe8, 0x30, 0xf1, 0x0d, 0xa0, 0xb2, 0x44, 0xfc, 0x9b, 0x8c,
-0xf8, 0x61, 0xed, 0x81, 0xd1, 0x62, 0x11, 0xb4, 0xe1, 0xd5, 0x39, 0x52, 0x89, 0xd3, 0xa8,
-0x49, 0x31, 0xdf, 0xb6, 0xf9, 0x91, 0xf4, 0x1c, 0x9d, 0x09, 0x95, 0x40, 0x56, 0xe7, 0xe3,
-0xcd, 0x5c, 0x92, 0xc1, 0x1d, 0x6b, 0xe9, 0x78, 0x6f, 0x8e, 0x94, 0x42, 0x66, 0xa2, 0xaa,
-0xd3, 0xc8, 0x2e, 0xe3, 0xf6, 0x07, 0x72, 0x0b, 0x6b, 0x1e, 0x7b, 0xb9, 0x7c, 0xe0, 0xa0,
-0xbc, 0xd9, 0x25, 0xdf, 0x87, 0xa8, 0x5f, 0x9c, 0xcc, 0xf0, 0xdb, 0x42, 0x8e, 0x07, 0x31,
-0x13, 0x01, 0x66, 0x32, 0xd1, 0xb8, 0xd6, 0xe3, 0x5e, 0x12, 0x76, 0x61, 0xd3, 0x38, 0x89,
-0xe6, 0x17, 0x6f, 0xa5, 0xf2, 0x71, 0x0e, 0xa5, 0xe2, 0x88, 0x30, 0xbb, 0xbe, 0x8a, 0xea,
-0xc7, 0x62, 0xc4, 0xcf, 0xb8, 0xcd, 0x33, 0x8d, 0x3d, 0x3e, 0xb5, 0x60, 0x3a, 0x03, 0x92,
-0xe4, 0x6d, 0x1b, 0xe0, 0xb4, 0x84, 0x08, 0x55, 0x88, 0xa7, 0x3a, 0xb9, 0x3d, 0x43, 0xc3,
-0xc0, 0xfa, 0x07, 0x6a, 0xca, 0x94, 0xad, 0x99, 0x55, 0xf1, 0xf1, 0xc0, 0x23, 0x87, 0x1d,
-0x3d, 0x1c, 0xd1, 0x66, 0xa0, 0x57, 0x10, 0x52, 0xa2, 0x7f, 0xbe, 0xf9, 0x88, 0xb6, 0x02,
-0xbf, 0x08, 0x23, 0xa9, 0x0c, 0x63, 0x17, 0x2a, 0xae, 0xf5, 0xf7, 0xb7, 0x21, 0x83, 0x92,
-0x31, 0x23, 0x0d, 0x20, 0xc3, 0xc2, 0x05, 0x21, 0x62, 0x8e, 0x45, 0xe8, 0x14, 0xc1, 0xda,
-0x75, 0xb8, 0xf8, 0x92, 0x01, 0xd0, 0x5d, 0x18, 0x9f, 0x99, 0x11, 0x19, 0xf5, 0x35, 0xe8,
-0x7f, 0x20, 0x88, 0x8c, 0x05, 0x75, 0xf5, 0xd7, 0x40, 0x17, 0xbb, 0x1e, 0x36, 0x52, 0xd9,
-0xa4, 0x9c, 0xc2, 0x9d, 0x42, 0x81, 0xd8, 0xc7, 0x8a, 0xe7, 0x4c, 0x81, 0xe0, 0xb7, 0x57,
-0xed, 0x48, 0x8b, 0xf0, 0x97, 0x15, 0x61, 0xd9, 0x2c, 0x7c, 0x45, 0xaf, 0xc2, 0xcd, 0xfc,
-0xaa, 0x13, 0xad, 0x59, 0xcc, 0xb2, 0xb2, 0x6e, 0xdd, 0x63, 0x9c, 0x32, 0x0f, 0xec, 0x83,
-0xbe, 0x78, 0xac, 0x91, 0x44, 0x1a, 0x1f, 0xea, 0xfd, 0x5d, 0x8e, 0xb4, 0xc0, 0x84, 0xd4,
-0xac, 0xb4, 0x87, 0x5f, 0xac, 0xef, 0xdf, 0xcd, 0x12, 0x56, 0xc8, 0xcd, 0xfe, 0xc5, 0xda,
-0xd3, 0xc1, 0x69, 0xf3, 0x61, 0x05, 0xea, 0x25, 0xe2, 0x12, 0x05, 0x8f, 0x39, 0x08, 0x08,
-0x7c, 0x37, 0xb6, 0x7e, 0x5b, 0xd8, 0xb1, 0x0e, 0xf2, 0xdb, 0x4b, 0xf1, 0xad, 0x90, 0x01,
-0x57, 0xcd, 0xa0, 0xb4, 0x52, 0xe8, 0xf3, 0xd7, 0x8a, 0xbd, 0x4f, 0x9f, 0x21, 0x40, 0x72,
-0xa4, 0xfc, 0x0b, 0x01, 0x2b, 0x2f, 0xb6, 0x4c, 0x95, 0x2d, 0x35, 0x33, 0x41, 0x6b, 0xa0,
-0x93, 0xe7, 0x2c, 0xf2, 0xd3, 0x72, 0x8b, 0xf4, 0x4f, 0x15, 0x3c, 0xaf, 0xd6, 0x12, 0xde,
-0x3f, 0x83, 0x3f, 0xff, 0xf8, 0x7f, 0xf6, 0xcc, 0xa6, 0x7f, 0xc9, 0x9a, 0x6e, 0x1f, 0xc1,
-0x0c, 0xfb, 0xee, 0x9c, 0xe7, 0xaf, 0xc9, 0x26, 0x54, 0xef, 0xb0, 0x39, 0xef, 0xb2, 0xe9,
-0x23, 0xc4, 0xef, 0xd1, 0xa1, 0xa4, 0x25, 0x24, 0x6f, 0x8d, 0x6a, 0xe5, 0x8a, 0x32, 0x3a,
-0xaf, 0xfc, 0xda, 0xce, 0x18, 0x25, 0x42, 0x07, 0x4d, 0x45, 0x8b, 0xdf, 0x85, 0xcf, 0x55,
-0xb2, 0x24, 0xfe, 0x9c, 0x69, 0x74, 0xa7, 0x6e, 0xa0, 0xce, 0xc0, 0x39, 0xf4, 0x86, 0xc6,
-0x8d, 0xae, 0xb9, 0x48, 0x64, 0x13, 0x0b, 0x40, 0x81, 0xa2, 0xc9, 0xa8, 0x85, 0x51, 0xee,
-0x9f, 0xcf, 0xa2, 0x8c, 0x19, 0x52, 0x48, 0xe2, 0xc1, 0xa8, 0x58, 0xb4, 0x10, 0x24, 0x06,
-0x58, 0x51, 0xfc, 0xb9, 0x12, 0xec, 0xfd, 0x73, 0xb4, 0x6d, 0x84, 0xfa, 0x06, 0x8b, 0x05,
-0x0b, 0x2d, 0xd6, 0xd6, 0x1f, 0x29, 0x82, 0x9f, 0x19, 0x12, 0x1e, 0xb2, 0x04, 0x8f, 0x7f,
-0x4d, 0xbd, 0x30, 0x2e, 0xe3, 0xe0, 0x88, 0x29, 0xc5, 0x93, 0xd6, 0x6c, 0x1f, 0x29, 0x45,
-0x91, 0xa7, 0x58, 0xcd, 0x05, 0x17, 0xd6, 0x6d, 0xb3, 0xca, 0x66, 0xcc, 0x3c, 0x4a, 0x74,
-0xfd, 0x08, 0x10, 0xa6, 0x99, 0x92, 0x10, 0xd2, 0x85, 0xab, 0x6e, 0x1d, 0x0e, 0x8b, 0x26,
-0x46, 0xd1, 0x6c, 0x84, 0xc0, 0x26, 0x43, 0x59, 0x68, 0xf0, 0x13, 0x1d, 0xfb, 0xe3, 0xd1,
-0xd2, 0xb4, 0x71, 0x9e, 0xf2, 0x59, 0x6a, 0x33, 0x29, 0x79, 0xd2, 0xd7, 0x26, 0xf1, 0xae,
-0x78, 0x9e, 0x1f, 0x0f, 0x3f, 0xe3, 0xe8, 0xd0, 0x27, 0x78, 0x77, 0xf6, 0xac, 0x9c, 0x56,
-0x39, 0x73, 0x8a, 0x6b, 0x2f, 0x34, 0x78, 0xb1, 0x11, 0xdb, 0xa4, 0x5c, 0x80, 0x01, 0x71,
-0x6a, 0xc2, 0xd1, 0x2e, 0x5e, 0x76, 0x28, 0x70, 0x93, 0xae, 0x3e, 0x78, 0xb0, 0x1f, 0x0f,
-0xda, 0xbf, 0xfb, 0x8a, 0x67, 0x65, 0x4f, 0x91, 0xed, 0x49, 0x75, 0x78, 0x62, 0xa2, 0x93,
-0xb5, 0x70, 0x7f, 0x4d, 0x08, 0x4e, 0x79, 0x61, 0xa8, 0x5f, 0x7f, 0xb4, 0x65, 0x9f, 0x91,
-0x54, 0x3a, 0xe8, 0x50, 0x33, 0xd3, 0xd5, 0x8a, 0x7c, 0xf3, 0x9e, 0x8b, 0x77, 0x7b, 0xc6,
-0xc6, 0x0c, 0x45, 0x95, 0x1f, 0xb0, 0xd0, 0x0b, 0x27, 0x4a, 0xfd, 0xc7, 0xf7, 0x0d, 0x5a,
-0x43, 0xc9, 0x7d, 0x35, 0xb0, 0x7d, 0xc4, 0x9c, 0x57, 0x1e, 0x76, 0x0d, 0xf1, 0x95, 0x30,
-0x71, 0xcc, 0xb3, 0x66, 0x3b, 0x63, 0xa8, 0x6c, 0xa3, 0x43, 0xa0, 0x24, 0xcc, 0xb7, 0x53,
-0xfe, 0xfe, 0xbc, 0x6e, 0x60, 0x89, 0xaf, 0x16, 0x21, 0xc8, 0x91, 0x6a, 0x89, 0xce, 0x80,
-0x2c, 0xf1, 0x59, 0xce, 0xc3, 0x60, 0x61, 0x3b, 0x0b, 0x19, 0xfe, 0x99, 0xac, 0x65, 0x90,
-0x15, 0x12, 0x05, 0xac, 0x7e, 0xff, 0x98, 0x7b, 0x66, 0x64, 0x0e, 0x4b, 0x5b, 0xaa, 0x8d,
-0x3b, 0xd2, 0x56, 0xcf, 0x99, 0x39, 0xee, 0x22, 0x81, 0xd0, 0x60, 0x06, 0x66, 0x20, 0x81,
-0x48, 0x3c, 0x6f, 0x3a, 0x77, 0xba, 0xcb, 0x52, 0xac, 0x79, 0x56, 0xaf, 0xe9, 0x16, 0x17,
-0x0a, 0xa3, 0x82, 0x08, 0xd5, 0x3c, 0x97, 0xcb, 0x09, 0xff, 0x7f, 0xf9, 0x4f, 0x60, 0x05,
-0xb9, 0x53, 0x26, 0xaa, 0xb8, 0x50, 0xaa, 0x19, 0x25, 0xae, 0x5f, 0xea, 0x8a, 0xd0, 0x89,
-0x12, 0x80, 0x43, 0x50, 0x24, 0x12, 0x21, 0x14, 0xcd, 0x77, 0xeb, 0x21, 0xcc, 0x5c, 0x09,
-0x64, 0xf3, 0xc7, 0xcb, 0xc5, 0x4b, 0xc3, 0xe7, 0xed, 0xe7, 0x86, 0x2c, 0x1d, 0x8e, 0x19,
-0x52, 0x9b, 0x2a, 0x0c, 0x18, 0x72, 0x0b, 0x1e, 0x1b, 0xb0, 0x0f, 0x42, 0x99, 0x04, 0xae,
-0xd5, 0xb7, 0x89, 0x1a, 0xb9, 0x4f, 0xd6, 0xaf, 0xf3, 0xc9, 0x93, 0x6f, 0xb0, 0x60, 0x83,
-0x6e, 0x6b, 0xd1, 0x5f, 0x3f, 0x1a, 0x83, 0x1e, 0x24, 0x00, 0x87, 0xb5, 0x3e, 0xdb, 0xf9,
-0x4d, 0xa7, 0x16, 0x2e, 0x19, 0x5b, 0x8f, 0x1b, 0x0d, 0x47, 0x72, 0x42, 0xe9, 0x0a, 0x11,
-0x08, 0x2d, 0x88, 0x1c, 0xbc, 0xc7, 0xb4, 0xbe, 0x29, 0x4d, 0x03, 0x5e, 0xec, 0xdf, 0xf3,
-0x3d, 0x2f, 0xe8, 0x1d, 0x9a, 0xd2, 0xd1, 0xab, 0x41, 0x3d, 0x87, 0x11, 0x45, 0xb0, 0x0d,
-0x46, 0xf5, 0xe8, 0x95, 0x62, 0x1c, 0x68, 0xf7, 0xa6, 0x5b, 0x39, 0x4e, 0xbf, 0x47, 0xba,
-0x5d, 0x7f, 0xb7, 0x6a, 0xf4, 0xba, 0x1d, 0x69, 0xf6, 0xa4, 0xe7, 0xe4, 0x6b, 0x3b, 0x0d,
-0x23, 0x16, 0x4a, 0xb2, 0x68, 0xf0, 0xb2, 0x0d, 0x09, 0x17, 0x6a, 0x63, 0x8c, 0x83, 0xd3,
-0xbd, 0x05, 0xc9, 0xf6, 0xf0, 0xa1, 0x31, 0x0b, 0x2c, 0xac, 0x83, 0xac, 0x80, 0x34, 0x32,
-0xb4, 0xec, 0xd0, 0xbc, 0x54, 0x82, 0x9a, 0xc8, 0xf6, 0xa0, 0x7d, 0xc6, 0x79, 0x73, 0xf4,
-0x20, 0x99, 0xf3, 0xb4, 0x01, 0xde, 0x91, 0x27, 0xf2, 0xc0, 0xdc, 0x81, 0x00, 0x4e, 0x7e,
-0x07, 0x99, 0xc8, 0x3a, 0x51, 0xbc, 0x38, 0xd6, 0x8a, 0xa2, 0xde, 0x3b, 0x6a, 0x8c, 0x1a,
-0x7c, 0x81, 0x0f, 0x3a, 0x1f, 0xe4, 0x05, 0x7b, 0x20, 0x35, 0x6b, 0xa5, 0x6a, 0xa7, 0xe7,
-0xbc, 0x9c, 0x20, 0xec, 0x00, 0x15, 0xe2, 0x51, 0xaf, 0x77, 0xeb, 0x29, 0x3c, 0x7d, 0x2e,
-0x00, 0x5c, 0x81, 0x21, 0xfa, 0x35, 0x6f, 0x40, 0xef, 0xfb, 0xd1, 0x3f, 0xcc, 0x9d, 0x55,
-0x53, 0xfb, 0x5a, 0xa5, 0x56, 0x89, 0x0b, 0x52, 0xeb, 0x57, 0x73, 0x4f, 0x1b, 0x67, 0x24,
-0xcb, 0xb8, 0x6a, 0x10, 0x69, 0xd6, 0xfb, 0x52, 0x40, 0xff, 0x20, 0xa5, 0xf3, 0x72, 0xe1,
-0x3d, 0xa4, 0x8c, 0x81, 0x66, 0x16, 0x0d, 0x5d, 0xad, 0xa8, 0x50, 0x25, 0x78, 0x31, 0x77,
-0x0c, 0x57, 0xe4, 0xe9, 0x15, 0x2d, 0xdb, 0x07, 0x87, 0xc8, 0xb0, 0x43, 0xde, 0xfc, 0xfe,
-0xa9, 0xeb, 0xf5, 0xb0, 0xd3, 0x7b, 0xe9, 0x1f, 0x6e, 0xca, 0xe4, 0x03, 0x95, 0xc5, 0xd1,
-0x59, 0x72, 0x63, 0xf0, 0x86, 0x54, 0xe8, 0x16, 0x62, 0x0b, 0x35, 0x29, 0xc2, 0x68, 0xd0,
-0xd6, 0x3e, 0x90, 0x60, 0x57, 0x1d, 0xc9, 0xed, 0x3f, 0xed, 0xb0, 0x2f, 0x7e, 0x97, 0x02,
-0x51, 0xec, 0xee, 0x6f, 0x82, 0x74, 0x76, 0x7f, 0xfb, 0xd6, 0xc4, 0xc3, 0xdd, 0xe8, 0xb1,
-0x60, 0xfc, 0xc6, 0xb9, 0x0d, 0x6a, 0x33, 0x78, 0xc6, 0xc1, 0xbf, 0x86, 0x2c, 0x50, 0xcc,
-0x9a, 0x70, 0x8e, 0x7b, 0xec, 0xab, 0x95, 0xac, 0x53, 0xa0, 0x4b, 0x07, 0x88, 0xaf, 0x42,
-0xed, 0x19, 0x8d, 0xf6, 0x32, 0x17, 0x48, 0x47, 0x1d, 0x41, 0x6f, 0xfe, 0x2e, 0xa7, 0x8f,
-0x4b, 0xa0, 0x51, 0xf3, 0xbf, 0x02, 0x0a, 0x48, 0x58, 0xf7, 0xa1, 0x6d, 0xea, 0xa5, 0x13,
-0x5a, 0x5b, 0xea, 0x0c, 0x9e, 0x52, 0x4f, 0x9e, 0xb9, 0x71, 0x7f, 0x23, 0x83, 0xda, 0x1b,
-0x86, 0x9a, 0x41, 0x29, 0xda, 0x70, 0xe7, 0x64, 0xa1, 0x7b, 0xd5, 0x0a, 0x22, 0x0d, 0x5c,
-0x40, 0xc4, 0x81, 0x07, 0x25, 0x35, 0x4a, 0x1c, 0x10, 0xdb, 0x45, 0x0a, 0xff, 0x36, 0xd4,
-0xe0, 0xeb, 0x5f, 0x68, 0xd6, 0x67, 0xc6, 0xd0, 0x8b, 0x76, 0x1a, 0x7d, 0x59, 0x42, 0xa1,
-0xcb, 0x96, 0x4d, 0x84, 0x09, 0x9a, 0x3d, 0xe0, 0x52, 0x85, 0x6e, 0x48, 0x90, 0x85, 0x2a,
-0x63, 0xb2, 0x69, 0xd2, 0x00, 0x43, 0x31, 0x37, 0xb3, 0x52, 0xaf, 0x62, 0xfa, 0xc1, 0xe0,
-0x03, 0xfb, 0x62, 0xaa, 0x88, 0xc9, 0xb2, 0x2c, 0xd5, 0xa8, 0xf5, 0xa5, 0x4c, 0x12, 0x59,
-0x4e, 0x06, 0x5e, 0x9b, 0x15, 0x66, 0x11, 0xb2, 0x27, 0x92, 0xdc, 0x98, 0x59, 0xde, 0xdf,
-0xfa, 0x9a, 0x32, 0x2e, 0xc0, 0x5d, 0x3c, 0x33, 0x41, 0x6d, 0xaf, 0xb2, 0x25, 0x23, 0x14,
-0xa5, 0x7b, 0xc7, 0x9b, 0x68, 0xf3, 0xda, 0xeb, 0xe3, 0xa9, 0xe2, 0x6f, 0x0e, 0x1d, 0x1c,
-0xba, 0x55, 0xb6, 0x34, 0x6a, 0x93, 0x1f, 0x1f, 0xb8, 0x34, 0xc8, 0x84, 0x08, 0xb1, 0x6b,
-0x6a, 0x28, 0x74, 0x74, 0xe5, 0xeb, 0x75, 0xe9, 0x7c, 0xd8, 0xba, 0xd8, 0x42, 0xa5, 0xee,
-0x1f, 0x80, 0xd9, 0x96, 0xb2, 0x2e, 0xe7, 0xbf, 0xba, 0xeb, 0xd1, 0x69, 0xbb, 0x8f, 0xfd,
-0x5a, 0x63, 0x8f, 0x39, 0x7f, 0xdf, 0x1d, 0x37, 0xd2, 0x18, 0x35, 0x9d, 0xb6, 0xcc, 0xe4,
-0x27, 0x81, 0x89, 0x38, 0x38, 0x68, 0x33, 0xe7, 0x78, 0xd8, 0x76, 0xf5, 0xee, 0xd0, 0x4a,
-0x07, 0x69, 0x19, 0x7a, 0xad, 0x18, 0xb1, 0x94, 0x61, 0x45, 0x53, 0xa2, 0x48, 0xda, 0x96,
-0x4a, 0xf9, 0xee, 0x94, 0x2a, 0x1f, 0x6e, 0x18, 0x3c, 0x92, 0x46, 0xd1, 0x1a, 0x28, 0x18,
-0x32, 0x1f, 0x3a, 0x45, 0xbe, 0x04, 0x35, 0x92, 0xe5, 0xa3, 0xcb, 0xb5, 0x2e, 0x32, 0x43,
-0xac, 0x65, 0x17, 0x89, 0x99, 0x15, 0x03, 0x9e, 0xb1, 0x23, 0x2f, 0xed, 0x76, 0x4d, 0xd8,
-0xac, 0x21, 0x40, 0xc4, 0x99, 0x4e, 0x65, 0x71, 0x2c, 0xb3, 0x45, 0xab, 0xfb, 0xe7, 0x72,
-0x39, 0x56, 0x30, 0x6d, 0xfb, 0x74, 0xeb, 0x99, 0xf3, 0xcd, 0x57, 0x5c, 0x78, 0x75, 0xe9,
-0x8d, 0xc3, 0xa2, 0xfb, 0x5d, 0xe0, 0x90, 0xc5, 0x55, 0xad, 0x91, 0x53, 0x4e, 0x9e, 0xbd,
-0x8c, 0x49, 0xa4, 0xa4, 0x69, 0x10, 0x0c, 0xc5, 0x76, 0xe9, 0x25, 0x86, 0x8d, 0x66, 0x23,
-0xa8, 0xdb, 0x5c, 0xe8, 0xd9, 0x30, 0xe1, 0x15, 0x7b, 0xc0, 0x99, 0x0f, 0x03, 0xec, 0xaa,
-0x12, 0xef, 0xce, 0xd4, 0xea, 0x55, 0x5c, 0x08, 0x86, 0xf4, 0xf4, 0xb0, 0x83, 0x42, 0x95,
-0x37, 0xb6, 0x38, 0xe0, 0x2b, 0x54, 0x89, 0xbd, 0x4e, 0x20, 0x9d, 0x3f, 0xc3, 0x4b, 0xb7,
-0xec, 0xfa, 0x5a, 0x14, 0x03, 0xcb, 0x64, 0xc8, 0x34, 0x4a, 0x4b, 0x6e, 0xf8, 0x6e, 0x56,
-0xf6, 0xdd, 0x5f, 0xa1, 0x24, 0xe2, 0xd4, 0xd0, 0x82, 0x64, 0x1f, 0x8e, 0x9b, 0xfa, 0xb4,
-0xcb, 0xdb, 0x0a, 0xe8, 0x15, 0xfc, 0x15, 0xab, 0x4b, 0x18, 0xbf, 0xd4, 0x42, 0x14, 0x48,
-0x82, 0x85, 0xdd, 0xeb, 0x49, 0x1b, 0x0b, 0x0b, 0x05, 0xe9, 0xb4, 0xa1, 0x33, 0x0a, 0x5d,
-0x0e, 0x6c, 0x4b, 0xc0, 0xd6, 0x6c, 0x7c, 0xfb, 0x69, 0x0b, 0x53, 0x19, 0xe4, 0xf3, 0x35,
-0xfc, 0xbe, 0xa1, 0x34, 0x02, 0x09, 0x4f, 0x74, 0x86, 0x92, 0xcd, 0x5d, 0x1a, 0xc1, 0x27,
-0x0c, 0xf2, 0xc5, 0xcf, 0xdd, 0x23, 0x93, 0x02, 0xbd, 0x41, 0x5e, 0x42, 0xf0, 0xa0, 0x9d,
-0x0c, 0x72, 0xc8, 0xec, 0x32, 0x0a, 0x8a, 0xfd, 0x3d, 0x5a, 0x41, 0x27, 0x0c, 0x88, 0x59,
-0xad, 0x94, 0x2e, 0xef, 0x5d, 0x8f, 0xc7, 0xdf, 0x66, 0xe4, 0xdd, 0x56, 0x6c, 0x7b, 0xca,
-0x55, 0x81, 0xae, 0xae, 0x5c, 0x1b, 0x1a, 0xab, 0xae, 0x99, 0x8d, 0xcc, 0x42, 0x97, 0x59,
-0xf4, 0x14, 0x3f, 0x75, 0xc6, 0xd1, 0x88, 0xba, 0xaa, 0x84, 0x4a, 0xd0, 0x34, 0x08, 0x3b,
-0x7d, 0xdb, 0x15, 0x06, 0xb0, 0x5c, 0xbd, 0x40, 0xf5, 0xa8, 0xec, 0xae, 0x36, 0x40, 0xdd,
-0x90, 0x1c, 0x3e, 0x0d, 0x7e, 0x73, 0xc7, 0xc2, 0xc5, 0x6a, 0xff, 0x52, 0x05, 0x7f, 0xbe,
-0xd0, 0x92, 0xfd, 0xb3, 0x6f, 0xff, 0x5d, 0xb7, 0x97, 0x64, 0x73, 0x7b, 0xca, 0xd1, 0x98,
-0x24, 0x6b, 0x0b, 0x01, 0x68, 0xdd, 0x27, 0x85, 0x85, 0xb5, 0x83, 0xc1, 0xe0, 0x50, 0x64,
-0xc7, 0xaf, 0xf1, 0xc6, 0x4d, 0xb1, 0xef, 0xc9, 0xb4, 0x0a, 0x6d, 0x65, 0xf3, 0x47, 0xcc,
-0xa3, 0x02, 0x21, 0x0c, 0xbe, 0x22, 0x29, 0x05, 0xcf, 0x5f, 0xe8, 0x94, 0x6c, 0xe5, 0xdc,
-0xc4, 0xdf, 0xbe, 0x3e, 0xa8, 0xb4, 0x18, 0xb0, 0x99, 0xb8, 0x6f, 0xff, 0x5d, 0xb9, 0xfd,
-0x3b, 0x5d, 0x16, 0xbf, 0x3e, 0xd8, 0xb3, 0xd8, 0x08, 0x34, 0xf6, 0x47, 0x35, 0x5b, 0x72,
-0x1a, 0x33, 0xad, 0x52, 0x5d, 0xb8, 0xd0, 0x77, 0xc6, 0xab, 0xba, 0x55, 0x09, 0x5f, 0x02,
-0xf8, 0xd4, 0x5f, 0x53, 0x06, 0x91, 0xcd, 0x74, 0x42, 0xae, 0x54, 0x91, 0x81, 0x62, 0x13,
-0x6f, 0xd8, 0xa9, 0x77, 0xc3, 0x6c, 0xcb, 0xf1, 0x29, 0x5a, 0xcc, 0xda, 0x35, 0xbd, 0x52,
-0x23, 0xbe, 0x59, 0xeb, 0x12, 0x6d, 0xb7, 0x53, 0xee, 0xfc, 0xb4, 0x1b, 0x13, 0x5e, 0xba,
-0x16, 0x7c, 0xc5, 0xf3, 0xe3, 0x6d, 0x07, 0x78, 0xf5, 0x2b, 0x21, 0x05, 0x88, 0x4c, 0xc0,
-0xa1, 0xe3, 0x36, 0x10, 0xf8, 0x1b, 0xd8, 0x17, 0xfb, 0x6a, 0x4e, 0xd8, 0xb3, 0x47, 0x2d,
-0x99, 0xbd, 0xbb, 0x5d, 0x37, 0x7d, 0xba, 0xf1, 0xe1, 0x7c, 0xc0, 0xc5, 0x54, 0x62, 0x7f,
-0xcf, 0x5a, 0x4a, 0x93, 0xcc, 0xf1, 0x1b, 0x34, 0xc8, 0xa6, 0x05, 0x4c, 0x55, 0x8b, 0x54,
-0x84, 0xd5, 0x77, 0xeb, 0xc0, 0x6d, 0x3a, 0x29, 0xbd, 0x75, 0x61, 0x09, 0x9a, 0x2c, 0xbb,
-0xf7, 0x18, 0x79, 0x34, 0x90, 0x24, 0xa5, 0x81, 0x70, 0x87, 0xc5, 0x02, 0x7c, 0xba, 0xd4,
-0x5e, 0x14, 0x8e, 0xe4, 0xed, 0xa2, 0x61, 0x6a, 0xb9, 0x6e, 0xb5, 0x4a, 0xb9, 0x01, 0x46,
-0xf4, 0xcf, 0xbc, 0x09, 0x2f, 0x27, 0x4b, 0xbd, 0x86, 0x7a, 0x10, 0xe1, 0xd4, 0xc8, 0xd9,
-0x20, 0x8d, 0x8a, 0x63, 0x00, 0x63, 0x44, 0xeb, 0x54, 0x0b, 0x75, 0x49, 0x10, 0xa2, 0xa7,
-0xad, 0xb9, 0xd1, 0x01, 0x80, 0x63, 0x25, 0xc8, 0x12, 0xa6, 0xce, 0x1e, 0xbe, 0xfe, 0x7e,
-0x5f, 0x3c, 0xdb, 0x34, 0xea, 0x37, 0xec, 0x3b, 0xd5, 0x28, 0xd2, 0x07, 0x8c, 0x9a, 0xb6,
-0xee, 0x5e, 0x3e, 0xdf, 0x1d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x5c, 0x1b, 0xb4, 0xea, 0x56,
-0x2e, 0xde, 0x1f, 0x9d, 0xb8, 0xd3, 0x24, 0xab, 0xd4, 0x2a, 0xd6, 0x2e, 0xde, 0x1f, 0x9d,
-0xb8, 0xf2, 0x66, 0x2f, 0xbd, 0xf8, 0x72, 0x66, 0x4e, 0x1e, 0x9f, 0x9d, 0xb8, 0xf2, 0x47,
-0x0c, 0x9a, 0xb6, 0xee, 0x3f, 0xfc, 0x7a, 0x57, 0x0d, 0x79, 0x70, 0x62, 0x27, 0xad, 0xb9,
-0xd1, 0x01, 0x61, 0x40, 0x02, 0x67, 0x2d, 0xd8, 0x32, 0xe6, 0x2f, 0xdc, 0x3a, 0xd7, 0x2c,
-0xbb, 0xf4, 0x4b, 0xf5, 0x49, 0xf1, 0x60, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0xf9, 0x51, 0x01,
-0x80, 0x63, 0x25, 0xa9, 0xb1, 0xe0, 0x42, 0xe7, 0x4c, 0x1a, 0x97, 0xac, 0xbb, 0xf4, 0x6a,
-0x37, 0xcd, 0x18, 0xb2, 0xe6, 0x2f, 0xdc, 0x1b, 0x95, 0xa8, 0xd2, 0x07, 0x6d, 0x58, 0x32,
-0xe6, 0x4e, 0x1e, 0x9f, 0xbc, 0xfa, 0x57, 0x0d, 0x79, 0x51, 0x20, 0xc2, 0x06, 0x6f, 0x5c,
-0x1b, 0x95, 0xa8, 0xb3, 0xc5, 0xe9, 0x31, 0xe0, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0x18, 0x93,
-0x85, 0x69, 0x31, 0xc1, 0xe1, 0x21, 0xc0, 0xe3, 0x44, 0x0a, 0x77, 0x6c, 0x5a, 0x17, 0x8d,
-0x98, 0x93, 0xa4, 0xab, 0xd4, 0x2a, 0xb7, 0xec, 0x5a, 0x17, 0xac, 0xbb, 0xf4, 0x4b, 0x14,
-0xaa, 0xb7, 0xec, 0x3b, 0xd5, 0x28, 0xb3, 0xc5, 0xe9, 0x31, 0xc1, 0x00, 0x82, 0x67, 0x4c,
-0xfb, 0x55, 0x28, 0xd2, 0x26, 0xaf, 0xbd, 0xd9, 0x11, 0x81, 0x61, 0x21, 0xa1, 0xa1, 0xc0,
-0x02, 0x86, 0x6f, 0x5c, 0x1b, 0xb4, 0xcb, 0x14, 0x8b, 0x94, 0xaa, 0xd6, 0x2e, 0xbf, 0xdd,
-0x19, 0xb0, 0xe2, 0x46, 0x0e, 0x7f, 0x7c, 0x5b, 0x15, 0x89, 0x90, 0x83, 0x84, 0x6b, 0x54,
-0x0b, 0x75, 0x68, 0x52, 0x07, 0x6d, 0x58, 0x32, 0xc7, 0xed, 0x58, 0x32, 0xc7, 0xed, 0x58,
-0x32, 0xe6, 0x4e, 0xff, 0x7c, 0x7a, 0x76, 0x6e, 0x3f, 0xdd, 0x38, 0xd3, 0x05, 0x88, 0x92,
-0xa6, 0xaf, 0xdc, 0x1b, 0xb4, 0xcb, 0xf5, 0x68, 0x52, 0x07, 0x8c, 0x7b, 0x55, 0x09, 0x90,
-0x83, 0x84, 0x6b, 0x54, 0x2a, 0xb7, 0xec, 0x3b, 0xd5, 0x09, 0x90, 0xa2, 0xc6, 0x0e, 0x7f,
-0x7c, 0x7a, 0x57, 0x0d, 0x98, 0xb2, 0xc7, 0xed, 0x58, 0x32, 0xc7, 0x0c, 0x7b, 0x74, 0x4b,
-0x14, 0x8b, 0x94, 0xaa, 0xb7, 0xcd, 0x18, 0x93, 0xa4, 0xca, 0x16, 0xae, 0xbf, 0xdd, 0x19,
-0xb0, 0xe2, 0x46, 0x0e, 0x7f, 0x5d, 0x19, 0x91, 0x81, 0x80, 0x63, 0x44, 0xeb, 0x35, 0xc9,
-0x10, 0x83, 0x65, 0x48, 0x12, 0xa6, 0xce, 0x1e, 0x9f, 0xbc, 0xdb, 0x15, 0x89, 0x71, 0x60,
-0x23, 0xc4, 0xeb, 0x54, 0x2a, 0xb7, 0xec, 0x5a, 0x36, 0xcf, 0x81, 0x10, 0xac, 0x74 };
+const uint8_t firmware_data[FIRMWARE_LENGTH] PROGMEM = {
+ 0x01, 0xe8, 0xba, 0x26, 0x0b, 0xb2, 0xbe, 0xfe, 0x7e, 0x5f, 0x3c, 0xdb, 0x15, 0xa8, 0xb3,
+ 0xe4, 0x2b, 0xb5, 0xe8, 0x53, 0x07, 0x6d, 0x3b, 0xd1, 0x20, 0xc2, 0x06, 0x6f, 0x3d, 0xd9,
+ 0x11, 0xa0, 0xc2, 0xe7, 0x2d, 0xb9, 0xd1, 0x20, 0xa3, 0xa5, 0xc8, 0xf3, 0x64, 0x4a, 0xf7,
+ 0x4d, 0x18, 0x93, 0xa4, 0xca, 0xf7, 0x6c, 0x5a, 0x36, 0xee, 0x5e, 0x3e, 0xfe, 0x7e, 0x7e,
+ 0x5f, 0x1d, 0x99, 0xb0, 0xc3, 0xe5, 0x29, 0xd3, 0x03, 0x65, 0x48, 0x12, 0x87, 0x6d, 0x58,
+ 0x32, 0xe6, 0x2f, 0xdc, 0x3a, 0xf2, 0x4f, 0xfd, 0x59, 0x11, 0x81, 0x61, 0x21, 0xc0, 0x02,
+ 0x86, 0x8e, 0x7f, 0x5d, 0x38, 0xf2, 0x47, 0x0c, 0x7b, 0x55, 0x28, 0xb3, 0xe4, 0x4a, 0x16,
+ 0xab, 0xbf, 0xdd, 0x38, 0xf2, 0x66, 0x4e, 0xff, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xa5, 0xc8,
+ 0x12, 0xa6, 0xaf, 0xdc, 0x3a, 0xd1, 0x41, 0x60, 0x75, 0x58, 0x24, 0x92, 0xd4, 0x72, 0x6c,
+ 0xe0, 0x2f, 0xfd, 0x23, 0x8d, 0x1c, 0x5b, 0xb2, 0x97, 0x36, 0x3d, 0x0b, 0xa2, 0x49, 0xb1,
+ 0x58, 0xf2, 0x1f, 0xc0, 0xcb, 0xf8, 0x41, 0x4f, 0xcd, 0x1e, 0x6b, 0x39, 0xa7, 0x2b, 0xe9,
+ 0x30, 0x16, 0x83, 0xd2, 0x0e, 0x47, 0x8f, 0xe3, 0xb1, 0xdf, 0xa2, 0x15, 0xdb, 0x5d, 0x30,
+ 0xc5, 0x1a, 0xab, 0x31, 0x99, 0xf3, 0xfa, 0xb2, 0x86, 0x69, 0xad, 0x7a, 0xe8, 0xa7, 0x18,
+ 0x6a, 0xcc, 0xc8, 0x65, 0x23, 0x87, 0xa8, 0x5f, 0xf5, 0x21, 0x59, 0x75, 0x09, 0x71, 0x45,
+ 0x55, 0x25, 0x4b, 0xda, 0xa1, 0xc3, 0xf7, 0x41, 0xab, 0x59, 0xd9, 0x74, 0x12, 0x55, 0x5f,
+ 0xbc, 0xaf, 0xd9, 0xfd, 0xb0, 0x1e, 0xa3, 0x0f, 0xff, 0xde, 0x11, 0x16, 0x6a, 0xae, 0x0e,
+ 0xe1, 0x5d, 0x3c, 0x10, 0x43, 0x9a, 0xa1, 0x0b, 0x24, 0x8f, 0x0d, 0x7f, 0x0b, 0x5e, 0x4c,
+ 0x42, 0xa4, 0x84, 0x2c, 0x40, 0xd0, 0x55, 0x39, 0xe6, 0x4b, 0xf8, 0x9b, 0x2f, 0xdc, 0x28,
+ 0xff, 0xfa, 0xb5, 0x85, 0x19, 0xe5, 0x28, 0xa1, 0x77, 0xaa, 0x73, 0xf3, 0x03, 0xc7, 0x62,
+ 0xa6, 0x91, 0x18, 0xc9, 0xb0, 0xcd, 0x05, 0xdc, 0xca, 0x81, 0x26, 0x1a, 0x47, 0x40, 0xda,
+ 0x36, 0x7d, 0x6a, 0x53, 0xc8, 0x5a, 0x77, 0x5d, 0x19, 0xa4, 0x1b, 0x23, 0x83, 0xd0, 0xb2,
+ 0xaa, 0x0e, 0xbf, 0x77, 0x4e, 0x3a, 0x3b, 0x59, 0x00, 0x31, 0x0d, 0x02, 0x1b, 0x88, 0x7a,
+ 0xd4, 0xbd, 0x9d, 0xcc, 0x58, 0x04, 0x69, 0xf6, 0x3b, 0xca, 0x42, 0xe2, 0xfd, 0xc3, 0x3d,
+ 0x39, 0xc5, 0xd0, 0x71, 0xe4, 0xc8, 0xb7, 0x3e, 0x3f, 0xc8, 0xe9, 0xca, 0xc9, 0x3f, 0x04,
+ 0x4e, 0x1b, 0x79, 0xca, 0xa5, 0x61, 0xc2, 0xed, 0x1d, 0xa6, 0xda, 0x5a, 0xe9, 0x7f, 0x65,
+ 0x8c, 0xbe, 0x12, 0x6e, 0xa4, 0x5b, 0x33, 0x2f, 0x84, 0x28, 0x9c, 0x1c, 0x88, 0x2d, 0xff,
+ 0x07, 0xbf, 0xa6, 0xd7, 0x5a, 0x88, 0x86, 0xb0, 0x3f, 0xf6, 0x31, 0x5b, 0x11, 0x6d, 0xf5,
+ 0x58, 0xeb, 0x58, 0x02, 0x9e, 0xb5, 0x9a, 0xb1, 0xff, 0x25, 0x9d, 0x8b, 0x4f, 0xb6, 0x0a,
+ 0xf9, 0xea, 0x3e, 0x3f, 0x21, 0x09, 0x65, 0x21, 0x22, 0xfe, 0x3d, 0x4e, 0x11, 0x5b, 0x9e,
+ 0x5a, 0x59, 0x8b, 0xdd, 0xd8, 0xce, 0xd6, 0xd9, 0x59, 0xd2, 0x1e, 0xfd, 0xef, 0x0d, 0x1b,
+ 0xd9, 0x61, 0x7f, 0xd7, 0x2d, 0xad, 0x62, 0x09, 0xe5, 0x22, 0x63, 0xea, 0xc7, 0x31, 0xd9,
+ 0xa1, 0x38, 0x80, 0x5c, 0xa7, 0x32, 0x82, 0xec, 0x1b, 0xa2, 0x49, 0x5a, 0x06, 0xd2, 0x7c,
+ 0xc9, 0x96, 0x57, 0xbb, 0x17, 0x75, 0xfc, 0x7a, 0x8f, 0x0d, 0x77, 0xb5, 0x7a, 0x8e, 0x3e,
+ 0xf4, 0xba, 0x2f, 0x69, 0x13, 0x26, 0xd6, 0xd9, 0x21, 0x60, 0x2f, 0x21, 0x3e, 0x87, 0xee,
+ 0xfd, 0x87, 0x16, 0x0d, 0xc8, 0x08, 0x00, 0x25, 0x71, 0xac, 0x2c, 0x03, 0x2a, 0x37, 0x2d,
+ 0xb3, 0x34, 0x09, 0x91, 0xe3, 0x06, 0x2c, 0x38, 0x37, 0x95, 0x3b, 0x17, 0x7a, 0xaf, 0xac,
+ 0x99, 0x55, 0xab, 0x41, 0x39, 0x5f, 0x8e, 0xa6, 0x43, 0x80, 0x03, 0x88, 0x6f, 0x7d, 0xbd,
+ 0x5a, 0xb4, 0x2b, 0x32, 0x23, 0x5a, 0xa9, 0x31, 0x32, 0x39, 0x4c, 0x5b, 0xf4, 0x6b, 0xaf,
+ 0x66, 0x6f, 0x3c, 0x8e, 0x2d, 0x82, 0x97, 0x9f, 0x4a, 0x01, 0xdc, 0x99, 0x98, 0x00, 0xec,
+ 0x38, 0x7a, 0x79, 0x70, 0xa6, 0x85, 0xd6, 0x21, 0x63, 0x0d, 0x45, 0x9a, 0x2e, 0x5e, 0xa7,
+ 0xb1, 0xea, 0x66, 0x6a, 0xbc, 0x62, 0x2d, 0x7b, 0x7d, 0x85, 0xea, 0x95, 0x2f, 0xc0, 0xe8,
+ 0x6f, 0x35, 0xa0, 0x3a, 0x02, 0x25, 0xbc, 0xb2, 0x5f, 0x5c, 0x43, 0x96, 0xcc, 0x26, 0xd2,
+ 0x16, 0xb4, 0x96, 0x73, 0xd7, 0x13, 0xc7, 0xae, 0x53, 0x15, 0x31, 0x89, 0x68, 0x66, 0x6d,
+ 0x2c, 0x92, 0x1f, 0xcc, 0x5b, 0xa7, 0x8f, 0x5d, 0xbb, 0xc9, 0xdb, 0xe8, 0x3b, 0x9d, 0x61,
+ 0x74, 0x8b, 0x05, 0xa1, 0x58, 0x52, 0x68, 0xee, 0x3d, 0x39, 0x79, 0xa0, 0x9b, 0xdd, 0xe1,
+ 0x55, 0xc9, 0x60, 0xeb, 0xad, 0xb8, 0x5b, 0xc2, 0x5a, 0xb5, 0x2c, 0x18, 0x55, 0xa9, 0x50,
+ 0xc3, 0xf6, 0x72, 0x5f, 0xcc, 0xe2, 0xf4, 0x55, 0xb5, 0xd6, 0xb5, 0x4a, 0x99, 0xa5, 0x28,
+ 0x74, 0x97, 0x18, 0xe8, 0xc0, 0x84, 0x89, 0x50, 0x03, 0x86, 0x4d, 0x1a, 0xb7, 0x09, 0x90,
+ 0xa2, 0x01, 0x04, 0xbb, 0x73, 0x62, 0xcb, 0x97, 0x22, 0x70, 0x5d, 0x52, 0x41, 0x8e, 0xd9,
+ 0x90, 0x15, 0xaa, 0xab, 0x0a, 0x31, 0x65, 0xb4, 0xda, 0xd0, 0xee, 0x24, 0xc9, 0x41, 0x91,
+ 0x1e, 0xbc, 0x46, 0x70, 0x40, 0x9d, 0xda, 0x0e, 0x2a, 0xe4, 0xb2, 0x4c, 0x9f, 0xf2, 0xfc,
+ 0xf3, 0x84, 0x17, 0x44, 0x1e, 0xd7, 0xca, 0x23, 0x1f, 0x3f, 0x5a, 0x22, 0x3d, 0xaf, 0x9b,
+ 0x2d, 0xfc, 0x41, 0xad, 0x26, 0xb4, 0x45, 0x67, 0x0b, 0x80, 0x0e, 0xf9, 0x61, 0x37, 0xec,
+ 0x3b, 0xf4, 0x4b, 0x14, 0xdf, 0x5a, 0x0c, 0x3a, 0x50, 0x0b, 0x14, 0x0c, 0x72, 0xae, 0xc6,
+ 0xc5, 0xec, 0x35, 0x53, 0x2d, 0x59, 0xed, 0x91, 0x74, 0xe2, 0xc4, 0xc8, 0xf2, 0x25, 0x6b,
+ 0x97, 0x6f, 0xc9, 0x76, 0xce, 0xa9, 0xb1, 0x99, 0x8f, 0x5a, 0x92, 0x3b, 0xc4, 0x8d, 0x54,
+ 0x50, 0x40, 0x72, 0xd6, 0x90, 0x83, 0xfc, 0xe5, 0x49, 0x8b, 0x17, 0xf5, 0xfd, 0x6b, 0x8d,
+ 0x32, 0x02, 0xe9, 0x0a, 0xfe, 0xbf, 0x00, 0x6b, 0xa3, 0xad, 0x5f, 0x09, 0x4b, 0x97, 0x2b,
+ 0x00, 0x58, 0x65, 0x2e, 0x07, 0x49, 0x0a, 0x3b, 0x6b, 0x2e, 0x50, 0x6c, 0x1d, 0xac, 0xb7,
+ 0x6a, 0x26, 0xd8, 0x13, 0xa4, 0xca, 0x16, 0xae, 0xab, 0x93, 0xb9, 0x1c, 0x1c, 0xb4, 0x47,
+ 0x6a, 0x38, 0x36, 0x17, 0x27, 0xc9, 0x7f, 0xc7, 0x64, 0xcb, 0x89, 0x58, 0xc5, 0x61, 0xc2,
+ 0xc6, 0xea, 0x15, 0x0b, 0x34, 0x0c, 0x5d, 0x61, 0x76, 0x6e, 0x2b, 0x62, 0x40, 0x92, 0xa3,
+ 0x6c, 0xef, 0xf4, 0xe4, 0xc3, 0xa1, 0xa8, 0xf5, 0x94, 0x79, 0x0d, 0xd1, 0x3d, 0xcb, 0x3d,
+ 0x40, 0xb6, 0xd0, 0xf0, 0x10, 0x54, 0xd8, 0x47, 0x25, 0x51, 0xc5, 0x41, 0x79, 0x00, 0xe5,
+ 0xa0, 0x72, 0xde, 0xbb, 0x3b, 0x62, 0x17, 0xf6, 0xbc, 0x5d, 0x00, 0x76, 0x2e, 0xa7, 0x3b,
+ 0xb6, 0xf1, 0x98, 0x72, 0x59, 0x2a, 0x73, 0xb0, 0x21, 0xd6, 0x49, 0xe0, 0xc0, 0xd5, 0xeb,
+ 0x02, 0x7d, 0x4b, 0x41, 0x28, 0x70, 0x2d, 0xec, 0x2b, 0x71, 0x1f, 0x0b, 0xb9, 0x71, 0x63,
+ 0x06, 0xe6, 0xbc, 0x60, 0xbb, 0xf4, 0x9a, 0x62, 0x43, 0x09, 0x18, 0x4e, 0x93, 0x06, 0x4d,
+ 0x76, 0xfa, 0x7f, 0xbd, 0x02, 0xe4, 0x50, 0x91, 0x12, 0xe5, 0x86, 0xff, 0x64, 0x1e, 0xaf,
+ 0x7e, 0xb3, 0xb2, 0xde, 0x89, 0xc1, 0xa2, 0x6f, 0x40, 0x7b, 0x41, 0x51, 0x63, 0xea, 0x25,
+ 0xd1, 0x97, 0x57, 0x92, 0xa8, 0x45, 0xa1, 0xa5, 0x45, 0x21, 0x43, 0x7f, 0x83, 0x15, 0x29,
+ 0xd0, 0x30, 0x53, 0x32, 0xb4, 0x5a, 0x17, 0x96, 0xbc, 0xc2, 0x68, 0xa9, 0xb7, 0xaf, 0xac,
+ 0xdf, 0xf1, 0xe3, 0x89, 0xba, 0x24, 0x79, 0x54, 0xc6, 0x14, 0x07, 0x1c, 0x1e, 0x0d, 0x3a,
+ 0x6b, 0xe5, 0x3d, 0x4e, 0x10, 0x60, 0x96, 0xec, 0x6c, 0xda, 0x47, 0xae, 0x03, 0x25, 0x39,
+ 0x1d, 0x74, 0xc8, 0xac, 0x6a, 0xf2, 0x6b, 0x05, 0x2a, 0x9a, 0xe7, 0xe8, 0x92, 0xd6, 0xc2,
+ 0x6d, 0xfa, 0xe8, 0xa7, 0x9d, 0x5f, 0x48, 0xc9, 0x75, 0xf1, 0x66, 0x6a, 0xdb, 0x5d, 0x9a,
+ 0xcd, 0x27, 0xdd, 0xb9, 0x24, 0x04, 0x9c, 0x18, 0xc2, 0x6d, 0x0c, 0x91, 0x34, 0x48, 0x42,
+ 0x6f, 0xe9, 0x59, 0x70, 0xc4, 0x7e, 0x81, 0x0e, 0x32, 0x0a, 0x93, 0x48, 0xb0, 0xc0, 0x15,
+ 0x9e, 0x05, 0xac, 0x36, 0x16, 0xcb, 0x59, 0x65, 0xa0, 0x83, 0xdf, 0x3e, 0xda, 0xfb, 0x1d,
+ 0x1a, 0xdb, 0x65, 0xec, 0x9a, 0xc6, 0xc3, 0x8e, 0x3c, 0x45, 0xfd, 0xc8, 0xf5, 0x1c, 0x6a,
+ 0x67, 0x0d, 0x8f, 0x99, 0x7d, 0x30, 0x21, 0x8c, 0xea, 0x22, 0x87, 0x65, 0xc9, 0xb2, 0x4c,
+ 0xe4, 0x1b, 0x46, 0xba, 0x54, 0xbd, 0x7c, 0xca, 0xd5, 0x8f, 0x5b, 0xa5, 0x01, 0x04, 0xd8,
+ 0x0a, 0x16, 0xbf, 0xb9, 0x50, 0x2e, 0x37, 0x2f, 0x64, 0xf3, 0x70, 0x11, 0x02, 0x05, 0x31,
+ 0x9b, 0xa0, 0xb2, 0x01, 0x5e, 0x4f, 0x19, 0xc9, 0xd4, 0xea, 0xa1, 0x79, 0x54, 0x53, 0xa7,
+ 0xde, 0x2f, 0x49, 0xd3, 0xd1, 0x63, 0xb5, 0x03, 0x15, 0x4e, 0xbf, 0x04, 0xb3, 0x26, 0x8b,
+ 0x20, 0xb2, 0x45, 0xcf, 0xcd, 0x5b, 0x82, 0x32, 0x88, 0x61, 0xa7, 0xa8, 0xb2, 0xa0, 0x72,
+ 0x96, 0xc0, 0xdb, 0x2b, 0xe2, 0x5f, 0xba, 0xe3, 0xf5, 0x8a, 0xde, 0xf1, 0x18, 0x01, 0x16,
+ 0x40, 0xd9, 0x86, 0x12, 0x09, 0x18, 0x1b, 0x05, 0x0c, 0xb1, 0xb5, 0x47, 0xe2, 0x43, 0xab,
+ 0xfe, 0x92, 0x63, 0x7e, 0x95, 0x2b, 0xf0, 0xaf, 0xe1, 0xf1, 0xc3, 0x4a, 0xff, 0x2b, 0x09,
+ 0xbb, 0x4a, 0x0e, 0x9a, 0xc4, 0xd8, 0x64, 0x7d, 0x83, 0xa0, 0x4f, 0x44, 0xdb, 0xc4, 0xa8,
+ 0x58, 0xef, 0xfc, 0x9e, 0x77, 0xf9, 0xa6, 0x8f, 0x58, 0x8b, 0x12, 0xf4, 0xe9, 0x81, 0x12,
+ 0x47, 0x51, 0x41, 0x83, 0xef, 0xf6, 0x73, 0xbc, 0x8e, 0x0f, 0x4c, 0x8f, 0x4e, 0x69, 0x90,
+ 0x77, 0x29, 0x5d, 0x92, 0xb0, 0x6d, 0x06, 0x67, 0x29, 0x60, 0xbd, 0x4b, 0x17, 0xc8, 0x89,
+ 0x69, 0x28, 0x29, 0xd6, 0x78, 0xcb, 0x11, 0x4c, 0xba, 0x8b, 0x68, 0xae, 0x7e, 0x9f, 0xef,
+ 0x95, 0xda, 0xe2, 0x9e, 0x7f, 0xe9, 0x55, 0xe5, 0xe1, 0xe2, 0xb7, 0xe6, 0x5f, 0xbb, 0x2c,
+ 0xa2, 0xe6, 0xee, 0xc7, 0x0a, 0x60, 0xa9, 0xd1, 0x80, 0xdf, 0x7f, 0xd6, 0x97, 0xab, 0x1d,
+ 0x22, 0x25, 0xfc, 0x79, 0x23, 0xe0, 0xae, 0xc5, 0xef, 0x16, 0xa4, 0xa1, 0x0f, 0x92, 0xa9,
+ 0xc7, 0xe3, 0x3a, 0x55, 0xdf, 0x62, 0x49, 0xd9, 0xf5, 0x84, 0x49, 0xc5, 0x90, 0x34, 0xd3,
+ 0xe1, 0xac, 0x99, 0x21, 0xb1, 0x02, 0x76, 0x4a, 0xfa, 0xd4, 0xbb, 0xa4, 0x9c, 0xa2, 0xe2,
+ 0xcb, 0x3d, 0x3b, 0x14, 0x75, 0x60, 0xd1, 0x02, 0xb4, 0xa3, 0xb4, 0x72, 0x06, 0xf9, 0x19,
+ 0x9c, 0xe2, 0xe4, 0xa7, 0x0f, 0x25, 0x88, 0xc6, 0x86, 0xd6, 0x8c, 0x74, 0x4e, 0x6e, 0xfc,
+ 0xa8, 0x48, 0x9e, 0xa7, 0x9d, 0x1a, 0x4b, 0x37, 0x09, 0xc8, 0xb0, 0x10, 0xbe, 0x6f, 0xfe,
+ 0xa3, 0xc4, 0x7a, 0xb5, 0x3d, 0xe8, 0x30, 0xf1, 0x0d, 0xa0, 0xb2, 0x44, 0xfc, 0x9b, 0x8c,
+ 0xf8, 0x61, 0xed, 0x81, 0xd1, 0x62, 0x11, 0xb4, 0xe1, 0xd5, 0x39, 0x52, 0x89, 0xd3, 0xa8,
+ 0x49, 0x31, 0xdf, 0xb6, 0xf9, 0x91, 0xf4, 0x1c, 0x9d, 0x09, 0x95, 0x40, 0x56, 0xe7, 0xe3,
+ 0xcd, 0x5c, 0x92, 0xc1, 0x1d, 0x6b, 0xe9, 0x78, 0x6f, 0x8e, 0x94, 0x42, 0x66, 0xa2, 0xaa,
+ 0xd3, 0xc8, 0x2e, 0xe3, 0xf6, 0x07, 0x72, 0x0b, 0x6b, 0x1e, 0x7b, 0xb9, 0x7c, 0xe0, 0xa0,
+ 0xbc, 0xd9, 0x25, 0xdf, 0x87, 0xa8, 0x5f, 0x9c, 0xcc, 0xf0, 0xdb, 0x42, 0x8e, 0x07, 0x31,
+ 0x13, 0x01, 0x66, 0x32, 0xd1, 0xb8, 0xd6, 0xe3, 0x5e, 0x12, 0x76, 0x61, 0xd3, 0x38, 0x89,
+ 0xe6, 0x17, 0x6f, 0xa5, 0xf2, 0x71, 0x0e, 0xa5, 0xe2, 0x88, 0x30, 0xbb, 0xbe, 0x8a, 0xea,
+ 0xc7, 0x62, 0xc4, 0xcf, 0xb8, 0xcd, 0x33, 0x8d, 0x3d, 0x3e, 0xb5, 0x60, 0x3a, 0x03, 0x92,
+ 0xe4, 0x6d, 0x1b, 0xe0, 0xb4, 0x84, 0x08, 0x55, 0x88, 0xa7, 0x3a, 0xb9, 0x3d, 0x43, 0xc3,
+ 0xc0, 0xfa, 0x07, 0x6a, 0xca, 0x94, 0xad, 0x99, 0x55, 0xf1, 0xf1, 0xc0, 0x23, 0x87, 0x1d,
+ 0x3d, 0x1c, 0xd1, 0x66, 0xa0, 0x57, 0x10, 0x52, 0xa2, 0x7f, 0xbe, 0xf9, 0x88, 0xb6, 0x02,
+ 0xbf, 0x08, 0x23, 0xa9, 0x0c, 0x63, 0x17, 0x2a, 0xae, 0xf5, 0xf7, 0xb7, 0x21, 0x83, 0x92,
+ 0x31, 0x23, 0x0d, 0x20, 0xc3, 0xc2, 0x05, 0x21, 0x62, 0x8e, 0x45, 0xe8, 0x14, 0xc1, 0xda,
+ 0x75, 0xb8, 0xf8, 0x92, 0x01, 0xd0, 0x5d, 0x18, 0x9f, 0x99, 0x11, 0x19, 0xf5, 0x35, 0xe8,
+ 0x7f, 0x20, 0x88, 0x8c, 0x05, 0x75, 0xf5, 0xd7, 0x40, 0x17, 0xbb, 0x1e, 0x36, 0x52, 0xd9,
+ 0xa4, 0x9c, 0xc2, 0x9d, 0x42, 0x81, 0xd8, 0xc7, 0x8a, 0xe7, 0x4c, 0x81, 0xe0, 0xb7, 0x57,
+ 0xed, 0x48, 0x8b, 0xf0, 0x97, 0x15, 0x61, 0xd9, 0x2c, 0x7c, 0x45, 0xaf, 0xc2, 0xcd, 0xfc,
+ 0xaa, 0x13, 0xad, 0x59, 0xcc, 0xb2, 0xb2, 0x6e, 0xdd, 0x63, 0x9c, 0x32, 0x0f, 0xec, 0x83,
+ 0xbe, 0x78, 0xac, 0x91, 0x44, 0x1a, 0x1f, 0xea, 0xfd, 0x5d, 0x8e, 0xb4, 0xc0, 0x84, 0xd4,
+ 0xac, 0xb4, 0x87, 0x5f, 0xac, 0xef, 0xdf, 0xcd, 0x12, 0x56, 0xc8, 0xcd, 0xfe, 0xc5, 0xda,
+ 0xd3, 0xc1, 0x69, 0xf3, 0x61, 0x05, 0xea, 0x25, 0xe2, 0x12, 0x05, 0x8f, 0x39, 0x08, 0x08,
+ 0x7c, 0x37, 0xb6, 0x7e, 0x5b, 0xd8, 0xb1, 0x0e, 0xf2, 0xdb, 0x4b, 0xf1, 0xad, 0x90, 0x01,
+ 0x57, 0xcd, 0xa0, 0xb4, 0x52, 0xe8, 0xf3, 0xd7, 0x8a, 0xbd, 0x4f, 0x9f, 0x21, 0x40, 0x72,
+ 0xa4, 0xfc, 0x0b, 0x01, 0x2b, 0x2f, 0xb6, 0x4c, 0x95, 0x2d, 0x35, 0x33, 0x41, 0x6b, 0xa0,
+ 0x93, 0xe7, 0x2c, 0xf2, 0xd3, 0x72, 0x8b, 0xf4, 0x4f, 0x15, 0x3c, 0xaf, 0xd6, 0x12, 0xde,
+ 0x3f, 0x83, 0x3f, 0xff, 0xf8, 0x7f, 0xf6, 0xcc, 0xa6, 0x7f, 0xc9, 0x9a, 0x6e, 0x1f, 0xc1,
+ 0x0c, 0xfb, 0xee, 0x9c, 0xe7, 0xaf, 0xc9, 0x26, 0x54, 0xef, 0xb0, 0x39, 0xef, 0xb2, 0xe9,
+ 0x23, 0xc4, 0xef, 0xd1, 0xa1, 0xa4, 0x25, 0x24, 0x6f, 0x8d, 0x6a, 0xe5, 0x8a, 0x32, 0x3a,
+ 0xaf, 0xfc, 0xda, 0xce, 0x18, 0x25, 0x42, 0x07, 0x4d, 0x45, 0x8b, 0xdf, 0x85, 0xcf, 0x55,
+ 0xb2, 0x24, 0xfe, 0x9c, 0x69, 0x74, 0xa7, 0x6e, 0xa0, 0xce, 0xc0, 0x39, 0xf4, 0x86, 0xc6,
+ 0x8d, 0xae, 0xb9, 0x48, 0x64, 0x13, 0x0b, 0x40, 0x81, 0xa2, 0xc9, 0xa8, 0x85, 0x51, 0xee,
+ 0x9f, 0xcf, 0xa2, 0x8c, 0x19, 0x52, 0x48, 0xe2, 0xc1, 0xa8, 0x58, 0xb4, 0x10, 0x24, 0x06,
+ 0x58, 0x51, 0xfc, 0xb9, 0x12, 0xec, 0xfd, 0x73, 0xb4, 0x6d, 0x84, 0xfa, 0x06, 0x8b, 0x05,
+ 0x0b, 0x2d, 0xd6, 0xd6, 0x1f, 0x29, 0x82, 0x9f, 0x19, 0x12, 0x1e, 0xb2, 0x04, 0x8f, 0x7f,
+ 0x4d, 0xbd, 0x30, 0x2e, 0xe3, 0xe0, 0x88, 0x29, 0xc5, 0x93, 0xd6, 0x6c, 0x1f, 0x29, 0x45,
+ 0x91, 0xa7, 0x58, 0xcd, 0x05, 0x17, 0xd6, 0x6d, 0xb3, 0xca, 0x66, 0xcc, 0x3c, 0x4a, 0x74,
+ 0xfd, 0x08, 0x10, 0xa6, 0x99, 0x92, 0x10, 0xd2, 0x85, 0xab, 0x6e, 0x1d, 0x0e, 0x8b, 0x26,
+ 0x46, 0xd1, 0x6c, 0x84, 0xc0, 0x26, 0x43, 0x59, 0x68, 0xf0, 0x13, 0x1d, 0xfb, 0xe3, 0xd1,
+ 0xd2, 0xb4, 0x71, 0x9e, 0xf2, 0x59, 0x6a, 0x33, 0x29, 0x79, 0xd2, 0xd7, 0x26, 0xf1, 0xae,
+ 0x78, 0x9e, 0x1f, 0x0f, 0x3f, 0xe3, 0xe8, 0xd0, 0x27, 0x78, 0x77, 0xf6, 0xac, 0x9c, 0x56,
+ 0x39, 0x73, 0x8a, 0x6b, 0x2f, 0x34, 0x78, 0xb1, 0x11, 0xdb, 0xa4, 0x5c, 0x80, 0x01, 0x71,
+ 0x6a, 0xc2, 0xd1, 0x2e, 0x5e, 0x76, 0x28, 0x70, 0x93, 0xae, 0x3e, 0x78, 0xb0, 0x1f, 0x0f,
+ 0xda, 0xbf, 0xfb, 0x8a, 0x67, 0x65, 0x4f, 0x91, 0xed, 0x49, 0x75, 0x78, 0x62, 0xa2, 0x93,
+ 0xb5, 0x70, 0x7f, 0x4d, 0x08, 0x4e, 0x79, 0x61, 0xa8, 0x5f, 0x7f, 0xb4, 0x65, 0x9f, 0x91,
+ 0x54, 0x3a, 0xe8, 0x50, 0x33, 0xd3, 0xd5, 0x8a, 0x7c, 0xf3, 0x9e, 0x8b, 0x77, 0x7b, 0xc6,
+ 0xc6, 0x0c, 0x45, 0x95, 0x1f, 0xb0, 0xd0, 0x0b, 0x27, 0x4a, 0xfd, 0xc7, 0xf7, 0x0d, 0x5a,
+ 0x43, 0xc9, 0x7d, 0x35, 0xb0, 0x7d, 0xc4, 0x9c, 0x57, 0x1e, 0x76, 0x0d, 0xf1, 0x95, 0x30,
+ 0x71, 0xcc, 0xb3, 0x66, 0x3b, 0x63, 0xa8, 0x6c, 0xa3, 0x43, 0xa0, 0x24, 0xcc, 0xb7, 0x53,
+ 0xfe, 0xfe, 0xbc, 0x6e, 0x60, 0x89, 0xaf, 0x16, 0x21, 0xc8, 0x91, 0x6a, 0x89, 0xce, 0x80,
+ 0x2c, 0xf1, 0x59, 0xce, 0xc3, 0x60, 0x61, 0x3b, 0x0b, 0x19, 0xfe, 0x99, 0xac, 0x65, 0x90,
+ 0x15, 0x12, 0x05, 0xac, 0x7e, 0xff, 0x98, 0x7b, 0x66, 0x64, 0x0e, 0x4b, 0x5b, 0xaa, 0x8d,
+ 0x3b, 0xd2, 0x56, 0xcf, 0x99, 0x39, 0xee, 0x22, 0x81, 0xd0, 0x60, 0x06, 0x66, 0x20, 0x81,
+ 0x48, 0x3c, 0x6f, 0x3a, 0x77, 0xba, 0xcb, 0x52, 0xac, 0x79, 0x56, 0xaf, 0xe9, 0x16, 0x17,
+ 0x0a, 0xa3, 0x82, 0x08, 0xd5, 0x3c, 0x97, 0xcb, 0x09, 0xff, 0x7f, 0xf9, 0x4f, 0x60, 0x05,
+ 0xb9, 0x53, 0x26, 0xaa, 0xb8, 0x50, 0xaa, 0x19, 0x25, 0xae, 0x5f, 0xea, 0x8a, 0xd0, 0x89,
+ 0x12, 0x80, 0x43, 0x50, 0x24, 0x12, 0x21, 0x14, 0xcd, 0x77, 0xeb, 0x21, 0xcc, 0x5c, 0x09,
+ 0x64, 0xf3, 0xc7, 0xcb, 0xc5, 0x4b, 0xc3, 0xe7, 0xed, 0xe7, 0x86, 0x2c, 0x1d, 0x8e, 0x19,
+ 0x52, 0x9b, 0x2a, 0x0c, 0x18, 0x72, 0x0b, 0x1e, 0x1b, 0xb0, 0x0f, 0x42, 0x99, 0x04, 0xae,
+ 0xd5, 0xb7, 0x89, 0x1a, 0xb9, 0x4f, 0xd6, 0xaf, 0xf3, 0xc9, 0x93, 0x6f, 0xb0, 0x60, 0x83,
+ 0x6e, 0x6b, 0xd1, 0x5f, 0x3f, 0x1a, 0x83, 0x1e, 0x24, 0x00, 0x87, 0xb5, 0x3e, 0xdb, 0xf9,
+ 0x4d, 0xa7, 0x16, 0x2e, 0x19, 0x5b, 0x8f, 0x1b, 0x0d, 0x47, 0x72, 0x42, 0xe9, 0x0a, 0x11,
+ 0x08, 0x2d, 0x88, 0x1c, 0xbc, 0xc7, 0xb4, 0xbe, 0x29, 0x4d, 0x03, 0x5e, 0xec, 0xdf, 0xf3,
+ 0x3d, 0x2f, 0xe8, 0x1d, 0x9a, 0xd2, 0xd1, 0xab, 0x41, 0x3d, 0x87, 0x11, 0x45, 0xb0, 0x0d,
+ 0x46, 0xf5, 0xe8, 0x95, 0x62, 0x1c, 0x68, 0xf7, 0xa6, 0x5b, 0x39, 0x4e, 0xbf, 0x47, 0xba,
+ 0x5d, 0x7f, 0xb7, 0x6a, 0xf4, 0xba, 0x1d, 0x69, 0xf6, 0xa4, 0xe7, 0xe4, 0x6b, 0x3b, 0x0d,
+ 0x23, 0x16, 0x4a, 0xb2, 0x68, 0xf0, 0xb2, 0x0d, 0x09, 0x17, 0x6a, 0x63, 0x8c, 0x83, 0xd3,
+ 0xbd, 0x05, 0xc9, 0xf6, 0xf0, 0xa1, 0x31, 0x0b, 0x2c, 0xac, 0x83, 0xac, 0x80, 0x34, 0x32,
+ 0xb4, 0xec, 0xd0, 0xbc, 0x54, 0x82, 0x9a, 0xc8, 0xf6, 0xa0, 0x7d, 0xc6, 0x79, 0x73, 0xf4,
+ 0x20, 0x99, 0xf3, 0xb4, 0x01, 0xde, 0x91, 0x27, 0xf2, 0xc0, 0xdc, 0x81, 0x00, 0x4e, 0x7e,
+ 0x07, 0x99, 0xc8, 0x3a, 0x51, 0xbc, 0x38, 0xd6, 0x8a, 0xa2, 0xde, 0x3b, 0x6a, 0x8c, 0x1a,
+ 0x7c, 0x81, 0x0f, 0x3a, 0x1f, 0xe4, 0x05, 0x7b, 0x20, 0x35, 0x6b, 0xa5, 0x6a, 0xa7, 0xe7,
+ 0xbc, 0x9c, 0x20, 0xec, 0x00, 0x15, 0xe2, 0x51, 0xaf, 0x77, 0xeb, 0x29, 0x3c, 0x7d, 0x2e,
+ 0x00, 0x5c, 0x81, 0x21, 0xfa, 0x35, 0x6f, 0x40, 0xef, 0xfb, 0xd1, 0x3f, 0xcc, 0x9d, 0x55,
+ 0x53, 0xfb, 0x5a, 0xa5, 0x56, 0x89, 0x0b, 0x52, 0xeb, 0x57, 0x73, 0x4f, 0x1b, 0x67, 0x24,
+ 0xcb, 0xb8, 0x6a, 0x10, 0x69, 0xd6, 0xfb, 0x52, 0x40, 0xff, 0x20, 0xa5, 0xf3, 0x72, 0xe1,
+ 0x3d, 0xa4, 0x8c, 0x81, 0x66, 0x16, 0x0d, 0x5d, 0xad, 0xa8, 0x50, 0x25, 0x78, 0x31, 0x77,
+ 0x0c, 0x57, 0xe4, 0xe9, 0x15, 0x2d, 0xdb, 0x07, 0x87, 0xc8, 0xb0, 0x43, 0xde, 0xfc, 0xfe,
+ 0xa9, 0xeb, 0xf5, 0xb0, 0xd3, 0x7b, 0xe9, 0x1f, 0x6e, 0xca, 0xe4, 0x03, 0x95, 0xc5, 0xd1,
+ 0x59, 0x72, 0x63, 0xf0, 0x86, 0x54, 0xe8, 0x16, 0x62, 0x0b, 0x35, 0x29, 0xc2, 0x68, 0xd0,
+ 0xd6, 0x3e, 0x90, 0x60, 0x57, 0x1d, 0xc9, 0xed, 0x3f, 0xed, 0xb0, 0x2f, 0x7e, 0x97, 0x02,
+ 0x51, 0xec, 0xee, 0x6f, 0x82, 0x74, 0x76, 0x7f, 0xfb, 0xd6, 0xc4, 0xc3, 0xdd, 0xe8, 0xb1,
+ 0x60, 0xfc, 0xc6, 0xb9, 0x0d, 0x6a, 0x33, 0x78, 0xc6, 0xc1, 0xbf, 0x86, 0x2c, 0x50, 0xcc,
+ 0x9a, 0x70, 0x8e, 0x7b, 0xec, 0xab, 0x95, 0xac, 0x53, 0xa0, 0x4b, 0x07, 0x88, 0xaf, 0x42,
+ 0xed, 0x19, 0x8d, 0xf6, 0x32, 0x17, 0x48, 0x47, 0x1d, 0x41, 0x6f, 0xfe, 0x2e, 0xa7, 0x8f,
+ 0x4b, 0xa0, 0x51, 0xf3, 0xbf, 0x02, 0x0a, 0x48, 0x58, 0xf7, 0xa1, 0x6d, 0xea, 0xa5, 0x13,
+ 0x5a, 0x5b, 0xea, 0x0c, 0x9e, 0x52, 0x4f, 0x9e, 0xb9, 0x71, 0x7f, 0x23, 0x83, 0xda, 0x1b,
+ 0x86, 0x9a, 0x41, 0x29, 0xda, 0x70, 0xe7, 0x64, 0xa1, 0x7b, 0xd5, 0x0a, 0x22, 0x0d, 0x5c,
+ 0x40, 0xc4, 0x81, 0x07, 0x25, 0x35, 0x4a, 0x1c, 0x10, 0xdb, 0x45, 0x0a, 0xff, 0x36, 0xd4,
+ 0xe0, 0xeb, 0x5f, 0x68, 0xd6, 0x67, 0xc6, 0xd0, 0x8b, 0x76, 0x1a, 0x7d, 0x59, 0x42, 0xa1,
+ 0xcb, 0x96, 0x4d, 0x84, 0x09, 0x9a, 0x3d, 0xe0, 0x52, 0x85, 0x6e, 0x48, 0x90, 0x85, 0x2a,
+ 0x63, 0xb2, 0x69, 0xd2, 0x00, 0x43, 0x31, 0x37, 0xb3, 0x52, 0xaf, 0x62, 0xfa, 0xc1, 0xe0,
+ 0x03, 0xfb, 0x62, 0xaa, 0x88, 0xc9, 0xb2, 0x2c, 0xd5, 0xa8, 0xf5, 0xa5, 0x4c, 0x12, 0x59,
+ 0x4e, 0x06, 0x5e, 0x9b, 0x15, 0x66, 0x11, 0xb2, 0x27, 0x92, 0xdc, 0x98, 0x59, 0xde, 0xdf,
+ 0xfa, 0x9a, 0x32, 0x2e, 0xc0, 0x5d, 0x3c, 0x33, 0x41, 0x6d, 0xaf, 0xb2, 0x25, 0x23, 0x14,
+ 0xa5, 0x7b, 0xc7, 0x9b, 0x68, 0xf3, 0xda, 0xeb, 0xe3, 0xa9, 0xe2, 0x6f, 0x0e, 0x1d, 0x1c,
+ 0xba, 0x55, 0xb6, 0x34, 0x6a, 0x93, 0x1f, 0x1f, 0xb8, 0x34, 0xc8, 0x84, 0x08, 0xb1, 0x6b,
+ 0x6a, 0x28, 0x74, 0x74, 0xe5, 0xeb, 0x75, 0xe9, 0x7c, 0xd8, 0xba, 0xd8, 0x42, 0xa5, 0xee,
+ 0x1f, 0x80, 0xd9, 0x96, 0xb2, 0x2e, 0xe7, 0xbf, 0xba, 0xeb, 0xd1, 0x69, 0xbb, 0x8f, 0xfd,
+ 0x5a, 0x63, 0x8f, 0x39, 0x7f, 0xdf, 0x1d, 0x37, 0xd2, 0x18, 0x35, 0x9d, 0xb6, 0xcc, 0xe4,
+ 0x27, 0x81, 0x89, 0x38, 0x38, 0x68, 0x33, 0xe7, 0x78, 0xd8, 0x76, 0xf5, 0xee, 0xd0, 0x4a,
+ 0x07, 0x69, 0x19, 0x7a, 0xad, 0x18, 0xb1, 0x94, 0x61, 0x45, 0x53, 0xa2, 0x48, 0xda, 0x96,
+ 0x4a, 0xf9, 0xee, 0x94, 0x2a, 0x1f, 0x6e, 0x18, 0x3c, 0x92, 0x46, 0xd1, 0x1a, 0x28, 0x18,
+ 0x32, 0x1f, 0x3a, 0x45, 0xbe, 0x04, 0x35, 0x92, 0xe5, 0xa3, 0xcb, 0xb5, 0x2e, 0x32, 0x43,
+ 0xac, 0x65, 0x17, 0x89, 0x99, 0x15, 0x03, 0x9e, 0xb1, 0x23, 0x2f, 0xed, 0x76, 0x4d, 0xd8,
+ 0xac, 0x21, 0x40, 0xc4, 0x99, 0x4e, 0x65, 0x71, 0x2c, 0xb3, 0x45, 0xab, 0xfb, 0xe7, 0x72,
+ 0x39, 0x56, 0x30, 0x6d, 0xfb, 0x74, 0xeb, 0x99, 0xf3, 0xcd, 0x57, 0x5c, 0x78, 0x75, 0xe9,
+ 0x8d, 0xc3, 0xa2, 0xfb, 0x5d, 0xe0, 0x90, 0xc5, 0x55, 0xad, 0x91, 0x53, 0x4e, 0x9e, 0xbd,
+ 0x8c, 0x49, 0xa4, 0xa4, 0x69, 0x10, 0x0c, 0xc5, 0x76, 0xe9, 0x25, 0x86, 0x8d, 0x66, 0x23,
+ 0xa8, 0xdb, 0x5c, 0xe8, 0xd9, 0x30, 0xe1, 0x15, 0x7b, 0xc0, 0x99, 0x0f, 0x03, 0xec, 0xaa,
+ 0x12, 0xef, 0xce, 0xd4, 0xea, 0x55, 0x5c, 0x08, 0x86, 0xf4, 0xf4, 0xb0, 0x83, 0x42, 0x95,
+ 0x37, 0xb6, 0x38, 0xe0, 0x2b, 0x54, 0x89, 0xbd, 0x4e, 0x20, 0x9d, 0x3f, 0xc3, 0x4b, 0xb7,
+ 0xec, 0xfa, 0x5a, 0x14, 0x03, 0xcb, 0x64, 0xc8, 0x34, 0x4a, 0x4b, 0x6e, 0xf8, 0x6e, 0x56,
+ 0xf6, 0xdd, 0x5f, 0xa1, 0x24, 0xe2, 0xd4, 0xd0, 0x82, 0x64, 0x1f, 0x8e, 0x9b, 0xfa, 0xb4,
+ 0xcb, 0xdb, 0x0a, 0xe8, 0x15, 0xfc, 0x15, 0xab, 0x4b, 0x18, 0xbf, 0xd4, 0x42, 0x14, 0x48,
+ 0x82, 0x85, 0xdd, 0xeb, 0x49, 0x1b, 0x0b, 0x0b, 0x05, 0xe9, 0xb4, 0xa1, 0x33, 0x0a, 0x5d,
+ 0x0e, 0x6c, 0x4b, 0xc0, 0xd6, 0x6c, 0x7c, 0xfb, 0x69, 0x0b, 0x53, 0x19, 0xe4, 0xf3, 0x35,
+ 0xfc, 0xbe, 0xa1, 0x34, 0x02, 0x09, 0x4f, 0x74, 0x86, 0x92, 0xcd, 0x5d, 0x1a, 0xc1, 0x27,
+ 0x0c, 0xf2, 0xc5, 0xcf, 0xdd, 0x23, 0x93, 0x02, 0xbd, 0x41, 0x5e, 0x42, 0xf0, 0xa0, 0x9d,
+ 0x0c, 0x72, 0xc8, 0xec, 0x32, 0x0a, 0x8a, 0xfd, 0x3d, 0x5a, 0x41, 0x27, 0x0c, 0x88, 0x59,
+ 0xad, 0x94, 0x2e, 0xef, 0x5d, 0x8f, 0xc7, 0xdf, 0x66, 0xe4, 0xdd, 0x56, 0x6c, 0x7b, 0xca,
+ 0x55, 0x81, 0xae, 0xae, 0x5c, 0x1b, 0x1a, 0xab, 0xae, 0x99, 0x8d, 0xcc, 0x42, 0x97, 0x59,
+ 0xf4, 0x14, 0x3f, 0x75, 0xc6, 0xd1, 0x88, 0xba, 0xaa, 0x84, 0x4a, 0xd0, 0x34, 0x08, 0x3b,
+ 0x7d, 0xdb, 0x15, 0x06, 0xb0, 0x5c, 0xbd, 0x40, 0xf5, 0xa8, 0xec, 0xae, 0x36, 0x40, 0xdd,
+ 0x90, 0x1c, 0x3e, 0x0d, 0x7e, 0x73, 0xc7, 0xc2, 0xc5, 0x6a, 0xff, 0x52, 0x05, 0x7f, 0xbe,
+ 0xd0, 0x92, 0xfd, 0xb3, 0x6f, 0xff, 0x5d, 0xb7, 0x97, 0x64, 0x73, 0x7b, 0xca, 0xd1, 0x98,
+ 0x24, 0x6b, 0x0b, 0x01, 0x68, 0xdd, 0x27, 0x85, 0x85, 0xb5, 0x83, 0xc1, 0xe0, 0x50, 0x64,
+ 0xc7, 0xaf, 0xf1, 0xc6, 0x4d, 0xb1, 0xef, 0xc9, 0xb4, 0x0a, 0x6d, 0x65, 0xf3, 0x47, 0xcc,
+ 0xa3, 0x02, 0x21, 0x0c, 0xbe, 0x22, 0x29, 0x05, 0xcf, 0x5f, 0xe8, 0x94, 0x6c, 0xe5, 0xdc,
+ 0xc4, 0xdf, 0xbe, 0x3e, 0xa8, 0xb4, 0x18, 0xb0, 0x99, 0xb8, 0x6f, 0xff, 0x5d, 0xb9, 0xfd,
+ 0x3b, 0x5d, 0x16, 0xbf, 0x3e, 0xd8, 0xb3, 0xd8, 0x08, 0x34, 0xf6, 0x47, 0x35, 0x5b, 0x72,
+ 0x1a, 0x33, 0xad, 0x52, 0x5d, 0xb8, 0xd0, 0x77, 0xc6, 0xab, 0xba, 0x55, 0x09, 0x5f, 0x02,
+ 0xf8, 0xd4, 0x5f, 0x53, 0x06, 0x91, 0xcd, 0x74, 0x42, 0xae, 0x54, 0x91, 0x81, 0x62, 0x13,
+ 0x6f, 0xd8, 0xa9, 0x77, 0xc3, 0x6c, 0xcb, 0xf1, 0x29, 0x5a, 0xcc, 0xda, 0x35, 0xbd, 0x52,
+ 0x23, 0xbe, 0x59, 0xeb, 0x12, 0x6d, 0xb7, 0x53, 0xee, 0xfc, 0xb4, 0x1b, 0x13, 0x5e, 0xba,
+ 0x16, 0x7c, 0xc5, 0xf3, 0xe3, 0x6d, 0x07, 0x78, 0xf5, 0x2b, 0x21, 0x05, 0x88, 0x4c, 0xc0,
+ 0xa1, 0xe3, 0x36, 0x10, 0xf8, 0x1b, 0xd8, 0x17, 0xfb, 0x6a, 0x4e, 0xd8, 0xb3, 0x47, 0x2d,
+ 0x99, 0xbd, 0xbb, 0x5d, 0x37, 0x7d, 0xba, 0xf1, 0xe1, 0x7c, 0xc0, 0xc5, 0x54, 0x62, 0x7f,
+ 0xcf, 0x5a, 0x4a, 0x93, 0xcc, 0xf1, 0x1b, 0x34, 0xc8, 0xa6, 0x05, 0x4c, 0x55, 0x8b, 0x54,
+ 0x84, 0xd5, 0x77, 0xeb, 0xc0, 0x6d, 0x3a, 0x29, 0xbd, 0x75, 0x61, 0x09, 0x9a, 0x2c, 0xbb,
+ 0xf7, 0x18, 0x79, 0x34, 0x90, 0x24, 0xa5, 0x81, 0x70, 0x87, 0xc5, 0x02, 0x7c, 0xba, 0xd4,
+ 0x5e, 0x14, 0x8e, 0xe4, 0xed, 0xa2, 0x61, 0x6a, 0xb9, 0x6e, 0xb5, 0x4a, 0xb9, 0x01, 0x46,
+ 0xf4, 0xcf, 0xbc, 0x09, 0x2f, 0x27, 0x4b, 0xbd, 0x86, 0x7a, 0x10, 0xe1, 0xd4, 0xc8, 0xd9,
+ 0x20, 0x8d, 0x8a, 0x63, 0x00, 0x63, 0x44, 0xeb, 0x54, 0x0b, 0x75, 0x49, 0x10, 0xa2, 0xa7,
+ 0xad, 0xb9, 0xd1, 0x01, 0x80, 0x63, 0x25, 0xc8, 0x12, 0xa6, 0xce, 0x1e, 0xbe, 0xfe, 0x7e,
+ 0x5f, 0x3c, 0xdb, 0x34, 0xea, 0x37, 0xec, 0x3b, 0xd5, 0x28, 0xd2, 0x07, 0x8c, 0x9a, 0xb6,
+ 0xee, 0x5e, 0x3e, 0xdf, 0x1d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x5c, 0x1b, 0xb4, 0xea, 0x56,
+ 0x2e, 0xde, 0x1f, 0x9d, 0xb8, 0xd3, 0x24, 0xab, 0xd4, 0x2a, 0xd6, 0x2e, 0xde, 0x1f, 0x9d,
+ 0xb8, 0xf2, 0x66, 0x2f, 0xbd, 0xf8, 0x72, 0x66, 0x4e, 0x1e, 0x9f, 0x9d, 0xb8, 0xf2, 0x47,
+ 0x0c, 0x9a, 0xb6, 0xee, 0x3f, 0xfc, 0x7a, 0x57, 0x0d, 0x79, 0x70, 0x62, 0x27, 0xad, 0xb9,
+ 0xd1, 0x01, 0x61, 0x40, 0x02, 0x67, 0x2d, 0xd8, 0x32, 0xe6, 0x2f, 0xdc, 0x3a, 0xd7, 0x2c,
+ 0xbb, 0xf4, 0x4b, 0xf5, 0x49, 0xf1, 0x60, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0xf9, 0x51, 0x01,
+ 0x80, 0x63, 0x25, 0xa9, 0xb1, 0xe0, 0x42, 0xe7, 0x4c, 0x1a, 0x97, 0xac, 0xbb, 0xf4, 0x6a,
+ 0x37, 0xcd, 0x18, 0xb2, 0xe6, 0x2f, 0xdc, 0x1b, 0x95, 0xa8, 0xd2, 0x07, 0x6d, 0x58, 0x32,
+ 0xe6, 0x4e, 0x1e, 0x9f, 0xbc, 0xfa, 0x57, 0x0d, 0x79, 0x51, 0x20, 0xc2, 0x06, 0x6f, 0x5c,
+ 0x1b, 0x95, 0xa8, 0xb3, 0xc5, 0xe9, 0x31, 0xe0, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0x18, 0x93,
+ 0x85, 0x69, 0x31, 0xc1, 0xe1, 0x21, 0xc0, 0xe3, 0x44, 0x0a, 0x77, 0x6c, 0x5a, 0x17, 0x8d,
+ 0x98, 0x93, 0xa4, 0xab, 0xd4, 0x2a, 0xb7, 0xec, 0x5a, 0x17, 0xac, 0xbb, 0xf4, 0x4b, 0x14,
+ 0xaa, 0xb7, 0xec, 0x3b, 0xd5, 0x28, 0xb3, 0xc5, 0xe9, 0x31, 0xc1, 0x00, 0x82, 0x67, 0x4c,
+ 0xfb, 0x55, 0x28, 0xd2, 0x26, 0xaf, 0xbd, 0xd9, 0x11, 0x81, 0x61, 0x21, 0xa1, 0xa1, 0xc0,
+ 0x02, 0x86, 0x6f, 0x5c, 0x1b, 0xb4, 0xcb, 0x14, 0x8b, 0x94, 0xaa, 0xd6, 0x2e, 0xbf, 0xdd,
+ 0x19, 0xb0, 0xe2, 0x46, 0x0e, 0x7f, 0x7c, 0x5b, 0x15, 0x89, 0x90, 0x83, 0x84, 0x6b, 0x54,
+ 0x0b, 0x75, 0x68, 0x52, 0x07, 0x6d, 0x58, 0x32, 0xc7, 0xed, 0x58, 0x32, 0xc7, 0xed, 0x58,
+ 0x32, 0xe6, 0x4e, 0xff, 0x7c, 0x7a, 0x76, 0x6e, 0x3f, 0xdd, 0x38, 0xd3, 0x05, 0x88, 0x92,
+ 0xa6, 0xaf, 0xdc, 0x1b, 0xb4, 0xcb, 0xf5, 0x68, 0x52, 0x07, 0x8c, 0x7b, 0x55, 0x09, 0x90,
+ 0x83, 0x84, 0x6b, 0x54, 0x2a, 0xb7, 0xec, 0x3b, 0xd5, 0x09, 0x90, 0xa2, 0xc6, 0x0e, 0x7f,
+ 0x7c, 0x7a, 0x57, 0x0d, 0x98, 0xb2, 0xc7, 0xed, 0x58, 0x32, 0xc7, 0x0c, 0x7b, 0x74, 0x4b,
+ 0x14, 0x8b, 0x94, 0xaa, 0xb7, 0xcd, 0x18, 0x93, 0xa4, 0xca, 0x16, 0xae, 0xbf, 0xdd, 0x19,
+ 0xb0, 0xe2, 0x46, 0x0e, 0x7f, 0x5d, 0x19, 0x91, 0x81, 0x80, 0x63, 0x44, 0xeb, 0x35, 0xc9,
+ 0x10, 0x83, 0x65, 0x48, 0x12, 0xa6, 0xce, 0x1e, 0x9f, 0xbc, 0xdb, 0x15, 0x89, 0x71, 0x60,
+ 0x23, 0xc4, 0xeb, 0x54, 0x2a, 0xb7, 0xec, 0x5a, 0x36, 0xcf, 0x81, 0x10, 0xac, 0x74
+};
// clang-format off
diff --git a/drivers/serial.h b/drivers/serial.h
index d9c2a69e96..0cfdbd9959 100644
--- a/drivers/serial.h
+++ b/drivers/serial.h
@@ -26,21 +26,4 @@ void soft_serial_initiator_init(void);
// target is interrupt accept side
void soft_serial_target_init(void);
-// initiator result
-#define TRANSACTION_END 0
-#define TRANSACTION_NO_RESPONSE 0x1
-#define TRANSACTION_DATA_ERROR 0x2
-#define TRANSACTION_TYPE_ERROR 0x4
-int soft_serial_transaction(int sstd_index);
-
-// target status
-// *SSTD_t.status has
-// initiator:
-// TRANSACTION_END
-// or TRANSACTION_NO_RESPONSE
-// or TRANSACTION_DATA_ERROR
-// target:
-// TRANSACTION_DATA_ERROR
-// or TRANSACTION_ACCEPTED
-#define TRANSACTION_ACCEPTED 0x8
-int soft_serial_get_and_clean_status(int sstd_index);
+bool soft_serial_transaction(int sstd_index);