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 | |
22 | struct 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 | |
31 | static 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 | |
64 | static 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 | |
72 | static 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 | |
171 | static 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 | |
190 | static 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 | |
208 | static DEFINE_SIMPLE_DEV_PM_OPS(cypress_sf_pm_ops, |
209 | cypress_sf_suspend, cypress_sf_resume); |
210 | |
211 | static struct i2c_device_id cypress_sf_id_table[] = { |
212 | { CYPRESS_SF_DEV_NAME, 0 }, |
213 | { } |
214 | }; |
215 | MODULE_DEVICE_TABLE(i2c, cypress_sf_id_table); |
216 | |
217 | #ifdef CONFIG_OF |
218 | static const struct of_device_id cypress_sf_of_match[] = { |
219 | { .compatible = "cypress,sf3155" , }, |
220 | { }, |
221 | }; |
222 | MODULE_DEVICE_TABLE(of, cypress_sf_of_match); |
223 | #endif |
224 | |
225 | static 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 | }; |
234 | module_i2c_driver(cypress_sf_driver); |
235 | |
236 | MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>" ); |
237 | MODULE_DESCRIPTION("Cypress StreetFighter Touchkey Driver" ); |
238 | MODULE_LICENSE("GPL v2" ); |
239 | |