root/drivers/input/keyboard/pinephone-keyboard.c
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright (C) 2021-2022 Samuel Holland <samuel@sholland.org>

#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/matrix_keypad.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>

#define DRV_NAME                        "pinephone-keyboard"

#define PPKB_CRC8_POLYNOMIAL            0x07

#define PPKB_DEVICE_ID_HI               0x00
#define PPKB_DEVICE_ID_HI_VALUE                 'K'
#define PPKB_DEVICE_ID_LO               0x01
#define PPKB_DEVICE_ID_LO_VALUE                 'B'
#define PPKB_FW_REVISION                0x02
#define PPKB_FW_FEATURES                0x03
#define PPKB_MATRIX_SIZE                0x06
#define PPKB_SCAN_CRC                   0x07
#define PPKB_SCAN_DATA                  0x08
#define PPKB_SYS_CONFIG                 0x20
#define PPKB_SYS_CONFIG_DISABLE_SCAN            BIT(0)
#define PPKB_SYS_SMBUS_COMMAND          0x21
#define PPKB_SYS_SMBUS_DATA             0x22
#define PPKB_SYS_COMMAND                0x23
#define PPKB_SYS_COMMAND_SMBUS_READ             0x91
#define PPKB_SYS_COMMAND_SMBUS_WRITE            0xa1

#define PPKB_ROWS                       6
#define PPKB_COLS                       12

/* Size of the scan buffer, including the CRC byte at the beginning. */
#define PPKB_BUF_LEN                    (1 + PPKB_COLS)

static const uint32_t ppkb_keymap[] = {
        KEY(0,  0, KEY_ESC),
        KEY(0,  1, KEY_1),
        KEY(0,  2, KEY_2),
        KEY(0,  3, KEY_3),
        KEY(0,  4, KEY_4),
        KEY(0,  5, KEY_5),
        KEY(0,  6, KEY_6),
        KEY(0,  7, KEY_7),
        KEY(0,  8, KEY_8),
        KEY(0,  9, KEY_9),
        KEY(0, 10, KEY_0),
        KEY(0, 11, KEY_BACKSPACE),

        KEY(1,  0, KEY_TAB),
        KEY(1,  1, KEY_Q),
        KEY(1,  2, KEY_W),
        KEY(1,  3, KEY_E),
        KEY(1,  4, KEY_R),
        KEY(1,  5, KEY_T),
        KEY(1,  6, KEY_Y),
        KEY(1,  7, KEY_U),
        KEY(1,  8, KEY_I),
        KEY(1,  9, KEY_O),
        KEY(1, 10, KEY_P),
        KEY(1, 11, KEY_ENTER),

        KEY(2,  0, KEY_LEFTMETA),
        KEY(2,  1, KEY_A),
        KEY(2,  2, KEY_S),
        KEY(2,  3, KEY_D),
        KEY(2,  4, KEY_F),
        KEY(2,  5, KEY_G),
        KEY(2,  6, KEY_H),
        KEY(2,  7, KEY_J),
        KEY(2,  8, KEY_K),
        KEY(2,  9, KEY_L),
        KEY(2, 10, KEY_SEMICOLON),

        KEY(3,  0, KEY_LEFTSHIFT),
        KEY(3,  1, KEY_Z),
        KEY(3,  2, KEY_X),
        KEY(3,  3, KEY_C),
        KEY(3,  4, KEY_V),
        KEY(3,  5, KEY_B),
        KEY(3,  6, KEY_N),
        KEY(3,  7, KEY_M),
        KEY(3,  8, KEY_COMMA),
        KEY(3,  9, KEY_DOT),
        KEY(3, 10, KEY_SLASH),

        KEY(4,  1, KEY_LEFTCTRL),
        KEY(4,  4, KEY_SPACE),
        KEY(4,  6, KEY_APOSTROPHE),
        KEY(4,  8, KEY_RIGHTBRACE),
        KEY(4,  9, KEY_LEFTBRACE),

        KEY(5,  2, KEY_FN),
        KEY(5,  3, KEY_LEFTALT),
        KEY(5,  5, KEY_RIGHTALT),

        /* FN layer */
        KEY(PPKB_ROWS + 0,  0, KEY_FN_ESC),
        KEY(PPKB_ROWS + 0,  1, KEY_F1),
        KEY(PPKB_ROWS + 0,  2, KEY_F2),
        KEY(PPKB_ROWS + 0,  3, KEY_F3),
        KEY(PPKB_ROWS + 0,  4, KEY_F4),
        KEY(PPKB_ROWS + 0,  5, KEY_F5),
        KEY(PPKB_ROWS + 0,  6, KEY_F6),
        KEY(PPKB_ROWS + 0,  7, KEY_F7),
        KEY(PPKB_ROWS + 0,  8, KEY_F8),
        KEY(PPKB_ROWS + 0,  9, KEY_F9),
        KEY(PPKB_ROWS + 0, 10, KEY_F10),
        KEY(PPKB_ROWS + 0, 11, KEY_DELETE),

        KEY(PPKB_ROWS + 1, 10, KEY_PAGEUP),

        KEY(PPKB_ROWS + 2,  0, KEY_SYSRQ),
        KEY(PPKB_ROWS + 2,  9, KEY_PAGEDOWN),
        KEY(PPKB_ROWS + 2, 10, KEY_INSERT),

        KEY(PPKB_ROWS + 3,  0, KEY_LEFTSHIFT),
        KEY(PPKB_ROWS + 3,  8, KEY_HOME),
        KEY(PPKB_ROWS + 3,  9, KEY_UP),
        KEY(PPKB_ROWS + 3, 10, KEY_END),

        KEY(PPKB_ROWS + 4, 1, KEY_LEFTCTRL),
        KEY(PPKB_ROWS + 4, 6, KEY_LEFT),
        KEY(PPKB_ROWS + 4, 8, KEY_RIGHT),
        KEY(PPKB_ROWS + 4, 9, KEY_DOWN),

        KEY(PPKB_ROWS + 5, 3, KEY_LEFTALT),
        KEY(PPKB_ROWS + 5, 5, KEY_RIGHTALT),
};

