| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Raspberry Pi Sense HAT joystick driver |
| * http://raspberrypi.org |
| * |
| * Copyright (C) 2015 Raspberry Pi |
| * Copyright (C) 2021 Charles Mirabile, Mwesigwa Guma, Joel Savitz |
| * |
| * Original Author: Serge Schneider |
| * Revised for upstream Linux by: Charles Mirabile, Mwesigwa Guma, Joel Savitz |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/input.h> |
| #include <linux/i2c.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/property.h> |
| |
| #define JOYSTICK_SMB_REG 0xf2 |
| |
| struct sensehat_joystick { |
| struct platform_device *pdev; |
| struct input_dev *keys_dev; |
| unsigned long prev_states; |
| struct regmap *regmap; |
| }; |
| |
| static const unsigned int keymap[] = { |
| BTN_DPAD_DOWN, BTN_DPAD_RIGHT, BTN_DPAD_UP, BTN_SELECT, BTN_DPAD_LEFT, |
| }; |
| |
| static irqreturn_t sensehat_joystick_report(int irq, void *cookie) |
| { |
| struct sensehat_joystick *sensehat_joystick = cookie; |
| unsigned long curr_states, changes; |
| unsigned int keys; |
| int error; |
| int i; |
| |
| error = regmap_read(sensehat_joystick->regmap, JOYSTICK_SMB_REG, &keys); |
| if (error < 0) { |
| dev_err(&sensehat_joystick->pdev->dev, |
| "Failed to read joystick state: %d", error); |
| return IRQ_NONE; |
| } |
| curr_states = keys; |
| bitmap_xor(&changes, &curr_states, &sensehat_joystick->prev_states, |
| ARRAY_SIZE(keymap)); |
| |
| for_each_set_bit(i, &changes, ARRAY_SIZE(keymap)) |
| input_report_key(sensehat_joystick->keys_dev, keymap[i], |
| curr_states & BIT(i)); |
| |
| input_sync(sensehat_joystick->keys_dev); |
| sensehat_joystick->prev_states = keys; |
| return IRQ_HANDLED; |
| } |
| |
| static int sensehat_joystick_probe(struct platform_device *pdev) |
| { |
| struct sensehat_joystick *sensehat_joystick; |
| int error, i, irq; |
| |
| sensehat_joystick = devm_kzalloc(&pdev->dev, sizeof(*sensehat_joystick), |
| GFP_KERNEL); |
| if (!sensehat_joystick) |
| return -ENOMEM; |
| |
| sensehat_joystick->pdev = pdev; |
| |
| sensehat_joystick->regmap = dev_get_regmap(pdev->dev.parent, NULL); |
| if (!sensehat_joystick->regmap) { |
| dev_err(&pdev->dev, "unable to get sensehat regmap"); |
| return -ENODEV; |
| } |
| |
| sensehat_joystick->keys_dev = devm_input_allocate_device(&pdev->dev); |
| if (!sensehat_joystick->keys_dev) { |
| dev_err(&pdev->dev, "Could not allocate input device"); |
| return -ENOMEM; |
| } |
| |
| sensehat_joystick->keys_dev->name = "Raspberry Pi Sense HAT Joystick"; |
| sensehat_joystick->keys_dev->phys = "sensehat-joystick/input0"; |
| sensehat_joystick->keys_dev->id.bustype = BUS_I2C; |
| |
| __set_bit(EV_KEY, sensehat_joystick->keys_dev->evbit); |
| __set_bit(EV_REP, sensehat_joystick->keys_dev->evbit); |
| for (i = 0; i < ARRAY_SIZE(keymap); i++) |
| __set_bit(keymap[i], sensehat_joystick->keys_dev->keybit); |
| |
| error = input_register_device(sensehat_joystick->keys_dev); |
| if (error) { |
| dev_err(&pdev->dev, "Could not register input device"); |
| return error; |
| } |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return irq; |
| |
| error = devm_request_threaded_irq(&pdev->dev, irq, |
| NULL, sensehat_joystick_report, |
| IRQF_ONESHOT, "keys", |
| sensehat_joystick); |
| if (error) { |
| dev_err(&pdev->dev, "IRQ request failed"); |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| static const struct of_device_id sensehat_joystick_device_id[] = { |
| { .compatible = "raspberrypi,sensehat-joystick" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, sensehat_joystick_device_id); |
| |
| static struct platform_driver sensehat_joystick_driver = { |
| .probe = sensehat_joystick_probe, |
| .driver = { |
| .name = "sensehat-joystick", |
| .of_match_table = sensehat_joystick_device_id, |
| }, |
| }; |
| |
| module_platform_driver(sensehat_joystick_driver); |
| |
| MODULE_DESCRIPTION("Raspberry Pi Sense HAT joystick driver"); |
| MODULE_AUTHOR("Charles Mirabile <cmirabil@redhat.com>"); |
| MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>"); |
| MODULE_LICENSE("GPL"); |