mirror of
https://github.com/clockworkpi/PicoCalc.git
synced 2026-10-23 19:35:51 +02:00
1544 lines
42 KiB
Diff
1544 lines
42 KiB
Diff
diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
|
|
index 0fab3056a939..a53fb8d570a1 100644
|
|
--- a/arch/arm/boot/dts/overlays/Makefile
|
|
+++ b/arch/arm/boot/dts/overlays/Makefile
|
|
@@ -283,7 +283,9 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
|
|
waveshare-can-fd-hat-mode-a.dtbo \
|
|
waveshare-can-fd-hat-mode-b.dtbo \
|
|
wittypi.dtbo \
|
|
- wm8960-soundcard.dtbo
|
|
+ wm8960-soundcard.dtbo \
|
|
+ picocalc_kbd.dtbo \
|
|
+ mmc-spi1.dtbo
|
|
|
|
targets += dtbs dtbs_install
|
|
targets += $(dtbo-y)
|
|
diff --git a/arch/arm/boot/dts/overlays/mmc-spi1-overlay.dts b/arch/arm/boot/dts/overlays/mmc-spi1-overlay.dts
|
|
new file mode 100644
|
|
index 000000000000..8afcb8e813f0
|
|
--- /dev/null
|
|
+++ b/arch/arm/boot/dts/overlays/mmc-spi1-overlay.dts
|
|
@@ -0,0 +1,34 @@
|
|
+/dts-v1/;
|
|
+/plugin/;
|
|
+
|
|
+/ {
|
|
+ compatible = "brcm,bcm2835";
|
|
+
|
|
+ fragment@0 {
|
|
+ target = <&spidev0>;
|
|
+ __overlay__ {
|
|
+ status = "disabled";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@1 {
|
|
+ target = <&spi1>;
|
|
+ __overlay__ {
|
|
+ status = "okay";
|
|
+
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ sdslot@0 {
|
|
+ compatible = "mmc-spi-slot";
|
|
+ reg = <0>; // SPI CS0
|
|
+ voltage-ranges = <3300 3300>;
|
|
+ spi-max-frequency = <8000000>;
|
|
+ gpios = <&gpio 6 1>;
|
|
+ cd-gpios = <&gpio 6 1>;
|
|
+ /* interrupts-extended = <&gpio 6 8>; */
|
|
+
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
diff --git a/arch/arm/boot/dts/overlays/picocalc_kbd-overlay.dts b/arch/arm/boot/dts/overlays/picocalc_kbd-overlay.dts
|
|
new file mode 100644
|
|
index 000000000000..f85d1ce62662
|
|
--- /dev/null
|
|
+++ b/arch/arm/boot/dts/overlays/picocalc_kbd-overlay.dts
|
|
@@ -0,0 +1,19 @@
|
|
+/dts-v1/;
|
|
+/plugin/;
|
|
+
|
|
+/ {
|
|
+ compatible = "brcm,bcm2835";
|
|
+
|
|
+ fragment@0 {
|
|
+ target = <&i2c1>; // Use hardware I2C-1 (SDA=GPIO2, SCL=GPIO3)
|
|
+ __overlay__ {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ picocalckbd@1f {
|
|
+ compatible = "picocalc_kbd";
|
|
+ reg = <0x1f>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
|
|
index 00292118b79b..093cadeb3c67 100644
|
|
--- a/drivers/input/keyboard/Kconfig
|
|
+++ b/drivers/input/keyboard/Kconfig
|
|
@@ -828,4 +828,13 @@ config KEYBOARD_CYPRESS_SF
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called cypress-sf.
|
|
|
|
+config KEYBOARD_PICOCALC
|
|
+ tristate "PicoCalc i2c keyboard support"
|
|
+ depends on I2C
|
|
+ help
|
|
+ Say Y here if you want to enable support for PicoCalc i2c keyboard
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called picocalc_kbd.
|
|
+
|
|
endif
|
|
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
|
|
index 5f67196bb2c1..e5a9f93d7e4d 100644
|
|
--- a/drivers/input/keyboard/Makefile
|
|
+++ b/drivers/input/keyboard/Makefile
|
|
@@ -73,3 +73,4 @@ obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o
|
|
obj-$(CONFIG_KEYBOARD_TM2_TOUCHKEY) += tm2-touchkey.o
|
|
obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o
|
|
obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o
|
|
+obj-$(CONFIG_KEYBOARD_PICOCALC) += picocalc_kbd.o
|
|
diff --git a/drivers/input/keyboard/debug_levels.h b/drivers/input/keyboard/debug_levels.h
|
|
new file mode 100644
|
|
index 000000000000..9407e82f2b47
|
|
--- /dev/null
|
|
+++ b/drivers/input/keyboard/debug_levels.h
|
|
@@ -0,0 +1,44 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer.
|
|
+ * debug_levels.h: Describes the debug levels that can be set for viewing code flow in dmesg.
|
|
+ * Use dmesg -wH | grep beepy-kbd for easy viewing.
|
|
+ */
|
|
+
|
|
+#ifndef DEBUG_LEVELS_H_
|
|
+#define DEBUG_LEVELS_H_
|
|
+
|
|
+/*
|
|
+ * Set Debug Level with DEBUG_LEVEL.
|
|
+ * Types of Debug Messages:
|
|
+ * 1. Functional Entries. printk() & dev_info() used to show Entries in init & exit, probe, irq, logic.
|
|
+ * 2. Read/Write Values. dev_info() used to show Values read and written from i2c functions.
|
|
+ * 3. Logical Debugs. dev_info() used for showing Logic flow.
|
|
+ * For All Informations -> DEBUG_LEVEL set as (DEBUG_LEVEL_FE | DEBUG_LEVEL_RW | DEBUG_LEVEL_LD)
|
|
+ * For Only Read/Write in I2C Client -> DEBUG_LEVEL set as (DEBUG_LEVEL_RW)
|
|
+ * For Informations on function entires-> DEBUG_LEVEL set as (DEBUG_LEVEL_FE)
|
|
+ * For No Debug -> DEBUG_LEVEL set as (DEBUG_LEVEL_OFF)
|
|
+ */
|
|
+
|
|
+#define DEBUG_LEVEL_OFF 0
|
|
+#define DEBUG_LEVEL_FE 1
|
|
+#define DEBUG_LEVEL_RW 2
|
|
+#define DEBUG_LEVEL_LD 4
|
|
+
|
|
+#define DEBUG_LEVEL DEBUG_LEVEL_OFF
|
|
+// #define DEBUG_LEVEL DEBUG_LEVEL_FE
|
|
+// #define DEBUG_LEVEL (DEBUG_LEVEL_LD)
|
|
+// #define DEBUG_LEVEL (DEBUG_LEVEL_FE | DEBUG_LEVEL_RW | DEBUG_LEVEL_LD)
|
|
+#endif
|
|
+
|
|
+#if (DEBUG_LEVEL & DEBUG_LEVEL_FE)
|
|
+#define dev_info_fe(...) dev_info(__VA_ARGS__)
|
|
+#else
|
|
+#define dev_info_fe(...)
|
|
+#endif
|
|
+
|
|
+#if (DEBUG_LEVEL & DEBUG_LEVEL_LD)
|
|
+#define dev_info_ld(...) dev_info(__VA_ARGS__)
|
|
+#else
|
|
+#define dev_info_ld(...)
|
|
+#endif
|
|
diff --git a/drivers/input/keyboard/picocalc_kbd.c b/drivers/input/keyboard/picocalc_kbd.c
|
|
new file mode 100644
|
|
index 000000000000..1673370c31a8
|
|
--- /dev/null
|
|
+++ b/drivers/input/keyboard/picocalc_kbd.c
|
|
@@ -0,0 +1,1098 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Keyboard Driver for picocalc
|
|
+ */
|
|
+
|
|
+#include <linux/version.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/power_supply.h>
|
|
+#include <linux/notifier.h>
|
|
+#include <linux/reboot.h>
|
|
+#include <linux/backlight.h>
|
|
+
|
|
+#include "picocalc_kbd_code.h"
|
|
+
|
|
+//#include "config.h"
|
|
+#include "debug_levels.h"
|
|
+
|
|
+#define REG_ID_BAT (0x0b)
|
|
+#define REG_ID_BKL (0x05)
|
|
+#define REG_ID_FIF (0x09)
|
|
+#define REG_ID_BK2 (0x0A)
|
|
+#define REG_ID_OFF (0x0E)
|
|
+#define REG_ID_RST (0x08)
|
|
+
|
|
+#define PICOCALC_WRITE_MASK (1<<7)
|
|
+
|
|
+#define KBD_BUS_TYPE BUS_I2C
|
|
+#define KBD_VENDOR_ID 0x0001
|
|
+#define KBD_PRODUCT_ID 0x0001
|
|
+#define KBD_VERSION_ID 0x0001
|
|
+/*
|
|
+#include "input_iface.h"
|
|
+#include "params_iface.h"
|
|
+#include "sysfs_iface.h"
|
|
+*/
|
|
+
|
|
+#define KBD_FIFO_SIZE 31
|
|
+
|
|
+static uint32_t sysfs_gid_setting = 0; // GID of files in /sys/firmware/picocalc
|
|
+
|
|
+static uint64_t mouse_fast_move_thr_time = 150000000ull;
|
|
+static int8_t mouse_move_step = 1;
|
|
+
|
|
+// From keyboard firmware source
|
|
+enum pico_key_state
|
|
+{
|
|
+ KEY_STATE_IDLE = 0,
|
|
+ KEY_STATE_PRESSED = 1,
|
|
+ KEY_STATE_HOLD = 2,
|
|
+ KEY_STATE_RELEASED = 3,
|
|
+ KEY_STATE_LONG_HOLD = 4,
|
|
+};
|
|
+struct key_fifo_item
|
|
+{
|
|
+ uint8_t _ : 4;
|
|
+ enum pico_key_state state : 4;
|
|
+ uint8_t scancode;
|
|
+};
|
|
+
|
|
+#define MOUSE_MOVE_LEFT (1 << 1)
|
|
+#define MOUSE_MOVE_RIGHT (1 << 2)
|
|
+#define MOUSE_MOVE_UP (1 << 3)
|
|
+#define MOUSE_MOVE_DOWN (1 << 4)
|
|
+
|
|
+struct kbd_ctx
|
|
+{
|
|
+ struct work_struct work_struct;
|
|
+ uint8_t version_number;
|
|
+
|
|
+ struct i2c_client *i2c_client;
|
|
+ struct input_dev *input_dev;
|
|
+
|
|
+ // Map from input HID scancodes to Linux keycodes
|
|
+ uint8_t *keycode_map;
|
|
+
|
|
+ // Key state and touch FIFO queue
|
|
+ uint8_t key_fifo_count;
|
|
+ struct key_fifo_item key_fifo_data[KBD_FIFO_SIZE];
|
|
+ uint64_t last_keypress_at;
|
|
+
|
|
+ int mouse_mode;
|
|
+ uint8_t mouse_move_dir;
|
|
+
|
|
+ struct power_supply *pc_battery_psy;
|
|
+ uint8_t bat_percent;
|
|
+ uint8_t bat_status;
|
|
+ uint16_t queued;
|
|
+
|
|
+ struct backlight_device *bl_dev;
|
|
+};
|
|
+
|
|
+void pc_battery_update(struct kbd_ctx *ctx);
|
|
+int picocalc_battery_probe(void);
|
|
+
|
|
+// Parse 0 to 255 from string
|
|
+static inline int parse_u8(char const* buf)
|
|
+{
|
|
+ int rc, result;
|
|
+
|
|
+ // Parse string value
|
|
+ if ((rc = kstrtoint(buf, 10, &result)) || (result < 0) || (result > 0xff)) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+// Read a single uint8_t value from I2C register
|
|
+static inline int kbd_read_i2c_u8(struct i2c_client* i2c_client, uint8_t reg_addr,
|
|
+ uint8_t* dst)
|
|
+{
|
|
+ int reg_value;
|
|
+
|
|
+ // Read value over I2C
|
|
+ if ((reg_value = i2c_smbus_read_byte_data(i2c_client, reg_addr)) < 0) {
|
|
+ dev_err(&i2c_client->dev,
|
|
+ "%s Could not read from register 0x%02X, error: %d\n",
|
|
+ __func__, reg_addr, reg_value);
|
|
+ return reg_value;
|
|
+ }
|
|
+
|
|
+ // Assign result to buffer
|
|
+ *dst = reg_value & 0xFF;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+// Write a single uint8_t value to I2C register
|
|
+static inline int kbd_write_i2c_u8(struct i2c_client* i2c_client, uint8_t reg_addr,
|
|
+ uint8_t src)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ // Write value over I2C
|
|
+ if ((rc = i2c_smbus_write_byte_data(i2c_client,
|
|
+ reg_addr | PICOCALC_WRITE_MASK, src))) {
|
|
+
|
|
+ dev_err(&i2c_client->dev,
|
|
+ "%s Could not write to register 0x%02X, Error: %d\n",
|
|
+ __func__, reg_addr, rc);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+// Read a pair of uint8_t values from I2C register
|
|
+static inline int kbd_read_i2c_2u8(struct i2c_client* i2c_client, uint8_t reg_addr,
|
|
+ uint8_t* dst)
|
|
+{
|
|
+ int word_value;
|
|
+
|
|
+ // Read value over I2C
|
|
+ if ((word_value = i2c_smbus_read_word_data(i2c_client, reg_addr)) < 0) {
|
|
+ dev_err(&i2c_client->dev,
|
|
+ "%s Could not read from register 0x%02X, error: %d\n",
|
|
+ __func__, reg_addr, word_value);
|
|
+ return word_value;
|
|
+ }
|
|
+
|
|
+ // Assign result to buffer
|
|
+ *dst = (uint8_t)(word_value & 0xFF);
|
|
+ *(dst + 1) = (uint8_t)((word_value & 0xFF00) >> 8);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+// Shared global state for global interfaces such as sysfs
|
|
+struct kbd_ctx *g_ctx;
|
|
+
|
|
+void input_fw_read_fifo(struct kbd_ctx* ctx)
|
|
+{
|
|
+ uint8_t fifo_idx;
|
|
+ int rc;
|
|
+
|
|
+ // Read number of FIFO items
|
|
+ /*
|
|
+ if (kbd_read_i2c_u8(ctx->i2c_client, REG_KEY, &ctx->key_fifo_count)) {
|
|
+ return;
|
|
+ }
|
|
+ */
|
|
+ ctx->key_fifo_count = 0;
|
|
+
|
|
+ // Read and transfer all FIFO items
|
|
+
|
|
+ for (fifo_idx = 0; fifo_idx < KBD_FIFO_SIZE; fifo_idx++) {
|
|
+
|
|
+ uint8_t data[2];
|
|
+ // Read 2 fifo items
|
|
+ if ((rc = kbd_read_i2c_2u8(ctx->i2c_client, 0x09,
|
|
+ (uint8_t*)&data))) {
|
|
+
|
|
+ dev_err(&ctx->i2c_client->dev,
|
|
+ "%s Could not read REG_FIF, Error: %d\n", __func__, rc);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (data[0] == 0)
|
|
+ {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ ctx->key_fifo_data[fifo_idx]._ = 0;
|
|
+ ctx->key_fifo_data[fifo_idx].state = data[0];
|
|
+ ctx->key_fifo_data[fifo_idx].scancode= data[1];
|
|
+ ctx->key_fifo_count++;
|
|
+ // Advance FIFO position
|
|
+ dev_info_fe(&ctx->i2c_client->dev,
|
|
+ "%s %02d: 0x%02x%02x State %d Scancode %d\n",
|
|
+ __func__, fifo_idx,
|
|
+ ((uint8_t*)&ctx->key_fifo_data[fifo_idx])[0],
|
|
+ ((uint8_t*)&ctx->key_fifo_data[fifo_idx])[1],
|
|
+ ctx->key_fifo_data[fifo_idx].state,
|
|
+ ctx->key_fifo_data[fifo_idx].scancode);
|
|
+ /*
|
|
+ printk("%02d: 0x%02x%02x State %d Scancode %d\n",
|
|
+ fifo_idx,
|
|
+ ((uint8_t*)&ctx->key_fifo_data[fifo_idx])[0],
|
|
+ ((uint8_t*)&ctx->key_fifo_data[fifo_idx])[1],
|
|
+ ctx->key_fifo_data[fifo_idx].state,
|
|
+ ctx->key_fifo_data[fifo_idx].scancode);
|
|
+ */
|
|
+ }
|
|
+}
|
|
+
|
|
+static void key_report_event(struct kbd_ctx* ctx,
|
|
+ struct key_fifo_item const* ev)
|
|
+{
|
|
+ uint8_t keycode;
|
|
+
|
|
+ // Only handle key pressed, held, or released events
|
|
+ if ((ev->state != KEY_STATE_PRESSED) && (ev->state != KEY_STATE_RELEASED)
|
|
+ && (ev->state != KEY_STATE_HOLD)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* right shift */
|
|
+ if (ev->scancode == 0xA3)
|
|
+ {
|
|
+ if (ev->state == KEY_STATE_PRESSED)
|
|
+ {
|
|
+ ctx->mouse_mode = !ctx->mouse_mode;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if(ev->scancode == 0x91){
|
|
+ pr_info("%s get power off pressed",__func__);
|
|
+
|
|
+ input_report_key(ctx->input_dev, KEY_POWER,1);
|
|
+ input_sync(ctx->input_dev);
|
|
+ input_report_key(ctx->input_dev, KEY_POWER, 0);
|
|
+ input_sync(ctx->input_dev);
|
|
+ return;
|
|
+
|
|
+ }
|
|
+
|
|
+ if (ctx->mouse_mode)
|
|
+ {
|
|
+ switch(ev->scancode)
|
|
+ {
|
|
+ /* KEY_BACKSPACE */
|
|
+/*
|
|
+ case '\b':
|
|
+ if (ev->state == KEY_STATE_PRESSED)
|
|
+ {
|
|
+ input_report_abs(ctx->input_dev, ABS_X, 0);
|
|
+ input_report_abs(ctx->input_dev, ABS_Y, 0);
|
|
+ }
|
|
+ return;
|
|
+*/
|
|
+ /* KEY_RIGHT */
|
|
+ case 0xb7:
|
|
+ if (ev->state == KEY_STATE_PRESSED)
|
|
+ {
|
|
+ if (!(ctx->mouse_move_dir & MOUSE_MOVE_RIGHT))
|
|
+ ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+ ctx->mouse_move_dir |= MOUSE_MOVE_RIGHT;
|
|
+ }
|
|
+ else if (ev->state == KEY_STATE_RELEASED)
|
|
+ {
|
|
+ ctx->mouse_move_dir &= ~MOUSE_MOVE_RIGHT;
|
|
+ }
|
|
+ return;
|
|
+ /* KEY_LEFT */
|
|
+ case 0xb4:
|
|
+ if (ev->state == KEY_STATE_PRESSED)
|
|
+ {
|
|
+ if (!(ctx->mouse_move_dir & MOUSE_MOVE_LEFT))
|
|
+ ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+ ctx->mouse_move_dir |= MOUSE_MOVE_LEFT;
|
|
+ }
|
|
+ else if (ev->state == KEY_STATE_RELEASED)
|
|
+ {
|
|
+ ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+ ctx->mouse_move_dir &= ~MOUSE_MOVE_LEFT;
|
|
+ }
|
|
+ return;
|
|
+ /* KEY_DOWN */
|
|
+ case 0xb6:
|
|
+ if (ev->state == KEY_STATE_PRESSED)
|
|
+ {
|
|
+ if (!(ctx->mouse_move_dir & MOUSE_MOVE_DOWN))
|
|
+ ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+ ctx->mouse_move_dir |= MOUSE_MOVE_DOWN;
|
|
+ }
|
|
+ else if (ev->state == KEY_STATE_RELEASED)
|
|
+ {
|
|
+ ctx->mouse_move_dir &= ~MOUSE_MOVE_DOWN;
|
|
+ }
|
|
+ return;
|
|
+ /* KEY_UP */
|
|
+ case 0xb5:
|
|
+ if (ev->state == KEY_STATE_PRESSED)
|
|
+ {
|
|
+ if (!(ctx->mouse_move_dir & MOUSE_MOVE_UP))
|
|
+ ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+ ctx->mouse_move_dir |= MOUSE_MOVE_UP;
|
|
+ }
|
|
+ else if (ev->state == KEY_STATE_RELEASED)
|
|
+ {
|
|
+ ctx->mouse_move_dir &= ~MOUSE_MOVE_UP;
|
|
+ }
|
|
+ return;
|
|
+ /* KEY_RIGHTBRACE */
|
|
+ case ']':
|
|
+ input_report_key(ctx->input_dev, BTN_LEFT, ev->state == KEY_STATE_PRESSED);
|
|
+ return;
|
|
+ /* KEY_LEFTBRACE */
|
|
+ case '[':
|
|
+ input_report_key(ctx->input_dev, BTN_RIGHT, ev->state == KEY_STATE_PRESSED);
|
|
+ return;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Post key scan event
|
|
+ input_event(ctx->input_dev, EV_MSC, MSC_SCAN, ev->scancode);
|
|
+
|
|
+ // Map input scancode to Linux input keycode
|
|
+
|
|
+ keycode = keycodes[ev->scancode];
|
|
+
|
|
+ //keycode = ev->scancode;
|
|
+ dev_info_fe(&ctx->i2c_client->dev,
|
|
+ "%s state %d, scancode %d mapped to keycode %d\n",
|
|
+ __func__, ev->state, ev->scancode, keycode);
|
|
+ //printk("state %d, scancode %d mapped to keycode %d\n",
|
|
+ // ev->state, ev->scancode, keycode);
|
|
+
|
|
+ // Scancode mapped to ignored keycode
|
|
+ if (keycode == 0) {
|
|
+ return;
|
|
+
|
|
+ // Scancode converted to keycode not in map
|
|
+ } else if (keycode == KEY_UNKNOWN) {
|
|
+ dev_warn(&ctx->i2c_client->dev,
|
|
+ "%s Could not get Keycode for Scancode: [0x%02X]\n",
|
|
+ __func__, ev->scancode);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Update last keypress time
|
|
+ g_ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+
|
|
+/*
|
|
+ if (keycode == KEY_STOP) {
|
|
+
|
|
+ // Pressing power button sends Tmux prefix (Control + code 171 in keymap)
|
|
+ if (ev->state == KEY_STATE_PRESSED) {
|
|
+ input_report_key(ctx->input_dev, KEY_LEFTCTRL, TRUE);
|
|
+ input_report_key(ctx->input_dev, 171, TRUE);
|
|
+ input_report_key(ctx->input_dev, 171, FALSE);
|
|
+ input_report_key(ctx->input_dev, KEY_LEFTCTRL, FALSE);
|
|
+
|
|
+ // Short hold power buttion opens Tmux menu (Control + code 174 in keymap)
|
|
+ } else if (ev->state == KEY_STATE_HOLD) {
|
|
+ input_report_key(ctx->input_dev, KEY_LEFTCTRL, TRUE);
|
|
+ input_report_key(ctx->input_dev, 174, TRUE);
|
|
+ input_report_key(ctx->input_dev, 174, FALSE);
|
|
+ input_report_key(ctx->input_dev, KEY_LEFTCTRL, FALSE);
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // Subsystem key handling
|
|
+ /*
|
|
+ if (input_fw_consumes_keycode(ctx, &keycode, keycode, ev->state)
|
|
+ || input_touch_consumes_keycode(ctx, &keycode, keycode, ev->state)
|
|
+ || input_modifiers_consumes_keycode(ctx, &keycode, keycode, ev->state)
|
|
+ || input_meta_consumes_keycode(ctx, &keycode, keycode, ev->state)) {
|
|
+ return;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // Ignore hold keys at this point
|
|
+ if (ev->state == KEY_STATE_HOLD) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+/*
|
|
+ // Apply pending sticky modifiers
|
|
+ keycode = input_modifiers_apply_pending(ctx, keycode);
|
|
+*/
|
|
+
|
|
+ // Report key to input system
|
|
+ input_report_key(ctx->input_dev, keycode, ev->state == KEY_STATE_PRESSED);
|
|
+
|
|
+ // Reset sticky modifiers
|
|
+// input_modifiers_reset(ctx);
|
|
+}
|
|
+
|
|
+static void input_workqueue_handler(struct work_struct *work_struct_ptr)
|
|
+{
|
|
+ struct kbd_ctx *ctx;
|
|
+ uint8_t fifo_idx;
|
|
+
|
|
+ // Get keyboard context from work struct
|
|
+ ctx = container_of(work_struct_ptr, struct kbd_ctx, work_struct);
|
|
+
|
|
+ input_fw_read_fifo(ctx);
|
|
+
|
|
+ if(ctx->queued == 0) {
|
|
+ ctx->queued++;
|
|
+ pc_battery_update(ctx);
|
|
+ }else {
|
|
+ if(ctx->queued > 255) {
|
|
+ ctx->queued = 0;
|
|
+ }else{
|
|
+ ctx->queued++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ // Process FIFO items
|
|
+ for (fifo_idx = 0; fifo_idx < ctx->key_fifo_count; fifo_idx++) {
|
|
+ key_report_event(ctx, &ctx->key_fifo_data[fifo_idx]);
|
|
+ }
|
|
+
|
|
+ if (ctx->mouse_mode)
|
|
+ {
|
|
+ uint64_t press_time = ktime_get_boottime_ns() - ctx->last_keypress_at;
|
|
+ if (press_time <= mouse_fast_move_thr_time)
|
|
+ {
|
|
+ mouse_move_step = 1;
|
|
+ }
|
|
+ else if (press_time <= 3 * mouse_fast_move_thr_time)
|
|
+ {
|
|
+ mouse_move_step = 2;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ mouse_move_step = 4;
|
|
+ }
|
|
+
|
|
+ if (ctx->mouse_move_dir & MOUSE_MOVE_LEFT)
|
|
+ {
|
|
+ input_report_rel(ctx->input_dev, REL_X, -mouse_move_step);
|
|
+ }
|
|
+ if (ctx->mouse_move_dir & MOUSE_MOVE_RIGHT)
|
|
+ {
|
|
+ input_report_rel(ctx->input_dev, REL_X, mouse_move_step);
|
|
+ }
|
|
+ if (ctx->mouse_move_dir & MOUSE_MOVE_DOWN)
|
|
+ {
|
|
+ input_report_rel(ctx->input_dev, REL_Y, mouse_move_step);
|
|
+ }
|
|
+ if (ctx->mouse_move_dir & MOUSE_MOVE_UP)
|
|
+ {
|
|
+ input_report_rel(ctx->input_dev, REL_Y, -mouse_move_step);
|
|
+ }
|
|
+ }
|
|
+
|
|
+
|
|
+ // Reset pending FIFO count
|
|
+ ctx->key_fifo_count = 0;
|
|
+
|
|
+ // Synchronize input system and clear client interrupt flag
|
|
+ input_sync(ctx->input_dev);
|
|
+ /*
|
|
+ if (kbd_write_i2c_u8(ctx->i2c_client, REG_INT, 0)) {
|
|
+ return;
|
|
+ }
|
|
+ */
|
|
+
|
|
+}
|
|
+static void kbd_timer_function(struct timer_list *data);
|
|
+DEFINE_TIMER(g_kbd_timer,kbd_timer_function);
|
|
+
|
|
+static void kbd_timer_function(struct timer_list *data)
|
|
+{
|
|
+ data = NULL;
|
|
+ schedule_work(&g_ctx->work_struct);
|
|
+ mod_timer(&g_kbd_timer, jiffies + HZ / 128);
|
|
+}
|
|
+
|
|
+int input_probe(struct i2c_client* i2c_client)
|
|
+{
|
|
+ int rc, i;
|
|
+
|
|
+ // Allocate keyboard context (managed by device lifetime)
|
|
+ g_ctx = devm_kzalloc(&i2c_client->dev, sizeof(*g_ctx), GFP_KERNEL);
|
|
+ if (!g_ctx) {
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ // Allocate and copy keycode array
|
|
+ g_ctx->keycode_map = devm_kmemdup(&i2c_client->dev, keycodes, NUM_KEYCODES,
|
|
+ GFP_KERNEL);
|
|
+ if (!g_ctx->keycode_map) {
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ // Initialize keyboard context
|
|
+ g_ctx->i2c_client = i2c_client;
|
|
+ g_ctx->last_keypress_at = ktime_get_boottime_ns();
|
|
+
|
|
+ // Run subsystem probes
|
|
+ /*
|
|
+ if ((rc = input_fw_probe(i2c_client, g_ctx))) {
|
|
+ dev_err(&i2c_client->dev, "picocalc_kbd: input_fw_probe failed\n");
|
|
+ return rc;
|
|
+ }
|
|
+ if ((rc = input_rtc_probe(i2c_client, g_ctx))) {
|
|
+ dev_err(&i2c_client->dev, "picocalc_kbd: input_rtc_probe failed\n");
|
|
+ return rc;
|
|
+ }
|
|
+ if ((rc = input_display_probe(i2c_client, g_ctx))) {
|
|
+ dev_err(&i2c_client->dev, "picocalc_kbd: input_display_probe failed\n");
|
|
+ return rc;
|
|
+ }
|
|
+ if ((rc = input_modifiers_probe(i2c_client, g_ctx))) {
|
|
+ dev_err(&i2c_client->dev, "picocalc_kbd: input_modifiers_probe failed\n");
|
|
+ return rc;
|
|
+ }
|
|
+ if ((rc = input_touch_probe(i2c_client, g_ctx))) {
|
|
+ dev_err(&i2c_client->dev, "picocalc_kbd: input_touch_probe failed\n");
|
|
+ return rc;
|
|
+ }
|
|
+ if ((rc = input_meta_probe(i2c_client, g_ctx))) {
|
|
+ dev_err(&i2c_client->dev, "picocalc_kbd: input_meta_probe failed\n");
|
|
+ return rc;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // Allocate input device
|
|
+ if ((g_ctx->input_dev = devm_input_allocate_device(&i2c_client->dev)) == NULL) {
|
|
+ dev_err(&i2c_client->dev,
|
|
+ "%s Could not devm_input_allocate_device BBQX0KBD.\n", __func__);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ // Initialize input device
|
|
+ g_ctx->input_dev->name = i2c_client->name;
|
|
+ g_ctx->input_dev->id.bustype = KBD_BUS_TYPE;
|
|
+ g_ctx->input_dev->id.vendor = KBD_VENDOR_ID;
|
|
+ g_ctx->input_dev->id.product = KBD_PRODUCT_ID;
|
|
+ g_ctx->input_dev->id.version = KBD_VERSION_ID;
|
|
+
|
|
+ // Initialize input device keycodes
|
|
+ g_ctx->input_dev->keycode = keycodes; //g_ctx->keycode_map;
|
|
+ g_ctx->input_dev->keycodesize = sizeof(keycodes[0]);
|
|
+ g_ctx->input_dev->keycodemax = ARRAY_SIZE(keycodes);
|
|
+
|
|
+ // Set input device keycode bits
|
|
+ for (i = 0; i < NUM_KEYCODES; i++) {
|
|
+ __set_bit(keycodes[i], g_ctx->input_dev->keybit);
|
|
+ }
|
|
+ __clear_bit(KEY_RESERVED, g_ctx->input_dev->keybit);
|
|
+ __set_bit(EV_REP, g_ctx->input_dev->evbit);
|
|
+ __set_bit(EV_KEY, g_ctx->input_dev->evbit);
|
|
+
|
|
+ // Set input device capabilities
|
|
+ input_set_capability(g_ctx->input_dev, EV_MSC, MSC_SCAN);
|
|
+ input_set_capability(g_ctx->input_dev, EV_REL, REL_X);
|
|
+ input_set_capability(g_ctx->input_dev, EV_REL, REL_Y);
|
|
+/*
|
|
+ input_set_capability(g_ctx->input_dev, EV_ABS, ABS_X);
|
|
+ input_set_capability(g_ctx->input_dev, EV_ABS, ABS_Y);
|
|
+ input_set_abs_params(g_ctx->input_dev, ABS_X, 0, 320, 4, 8);
|
|
+ input_set_abs_params(g_ctx->input_dev, ABS_Y, 0, 320, 4, 8);
|
|
+*/
|
|
+ input_set_capability(g_ctx->input_dev, EV_KEY, BTN_LEFT);
|
|
+ input_set_capability(g_ctx->input_dev, EV_KEY, BTN_RIGHT);
|
|
+
|
|
+ // Request IRQ handler for I2C client and initialize workqueue
|
|
+ /*
|
|
+ if ((rc = devm_request_threaded_irq(&i2c_client->dev,
|
|
+ i2c_client->irq, NULL, input_irq_handler, IRQF_SHARED | IRQF_ONESHOT,
|
|
+ i2c_client->name, g_ctx))) {
|
|
+
|
|
+ dev_err(&i2c_client->dev,
|
|
+ "Could not claim IRQ %d; error %d\n", i2c_client->irq, rc);
|
|
+ return rc;
|
|
+ }
|
|
+ */
|
|
+ g_ctx->mouse_mode = FALSE;
|
|
+ g_ctx->mouse_move_dir = 0;
|
|
+
|
|
+
|
|
+ picocalc_battery_probe();
|
|
+ g_ctx->bat_percent = 0;
|
|
+ g_ctx->bat_status = 0;
|
|
+ g_ctx->queued = 0;
|
|
+
|
|
+ INIT_WORK(&g_ctx->work_struct, input_workqueue_handler);
|
|
+ g_kbd_timer.expires = jiffies + HZ / 128;
|
|
+ add_timer(&g_kbd_timer);
|
|
+
|
|
+ // Register input device with input subsystem
|
|
+ dev_info(&i2c_client->dev,
|
|
+ "%s registering input device", __func__);
|
|
+ if ((rc = input_register_device(g_ctx->input_dev))) {
|
|
+ dev_err(&i2c_client->dev,
|
|
+ "Failed to register input device, error: %d\n", rc);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void input_shutdown(struct i2c_client* i2c_client)
|
|
+{
|
|
+ // Run subsystem shutdowns
|
|
+ /*
|
|
+ input_meta_shutdown(i2c_client, g_ctx);
|
|
+ input_touch_shutdown(i2c_client, g_ctx);
|
|
+ input_modifiers_shutdown(i2c_client, g_ctx);
|
|
+ input_display_shutdown(i2c_client, g_ctx);
|
|
+ input_rtc_shutdown(i2c_client, g_ctx);
|
|
+ input_fw_shutdown(i2c_client, g_ctx);
|
|
+ */
|
|
+
|
|
+ // Remove context from global state
|
|
+ // (It is freed by the device-specific memory mananger)
|
|
+ del_timer(&g_kbd_timer);
|
|
+ g_ctx = NULL;
|
|
+}
|
|
+
|
|
+uint32_t params_get_sysfs_gid(void)
|
|
+{
|
|
+ return sysfs_gid_setting;
|
|
+}
|
|
+
|
|
+// Read battery percent over I2C
|
|
+static int read_battery_percent(void)
|
|
+{
|
|
+ int rc;
|
|
+ uint8_t percent[2];
|
|
+
|
|
+ // Make sure I2C client was initialized
|
|
+ if ((g_ctx == NULL) || (g_ctx->i2c_client == NULL)) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ // Read battery level
|
|
+ if ((rc = kbd_read_i2c_2u8(g_ctx->i2c_client, REG_ID_BAT, percent)) < 0) {
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ g_ctx->bat_status = (percent[1] >> 7) & 0x01;
|
|
+ percent[1] &= ~(1<<7);
|
|
+ g_ctx->bat_percent = percent[1];
|
|
+
|
|
+ //pr_info("%d picoclac_kbd battery.\n", percent[1]);
|
|
+ // Calculate raw battery level
|
|
+ return percent[1];
|
|
+}
|
|
+
|
|
+static int parse_and_write_i2c_u8(char const* buf, size_t count, uint8_t reg)
|
|
+{
|
|
+ int parsed;
|
|
+
|
|
+ // Parse string entry
|
|
+ if ((parsed = parse_u8(buf)) < 0) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ // Write value to LED register if available
|
|
+ if (g_ctx && g_ctx->i2c_client) {
|
|
+ kbd_write_i2c_u8(g_ctx->i2c_client, reg, (uint8_t)parsed);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+// Sysfs entries
|
|
+
|
|
+// Battery percent approximate
|
|
+static ssize_t battery_percent_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ int percent;
|
|
+
|
|
+ if ((percent = read_battery_percent()) < 0) {
|
|
+ return percent;
|
|
+ }
|
|
+
|
|
+ // Format into buffer
|
|
+ return sprintf(buf, "%d\n", percent);
|
|
+}
|
|
+struct kobj_attribute battery_percent_attr
|
|
+ = __ATTR(battery_percent, 0444, battery_percent_show, NULL);
|
|
+
|
|
+// Keyboard backlight value
|
|
+static ssize_t __used keyboard_backlight_store(struct kobject *kobj,
|
|
+ struct kobj_attribute *attr, char const *buf, size_t count)
|
|
+{
|
|
+ return parse_and_write_i2c_u8(buf, count, REG_ID_BK2);
|
|
+}
|
|
+struct kobj_attribute keyboard_backlight_attr
|
|
+ = __ATTR(keyboard_backlight, 0220, NULL, keyboard_backlight_store);
|
|
+
|
|
+// screen backlight value
|
|
+static ssize_t __used screen_backlight_store(struct kobject *kobj,
|
|
+ struct kobj_attribute *attr, char const *buf, size_t count)
|
|
+{
|
|
+ return parse_and_write_i2c_u8(buf, count, REG_ID_BKL);
|
|
+}
|
|
+struct kobj_attribute screen_backlight_attr
|
|
+ = __ATTR(screen_backlight, 0220, NULL, screen_backlight_store);
|
|
+
|
|
+// Time since last keypress in milliseconds
|
|
+static ssize_t last_keypress_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ uint64_t last_keypress_ms;
|
|
+
|
|
+ if (g_ctx) {
|
|
+
|
|
+ // Get time in ns
|
|
+ last_keypress_ms = ktime_get_boottime_ns();
|
|
+ if (g_ctx->last_keypress_at < last_keypress_ms) {
|
|
+ last_keypress_ms -= g_ctx->last_keypress_at;
|
|
+
|
|
+ // Calculate time in milliseconds
|
|
+ last_keypress_ms = div_u64(last_keypress_ms, 1000000);
|
|
+
|
|
+ // Format into buffer
|
|
+ return sprintf(buf, "%lld\n", last_keypress_ms);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return sprintf(buf, "-1\n");
|
|
+}
|
|
+struct kobj_attribute last_keypress_attr
|
|
+ = __ATTR(last_keypress, 0444, last_keypress_show, NULL);
|
|
+
|
|
+// Sysfs attributes (entries)
|
|
+struct kobject *picocalc_kobj = NULL;
|
|
+static struct attribute *picocalc_attrs[] = {
|
|
+ &battery_percent_attr.attr,
|
|
+ &screen_backlight_attr.attr,
|
|
+ &last_keypress_attr.attr,
|
|
+ &keyboard_backlight_attr.attr,
|
|
+ NULL,
|
|
+};
|
|
+static struct attribute_group picocalc_attr_group = {
|
|
+ .attrs = picocalc_attrs
|
|
+};
|
|
+
|
|
+static void picocalc_get_ownership
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
+(struct kobject *kobj, kuid_t *uid, kgid_t *gid)
|
|
+#else
|
|
+(struct kobject const *kobj, kuid_t *uid, kgid_t *gid)
|
|
+#endif
|
|
+{
|
|
+ if (gid != NULL) {
|
|
+ gid->val = params_get_sysfs_gid();
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct kobj_type picocalc_ktype = {
|
|
+ .get_ownership = picocalc_get_ownership,
|
|
+ .sysfs_ops = &kobj_sysfs_ops
|
|
+};
|
|
+
|
|
+int sysfs_probe(struct i2c_client* i2c_client)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ // Allocate custom sysfs type
|
|
+ if ((picocalc_kobj = devm_kzalloc(&i2c_client->dev, sizeof(*picocalc_kobj), GFP_KERNEL)) == NULL) {
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ // Create sysfs entries for picocalc with custom type
|
|
+ rc = kobject_init_and_add(picocalc_kobj, &picocalc_ktype, firmware_kobj, "picocalc");
|
|
+ if (rc < 0) {
|
|
+ kobject_put(picocalc_kobj);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ // Create sysfs attributes
|
|
+ if (sysfs_create_group(picocalc_kobj, &picocalc_attr_group)) {
|
|
+ kobject_put(picocalc_kobj);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void sysfs_shutdown(struct i2c_client* i2c_client)
|
|
+{
|
|
+ // Remove sysfs entry
|
|
+ if (picocalc_kobj) {
|
|
+ kobject_put(picocalc_kobj);
|
|
+ picocalc_kobj = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int picocalc_kbd_probe
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
+(struct i2c_client* i2c_client, struct i2c_device_id const* i2c_id)
|
|
+#else
|
|
+(struct i2c_client* i2c_client)
|
|
+#endif
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ // Initialize key handler system
|
|
+ if ((rc = input_probe(i2c_client))) {
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ // Initialize module parameters
|
|
+ /*
|
|
+ if ((rc = params_probe())) {
|
|
+ return rc;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ // Initialize sysfs interface
|
|
+ if ((rc = sysfs_probe(i2c_client))) {
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void picocalc_kbd_shutdown(struct i2c_client* i2c_client)
|
|
+{
|
|
+ sysfs_shutdown(i2c_client);
|
|
+// params_shutdown();
|
|
+ input_shutdown(i2c_client);
|
|
+}
|
|
+
|
|
+static void picocalc_kbd_remove(struct i2c_client* i2c_client)
|
|
+{
|
|
+ dev_info_fe(&i2c_client->dev,
|
|
+ "%s Removing picocalc-kbd.\n", __func__);
|
|
+
|
|
+ picocalc_kbd_shutdown(i2c_client);
|
|
+}
|
|
+
|
|
+// Driver definitions
|
|
+
|
|
+// Device IDs
|
|
+static const struct i2c_device_id picocalc_kbd_i2c_device_id[] = {
|
|
+ { "picocalc_kbd", 0, },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(i2c, picocalc_kbd_i2c_device_id);
|
|
+static const struct of_device_id picocalc_kbd_of_device_id[] = {
|
|
+ { .compatible = "picocalc_kbd", },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, picocalc_kbd_of_device_id);
|
|
+
|
|
+// Callbacks
|
|
+static struct i2c_driver picocalc_kbd_driver = {
|
|
+ .driver = {
|
|
+ .name = "picocalc_kbd",
|
|
+ .of_match_table = picocalc_kbd_of_device_id,
|
|
+ },
|
|
+ .probe = picocalc_kbd_probe,
|
|
+ .shutdown = picocalc_kbd_shutdown,
|
|
+ .remove = picocalc_kbd_remove,
|
|
+ .id_table = picocalc_kbd_i2c_device_id,
|
|
+};
|
|
+
|
|
+////---------------------------------------------Battery
|
|
+#define BATTERY_FULL_DESIGN 4000000 // μWh
|
|
+
|
|
+static enum power_supply_property pc_battery_props[] = {
|
|
+ POWER_SUPPLY_PROP_PRESENT,
|
|
+ POWER_SUPPLY_PROP_CAPACITY,
|
|
+ POWER_SUPPLY_PROP_STATUS,
|
|
+ POWER_SUPPLY_PROP_TYPE,
|
|
+ POWER_SUPPLY_PROP_ENERGY_NOW,
|
|
+ POWER_SUPPLY_PROP_ENERGY_FULL,
|
|
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
|
|
+};
|
|
+
|
|
+void pc_battery_update(struct kbd_ctx *ctx) {
|
|
+ read_battery_percent();
|
|
+ power_supply_changed(ctx->pc_battery_psy);
|
|
+}
|
|
+
|
|
+static int pc_battery_get_property(struct power_supply *psy,
|
|
+ enum power_supply_property psp,
|
|
+ union power_supply_propval *val)
|
|
+{
|
|
+
|
|
+
|
|
+ switch (psp) {
|
|
+ case POWER_SUPPLY_PROP_PRESENT:
|
|
+ val->intval = 1;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_CAPACITY:
|
|
+ val->intval = g_ctx->bat_percent;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_STATUS:
|
|
+ if(g_ctx->bat_status) {
|
|
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
+ }else{
|
|
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
+ }
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TYPE:
|
|
+ val->intval = POWER_SUPPLY_TYPE_BATTERY;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
|
|
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
|
|
+ val->intval = BATTERY_FULL_DESIGN;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
|
|
+ val->intval = (g_ctx->bat_percent * BATTERY_FULL_DESIGN) / 100;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return 0;
|
|
+
|
|
+
|
|
+
|
|
+}
|
|
+
|
|
+static const struct power_supply_desc my_battery_desc = {
|
|
+ .name = "BAT0",
|
|
+ .type = POWER_SUPPLY_TYPE_BATTERY,
|
|
+ .properties = pc_battery_props,
|
|
+ .num_properties = ARRAY_SIZE(pc_battery_props),
|
|
+ .get_property = pc_battery_get_property,
|
|
+};
|
|
+
|
|
+
|
|
+int picocalc_battery_probe(void)
|
|
+{
|
|
+ struct power_supply_config psy_cfg = {};
|
|
+ g_ctx->pc_battery_psy = power_supply_register(NULL, &my_battery_desc, &psy_cfg);
|
|
+ return PTR_ERR_OR_ZERO(g_ctx->pc_battery_psy);
|
|
+}
|
|
+
|
|
+
|
|
+static int shutdown_notify(struct notifier_block *nb, unsigned long action, void *data)
|
|
+{
|
|
+
|
|
+ int rc;
|
|
+ if (action == SYS_POWER_OFF || action == SYS_HALT ) {
|
|
+ pr_info("picocalc_kbd: sending shutdown command to keyboard\n");
|
|
+
|
|
+ if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_ID_OFF,0x07)) < 0) {
|
|
+ pr_err("Failed to send shutdown cmd to picocalc kbd: %d\n", rc);
|
|
+ return rc;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (action == SYS_RESTART){
|
|
+ pr_info("picocalc_kbd: sending reset command to keyboard\n");
|
|
+ if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_ID_RST,0x01)) < 0) {
|
|
+ pr_err("Failed to send reset cmd to picocalc kbd: %d\n", rc);
|
|
+ return rc;
|
|
+ }
|
|
+ }
|
|
+ return NOTIFY_DONE;
|
|
+}
|
|
+
|
|
+
|
|
+static struct notifier_block shutdown_notifier = {
|
|
+ .notifier_call = shutdown_notify,
|
|
+ .priority = 0,
|
|
+};
|
|
+
|
|
+////////////////////////////////////////////////////////////////////////Screen backlight
|
|
+
|
|
+
|
|
+static int pcbl_update_status(struct backlight_device *bl)
|
|
+{
|
|
+ struct backlight_properties *props = &bl->props;
|
|
+ int brightness = props->brightness;
|
|
+ u8 val = brightness; // 0~255 brightness
|
|
+ int ret;
|
|
+ int power = max(props->power, props->fb_blank);
|
|
+ if(power){
|
|
+ val = 0;
|
|
+ }
|
|
+
|
|
+ if ((ret = kbd_write_i2c_u8(g_ctx->i2c_client, REG_ID_BKL,val)) < 0) {
|
|
+ pr_err("Failed to update picocalc lcd backlight: %d\n", ret);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int pcbl_get_brightness(struct backlight_device *bl)
|
|
+{
|
|
+ uint8_t data[2];
|
|
+ int rc;
|
|
+
|
|
+ if ((rc = kbd_read_i2c_2u8(g_ctx->i2c_client, REG_ID_BKL,
|
|
+ (uint8_t*)&data))) {
|
|
+ dev_err(&g_ctx->i2c_client->dev,
|
|
+ "%s Could not read REG_ID_BKL, Error: %d\n", __func__, rc);
|
|
+
|
|
+ }
|
|
+
|
|
+ return data[1];
|
|
+}
|
|
+
|
|
+static const struct backlight_ops pcbl_ops = {
|
|
+ .update_status = pcbl_update_status,
|
|
+ .get_brightness = pcbl_get_brightness,
|
|
+};
|
|
+
|
|
+static int pcbl_probe(void)
|
|
+{
|
|
+ struct backlight_properties props;
|
|
+ struct backlight_device *bl;
|
|
+ struct i2c_client *client;
|
|
+
|
|
+ client = g_ctx->i2c_client;
|
|
+
|
|
+ memset(&props, 0, sizeof(props));
|
|
+ props.type = BACKLIGHT_RAW;
|
|
+ props.max_brightness = 255;
|
|
+
|
|
+ bl = devm_backlight_device_register(&client->dev, "picocalc_lcd_backlight", &client->dev, g_ctx, &pcbl_ops, &props);
|
|
+ if (IS_ERR(bl))
|
|
+ return PTR_ERR(bl);
|
|
+
|
|
+ bl->props.brightness = 128;
|
|
+ bl->props.power = FB_BLANK_UNBLANK;
|
|
+
|
|
+ backlight_update_status(bl);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+// Module constructor
|
|
+static int __init picocalc_kbd_init(void)
|
|
+{
|
|
+ int rc;
|
|
+
|
|
+ // Adding the I2C driver will call the _probe function to continue setup
|
|
+ if ((rc = i2c_add_driver(&picocalc_kbd_driver))) {
|
|
+ pr_err("%s Could not initialise picocalc-kbd! Error: %d\n",
|
|
+ __func__, rc);
|
|
+ return rc;
|
|
+ }
|
|
+ pr_info("%s Initalised picoclac_kbd.\n", __func__);
|
|
+
|
|
+ register_reboot_notifier(&shutdown_notifier);
|
|
+
|
|
+ pr_info("%s Initalised picoclac_kbd shutdown_notifier.\n", __func__);
|
|
+
|
|
+ pcbl_probe();
|
|
+
|
|
+ pr_info("%s Initalised picoclac_kbd lcd backlight.\n", __func__);
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+module_init(picocalc_kbd_init);
|
|
+
|
|
+// Module destructor
|
|
+static void __exit picocalc_kbd_exit(void)
|
|
+{
|
|
+ pr_info("%s Exiting picocalc_kbd.\n", __func__);
|
|
+ i2c_del_driver(&picocalc_kbd_driver);
|
|
+
|
|
+ power_supply_unregister(g_ctx->pc_battery_psy);
|
|
+ unregister_reboot_notifier(&shutdown_notifier);
|
|
+
|
|
+}
|
|
+module_exit(picocalc_kbd_exit);
|
|
+
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_AUTHOR("hiro <hiro@hiro.com>");
|
|
+MODULE_DESCRIPTION("keyboard driver for picocalc");
|
|
+MODULE_VERSION("0.01");
|
|
diff --git a/drivers/input/keyboard/picocalc_kbd_code.h b/drivers/input/keyboard/picocalc_kbd_code.h
|
|
new file mode 100644
|
|
index 000000000000..25569b524c9f
|
|
--- /dev/null
|
|
+++ b/drivers/input/keyboard/picocalc_kbd_code.h
|
|
@@ -0,0 +1,208 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer.
|
|
+ * bbq10kbd_codes.h: Keyboard Layout and Scancodes-Keycodes mapping.
|
|
+ */
|
|
+
|
|
+#ifndef BBQ10KBD_PMOD_CODES_H_
|
|
+#define BBQ10KBD_PMOD_CODES_H_
|
|
+
|
|
+/*
|
|
+ * BBQ10KBD PMOD KEYBOARD LAYOUT
|
|
+ *
|
|
+ * +------+-----+----+----+----+----+----+-----+-----+-------+
|
|
+ * |# |1 |2 |3 |( | )|_ | -| +| @|
|
|
+ * | Q | W | E | R | T | Y | U | I | O | P |
|
|
+ * | S+| S-|PgUp|PgDn| \|UP |^ |= |{ |} |
|
|
+ * +------+-----+----+----+----+----+----+-----+-----+-------+
|
|
+ * |* |4 |5 |6 |/ | :|; | '| "| ESC|
|
|
+ * | A | S | D | F | G | H | J | K | L | BKSP |
|
|
+ * | ?| Sx| [| ]|LEFT|HOME|RGHT|V+ |V- |DLT |
|
|
+ * +------+-----+----+----+----+----+----+-----+-----+-------+
|
|
+ * | |7 |8 |9 |? | !|, | .| `| |
|
|
+ * |LFTALT| Z | X | C | V | B | N | M | $ | ENTER |
|
|
+ * | | K+| K-| °| <|DOWN|> |MENU |Vx | |
|
|
+ * +------+-----+----+----+----+----+----+-----+-----+-------+
|
|
+ * | |0 |TAO | | |
|
|
+ * | LEFT_SHIFT | ~ | SPACE |RTALT| RIGHT_SHIFT |
|
|
+ * | | Kx| &| | |
|
|
+ * +------------+----+-------------------+-----+-------------+
|
|
+ *
|
|
+ * Notes:
|
|
+ * Most Important to know:
|
|
+ * To present a character as mentioned above a key in the above layout, use LFTALT + Key.
|
|
+ * To present a character as mentioned below a key in the above layout, use RTALT + Key.
|
|
+ *
|
|
+ * Changes from arturo182's layout, if you are used to that, it's good to know these changes.
|
|
+ * 1. Sym Key (Scancode 0x1D) on Keyboard is mapped to RTALT.
|
|
+ * 2. Note "|" above Enter Key as per original keyboard layout is removed and added under "A"
|
|
+ * 3. The external buttons on the Keyboard Featherwing have been changed as below:
|
|
+ *__3.1 Leftmost Button changed from Menu Key to Left Ctrl Key.
|
|
+ *__3.2 Innet Left Button changed from Left Ctrl Key to Page Up Key.
|
|
+ *__3.3 Inner Right Button changed from Back Key to Page Down Key.
|
|
+ *__3.4 Outer Right Button changed from Left Shift Key to Menu Key.
|
|
+ *__3.5 Center Key of 5-way Button changed from Enter Key to Home Key.
|
|
+ */
|
|
+
|
|
+
|
|
+#define NUM_KEYCODES 256
|
|
+
|
|
+static unsigned short keycodes[NUM_KEYCODES] = {
|
|
+
|
|
+
|
|
+ [0xA1] = KEY_LEFTALT,
|
|
+ [0xA2] = KEY_LEFTSHIFT,
|
|
+ [0xA3] = KEY_RIGHTSHIFT,
|
|
+ [0xA5] = KEY_LEFTCTRL,
|
|
+
|
|
+ ['a'] = KEY_A,
|
|
+ ['b'] = KEY_B,
|
|
+ ['c'] = KEY_C,
|
|
+ ['d'] = KEY_D,
|
|
+ ['e'] = KEY_E,
|
|
+ ['f'] = KEY_F,
|
|
+ ['g'] = KEY_G,
|
|
+ ['h'] = KEY_H,
|
|
+ ['i'] = KEY_I,
|
|
+ ['j'] = KEY_J,
|
|
+ ['k'] = KEY_K,
|
|
+ ['l'] = KEY_L,
|
|
+ ['m'] = KEY_M,
|
|
+ ['n'] = KEY_N,
|
|
+ ['o'] = KEY_O,
|
|
+ ['p'] = KEY_P,
|
|
+ ['q'] = KEY_Q,
|
|
+ ['r'] = KEY_R,
|
|
+ ['s'] = KEY_S,
|
|
+ ['t'] = KEY_T,
|
|
+ ['u'] = KEY_U,
|
|
+ ['v'] = KEY_V,
|
|
+ ['w'] = KEY_W,
|
|
+ ['x'] = KEY_X,
|
|
+ ['y'] = KEY_Y,
|
|
+ ['z'] = KEY_Z,
|
|
+
|
|
+ ['A'] = KEY_A,
|
|
+ ['B'] = KEY_B,
|
|
+ ['C'] = KEY_C,
|
|
+ ['D'] = KEY_D,
|
|
+ ['E'] = KEY_E,
|
|
+ ['F'] = KEY_F,
|
|
+ ['G'] = KEY_G,
|
|
+ ['H'] = KEY_H,
|
|
+ ['I'] = KEY_I,
|
|
+ ['J'] = KEY_J,
|
|
+ ['K'] = KEY_K,
|
|
+ ['L'] = KEY_L,
|
|
+ ['M'] = KEY_M,
|
|
+ ['N'] = KEY_N,
|
|
+ ['O'] = KEY_O,
|
|
+ ['P'] = KEY_P,
|
|
+ ['Q'] = KEY_Q,
|
|
+ ['R'] = KEY_R,
|
|
+ ['S'] = KEY_S,
|
|
+ ['T'] = KEY_T,
|
|
+ ['U'] = KEY_U,
|
|
+ ['V'] = KEY_V,
|
|
+ ['W'] = KEY_W,
|
|
+ ['X'] = KEY_X,
|
|
+ ['Y'] = KEY_Y,
|
|
+ ['Z'] = KEY_Z,
|
|
+
|
|
+ [' '] = KEY_SPACE,
|
|
+ ['~'] = KEY_0,
|
|
+ ['$'] = KEY_4,
|
|
+
|
|
+ ['\b'] = KEY_BACKSPACE,
|
|
+ ['\n'] = KEY_ENTER,
|
|
+ /*
|
|
+ * As per the kernel, a keyboard needs to indicate, in advance, which key values it can report.
|
|
+ * In order to that, it should have unique scancodes pointing those scancode-keycode pairs.
|
|
+ * With the configuration set for now, the keyboard never outputs lower case letters, numbers, and equal to sign.
|
|
+ * We can use these as bogus scancodes, and map the keys we want the keyboard to say its pressed when modifier keys are used.
|
|
+ * This can change however, in case future software versions of the keyboard micrcontroller itself changes to output other stuff.
|
|
+ */
|
|
+ ['0'] = KEY_0,
|
|
+ ['1'] = KEY_1,
|
|
+ ['2'] = KEY_2,
|
|
+ ['3'] = KEY_3,
|
|
+ ['4'] = KEY_4,
|
|
+ ['5'] = KEY_5,
|
|
+ ['6'] = KEY_6,
|
|
+ ['7'] = KEY_7,
|
|
+ ['8'] = KEY_8,
|
|
+ ['9'] = KEY_9,
|
|
+
|
|
+ [0x81] = KEY_F1,
|
|
+ [0x82] = KEY_F2,
|
|
+ [0x83] = KEY_F3,
|
|
+ [0x84] = KEY_F4,
|
|
+ [0x85] = KEY_F5,
|
|
+ [0x86] = KEY_F6,
|
|
+ [0x87] = KEY_F7,
|
|
+ [0x88] = KEY_F8,
|
|
+ [0x89] = KEY_F9,
|
|
+ [0x90] = KEY_F10,
|
|
+
|
|
+ ['#'] = KEY_3,
|
|
+ ['@'] = KEY_2,
|
|
+ [0x08] = KEY_BACKSPACE,
|
|
+ [0xD4] = KEY_DELETE,
|
|
+ [0xD5] = KEY_END,
|
|
+ [0xC1] = KEY_CAPSLOCK,
|
|
+ [0x09] = KEY_TAB,
|
|
+ [0xD2] = KEY_HOME,
|
|
+ [0xB1] = KEY_ESC,
|
|
+ [0xd0] = KEY_BREAK,
|
|
+ [0xd0] = KEY_PAUSE,
|
|
+ ['='] = KEY_EQUAL,
|
|
+ ['+'] = KEY_EQUAL,
|
|
+ ['-'] = KEY_MINUS,
|
|
+ ['_'] = KEY_MINUS,
|
|
+ ['\\'] = KEY_BACKSLASH,
|
|
+ ['|'] = KEY_BACKSLASH,
|
|
+ ['!'] = KEY_1,
|
|
+ [0xD1] = KEY_INSERT,
|
|
+ ['*'] = KEY_8,
|
|
+ ['&'] = KEY_7,
|
|
+ ['^'] = KEY_6,
|
|
+ ['%'] = KEY_5,
|
|
+
|
|
+ ['.'] = KEY_DOT,
|
|
+ ['>'] = KEY_DOT,
|
|
+
|
|
+ [';'] = KEY_SEMICOLON,
|
|
+ [':'] = KEY_SEMICOLON,
|
|
+
|
|
+ [','] = KEY_COMMA,
|
|
+ ['<'] = KEY_COMMA,
|
|
+
|
|
+ ['`'] = KEY_GRAVE,
|
|
+ ['~'] = KEY_GRAVE,
|
|
+
|
|
+ ['"'] = KEY_APOSTROPHE,
|
|
+ ['\''] = KEY_APOSTROPHE,
|
|
+
|
|
+ ['?'] = KEY_SLASH,
|
|
+ ['/'] = KEY_SLASH,
|
|
+
|
|
+ [')'] = KEY_0,
|
|
+ ['('] = KEY_9,
|
|
+
|
|
+ [']'] = KEY_RIGHTBRACE,
|
|
+ ['}'] = KEY_RIGHTBRACE,
|
|
+
|
|
+ ['['] = KEY_LEFTBRACE,
|
|
+ ['{'] = KEY_LEFTBRACE,
|
|
+
|
|
+ [0xb7] = KEY_RIGHT,
|
|
+ [0xb5] = KEY_UP,
|
|
+ [0xb6] = KEY_DOWN,
|
|
+ [0xb4] = KEY_LEFT,
|
|
+ [0xd6] = KEY_PAGEUP,
|
|
+ [0xd7] = KEY_PAGEDOWN,
|
|
+ [0x91] = KEY_POWER,
|
|
+};
|
|
+
|
|
+
|
|
+#endif
|
|
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
|
|
index 3563f2e3e979..90314f1a93af 100644
|
|
--- a/drivers/mmc/core/core.c
|
|
+++ b/drivers/mmc/core/core.c
|
|
@@ -2104,10 +2104,19 @@ int _mmc_detect_card_removed(struct mmc_host *host)
|
|
int ret;
|
|
|
|
if (!host->card || mmc_card_removed(host->card))
|
|
+ {
|
|
+ pr_info("%s card already removed",__func__);
|
|
return 1;
|
|
+ }
|
|
|
|
ret = host->bus_ops->alive(host);
|
|
|
|
+ if (mmc_host_is_spi(host)){
|
|
+ if( host->ops->get_cd ) {
|
|
+ ret = !host->ops->get_cd(host);
|
|
+ }
|
|
+ }
|
|
+
|
|
/*
|
|
* Card detect status and alive check may be out of sync if card is
|
|
* removed slowly, when card detect switch changes while card/slot
|
|
@@ -2223,8 +2232,13 @@ void mmc_rescan(struct work_struct *work)
|
|
}
|
|
|
|
/* Verify a registered card to be functional, else remove it. */
|
|
- if (host->bus_ops)
|
|
- host->bus_ops->detect(host);
|
|
+ if (host->bus_ops){
|
|
+ if (mmc_host_is_spi(host)){
|
|
+ mmc_detect_mmc(host);
|
|
+ }else{
|
|
+ host->bus_ops->detect(host);
|
|
+ }
|
|
+ }
|
|
|
|
host->detect_change = 0;
|
|
|
|
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
|
|
index f5f3f623ea49..275cb5457a49 100644
|
|
--- a/drivers/mmc/core/core.h
|
|
+++ b/drivers/mmc/core/core.h
|
|
@@ -78,6 +78,7 @@ void _mmc_detect_change(struct mmc_host *host, unsigned long delay,
|
|
int _mmc_detect_card_removed(struct mmc_host *host);
|
|
int mmc_detect_card_removed(struct mmc_host *host);
|
|
|
|
+int mmc_detect_mmc(struct mmc_host *host);
|
|
int mmc_attach_mmc(struct mmc_host *host);
|
|
int mmc_attach_sd(struct mmc_host *host);
|
|
int mmc_attach_sdio(struct mmc_host *host);
|
|
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
|
|
index 89cd48fcec79..20b2cfaa78fc 100644
|
|
--- a/drivers/mmc/core/mmc.c
|
|
+++ b/drivers/mmc/core/mmc.c
|
|
@@ -2276,6 +2276,11 @@ static const struct mmc_bus_ops mmc_ops = {
|
|
.flush_cache = _mmc_flush_cache,
|
|
};
|
|
|
|
+int mmc_detect_mmc(struct mmc_host *host){
|
|
+ mmc_detect(host);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
/*
|
|
* Starting point for MMC card init.
|
|
*/
|