Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH v4 2/4] HID: steam: add serial number information.
From: Andy Shevchenko @ 2018-02-28 20:17 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input
In-Reply-To: <20180228184322.29636-3-rodrigorivascosta@gmail.com>

On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
<rodrigorivascosta@gmail.com> wrote:
> This device has a feature report to send and receive commands.
> Use it to get the serial number and set the device's uniq value.

>  #include <linux/module.h>
>  #include <linux/workqueue.h>
>  #include <linux/rcupdate.h>

> +#include <linux/delay.h>

Better to keep it somehow sorted (yes, I see it's not originally, but
better to squeeze new header to the most ordered part).


> @@ -41,8 +42,99 @@ struct steam_device {
>         unsigned long quirks;
>         struct work_struct work_connect;
>         bool connected;

> +       char serial_no[11];

11 is a magic.

>  };
>
> +static int steam_recv_report(struct steam_device *steam,
> +               u8 *data, int size)
> +{
> +       struct hid_report *r;
> +       u8 *buf;
> +       int ret;
> +
> +       r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
> +       if (hid_report_len(r) < 64)
> +               return -EINVAL;

+ empty line.

> +       buf = hid_alloc_report_buf(r, GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       /*
> +        * The report ID is always 0, so strip the first byte from the output.
> +        * hid_report_len() is not counting the report ID, so +1 to the length
> +        * or else we get a EOVERFLOW. We are safe from a buffer overflow
> +        * because hid_alloc_report_buf() allocates +7 bytes.
> +        */
> +       ret = hid_hw_raw_request(steam->hdev, 0x00,
> +                       buf, hid_report_len(r) + 1,
> +                       HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
> +       if (ret > 0)
> +               memcpy(data, buf + 1, min(size, ret - 1));
> +       kfree(buf);
> +       return ret;
> +}
> +
> +static int steam_send_report(struct steam_device *steam,
> +               u8 *cmd, int size)
> +{
> +       struct hid_report *r;
> +       u8 *buf;
> +       int retry;
> +       int ret;
> +
> +       r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
> +       if (hid_report_len(r) < 64)
> +               return -EINVAL;

+empty line.

> +       buf = hid_alloc_report_buf(r, GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       /* The report ID is always 0 */
> +       memcpy(buf + 1, cmd, size);
> +
> +       /*
> +        * Sometimes the wireless controller fails with EPIPE
> +        * when sending a feature report.
> +        * Doing a HID_REQ_GET_REPORT and waiting for a while
> +        * seems to fix that.
> +        */

> +       for (retry = 0; retry < 10; ++retry) {
> +               ret = hid_hw_raw_request(steam->hdev, 0,
> +                               buf, size + 1,
> +                               HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> +               if (ret != -EPIPE)
> +                       break;
> +               steam_recv_report(steam, NULL, 0);
> +               msleep(50);
> +       }

Personally I consider do{}while in case of "timeout loops" much easier to parse.

unsigned int retry = 10;
...

do {
...
} while (--retry);

> +       kfree(buf);
> +       if (ret < 0)
> +               hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__,
> +                               ret, size, cmd);
> +       return ret;
> +}
> +
> +static int steam_get_serial(struct steam_device *steam)
> +{
> +       /*
> +        * Send: 0xae 0x15 0x01
> +        * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
> +        */
> +       int ret;
> +       u8 cmd[] = {0xae, 0x15, 0x01};

> +       u8 reply[14];
> +
> +       ret = steam_send_report(steam, cmd, sizeof(cmd));
> +       if (ret < 0)
> +               return ret;
> +       ret = steam_recv_report(steam, reply, sizeof(reply));
> +       if (ret < 0)
> +               return ret;


> +       reply[13] = 0;
> +       strcpy(steam->serial_no, reply + 3);

strlcpy()

> +       return 0;
> +}


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH v3 05/10] pwm: add PWM mode to pwm_config()
From: Jani Nikula @ 2018-02-28 20:04 UTC (permalink / raw)
  To: Thierry Reding, Claudiu Beznea
  Cc: mark.rutland, milo.kim, linux-fbdev, sean, devicetree, airlied,
	mturquette, kamil, dri-devel, linux-kernel, alexandre.belloni,
	pavel, lee.jones, linux-clk, linux-leds, daniel.thompson,
	linux-samsung-soc, shc_work, corbet, linux-doc, linux, krzk,
	kgene, linux-input, linux, linux-media, linux-pwm, jdelvare,
	b.zolnierkie, intel-gfx, robh+dt, jacek.anaszewski, rodrigo.vivi,
	mchehab, linux-arm-kernel, linux-hwmon
In-Reply-To: <20180228194429.GD22932@mithrandir>

On Wed, 28 Feb 2018, Thierry Reding <thierry.reding@gmail.com> wrote:
> Anyone that needs something other than normal mode should use the new
> atomic PWM API.

At the risk of revealing my true ignorance, what is the new atomic PWM
API? Where? Examples of how one would convert old code over to the new
API?

BR,
Jani.

-- 
Jani Nikula, Intel Open Source Technology Center
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply

* Re: [PATCH v3 05/10] pwm: add PWM mode to pwm_config()
From: Thierry Reding @ 2018-02-28 19:44 UTC (permalink / raw)
  To: Claudiu Beznea
  Cc: mark.rutland, milo.kim, linux-fbdev, sean, devicetree, airlied,
	mturquette, kamil, dri-devel, linux-kernel, alexandre.belloni,
	pavel, lee.jones, linux-clk, linux-leds, daniel.thompson,
	linux-samsung-soc, shc_work, corbet, linux-doc, linux, krzk,
	kgene, linux-input, linux, linux-media, linux-pwm, jdelvare,
	b.zolnierkie, intel-gfx, robh+dt, jacek.anaszewski, rodrigo.vivi,
	mchehab, linux-arm-kernel, linux-hwmon
In-Reply-To: <1519300881-8136-6-git-send-email-claudiu.beznea@microchip.com>


[-- Attachment #1.1: Type: text/plain, Size: 1581 bytes --]

On Thu, Feb 22, 2018 at 02:01:16PM +0200, Claudiu Beznea wrote:
> Add PWM mode to pwm_config() function. The drivers which uses pwm_config()
> were adapted to this change.
> 
> Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
> ---
>  arch/arm/mach-s3c24xx/mach-rx1950.c  | 11 +++++++++--
>  drivers/bus/ts-nbus.c                |  2 +-
>  drivers/clk/clk-pwm.c                |  3 ++-
>  drivers/gpu/drm/i915/intel_panel.c   | 17 ++++++++++++++---
>  drivers/hwmon/pwm-fan.c              |  2 +-
>  drivers/input/misc/max77693-haptic.c |  2 +-
>  drivers/input/misc/max8997_haptic.c  |  6 +++++-
>  drivers/leds/leds-pwm.c              |  5 ++++-
>  drivers/media/rc/ir-rx51.c           |  5 ++++-
>  drivers/media/rc/pwm-ir-tx.c         |  5 ++++-
>  drivers/video/backlight/lm3630a_bl.c |  4 +++-
>  drivers/video/backlight/lp855x_bl.c  |  4 +++-
>  drivers/video/backlight/lp8788_bl.c  |  5 ++++-
>  drivers/video/backlight/pwm_bl.c     | 11 +++++++++--
>  drivers/video/fbdev/ssd1307fb.c      |  3 ++-
>  include/linux/pwm.h                  |  6 ++++--
>  16 files changed, 70 insertions(+), 21 deletions(-)

I don't think it makes sense to leak mode support into the legacy API.
The pwm_config() function is considered legacy and should eventually go
away. As such it doesn't make sense to integrate a new feature such as
PWM modes into it. All users of pwm_config() assume normal mode, and
that's what pwm_config() should provide.

Anyone that needs something other than normal mode should use the new
atomic PWM API.

Thierry

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

^ permalink raw reply

* Re: [RFC 4/4] input: misc: Add Gateworks System Controller support
From: Tim Harvey @ 2018-02-28 19:44 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown, linux-kernel,
	devicetree, linux-arm-kernel, linux-hwmon, linux-input
In-Reply-To: <20180228045438.GA151045@dtor-ws>

On Tue, Feb 27, 2018 at 8:54 PM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> Hi Tim,

Hi Dmitry - thanks for the review!

>
> On Tue, Feb 27, 2018 at 05:21:14PM -0800, Tim Harvey wrote:
>> Add support for dispatching Linux Input events for the various interrupts
>> the Gateworks System Controller provides.
>>
>> Signed-off-by: Tim Harvey <tharvey@gateworks.com>
>> ---
>>  drivers/input/misc/Kconfig     |   6 ++
>>  drivers/input/misc/Makefile    |   1 +
>>  drivers/input/misc/gsc-input.c | 196 +++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 203 insertions(+)
>>  create mode 100644 drivers/input/misc/gsc-input.c
>>
>> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
>> index 9f082a3..3d18a0e 100644
>> --- a/drivers/input/misc/Kconfig
>> +++ b/drivers/input/misc/Kconfig
>> @@ -117,6 +117,12 @@ config INPUT_E3X0_BUTTON
>>         To compile this driver as a module, choose M here: the
>>         module will be called e3x0_button.
>>
>> +config INPUT_GSC
>> +     tristate "Gateworks System Controller input support"
>> +     depends on MFD_GSC
>> +     help
>> +       Say Y here if you want Gateworks System Controller input support.
>> +
>
> "To compile this driver as a module..."
>

ok

>>  config INPUT_PCSPKR
>>       tristate "PC Speaker support"
>>       depends on PCSPKR_PLATFORM
>> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
>> index 4b6118d..969debe 100644
>> --- a/drivers/input/misc/Makefile
>> +++ b/drivers/input/misc/Makefile
>> @@ -38,6 +38,7 @@ obj-$(CONFIG_INPUT_GP2A)            += gp2ap002a00f.o
>>  obj-$(CONFIG_INPUT_GPIO_BEEPER)              += gpio-beeper.o
>>  obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o
>>  obj-$(CONFIG_INPUT_GPIO_DECODER)     += gpio_decoder.o
>> +obj-$(CONFIG_INPUT_GSC)                      += gsc-input.o
>>  obj-$(CONFIG_INPUT_HISI_POWERKEY)    += hisi_powerkey.o
>>  obj-$(CONFIG_HP_SDC_RTC)             += hp_sdc_rtc.o
>>  obj-$(CONFIG_INPUT_IMS_PCU)          += ims-pcu.o
>> diff --git a/drivers/input/misc/gsc-input.c b/drivers/input/misc/gsc-input.c
>> new file mode 100644
>> index 0000000..7cf217c
>> --- /dev/null
>> +++ b/drivers/input/misc/gsc-input.c
>> @@ -0,0 +1,196 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2018 Gateworks Corporation
>> + */
>
> Let's keep the same // comment block fir the copyright notice as well.
> An one-line describing what this driver is would be appreciated too.

ok - will have this in v2:

// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2018 Gateworks Corporation
//
// This driver dispatches Linux input events for GSC interrupt events
//


>
>> +#define DEBUG
>
> Please no.
>

oops, did not mean to submit that

>> +
>> +#include <linux/input.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +
>> +#include <linux/mfd/gsc.h>
>> +
>> +struct gsc_irq {
>> +     unsigned int irq;
>> +     const char *name;
>> +     unsigned int virq;
>> +};
>> +
>> +static struct gsc_irq gsc_irqs[] = {
>> +     { GSC_IRQ_PB,           "button" },
>> +     { GSC_IRQ_KEY_ERASED,   "key-erased" },
>> +     { GSC_IRQ_EEPROM_WP,    "eeprom-wp" },
>> +     { GSC_IRQ_TAMPER,       "tamper" },
>> +     { GSC_IRQ_SWITCH_HOLD,  "button-held" },
>> +};
>> +
>> +struct gsc_input_info {
>> +     struct device *dev;
>> +     struct gsc_dev *gsc;
>> +     struct input_dev *input;
>> +
>> +     int irq;
>> +     struct work_struct irq_work;
>> +     struct mutex mutex;
>> +};
>> +
>> +static void gsc_input_irq_work(struct work_struct *work)
>> +{
>> +     struct gsc_input_info *info = container_of(work, struct gsc_input_info,
>> +                                                irq_work);
>> +     struct gsc_dev *gsc = info->gsc;
>> +     int i, ret = 0;
>> +     int key, sts;
>> +     struct gsc_irq *gsc_irq = NULL;
>> +
>> +     dev_dbg(gsc->dev, "%s irq%d\n", __func__, info->irq);
>> +     mutex_lock(&info->mutex);
>> +
>> +     for (i = 0; i < ARRAY_SIZE(gsc_irqs);i++)
>> +             if (info->irq == gsc_irqs[i].virq)
>> +                     gsc_irq = &gsc_irqs[i];
>> +     if (!gsc_irq) {
>> +             dev_err(info->dev, "interrupt: irq%d occurred\n", info->irq);
>> +             mutex_unlock(&info->mutex);
>> +             return;
>> +     }
>> +
>> +     ret = regmap_read(info->gsc->regmap, GSC_IRQ_STATUS, &sts);
>
> Why is this needed? To clear irq? What happens if several events happen
> at the same time? Do we lose one of them?

it was for original debugging and not needed - will remove

>
>> +     if (ret) {
>> +             dev_err(info->dev, "failed to read status register\n");
>> +             mutex_unlock(&info->mutex);
>> +             return;
>> +     }
>> +
>> +     key = -1;
>> +     switch (gsc_irq->virq) {
>> +     case GSC_IRQ_PB:
>> +             key = BTN_0;
>> +             break;
>> +     case GSC_IRQ_KEY_ERASED:
>> +             key = BTN_1;
>> +             break;
>> +     case GSC_IRQ_EEPROM_WP:
>> +             key = BTN_2;
>> +             break;
>> +     case GSC_IRQ_GPIO:
>> +             key = BTN_3;
>> +             break;
>> +     case GSC_IRQ_TAMPER:
>> +             key = BTN_4;
>> +             break;
>> +     case GSC_IRQ_SWITCH_HOLD:
>> +             key = BTN_5;
>
> Could we provide the mapping in DTS instead of hard-coding them?

Yes, that makes sense. I'll propose something like the following in v2:

gsc_input {
   compatible = "gw,gsc-input";

   button {
      label = "user pushbutton";
      linux,code = <256>;
      interrupts = <0>
   };

   key-erased {
      label = "key erased";
      linux,code = <257>;
      interrupts = <1>
   };

   ...
};


>
>> +             break;
>> +     }
>> +
>> +     if (key != -1) {
>> +             dev_dbg(&info->input->dev, "bit%d: key=0x%03x %s\n",
>> +                     gsc_irq->virq, key, gsc_irq->name);
>> +             input_report_key(info->input, key, 1);
>
>                 input_sync();

right - thanks!

>
>> +             input_report_key(info->input, key, 0);
>> +             input_sync(info->input);
>> +     }
>> +
>> +     mutex_unlock(&info->mutex);
>> +}
>> +
>> +static irqreturn_t gsc_input_irq(int irq, void *data)
>> +{
>> +     struct gsc_input_info *info = data;
>> +
>> +     dev_dbg(info->gsc->dev, "%s irq%d\n", __func__, irq);
>> +     info->irq = irq;
>> +     schedule_work(&info->irq_work);
>
> Why not use threaded interrupt?

I am using request_threaded_irq with thread_fn with thread_fn (vs handler).

Do you mean why use a work procedure? I guess I don't need that and
can call input_report_key directly from the irq.

>
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int gsc_input_probe(struct platform_device *pdev)
>> +{
>> +     struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
>> +     struct gsc_input_info *info;
>> +     struct input_dev *input;
>> +     int ret, i;
>> +
>> +     dev_dbg(&pdev->dev, "%s\n", __func__);
>> +     info = devm_kzalloc(&pdev->dev, sizeof(struct gsc_input_info),
>> +                         GFP_KERNEL);
>> +     if (!info)
>> +             return -ENOMEM;
>> +     info->dev = &pdev->dev;
>> +     info->gsc = gsc;
>> +
>> +     /* Register input device */
>> +     input = devm_input_allocate_device(&pdev->dev);
>> +     if (!input) {
>> +             dev_err(&pdev->dev, "Can't allocate input device\n");
>> +             return -ENOMEM;
>> +     }
>> +     info->input = input;
>> +
>> +     input->name = KBUILD_MODNAME;
>> +     input->dev.parent = &pdev->dev;
>
> Not needed - it is set by devm_input_allocate_device().

ok

>
>> +
>> +     input_set_capability(input, EV_KEY, BTN_0); /* button */
>> +     input_set_capability(input, EV_KEY, BTN_1); /* key erased */
>> +     input_set_capability(input, EV_KEY, BTN_2); /* ee wp */
>> +     input_set_capability(input, EV_KEY, BTN_3); /* gpio */
>> +     input_set_capability(input, EV_KEY, BTN_4); /* tamper */
>> +     input_set_capability(input, EV_KEY, BTN_5); /* button held */
>> +
>> +     ret = input_register_device(input);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "Can't register input device: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     platform_set_drvdata(pdev, gsc);
>> +     mutex_init(&info->mutex);
>> +     INIT_WORK(&info->irq_work, gsc_input_irq_work);
>> +
>> +     /* Support irq domain */
>> +     for (i = 0; i < ARRAY_SIZE(gsc_irqs); i++) {
>> +             struct gsc_irq *gsc_irq = &gsc_irqs[i];
>> +             int virq;
>> +
>> +             virq = regmap_irq_get_virq(gsc->irq_chip_data, gsc_irq->irq);
>> +             if (virq <= 0)
>> +                     return -EINVAL;
>> +             gsc_irq->virq = virq;
>
> I'd say mapping should be done by MFD piece. You can add interrupts as
> resources and fetch them here.

can you point me to an example dts/driver?

Tim

>
>> +
>> +             ret = devm_request_threaded_irq(&pdev->dev, virq, NULL,
>> +                                             gsc_input_irq, 0,
>> +                                             gsc_irq->name, info);
>> +             if (ret) {
>> +                     dev_err(&pdev->dev,
>> +                             "failed: irq request (IRQ: %d, error: %d\n)",
>> +                             gsc_irq->irq, ret);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct of_device_id gsc_input_dt_ids[] = {
>> +     { .compatible = "gw,gsc-input", },
>> +     {}
>> +};
>> +
>> +static struct platform_driver gsc_input_driver = {
>> +     .driver = {
>> +             .name = KBUILD_MODNAME,
>> +             .of_match_table = gsc_input_dt_ids,
>> +     },
>> +     .probe = gsc_input_probe,
>> +};
>> +
>> +module_platform_driver(gsc_input_driver);
>> +
>> +MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
>> +MODULE_DESCRIPTION("GSC input driver");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 2.7.4
>>
>
> Thanks.
>
> --
> Dmitry

^ permalink raw reply

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
From: Andy Shevchenko @ 2018-02-28 19:21 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input
In-Reply-To: <20180228184322.29636-2-rodrigorivascosta@gmail.com>

On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
<rodrigorivascosta@gmail.com> wrote:
> There are two ways to connect the Steam Controller: directly to the USB
> or with the USB wireless adapter.  Both methods are similar, but the
> wireless adapter can connect up to 4 devices at the same time.
>
> The wired device will appear as 3 interfaces: a virtual mouse, a virtual
> keyboard and a custom HID device.
>
> The wireless device will appear as 5 interfaces: a virtual keyboard and
> 4 custom HID devices, that will remain silent until a device is actually
> connected.
>
> The custom HID device has a report descriptor with all vendor specific
> usages, so the hid-generic is not very useful. In a PC/SteamBox Valve
> Steam Client provices a software translation by using direct USB access
> and a creates a uinput virtual gamepad.
>
> This driver was reverse engineered to provide direct kernel support in
> case you cannot, or do not want to, use Valve Steam Client. It disables
> the virtual keyboard and mouse, as they are not so useful when you have
> a working gamepad.


> +// SPDX-License-Identifier: GPL-2.0

> +MODULE_LICENSE("GPL");

Not the same.

> +static void steam_unregister(struct steam_device *steam)
> +{
> +       struct input_dev *input;
> +
> +       rcu_read_lock();
> +       input = rcu_dereference(steam->input);
> +       rcu_read_unlock();
> +

> +       if (input) {

if (!input)
 return;

?

> +               RCU_INIT_POINTER(steam->input, NULL);
> +               synchronize_rcu();
> +               hid_info(steam->hdev, "Steam Controller disconnected");
> +               input_unregister_device(input);
> +       }
> +}

> +static bool steam_is_valve_interface(struct hid_device *hdev)
> +{
> +       struct hid_report_enum *rep_enum;
> +       struct hid_report *hreport;
> +
> +       /*
> +        * The wired device creates 3 interfaces:
> +        *  0: emulated mouse.
> +        *  1: emulated keyboard.
> +        *  2: the real game pad.
> +        * The wireless device creates 5 interfaces:
> +        *  0: emulated keyboard.
> +        *  1-4: slots where up to 4 real game pads will be connected to.
> +        * We know which one is the real gamepad interface because they are the
> +        * only ones with a feature report.
> +        */
> +       rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];

> +       list_for_each_entry(hreport, &rep_enum->report_list, list) {
> +               /* should we check hreport->id == 0? */
> +               return true;
> +       }
> +       return false;

So, for now it's just an equivalent of

return !list_empty();

?

> +}

> +       /*
> +        * From this point on, if anything fails return 0 and ignores
> +        * the error, so that the default HID devices are still bound.
> +        */
> +       steam = devm_kzalloc(&hdev->dev,
> +                       sizeof(struct steam_device), GFP_KERNEL);

sizeof(*steam) saves a line.

> +       if (!steam) {
> +               ret = -ENOMEM;
> +               goto mem_fail;
> +       }

> +static void steam_remove(struct hid_device *hdev)
> +{
> +       struct steam_device *steam = hid_get_drvdata(hdev);
> +
> +       if (steam && (steam->quirks & STEAM_QUIRK_WIRELESS)) {
> +               hid_info(hdev, "Steam wireless receiver disconnected");
> +               hid_hw_close(hdev);
> +       }
> +
> +       hid_hw_stop(hdev);
> +
> +       if (steam) {
> +               cancel_work_sync(&steam->work_connect);
> +               steam_unregister(steam);

> +               hid_set_drvdata(hdev, NULL);

Hmm.. Doesn't HID do this?

> +       }

if (steam) {
...
       hid_hw_stop(hdev);
...
} else {
       hid_hw_stop(hdev);
}

?

> +}

> +static void steam_do_input_event(struct steam_device *steam,
> +               struct input_dev *input, u8 *data)
> +{
> +       /* 24 bits of buttons */
> +       u8 b8, b9, b10;
> +       bool lpad_touched, lpad_and_joy;
> +
> +       b8 = data[8];
> +       b9 = data[9];
> +       b10 = data[10];
> +
> +       input_report_abs(input, ABS_Z, data[11]);
> +       input_report_abs(input, ABS_RZ, data[12]);
> +
> +       /*
> +        * These two bits tells how to interpret the values X and Y.
> +        * lpad_and_joy tells that the joystick and the lpad are used at the
> +        * same time.
> +        * lpad_touched tells whether X/Y are to be read as lpad coord or
> +        * joystick values.
> +        * (lpad_touched || lpad_and_joy) tells if the lpad is really touched.
> +        */

> +       lpad_touched = b10 & 0x08;

BIT(3) ?

> +       lpad_and_joy = b10 & 0x80;

BIT(7) ?

> +       input_event(input, EV_KEY, BTN_TR2, !!(b8 & 0x01));
> +       input_event(input, EV_KEY, BTN_TL2, !!(b8 & 0x02));
> +       input_event(input, EV_KEY, BTN_TR, !!(b8 & 0x04));
> +       input_event(input, EV_KEY, BTN_TL, !!(b8 & 0x08));
> +       input_event(input, EV_KEY, BTN_Y, !!(b8 & 0x10));
> +       input_event(input, EV_KEY, BTN_B, !!(b8 & 0x20));
> +       input_event(input, EV_KEY, BTN_X, !!(b8 & 0x40));
> +       input_event(input, EV_KEY, BTN_A, !!(b8 & 0x80));
> +       input_event(input, EV_KEY, BTN_SELECT, !!(b9 & 0x10));
> +       input_event(input, EV_KEY, BTN_MODE, !!(b9 & 0x20));
> +       input_event(input, EV_KEY, BTN_START, !!(b9 & 0x40));
> +       input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & 0x80));
> +       input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & 0x01));
> +       input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & 0x04));
> +       input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & 0x40));
> +       input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
> +       input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & 0x10));

