| 1 | |
| 2 | |
| 3 | * Copyright (C) IBM Corporation 2020
|
| 4 |
|
| 5 |
|
| 6 | #include <linux/i2c.h>
|
| 7 | #include <linux/init.h>
|
| 8 | #include <linux/input.h>
|
| 9 | #include <linux/kernel.h>
|
| 10 | #include <linux/limits.h>
|
| 11 | #include <linux/module.h>
|
| 12 | #include <linux/of.h>
|
| 13 | #include <linux/spinlock.h>
|
| 14 |
|
| 15 | #define DEVICE_NAME "ibm-panel"
|
| 16 | #define PANEL_KEYCODES_COUNT 3
|
| 17 |
|
| 18 | struct ibm_panel {
|
| 19 | u8 idx;
|
| 20 | u8 command[11];
|
| 21 | u32 keycodes[PANEL_KEYCODES_COUNT];
|
| 22 | spinlock_t lock;
|
| 23 | struct input_dev *input;
|
| 24 | };
|
| 25 |
|
| 26 | static u8 ibm_panel_calculate_checksum(struct ibm_panel *panel)
|
| 27 | {
|
| 28 | u8 chksum;
|
| 29 | u16 sum = 0;
|
| 30 | unsigned int i;
|
| 31 |
|
| 32 | for (i = 0; i < sizeof(panel->command) - 1; ++i) {
|
| 33 | sum += panel->command[i];
|
| 34 | if (sum & 0xff00) {
|
| 35 | sum &= 0xff;
|
| 36 | sum++;
|
| 37 | }
|
| 38 | }
|
| 39 |
|
| 40 | chksum = sum & 0xff;
|
| 41 | chksum = ~chksum;
|
| 42 | chksum++;
|
| 43 |
|
| 44 | return chksum;
|
| 45 | }
|
| 46 |
|
| 47 | static void ibm_panel_process_command(struct ibm_panel *panel)
|
| 48 | {
|
| 49 | u8 button;
|
| 50 | u8 chksum;
|
| 51 |
|
| 52 | if (panel->command[0] != 0xff && panel->command[1] != 0xf0) {
|
| 53 | dev_dbg(&panel->input->dev, "command invalid: %02x %02x\n",
|
| 54 | panel->command[0], panel->command[1]);
|
| 55 | return;
|
| 56 | }
|
| 57 |
|
| 58 | chksum = ibm_panel_calculate_checksum(panel);
|
| 59 | if (chksum != panel->command[sizeof(panel->command) - 1]) {
|
| 60 | dev_dbg(&panel->input->dev,
|
| 61 | "command failed checksum: %u != %u\n", chksum,
|
| 62 | panel->command[sizeof(panel->command) - 1]);
|
| 63 | return;
|
| 64 | }
|
| 65 |
|
| 66 | button = panel->command[2] & 0xf;
|
| 67 | if (button < PANEL_KEYCODES_COUNT) {
|
| 68 | input_report_key(panel->input, panel->keycodes[button],
|
| 69 | !(panel->command[2] & 0x80));
|
| 70 | input_sync(panel->input);
|
| 71 | } else {
|
| 72 | dev_dbg(&panel->input->dev, "unknown button %u\n",
|
| 73 | button);
|
| 74 | }
|
| 75 | }
|
| 76 |
|
| 77 | static int ibm_panel_i2c_slave_cb(struct i2c_client *client,
|
| 78 | enum i2c_slave_event event, u8 *val)
|
| 79 | {
|
| 80 | struct ibm_panel *panel = i2c_get_clientdata(client);
|
| 81 |
|
| 82 | dev_dbg(&panel->input->dev, "event: %u data: %02x\n", event, *val);
|
| 83 |
|
| 84 | guard(spinlock_irqsave)(&panel->lock);
|
| 85 |
|
| 86 | switch (event) {
|
| 87 | case I2C_SLAVE_STOP:
|
| 88 | if (panel->idx == sizeof(panel->command))
|
| 89 | ibm_panel_process_command(panel);
|
| 90 | else
|
| 91 | dev_dbg(&panel->input->dev,
|
| 92 | "command incorrect size %u\n", panel->idx);
|
| 93 | fallthrough;
|
| 94 | case I2C_SLAVE_WRITE_REQUESTED:
|
| 95 | panel->idx = 0;
|
| 96 | break;
|
| 97 | case I2C_SLAVE_WRITE_RECEIVED:
|
| 98 | if (panel->idx < sizeof(panel->command))
|
| 99 | panel->command[panel->idx++] = *val;
|
| 100 | else
|
| 101 | |
| 102 | * The command is too long and therefore invalid, so set the index
|
| 103 | * to it's largest possible value. When a STOP is finally received,
|
| 104 | * the command will be rejected upon processing.
|
| 105 |
|
| 106 | panel->idx = U8_MAX;
|
| 107 | break;
|
| 108 | case I2C_SLAVE_READ_REQUESTED:
|
| 109 | case I2C_SLAVE_READ_PROCESSED:
|
| 110 | *val = 0xff;
|
| 111 | break;
|
| 112 | default:
|
| 113 | break;
|
| 114 | }
|
| 115 |
|
| 116 | return 0;
|
| 117 | }
|
| 118 |
|
| 119 | static int ibm_panel_probe(struct i2c_client *client)
|
| 120 | {
|
| 121 | struct ibm_panel *panel;
|
| 122 | int i;
|
| 123 | int error;
|
| 124 |
|
| 125 | panel = devm_kzalloc(&client->dev, sizeof(*panel), GFP_KERNEL);
|
| 126 | if (!panel)
|
| 127 | return -ENOMEM;
|
| 128 |
|
| 129 | spin_lock_init(&panel->lock);
|
| 130 |
|
| 131 | panel->input = devm_input_allocate_device(&client->dev);
|
| 132 | if (!panel->input)
|
| 133 | return -ENOMEM;
|
| 134 |
|
| 135 | panel->input->name = client->name;
|
| 136 | panel->input->id.bustype = BUS_I2C;
|
| 137 |
|
| 138 | error = device_property_read_u32_array(&client->dev,
|
| 139 | "linux,keycodes",
|
| 140 | panel->keycodes,
|
| 141 | PANEL_KEYCODES_COUNT);
|
| 142 | if (error) {
|
| 143 | |
| 144 | * Use gamepad buttons as defaults for compatibility with
|
| 145 | * existing applications.
|
| 146 |
|
| 147 | panel->keycodes[0] = BTN_NORTH;
|
| 148 | panel->keycodes[1] = BTN_SOUTH;
|
| 149 | panel->keycodes[2] = BTN_SELECT;
|
| 150 | }
|
| 151 |
|
| 152 | for (i = 0; i < PANEL_KEYCODES_COUNT; ++i)
|
| 153 | input_set_capability(panel->input, EV_KEY, panel->keycodes[i]);
|
| 154 |
|
| 155 | error = input_register_device(panel->input);
|
| 156 | if (error) {
|
| 157 | dev_err(&client->dev,
|
| 158 | "Failed to register input device: %d\n", error);
|
| 159 | return error;
|
| 160 | }
|
| 161 |
|
| 162 | i2c_set_clientdata(client, panel);
|
| 163 | error = i2c_slave_register(client, ibm_panel_i2c_slave_cb);
|
| 164 | if (error) {
|
| 165 | dev_err(&client->dev,
|
| 166 | "Failed to register as i2c slave: %d\n", error);
|
| 167 | return error;
|
| 168 | }
|
| 169 |
|
| 170 | return 0;
|
| 171 | }
|
| 172 |
|
| 173 | static void ibm_panel_remove(struct i2c_client *client)
|
| 174 | {
|
| 175 | i2c_slave_unregister(client);
|
| 176 | }
|
| 177 |
|
| 178 | static const struct of_device_id ibm_panel_match[] = {
|
| 179 | { .compatible = "ibm,op-panel" },
|
| 180 | { }
|
| 181 | };
|
| 182 | MODULE_DEVICE_TABLE(of, ibm_panel_match);
|
| 183 |
|
| 184 | static struct i2c_driver ibm_panel_driver = {
|
| 185 | .driver = {
|
| 186 | .name = DEVICE_NAME,
|
| 187 | .of_match_table = ibm_panel_match,
|
| 188 | },
|
| 189 | .probe = ibm_panel_probe,
|
| 190 | .remove = ibm_panel_remove,
|
| 191 | };
|
| 192 | module_i2c_driver(ibm_panel_driver);
|
| 193 |
|
| 194 | MODULE_AUTHOR("Eddie James <[email protected]>");
|
| 195 | MODULE_DESCRIPTION("IBM Operation Panel Driver");
|
| 196 | MODULE_LICENSE("GPL");
|