xref: /linux/drivers/input/keyboard/cypress-sf.c (revision 34f7c6e7d4396090692a09789db231e12cb4762b)
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(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(&changed, &keystates, &touchkey->keystates,
48 		   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(touchkey->input_dev,
55 				 touchkey->keycodes[key], new_state);
56 	}
57 
58 	input_sync(touchkey->input_dev);
59 	touchkey->keystates = keystates;
60 
61 	return IRQ_HANDLED;
62 }
63 
64 static int cypress_sf_probe(struct i2c_client *client)
65 {
66 	struct cypress_sf_data *touchkey;
67 	int key, error;
68 
69 	touchkey = devm_kzalloc(&client->dev, sizeof(*touchkey), GFP_KERNEL);
70 	if (!touchkey)
71 		return -ENOMEM;
72 
73 	touchkey->client = client;
74 	i2c_set_clientdata(client, touchkey);
75 
76 	touchkey->regulators[0].supply = "vdd";
77 	touchkey->regulators[1].supply = "avdd";
78 
79 	error = devm_regulator_bulk_get(&client->dev,
80 					ARRAY_SIZE(touchkey->regulators),
81 					touchkey->regulators);
82 	if (error) {
83 		dev_err(&client->dev, "Failed to get regulators: %d\n", error);
84 		return error;
85 	}
86 
87 	touchkey->num_keys = device_property_read_u32_array(&client->dev,
88 							    "linux,keycodes",
89 							    NULL, 0);
90 	if (touchkey->num_keys < 0) {
91 		/* Default key count */
92 		touchkey->num_keys = 2;
93 	}
94 
95 	touchkey->keycodes = devm_kcalloc(&client->dev,
96 					  touchkey->num_keys,
97 					  sizeof(*touchkey->keycodes),
98 					  GFP_KERNEL);
99 	if (!touchkey->keycodes)
100 		return -ENOMEM;
101 
102 	error = device_property_read_u32_array(&client->dev, "linux,keycodes",
103 					       touchkey->keycodes,
104 					       touchkey->num_keys);
105 
106 	if (error) {
107 		dev_warn(&client->dev,
108 			 "Failed to read keycodes: %d, using defaults\n",
109 			 error);
110 
111 		/* Default keycodes */
112 		touchkey->keycodes[0] = KEY_BACK;
113 		touchkey->keycodes[1] = KEY_MENU;
114 	}
115 
116 	error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators),
117 				      touchkey->regulators);
118 	if (error) {
119 		dev_err(&client->dev,
120 			"Failed to enable regulators: %d\n", error);
121 		return error;
122 	}
123 
124 	touchkey->input_dev = devm_input_allocate_device(&client->dev);
125 	if (!touchkey->input_dev) {
126 		dev_err(&client->dev, "Failed to allocate input device\n");
127 		return -ENOMEM;
128 	}
129 
130 	touchkey->input_dev->name = CYPRESS_SF_DEV_NAME;
131 	touchkey->input_dev->id.bustype = BUS_I2C;
132 
133 	for (key = 0; key < touchkey->num_keys; ++key)
134 		input_set_capability(touchkey->input_dev,
135 				     EV_KEY, touchkey->keycodes[key]);
136 
137 	error = input_register_device(touchkey->input_dev);
138 	if (error) {
139 		dev_err(&client->dev,
140 			"Failed to register input device: %d\n", error);
141 		return error;
142 	}
143 
144 	error = devm_request_threaded_irq(&client->dev, client->irq,
145 					  NULL, cypress_sf_irq_handler,
146 					  IRQF_ONESHOT,
147 					  CYPRESS_SF_DEV_NAME, touchkey);
148 	if (error) {
149 		dev_err(&client->dev,
150 			"Failed to register threaded irq: %d", error);
151 		return error;
152 	}
153 
154 	return 0;
155 };
156 
157 static int __maybe_unused cypress_sf_suspend(struct device *dev)
158 {
159 	struct i2c_client *client = to_i2c_client(dev);
160 	struct cypress_sf_data *touchkey = i2c_get_clientdata(client);
161 	int error;
162 
163 	disable_irq(client->irq);
164 
165 	error = regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators),
166 				       touchkey->regulators);
167 	if (error) {
168 		dev_err(dev, "Failed to disable regulators: %d", error);
169 		enable_irq(client->irq);
170 		return error;
171 	}
172 
173 	return 0;
174 }
175 
176 static int __maybe_unused cypress_sf_resume(struct device *dev)
177 {
178 	struct i2c_client *client = to_i2c_client(dev);
179 	struct cypress_sf_data *touchkey = i2c_get_clientdata(client);
180 	int error;
181 
182 	error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators),
183 				      touchkey->regulators);
184 	if (error) {
185 		dev_err(dev, "Failed to enable regulators: %d", error);
186 		return error;
187 	}
188 
189 	enable_irq(client->irq);
190 
191 	return 0;
192 }
193 
194 static SIMPLE_DEV_PM_OPS(cypress_sf_pm_ops,
195 			 cypress_sf_suspend, cypress_sf_resume);
196 
197 static struct i2c_device_id cypress_sf_id_table[] = {
198 	{ CYPRESS_SF_DEV_NAME, 0 },
199 	{ }
200 };
201 MODULE_DEVICE_TABLE(i2c, cypress_sf_id_table);
202 
203 #ifdef CONFIG_OF
204 static const struct of_device_id cypress_sf_of_match[] = {
205 	{ .compatible = "cypress,sf3155", },
206 	{ },
207 };
208 MODULE_DEVICE_TABLE(of, cypress_sf_of_match);
209 #endif
210 
211 static struct i2c_driver cypress_sf_driver = {
212 	.driver = {
213 		.name = CYPRESS_SF_DEV_NAME,
214 		.pm = &cypress_sf_pm_ops,
215 		.of_match_table = of_match_ptr(cypress_sf_of_match),
216 	},
217 	.id_table = cypress_sf_id_table,
218 	.probe_new = cypress_sf_probe,
219 };
220 module_i2c_driver(cypress_sf_driver);
221 
222 MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
223 MODULE_DESCRIPTION("Cypress StreetFighter Touchkey Driver");
224 MODULE_LICENSE("GPL v2");
225