BIT(x) ?

> +
> +       input_report_abs(input, ABS_HAT0X,
> +                       !!(b9 & 0x02) - !!(b9 & 0x04));
> +       input_report_abs(input, ABS_HAT0Y,
> +                       !!(b9 & 0x08) - !!(b9 & 0x01));

BIT(x) ?

> +}

> +static int steam_raw_event(struct hid_device *hdev,
> +                       struct hid_report *report, u8 *data,
> +                       int size)
> +{
> +       struct steam_device *steam = hid_get_drvdata(hdev);
> +       struct input_dev *input;
> +

> +       if (!steam)
> +               return 0;

When it's possible?

> +       return 0;
> +}

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [RFC 2/4] mfd: add Gateworks System Controller core driver
From: Andrew Lunn @ 2018-02-28 18:53 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown, Dmitry Torokhov,
	linux-hwmon, devicetree, linux-kernel, linux-arm-kernel,
	linux-input
In-Reply-To: <1519780874-8558-3-git-send-email-tharvey@gateworks.com>

> +		dev_err(&client->dev, ">> 0x%02x %d\n", reg, ret);
> +		return ret;
> +	}
> +	dev_dbg(&client->dev, ">> 0x%02x=0x%02x (%d)\n", reg, val, retry);
> +
> +        return 0;

Hi Tim

There appears to be a few spaces vs tabs issues in this file.

      Andrew
 

^ permalink raw reply

* [PATCH v4 4/4] HID: steam: add battery device.
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa
In-Reply-To: <20180228184322.29636-1-rodrigorivascosta@gmail.com>

The wireless Steam Controller is battery operated, so add the battery
device and power information.

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/hid-steam.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 140 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 894b678685d3..ff38fddb4f6c 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -19,6 +19,7 @@
 #include <linux/workqueue.h>
 #include <linux/rcupdate.h>
 #include <linux/delay.h>
+#include <linux/power_supply.h>
 #include "hid-ids.h"
 
 MODULE_LICENSE("GPL");
@@ -43,6 +44,10 @@ struct steam_device {
 	struct work_struct work_connect;
 	bool connected;
 	char serial_no[11];
+	struct power_supply_desc battery_desc;
+	struct power_supply __rcu *battery;
+	u8 battery_charge;
+	u16 voltage;
 };
 
 static int steam_recv_report(struct steam_device *steam,
@@ -164,6 +169,85 @@ static void steam_input_close(struct input_dev *dev)
 	hid_hw_close(steam->hdev);
 }
 
