mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
5.8 KiB
238 lines
5.8 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Cypress StreetFighter Touchkey Driver |
|
* |
|
* Copyright (c) 2021 Yassine Oudjana <[email protected]> |
|
*/ |
|
|
|
#include <linux/bitmap.h> |
|
#include <linux/bitops.h> |
|
#include <linux/device.h> |
|
#include <linux/i2c.h> |
|
#include <linux/input.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/module.h> |
|
#include <linux/pm.h> |
|
#include <linux/regulator/consumer.h> |
|
|
|
#define CYPRESS_SF_DEV_NAME "cypress-sf" |
|
|
|
#define CYPRESS_SF_REG_BUTTON_STATUS 0x4a |
|
|
|
struct cypress_sf_data { |
|
struct i2c_client *client; |
|
struct input_dev *input_dev; |
|
struct regulator_bulk_data regulators[2]; |
|
u32 *keycodes; |
|
unsigned long keystates; |
|
int num_keys; |
|
}; |
|
|
|
static irqreturn_t cypress_sf_irq_handler(int irq, void *devid) |
|
{ |
|
struct cypress_sf_data *touchkey = devid; |
|
unsigned long keystates, changed; |
|
bool new_state; |
|
int val, key; |
|
|
|
val = i2c_smbus_read_byte_data(touchkey->client, |
|
CYPRESS_SF_REG_BUTTON_STATUS); |
|
if (val < 0) { |
|
dev_err(&touchkey->client->dev, |
|
"Failed to read button status: %d", val); |
|
return IRQ_NONE; |
|
} |
|
keystates = val; |
|
|
|
bitmap_xor(&changed, &keystates, &touchkey->keystates, |
|
touchkey->num_keys); |
|
|
|
for_each_set_bit(key, &changed, touchkey->num_keys) { |
|
new_state = keystates & BIT(key); |
|
dev_dbg(&touchkey->client->dev, |
|
"Key %d changed to %d", key, new_state); |
|
input_report_key(touchkey->input_dev, |
|
touchkey->keycodes[key], new_state); |
|
} |
|
|
|
input_sync(touchkey->input_dev); |
|
touchkey->keystates = keystates; |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
static void cypress_sf_disable_regulators(void *arg) |
|
{ |
|
struct cypress_sf_data *touchkey = arg; |
|
|
|
regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators), |
|
touchkey->regulators); |
|
} |
|
|
|
static int cypress_sf_probe(struct i2c_client *client) |
|
{ |
|
struct cypress_sf_data *touchkey; |
|
int key, error; |
|
|
|
touchkey = devm_kzalloc(&client->dev, sizeof(*touchkey), GFP_KERNEL); |
|
if (!touchkey) |
|
return -ENOMEM; |
|
|
|
touchkey->client = client; |
|
i2c_set_clientdata(client, touchkey); |
|
|
|
touchkey->regulators[0].supply = "vdd"; |
|
touchkey->regulators[1].supply = "avdd"; |
|
|
|
error = devm_regulator_bulk_get(&client->dev, |
|
ARRAY_SIZE(touchkey->regulators), |
|
touchkey->regulators); |
|
if (error) { |
|
dev_err(&client->dev, "Failed to get regulators: %d\n", error); |
|
return error; |
|
} |
|
|
|
touchkey->num_keys = device_property_read_u32_array(&client->dev, |
|
"linux,keycodes", |
|
NULL, 0); |
|
if (touchkey->num_keys < 0) { |
|
/* Default key count */ |
|
touchkey->num_keys = 2; |
|
} |
|
|
|
touchkey->keycodes = devm_kcalloc(&client->dev, |
|
touchkey->num_keys, |
|
sizeof(*touchkey->keycodes), |
|
GFP_KERNEL); |
|
if (!touchkey->keycodes) |
|
return -ENOMEM; |
|
|
|
error = device_property_read_u32_array(&client->dev, "linux,keycodes", |
|
touchkey->keycodes, |
|
touchkey->num_keys); |
|
|
|
if (error) { |
|
dev_warn(&client->dev, |
|
"Failed to read keycodes: %d, using defaults\n", |
|
error); |
|
|
|
/* Default keycodes */ |
|
touchkey->keycodes[0] = KEY_BACK; |
|
touchkey->keycodes[1] = KEY_MENU; |
|
} |
|
|
|
error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators), |
|
touchkey->regulators); |
|
if (error) { |
|
dev_err(&client->dev, |
|
"Failed to enable regulators: %d\n", error); |
|
return error; |
|
} |
|
|
|
error = devm_add_action_or_reset(&client->dev, |
|
cypress_sf_disable_regulators, |
|
touchkey); |
|
if (error) |
|
return error; |
|
|
|
touchkey->input_dev = devm_input_allocate_device(&client->dev); |
|
if (!touchkey->input_dev) { |
|
dev_err(&client->dev, "Failed to allocate input device\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
touchkey->input_dev->name = CYPRESS_SF_DEV_NAME; |
|
touchkey->input_dev->id.bustype = BUS_I2C; |
|
|
|
for (key = 0; key < touchkey->num_keys; ++key) |
|
input_set_capability(touchkey->input_dev, |
|
EV_KEY, touchkey->keycodes[key]); |
|
|
|
error = input_register_device(touchkey->input_dev); |
|
if (error) { |
|
dev_err(&client->dev, |
|
"Failed to register input device: %d\n", error); |
|
return error; |
|
} |
|
|
|
error = devm_request_threaded_irq(&client->dev, client->irq, |
|
NULL, cypress_sf_irq_handler, |
|
IRQF_ONESHOT, |
|
CYPRESS_SF_DEV_NAME, touchkey); |
|
if (error) { |
|
dev_err(&client->dev, |
|
"Failed to register threaded irq: %d", error); |
|
return error; |
|
} |
|
|
|
return 0; |
|
}; |
|
|
|
static int __maybe_unused cypress_sf_suspend(struct device *dev) |
|
{ |
|
struct i2c_client *client = to_i2c_client(dev); |
|
struct cypress_sf_data *touchkey = i2c_get_clientdata(client); |
|
int error; |
|
|
|
disable_irq(client->irq); |
|
|
|
error = regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators), |
|
touchkey->regulators); |
|
if (error) { |
|
dev_err(dev, "Failed to disable regulators: %d", error); |
|
enable_irq(client->irq); |
|
return error; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int __maybe_unused cypress_sf_resume(struct device *dev) |
|
{ |
|
struct i2c_client *client = to_i2c_client(dev); |
|
struct cypress_sf_data *touchkey = i2c_get_clientdata(client); |
|
int error; |
|
|
|
error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators), |
|
touchkey->regulators); |
|
if (error) { |
|
dev_err(dev, "Failed to enable regulators: %d", error); |
|
return error; |
|
} |
|
|
|
enable_irq(client->irq); |
|
|
|
return 0; |
|
} |
|
|
|
static SIMPLE_DEV_PM_OPS(cypress_sf_pm_ops, |
|
cypress_sf_suspend, cypress_sf_resume); |
|
|
|
static struct i2c_device_id cypress_sf_id_table[] = { |
|
{ CYPRESS_SF_DEV_NAME, 0 }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(i2c, cypress_sf_id_table); |
|
|
|
#ifdef CONFIG_OF |
|
static const struct of_device_id cypress_sf_of_match[] = { |
|
{ .compatible = "cypress,sf3155", }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(of, cypress_sf_of_match); |
|
#endif |
|
|
|
static struct i2c_driver cypress_sf_driver = { |
|
.driver = { |
|
.name = CYPRESS_SF_DEV_NAME, |
|
.pm = &cypress_sf_pm_ops, |
|
.of_match_table = of_match_ptr(cypress_sf_of_match), |
|
}, |
|
.id_table = cypress_sf_id_table, |
|
.probe_new = cypress_sf_probe, |
|
}; |
|
module_i2c_driver(cypress_sf_driver); |
|
|
|
MODULE_AUTHOR("Yassine Oudjana <[email protected]>"); |
|
MODULE_DESCRIPTION("Cypress StreetFighter Touchkey Driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|