1 // SPDX-License-Identifier: GPL-2.0-or-later 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 17 struct ibm_panel { 18 u8 idx; 19 u8 command[11]; 20 spinlock_t lock; /* protects writes to idx and command */ 21 struct input_dev *input; 22 }; 23 24 static void ibm_panel_process_command(struct ibm_panel *panel) 25 { 26 u8 i; 27 u8 chksum; 28 u16 sum = 0; 29 int pressed; 30 int released; 31 32 if (panel->command[0] != 0xff && panel->command[1] != 0xf0) { 33 dev_dbg(&panel->input->dev, "command invalid\n"); 34 return; 35 } 36 37 for (i = 0; i < sizeof(panel->command) - 1; ++i) { 38 sum += panel->command[i]; 39 if (sum & 0xff00) { 40 sum &= 0xff; 41 sum++; 42 } 43 } 44 45 chksum = sum & 0xff; 46 chksum = ~chksum; 47 chksum++; 48 49 if (chksum != panel->command[sizeof(panel->command) - 1]) { 50 dev_dbg(&panel->input->dev, "command failed checksum\n"); 51 return; 52 } 53 54 released = panel->command[2] & 0x80; 55 pressed = released ? 0 : 1; 56 57 switch (panel->command[2] & 0xf) { 58 case 0: 59 input_report_key(panel->input, BTN_NORTH, pressed); 60 break; 61 case 1: 62 input_report_key(panel->input, BTN_SOUTH, pressed); 63 break; 64 case 2: 65 input_report_key(panel->input, BTN_SELECT, pressed); 66 break; 67 default: 68 dev_dbg(&panel->input->dev, "unknown command %u\n", 69 panel->command[2] & 0xf); 70 return; 71 } 72 73 input_sync(panel->input); 74 } 75 76 static int ibm_panel_i2c_slave_cb(struct i2c_client *client, 77 enum i2c_slave_event event, u8 *val) 78 { 79 unsigned long flags; 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 spin_lock_irqsave(&panel->lock, flags); 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 spin_unlock_irqrestore(&panel->lock, flags); 117 118 return 0; 119 } 120 121 static int ibm_panel_probe(struct i2c_client *client, 122 const struct i2c_device_id *id) 123 { 124 int rc; 125 struct ibm_panel *panel = devm_kzalloc(&client->dev, sizeof(*panel), 126 GFP_KERNEL); 127 128 if (!panel) 129 return -ENOMEM; 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 input_set_capability(panel->input, EV_KEY, BTN_NORTH); 138 input_set_capability(panel->input, EV_KEY, BTN_SOUTH); 139 input_set_capability(panel->input, EV_KEY, BTN_SELECT); 140 141 rc = input_register_device(panel->input); 142 if (rc) { 143 dev_err(&client->dev, "Failed to register input device: %d\n", 144 rc); 145 return rc; 146 } 147 148 spin_lock_init(&panel->lock); 149 150 i2c_set_clientdata(client, panel); 151 rc = i2c_slave_register(client, ibm_panel_i2c_slave_cb); 152 if (rc) { 153 input_unregister_device(panel->input); 154 return rc; 155 } 156 157 return 0; 158 } 159 160 static int ibm_panel_remove(struct i2c_client *client) 161 { 162 int rc; 163 struct ibm_panel *panel = i2c_get_clientdata(client); 164 165 rc = i2c_slave_unregister(client); 166 167 input_unregister_device(panel->input); 168 169 return rc; 170 } 171 172 static const struct of_device_id ibm_panel_match[] = { 173 { .compatible = "ibm,op-panel" }, 174 { } 175 }; 176 177 static struct i2c_driver ibm_panel_driver = { 178 .driver = { 179 .name = DEVICE_NAME, 180 .of_match_table = ibm_panel_match, 181 }, 182 .probe = ibm_panel_probe, 183 .remove = ibm_panel_remove, 184 }; 185 module_i2c_driver(ibm_panel_driver); 186 187 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>"); 188 MODULE_DESCRIPTION("IBM Operation Panel Driver"); 189 MODULE_LICENSE("GPL"); 190