+static enum power_supply_property steam_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int steam_battery_get_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	struct steam_device *steam = power_supply_get_drvdata(psy);
+	unsigned long flags;
+	s16 volts;
+	u8 batt;
+	int ret = 0;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	volts = steam->voltage;
+	batt = steam->battery_charge;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = volts * 1000; /* mV -> uV */
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = batt;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int steam_battery_register(struct steam_device *steam)
+{
+	struct power_supply *battery;
+	struct power_supply_config battery_cfg = { .drv_data = steam, };
+	unsigned long flags;
+	int ret;
+
+	steam->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+	steam->battery_desc.properties = steam_battery_props;
+	steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props);
+	steam->battery_desc.get_property = steam_battery_get_property;
+	steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev,
+			GFP_KERNEL, "steam-controller-%s-battery",
+			steam->serial_no);
+	if (!steam->battery_desc.name)
+		return -ENOMEM;
+
+	/* avoid the warning of 0% battery while waiting for the first info */
+	spin_lock_irqsave(&steam->lock, flags);
+	steam->voltage = 3000;
+	steam->battery_charge = 100;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	battery = power_supply_register(&steam->hdev->dev,
+			&steam->battery_desc, &battery_cfg);
+	if (IS_ERR(battery)) {
+		ret = PTR_ERR(battery);
+		hid_err(steam->hdev,
+				"%s:power_supply_register failed with error %d\n",
+				__func__, ret);
+		return ret;
+	}
+	rcu_assign_pointer(steam->battery, battery);
+	power_supply_powers(battery, &steam->hdev->dev);
+	return 0;
+}
+
 static int steam_register(struct steam_device *steam)
 {
 	struct hid_device *hdev = steam->hdev;
@@ -251,6 +335,10 @@ static int steam_register(struct steam_device *steam)
 
 	rcu_assign_pointer(steam->input, input);
 
+	/* ignore battery errors, we can live without it */
+	if (steam->quirks & STEAM_QUIRK_WIRELESS)
+		steam_battery_register(steam);
+
 	return 0;
 
 input_register_fail:
@@ -261,11 +349,18 @@ static int steam_register(struct steam_device *steam)
 static void steam_unregister(struct steam_device *steam)
 {
 	struct input_dev *input;
+	struct power_supply *battery;
 
 	rcu_read_lock();
 	input = rcu_dereference(steam->input);
+	battery = rcu_dereference(steam->battery);
 	rcu_read_unlock();
 
+	if (battery) {
+		RCU_INIT_POINTER(steam->battery, NULL);
+		synchronize_rcu();
+		power_supply_unregister(battery);
+	}
 	if (input) {
 		RCU_INIT_POINTER(steam->input, NULL);
 		synchronize_rcu();
@@ -568,12 +663,44 @@ static void steam_do_input_event(struct steam_device *steam,
 	input_sync(input);
 }
 
+/*
+ * The size for this message payload is 11.
+ * The known values are:
+ *  Offset| Type  | Meaning
+ * -------+-------+---------------------------
+ *  4-7   | u32   | sequence number
+ *  8-11  | --    | always 0
+ *  12-13 | u16   | voltage (mV)
+ *  14    | u8    | battery percent
+ */
+static void steam_do_battery_event(struct steam_device *steam,
+		struct power_supply *battery, u8 *data)
+{
+	unsigned long flags;
+
+	s16 volts = le16_to_cpup((__le16 *)(data + 12));
+	u8 batt = data[14];
+
+	/* Creating the battery may have failed */
+	rcu_read_lock();
+	battery = rcu_dereference(steam->battery);
+	if (likely(battery)) {
+		spin_lock_irqsave(&steam->lock, flags);
+		steam->voltage = volts;
+		steam->battery_charge = batt;
+		spin_unlock_irqrestore(&steam->lock, flags);
+		power_supply_changed(battery);
+	}
+	rcu_read_unlock();
+}
+
 static int steam_raw_event(struct hid_device *hdev,
 			struct hid_report *report, u8 *data,
 			int size)
 {
 	struct steam_device *steam = hid_get_drvdata(hdev);
 	struct input_dev *input;
+	struct power_supply *battery;
 
 	if (!steam)
 		return 0;
@@ -626,7 +753,19 @@ static int steam_raw_event(struct hid_device *hdev,
 		}
 		break;
 	case 0x04:
-		/* TODO battery status */
+		if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+			rcu_read_lock();
+			battery = rcu_dereference(steam->battery);
+			if (likely(battery)) {
+				steam_do_battery_event(steam, battery, data);
+			} else {
+				dbg_hid(
+				  "%s: battery data without connect event\n",
+				  __func__);
+				steam_do_connect_event(steam, true);
+			}
+			rcu_read_unlock();
+		}
 		break;
 	}
 	return 0;
-- 
2.16.2

^ permalink raw reply related

* [PATCH v4 3/4] HID: steam: command to check wireless connection
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa
In-Reply-To: <20180228184322.29636-1-rodrigorivascosta@gmail.com>

The wireless adaptor does not tell if a device is already connected when
steam_probe() is run.
Use a command to request the connection status.

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/hid-steam.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 9e4b1f640bef..894b678685d3 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -114,6 +114,11 @@ static int steam_send_report(struct steam_device *steam,
 	return ret;
 }
 
+static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd)
+{
+	return steam_send_report(steam, &cmd, 1);
+}
+
 static int steam_get_serial(struct steam_device *steam)
 {
 	/*
@@ -135,6 +140,16 @@ static int steam_get_serial(struct steam_device *steam)
 	return 0;
 }
 
+/*
+ * This command requests the wireless adaptor to post an event
+ * with the connection status. Useful if this driver is loaded when
+ * the controller is already connected.
+ */
+static inline int steam_request_conn_status(struct steam_device *steam)
+{
+	return steam_send_report_byte(steam, 0xb4);
+}
+
 static int steam_input_open(struct input_dev *dev)
 {
 	struct steam_device *steam = input_get_drvdata(dev);
@@ -363,6 +378,7 @@ static int steam_probe(struct hid_device *hdev,
 			goto hid_hw_open_fail;
 		}
 		hid_info(hdev, "Steam wireless receiver connected");
+		steam_request_conn_status(steam);
 	} else {
 		ret = steam_register(steam);
 		if (ret) {
-- 
2.16.2

^ permalink raw reply related

* [PATCH v4 2/4] HID: steam: add serial number information.
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa
In-Reply-To: <20180228184322.29636-1-rodrigorivascosta@gmail.com>

This device has a feature report to send and receive commands.
Use it to get the serial number and set the device's uniq value.

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/hid-steam.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 101 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 96256426c366..9e4b1f640bef 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -18,6 +18,7 @@
 #include <linux/module.h>
 #include <linux/workqueue.h>
 #include <linux/rcupdate.h>
+#include <linux/delay.h>
 #include "hid-ids.h"
 
 MODULE_LICENSE("GPL");
@@ -41,8 +42,99 @@ struct steam_device {
 	unsigned long quirks;
 	struct work_struct work_connect;
 	bool connected;
+	char serial_no[11];
 };
 
+static int steam_recv_report(struct steam_device *steam,
+		u8 *data, int size)
+{
+	struct hid_report *r;
+	u8 *buf;
+	int ret;
+
+	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
+	if (hid_report_len(r) < 64)
+		return -EINVAL;
+	buf = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/*
+	 * The report ID is always 0, so strip the first byte from the output.
+	 * hid_report_len() is not counting the report ID, so +1 to the length
+	 * or else we get a EOVERFLOW. We are safe from a buffer overflow
+	 * because hid_alloc_report_buf() allocates +7 bytes.
+	 */
+	ret = hid_hw_raw_request(steam->hdev, 0x00,
+			buf, hid_report_len(r) + 1,
+			HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret > 0)
+		memcpy(data, buf + 1, min(size, ret - 1));
+	kfree(buf);
+	return ret;
+}
+
+static int steam_send_report(struct steam_device *steam,
+		u8 *cmd, int size)
+{
+	struct hid_report *r;
+	u8 *buf;
+	int retry;
+	int ret;
+
+	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
+	if (hid_report_len(r) < 64)
+		return -EINVAL;
+	buf = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/* The report ID is always 0 */
+	memcpy(buf + 1, cmd, size);
+
+	/*
+	 * Sometimes the wireless controller fails with EPIPE
+	 * when sending a feature report.
+	 * Doing a HID_REQ_GET_REPORT and waiting for a while
+	 * seems to fix that.
+	 */
+	for (retry = 0; retry < 10; ++retry) {
+		ret = hid_hw_raw_request(steam->hdev, 0,
+				buf, size + 1,
+				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+		if (ret != -EPIPE)
+			break;
+		steam_recv_report(steam, NULL, 0);
+		msleep(50);
+	}
+	kfree(buf);
+	if (ret < 0)
+		hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__,
+				ret, size, cmd);
+	return ret;
+}
+
+static int steam_get_serial(struct steam_device *steam)
+{
+	/*
+	 * Send: 0xae 0x15 0x01
+	 * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
+	 */
+	int ret;
+	u8 cmd[] = {0xae, 0x15, 0x01};
+	u8 reply[14];
+
+	ret = steam_send_report(steam, cmd, sizeof(cmd));
+	if (ret < 0)
+		return ret;
+	ret = steam_recv_report(steam, reply, sizeof(reply));
+	if (ret < 0)
+		return ret;
+	reply[13] = 0;
+	strcpy(steam->serial_no, reply + 3);
+	return 0;
+}
+
 static int steam_input_open(struct input_dev *dev)
 {
 	struct steam_device *steam = input_get_drvdata(dev);
@@ -71,7 +163,12 @@ static int steam_register(struct steam_device *steam)
 		return 0;
 	}
 
-	hid_info(hdev, "Steam Controller connected");
+	ret = steam_get_serial(steam);
+	if (ret)
+		return ret;
+
+	hid_info(hdev, "Steam Controller '%s' connected",
+			steam->serial_no);
 
 	input = input_allocate_device();
 	if (!input)
@@ -86,7 +183,7 @@ static int steam_register(struct steam_device *steam)
 		"Wireless Steam Controller" :
 		"Steam Controller";
 	input->phys = hdev->phys;
-	input->uniq = hdev->uniq;
+	input->uniq = steam->serial_no;
 	input->id.bustype = hdev->bus;
 	input->id.vendor = hdev->vendor;
 	input->id.product = hdev->product;
@@ -157,7 +254,8 @@ static void steam_unregister(struct steam_device *steam)
 	if (input) {
 		RCU_INIT_POINTER(steam->input, NULL);
 		synchronize_rcu();
-		hid_info(steam->hdev, "Steam Controller disconnected");
+		hid_info(steam->hdev, "Steam Controller '%s' disconnected",
+				steam->serial_no);
 		input_unregister_device(input);
 	}
 }
-- 
2.16.2

^ permalink raw reply related

* [PATCH v4 1/4] HID: add driver for Valve Steam Controller
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa
In-Reply-To: <20180228184322.29636-1-rodrigorivascosta@gmail.com>

There are two ways to connect the Steam Controller: directly to the USB
or with the USB wireless adapter.  Both methods are similar, but the
wireless adapter can connect up to 4 devices at the same time.

The wired device will appear as 3 interfaces: a virtual mouse, a virtual
keyboard and a custom HID device.

The wireless device will appear as 5 interfaces: a virtual keyboard and
4 custom HID devices, that will remain silent until a device is actually
connected.

The custom HID device has a report descriptor with all vendor specific
usages, so the hid-generic is not very useful. In a PC/SteamBox Valve
Steam Client provices a software translation by using direct USB access
and a creates a uinput virtual gamepad.

This driver was reverse engineered to provide direct kernel support in
case you cannot, or do not want to, use Valve Steam Client. It disables
the virtual keyboard and mouse, as they are not so useful when you have
a working gamepad.

Working: buttons, axes, pads, wireless connect/disconnect.

TO-DO: Battery, force-feedback, accelerometer/gyro, led, beeper...

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/Kconfig     |   8 +
 drivers/hid/Makefile    |   1 +
 drivers/hid/hid-ids.h   |   4 +
 drivers/hid/hid-steam.c | 544 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 557 insertions(+)
 create mode 100644 drivers/hid/hid-steam.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 19c499f5623d..6e80fbf04e03 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -823,6 +823,14 @@ config HID_SPEEDLINK
 	---help---
 	Support for Speedlink Vicious and Divine Cezanne mouse.
 
+config HID_STEAM
+	tristate "Steam Controller support"
+	depends on HID
+	---help---
+	Say Y here if you have a Steam Controller if you want to use it
+	without running the Steam Client. It supports both the wired and
+	the wireless adaptor.
+
 config HID_STEELSERIES
 	tristate "Steelseries SRW-S1 steering wheel support"
 	depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index eb13b9e92d85..60a8abf84682 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -95,6 +95,7 @@ obj-$(CONFIG_HID_SAMSUNG)	+= hid-samsung.o
 obj-$(CONFIG_HID_SMARTJOYPLUS)	+= hid-sjoy.o
 obj-$(CONFIG_HID_SONY)		+= hid-sony.o
 obj-$(CONFIG_HID_SPEEDLINK)	+= hid-speedlink.o
+obj-$(CONFIG_HID_STEAM)		+= hid-steam.o
 obj-$(CONFIG_HID_STEELSERIES)	+= hid-steelseries.o
 obj-$(CONFIG_HID_SUNPLUS)	+= hid-sunplus.o
 obj-$(CONFIG_HID_GREENASIA)	+= hid-gaff.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 43ddcdfbd0da..be31a3c20818 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -988,6 +988,10 @@
 #define USB_VENDOR_ID_STANTUM_SITRONIX		0x1403
 #define USB_DEVICE_ID_MTP_SITRONIX		0x5001
 
+#define USB_VENDOR_ID_VALVE			0x28de
+#define USB_DEVICE_ID_STEAM_CONTROLLER		0x1102
+#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS	0x1142
+
 #define USB_VENDOR_ID_STEELSERIES	0x1038
 #define USB_DEVICE_ID_STEELSERIES_SRWS1	0x1410
 
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
new file mode 100644
index 000000000000..96256426c366
--- /dev/null
+++ b/drivers/hid/hid-steam.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID driver for Valve Steam Controller
+ *
+ * Copyright (c) 2018 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
+ *
+ * Supports both the wired and wireless interfaces.
+ *
+ * For additional functions, such as disabling the virtual mouse/keyboard or
+ * changing the right-pad margin you can use the user-space tool at:
+ *
+ *   https://github.com/rodrigorc/steamctrl
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/rcupdate.h>
+#include "hid-ids.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>");
+
+#define STEAM_QUIRK_WIRELESS		BIT(0)
+
+/* Touch pads are 40 mm in diameter and 65535 units */
+#define STEAM_PAD_RESOLUTION 1638
+/* Trigger runs are about 5 mm and 256 units */
+#define STEAM_TRIGGER_RESOLUTION 51
+/* Joystick runs are about 5 mm and 256 units */
+#define STEAM_JOYSTICK_RESOLUTION 51
+
+#define STEAM_PAD_FUZZ 256
+
+struct steam_device {
+	spinlock_t lock;
+	struct hid_device *hdev;
+	struct input_dev __rcu *input;
+	unsigned long quirks;
+	struct work_struct work_connect;
+	bool connected;
+};
+
+static int steam_input_open(struct input_dev *dev)
+{
+	struct steam_device *steam = input_get_drvdata(dev);
+
+	return hid_hw_open(steam->hdev);
+}
+
+static void steam_input_close(struct input_dev *dev)
+{
+	struct steam_device *steam = input_get_drvdata(dev);
+
+	hid_hw_close(steam->hdev);
+}
+
+static int steam_register(struct steam_device *steam)
+{
+	struct hid_device *hdev = steam->hdev;
+	struct input_dev *input;
+	int ret;
+
+	rcu_read_lock();
+	input = rcu_dereference(steam->input);
+	rcu_read_unlock();
+	if (input) {
+		dbg_hid("%s: already connected\n", __func__);
+		return 0;
+	}
+
+	hid_info(hdev, "Steam Controller connected");
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input_set_drvdata(input, steam);
+	input->dev.parent = &hdev->dev;
+	input->open = steam_input_open;
+	input->close = steam_input_close;
+
+	input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ?
+		"Wireless Steam Controller" :
+		"Steam Controller";
+	input->phys = hdev->phys;
+	input->uniq = hdev->uniq;
+	input->id.bustype = hdev->bus;
+	input->id.vendor = hdev->vendor;
+	input->id.product = hdev->product;
+	input->id.version = hdev->version;
+
+	input_set_capability(input, EV_KEY, BTN_TR2);
+	input_set_capability(input, EV_KEY, BTN_TL2);
+	input_set_capability(input, EV_KEY, BTN_TR);
+	input_set_capability(input, EV_KEY, BTN_TL);
+	input_set_capability(input, EV_KEY, BTN_Y);
+	input_set_capability(input, EV_KEY, BTN_B);
+	input_set_capability(input, EV_KEY, BTN_X);
+	input_set_capability(input, EV_KEY, BTN_A);
+	input_set_capability(input, EV_KEY, BTN_SELECT);
+	input_set_capability(input, EV_KEY, BTN_MODE);
+	input_set_capability(input, EV_KEY, BTN_START);
+	input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
+	input_set_capability(input, EV_KEY, BTN_GEAR_UP);
+	input_set_capability(input, EV_KEY, BTN_THUMBR);
+	input_set_capability(input, EV_KEY, BTN_THUMBL);
+	input_set_capability(input, EV_KEY, BTN_THUMB);
+	input_set_capability(input, EV_KEY, BTN_THUMB2);
+
+	input_set_abs_params(input, ABS_Z, 0, 255, 0, 0);
+	input_set_abs_params(input, ABS_RZ, 0, 255, 0, 0);
+	input_set_abs_params(input, ABS_X, -32767, 32767, 0, 0);
+	input_set_abs_params(input, ABS_Y, -32767, 32767, 0, 0);
+	input_set_abs_params(input, ABS_RX, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_RY, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT1X, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT1Y, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+	input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+	input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
+	input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
+	input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT1X, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT1Y, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_Z, STEAM_TRIGGER_RESOLUTION);
+	input_abs_set_res(input, ABS_RZ, STEAM_TRIGGER_RESOLUTION);
+
+	ret = input_register_device(input);
+	if (ret)
+		goto input_register_fail;
+
+	rcu_assign_pointer(steam->input, input);
+
+	return 0;
+
+input_register_fail:
+	input_free_device(input);
+	return ret;
+}
+
+static void steam_unregister(struct steam_device *steam)
+{
+	struct input_dev *input;
+
+	rcu_read_lock();
+	input = rcu_dereference(steam->input);
+	rcu_read_unlock();
+
+	if (input) {
+		RCU_INIT_POINTER(steam->input, NULL);
+		synchronize_rcu();
+		hid_info(steam->hdev, "Steam Controller disconnected");
+		input_unregister_device(input);
+	}
+}
+
+static void steam_work_connect_cb(struct work_struct *work)
+{
+	struct steam_device *steam = container_of(work, struct steam_device,
+							work_connect);
+	unsigned long flags;
+	bool connected;
+	int ret;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	connected = steam->connected;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	if (connected) {
+		ret = steam_register(steam);
+		if (ret) {
+			hid_err(steam->hdev,
+				"%s:steam_register failed with error %d\n",
+				__func__, ret);
+			return;
+		}
+	} else {
+		steam_unregister(steam);
+	}
+}
+
+static bool steam_is_valve_interface(struct hid_device *hdev)
+{
+	struct hid_report_enum *rep_enum;
+	struct hid_report *hreport;
+
+	/*
+	 * The wired device creates 3 interfaces:
+	 *  0: emulated mouse.
+	 *  1: emulated keyboard.
+	 *  2: the real game pad.
+	 * The wireless device creates 5 interfaces:
+	 *  0: emulated keyboard.
+	 *  1-4: slots where up to 4 real game pads will be connected to.
+	 * We know which one is the real gamepad interface because they are the
+	 * only ones with a feature report.
+	 */
+	rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+	list_for_each_entry(hreport, &rep_enum->report_list, list) {
+		/* should we check hreport->id == 0? */
+		return true;
+	}
+	return false;
+}
+
+static int steam_probe(struct hid_device *hdev,
+				const struct hid_device_id *id)
+{
+	struct steam_device *steam;
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev,
+			"%s:parse of hid interface failed\n", __func__);
+		return ret;
+	}
+
+	/*
+	 * Connect the HID device so that Steam Controller keeps on working
+	 * without changes.
+	 */
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev,
+			"%s:hid_hw_start failed with error %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	if (!steam_is_valve_interface(hdev))
+		return 0;
+
+	/*
+	 * From this point on, if anything fails return 0 and ignores
+	 * the error, so that the default HID devices are still bound.
+	 */
+	steam = devm_kzalloc(&hdev->dev,
+			sizeof(struct steam_device), GFP_KERNEL);
+	if (!steam) {
+		ret = -ENOMEM;
+		goto mem_fail;
+	}
+
+	spin_lock_init(&steam->lock);
+	steam->hdev = hdev;
+	hid_set_drvdata(hdev, steam);
+	steam->quirks = id->driver_data;
+	INIT_WORK(&steam->work_connect, steam_work_connect_cb);
+
+	if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+		ret = hid_hw_open(hdev);
+		if (ret) {
+			hid_err(hdev,
+				"%s:hid_hw_open for wireless\n",
+				__func__);
+			goto hid_hw_open_fail;
+		}
+		hid_info(hdev, "Steam wireless receiver connected");
+	} else {
+		ret = steam_register(steam);
+		if (ret) {
+			hid_err(hdev,
+				"%s:steam_register failed with error %d\n",
+				__func__, ret);
+			goto input_register_fail;
+		}
+	}
+
+	return 0;
+
+input_register_fail:
+hid_hw_open_fail:
+	cancel_work_sync(&steam->work_connect);
+	hid_set_drvdata(hdev, NULL);
+mem_fail:
+	hid_err(hdev, "%s: failed with error %d\n",
+			__func__, ret);
+	return 0;
+}
+
+static void steam_remove(struct hid_device *hdev)
+{
+	struct steam_device *steam = hid_get_drvdata(hdev);
+
+	if (steam && (steam->quirks & STEAM_QUIRK_WIRELESS)) {
+		hid_info(hdev, "Steam wireless receiver disconnected");
+		hid_hw_close(hdev);
+	}
+
+	hid_hw_stop(hdev);
+
+	if (steam) {
+		cancel_work_sync(&steam->work_connect);
+		steam_unregister(steam);
+		hid_set_drvdata(hdev, NULL);
+	}
+}
+
+static void steam_do_connect_event(struct steam_device *steam, bool connected)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	steam->connected = connected;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	if (schedule_work(&steam->work_connect) == 0)
+		dbg_hid("%s: connected=%d event already queued\n",
+				__func__, connected);
+}
+
+/*
+ * The size for this message payload is 60.
+ * The known values are:
+ *  (* values are not sent through wireless)
+ *  (* accelerator/gyro is disabled by default)
+ *  Offset| Type  | Mapped to |Meaning
+ * -------+-------+-----------+--------------------------
+ *  4-7   | u32   | --        | sequence number
+ *  8-10  | 24bit | see below | buttons
+ *  11    | u8    | ABS_Z     | left trigger
+ *  12    | u8    | ABS_RZ    | right trigger
+ *  13-15 | --    | --        | always 0
+ *  16-17 | s16   | ABS_X     | X value
+ *  18-19 | s16   | ABS_Y     | Y value
+ *  20-21 | s16   | ABS_RX    | right-pad X value
+ *  22-23 | s16   | ABS_RY    | right-pad Y value
+ *  24-25 | s16   | --        | * left trigger
+ *  26-27 | s16   | --        | * right trigger
+ *  28-29 | s16   | --        | * accelerometer X value
+ *  30-31 | s16   | --        | * accelerometer Y value
+ *  32-33 | s16   | --        | * accelerometer Z value
+ *  34-35 | s16   | --        | gyro X value
+ *  36-36 | s16   | --        | gyro Y value
+ *  38-39 | s16   | --        | gyro Z value
+ *  40-41 | s16   | --        | quaternion W value
+ *  42-43 | s16   | --        | quaternion X value
+ *  44-45 | s16   | --        | quaternion Y value
+ *  46-47 | s16   | --        | quaternion Z value
+ *  48-49 | --    | --        | always 0
+ *  50-51 | s16   | --        | * left trigger (uncalibrated)
+ *  52-53 | s16   | --        | * right trigger (uncalibrated)
+ *  54-55 | s16   | --        | * joystick X value (uncalibrated)
+ *  56-57 | s16   | --        | * joystick Y value (uncalibrated)
+ *  58-59 | s16   | --        | * left-pad X value
+ *  60-61 | s16   | --        | * left-pad Y value
+ *  62-63 | u16   | --        | * battery voltage
+ *
+ * The buttons are:
+ *  Bit  | Mapped to  | Description
+ * ------+------------+--------------------------------
+ *  8.0  | BTN_TR2    | right trigger fully pressed
+ *  8.1  | BTN_TL2    | left trigger fully pressed
+ *  8.2  | BTN_TR     | right shoulder
+ *  8.3  | BTN_TL     | left shoulder
+ *  8.4  | BTN_Y      | button Y
+ *  8.5  | BTN_B      | button B
+ *  8.6  | BTN_X      | button X
+ *  8.7  | BTN_A      | button A
+ *  9.0  | -ABS_HAT0Y | lef-pad up
+ *  9.1  | +ABS_HAT0X | lef-pad right
+ *  9.2  | -ABS_HAT0X | lef-pad left
+ *  9.3  | +ABS_HAT0Y | lef-pad down
+ *  9.4  | BTN_SELECT | menu left
+ *  9.5  | BTN_MODE   | steam logo
+ *  9.6  | BTN_START  | menu right
+ *  9.7  | BTN_GEAR_DOWN | left back lever
+ * 10.0  | BTN_GEAR_UP   | right back lever
+ * 10.1  | --         | left-pad clicked
+ * 10.2  | BTN_THUMBR | right-pad clicked
+ * 10.3  | BTN_THUMB  | left-pad touched (but see explanation below)
+ * 10.4  | BTN_THUMB2 | right-pad touched
+ * 10.5  | --         | unknown
+ * 10.6  | BTN_THUMBL | joystick clicked
+ * 10.7  | --         | lpad_and_joy
+ */
+
+static void steam_do_input_event(struct steam_device *steam,
+		struct input_dev *input, u8 *data)
+{
+	/* 24 bits of buttons */
+	u8 b8, b9, b10;
+	bool lpad_touched, lpad_and_joy;
+
+	b8 = data[8];
+	b9 = data[9];
+	b10 = data[10];
+
+	input_report_abs(input, ABS_Z, data[11]);
+	input_report_abs(input, ABS_RZ, data[12]);
+
+	/*
+	 * These two bits tells how to interpret the values X and Y.
+	 * lpad_and_joy tells that the joystick and the lpad are used at the
+	 * same time.
+	 * lpad_touched tells whether X/Y are to be read as lpad coord or
+	 * joystick values.
+	 * (lpad_touched || lpad_and_joy) tells if the lpad is really touched.
+	 */
+	lpad_touched = b10 & 0x08;
+	lpad_and_joy = b10 & 0x80;
+	input_report_abs(input, lpad_touched ? ABS_HAT1X : ABS_X,
+			(s16) le16_to_cpup((__le16 *)(data + 16)));
+	input_report_abs(input, lpad_touched ? ABS_HAT1Y : ABS_Y,
+			-(s16) le16_to_cpup((__le16 *)(data + 18)));
+	/* Check if joystick is centered */
+	if (lpad_touched && !lpad_and_joy) {
+		input_report_abs(input, ABS_X, 0);
+		input_report_abs(input, ABS_Y, 0);
+	}
+	/* Check if lpad is untouched */
+	if (!(lpad_touched || lpad_and_joy)) {
+		input_report_abs(input, ABS_HAT1X, 0);
+		input_report_abs(input, ABS_HAT1Y, 0);
+	}
+
+	input_report_abs(input, ABS_RX,
+			(s16) le16_to_cpup((__le16 *)(data + 20)));
+	input_report_abs(input, ABS_RY,
+			-(s16) le16_to_cpup((__le16 *)(data + 22)));
+
+	input_event(input, EV_KEY, BTN_TR2, !!(b8 & 0x01));
+	input_event(input, EV_KEY, BTN_TL2, !!(b8 & 0x02));
+	input_event(input, EV_KEY, BTN_TR, !!(b8 & 0x04));
+	input_event(input, EV_KEY, BTN_TL, !!(b8 & 0x08));
+	input_event(input, EV_KEY, BTN_Y, !!(b8 & 0x10));
+	input_event(input, EV_KEY, BTN_B, !!(b8 & 0x20));
+	input_event(input, EV_KEY, BTN_X, !!(b8 & 0x40));
+	input_event(input, EV_KEY, BTN_A, !!(b8 & 0x80));
+	input_event(input, EV_KEY, BTN_SELECT, !!(b9 & 0x10));
+	input_event(input, EV_KEY, BTN_MODE, !!(b9 & 0x20));
+	input_event(input, EV_KEY, BTN_START, !!(b9 & 0x40));
+	input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & 0x80));
+	input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & 0x01));
+	input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & 0x04));
+	input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & 0x40));
+	input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
+	input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & 0x10));
+
+	input_report_abs(input, ABS_HAT0X,
+			!!(b9 & 0x02) - !!(b9 & 0x04));
+	input_report_abs(input, ABS_HAT0Y,
+			!!(b9 & 0x08) - !!(b9 & 0x01));
+
+	input_sync(input);
+}
+
+static int steam_raw_event(struct hid_device *hdev,
+			struct hid_report *report, u8 *data,
+			int size)
+{
+	struct steam_device *steam = hid_get_drvdata(hdev);
+	struct input_dev *input;
+
+	if (!steam)
+		return 0;
+
+	/*
+	 * All messages are size=64, all values little-endian.
+	 * The format is:
+	 *  Offset| Meaning
+	 * -------+--------------------------------------------
+	 *  0-1   | always 0x01, 0x00, maybe protocol version?
+	 *  2     | type of message
+	 *  3     | length of the real payload (not checked)
+	 *  4-n   | payload data, depends on the type
+	 *
+	 * There are these known types of message:
+	 *  0x01: input data (60 bytes)
+	 *  0x03: wireless connect/disconnect (1 byte)
+	 *  0x04: battery status (11 bytes)
+	 */
+
+	if (size != 64 || data[0] != 1 || data[1] != 0)
+		return 0;
+
+	switch (data[2]) {
+	case 0x01:
+		rcu_read_lock();
+		input = rcu_dereference(steam->input);
+		if (likely(input)) {
+			steam_do_input_event(steam, input, data);
+		} else {
+			dbg_hid("%s: input data without connect event\n",
+					__func__);
+			steam_do_connect_event(steam, true);
+		}
+		rcu_read_unlock();
+		break;
+	case 0x03:
+		/*
+		 * The payload of this event is a single byte:
+		 *  0x01: disconnected.
+		 *  0x02: connected.
+		 */
+		switch (data[4]) {
+		case 0x01:
+			steam_do_connect_event(steam, false);
+			break;
+		case 0x02:
+			steam_do_connect_event(steam, true);
+			break;
+		}
+		break;
+	case 0x04:
+		/* TODO battery status */
+		break;
+	}
+	return 0;
+}
+
+static const struct hid_device_id steam_controllers[] = {
+	{ /* Wired Steam Controller */
+	  HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+		USB_DEVICE_ID_STEAM_CONTROLLER)
+	},
+	{ /* Wireless Steam Controller */
+	  HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+		USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS),
+	  .driver_data = STEAM_QUIRK_WIRELESS
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, steam_controllers);
+
+static struct hid_driver steam_controller_driver = {
+	.name = "hid-steam",
+	.id_table = steam_controllers,
+	.probe = steam_probe,
+	.remove = steam_remove,
+	.raw_event = steam_raw_event,
+};
+
+module_hid_driver(steam_controller_driver);
-- 
2.16.2