static const struct matrix_keymap_data ppkb_keymap_data = {
        .keymap         = ppkb_keymap,
        .keymap_size    = ARRAY_SIZE(ppkb_keymap),
};

struct pinephone_keyboard {
        struct i2c_adapter adapter;
        struct input_dev *input;
        u8 buf[2][PPKB_BUF_LEN];
        u8 crc_table[CRC8_TABLE_SIZE];
        u8 fn_state[PPKB_COLS];
        bool buf_swap;
        bool fn_pressed;
};

static int ppkb_adap_smbus_xfer(struct i2c_adapter *adap, u16 addr,
                                unsigned short flags, char read_write,
                                u8 command, int size,
                                union i2c_smbus_data *data)
{
        struct i2c_client *client = adap->algo_data;
        u8 buf[3];
        int ret;

        buf[0] = command;
        buf[1] = data->byte;
        buf[2] = read_write == I2C_SMBUS_READ ? PPKB_SYS_COMMAND_SMBUS_READ
                                              : PPKB_SYS_COMMAND_SMBUS_WRITE;

        ret = i2c_smbus_write_i2c_block_data(client, PPKB_SYS_SMBUS_COMMAND,
                                             sizeof(buf), buf);
        if (ret)
                return ret;

        /* Read back the command status until it passes or fails. */
        do {
                usleep_range(300, 500);
                ret = i2c_smbus_read_byte_data(client, PPKB_SYS_COMMAND);
        } while (ret == buf[2]);
        if (ret < 0)
                return ret;
        /* Commands return 0x00 on success and 0xff on failure. */
        if (ret)
                return -EIO;

        if (read_write == I2C_SMBUS_READ) {
                ret = i2c_smbus_read_byte_data(client, PPKB_SYS_SMBUS_DATA);
                if (ret < 0)
                        return ret;

                data->byte = ret;
        }

        return 0;
}

static u32 ppkg_adap_functionality(struct i2c_adapter *adap)
{
        return I2C_FUNC_SMBUS_BYTE_DATA;
}

static const struct i2c_algorithm ppkb_adap_algo = {
        .smbus_xfer             = ppkb_adap_smbus_xfer,
        .functionality          = ppkg_adap_functionality,
};

