root/drivers/input/keyboard/cypress-sf.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Cypress StreetFighter Touchkey Driver
 *
 * Copyright (c) 2021 Yassine Oudjana <y.oudjana@protonmail.com>
 */

#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 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 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 DEFINE_SIMPLE_DEV_PM_OPS(cypress_sf_pm_ops,
                                cypress_sf_suspend, cypress_sf_resume);

static const struct i2c_device_id cypress_sf_id_table[] = {
        { .name = CYPRESS_SF_DEV_NAME },
        { }
};
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 = pm_sleep_ptr(&cypress_sf_pm_ops),
                .of_match_table = of_match_ptr(cypress_sf_of_match),
        },
        .id_table = cypress_sf_id_table,
        .probe = cypress_sf_probe,
};
module_i2c_driver(cypress_sf_driver);

MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
MODULE_DESCRIPTION("Cypress StreetFighter Touchkey Driver");
MODULE_LICENSE("GPL v2");