^ permalink raw reply related

* [PATCH v4 0/4] new driver for Valve Steam Controller
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa

This patchset implements a driver for Valve Steam Controller, based on a
reverse analysis by myself.

This is reroll v4, changes since v3:
 * Add command to check the wireless connection status on probe, without
   waiting for a message (thanks to Clément Vuchener for the tip).
 * Removed the error code on redundant connection/disconnection messages. That
   was harmless but polluted dmesg.
 * Added buttons for touching the left-pad and right-pad.
 * Fixed a misplaced #include from 2/4 to 1/4.

I've added the new command from the first point in a new commit, so now there
are 4 parts. Feel free to merge that with the previous (or all of them) if you
wish.

About the new buttons, before I considered those unnecesary because I thought
of the pads as a kind of flat joysticks, so when they are not touched they go
back to the virtual center (0,0), and the touch/untouch events are not useful.
But I've been proved short-sighted in this kind of decisions before, so I've
added them, sure they are useful for somebody.

Also, I've been running some tests, with the driver, the Steam Client and my
stemctrl utility [1], and all works just fine. Even unloading and reloading
the modules (with a UDEV rule to disable the lizard mode) while the Steam
Client is running works without any issue.

[1]: https://github.com/rodrigorc/steamctrl

Rodrigo Rivas Costa (4):
  HID: add driver for Valve Steam Controller
  HID: steam: add serial number information.
  HID: steam: command to check wireless connection
  HID: steam: add battery device.

 drivers/hid/Kconfig     |   8 +
 drivers/hid/Makefile    |   1 +
 drivers/hid/hid-ids.h   |   4 +
 drivers/hid/hid-steam.c | 797 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 810 insertions(+)
 create mode 100644 drivers/hid/hid-steam.c

