1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Cypress StreetFighter Touchkey Driver
4 *
5 * Copyright (c) 2021 Yassine Oudjana <y.oudjana@protonmail.com>
6 */
7
8#include <linux/bitmap.h>
9#include <linux/bitops.h>
10#include <linux/device.h>
11#include <linux/i2c.h>
12#include <linux/input.h>
13#include <linux/interrupt.h>
14#include <linux/module.h>
15#include <linux/pm.h>
16#include <linux/regulator/consumer.h>
17
18#define CYPRESS_SF_DEV_NAME "cypress-sf"
19
20#define CYPRESS_SF_REG_BUTTON_STATUS 0x4a
21
22struct cypress_sf_data {
23 struct i2c_client *client;
24 struct input_dev *input_dev;
25 struct regulator_bulk_data regulators[2];
26 u32 *keycodes;
27 unsigned long keystates;
28 int num_keys;
29};
30
31static irqreturn_t cypress_sf_irq_handler(int irq, void *devid)
32{
33 struct cypress_sf_data *touchkey = devid;
34 unsigned long keystates, changed;
35 bool new_state;
36 int val, key;
37
38 val = i2c_smbus_read_byte_data(client: touchkey->client,
39 CYPRESS_SF_REG_BUTTON_STATUS);
40 if (val < 0) {
41 dev_err(&touchkey->client->dev,
42 "Failed to read button status: %d", val);
43 return IRQ_NONE;
44 }
45 keystates = val;
46
47 bitmap_xor(dst: &changed, src1: &keystates, src2: &touchkey->keystates,
48 nbits: touchkey->num_keys);
49
50 for_each_set_bit(key, &changed, touchkey->num_keys) {
51 new_state = keystates & BIT(key);
52 dev_dbg(&touchkey->client->dev,
53 "Key %d changed to %d", key, new_state);
54 input_report_key(dev: touchkey->input_dev,
55 code: touchkey->keycodes[key], value: new_state);
56 }
57
58 input_sync(dev: touchkey->input_dev);
59 touchkey->keystates = keystates;
60
61 return IRQ_HANDLED;
62}
63
64static void cypress_sf_disable_regulators(void *arg)
65{
66 struct cypress_sf_data *touchkey = arg;
67
68 regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators),
69 consumers: touchkey->regulators);
70}
71
72static int cypress_sf_probe(struct i2c_client *client)
73{
74 struct cypress_sf_data *touchkey;
75 int key, error;
76
77 touchkey = devm_kzalloc(dev: &client->dev, size: sizeof(*touchkey), GFP_KERNEL);
78 if (!touchkey)
79 return -ENOMEM;
80
81 touchkey->client = client;
82 i2c_set_clientdata(client, data: touchkey);
83
84 touchkey->regulators[0].supply = "vdd";
85 touchkey->regulators[1].supply = "avdd";
86
87 error = devm_regulator_bulk_get(dev: &client->dev,
88 ARRAY_SIZE(touchkey->regulators),
89 consumers: touchkey->regulators);
90 if (error) {
91 dev_err(&client->dev, "Failed to get regulators: %d\n", error);
92 return error;
93 }
94
95 touchkey->num_keys = device_property_read_u32_array(dev: &client->dev,
96 propname: "linux,keycodes",
97 NULL, nval: 0);
98 if (touchkey->num_keys < 0) {
99 /* Default key count */
100 touchkey->num_keys = 2;
101 }
102
103 touchkey->keycodes = devm_kcalloc(dev: &client->dev,
104 n: touchkey->num_keys,
105 size: sizeof(*touchkey->keycodes),
106 GFP_KERNEL);
107 if (!touchkey->keycodes)
108 return -ENOMEM;
109
110 error = device_property_read_u32_array(dev: &client->dev, propname: "linux,keycodes",
111 val: touchkey->keycodes,
112 nval: touchkey->num_keys);
113
114 if (error) {
115 dev_warn(&client->dev,
116 "Failed to read keycodes: %d, using defaults\n",
117 error);
118
119 /* Default keycodes */
120 touchkey->keycodes[0] = KEY_BACK;
121 touchkey->keycodes[1] = KEY_MENU;
122 }
123
124 error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators),
125 consumers: touchkey->regulators);
126 if (error) {
127 dev_err(&client->dev,
128 "Failed to enable regulators: %d\n", error);
129 return error;
130 }
131
132 error = devm_add_action_or_reset(&client->dev,
133 cypress_sf_disable_regulators,
134 touchkey);
135 if (error)
136 return error;
137
138 touchkey->input_dev = devm_input_allocate_device(&client->dev);
139 if (!touchkey->input_dev) {
140 dev_err(&client->dev, "Failed to allocate input device\n");
141 return -ENOMEM;
142 }
143
144 touchkey->input_dev->name = CYPRESS_SF_DEV_NAME;
145 touchkey->input_dev->id.bustype = BUS_I2C;
146
147 for (key = 0; key < touchkey->num_keys; ++key)
148 input_set_capability(dev: touchkey->input_dev,
149 EV_KEY, code: touchkey->keycodes[key]);
150
151 error = input_register_device(touchkey->input_dev);
152 if (error) {
153 dev_err(&client->dev,
154 "Failed to register input device: %d\n", error);
155 return error;
156 }
157
158 error = devm_request_threaded_irq(dev: &client->dev, irq: client->irq,
159 NULL, thread_fn: cypress_sf_irq_handler,
160 IRQF_ONESHOT,
161 CYPRESS_SF_DEV_NAME, dev_id: touchkey);
162 if (error) {
163 dev_err(&client->dev,
164 "Failed to register threaded irq: %d", error);
165 return error;
166 }
167
168 return 0;
169};
170
171static int cypress_sf_suspend(struct device *dev)
172{
173 struct i2c_client *client = to_i2c_client(dev);
174 struct cypress_sf_data *touchkey = i2c_get_clientdata(client);
175 int error;
176
177 disable_irq(irq: client->irq);
178
179 error = regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators),
180 consumers: touchkey->regulators);
181 if (error) {
182 dev_err(dev, "Failed to disable regulators: %d", error);
183 enable_irq(irq: client->irq);
184 return error;
185 }
186
187 return 0;
188}
189
190static int cypress_sf_resume(struct device *dev)
191{
192 struct i2c_client *client = to_i2c_client(dev);
193 struct cypress_sf_data *touchkey = i2c_get_clientdata(client);
194 int error;
195
196 error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators),
197 consumers: touchkey->regulators);
198 if (error) {
199 dev_err(dev, "Failed to enable regulators: %d", error);
200 return error;
201 }
202
203 enable_irq(irq: client->irq);
204
205 return 0;
206}
207
208static DEFINE_SIMPLE_DEV_PM_OPS(cypress_sf_pm_ops,
209 cypress_sf_suspend, cypress_sf_resume);
210
211static struct i2c_device_id cypress_sf_id_table[] = {
212 { CYPRESS_SF_DEV_NAME, 0 },
213 { }
214};
215MODULE_DEVICE_TABLE(i2c, cypress_sf_id_table);
216
217#ifdef CONFIG_OF
218static const struct of_device_id cypress_sf_of_match[] = {
219 { .compatible = "cypress,sf3155", },
220 { },
221};
222MODULE_DEVICE_TABLE(of, cypress_sf_of_match);
223#endif
224
225static struct i2c_driver cypress_sf_driver = {
226 .driver = {
227 .name = CYPRESS_SF_DEV_NAME,
228 .pm = pm_sleep_ptr(&cypress_sf_pm_ops),
229 .of_match_table = of_match_ptr(cypress_sf_of_match),
230 },
231 .id_table = cypress_sf_id_table,
232 .probe = cypress_sf_probe,
233};
234module_i2c_driver(cypress_sf_driver);
235
236MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
237MODULE_DESCRIPTION("Cypress StreetFighter Touchkey Driver");
238MODULE_LICENSE("GPL v2");
239

source code of linux/drivers/input/keyboard/cypress-sf.c