static void ppkb_update(struct i2c_client *client)
{
        struct pinephone_keyboard *ppkb = i2c_get_clientdata(client);
        unsigned short *keymap = ppkb->input->keycode;
        int row_shift = get_count_order(PPKB_COLS);
        u8 *old_buf = ppkb->buf[!ppkb->buf_swap];
        u8 *new_buf = ppkb->buf[ppkb->buf_swap];
        int col, crc, ret, row;
        struct device *dev = &client->dev;

        ret = i2c_smbus_read_i2c_block_data(client, PPKB_SCAN_CRC,
                                            PPKB_BUF_LEN, new_buf);
        if (ret != PPKB_BUF_LEN) {
                dev_err(dev, "Failed to read scan data: %d\n", ret);
                return;
        }

        crc = crc8(ppkb->crc_table, &new_buf[1], PPKB_COLS, CRC8_INIT_VALUE);
        if (crc != new_buf[0]) {
                dev_err(dev, "Bad scan data (%02x != %02x)\n", crc, new_buf[0]);
                return;
        }

        ppkb->buf_swap = !ppkb->buf_swap;

        for (col = 0; col < PPKB_COLS; ++col) {
                u8 old = old_buf[1 + col];
                u8 new = new_buf[1 + col];
                u8 changed = old ^ new;

                if (!changed)
                        continue;

                for (row = 0; row < PPKB_ROWS; ++row) {
                        u8 mask = BIT(row);
                        u8 value = new & mask;
                        unsigned short code;
                        bool fn_state;

                        if (!(changed & mask))
                                continue;

                        /*
                         * Save off the FN key state when the key was pressed,
                         * and use that to determine the code during a release.
                         */
                        fn_state = value ? ppkb->fn_pressed : ppkb->fn_state[col] & mask;
                        if (fn_state)
                                ppkb->fn_state[col] ^= mask;

                        /* The FN layer is a second set of rows. */
                        code = MATRIX_SCAN_CODE(fn_state ? PPKB_ROWS + row : row,
                                                col, row_shift);
                        input_event(ppkb->input, EV_MSC, MSC_SCAN, code);
                        input_report_key(ppkb->input, keymap[code], value);
                        if (keymap[code] == KEY_FN)
                                ppkb->fn_pressed = value;
                }
        }
        input_sync(ppkb->input);
}

static irqreturn_t ppkb_irq_thread(int irq, void *data)
{
        struct i2c_client *client = data;

        ppkb_update(client);

        return IRQ_HANDLED;
}

static int ppkb_set_scan(struct i2c_client *client, bool enable)
{
        struct device *dev = &client->dev;
        int ret, val;

        ret = i2c_smbus_read_byte_data(client, PPKB_SYS_CONFIG);
        if (ret < 0) {
                dev_err(dev, "Failed to read config: %d\n", ret);
                return ret;
        }

        if (enable)
                val = ret & ~PPKB_SYS_CONFIG_DISABLE_SCAN;
        else
                val = ret | PPKB_SYS_CONFIG_DISABLE_SCAN;

        ret = i2c_smbus_write_byte_data(client, PPKB_SYS_CONFIG, val);
        if (ret) {
                dev_err(dev, "Failed to write config: %d\n", ret);
                return ret;
        }

        return 0;
}

static int ppkb_open(struct input_dev *input)
{
        struct i2c_client *client = input_get_drvdata(input);
        int error;

        error = ppkb_set_scan(client, true);
        if (error)
                return error;

        return 0;
}

static void ppkb_close(struct input_dev *input)
{
        struct i2c_client *client = input_get_drvdata(input);

        ppkb_set_scan(client, false);
}