-- 
2.16.2

^ permalink raw reply

* Re: [RFC 0/4] Add support for the Gateworks System Controller
From: Andrew Lunn @ 2018-02-28 16:56 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown, Dmitry Torokhov,
	linux-hwmon, devicetree, linux-kernel, linux-arm-kernel,
	linux-input
In-Reply-To: <CAJ+vNU3hG7z1mB0xKFZu-8o1r3hj-pOqkfVnWHvxxaxzrpGuzw@mail.gmail.com>

Hi Tim

Cool. I would say this is done right.

> One issue I'm trying to figure out the best way to deal with is the
> fact that the GSC can 'NAK' transactions occasionally which is why I
> override the regmap read/write functions and provide retries. This
> resolves the issue for the mfd core driver and sub-module drivers but
> doesn't resolve the issue with these 'emulated devices' which have
> their own stand-alone drivers. I'm not sure how to best deal with that
> yet. I tried to add retires to the i2c adapter but that wasn't
> accepted upstream because it was too generic and I was told I need to
> work around it in device-drivers.

How about writing an i2c bus driver which sits directly on top of
another i2c bus? Basically a one port i2c mux.

The current mux code does not seem to directly allow it, since it
calls i2c_transfer() directly on the parent, where as you want it to
call your own i2c_transfer function. But maybe you could expended the
core mux code to allow the i2c_mux_core structure to contain a transfer
function?

      Andrew

^ permalink raw reply

* Re: [RFC 0/4] Add support for the Gateworks System Controller
From: Tim Harvey @ 2018-02-28 16:34 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown, Dmitry Torokhov,
	linux-hwmon, devicetree, linux-kernel, linux-arm-kernel,
	linux-input
In-Reply-To: <20180228144424.GC12303@lunn.ch>

