// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) IBM Corporation 2020 */ #include #include #include #include #include #include #include #include #define DEVICE_NAME "ibm-panel" #define PANEL_KEYCODES_COUNT 3 struct ibm_panel { u8 idx; u8 command[11]; u32 keycodes[PANEL_KEYCODES_COUNT]; spinlock_t lock; /* protects writes to idx and command */ struct input_dev *input; }; static u8 ibm_panel_calculate_checksum(struct ibm_panel *panel) { u8 chksum; u16 sum = 0; unsigned int i; for (i = 0; i < sizeof(panel->command) - 1; ++i) { sum += panel->command[i]; if (sum & 0xff00) { sum &= 0xff; sum++; } } chksum = sum & 0xff; chksum = ~chksum; chksum++; return chksum; } static void ibm_panel_process_command(struct ibm_panel *panel) { u8 button; u8 chksum; if (panel->command[0] != 0xff && panel->command[1] != 0xf0) { dev_dbg(&panel->input->dev, "command invalid: %02x %02x\n", panel->command[0], panel->command[1]); return; } chksum = ibm_panel_calculate_checksum(panel); if (chksum != panel->command[sizeof(panel->command) - 1]) { dev_dbg(&panel->input->dev, "command failed checksum: %u != %u\n", chksum, panel->command[sizeof(panel->command) - 1]); return; } button = panel->command[2] & 0xf; if (button < PANEL_KEYCODES_COUNT) { input_report_key(panel->input, panel->keycodes[button], !(panel->command[2] & 0x80)); input_sync(panel->input); } else { dev_dbg(&panel->input->dev, "unknown button %u\n", button); } } static int ibm_panel_i2c_slave_cb(struct i2c_client *client, enum i2c_slave_event event, u8 *val) { unsigned long flags; struct ibm_panel *panel = i2c_get_clientdata(client); dev_dbg(&panel->input->dev, "event: %u data: %02x\n", event, *val); spin_lock_irqsave(&panel->lock, flags); switch (event) { case I2C_SLAVE_STOP: if (panel->idx == sizeof(panel->command)) ibm_panel_process_command(panel); else dev_dbg(&panel->input->dev, "command incorrect size %u\n", panel->idx); fallthrough; case I2C_SLAVE_WRITE_REQUESTED: panel->idx = 0; break; case I2C_SLAVE_WRITE_RECEIVED: if (panel->idx < sizeof(panel->command)) panel->command[panel->idx++] = *val; else /* * The command is too long and therefore invalid, so set the index * to it's largest possible value. When a STOP is finally received, * the command will be rejected upon processing. */ panel->idx = U8_MAX; break; case I2C_SLAVE_READ_REQUESTED: case I2C_SLAVE_READ_PROCESSED: *val = 0xff; break; default: break; } spin_unlock_irqrestore(&panel->lock, flags); return 0; } static int ibm_panel_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ibm_panel *panel; int i; int error; panel = devm_kzalloc(&client->dev, sizeof(*panel), GFP_KERNEL); if (!panel) return -ENOMEM; spin_lock_init(&panel->lock); panel->input = devm_input_allocate_device(&client->dev); if (!panel->input) return -ENOMEM; panel->input->name = client->name; panel->input->id.bustype = BUS_I2C; error = device_property_read_u32_array(&client->dev, "linux,keycodes", panel->keycodes, PANEL_KEYCODES_COUNT); if (error) { /* * Use gamepad buttons as defaults for compatibility with * existing applications. */ panel->keycodes[0] = BTN_NORTH; panel->keycodes[1] = BTN_SOUTH; panel->keycodes[2] = BTN_SELECT; } for (i = 0; i < PANEL_KEYCODES_COUNT; ++i) input_set_capability(panel->input, EV_KEY, panel->keycodes[i]); error = input_register_device(panel->input); if (error) { dev_err(&client->dev, "Failed to register input device: %d\n", error); return error; } i2c_set_clientdata(client, panel); error = i2c_slave_register(client, ibm_panel_i2c_slave_cb); if (error) { dev_err(&client->dev, "Failed to register as i2c slave: %d\n", error); return error; } return 0; } static void ibm_panel_remove(struct i2c_client *client) { i2c_slave_unregister(client); } static const struct of_device_id ibm_panel_match[] = { { .compatible = "ibm,op-panel" }, { } }; MODULE_DEVICE_TABLE(of, ibm_panel_match); static struct i2c_driver ibm_panel_driver = { .driver = { .name = DEVICE_NAME, .of_match_table = ibm_panel_match, }, .probe = ibm_panel_probe, .remove = ibm_panel_remove, }; module_i2c_driver(ibm_panel_driver); MODULE_AUTHOR("Eddie James "); MODULE_DESCRIPTION("IBM Operation Panel Driver"); MODULE_LICENSE("GPL");