cypress-sf.c (5912B)
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(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 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 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(&client->dev, sizeof(*touchkey), GFP_KERNEL); 78 if (!touchkey) 79 return -ENOMEM; 80 81 touchkey->client = client; 82 i2c_set_clientdata(client, touchkey); 83 84 touchkey->regulators[0].supply = "vdd"; 85 touchkey->regulators[1].supply = "avdd"; 86 87 error = devm_regulator_bulk_get(&client->dev, 88 ARRAY_SIZE(touchkey->regulators), 89 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(&client->dev, 96 "linux,keycodes", 97 NULL, 0); 98 if (touchkey->num_keys < 0) { 99 /* Default key count */ 100 touchkey->num_keys = 2; 101 } 102 103 touchkey->keycodes = devm_kcalloc(&client->dev, 104 touchkey->num_keys, 105 sizeof(*touchkey->keycodes), 106 GFP_KERNEL); 107 if (!touchkey->keycodes) 108 return -ENOMEM; 109 110 error = device_property_read_u32_array(&client->dev, "linux,keycodes", 111 touchkey->keycodes, 112 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 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(touchkey->input_dev, 149 EV_KEY, 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(&client->dev, client->irq, 159 NULL, cypress_sf_irq_handler, 160 IRQF_ONESHOT, 161 CYPRESS_SF_DEV_NAME, 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 __maybe_unused 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(client->irq); 178 179 error = regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators), 180 touchkey->regulators); 181 if (error) { 182 dev_err(dev, "Failed to disable regulators: %d", error); 183 enable_irq(client->irq); 184 return error; 185 } 186 187 return 0; 188} 189 190static int __maybe_unused 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 touchkey->regulators); 198 if (error) { 199 dev_err(dev, "Failed to enable regulators: %d", error); 200 return error; 201 } 202 203 enable_irq(client->irq); 204 205 return 0; 206} 207 208static 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 = &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_new = 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");