On Wed, Feb 28, 2018 at 6:44 AM, Andrew Lunn <andrew@lunn.ch> wrote:
> On Tue, Feb 27, 2018 at 05:21:10PM -0800, Tim Harvey wrote:
>> This series adds support for the Gateworks System Controller used on Gateworks
>> Laguna, Ventana, and Newport product families.
>>
>> The GSC is an MSP430 I2C slave controller whose firmware embeds the following
>> features:
>>  - I/O expander (16 GPIO's emulating a PCA955x)
>>  - EEPROM (enumating AT24)
>>  - RTC (enumating DS1672)
>
> Hi Tim
>
> Maybe it is in these patches, and i missed it....
>
> How do these emulated devices work? Does the controller respond to
> different addresses for these different emulated devices? Or is it an
> I2c bus mux?
>

Andrew,

You didn't miss it - I probably need to explain it better.

The 'emulated devices' do respond on different slave addresses (which
match one of or the only the slave addresses those parts support). For
example the device-tree for the GW54xx has the following which are all
from the GSC which is the only thing on i2c1:

&i2c1 {
        clock-frequency = <100000>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_i2c1>;
        status = "okay";

        gsc: gsc@20 {
                compatible = "gw,gsc_v2";
                reg = <0x20>;
                interrupt-parent = <&gpio1>;
                interrupts = <4 GPIO_ACTIVE_LOW>;
                interrupt-controller;
                #interrupt-cells = <1>;

                gsc_input {
                        compatible = "gw,gsc-input";
                };

                gsc_hwmon {
                        compatible = "gw,gsc-hwmon";
                        #address-cells = <1>;
                        #size-cells = <0>;

                        hwmon@0 { /* A0: Board Temperature */
                                type = <0>;
                                reg = <0x00>;
                                label = "temp";
                                /* lookup table */
                        };

                        hwmon@1 { /* A1: Input Voltage */
                                type = <1>;
                                reg = <0x02>;
                                label = "Vin";
                                gw,voltage-divider = <22100 1000>;
                                gw,offset = <800>;
                        };

                        hwmon@2 { /* A2: 5P0 */
                                type = <1>;
                                reg = <0x0b>;
                                label = "5P0";
                                gw,voltage-divider = <22100 1000>;
                        };

                        hwmon@4 { /* A4: 0-5V input */
                                type = <1>;
                                reg = <0x14>;
                                label = "ANL0";
                                gw,voltage-divider = <10000 10000>;
                        };

                        hwmon@5 { /* A5: 2P5 PCIe/GigE */
                                type = <1>;
                                reg = <0x23>;
                                label = "2P5";
                                gw,voltage-divider = <10000 10000>;
                        };

                        hwmon@6 { /* A6: 1P8 Aud/Vid */
                                type = <1>;
                                reg = <0x1d>;
                                label = "1P8";
                        };

                        hwmon@7 { /* A7: GPS */
                                type = <1>;
                                reg = <0x26>;
                                label = "GPS";
                                gw,voltage-divider = <4990 10000>;
                        };

                        hwmon@12 { /* A12: VDD_CORE */
                                type = <1>;
                                reg = <0x3>;
                                label = "VDD_CORE";
                        };

                        hwmon@13 { /* A13: VDD_SOC */
                                type = <1>;
                                reg = <0x11>;
                                label = "VDD_SOC";
                        };

                        hwmon@14 { /* A14: 1P0 PCIe SW */
                                type = <1>;
                                reg = <0x20>;
                                label = "1P0";
                        };

                        hwmon@15 { /* fan0 */
                                type = <2>;
                                reg = <0x2c>;
                                label = "fan_50p";
                        };

                        hwmon@16 { /* fan1 */
                                type = <2>;
                                reg = <0x2e>;
                                label = "fan_60p";
                        };

                        hwmon@17 { /* fan2 */
                                type = <2>;
                                reg = <0x30>;
                                label = "fan_70p";
                        };

                        hwmon@18 { /* fan3 */
                                type = <2>;
                                reg = <0x32>;
                                label = "fan_80p";
                        };

                        hwmon@19 { /* fan4 */
                                type = <2>;
                                reg = <0x34>;
                                label = "fan_90p";
                        };

                        hwmon@20 { /* fan5 */
                                type = <2>;
                                reg = <0x36>;
                                label = "fan_100p";
                        };
                };
        };

        gsc_gpio: pca9555@23 {
                compatible = "nxp,pca9555";
                reg = <0x23>;
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-parent = <&gsc>;
                interrupts = <4>;
        };

        eeprom1: eeprom@50 {
                compatible = "atmel,24c02";
                reg = <0x50>;
                pagesize = <16>;
        };

        eeprom2: eeprom@51 {
                compatible = "atmel,24c02";
                reg = <0x51>;
                pagesize = <16>;
        };

        eeprom3: eeprom@52 {
                compatible = "atmel,24c02";
                reg = <0x52>;
                pagesize = <16>;
        };

        eeprom4: eeprom@53 {
                compatible = "atmel,24c02";
                reg = <0x53>;
                pagesize = <16>;
        };

        rtc: ds1672@68 {
                compatible = "dallas,ds1672";
                reg = <0x68>;
        };
};


One issue I'm trying to figure out the best way to deal with is the
fact that the GSC can 'NAK' transactions occasionally which is why I
override the regmap read/write functions and provide retries. This
resolves the issue for the mfd core driver and sub-module drivers but
doesn't resolve the issue with these 'emulated devices' which have
their own stand-alone drivers. I'm not sure how to best deal with that
yet. I tried to add retires to the i2c adapter but that wasn't
accepted upstream because it was too generic and I was told I need to
work around it in device-drivers. I'm guessing I need to write my own
sub-module drivers that will largely duplicate whats in the
stand-alone drivers (ds1672, at24, pca9553x).

Regards,

Tim

^ permalink raw reply

* Re: [RFC 0/4] Add support for the Gateworks System Controller
From: Andrew Lunn @ 2018-02-28 14:44 UTC (permalink / raw)
  To: Tim Harvey
  Cc: Lee Jones, Rob Herring, Mark Rutland, Mark Brown, Dmitry Torokhov,
	linux-hwmon, devicetree, linux-kernel, linux-arm-kernel,
	linux-input
In-Reply-To: <1519780874-8558-1-git-send-email-tharvey@gateworks.com>

On Tue, Feb 27, 2018 at 05:21:10PM -0800, Tim Harvey wrote:
> This series adds support for the Gateworks System Controller used on Gateworks
> Laguna, Ventana, and Newport product families.
> 
> The GSC is an MSP430 I2C slave controller whose firmware embeds the following
> features:
>  - I/O expander (16 GPIO's emulating a PCA955x)
>  - EEPROM (enumating AT24)
>  - RTC (enumating DS1672)

Hi Tim

Maybe it is in these patches, and i missed it....

How do these emulated devices work? Does the controller respond to
different addresses for these different emulated devices? Or is it an
I2c bus mux?

    Andrew

^ permalink raw reply

* Re: [PATCH v3] Input: gpio_keys: Add level trigger support for GPIO keys
From: Arnd Bergmann @ 2018-02-28 14:44 UTC (permalink / raw)
  To: Baolin Wang
  Cc: Dmitry Torokhov, Rob Herring, Mark Rutland, gregkh, lumotuwe,
	Arvind Yadav, josephl, kstewart, Philippe Ombredanne,
	Thomas Gleixner, open list:HID CORE LAYER, DTML,
	Linux Kernel Mailing List, Mark Brown, Linus Walleij
In-Reply-To: <826093167e8fb24723f474b0272f3dcab1b6a97e.1519821626.git.baolin.wang@linaro.org>

On Wed, Feb 28, 2018 at 1:44 PM, Baolin Wang <baolin.wang@linaro.org> wrote:
> On some platforms (such as Spreadtrum platform), the GPIO keys can only
> be triggered by level type. So this patch introduces one property to
> indicate if the GPIO trigger type is level trigger or edge trigger.
>
> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
> ---
> Changes since v2:
>  - Use 'interrupt' property to indicate the irq type.
>
> Changes since v1:
>  - Diable the GPIO irq until reversing the GPIO level type.

I've looked at your patch in more detail now, and given it a bit more thought.

I wonder if you could move that logic into your gpiochip/irqchip driver instead.
It seems that what you do in the gpio-keys driver is to emulate edge triggered
behavior on a level triggered irqchip.

If you put the same logic into the gpio driver, you could simply make it
pretend to support an edge trigger on both edges and call into the interrupt
handler whenever the state changes.

        Arnd

^ permalink raw reply

* [RESEND PATCH 6/6] input: usbtouchscreen: do not rely on input_dev->users
From: Marcus Folkesson @ 2018-02-28 13:38 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel, Marcus Folkesson
In-Reply-To: <20180228133803.30040-1-marcus.folkesson@gmail.com>

If the device is unused and suspended, a call to open will cause the
device to autoresume through the call to usb_autopm_get_interface().

input_dev->users is already incremented by the input subsystem,
therefore this expression will always be evaluated to true:

	if (input->users || usbtouch->type->irq_always)
		result = usb_submit_urb(usbtouch->irq, GFP_NOIO);

The same URB will then be fail when resubmitted in usbtouch_open().

Introduce usbtouch->is_open to keep track of the state instead.

Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
 drivers/input/touchscreen/usbtouchscreen.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c
index e964658203d8..0356ad792e40 100644
--- a/drivers/input/touchscreen/usbtouchscreen.c
+++ b/drivers/input/touchscreen/usbtouchscreen.c
@@ -114,6 +114,7 @@ struct usbtouch_usb {
 	struct input_dev *input;
 	struct usbtouch_device_info *type;
 	struct mutex pm_mutex;  /* serialize access to open/suspend */
+	bool is_open;
 	char name[128];
 	char phys[64];
 	void *priv;
@@ -1466,6 +1467,7 @@ static int usbtouch_open(struct input_dev *input)
 	}
 
 	usbtouch->interface->needs_remote_wakeup = 1;
+	usbtouch->is_open = 1;
 out_put:
 	mutex_unlock(&usbtouch->pm_mutex);
 	usb_autopm_put_interface(usbtouch->interface);
@@ -1481,6 +1483,7 @@ static void usbtouch_close(struct input_dev *input)
 	mutex_lock(&usbtouch->pm_mutex);
 	if (!usbtouch->type->irq_always)
 		usb_kill_urb(usbtouch->irq);
+	usbtouch->is_open = 0;
 	mutex_lock(&usbtouch->pm_mutex);
 
 	r = usb_autopm_get_interface(usbtouch->interface);
@@ -1502,11 +1505,10 @@ static int usbtouch_suspend
 static int usbtouch_resume(struct usb_interface *intf)
 {
 	struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
-	struct input_dev *input = usbtouch->input;
 	int result = 0;
 
 	mutex_lock(&usbtouch->pm_mutex);
-	if (input->users || usbtouch->type->irq_always)
+	if (usbtouch->is_open || usbtouch->type->irq_always)
 		result = usb_submit_urb(usbtouch->irq, GFP_NOIO);
 	mutex_unlock(&usbtouch->pm_mutex);
 
@@ -1516,7 +1518,6 @@ static int usbtouch_resume(struct usb_interface *intf)
 static int usbtouch_reset_resume(struct usb_interface *intf)
 {
 	struct usbtouch_usb *usbtouch = usb_get_intfdata(intf);
-	struct input_dev *input = usbtouch->input;
 	int err = 0;
 
 	/* reinit the device */
@@ -1532,7 +1533,7 @@ static int usbtouch_reset_resume(struct usb_interface *intf)
 
 	/* restart IO if needed */
 	mutex_lock(&usbtouch->pm_mutex);
-	if (input->users)
+	if (usbtouch->is_open)
 		err = usb_submit_urb(usbtouch->irq, GFP_NOIO);
 	mutex_unlock(&usbtouch->pm_mutex);
 
-- 
2.16.2

^ permalink raw reply related

* [RESEND PATCH 5/6] input: usbtouchscreen: fix deadlock in autosuspend
From: Marcus Folkesson @ 2018-02-28 13:38 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel, Marcus Folkesson
In-Reply-To: <20180228133803.30040-1-marcus.folkesson@gmail.com>

usb_autopm_get_interface() that is called in usbtouch_open() does an
autoresume if the device is suspended.

input_dev->mutex used in usbtouch_resume() is in this case already
taken by the input subsystem and will cause a deadlock.

Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
 drivers/input/touchscreen/usbtouchscreen.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c
index 2c41107240de..e964658203d8 100644
--- a/drivers/input/touchscreen/usbtouchscreen.c
+++ b/drivers/input/touchscreen/usbtouchscreen.c
@@ -54,6 +54,7 @@
 #include <linux/usb.h>
 #include <linux/usb/input.h>
 #include <linux/hid.h>
+#include <linux/mutex.h>
 
 
 #define DRIVER_VERSION		"v0.6"
@@ -112,6 +113,7 @@ struct usbtouch_usb {
 	struct usb_interface *interface;
 	struct input_dev *input;
 	struct usbtouch_device_info *type;
+	struct mutex pm_mutex;  /* serialize access to open/suspend */
 	char name[128];
 	char phys[64];
 	void *priv;
@@ -1455,6 +1457,7 @@ static int usbtouch_open(struct input_dev *input)
 	if (r < 0)
 		goto out;
 
+	mutex_lock(&usbtouch->pm_mutex);
 	if (!usbtouch->type->irq_always) {
 		if (usb_submit_urb(usbtouch->irq, GFP_KERNEL)) {
 			r = -EIO;
@@ -1464,6 +1467,7 @@ static int usbtouch_open(struct input_dev *input)
 
 	usbtouch->interface->needs_remote_wakeup = 1;
 out_put:
+	mutex_unlock(&usbtouch->pm_mutex);
 	usb_autopm_put_interface(usbtouch->interface);
 out:
 	return r;
@@ -1474,8 +1478,11 @@ static void usbtouch_close(struct input_dev *input)
 	struct usbtouch_usb *usbtouch = input_get_drvdata(input);
 	int r;
 
+	mutex_lock(&usbtouch->pm_mutex);
 	if (!usbtouch->type->irq_always)
 		usb_kill_urb(usbtouch->irq);
+	mutex_lock(&usbtouch->pm_mutex);
+
 	r = usb_autopm_get_interface(usbtouch->interface);
 	usbtouch->interface->needs_remote_wakeup = 0;
 	if (!r)
@@ -1498,10 +1505,10 @@ static int usbtouch_resume(struct usb_interface *intf)
 	struct input_dev *input = usbtouch->input;
 	int result = 0;
 
-	mutex_lock(&input->mutex);
+	mutex_lock(&usbtouch->pm_mutex);
 	if (input->users || usbtouch->type->irq_always)
 		result = usb_submit_urb(usbtouch->irq, GFP_NOIO);
-	mutex_unlock(&input->mutex);
+	mutex_unlock(&usbtouch->pm_mutex);
 
 	return result;
 }
@@ -1524,10 +1531,10 @@ static int usbtouch_reset_resume(struct usb_interface *intf)
 	}
 
 	/* restart IO if needed */
-	mutex_lock(&input->mutex);
+	mutex_lock(&usbtouch->pm_mutex);
 	if (input->users)
 		err = usb_submit_urb(usbtouch->irq, GFP_NOIO);
-	mutex_unlock(&input->mutex);
+	mutex_unlock(&usbtouch->pm_mutex);
 
 	return err;
 }
-- 
2.16.2

^ permalink raw reply related

* [RESEND PATCH 4/6] input: pegasus_notetaker: do not rely on input_dev->users
From: Marcus Folkesson @ 2018-02-28 13:38 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel, Marcus Folkesson
In-Reply-To: <20180228133803.30040-1-marcus.folkesson@gmail.com>

If the device is unused and suspended, a call to open will cause the
device to autoresume through the call to usb_autopm_get_interface().

input_dev->users is already incremented by the input subsystem,
therefore this expression will always be evaluated to true:

	if (pegasus->dev->users && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
		retval = -EIO;

The same URB will then be fail when resubmitted in pegasus_open().

Introduce pegasus->is_open to keep track of the state instead.

Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
 drivers/input/tablet/pegasus_notetaker.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/input/tablet/pegasus_notetaker.c b/drivers/input/tablet/pegasus_notetaker.c
index 9ab1ed5e20e7..74c93e09c337 100644
--- a/drivers/input/tablet/pegasus_notetaker.c
+++ b/drivers/input/tablet/pegasus_notetaker.c
@@ -80,6 +80,7 @@ struct pegasus {
 
 	/* serialize access to open/suspend */
 	struct mutex pm_mutex;
+	bool is_open;
 
 	char name[128];
 	char phys[64];
@@ -232,6 +233,7 @@ static int pegasus_open(struct input_dev *dev)
 	if (error)
 		goto err_kill_urb;
 
+	pegasus->is_open = 1;
 	mutex_unlock(&pegasus->pm_mutex);
 	return 0;
 
@@ -251,6 +253,7 @@ static void pegasus_close(struct input_dev *dev)
 	mutex_lock(&pegasus->pm_mutex);
 	usb_kill_urb(pegasus->irq);
 	cancel_work_sync(&pegasus->init);
+	pegasus->is_open = 0;
 	mutex_unlock(&pegasus->pm_mutex);
 
 	usb_autopm_put_interface(pegasus->intf);
@@ -415,7 +418,7 @@ static int pegasus_resume(struct usb_interface *intf)
 	int retval = 0;
 
 	mutex_lock(&pegasus->pm_mutex);
-	if (pegasus->dev->users && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
+	if (pegasus->is_open && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
 		retval = -EIO;
 	mutex_unlock(&pegasus->pm_mutex);
 
@@ -428,7 +431,7 @@ static int pegasus_reset_resume(struct usb_interface *intf)
 	int retval = 0;
 
 	mutex_lock(&pegasus->pm_mutex);
-	if (pegasus->dev->users) {
+	if (pegasus->is_open) {
 		retval = pegasus_set_mode(pegasus, PEN_MODE_XY,
 					  NOTETAKER_LED_MOUSE);
 		if (!retval && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
-- 
2.16.2

^ permalink raw reply related

* [RESEND PATCH 3/6] input: pagasus_notetaker: fix deadlock in autosuspend
From: Marcus Folkesson @ 2018-02-28 13:38 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel, Marcus Folkesson
In-Reply-To: <20180228133803.30040-1-marcus.folkesson@gmail.com>

usb_autopm_get_interface() that is called in pegasus_open() does an
autoresume if the device is suspended.

input_dev->mutex used in pegasus_resume() is in this case already
taken by the input subsystem and will cause a deadlock.

Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
 drivers/input/tablet/pegasus_notetaker.c | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/drivers/input/tablet/pegasus_notetaker.c b/drivers/input/tablet/pegasus_notetaker.c
index 47de5a81172f..9ab1ed5e20e7 100644
--- a/drivers/input/tablet/pegasus_notetaker.c
+++ b/drivers/input/tablet/pegasus_notetaker.c
@@ -41,6 +41,7 @@
 #include <linux/usb/input.h>
 #include <linux/slab.h>
 #include <linux/workqueue.h>
+#include <linux/mutex.h>
 
 /* USB HID defines */
 #define USB_REQ_GET_REPORT		0x01
@@ -76,6 +77,10 @@ struct pegasus {
 	struct usb_device *usbdev;
 	struct usb_interface *intf;
 	struct urb *irq;
+
+	/* serialize access to open/suspend */
+	struct mutex pm_mutex;
+
 	char name[128];
 	char phys[64];
 	struct work_struct init;
@@ -216,6 +221,7 @@ static int pegasus_open(struct input_dev *dev)
 	if (error)
 		return error;
 
+	mutex_lock(&pegasus->pm_mutex);
 	pegasus->irq->dev = pegasus->usbdev;
 	if (usb_submit_urb(pegasus->irq, GFP_KERNEL)) {
 		error = -EIO;
@@ -226,12 +232,14 @@ static int pegasus_open(struct input_dev *dev)
 	if (error)
 		goto err_kill_urb;
 
+	mutex_unlock(&pegasus->pm_mutex);
 	return 0;
 
 err_kill_urb:
 	usb_kill_urb(pegasus->irq);
 	cancel_work_sync(&pegasus->init);
 err_autopm_put:
+	mutex_unlock(&pegasus->pm_mutex);
 	usb_autopm_put_interface(pegasus->intf);
 	return error;
 }
@@ -240,8 +248,11 @@ static void pegasus_close(struct input_dev *dev)
 {
 	struct pegasus *pegasus = input_get_drvdata(dev);
 
+	mutex_lock(&pegasus->pm_mutex);
 	usb_kill_urb(pegasus->irq);
 	cancel_work_sync(&pegasus->init);
+	mutex_unlock(&pegasus->pm_mutex);
+
 	usb_autopm_put_interface(pegasus->intf);
 }
 
@@ -274,6 +285,8 @@ static int pegasus_probe(struct usb_interface *intf,
 		goto err_free_mem;
 	}
 
+	mutex_init(&pegasus->pm_mutex);
+
 	pegasus->usbdev = dev;
 	pegasus->dev = input_dev;
 	pegasus->intf = intf;
@@ -388,10 +401,10 @@ static int pegasus_suspend(struct usb_interface *intf, pm_message_t message)
 {
 	struct pegasus *pegasus = usb_get_intfdata(intf);
 
-	mutex_lock(&pegasus->dev->mutex);
+	mutex_lock(&pegasus->pm_mutex);
 	usb_kill_urb(pegasus->irq);
 	cancel_work_sync(&pegasus->init);
-	mutex_unlock(&pegasus->dev->mutex);
+	mutex_unlock(&pegasus->pm_mutex);
 
 	return 0;
 }
@@ -401,10 +414,10 @@ static int pegasus_resume(struct usb_interface *intf)
 	struct pegasus *pegasus = usb_get_intfdata(intf);
 	int retval = 0;
 
-	mutex_lock(&pegasus->dev->mutex);
+	mutex_lock(&pegasus->pm_mutex);
 	if (pegasus->dev->users && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
 		retval = -EIO;
-	mutex_unlock(&pegasus->dev->mutex);
+	mutex_unlock(&pegasus->pm_mutex);
 
 	return retval;
 }
@@ -414,14 +427,14 @@ static int pegasus_reset_resume(struct usb_interface *intf)
 	struct pegasus *pegasus = usb_get_intfdata(intf);
 	int retval = 0;
 
-	mutex_lock(&pegasus->dev->mutex);
+	mutex_lock(&pegasus->pm_mutex);
 	if (pegasus->dev->users) {
 		retval = pegasus_set_mode(pegasus, PEN_MODE_XY,
 					  NOTETAKER_LED_MOUSE);
 		if (!retval && usb_submit_urb(pegasus->irq, GFP_NOIO) < 0)
 			retval = -EIO;
 	}
-	mutex_unlock(&pegasus->dev->mutex);
+	mutex_unlock(&pegasus->pm_mutex);
 
 	return retval;
 }
-- 
2.16.2

^ permalink raw reply related

* [RESEND PATCH 2/6] input: synaptics_usb: do not rely on input_dev->users
From: Marcus Folkesson @ 2018-02-28 13:37 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel, Marcus Folkesson
In-Reply-To: <20180228133803.30040-1-marcus.folkesson@gmail.com>

If the device is unused and suspended, a call to open will cause the
device to autoresume through the call to usb_autopm_get_interface().

input_dev->users is already incremented by the input subsystem,
therefore this expression will always be evaluated to true:

	if ((input_dev->users || (synusb->flags & SYNUSB_IO_ALWAYS)) &&
	    usb_submit_urb(synusb->urb, GFP_NOIO) < 0) {
		retval = -EIO;
	}

The same URB will then be fail when resubmitted in synusb_open().

Introduce synusb->is_open to keep track of the state instead.

Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
 drivers/input/mouse/synaptics_usb.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/input/mouse/synaptics_usb.c b/drivers/input/mouse/synaptics_usb.c
index 2c66913cf5a2..e2b726751220 100644
--- a/drivers/input/mouse/synaptics_usb.c
+++ b/drivers/input/mouse/synaptics_usb.c
@@ -84,6 +84,7 @@ struct synusb {
 
 	/* serialize access to open/suspend */
 	struct mutex pm_mutex;
+	bool is_open;
 
 	/* input device related data structures */
 	struct input_dev *input;
@@ -266,6 +267,7 @@ static int synusb_open(struct input_dev *dev)
 	}
 
 	synusb->intf->needs_remote_wakeup = 1;
+	synusb->is_open = 1;
 
 out:
 	mutex_unlock(&synusb->pm_mutex);
@@ -283,6 +285,7 @@ static void synusb_close(struct input_dev *dev)
 	mutex_lock(&synusb->pm_mutex);
 	usb_kill_urb(synusb->urb);
 	synusb->intf->needs_remote_wakeup = 0;
+	synusb->is_open = 0;
 	mutex_unlock(&synusb->pm_mutex);
 
 	if (!autopm_error)
@@ -485,12 +488,11 @@ static int synusb_suspend(struct usb_interface *intf, pm_message_t message)
 static int synusb_resume(struct usb_interface *intf)
 {
 	struct synusb *synusb = usb_get_intfdata(intf);
-	struct input_dev *input_dev = synusb->input;
 	int retval = 0;
 
 	mutex_lock(&synusb->pm_mutex);
 
-	if ((input_dev->users || (synusb->flags & SYNUSB_IO_ALWAYS)) &&
+	if ((synusb->is_open || (synusb->flags & SYNUSB_IO_ALWAYS)) &&
 	    usb_submit_urb(synusb->urb, GFP_NOIO) < 0) {
 		retval = -EIO;
 	}
@@ -513,10 +515,9 @@ static int synusb_pre_reset(struct usb_interface *intf)
 static int synusb_post_reset(struct usb_interface *intf)
 {
 	struct synusb *synusb = usb_get_intfdata(intf);
-	struct input_dev *input_dev = synusb->input;
 	int retval = 0;
 
-	if ((input_dev->users || (synusb->flags & SYNUSB_IO_ALWAYS)) &&
+	if ((synusb->is_open || (synusb->flags & SYNUSB_IO_ALWAYS)) &&
 	    usb_submit_urb(synusb->urb, GFP_NOIO) < 0) {
 		retval = -EIO;
 	}
-- 
2.16.2

^ permalink raw reply related

* [RESEND PATCH 1/6] input: synaptics_usb: fix deadlock in autosuspend
From: Marcus Folkesson @ 2018-02-28 13:37 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel, Marcus Folkesson
In-Reply-To: <20180228133803.30040-1-marcus.folkesson@gmail.com>

usb_autopm_get_interface() that is called in synusb_open() does an
autoresume if the device is suspended.

input_dev->mutex used in synusb_resume() is in this case already
taken by the input subsystem and will cause a deadlock.

Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
---
 drivers/input/mouse/synaptics_usb.c | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/drivers/input/mouse/synaptics_usb.c b/drivers/input/mouse/synaptics_usb.c
index cb7d15d826d0..2c66913cf5a2 100644
--- a/drivers/input/mouse/synaptics_usb.c
+++ b/drivers/input/mouse/synaptics_usb.c
@@ -82,6 +82,9 @@ struct synusb {
 	struct urb *urb;
 	unsigned char *data;
 
+	/* serialize access to open/suspend */
+	struct mutex pm_mutex;
+
 	/* input device related data structures */
 	struct input_dev *input;
 	char name[128];
@@ -252,6 +255,7 @@ static int synusb_open(struct input_dev *dev)
 		return retval;
 	}
 
+	mutex_lock(&synusb->pm_mutex);
 	retval = usb_submit_urb(synusb->urb, GFP_KERNEL);
 	if (retval) {
 		dev_err(&synusb->intf->dev,
@@ -264,6 +268,7 @@ static int synusb_open(struct input_dev *dev)
 	synusb->intf->needs_remote_wakeup = 1;
 
 out:
+	mutex_unlock(&synusb->pm_mutex);
 	usb_autopm_put_interface(synusb->intf);
 	return retval;
 }
@@ -275,8 +280,10 @@ static void synusb_close(struct input_dev *dev)
 
 	autopm_error = usb_autopm_get_interface(synusb->intf);
 
+	mutex_lock(&synusb->pm_mutex);
 	usb_kill_urb(synusb->urb);
 	synusb->intf->needs_remote_wakeup = 0;
+	mutex_unlock(&synusb->pm_mutex);
 
 	if (!autopm_error)
 		usb_autopm_put_interface(synusb->intf);
@@ -315,6 +322,7 @@ static int synusb_probe(struct usb_interface *intf,
 	synusb->udev = udev;
 	synusb->intf = intf;
 	synusb->input = input_dev;
+	mutex_init(&synusb->pm_mutex);
 
 	synusb->flags = id->driver_info;
 	if (synusb->flags & SYNUSB_COMBO) {
@@ -466,11 +474,10 @@ static void synusb_disconnect(struct usb_interface *intf)
 static int synusb_suspend(struct usb_interface *intf, pm_message_t message)
 {
 	struct synusb *synusb = usb_get_intfdata(intf);
-	struct input_dev *input_dev = synusb->input;
 
-	mutex_lock(&input_dev->mutex);
+	mutex_lock(&synusb->pm_mutex);
 	usb_kill_urb(synusb->urb);
-	mutex_unlock(&input_dev->mutex);
+	mutex_unlock(&synusb->pm_mutex);
 
 	return 0;
 }
@@ -481,14 +488,14 @@ static int synusb_resume(struct usb_interface *intf)
 	struct input_dev *input_dev = synusb->input;
 	int retval = 0;
 
-	mutex_lock(&input_dev->mutex);
+	mutex_lock(&synusb->pm_mutex);
 
 	if ((input_dev->users || (synusb->flags & SYNUSB_IO_ALWAYS)) &&
 	    usb_submit_urb(synusb->urb, GFP_NOIO) < 0) {
 		retval = -EIO;
 	}
 
-	mutex_unlock(&input_dev->mutex);
+	mutex_unlock(&synusb->pm_mutex);
 
 	return retval;
 }
@@ -496,9 +503,8 @@ static int synusb_resume(struct usb_interface *intf)
 static int synusb_pre_reset(struct usb_interface *intf)
 {
 	struct synusb *synusb = usb_get_intfdata(intf);
-	struct input_dev *input_dev = synusb->input;
 
-	mutex_lock(&input_dev->mutex);
+	mutex_lock(&synusb->pm_mutex);
 	usb_kill_urb(synusb->urb);
 
 	return 0;
@@ -515,7 +521,7 @@ static int synusb_post_reset(struct usb_interface *intf)
 		retval = -EIO;
 	}
 
-	mutex_unlock(&input_dev->mutex);
+	mutex_unlock(&synusb->pm_mutex);
 
 	return retval;
 }
-- 
2.16.2

^ permalink raw reply related

* Fix deadlocks in autosuspend
From: Marcus Folkesson @ 2018-02-28 13:37 UTC (permalink / raw)
  To: Dmitry Torokhov, Arvind Yadav; +Cc: linux-input, linux-kernel

Hello,

I have not recieved any feedback on these so I resend them.

I got this deadlock on my own driver (pxrc) when using the same
construction.

Please have a look

Here is a clip from 
[PATCH v3] input: pxrc: new driver for PhoenixRC Flight Controller Adapter [1]
that describes the problem.

-------------------------------8<----------------------------------------------
Also, I think we have a deadlock in the synaptics_usb driver.

When the device is suspended and someone is open the device, the input
subsystem will call input_open_device() which takes the
input_dev->mutex and then call input_dev->open().

synusb_open() has a call to usb_autopm_get_interface() which will
result in a call to the registered resume-function if the device is
suspended. (see Documentation/driver-api/usb/power-manaement.rst).

In the case of snaptics_usb, it will take the input_dev->mutex in the
resume function.

I have no synaptic mouse, but tested to put the same code into my
driver just to confirm, and got the following dump:

[ 9215.626476] INFO: task input-events:8590 blocked for more than 120 seconds.
[ 9215.626495]       Not tainted 4.15.0-rc8-ARCH+ #6
[ 9215.626500] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 9215.626507] input-events    D    0  8590   4394 0x00000004
[ 9215.626520] Call Trace:
[ 9215.626546]  ? __schedule+0x236/0x850
[ 9215.626559]  schedule+0x2f/0x90
[ 9215.626569]  schedule_preempt_disabled+0x11/0x20
[ 9215.626579]  __mutex_lock.isra.0+0x1aa/0x520
[ 9215.626609]  ? usb_runtime_suspend+0x70/0x70 [usbcore]
[ 9215.626622]  ? pxrc_resume+0x37/0x70 [pxrc]
[ 9215.626632]  pxrc_resume+0x37/0x70 [pxrc]
[ 9215.626655]  usb_resume_interface.isra.2+0x39/0xe0 [usbcore]
[ 9215.626676]  usb_resume_both+0xd2/0x120 [usbcore]
[ 9215.626688]  __rpm_callback+0xb6/0x1f0
[ 9215.626699]  rpm_callback+0x1f/0x70
[ 9215.626718]  ? usb_runtime_suspend+0x70/0x70 [usbcore]
[ 9215.626726]  rpm_resume+0x4e2/0x7f0
[ 9215.626737]  rpm_resume+0x582/0x7f0
[ 9215.626749]  __pm_runtime_resume+0x3a/0x50
[ 9215.626767]  usb_autopm_get_interface+0x1d/0x50 [usbcore]
[ 9215.626780]  pxrc_open+0x17/0x8d [pxrc]
[ 9215.626791]  input_open_device+0x70/0xa0
[ 9215.626804]  evdev_open+0x183/0x1c0 [evdev]
[ 9215.626819]  chrdev_open+0xa0/0x1b0
[ 9215.626830]  ? cdev_put.part.1+0x20/0x20
[ 9215.626840]  do_dentry_open+0x1ad/0x2c0
[ 9215.626855]  path_openat+0x576/0x1300
[ 9215.626868]  ? alloc_set_pte+0x22c/0x520
[ 9215.626883]  ? filemap_map_pages+0x19b/0x340
[ 9215.626893]  do_filp_open+0x9b/0x110
[ 9215.626908]  ? __check_object_size+0x9d/0x190
[ 9215.626920]  ? __alloc_fd+0xaf/0x160
[ 9215.626931]  ? do_sys_open+0x1bd/0x250
[ 9215.626942]  do_sys_open+0x1bd/0x250
[ 9215.626956]  entry_SYSCALL_64_fastpath+0x20/0x83
[ 9215.626967] RIP: 0033:0x7fbf6358f7ae


tablet/pegasus_notetaker.c and touchscreen/usbtouchscreen.c has the same
construction (taking input_dev->mutex in resume/suspend and call
usb_autopm_get_interface() in open()).

I will create a separate "pm_mutex" to use instead of input_dev->mutex
to get rid of the lockups in those drivers 

-------------------------------8<----------------------------------------------


[1] https://lkml.org/lkml/2018/1/20/191

Thanks,

Best regards
Marcus Folkesson

^ permalink raw reply

* Re: [PATCH v2] Input: gpio_keys: Add level trigger support for GPIO keys
From: Linus Walleij @ 2018-02-28 12:53 UTC (permalink / raw)
  To: Baolin Wang, Dmitry Torokhov
  Cc: Rob Herring, Mark Rutland, Greg KH, stephen lu, Arvind Yadav,
	Joseph Lo, Kate Stewart, Philippe Ombredanne, Thomas Gleixner,
	Linux Input, DTML, LKML, Mark Brown
In-Reply-To: <CAMz4ku+0uzSDiM3AOnDcnVDqAjoV47nVAK+yQAw2CzgO6Yw-Bg@mail.gmail.com>

On Mon, Feb 26, 2018 at 7:24 AM, Baolin Wang <baolin.wang@linaro.org> wrote:
> On 21 February 2018 at 19:35, Baolin Wang <baolin.wang@linaro.org> wrote:
>> On 20 February 2018 at 02:11, Rob Herring <robh@kernel.org> wrote:

>>>> diff --git a/Documentation/devicetree/bindings/input/gpio-keys.txt b/Documentation/devicetree/bindings/input/gpio-keys.txt
>>>> index a949404..e3104bd 100644
>>>> --- a/Documentation/devicetree/bindings/input/gpio-keys.txt
>>>> +++ b/Documentation/devicetree/bindings/input/gpio-keys.txt
>>>> @@ -29,6 +29,8 @@ Optional subnode-properties:
>>>>       - linux,can-disable: Boolean, indicates that button is connected
>>>>         to dedicated (not shared) interrupt which can be disabled to
>>>>         suppress events from the button.
>>>> +     - gpio-key,level-trigger: Boolean, indicates that button's interrupt
>>>> +       type is level trigger. Otherwise it is edge trigger as default.
>>>
>>> No. Just use 'interrupts' instead of 'gpios' and specify the trigger
>>> type. Or put both if you need to read the state.
>>
>> Okay, so something as below to get the level type from the
>> 'interrupts' property.
>> if (fwnode_property_read_u32(child, "interrupts", &button->level_type))
>>         button->level_type = IRQ_TYPE_NONE;
>
> After more thinking, if we use 'interrupts' to indicate the irq type
> for this case, we cannot specify the irq number due to the irq number
> should be get by gpiod_to_irq(). So the device nodes look weird, since
> we should define the index of the interrupt controller instead of the
> irq type if the #interrupt_cells is set to 1 according to the
> interrupt controller documentation. What do you think about this?

I think what you're ultimately seeing is a bad fit between this
GPIO/irq-controller and the Linux gpio keys driver, it doesn't
have very much to do with the device tree bindings.

What I think is appropriate is to try to create a new input driver
in Linux that just takes an interrupt, not a GPIO, and let the
GPIO chip only act as an irqchip for this.

This avoid the complicated step of converting a GPIO to an
interrupt in order to use it, when all you really want to
do is use an interrupt, you don't really care about the
GPIO here, correct?

So we would create
drivers/input/keyboard/interrupt_keys.c
however I suspect a bunch of code would need to be shared
with gpio_keys.c so maybe it is necessary to break out the
parts of gpio_keys.c into its own helper file.

Or maybe even have the
pure interrupt handling as part of gpio_keys.c, i.e. if the
driver can't find any associated GPIO, it goes on to
check if there is an interrupt assigned to the device node
and use that directly instead.

Either way, Dmitry must be involved.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH v3] Input: gpio_keys: Add level trigger support for GPIO keys
From: Baolin Wang @ 2018-02-28 12:44 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, mark.rutland
  Cc: gregkh, lumotuwe, arvind.yadav.cs, josephl, kstewart, pombredanne,
	tglx, linux-input, devicetree, linux-kernel, broonie,
	linus.walleij, arnd, baolin.wang

On some platforms (such as Spreadtrum platform), the GPIO keys can only
be triggered by level type. So this patch introduces one property to
indicate if the GPIO trigger type is level trigger or edge trigger.

Signed-off-by: Baolin Wang <baolin.wang@linaro.org>
---
Changes since v2:
 - Use 'interrupt' property to indicate the irq type.

Changes since v1:
 - Diable the GPIO irq until reversing the GPIO level type.
---
 drivers/input/keyboard/gpio_keys.c |   32 ++++++++++++++++++++++++++++++--
 include/linux/gpio_keys.h          |    3 +++
 2 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index 87e613d..9e05c80 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -385,6 +385,20 @@ static void gpio_keys_gpio_work_func(struct work_struct *work)
 	struct gpio_button_data *bdata =
 		container_of(work, struct gpio_button_data, work.work);
 
+	if (bdata->button->level_trigger) {
+		unsigned int trigger =
+			irq_get_trigger_type(bdata->irq) & ~IRQF_TRIGGER_MASK;
+		int state = gpiod_get_raw_value_cansleep(bdata->gpiod);
+
+		if (state)
+			trigger |= IRQF_TRIGGER_LOW;
+		else
+			trigger |= IRQF_TRIGGER_HIGH;
+
+		irq_set_irq_type(bdata->irq, trigger);
+		enable_irq(bdata->irq);
+	}
+
 	gpio_keys_gpio_report_event(bdata);
 
 	if (bdata->button->wakeup)
@@ -397,6 +411,9 @@ static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
 
 	BUG_ON(irq != bdata->irq);
 
+	if (bdata->button->level_trigger)
+		disable_irq_nosync(bdata->irq);
+
 	if (bdata->button->wakeup) {
 		const struct gpio_keys_button *button = bdata->button;
 
@@ -566,7 +583,11 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
 		INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
 
 		isr = gpio_keys_gpio_isr;
-		irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+		if (button->level_trigger)
+			irqflags = gpiod_is_active_low(bdata->gpiod) ?
+				IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
+		else
+			irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
 
 	} else {
 		if (!button->irq) {
@@ -696,10 +717,17 @@ static void gpio_keys_close(struct input_dev *input)
 	device_property_read_string(dev, "label", &pdata->name);
 
 	device_for_each_child_node(dev, child) {
-		if (is_of_node(child))
+		if (is_of_node(child)) {
 			button->irq =
 				irq_of_parse_and_map(to_of_node(child), 0);
 
+			if (button->irq)
+				button->level_trigger =
+					irq_get_trigger_type(button->irq) &
+					(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW) ?
+					true : false;
+		}
+
 		if (fwnode_property_read_u32(child, "linux,code",
 					     &button->code)) {
 			dev_err(dev, "Button without keycode\n");
diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h
index d06bf77..1286136 100644
--- a/include/linux/gpio_keys.h
+++ b/include/linux/gpio_keys.h
@@ -16,6 +16,8 @@
  * @debounce_interval:	debounce ticks interval in msecs
  * @can_disable:	%true indicates that userspace is allowed to
  *			disable button via sysfs
+ * @level_trigger:	indicate if the button's interrupt type is
+ *			level trigger or not
  * @value:		axis value for %EV_ABS
  * @irq:		Irq number in case of interrupt keys
  */
@@ -28,6 +30,7 @@ struct gpio_keys_button {
 	int wakeup;
 	int debounce_interval;
 	bool can_disable;
+	bool level_trigger;
 	int value;
 	unsigned int irq;
 };
-- 
1.7.9.5

^ permalink raw reply related

* Re: [PATCH 1/2] input: Add RAVE SP Powerbutton driver
From: Dmitry Torokhov @ 2018-02-28  4:59 UTC (permalink / raw)
  To: Andrey Smirnov
  Cc: linux-input, linux-kernel, Rob Herring, Mark Rutland, devicetree,
	Guenter Roeck, Chris Healy, Lucas Stach
In-Reply-To: <20180226154130.25774-1-andrew.smirnov@gmail.com>

Hi Andrey,

On Mon, Feb 26, 2018 at 07:41:29AM -0800, Andrey Smirnov wrote:
> Add driver that properly handles input event emitted by RAVE SP
> devices.
> 
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: linux-input@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: devicetree@vger.kernel.org
> Cc: Guenter Roeck <linux@roeck-us.net>
> Cc: Chris Healy <cphealy@gmail.com>
> Cc: Lucas Stach <l.stach@pengutronix.de>
> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
> ---
>  drivers/input/misc/Kconfig             |  9 ++++
>  drivers/input/misc/Makefile            |  1 +
>  drivers/input/misc/rave-sp-pwrbutton.c | 92 ++++++++++++++++++++++++++++++++++
>  3 files changed, 102 insertions(+)
>  create mode 100644 drivers/input/misc/rave-sp-pwrbutton.c
> 
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 62a1312a7387..6a3c753b093b 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -841,4 +841,13 @@ config INPUT_HISI_POWERKEY
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called hisi_powerkey.
>  
> +config INPUT_RAVE_SP_PWRBUTTON
> +	tristate "RAVE SP Power button Driver"
> +	depends on RAVE_SP_CORE
> +	help
> +	  Say Y here if you want to enable power key reporting from RAVE SP
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called rave-sp-pwrbutton.
> +
>  endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index a8f61af865aa..6ebeec1f4dd0 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -80,3 +80,4 @@ obj-$(CONFIG_INPUT_WM831X_ON)		+= wm831x-on.o
>  obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND)	+= xen-kbdfront.o
>  obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
>  obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR)	+= ideapad_slidebar.o
> +obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON)	+= rave-sp-pwrbutton.o

Please put it in [somewhat] alphabetical order (even though ideapad is
out of order). 


> diff --git a/drivers/input/misc/rave-sp-pwrbutton.c b/drivers/input/misc/rave-sp-pwrbutton.c
> new file mode 100644
> index 000000000000..7511062c724b
> --- /dev/null
> +++ b/drivers/input/misc/rave-sp-pwrbutton.c
> @@ -0,0 +1,92 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +/*
> + * Power Button driver for RAVE SP
> + *
> + * Copyright (C) 2017 Zodiac Inflight Innovations
> + *
> + */

Let's keep SPDX and copyright in the same // comment block.

> +
> +#include <linux/input.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mfd/rave-sp.h>
> +#include <linux/platform_device.h>
> +
> +#define RAVE_SP_EVNT_BUTTON_PRESS	(RAVE_SP_EVNT_BASE + 0x00)
> +
> +struct rave_sp_power_button {
> +	struct input_dev *idev;
> +	struct notifier_block nb;
> +};
> +
> +static int rave_sp_power_button_event(struct notifier_block *nb,
> +				      unsigned long action, void *data)
> +{
> +	struct rave_sp_power_button *pb =
> +		container_of(nb, struct rave_sp_power_button, nb);
> +	const u8 event = rave_sp_action_unpack_event(action);
> +	const u8 value = rave_sp_action_unpack_value(action);
> +	struct input_dev *idev = pb->idev;
> +
> +	if (event == RAVE_SP_EVNT_BUTTON_PRESS) {
> +		input_report_key(idev, KEY_POWER, value);
> +		input_sync(idev);
> +
> +		return NOTIFY_STOP;
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int rave_sp_pwrbutton_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rave_sp_power_button *pb;
> +	struct input_dev *idev;
> +	int ret;
> +
> +	pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
> +	if (!pb)
> +		return -ENOMEM;
> +
> +	idev = devm_input_allocate_device(dev);
> +	if (!idev)
> +		return -ENOMEM;
> +
> +	idev->name = pdev->name;
> +	idev->dev.parent = dev;

Not needed - done by devm_input_allocate_device().

> +
> +	input_set_capability(idev, EV_KEY, KEY_POWER);
> +
> +	ret = input_register_device(idev);
> +	if (ret)
> +		return ret;

Please call this "error".

> +
> +	pb->idev = idev;
> +	pb->nb.notifier_call = rave_sp_power_button_event;
> +	pb->nb.priority = 128;
> +
> +	return devm_rave_sp_register_event_notifier(dev, &pb->nb);

Prefer

	error = devm_rave...();
	if (error)
		return error;

	return 0;

> +}
> +
> +static const struct of_device_id rave_sp_pwrbutton_of_match[] = {
> +	{ .compatible = "zii,rave-sp-pwrbutton" },
> +	{}
> +};
> +
> +static struct platform_driver rave_sp_pwrbutton_driver = {
> +	.probe = rave_sp_pwrbutton_probe,
> +	.driver	= {
> +		.name = KBUILD_MODNAME,
> +		.of_match_table = rave_sp_pwrbutton_of_match,
> +	},
> +};
> +module_platform_driver(rave_sp_pwrbutton_driver);
> +
> +MODULE_DEVICE_TABLE(of, rave_sp_pwrbutton_of_match);
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>");
> +MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>");
> +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
> +MODULE_DESCRIPTION("RAVE SP Power Button driver");
> -- 
> 2.14.3
> 

-- 
Dmitry

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox