From e2163ed8033b076006eaa36557dc7840c30c4691 Mon Sep 17 00:00:00 2001 From: Takumi Sueda Date: Tue, 5 Jan 2021 04:35:44 +0900 Subject: [PATCH] input: keyboard: implement brain-kbd-i2c --- arch/arm/boot/dts/imx28-brain.dtsi | 8 + arch/arm/boot/dts/imx28-pwsh1.dts | 112 ++++++++- arch/arm/configs/brain_defconfig | 3 +- drivers/input/keyboard/Kconfig | 10 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/brain-kbd-i2c.c | 321 +++++++++++++++++++++++++ 6 files changed, 441 insertions(+), 14 deletions(-) create mode 100644 drivers/input/keyboard/brain-kbd-i2c.c diff --git a/arch/arm/boot/dts/imx28-brain.dtsi b/arch/arm/boot/dts/imx28-brain.dtsi index 56088ae3a1b7..3c4a1479ddf0 100644 --- a/arch/arm/boot/dts/imx28-brain.dtsi +++ b/arch/arm/boot/dts/imx28-brain.dtsi @@ -226,6 +226,14 @@ pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins_a>; status = "okay"; + + keyboard_i2c: keyboard_i2c@28 { + status = "disabled"; + compatible = "sharp,brain-kbd-i2c"; + reg = <0x28>; + interrupt-parent = <&gpio4>; + interrupts = <2 4>; + }; }; pwm: pwm@80064000 { diff --git a/arch/arm/boot/dts/imx28-pwsh1.dts b/arch/arm/boot/dts/imx28-pwsh1.dts index 00389975965d..0874eda189cc 100644 --- a/arch/arm/boot/dts/imx28-pwsh1.dts +++ b/arch/arm/boot/dts/imx28-pwsh1.dts @@ -3,6 +3,7 @@ // Copyright 2012 Freescale Semiconductor, Inc. /dts-v1/; +#include #include "imx28-brain.dtsi" / { @@ -11,19 +12,106 @@ }; &brainlcd { - status = "okay"; + status = "okay"; - sharp,lcd-width = <800>; - sharp,lcd-height = <480>; - sharp,lcd-width-mm = <112>; - sharp,lcd-height-mm = <67>; + sharp,lcd-width = <800>; + sharp,lcd-height = <480>; + sharp,lcd-width-mm = <112>; + sharp,lcd-height-mm = <67>; - sharp,mac-transpose; - sharp,mac-inversion; - sharp,mac-bgr; + sharp,mac-transpose; + sharp,mac-inversion; + sharp,mac-bgr; - sharp,en-gpios = - <&gpio0 26 GPIO_ACTIVE_HIGH>, - <&gpio0 27 GPIO_ACTIVE_HIGH>, - <&gpio4 16 GPIO_ACTIVE_HIGH>; + sharp,en-gpios = + <&gpio0 26 GPIO_ACTIVE_HIGH>, + <&gpio0 27 GPIO_ACTIVE_HIGH>, + <&gpio4 16 GPIO_ACTIVE_HIGH>; +}; + +&keyboard_i2c { + status = "okay"; + keymap = + <0x01 KEY_POWER>, /* Power */ + <0x07 KEY_ESC>, /* Search */ + <0x0d KEY_TAB>, /* Kokugo */ + <0x11 KEY_PAGEUP>, /* Eiwa Waei */ + <0x15 KEY_PAGEDOWN>, /* My Dictionary */ + <0x25 KEY_INSERT>, /* History / Bookmark */ + <0x1d KEY_DELETE>, /* Marker test */ + /* <0x2b Memorization tool> */ + /* <0x24 Home> */ + <0x02 KEY_Q>, /* Q */ + <0x08 KEY_W>, /* W */ + <0x0e KEY_E>, /* E */ + <0x12 KEY_R>, /* R */ + <0x16 KEY_T>, /* T */ + <0x1e KEY_Y>, /* Y */ + <0x26 KEY_U>, /* U */ + <0x2c KEY_I>, /* I */ + <0x27 KEY_O>, /* O */ + <0x2d KEY_P>, /* P */ + <0x03 KEY_A>, /* A */ + <0x09 KEY_S>, /* S */ + <0x0f KEY_D>, /* D */ + <0x13 KEY_F>, /* F */ + <0x17 KEY_G>, /* G */ + <0x1f KEY_H>, /* H */ + <0x20 KEY_J>, /* J */ + <0x28 KEY_K>, /* K */ + <0x2e KEY_L>, /* L */ + <0x05 KEY_LEFTSHIFT>, + <0x04 KEY_Z>, /* Z */ + <0x0a KEY_X>, /* X */ + <0x10 KEY_C>, /* C */ + <0x14 KEY_V>, /* V */ + <0x18 KEY_B>, /* B */ + <0x21 KEY_N>, /* N */ + <0x29 KEY_M>, /* M */ + <0x2f KEY_MINUS>, /* Minus */ + <0x31 KEY_BACKSPACE>, /* Backspace */ + <0x0b KEY_LEFTCTRL>, /* Page Up */ + /* <0x0c KEY_PAGEDOWN>, */ /* Page Down */ + <0x06 KEY_LEFTALT>, /* Switch characters */ + /* < 0x19, Symbols>, */ + <0x1b KEY_ESC>, /* Go Back */ + <0x1c KEY_SPACE>, /* Space */ + <0x23 KEY_ENTER>, /* Enter */ + <0x1a KEY_LEFT>, /* Left */ + <0x22 KEY_UP>, /* Up */ + <0x2a KEY_DOWN>, /* Down */ + <0x30 KEY_RIGHT>; /* Right */ + keymap-symbol = + <0x01 KEY_POWER>, /* Power */ + <0x07 KEY_ESC>, /* Search */ + <0x0d KEY_TAB>, /* Kokugo */ + <0x11 KEY_PAGEUP>, /* Eiwa Waei */ + <0x15 KEY_PAGEDOWN>, /* My Dictionary */ + <0x25 KEY_INSERT>, /* History / Bookmark */ + <0x1d KEY_DELETE>, /* Marker test */ + <0x02 KEY_1>, /* Q */ + <0x08 KEY_2>, /* W */ + <0x0e KEY_3>, /* E */ + <0x12 KEY_4>, /* R */ + <0x16 KEY_5>, /* T */ + <0x1e KEY_6>, /* Y */ + <0x26 KEY_7>, /* U */ + <0x2c KEY_8>, /* I */ + <0x27 KEY_9>, /* O */ + <0x2d KEY_0>, /* P */ + <0x0f KEY_GRAVE>, /* D */ + <0x13 KEY_EQUAL>, /* F */ + <0x17 KEY_BACKSLASH>, /* G */ + <0x1f KEY_SEMICOLON>, /* H */ + <0x20 KEY_APOSTROPHE>, /* J */ + <0x28 KEY_LEFTBRACE>, /* K */ + <0x2e KEY_RIGHTBRACE>, /* L */ + <0x05 KEY_LEFTSHIFT>, + <0x21 KEY_COMMA>, /* N */ + <0x29 KEY_DOT>, /* M */ + <0x2f KEY_SLASH>, /* Minus */ + <0x31 KEY_BACKSPACE>, /* Backspace */ + <0x0b KEY_LEFTCTRL>, /* Page Up */ + /* <0x0c KEY_PAGEDOWN>, */ /* Page Down */ + <0x06 KEY_LEFTALT>; /* Switch characters */ }; diff --git a/arch/arm/configs/brain_defconfig b/arch/arm/configs/brain_defconfig index d15bacd5b098..beab9b6110d0 100644 --- a/arch/arm/configs/brain_defconfig +++ b/arch/arm/configs/brain_defconfig @@ -64,12 +64,11 @@ CONFIG_USB_USBNET=y CONFIG_USB_NET_SMSC95XX=y # CONFIG_WLAN is not set CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_KEYBOARD is not set +CONFIG_KEYBOARD_BRAIN_I2C=y # CONFIG_INPUT_MOUSE is not set CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_MXS_LRADC=y CONFIG_TOUCHSCREEN_TSC2007=m -# CONFIG_SERIO is not set # CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 321a097ee9ed..6e423d3373ee 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -781,6 +781,16 @@ config KEYBOARD_BCM To compile this driver as a module, choose M here: the module will be called bcm-keypad. +config KEYBOARD_BRAIN_I2C + tristate "SHARP Brain keyboard (I2C)" + depends on OF && I2C + help + Say Y here to enable the SHARP Brain keyboard driver + which is accessible via I2C bus. + + To compile this driver as a module, choose M here: the + module will be called brain-kbd-i2c. + config KEYBOARD_MTK_PMIC tristate "MediaTek PMIC keys support" depends on MFD_MT6397 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 0155a487d1bd..d17cd24e914b 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_KEYBOARD_APPLESPI) += applespi.o obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o obj-$(CONFIG_KEYBOARD_BCM) += bcm-keypad.o +obj-$(CONFIG_KEYBOARD_BRAIN_I2C) += brain-kbd-i2c.o obj-$(CONFIG_KEYBOARD_CAP11XX) += cap11xx.o obj-$(CONFIG_KEYBOARD_CLPS711X) += clps711x-keypad.o obj-$(CONFIG_KEYBOARD_CROS_EC) += cros_ec_keyb.o diff --git a/drivers/input/keyboard/brain-kbd-i2c.c b/drivers/input/keyboard/brain-kbd-i2c.c new file mode 100644 index 000000000000..eb716bc28bdd --- /dev/null +++ b/drivers/input/keyboard/brain-kbd-i2c.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SHARP Brain keyboard (I2C) driver + * + * Copyright 2021 Takumi Sueda + * + * Author: Takumi Sueda + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BRAIN_KBD_I2C_DEV_NAME "brain-kbd-i2c" + +#define BK_CMD_KEYCODE 0x04 + +#define BK_IS_PRESSED(val) ((~val & 0x40) >> 6) + +struct keymap_def { + u8 brain_keycode; + unsigned int kernel_keycode; +}; + +struct bk_i2c_data { + struct i2c_client *cli; + struct input_dev *idev; + struct keymap_def *km; + struct keymap_def *km_symbol; + int kmlen; + int kmlen_symbol; + + bool symbol; +}; + +static bool detect_key(struct bk_i2c_data *kbd, u8 keycode) +{ + int i; + + if ((keycode & 0x3f) == 0x19) { + if (BK_IS_PRESSED(keycode)) { + dev_dbg(&kbd->cli->dev, "symbol pressed!\n"); + kbd->symbol = true; + } else { + dev_dbg(&kbd->cli->dev, "symbol released!\n"); + kbd->symbol = false; + } + return true; + } + + if (kbd->symbol) { + for (i = 0; i < kbd->kmlen_symbol; i++) { + if ((keycode & 0x3f) == + kbd->km_symbol[i].brain_keycode) { + dev_dbg(&kbd->cli->dev, + "symbol: pressed %02x\n", + kbd->km_symbol[i].brain_keycode); + input_report_key( + kbd->idev, + kbd->km_symbol[i].kernel_keycode, + BK_IS_PRESSED(keycode)); + return true; + } + } + } else { + for (i = 0; i < kbd->kmlen; i++) { + if ((keycode & 0x3f) == kbd->km[i].brain_keycode) { + dev_dbg(&kbd->cli->dev, + "normal: pressed %02x\n", + kbd->km[i].brain_keycode); + input_report_key(kbd->idev, + kbd->km[i].kernel_keycode, + BK_IS_PRESSED(keycode)); + return true; + } + } + } + return false; +} + +static irqreturn_t bk_i2c_irq_handler(int irq, void *devid) +{ + struct bk_i2c_data *kbd = devid; + s32 raw; + u8 n, k1, k2, k3; + + raw = i2c_smbus_read_word_swapped(kbd->cli, BK_CMD_KEYCODE); + if (raw < 0) { + dev_err(&kbd->cli->dev, "failed to read keycode: %d\n", raw); + goto err; + } + + n = raw >> 8; + k1 = raw & 0xff; + + if (k1 == 0x00) { + dev_dbg(&kbd->cli->dev, + "interrupted but no key press was found\n"); + goto err; + } + + if (n == 1) { + if (detect_key(kbd, k1)) { + goto done; + } else { + dev_dbg(&kbd->cli->dev, + "unknown key was pressed: %02x\n", (k1 & 0x3f)); + goto err; + } + } + + raw = i2c_smbus_read_word_swapped(kbd->cli, BK_CMD_KEYCODE); + if (raw < 0) { + dev_err(&kbd->cli->dev, "failed to read 2nd/3rd keycode: %d\n", + raw); + goto err; + } + + k2 = (raw & 0xff00) >> 8; + k3 = raw & 0xff; + + detect_key(kbd, k2); + + if (n == 3) { + detect_key(kbd, k3); + } + +done: + input_sync(kbd->idev); +err: + return IRQ_HANDLED; +} + +static int bk_i2c_probe(struct i2c_client *cli, const struct i2c_device_id *id) +{ + struct bk_i2c_data *kbd; + int i, err, cells, len, offset; + u32 brain_keycode, kernel_keycode; + + if (!i2c_check_functionality(cli->adapter, I2C_FUNC_SMBUS_BYTE)) { + dev_err(&cli->dev, "the I2C bus is not compatible\n"); + return -EIO; + } + + kbd = devm_kzalloc(&cli->dev, sizeof(*kbd), GFP_KERNEL); + if (!kbd) { + return -ENOMEM; + } + + cells = 2; + if (!of_get_property(cli->dev.of_node, "keymap", &len)) { + dev_err(&cli->dev, "DT node has no keymap\n"); + return -EINVAL; + } + + len /= sizeof(u32) * cells; + kbd->kmlen = len; + + kbd->km = devm_kzalloc(&cli->dev, sizeof(struct keymap_def) * len, + GFP_KERNEL); + if (!kbd->km) { + return -ENOMEM; + } + + for (i = 0; i < len; i++) { + offset = i * cells; + if (of_property_read_u32_index(cli->dev.of_node, "keymap", + offset, &brain_keycode)) { + dev_err(&cli->dev, + "could not read DT property (brain keycode)\n"); + return -EINVAL; + } + if (of_property_read_u32_index(cli->dev.of_node, "keymap", + offset + 1, &kernel_keycode)) { + dev_err(&cli->dev, + "could not read DT property (kernel keycode)\n"); + return -EINVAL; + } + kbd->km[i].brain_keycode = brain_keycode; + kbd->km[i].kernel_keycode = kernel_keycode; + dev_dbg(&cli->dev, "normal: brain: %02x, kernel: %02x", + brain_keycode, kernel_keycode); + } + + if (!of_get_property(cli->dev.of_node, "keymap-symbol", &len)) { + dev_err(&cli->dev, "DT node has no keymap (symbol)\n"); + return -EINVAL; + } + + len /= sizeof(u32) * cells; + kbd->kmlen_symbol = len; + + kbd->km_symbol = devm_kzalloc( + &cli->dev, sizeof(struct keymap_def) * len, GFP_KERNEL); + if (!kbd->km_symbol) { + return -ENOMEM; + } + + for (i = 0; i < len; i++) { + offset = i * cells; + if (of_property_read_u32_index(cli->dev.of_node, + "keymap-symbol", offset, + &brain_keycode)) { + dev_err(&cli->dev, + "could not read DT property (brain keycode)\n"); + return -EINVAL; + } + if (of_property_read_u32_index(cli->dev.of_node, + "keymap-symbol", offset + 1, + &kernel_keycode)) { + dev_err(&cli->dev, + "could not read DT property (kernel keycode)\n"); + return -EINVAL; + } + kbd->km_symbol[i].brain_keycode = brain_keycode; + kbd->km_symbol[i].kernel_keycode = kernel_keycode; + dev_dbg(&cli->dev, "symbol: brain: %02x, kernel: %02x", + brain_keycode, kernel_keycode); + } + + kbd->cli = cli; + i2c_set_clientdata(cli, kbd); + + kbd->idev = devm_input_allocate_device(&cli->dev); + if (!kbd->idev) { + dev_err(&cli->dev, "failed to allocate inpute device\n"); + return -ENOMEM; + } + + kbd->idev->name = BRAIN_KBD_I2C_DEV_NAME; + kbd->idev->id.bustype = BUS_I2C; + + __set_bit(EV_REP, kbd->idev->evbit); /* autorepeat */ + + for (i = 0; i < kbd->kmlen; i++) { + input_set_capability(kbd->idev, EV_KEY, + kbd->km[i].kernel_keycode); + } + + for (i = 0; i < kbd->kmlen_symbol; i++) { + input_set_capability(kbd->idev, EV_KEY, + kbd->km_symbol[i].kernel_keycode); + } + + err = input_register_device(kbd->idev); + if (err) { + dev_err(&cli->dev, "failed to register input device: %d\n", + err); + return err; + } + + i2c_smbus_read_byte_data(kbd->cli, 0x00); + i2c_smbus_read_byte_data(kbd->cli, 0x0a); + i2c_smbus_read_byte_data(kbd->cli, 0x01); + + i2c_smbus_read_byte_data(kbd->cli, 0x40); + i2c_smbus_read_byte_data(kbd->cli, 0x41); + i2c_smbus_read_byte_data(kbd->cli, 0x48); + + i2c_smbus_read_byte_data(kbd->cli, 0x00); + i2c_smbus_read_word_data(kbd->cli, 0x04); + i2c_smbus_read_byte_data(kbd->cli, 0x01); + i2c_smbus_read_byte_data(kbd->cli, 0x0a); + i2c_smbus_read_byte_data(kbd->cli, 0x00); + i2c_smbus_read_byte_data(kbd->cli, 0x0a); + i2c_smbus_read_byte_data(kbd->cli, 0x01); + i2c_smbus_write_byte_data(kbd->cli, 0x88, 0x08); + i2c_smbus_write_byte_data(kbd->cli, 0x82, 0x05); + + i2c_smbus_read_byte_data(kbd->cli, 0x00); + i2c_smbus_read_byte_data(kbd->cli, 0x0a); + + i2c_smbus_write_byte_data(kbd->cli, 0x82, 0x02); + i2c_smbus_write_byte_data(kbd->cli, 0x82, 0x03); + + msleep(500); + + i2c_smbus_read_word_data(kbd->cli, 0x04); + + err = devm_request_threaded_irq(&cli->dev, cli->irq, bk_i2c_irq_handler, + NULL, IRQF_ONESHOT, + BRAIN_KBD_I2C_DEV_NAME, kbd); + if (err) { + dev_err(&cli->dev, "failed to request irq: %d\n", err); + return err; + } + return 0; +} + +static const struct i2c_device_id bk_i2c_id_table[] = { + { BRAIN_KBD_I2C_DEV_NAME, 0 }, + { /* sentinel */ }, +}; + +static const struct of_device_id bk_i2c_of_match[] = { + { + .compatible = "sharp,brain-kbd-i2c", + }, + { /* sentinel */ }, +}; + +static struct i2c_driver bk_i2c_driver = { + .driver = { + .name = BRAIN_KBD_I2C_DEV_NAME, + .of_match_table = of_match_ptr(bk_i2c_of_match), + }, + .probe = bk_i2c_probe, + .id_table = bk_i2c_id_table, +}; +module_i2c_driver(bk_i2c_driver); + +MODULE_AUTHOR("Takumi Sueda "); +MODULE_DESCRIPTION("SHARP Brain keyboard driver"); +MODULE_LICENSE("GPL v2");