static int ppkb_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev;
        unsigned int phys_rows, phys_cols;
        struct pinephone_keyboard *ppkb;
        u8 info[PPKB_MATRIX_SIZE + 1];
        struct device_node *i2c_bus;
        int ret;
        int error;

        error = devm_regulator_get_enable(dev, "vbat");
        if (error) {
                dev_err(dev, "Failed to get VBAT supply: %d\n", error);
                return error;
        }

        ret = i2c_smbus_read_i2c_block_data(client, 0, sizeof(info), info);
        if (ret != sizeof(info)) {
                error = ret < 0 ? ret : -EIO;
                dev_err(dev, "Failed to read device ID: %d\n", error);
                return error;
        }

        if (info[PPKB_DEVICE_ID_HI] != PPKB_DEVICE_ID_HI_VALUE ||
            info[PPKB_DEVICE_ID_LO] != PPKB_DEVICE_ID_LO_VALUE) {
                dev_warn(dev, "Unexpected device ID: %#02x %#02x\n",
                         info[PPKB_DEVICE_ID_HI], info[PPKB_DEVICE_ID_LO]);
                return -ENODEV;
        }

        dev_info(dev, "Found firmware version %d.%d features %#x\n",
                 info[PPKB_FW_REVISION] >> 4,
                 info[PPKB_FW_REVISION] & 0xf,
                 info[PPKB_FW_FEATURES]);

        phys_rows = info[PPKB_MATRIX_SIZE] & 0xf;
        phys_cols = info[PPKB_MATRIX_SIZE] >> 4;
        if (phys_rows != PPKB_ROWS || phys_cols != PPKB_COLS) {
                dev_err(dev, "Unexpected keyboard size %ux%u\n",
                        phys_rows, phys_cols);
                return -EINVAL;
        }

        /* Disable scan by default to save power. */
        error = ppkb_set_scan(client, false);
        if (error)
                return error;

        ppkb = devm_kzalloc(dev, sizeof(*ppkb), GFP_KERNEL);
        if (!ppkb)
                return -ENOMEM;

        i2c_set_clientdata(client, ppkb);

        i2c_bus = of_get_child_by_name(dev->of_node, "i2c");
        if (i2c_bus) {
                ppkb->adapter.owner = THIS_MODULE;
                ppkb->adapter.algo = &ppkb_adap_algo;
                ppkb->adapter.algo_data = client;
                ppkb->adapter.dev.parent = dev;
                ppkb->adapter.dev.of_node = i2c_bus;
                strscpy(ppkb->adapter.name, DRV_NAME, sizeof(ppkb->adapter.name));

                error = devm_i2c_add_adapter(dev, &ppkb->adapter);
                if (error) {
                        dev_err(dev, "Failed to add I2C adapter: %d\n", error);
                        return error;
                }
        }

        crc8_populate_msb(ppkb->crc_table, PPKB_CRC8_POLYNOMIAL);

        ppkb->input = devm_input_allocate_device(dev);
        if (!ppkb->input)
                return -ENOMEM;

        input_set_drvdata(ppkb->input, client);

        ppkb->input->name = "PinePhone Keyboard";
        ppkb->input->phys = DRV_NAME "/input0";
        ppkb->input->id.bustype = BUS_I2C;
        ppkb->input->open = ppkb_open;
        ppkb->input->close = ppkb_close;

        input_set_capability(ppkb->input, EV_MSC, MSC_SCAN);
        __set_bit(EV_REP, ppkb->input->evbit);

        error = matrix_keypad_build_keymap(&ppkb_keymap_data, NULL,
                                           2 * PPKB_ROWS, PPKB_COLS, NULL,
                                           ppkb->input);
        if (error) {
                dev_err(dev, "Failed to build keymap: %d\n", error);
                return error;
        }

        error = input_register_device(ppkb->input);
        if (error) {
                dev_err(dev, "Failed to register input: %d\n", error);
                return error;
        }

        error = devm_request_threaded_irq(dev, client->irq,
                                          NULL, ppkb_irq_thread,
                                          IRQF_ONESHOT, client->name, client);
        if (error) {
                dev_err(dev, "Failed to request IRQ: %d\n", error);
                return error;
        }

        return 0;
}

static const struct of_device_id ppkb_of_match[] = {
        { .compatible = "pine64,pinephone-keyboard" },
        { }
};
MODULE_DEVICE_TABLE(of, ppkb_of_match);

static struct i2c_driver ppkb_driver = {
        .probe          = ppkb_probe,
        .driver         = {
                .name           = DRV_NAME,
                .of_match_table = ppkb_of_match,
        },
};
module_i2c_driver(ppkb_driver);

MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
MODULE_DESCRIPTION("Pine64 PinePhone keyboard driver");
MODULE_LICENSE("GPL");