xref: /openbmc/linux/drivers/input/misc/ibm-panel.c (revision 39ca8088)
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