Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH 00/19] amba: store owner from modules with amba_driver_register()
From: Suzuki K Poulose @ 2024-03-27  9:22 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Russell King, Mike Leach, James Clark,
	Alexander Shishkin, Maxime Coquelin, Alexandre Torgue,
	Linus Walleij, Andi Shyti, Olivia Mackall, Herbert Xu, Vinod Koul,
	Dmitry Torokhov, Miquel Raynal, Michal Simek, Eric Auger,
	Alex Williamson
  Cc: linux-kernel, coresight, linux-arm-kernel, linux-stm32, linux-i2c,
	linux-crypto, dmaengine, linux-input, kvm
In-Reply-To: <f23f2e60-e5c0-4c3c-9722-dba63a6e7ef6@linaro.org>

On 27/03/2024 05:57, Krzysztof Kozlowski wrote:
> On 27/03/2024 00:24, Suzuki K Poulose wrote:
>> Hi Krzysztof
>>
>> On 26/03/2024 20:23, Krzysztof Kozlowski wrote:
>>> Merging
>>> =======
>>> All further patches depend on the first amba patch, therefore please ack
>>> and this should go via one tree.
>>
>> Are you able to provide a stable branch with these patches once you pull
> 
> I doubt I will be merging this. I think amba code goes through Russell.
> 
>> them in to "one tree" here ? We have changes coming up in the coresight
>> tree, which would conflict with the changes here (horribly).
>>
> 
> You mean conflict with  coresight conversion to platform driver? Worst

Yes.

> case it is solveable: just drop .owner.

Or, we could merge the CoreSight changes (as they are really not
affected by the problem this series is trying to address) after the
base changes land in AMBA, via the CoreSight tree.


Suzuki

> 
> Best regards,
> Krzysztof
> 


^ permalink raw reply

* Re: [PATCH v9 00/38] ep93xx device tree conversion
From: Andy Shevchenko @ 2024-03-27 10:59 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Andy Shevchenko, nikita.shubin, Hartley Sweeten,
	Alexander Sverdlin, Russell King, Lukasz Majewski, Linus Walleij,
	Bartosz Golaszewski, Michael Turquette, Stephen Boyd,
	Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Vinod Koul, Wim Van Sebroeck, Guenter Roeck, Thierry Reding,
	Uwe Kleine-König, Mark Brown, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Miquel Raynal, Richard Weinberger,
	Vignesh Raghavendra, Damien Le Moal, Sergey Shtylyov,
	Dmitry Torokhov, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
	Ralf Baechle, Wu, Aaron, Lee Jones, Olof Johansson, Niklas Cassel,
	linux-arm-kernel, linux-kernel, linux-gpio, linux-clk, linux-pm,
	devicetree, dmaengine, linux-watchdog, linux-pwm, linux-spi,
	netdev, linux-mtd, linux-ide, linux-input, linux-sound,
	Arnd Bergmann, Bartosz Golaszewski, Andrew Lunn
In-Reply-To: <a16f45c9-747c-4a19-98a3-aa5f47ee5c4d@linaro.org>

On Wed, Mar 27, 2024 at 7:07 AM Krzysztof Kozlowski
<krzysztof.kozlowski@linaro.org> wrote:
> On 26/03/2024 15:49, Andy Shevchenko wrote:
> > On Tue, Mar 26, 2024 at 11:19:54AM +0100, Krzysztof Kozlowski wrote:
> >> On 26/03/2024 10:18, Nikita Shubin via B4 Relay wrote:

...

> >> A lot of this could have been already merged if you split it... Just
> >> saying...
> >
> > But you able to apply DT schema patches if you wish.
> > Just doing? :-)
>
> Me? Why? DT bindings are supposed to go via subsystem maintainers, not
> DT tree.

Okay, I never remembered this rule, thank you for clarifying.

> Plus, I do not apply any bindings patches, except for managed
> subsystems and none of them are touched here.

Good to know!

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: In kernel virtual HID devices (was Future handling of complex RGB devices on Linux v3)
From: Hans de Goede @ 2024-03-27 11:01 UTC (permalink / raw)
  To: Benjamin Tissoires, Werner Sembach
  Cc: Lee Jones, jikos, linux-kernel, Jelle van der Waa, Miguel Ojeda,
	dri-devel@lists.freedesktop.org, linux-input, ojeda, linux-leds,
	Pavel Machek, Gregor Riepl
In-Reply-To: <vjd5xqgd2gsyz4ubgk6eusuyqdtxpdw6vogc5u537x2a245xcj@m2twppbxea4p>

Hi,

On 3/26/24 4:39 PM, Benjamin Tissoires wrote:
> On Mar 26 2024, Werner Sembach wrote:
>> Hi all,
>>
>> Am 25.03.24 um 19:30 schrieb Hans de Goede:
>>
>> [snip]
>>>>> If the kernel already handles the custom protocol into generic HID, the
>>>>> work for userspace is not too hard because they can deal with a known
>>>>> protocol and can be cross-platform in their implementation.
>>>>>
>>>>> I'm mentioning that cross-platform because SDL used to rely on the
>>>>> input, LEDs, and other Linux peculiarities and eventually fell back on
>>>>> using hidraw only because it's way more easier that way.
>>>>>
>>>>> The other advantage of LampArray is that according to Microsoft's
>>>>> document, new devices are going to support it out of the box, so they'll
>>>>> be supported out of the box directly.
>>>>>
>>>>> Most of the time my stance is "do not add new kernel API, you'll regret
>>>>> it later". So in that case, given that we have a formally approved
>>>>> standard, I would suggest to use it, and consider it your API.
>>>> The only new UAPI would be the use_leds_uapi switch to turn on/off the backwards compatibility.
> 
> I have my reserves with such a kill switch (see below).
> 
>>> Actually we don't even need that. Typically there is a single HID
>>> driver handling both keys and the backlight, so userspace cannot
>>> just unbind the HID driver since then the keys stop working.
> 
> I don't think Werner meant unbinding the HID driver, just a toggle to
> enable/disable the basic HID core processing of LampArray.

Right, what I was trying to say is that unbinding the driver
might be an alternative. I know things like the G15 keyboard
userspace daemons used to do this in the past.

But Werner is right that this won't be an option if the actual
keyboard presses and the LampArray parts are part of a single
HID device.

> 
>>>
>>> But with a virtual LampArray HID device the only functionality
>>> for an in kernel HID driver would be to export a basic keyboard
>>> backlight control interface for simple non per key backlight control
>>> to integrate nicely with e.g. GNOME's backlight control.
>>
>> Don't forget that in the future there will be devices that natively support
>> LampArray in their firmware, so for them it is the same device.
> 
> Yeah, the generic LampArray support will not be able to differentiate
> "emulated" devices from native ones.
> 
>>
>> Regards,
>>
>> Werner
>>
>>> And then when OpenRGB wants to take over it can just unbind the HID
>>> driver from the HID device using existing mechanisms for that.
> 
> Again no, it'll be too unpredicted.
> 
>>>
>>> Hmm, I wonder if that will not also kill hidraw support though ...
>>> I guess getting hidraw support back might require then also manually
>>> binding the default HID input driver.  Bentiss any input on this?
> 
> To be able to talk over hidraw you need a driver to be bound, yes. But I
> had the impression that LampArray would be supported by default in
> hid-input.c, thus making this hard to remove. Having a separate driver
> will work, but as soon as the LampArray device will also export a
> multitouch touchpad, we are screwed and will have to make a choice
> between LampArray and touch...

The idea is to have the actual RGB support in userspace through hidraw,
I believe we all agreed on that higher up in the thread.

Werner would like for there to also be a simpler in kernel support
which treats the per key lighting as if it is a more standard
(e.g. thinkpad x1) style keyboard backlight so that existing desktop
environment integration for that will work for users who do not
install say openrgb.

The question is how do we disable the in kernel basic kbd_backlight support
when openrgb wants to take over and fully control the LEDs ?

Given that driver unbinding is out of the question I think that we are
back to having a sysfs attribute to disable / re-enable the in kernel
basic kbd_backlight support.

Note that the basic kbd_backlight support also allows e.g. GNOME to
set the brightness (not only monitor it) so at a minimum we need to
disable the "write" support when e.g. openrgb has control.

Regards,

Hans




^ permalink raw reply

* Re: In kernel virtual HID devices (was Future handling of complex RGB devices on Linux v3)
From: Benjamin Tissoires @ 2024-03-27 11:03 UTC (permalink / raw)
  To: Werner Sembach
  Cc: Hans de Goede, Lee Jones, jikos, linux-kernel, Jelle van der Waa,
	Miguel Ojeda, dri-devel@lists.freedesktop.org, linux-input, ojeda,
	linux-leds, Pavel Machek, Gregor Riepl
In-Reply-To: <9b5151f9-4d1c-401e-abb5-540097749b76@tuxedocomputers.com>

On Mar 26 2024, Werner Sembach wrote:
> Hi all,
> 
> Am 26.03.24 um 16:39 schrieb Benjamin Tissoires:
> > On Mar 26 2024, Werner Sembach wrote:
> > > Hi all,
> > > 
> > > Am 25.03.24 um 19:30 schrieb Hans de Goede:
> > > 
> > > [snip]
> > > > > > If the kernel already handles the custom protocol into generic HID, the
> > > > > > work for userspace is not too hard because they can deal with a known
> > > > > > protocol and can be cross-platform in their implementation.
> > > > > > 
> > > > > > I'm mentioning that cross-platform because SDL used to rely on the
> > > > > > input, LEDs, and other Linux peculiarities and eventually fell back on
> > > > > > using hidraw only because it's way more easier that way.
> > > > > > 
> > > > > > The other advantage of LampArray is that according to Microsoft's
> > > > > > document, new devices are going to support it out of the box, so they'll
> > > > > > be supported out of the box directly.
> > > > > > 
> > > > > > Most of the time my stance is "do not add new kernel API, you'll regret
> > > > > > it later". So in that case, given that we have a formally approved
> > > > > > standard, I would suggest to use it, and consider it your API.
> > > > > The only new UAPI would be the use_leds_uapi switch to turn on/off the backwards compatibility.
> > I have my reserves with such a kill switch (see below).
> > 
> > > > Actually we don't even need that. Typically there is a single HID
> > > > driver handling both keys and the backlight, so userspace cannot
> > > > just unbind the HID driver since then the keys stop working.
> > I don't think Werner meant unbinding the HID driver, just a toggle to
> > enable/disable the basic HID core processing of LampArray.
> > 
> > > > But with a virtual LampArray HID device the only functionality
> > > > for an in kernel HID driver would be to export a basic keyboard
> > > > backlight control interface for simple non per key backlight control
> > > > to integrate nicely with e.g. GNOME's backlight control.
> > > Don't forget that in the future there will be devices that natively support
> > > LampArray in their firmware, so for them it is the same device.
> > Yeah, the generic LampArray support will not be able to differentiate
> > "emulated" devices from native ones.
> > 
> > > Regards,
> > > 
> > > Werner
> > > 
> > > > And then when OpenRGB wants to take over it can just unbind the HID
> > > > driver from the HID device using existing mechanisms for that.
> > Again no, it'll be too unpredicted.
> > 
> > > > Hmm, I wonder if that will not also kill hidraw support though ...
> > > > I guess getting hidraw support back might require then also manually
> > > > binding the default HID input driver.  Bentiss any input on this?
> > To be able to talk over hidraw you need a driver to be bound, yes. But I
> > had the impression that LampArray would be supported by default in
> > hid-input.c, thus making this hard to remove. Having a separate driver
> > will work, but as soon as the LampArray device will also export a
> > multitouch touchpad, we are screwed and will have to make a choice
> > between LampArray and touch...
> > 
> > > > Background info: as discussed earlier in the thread Werner would like
> > > > to have a basic driver registering a /sys/class/leds/foo::kbd_backlight/
> > > > device, since those are automatically supported by GNOME (and others)
> > > > and will give basic kbd backlight brightness control in the desktop
> > > > environment. This could be a simple HID driver for
> > > > the hid_allocate_device()-ed virtual HID device, but userspace needs
> > > > to be able to move that out of the way when it wants to take over
> > > > full control of the per key lighting.
> > Do we really need to entirely unregister the led class device? Can't we
> > snoop on the commands and get some "mean value"?
> > 
> > > > Regards,
> > > > 
> > > > Hans
> > > > 
> > > > 
> > > > 
> > > > 
> > > > 
> > > > 
> > > > 
> > > > > The control flow for the whole system would look something like this:
> > > > > 
> > > > > - System boots
> > > > > 
> > > > >       - Kernel driver initializes keyboard (maybe stops rainbowpuke boot effects, sets brightness to a default value, or initializes a solid color)
> > > > > 
> > > > >       - systemd-backlight restores last keyboard backlight brightness
> > > > > 
> > > > >       - UPower sees sysfs leds entry and exposes it to DBus for DEs to do keyboard brightness handling
> > > > > 
> > > > > - If the user wants more control they (auto-)start OpenRGB
> > > > > 
> > > > >       - OpenRGB disables sysfs leds entry via use_leds_uapi to prevent double control of the same device by UPower
> > > > > 
> > > > >       - OpenRGB directly interacts with hidraw device via LampArray API to give fine granular control of the backlight
> > > > > 
> > > > >       - When OpenRGB closes it should reenable the sysfs leds entry
> > That's where your plan falls short: if OpenRGB crashes, or is killed it
> > will not reset that bit.
> > 
> > Next question: is OpenRGB supposed to keep the hidraw node opened all
> > the time or not?
> TBH I didn't look at the OpenRGB code yet and LampArray there is currently
> only planned. I somewhat hope that until the kernel driver is ready someone
> else already picked up implementing LampArray in OpenRGB.
> > 
> > If it has to keep it open, we should be able to come up with a somewhat
> > similar hack that we have with hid-steam: when the hidraw node is
> > opened, we disable the kernel processing of LampArray. When the node is
> > closed, we re-enable it.
> > 
> > But that also means we have to distinguish steam/SDL from OpenRGB...
> 
> My first thought here also: What is if something else is reading hidraw devices?
> 
> Especially for hidraw devices that are not just LampArray.
> 
> > 
> > I just carefully read the LampArray spec. And it's simpler than what
> > I expected. But the thing that caught my attention was that it's
> > mentioned that there is no way for the host to query the current
> > color/illumination of the LEDs when the mode is set to
> > AutonomousMode=false. Which means that the kernel should be able to
> > snoop into any commands sent from OpenRGB to the device, compute a mean
> > value and update its internal state.
> > 
> > Basically all we need is the "toggle" to put the led class in read-only
> > mode while OpenRGB kicks in. Maybe given that we are about to snoop on
> > the commands sent, we can detect that there is a LampArray command
> > emitted, attach this information to the struct file * in hidraw, and
> > then re-enable rw when that user closes the hidraw node.
> 
> I think a read-only mode is not part of the current led class UAPI. Also I
> don't want to associate AutonomousMode=true with led class is used.
> AutonomousMode=true could for example mean that it is controlled via
> keyboard shortcuts that are directly handled in the keyboard firmware, aka a
> case where you want neither OpenRGB nor led class make any writes to the
> keyboard.
> 
> Or AutonomousMode=true could mean that on a device that implements both a
> LampArray interface as well as a proprietary legacy interface is currently
> controlled via the proprietary legacy interface (a lot of which are
> supported by OpenRGB).

Then how is the kernel supposed to handle LampArrays?

If you need the kernel to use a ledclass, the kernel will have to set
the device into AutonomousMode=false. When the kernel is done
configuring the leds, it can not switch back to AutonomousMode=true or
its config will likely be dumped by the device.

OpenRGB can open the device, switch it to AutonomousMode=false and we
can rely on it to do the right things as long as it is alive. But I do
not see how the kernel could do the same.

FWIW, I also have a couple of crazy ideas currently boiling in my head
to "solve" that but I'd rather have a consensus on the high level side
of things before we go too deep into the technical workaround.

Cheers,
Benjamin

> 
> Regards,
> 
> Werner
> 
> > 
> > Cheers,
> > Benjamin
> > 
> > > > > - System shutdown
> > > > > 
> > > > >       - Since OpenRGB reenables the sysfs leds entry, systemd-backlight can correctly store a brightness value for next boot
> > > > > 
> > > > > Regards,
> > > > > 
> > > > > Werner
> > > > > 
> > > > > > Side note to self: I really need to resurrect the hidraw revoke series
> > > > > > so we could export those hidraw node to userspace with uaccess through
> > > > > > logind...
> > > > > > 
> > > > > > Cheers,
> > > > > > Benjamin

^ permalink raw reply

* Re: [PATCH 00/19] ACPI: store owner from modules with acpi_bus_register_driver()
From: Rafael J. Wysocki @ 2024-03-27 13:16 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Rafael J. Wysocki, Len Brown, Robert Moore, Dmitry Torokhov,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Benson Leung, Tzung-Bi Shih, Corentin Chary, Luke D. Jones,
	Hans de Goede, Ilpo Järvinen, Thadeu Lima de Souza Cascardo,
	Daniel Oliveira Nascimento, Pali Rohár, Matan Ziv-Av,
	Mattia Dongili, Azael Avalos, Richard Cochran, Jeff Sipek,
	Ajay Kaher, Alexey Makhalov, VMware PV-Drivers Reviewers,
	Theodore Ts'o, Jason A. Donenfeld, Rafael J. Wysocki,
	linux-acpi, linux-kernel, acpica-devel, linux-input, netdev,
	chrome-platform, platform-driver-x86
In-Reply-To: <20240327-b4-module-owner-acpi-v1-0-725241a2d224@linaro.org>

On Wed, Mar 27, 2024 at 8:44 AM Krzysztof Kozlowski
<krzysztof.kozlowski@linaro.org> wrote:
>
> Merging
> =======
> All further patches depend on the first amba patch, therefore please ack
> and this should go via one tree: ACPI?
>
> Description
> ===========
> Modules registering driver with acpi_bus_register_driver() often forget to
> set .owner field.
>
> Solve the problem by moving this task away from the drivers to the core
> amba bus code, just like we did for platform_driver in commit
> 9447057eaff8 ("platform_device: use a macro instead of
> platform_driver_register").
>
> Best regards,
> Krzysztof
>
> ---
> Krzysztof Kozlowski (19):
>       ACPI: store owner from modules with acpi_bus_register_driver()
>       Input: atlas: - drop owner assignment
>       net: fjes: drop owner assignment
>       platform: chrome: drop owner assignment
>       platform: asus-laptop: drop owner assignment
>       platform: classmate-laptop: drop owner assignment
>       platform/x86/dell: drop owner assignment
>       platform/x86/eeepc: drop owner assignment
>       platform/x86/intel/rst: drop owner assignment
>       platform/x86/intel/smartconnect: drop owner assignment
>       platform/x86/lg-laptop: drop owner assignment
>       platform/x86/sony-laptop: drop owner assignment
>       platform/x86/toshiba_acpi: drop owner assignment
>       platform/x86/toshiba_bluetooth: drop owner assignment
>       platform/x86/toshiba_haps: drop owner assignment
>       platform/x86/wireless-hotkey: drop owner assignment
>       ptp: vmw: drop owner assignment
>       virt: vmgenid: drop owner assignment
>       ACPI: drop redundant owner from acpi_driver

I definitely like this, so

Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

for the series and I can pick it up if people agree.

Thanks!

^ permalink raw reply

* Re: [PATCH 00/19] ACPI: store owner from modules with acpi_bus_register_driver()
From: Hans de Goede @ 2024-03-27 13:30 UTC (permalink / raw)
  To: Rafael J. Wysocki, Krzysztof Kozlowski
  Cc: Len Brown, Robert Moore, Dmitry Torokhov, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Benson Leung,
	Tzung-Bi Shih, Corentin Chary, Luke D. Jones, Ilpo Järvinen,
	Thadeu Lima de Souza Cascardo, Daniel Oliveira Nascimento,
	Pali Rohár, Matan Ziv-Av, Mattia Dongili, Azael Avalos,
	Richard Cochran, Jeff Sipek, Ajay Kaher, Alexey Makhalov,
	VMware PV-Drivers Reviewers, Theodore Ts'o,
	Jason A. Donenfeld, Rafael J. Wysocki, linux-acpi, linux-kernel,
	acpica-devel, linux-input, netdev, chrome-platform,
	platform-driver-x86
In-Reply-To: <CAJZ5v0hEiKJJWn-TVoyL6DEbCcMpL39_q+HLG_YZyjf9g29CXA@mail.gmail.com>

Hi,

On 3/27/24 2:16 PM, Rafael J. Wysocki wrote:
> On Wed, Mar 27, 2024 at 8:44 AM Krzysztof Kozlowski
> <krzysztof.kozlowski@linaro.org> wrote:
>>
>> Merging
>> =======
>> All further patches depend on the first amba patch, therefore please ack
>> and this should go via one tree: ACPI?
>>
>> Description
>> ===========
>> Modules registering driver with acpi_bus_register_driver() often forget to
>> set .owner field.
>>
>> Solve the problem by moving this task away from the drivers to the core
>> amba bus code, just like we did for platform_driver in commit
>> 9447057eaff8 ("platform_device: use a macro instead of
>> platform_driver_register").
>>
>> Best regards,
>> Krzysztof
>>
>> ---
>> Krzysztof Kozlowski (19):
>>       ACPI: store owner from modules with acpi_bus_register_driver()
>>       Input: atlas: - drop owner assignment
>>       net: fjes: drop owner assignment
>>       platform: chrome: drop owner assignment
>>       platform: asus-laptop: drop owner assignment
>>       platform: classmate-laptop: drop owner assignment
>>       platform/x86/dell: drop owner assignment
>>       platform/x86/eeepc: drop owner assignment
>>       platform/x86/intel/rst: drop owner assignment
>>       platform/x86/intel/smartconnect: drop owner assignment
>>       platform/x86/lg-laptop: drop owner assignment
>>       platform/x86/sony-laptop: drop owner assignment
>>       platform/x86/toshiba_acpi: drop owner assignment
>>       platform/x86/toshiba_bluetooth: drop owner assignment
>>       platform/x86/toshiba_haps: drop owner assignment
>>       platform/x86/wireless-hotkey: drop owner assignment
>>       ptp: vmw: drop owner assignment
>>       virt: vmgenid: drop owner assignment
>>       ACPI: drop redundant owner from acpi_driver
> 
> I definitely like this, so
> 
> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> 
> for the series and I can pick it up if people agree.


> 
> Thanks!
> 


^ permalink raw reply

* Re: [PATCH 00/19] ACPI: store owner from modules with acpi_bus_register_driver()
From: Hans de Goede @ 2024-03-27 13:32 UTC (permalink / raw)
  To: Rafael J. Wysocki, Krzysztof Kozlowski
  Cc: Len Brown, Robert Moore, Dmitry Torokhov, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Benson Leung,
	Tzung-Bi Shih, Corentin Chary, Luke D. Jones, Ilpo Järvinen,
	Thadeu Lima de Souza Cascardo, Daniel Oliveira Nascimento,
	Pali Rohár, Matan Ziv-Av, Mattia Dongili, Azael Avalos,
	Richard Cochran, Jeff Sipek, Ajay Kaher, Alexey Makhalov,
	VMware PV-Drivers Reviewers, Theodore Ts'o,
	Jason A. Donenfeld, Rafael J. Wysocki, linux-acpi, linux-kernel,
	acpica-devel, linux-input, netdev, chrome-platform,
	platform-driver-x86
In-Reply-To: <CAJZ5v0hEiKJJWn-TVoyL6DEbCcMpL39_q+HLG_YZyjf9g29CXA@mail.gmail.com>

Hi,

<sorry for the empty previous reply, my bad>

On 3/27/24 2:16 PM, Rafael J. Wysocki wrote:
> On Wed, Mar 27, 2024 at 8:44 AM Krzysztof Kozlowski
> <krzysztof.kozlowski@linaro.org> wrote:
>>
>> Merging
>> =======
>> All further patches depend on the first amba patch, therefore please ack
>> and this should go via one tree: ACPI?
>>
>> Description
>> ===========
>> Modules registering driver with acpi_bus_register_driver() often forget to
>> set .owner field.
>>
>> Solve the problem by moving this task away from the drivers to the core
>> amba bus code, just like we did for platform_driver in commit
>> 9447057eaff8 ("platform_device: use a macro instead of
>> platform_driver_register").
>>
>> Best regards,
>> Krzysztof
>>
>> ---
>> Krzysztof Kozlowski (19):
>>       ACPI: store owner from modules with acpi_bus_register_driver()
>>       Input: atlas: - drop owner assignment
>>       net: fjes: drop owner assignment
>>       platform: chrome: drop owner assignment
>>       platform: asus-laptop: drop owner assignment
>>       platform: classmate-laptop: drop owner assignment
>>       platform/x86/dell: drop owner assignment
>>       platform/x86/eeepc: drop owner assignment
>>       platform/x86/intel/rst: drop owner assignment
>>       platform/x86/intel/smartconnect: drop owner assignment
>>       platform/x86/lg-laptop: drop owner assignment
>>       platform/x86/sony-laptop: drop owner assignment
>>       platform/x86/toshiba_acpi: drop owner assignment
>>       platform/x86/toshiba_bluetooth: drop owner assignment
>>       platform/x86/toshiba_haps: drop owner assignment
>>       platform/x86/wireless-hotkey: drop owner assignment
>>       ptp: vmw: drop owner assignment
>>       virt: vmgenid: drop owner assignment
>>       ACPI: drop redundant owner from acpi_driver
> 
> I definitely like this, so
> 
> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> 
> for the series and I can pick it up if people agree.

Thanks all the drivers/platform/x86/* change look good
to me:

Acked-by: Hans de Goede <hdegoede@redhat.com>

And I'm fine with merging these through the linux-pm /
ACPI tree.

Regards,

Hans




^ permalink raw reply

* [syzbot] [input?] WARNING in cm109_input_open/usb_submit_urb (3)
From: syzbot @ 2024-03-27 14:52 UTC (permalink / raw)
  To: dmitry.torokhov, linux-input, linux-kernel, syzkaller-bugs

Hello,

syzbot found the following issue on:

HEAD commit:    fe46a7dd189e Merge tag 'sound-6.9-rc1' of git://git.kernel..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=14136479180000
kernel config:  https://syzkaller.appspot.com/x/.config?x=fe78468a74fdc3b7
dashboard link: https://syzkaller.appspot.com/bug?extid=ac0f9c4cc1e034160492
compiler:       Debian clang version 15.0.6, GNU ld (GNU Binutils for Debian) 2.40

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/0f7abe4afac7/disk-fe46a7dd.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/82598d09246c/vmlinux-fe46a7dd.xz
kernel image: https://storage.googleapis.com/syzbot-assets/efa23788c875/bzImage-fe46a7dd.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+ac0f9c4cc1e034160492@syzkaller.appspotmail.com

input: CM109 USB driver as /devices/platform/dummy_hcd.0/usb1/1-1/1-1:0.8/input/input8
------------[ cut here ]------------
URB ffff8880285c2100 submitted while active
WARNING: CPU: 1 PID: 5112 at drivers/usb/core/urb.c:379 usb_submit_urb+0x1039/0x18c0 drivers/usb/core/urb.c:379
Modules linked in:
CPU: 1 PID: 5112 Comm: kworker/1:3 Not tainted 6.8.0-syzkaller-08951-gfe46a7dd189e #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/29/2024
Workqueue: usb_hub_wq hub_event
RIP: 0010:usb_submit_urb+0x1039/0x18c0 drivers/usb/core/urb.c:379
Code: 00 eb 66 e8 b9 b2 86 fa e9 79 f0 ff ff e8 af b2 86 fa c6 05 1e 90 63 08 01 90 48 c7 c7 c0 96 4b 8c 4c 89 ee e8 c8 86 49 fa 90 <0f> 0b 90 90 e9 40 f0 ff ff e8 89 b2 86 fa eb 12 e8 82 b2 86 fa 41
RSP: 0018:ffffc90004a0eb48 EFLAGS: 00010246
RAX: e879a53475439e00 RBX: 0000000000000cc0 RCX: 0000000000040000
RDX: ffffc900134bb000 RSI: 000000000003ffff RDI: 0000000000040000
RBP: ffff8880285c2108 R08: ffffffff8157cc12 R09: 1ffff110172a51a2
R10: dffffc0000000000 R11: ffffed10172a51a3 R12: 1ffff11005a65a0a
R13: ffff8880285c2100 R14: dffffc0000000000 R15: ffff88802d32d010
FS:  0000000000000000(0000) GS:ffff8880b9500000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000020002024 CR3: 0000000068c66000 CR4: 0000000000350ef0
Call Trace:
 <TASK>
 cm109_input_open+0x1f9/0x470 drivers/input/misc/cm109.c:572
 input_open_device+0x193/0x2e0 drivers/input/input.c:654
 kbd_connect+0xe9/0x130 drivers/tty/vt/keyboard.c:1593
 input_attach_handler drivers/input/input.c:1064 [inline]
 input_register_device+0xcfc/0x1090 drivers/input/input.c:2396
 cm109_usb_probe+0x10cd/0x1600 drivers/input/misc/cm109.c:806
 usb_probe_interface+0x5cd/0xb00 drivers/usb/core/driver.c:399
 really_probe+0x2a0/0xc50 drivers/base/dd.c:658
 __driver_probe_device+0x1a2/0x3e0 drivers/base/dd.c:800
 driver_probe_device+0x50/0x430 drivers/base/dd.c:830
 __device_attach_driver+0x2d6/0x530 drivers/base/dd.c:958
 bus_for_each_drv+0x250/0x2e0 drivers/base/bus.c:457
 __device_attach+0x333/0x520 drivers/base/dd.c:1030
 bus_probe_device+0x189/0x260 drivers/base/bus.c:532
 device_add+0x8ff/0xca0 drivers/base/core.c:3639
 usb_set_configuration+0x1976/0x1fb0 drivers/usb/core/message.c:2207
 usb_generic_driver_probe+0x88/0x140 drivers/usb/core/generic.c:254
 usb_probe_device+0x140/0x2d0 drivers/usb/core/driver.c:294
 really_probe+0x2a0/0xc50 drivers/base/dd.c:658
 __driver_probe_device+0x1a2/0x3e0 drivers/base/dd.c:800
 driver_probe_device+0x50/0x430 drivers/base/dd.c:830
 __device_attach_driver+0x2d6/0x530 drivers/base/dd.c:958
 bus_for_each_drv+0x250/0x2e0 drivers/base/bus.c:457
 __device_attach+0x333/0x520 drivers/base/dd.c:1030
 bus_probe_device+0x189/0x260 drivers/base/bus.c:532
 device_add+0x8ff/0xca0 drivers/base/core.c:3639
 usb_new_device+0x104a/0x19a0 drivers/usb/core/hub.c:2614
 hub_port_connect drivers/usb/core/hub.c:5483 [inline]
 hub_port_connect_change drivers/usb/core/hub.c:5623 [inline]
 port_event drivers/usb/core/hub.c:5783 [inline]
 hub_event+0x2d13/0x50f0 drivers/usb/core/hub.c:5865
 process_one_work kernel/workqueue.c:3254 [inline]
 process_scheduled_works+0xa02/0x1770 kernel/workqueue.c:3335
 worker_thread+0x86d/0xd70 kernel/workqueue.c:3416
 kthread+0x2f2/0x390 kernel/kthread.c:388
 ret_from_fork+0x4d/0x80 arch/x86/kernel/process.c:147
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:243
 </TASK>


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup

^ permalink raw reply

* Re: [PATCH v1 1/4] dt-bindings: input: Add Himax HX83102J touchscreen
From: Conor Dooley @ 2024-03-27 16:44 UTC (permalink / raw)
  To: Allen Lin
  Cc: Conor Dooley, Rob Herring, dmitry.torokhov,
	krzysztof.kozlowski+dt, conor+dt, jikos, benjamin.tissoires,
	linux-input, devicetree, linux-kernel
In-Reply-To: <TY0PR06MB5611AE812B72B349E85118D59E342@TY0PR06MB5611.apcprd06.prod.outlook.com>

[-- Attachment #1: Type: text/plain, Size: 4223 bytes --]

On Wed, Mar 27, 2024 at 03:48:48PM +0800, Allen Lin wrote:
> Conor Dooley <conor@kernel.org> 於 2024年3月27日 週三 上午3:28寫道:
> >
> > On Tue, Mar 26, 2024 at 06:40:28PM +0800, Allen Lin wrote:
> > > Conor Dooley <conor.dooley@microchip.com> 於 2024年3月26日 週二 下午4:48寫道:
> > > >
> > > > On Tue, Mar 26, 2024 at 01:46:56PM +0800, Allen Lin wrote:
> > > > > Conor Dooley <conor@kernel.org> 於 2024年3月23日 週六 上午2:34寫道:
> > > > > >
> > > > > > On Fri, Mar 22, 2024 at 01:30:09PM -0500, Rob Herring wrote:
> > > > > > > On Fri, Mar 22, 2024 at 05:54:08PM +0000, Conor Dooley wrote:
> > > > > > > > On Fri, Mar 22, 2024 at 04:56:03PM +0800, Allen_Lin wrote:
> > > > > > > > > Add the HX83102j touchscreen device tree bindings documents.
> > > > > > > > > HX83102j is a Himax TDDI touchscreen controller.
> > > > > > > > > It's power sequence should be bound with a lcm driver, thus it
> > > > > > > > > needs to be a panel follower. Others are the same as normal SPI
> > > > > > > > > touchscreen controller.
> > > > > > > > >
> > > > > > > > > Signed-off-by: Allen_Lin <allencl_lin@hotmail.com>
> > > > > > > >
> > > > > > > > note to self/Krzysztof/Rob:
> > > > > > > > There was a previous attempt at this kind of device. This version looks
> > > > > > > > better but might be incomplete given there's a bunch more properties in
> > > > > > > > that patchset:
> > > > > > > > https://lore.kernel.org/all/20231017091900.801989-1-tylor_yang@himax.corp-partner.google.com/
> > > > > > >
> > > > > > > Those don't look like properties we want coming back.
> > > > > >
> > > > > > Oh, I don't want most of them coming back either. There are some
> > > > > > supplies in there though that I think we would like to come back, no?
> > > > > > Maybe this particular device doesn't have any supplies, but that doesn't
> > > > > > really seem credible.
> > > > >
> > > > > We will use Firmware-name in Device Tree.
> > > >
> > > > > For power supply settings, because there may be other device using
> > > > > same regulator.
> > > >
> > > > If there are other devices using the same regulator is it more
> > > > important that you document it so that it doesn't get disabled by the
> > > > other users.
> > > >
> > > > > We plan to define it as an optional property for user to control in
> > > > > next release.
> > > >
> > > > I don't see how the regulator would not be required, the device doesn't
> > > > function without power.
> > > >
> > > > Thanks,
> > > > Conor.
> > >
> > > I will set power supply as required.
> > > The description of power supply as below,
> > >
> > > properties:
> > >   vccd-supply:
> > >     description: A phandle for the regualtor supplying IO power. Should be own
> > >                  by TPIC only.
> >
> > What does "owned by TPIC" only mean? Why would the vccd supply not be
> > allowed to be shared with another device?
> >
> > > This works for TP digital IO only, main power is
> > >                  given by display part VSP/VSN power source which is controlled
> > >                  by lcm driver.
> >
> > What drivers control things doesn't really matter here, we're just
> > describing the hardware. If there's another supply to the controller,
> > then document it too please.
> >
> 
> Below is IC power sequence introduction.
> https://github.com/HimaxSoftware/Doc/tree/main/Himax_Chipset_Power_Sequence
> 
> TDDI IC, which means Touch and Display Driver is integrated in one IC,
> So some power supplies will be controlled by Display driver.

If someone was to turn off the supplies, would the touch component stop
working? The document says that these supplies must be turned on before
the touch supplies, so I'm going to assume that both are needed for the
device to function.

> In yaml Document, can we just list power supplies controlled by touch driver?

If the touch part of this device needs the supplies to function, then
you need to mention them, regardless of where they're controlled. All we
are doing in the binding is describing the hardware. What drivers do
what depends entirely on what software you're using.

Cheers,
Conor.

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

^ permalink raw reply

* [PATCH] Input: stmpe - drop driver owner assignment
From: Krzysztof Kozlowski @ 2024-03-27 17:46 UTC (permalink / raw)
  To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, linux-input,
	linux-stm32, linux-arm-kernel, linux-kernel
  Cc: Krzysztof Kozlowski

Core in platform_driver_register() already sets the .owner, so driver
does not need to.

Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
 drivers/input/keyboard/stmpe-keypad.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
index 2013c0afd0c3..ef2f44027894 100644
--- a/drivers/input/keyboard/stmpe-keypad.c
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -413,7 +413,6 @@ static void stmpe_keypad_remove(struct platform_device *pdev)
 
 static struct platform_driver stmpe_keypad_driver = {
 	.driver.name	= "stmpe-keypad",
-	.driver.owner	= THIS_MODULE,
 	.probe		= stmpe_keypad_probe,
 	.remove_new	= stmpe_keypad_remove,
 };
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH 01/19] amba: store owner from modules with amba_driver_register()
From: Andi Shyti @ 2024-03-27 20:33 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Russell King, Suzuki K Poulose, Mike Leach, James Clark,
	Alexander Shishkin, Maxime Coquelin, Alexandre Torgue,
	Linus Walleij, Olivia Mackall, Herbert Xu, Vinod Koul,
	Dmitry Torokhov, Miquel Raynal, Michal Simek, Eric Auger,
	Alex Williamson, linux-kernel, coresight, linux-arm-kernel,
	linux-stm32, linux-i2c, linux-crypto, dmaengine, linux-input, kvm
In-Reply-To: <20240326-module-owner-amba-v1-1-4517b091385b@linaro.org>

Hi Krzysztof,

...

>  /**
> - *	amba_driver_register - register an AMBA device driver
> + *	__amba_driver_register - register an AMBA device driver
>   *	@drv: amba device driver structure
> + *	@owner: owning module/driver
>   *
>   *	Register an AMBA device driver with the Linux device model
>   *	core.  If devices pre-exist, the drivers probe function will
>   *	be called.
>   */
> -int amba_driver_register(struct amba_driver *drv)
> +int __amba_driver_register(struct amba_driver *drv,

...

> +/*
> + * use a macro to avoid include chaining to get THIS_MODULE
> + */

Should the documentation be moved here? Well... I don't see any
documentation linking this file yet, but in case it comes we want
documented amba_driver_register() rather than
__amba_driver_register().

Andi

> +#define amba_driver_register(drv) \
> +	__amba_driver_register(drv, THIS_MODULE)
> +

^ permalink raw reply

* Re: [PATCH 14/19] i2c: nomadik: drop owner assignment
From: Andi Shyti @ 2024-03-27 20:34 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Russell King, Suzuki K Poulose, Mike Leach, James Clark,
	Alexander Shishkin, Maxime Coquelin, Alexandre Torgue,
	Linus Walleij, Olivia Mackall, Herbert Xu, Vinod Koul,
	Dmitry Torokhov, Miquel Raynal, Michal Simek, Eric Auger,
	Alex Williamson, linux-kernel, coresight, linux-arm-kernel,
	linux-stm32, linux-i2c, linux-crypto, dmaengine, linux-input, kvm
In-Reply-To: <20240326-module-owner-amba-v1-14-4517b091385b@linaro.org>

Hi Krzysztof,

On Tue, Mar 26, 2024 at 09:23:44PM +0100, Krzysztof Kozlowski wrote:
> Amba bus core already sets owner, so driver does not need to.
> 
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

Acked-by: Andi Shyti <andi.shyti@kernel.org>

Andi

^ permalink raw reply

* [PATCH] input/touchscreen: synaptics_tcm_oncell: add driver
From: Frieder Hannenheim @ 2024-03-27 21:39 UTC (permalink / raw)
  To: dmitry.torokhov, linux-input, linux-kernel; +Cc: Frieder Hannenheim

This is a bit of a stripped down and partially reworked driver for the
synaptics_tcm_oncell touchscreen. I based my work off the driver in the
LineageOS kernel found at
https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
lineage-20. The code was originally written by OnePlus developers but
I'm not sure how to credit them correctly.

Currently the driver is in a pretty good shape, the only thing that is
not working is setting a report config. To me it looks like some data is
sent by the touchscreen firmware after setting the report config that is
making the irq handler crash. Sadly I haven't been able to test out why.
The driver works fine also with the default touch report config so maybe
we can just use that and not set our own. 

Signed-off-by: Frieder Hannenheim <friederhannenheim@riseup.net>
---
 .../input/touchscreen/syna,s3908.yaml         |   63 +
 drivers/input/touchscreen/Kconfig             |   11 +
 drivers/input/touchscreen/Makefile            |    1 +
 .../input/touchscreen/synaptics_tcm_oncell.c  | 1104 +++++++++++++++++
 4 files changed, 1179 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
 create mode 100644 drivers/input/touchscreen/synaptics_tcm_oncell.c

diff --git a/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
new file mode 100644
index 000000000000..1a85747e2f52
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/synaptics,s3908.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synaptics s3908 touchscreen controller
+
+maintainers:
+  - Frieder Hannenheim <frieder.hannenheim@riseup.net>
+
+allOf:
+  - $ref: touchscreen.yaml#
+
+properties:
+  compatible:
+    const: syna,s3908
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description: reset gpio the chip is connected to.
+
+  vdd-supply: true
+  vcc-supply: true
+
+  max-objects: true
+
+unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - reset-gpios
+  - vdd-supply
+  - vcc-supply
+  - max-objects
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        touchscreen@48 {
+            compatible = "syna,s3908";
+            reg = <0x48>;
+            interrupt-parent = <&gpa1>;
+            interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
+            reset-gpios = <38 0>;
+            vdd-supply = <&ldo30_reg>;
+            vcc-supply = <&ldo31_reg>;
+
+            max-objects = <10>;
+        };
+    };
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 1f8b33c2b03d..eba31ae27391 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -502,6 +502,17 @@ config TOUCHSCREEN_S6SY761
 
 	  To compile this driver as module, choose M here: the
 	  module will be called s6sy761.
+	  
+config TOUCHSCREEN_SYNA_TCM_ONCELL
+	tristate "Synaptics TCM Oncell Touchscreen driver"
+	depends on I2C
+	help
+	  Say Y if you have the Synaptics TCM Oncell driver
+
+	  If unsure, say N
+
+	  To compile this driver as module, choose M here: the
+	  module will be called synaptics_tcm_oncell.
 
 config TOUCHSCREEN_GUNZE
 	tristate "Gunze AHL-51S touchscreen"
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 7d52592f4290..f5395ccf09d5 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
 obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
 obj-$(CONFIG_TOUCHSCREEN_SUR40)		+= sur40.o
 obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI)	+= surface3_spi.o
+obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM_ONCELL)	+= synaptics_tcm_oncell.o
 obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC)	+= ti_am335x_tsc.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
diff --git a/drivers/input/touchscreen/synaptics_tcm_oncell.c b/drivers/input/touchscreen/synaptics_tcm_oncell.c
new file mode 100644
index 000000000000..b874287f37af
--- /dev/null
+++ b/drivers/input/touchscreen/synaptics_tcm_oncell.c
@@ -0,0 +1,1104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Driver for Synaptics TCM Oncell Touchscreens
+ *
+ *  Copyright (c) 2024 Frieder Hannenheim <friederhannenheim@riseup.net>
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of_gpio.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+/* Meta Information */
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Frieder Hannenheim");
+MODULE_DESCRIPTION("A driver for Synaptics TCM Oncell Touchpanels");
+
+#define TOUCHPANEL_DEVICE "syna-tcm"
+
+#define POWERUP_TO_RESET_TIME 10
+#define RESET_TO_NORMAL_TIME 80
+
+#define RESPONSE_TIMEOUT_MS 1000
+
+#define MESSAGE_HEADER_SIZE 4
+#define MESSAGE_MARKER 0xA5
+#define MESSAGE_PADDING 0x5A
+
+#define READ_LENGTH 9
+#define MAX_I2C_RETRY_TIME 4
+
+#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */
+#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */
+
+#define APP_STATUS_POLL_MS 100
+#define TOUCH_REPORT_CONFIG_SIZE 128
+
+enum command {
+	CMD_NONE = 0x00,
+	CMD_CONTINUE_WRITE = 0x01,
+	CMD_IDENTIFY = 0x02,
+	CMD_RESET = 0x04,
+	CMD_ENABLE_REPORT = 0x05,
+	CMD_DISABLE_REPORT = 0x06,
+	CMD_GET_BOOT_INFO = 0x10,
+	CMD_ERASE_FLASH = 0x11,
+	CMD_WRITE_FLASH = 0x12,
+	CMD_READ_FLASH = 0x13,
+	CMD_RUN_APPLICATION_FIRMWARE = 0x14,
+	CMD_SPI_MASTER_WRITE_THEN_READ = 0x15,
+	CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16,
+	CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f,
+	CMD_GET_APPLICATION_INFO = 0x20,
+	CMD_GET_STATIC_CONFIG = 0x21,
+	CMD_SET_STATIC_CONFIG = 0x22,
+	CMD_GET_DYNAMIC_CONFIG = 0x23,
+	CMD_SET_DYNAMIC_CONFIG = 0x24,
+	CMD_GET_TOUCH_REPORT_CONFIG = 0x25,
+	CMD_SET_TOUCH_REPORT_CONFIG = 0x26,
+	CMD_REZERO = 0x27,
+	CMD_COMMIT_CONFIG = 0x28,
+	CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29,
+	CMD_PRODUCTION_TEST = 0x2a,
+	CMD_SET_CONFIG_ID = 0x2b,
+	CMD_ENTER_DEEP_SLEEP = 0x2c,
+	CMD_EXIT_DEEP_SLEEP = 0x2d,
+	CMD_GET_TOUCH_INFO = 0x2e,
+	CMD_GET_DATA_LOCATION = 0x2f,
+	CMD_DOWNLOAD_CONFIG = 0xc0,
+	CMD_GET_NSM_INFO = 0xc3,
+	CMD_EXIT_ESD = 0xc4,
+};
+
+enum touch_report_code {
+	TOUCH_END = 0,
+	TOUCH_FOREACH_ACTIVE_OBJECT = 1,
+	TOUCH_FOREACH_OBJECT = 2,
+	TOUCH_FOREACH_END = 3,
+	TOUCH_PAD_TO_NEXT_BYTE = 4,
+	TOUCH_TIMESTAMP = 5,
+	TOUCH_OBJECT_N_INDEX = 6,
+	TOUCH_OBJECT_N_CLASSIFICATION = 7,
+	TOUCH_OBJECT_N_X_POSITION = 8,
+	TOUCH_OBJECT_N_Y_POSITION = 9,
+	TOUCH_OBJECT_N_Z = 10,
+	TOUCH_OBJECT_N_X_WIDTH = 11,
+	TOUCH_OBJECT_N_Y_WIDTH = 12,
+	TOUCH_OBJECT_N_TX_POSITION_TIXELS = 13,
+	TOUCH_OBJECT_N_RX_POSITION_TIXELS = 14,
+	TOUCH_0D_BUTTONS_STATE = 15,
+	TOUCH_GESTURE_DOUBLE_TAP = 16,
+	TOUCH_FRAME_RATE = 17,
+	TOUCH_POWER_IM = 18,
+	TOUCH_CID_IM = 19,
+	TOUCH_RAIL_IM = 20,
+	TOUCH_CID_VARIANCE_IM = 21,
+	TOUCH_NSM_FREQUENCY = 22,
+	TOUCH_NSM_STATE = 23,
+	TOUCH_NUM_OF_ACTIVE_OBJECTS = 23,
+	TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME = 24,
+	TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80,
+	TOUCH_TUNING_SMALL_OBJECT_PARAMS = 0x81,
+	TOUCH_TUNING_0D_BUTTONS_VARIANCE = 0x82,
+	TOUCH_REPORT_GESTURE_SWIPE = 193,
+	TOUCH_REPORT_GESTURE_CIRCLE = 194,
+	TOUCH_REPORT_GESTURE_UNICODE = 195,
+	TOUCH_REPORT_GESTURE_VEE = 196,
+	TOUCH_REPORT_GESTURE_TRIANGLE = 197,
+	TOUCH_REPORT_GESTURE_INFO = 198,
+	TOUCH_REPORT_GESTURE_COORDINATE = 199,
+	TOUCH_REPORT_CUSTOMER_GRIP_INFO = 203,
+};
+
+enum touch_status {
+	LIFT = 0,
+	FINGER = 1,
+	GLOVED_FINGER = 2,
+	NOP = -1,
+};
+
+enum status_code {
+	STATUS_IDLE = 0x00,
+	STATUS_OK = 0x01,
+	STATUS_BUSY = 0x02,
+	STATUS_CONTINUED_READ = 0x03,
+	STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
+	STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
+	STATUS_NOT_IMPLEMENTED = 0x0e,
+	STATUS_ERROR = 0x0f,
+	STATUS_INVALID = 0xff,
+};
+
+enum report_type {
+	REPORT_IDENTIFY = 0x10,
+	REPORT_TOUCH = 0x11,
+	REPORT_DELTA = 0x12,
+	REPORT_RAW = 0x13,
+	REPORT_DEBUG = 0x14,
+	REPORT_LOG = 0x1d,
+	REPORT_TOUCH_HOLD = 0x20,
+};
+
+enum syna_tcm_oncell_regulators {
+	SYNA_TCM_ONCELL_REGULATOR_VDD,
+	SYNA_TCM_ONCELL_REGULATOR_VCC,
+};
+
+enum boot_mode {
+	MODE_APPLICATION = 0x01,
+	MODE_HOST_DOWNLOAD = 0x02,
+	MODE_BOOTLOADER = 0x0b,
+	MODE_TDDI_BOOTLOADER = 0x0c,
+};
+
+enum app_status {
+	APP_STATUS_OK = 0x00,
+	APP_STATUS_BOOTING = 0x01,
+	APP_STATUS_UPDATING = 0x02,
+	APP_STATUS_BAD_APP_CONFIG = 0xff,
+};
+
+struct syna_tcm_app_info {
+	unsigned char version[2];
+	unsigned char status[2];
+	unsigned char static_config_size[2];
+	unsigned char dynamic_config_size[2];
+	unsigned char app_config_start_write_block[2];
+	unsigned char app_config_size[2];
+	unsigned char max_touch_report_config_size[2];
+	unsigned char max_touch_report_payload_size[2];
+	unsigned char customer_config_id[16];
+	unsigned char max_x[2];
+	unsigned char max_y[2];
+	unsigned char max_objects[2];
+	unsigned char num_of_buttons[2];
+	unsigned char num_of_image_rows[2];
+	unsigned char num_of_image_cols[2];
+	unsigned char has_hybrid_data[2];
+};
+
+struct syna_tcm_identification {
+	unsigned char version;
+	unsigned char mode;
+	unsigned char part_number[16];
+	unsigned char build_id[4];
+	unsigned char max_write_size[2];
+};
+
+struct syna_tcm_message_header {
+	unsigned char marker;
+	unsigned char code;
+	unsigned char length[2];
+};
+
+struct touch_data {
+	unsigned char status;
+	unsigned int x_pos;
+	unsigned int y_pos;
+	unsigned int x_width;
+	unsigned int y_width;
+	unsigned int z;
+};
+
+struct syna_tcm_data {
+	struct i2c_client *client;
+	struct regulator_bulk_data regulators[2];
+	struct input_dev *input;
+	struct gpio_desc *reset_gpio;
+	struct touchscreen_properties prop;
+	struct syna_tcm_identification id_info;
+	struct syna_tcm_app_info app_info;
+
+	unsigned char *response_buf;
+	unsigned int response_length; // Response length including header
+
+	unsigned int app_status;
+
+	unsigned char *report_config;
+	unsigned int report_size;
+
+	bool initialize_done;
+
+	unsigned int touchpanel_max_objects;
+};
+
+DECLARE_COMPLETION(response_complete);
+
+static inline unsigned int le2_to_uint(const unsigned char *src)
+{
+	return (unsigned int)src[0] + (unsigned int)src[1] * 0x100;
+}
+
+static inline unsigned int ceil_div(unsigned int dividend, unsigned int divisor)
+{
+	return (dividend + divisor - 1) / divisor;
+}
+
+static int touch_i2c_continue_read(struct i2c_client *client,
+				   unsigned char *data, unsigned short length)
+{
+	int retval;
+	unsigned char retry;
+	struct i2c_msg msg;
+
+	msg.addr = client->addr;
+	msg.flags = I2C_M_RD;
+	msg.len = length;
+	msg.buf = data;
+
+	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
+		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
+			retval = length;
+			break;
+		}
+		msleep(20);
+	}
+	if (retry == MAX_I2C_RETRY_TIME) {
+		pr_warn("%s: I2C read over retry limit\n", __func__);
+		retval = -EIO;
+	}
+	return retval;
+}
+
+static int touch_i2c_continue_write(struct i2c_client *client,
+				    unsigned char *data, unsigned short length)
+{
+	int retval;
+	unsigned char retry;
+	struct i2c_msg msg;
+
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.buf = data;
+	msg.len = length;
+
+	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
+		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
+			retval = length;
+			break;
+		}
+		msleep(20);
+	}
+	if (retry == MAX_I2C_RETRY_TIME) {
+		pr_warn("%s: I2C write over retry limit\n", __func__);
+		retval = -EIO;
+	}
+	return retval;
+}
+
+static int syna_tcm_continued_read(struct syna_tcm_data *tcm_info,
+				   unsigned int payload_length,
+				   unsigned char *message_buffer)
+{
+	int retval = 0;
+	unsigned char marker = 0, code = 0;
+	unsigned int total_length = 0, remaining_length = 0;
+	unsigned char *temp_buffer;
+
+	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
+	remaining_length = total_length - READ_LENGTH;
+
+	temp_buffer = kmalloc(remaining_length + 2, GFP_KERNEL);
+	if (!temp_buffer)
+		return -ENOMEM;
+
+	retval = touch_i2c_continue_read(tcm_info->client, temp_buffer,
+					 remaining_length + 2);
+	if (retval < 0) {
+		pr_warn("touch_i2c_continue_read failed");
+		return retval;
+	}
+
+	marker = temp_buffer[0];
+	code = temp_buffer[1];
+
+	if (marker != MESSAGE_MARKER) {
+		pr_warn("incorrect header marker (0x%02x)\n", marker);
+		return -EIO;
+	}
+
+	if (code != STATUS_CONTINUED_READ) {
+		pr_warn("incorrect header code (0x%02x)\n", code);
+		return -EIO;
+	}
+
+	memcpy(&message_buffer[READ_LENGTH], &temp_buffer[2], remaining_length);
+	message_buffer[total_length - 1] = MESSAGE_PADDING;
+	kfree(temp_buffer);
+
+	return 0;
+}
+
+/**
+ * syna_tcm_raw_write() - write command/data to device without receiving
+ * response
+ *
+ * @tcm_info: handle of core module
+ * @command: command to send to device
+ * @data: data to send to device
+ * @length: length of data in bytes
+ * @add_length: wether the length of the message should be added after the command
+ *
+ * A command and its data, if any, are sent to the device.
+ */
+static int syna_tcm_raw_write(struct syna_tcm_data *tcm_info,
+			      unsigned char command, unsigned char *data,
+			      unsigned int length, bool add_length)
+{
+	int retval = 0;
+	unsigned char *out_buf;
+
+	unsigned int header_size = add_length && length ? 3 : 1;
+
+	out_buf = kmalloc(length + header_size, GFP_KERNEL);
+	if (retval < 0)
+		return retval;
+
+	out_buf[0] = command;
+	if (add_length && length) {
+		out_buf[1] = (unsigned char)length;
+		out_buf[2] = (unsigned char)(length >> 8);
+	}
+
+	if (length)
+		memcpy(&out_buf[header_size], data, length);
+
+	retval = touch_i2c_continue_write(tcm_info->client, out_buf,
+					  length + header_size);
+	if (retval < 0) {
+		kfree(out_buf);
+		return retval;
+	}
+
+	kfree(out_buf);
+	return 0;
+}
+
+/**
+ * syna_tcm_read_message() - read message from device
+ *
+ * @tcm_info: handle of core module
+ * @length: length of data in bytes in raw read mode
+ */
+static int syna_tcm_read_message(struct syna_tcm_data *tcm_info,
+				 unsigned int length)
+{
+	int retval = 0;
+	unsigned int total_length = 0, payload_length = 0;
+	unsigned char *message_buffer;
+	struct syna_tcm_message_header *header = NULL;
+
+	message_buffer = kmalloc(READ_LENGTH, GFP_KERNEL);
+	if (!message_buffer)
+		return -ENOMEM;
+
+	retval = touch_i2c_continue_read(tcm_info->client, message_buffer,
+					 READ_LENGTH);
+	if (retval < 0)
+		return retval;
+
+	header = (struct syna_tcm_message_header *)message_buffer;
+	if (header->marker != MESSAGE_MARKER) {
+		pr_warn("wrong header marker:0x%02x\n", header->marker);
+		return -ENXIO;
+	}
+
+	unsigned char report_code = header->code;
+
+	payload_length = le2_to_uint(header->length);
+
+	if (report_code <= STATUS_ERROR || report_code == STATUS_INVALID) {
+		switch (report_code) {
+		case STATUS_OK:
+			break;
+		case STATUS_CONTINUED_READ:
+		case STATUS_IDLE:
+		case STATUS_BUSY:
+			payload_length = 0;
+			kfree(message_buffer);
+			return 0;
+		default:
+			if (report_code != STATUS_ERROR) {
+				pr_warn("IO ERROR");
+				kfree(message_buffer);
+				return -EIO;
+			}
+		}
+	}
+
+	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
+
+	unsigned char *temp_buffer = message_buffer;
+
+	message_buffer = kmalloc(total_length, GFP_KERNEL);
+	if (!message_buffer)
+		return -ENOMEM;
+
+	// Copy data already read to message_buffer
+	memcpy(message_buffer, temp_buffer, READ_LENGTH);
+	kfree(temp_buffer);
+
+	if (payload_length == 0) {
+		message_buffer[total_length - 1] = MESSAGE_PADDING;
+		goto check_padding;
+	}
+
+	if (MESSAGE_HEADER_SIZE + payload_length > READ_LENGTH) {
+		retval = syna_tcm_continued_read(tcm_info, payload_length,
+						 message_buffer);
+		if (retval < 0) {
+			pr_warn("failed to do continued read\n");
+			return retval;
+		};
+	}
+
+check_padding:
+	if (message_buffer[total_length - 1] != MESSAGE_PADDING) {
+		pr_warn("missing message padding");
+		return -EIO;
+	}
+
+	if (tcm_info->response_buf != NULL)
+		kfree(tcm_info->response_buf);
+
+	tcm_info->response_buf = message_buffer;
+	tcm_info->response_length = total_length;
+
+	return 0;
+}
+
+static int syna_tcm_write_message(struct syna_tcm_data *tcm_info,
+				  unsigned char command, unsigned char *payload,
+				  unsigned int length, unsigned char **resp_buf,
+				  unsigned int *resp_length)
+{
+	int retval = 0;
+
+	if (resp_buf == NULL) {
+		retval = syna_tcm_raw_write(tcm_info, command, payload, length,
+					    false);
+		goto exit;
+	}
+	reinit_completion(&response_complete);
+
+	tcm_info->response_buf = *resp_buf;
+	tcm_info->response_length = *resp_length;
+
+	// Here write command to device
+	retval = syna_tcm_raw_write(tcm_info, command, payload, length, true);
+	if (retval != 0)
+		goto exit;
+
+	retval = wait_for_completion_timeout(&response_complete,
+					     RESPONSE_TIMEOUT_MS);
+	if (retval == 0) {
+		pr_warn("timed out waiting for response (command 0x%02x)\n",
+			command);
+		retval = -EIO;
+	}
+
+exit:
+	if (tcm_info->response_length < 0)
+		return -1;
+	if (resp_buf != NULL) {
+		*resp_buf = tcm_info->response_buf;
+		*resp_length = tcm_info->response_length;
+	}
+
+	return retval;
+}
+
+static int syna_tcm_set_normal_report_config(struct syna_tcm_data *tcm_info)
+{
+	int retval;
+	unsigned int idx = 0;
+	unsigned int length;
+
+	length = le2_to_uint(tcm_info->app_info.max_touch_report_config_size);
+
+	if (length < TOUCH_REPORT_CONFIG_SIZE) {
+		pr_warn("invalid maximum touch report config size: %d\n",
+			length);
+		return -EINVAL;
+	}
+
+	unsigned char *report_buf = kmalloc(length, GFP_KERNEL);
+
+	if (!report_buf)
+		return -ENOMEM;
+
+	report_buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT;
+	report_buf[idx++] = TOUCH_OBJECT_N_INDEX;
+	report_buf[idx++] = 4;
+	report_buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION;
+	report_buf[idx++] = 4;
+	report_buf[idx++] = TOUCH_OBJECT_N_X_POSITION;
+	report_buf[idx++] = 16;
+	report_buf[idx++] = TOUCH_OBJECT_N_Y_POSITION;
+	report_buf[idx++] = 16;
+	report_buf[idx++] = TOUCH_OBJECT_N_X_WIDTH;
+	report_buf[idx++] = 12;
+	report_buf[idx++] = TOUCH_OBJECT_N_Y_WIDTH;
+	report_buf[idx++] = 12;
+	report_buf[idx++] = TOUCH_FOREACH_END;
+	report_buf[idx++] = TOUCH_END;
+
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+	retval = syna_tcm_write_message(tcm_info, CMD_SET_TOUCH_REPORT_CONFIG,
+					report_buf, length, &resp_buf,
+					&resp_length);
+	if (retval < 0) {
+		pr_warn("failed to set report config");
+		return retval;
+	}
+
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_get_report_config(struct syna_tcm_data *tcm_info)
+{
+	int retval;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+	retval = syna_tcm_write_message(tcm_info, CMD_GET_TOUCH_REPORT_CONFIG,
+					NULL, 0, &resp_buf, &resp_length);
+	if (retval < 0) {
+		pr_warn("failed to write command 0x%02x\n",
+			CMD_GET_TOUCH_REPORT_CONFIG);
+		return retval;
+	}
+
+	tcm_info->report_size = resp_length - MESSAGE_HEADER_SIZE - 1;
+
+	kfree(tcm_info->report_config);
+	tcm_info->report_config = kmalloc(tcm_info->report_size, GFP_KERNEL);
+
+	memcpy(tcm_info->report_config, &resp_buf[MESSAGE_HEADER_SIZE],
+	       tcm_info->report_size);
+
+	return 0;
+}
+
+static int syna_tcm_get_app_info(struct syna_tcm_data *tcm_info)
+{
+	int retval = 0;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+	unsigned int timeout = RESPONSE_TIMEOUT_MS;
+
+get_app_info:
+	retval = syna_tcm_write_message(tcm_info, CMD_GET_APPLICATION_INFO,
+					NULL, 0, &resp_buf, &resp_length);
+	if (retval < 0) {
+		pr_warn("failed to write command 0x%02x\n",
+			CMD_GET_APPLICATION_INFO);
+		goto exit;
+	}
+
+	memcpy(&tcm_info->app_info, &resp_buf[MESSAGE_HEADER_SIZE],
+	       sizeof(tcm_info->app_info));
+
+	tcm_info->app_status = le2_to_uint(tcm_info->app_info.status);
+
+	if (tcm_info->app_status == APP_STATUS_BOOTING ||
+	    tcm_info->app_status == APP_STATUS_UPDATING) {
+		if (timeout > 0) {
+			msleep(APP_STATUS_POLL_MS);
+			timeout -= APP_STATUS_POLL_MS;
+			goto get_app_info;
+		}
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_identify(struct syna_tcm_data *tcm_info)
+{
+	int retval = 0;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+	retval = syna_tcm_write_message(tcm_info, CMD_IDENTIFY, NULL, 0,
+					&resp_buf, &resp_length);
+	if (retval < 0)
+		goto exit;
+
+	memcpy(&tcm_info->id_info, &resp_buf[MESSAGE_HEADER_SIZE],
+	       sizeof(tcm_info->id_info));
+
+	if (tcm_info->id_info.mode == MODE_APPLICATION)
+		retval = syna_tcm_get_app_info(tcm_info);
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_run_application_firmware(struct syna_tcm_data *tcm_info)
+{
+	int retval = 0;
+	unsigned int retry = 3;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+retry:
+	retval = syna_tcm_write_message(tcm_info, CMD_RUN_APPLICATION_FIRMWARE,
+					NULL, 0, &resp_buf, &resp_length);
+
+	if (retval < 0) {
+		if (retry--)
+			goto retry;
+		goto exit;
+	}
+
+	retval = syna_tcm_identify(tcm_info);
+	if (retval < 0) {
+		if (retry--)
+			goto retry;
+		goto exit;
+	}
+
+	pr_info("syna_tcm: boot_mode: 0x%02x", tcm_info->id_info.mode);
+
+	if (tcm_info->id_info.mode != MODE_APPLICATION) {
+		if (retry--)
+			goto retry;
+		retval = -EINVAL;
+		goto exit;
+	} else if (tcm_info->app_status != APP_STATUS_OK) {
+		pr_warn("syna_tcm: application status = 0x%02x\n",
+			tcm_info->app_status);
+		if (retry--)
+			goto retry;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+/*
+ * syna_get_report_data - Retrieve data from touch report
+ *
+ * @tcm_info: handle of tcm module
+ * @offset: start bit of retrieved data
+ * @bits: total bits of retrieved data
+ * @data: pointer of data, at most 4 byte
+ * Retrieve data from the touch report based on the bit offset and bit length
+ * information from the touch report configuration.
+ */
+static int syna_tcm_get_report_data(unsigned char *report_buf,
+				    unsigned int report_buf_length,
+				    unsigned int offset, unsigned int num_bits,
+				    unsigned int *data)
+{
+	unsigned char mask = 0;
+	unsigned char byte_data = 0;
+	unsigned int output_data = 0;
+	unsigned int bit_offset = offset % 8;
+	unsigned int byte_offset = offset / 8;
+	unsigned int data_bits = 0;
+	unsigned int available_bits = 0;
+	unsigned int remaining_bits = num_bits;
+
+	if (num_bits == 0 || num_bits > 32) {
+		pr_warn("report value larger than 32 bits: %d\n", num_bits);
+		memcpy(data, &report_buf[byte_offset], num_bits / 8);
+		return 0;
+	}
+
+	if ((offset + num_bits) > report_buf_length * 8) {
+		pr_warn("report: offset and bits beyond total read length: %d vs %d bits",
+			offset + num_bits, report_buf_length * 8);
+		*data = 0;
+		return -1;
+	}
+
+	while (remaining_bits) {
+		byte_data = report_buf[byte_offset];
+		byte_data >>= bit_offset;
+
+		available_bits = 8 - bit_offset;
+		data_bits = available_bits < remaining_bits ? available_bits :
+							      remaining_bits;
+		mask = 0xff >> (8 - data_bits);
+
+		byte_data &= mask;
+
+		output_data |= byte_data << (num_bits - remaining_bits);
+
+		bit_offset = 0;
+		byte_offset += 1;
+		remaining_bits -= data_bits;
+	}
+
+	*data = output_data;
+
+	return 0;
+}
+
+static int syna_tcm_parse_touch_report(struct syna_tcm_data *tcm_info)
+{
+	int config_data_pointer = 0, obj = 0, foreach_start = 0, foreach_end,
+	    retval;
+	unsigned int offset = 0, num_bits, data = 0;
+	unsigned char *report_config = tcm_info->report_config;
+	unsigned int config_size = tcm_info->report_size;
+
+	unsigned char *report = &tcm_info->response_buf[MESSAGE_HEADER_SIZE];
+	unsigned int report_size =
+		tcm_info->response_length - MESSAGE_HEADER_SIZE - 1;
+	unsigned char code;
+
+	struct touch_data *touch_data =
+		kmalloc(sizeof(touch_data) * tcm_info->touchpanel_max_objects,
+			GFP_KERNEL);
+	memset(touch_data, 0,
+	       sizeof(touch_data) * tcm_info->touchpanel_max_objects);
+
+	while (config_data_pointer < config_size) {
+		code = report_config[config_data_pointer++];
+		switch (code) {
+		case TOUCH_END:
+			goto exit;
+		case TOUCH_FOREACH_ACTIVE_OBJECT:
+			obj = 0;
+			foreach_start = config_data_pointer;
+			if (offset >= report_size * 8)
+				goto exit;
+			break;
+		case TOUCH_FOREACH_END:
+			foreach_end = config_data_pointer;
+			if (offset < report_size * 8)
+				config_data_pointer = foreach_start;
+			break;
+		// Ignored
+		case TOUCH_OBJECT_N_TX_POSITION_TIXELS:
+		case TOUCH_OBJECT_N_RX_POSITION_TIXELS:
+		case TOUCH_FRAME_RATE:
+		case TOUCH_0D_BUTTONS_STATE:
+		case 30: // I don't know what command 30 is and it isn't in the downstream driver. After it comes a length of 8 bits and this is ignored
+			num_bits = report_config[config_data_pointer++];
+			offset += num_bits;
+			break;
+		case TOUCH_PAD_TO_NEXT_BYTE:
+			offset = ceil_div(offset, 8) * 8;
+			break;
+		case TOUCH_OBJECT_N_INDEX:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &obj);
+			if (retval < 0) {
+				pr_warn("failed to get object index\n");
+				return retval;
+			}
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_CLASSIFICATION:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object classification\n");
+				return retval;
+			}
+			if (obj >= tcm_info->touchpanel_max_objects) {
+				pr_warn("too many objects\n");
+				return -1;
+			}
+			touch_data[obj].status = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_X_POSITION:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object x position\n");
+				return retval;
+			}
+			touch_data[obj].x_pos = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_Y_POSITION:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object y position\n");
+				return retval;
+			}
+			touch_data[obj].y_pos = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_Z:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object z\n");
+				return retval;
+			}
+			touch_data[obj].z = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_X_WIDTH:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object x width\n");
+				return retval;
+			}
+			touch_data[obj].x_width = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_Y_WIDTH:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object y width\n");
+				return retval;
+			}
+			touch_data[obj].y_width = data;
+			offset += num_bits;
+			break;
+		default:
+			pr_notice("unrecognized touch report config code: %d",
+				  code);
+		}
+	}
+
+exit:
+	for (int i = 0; i < tcm_info->touchpanel_max_objects; i++) {
+		if (touch_data[i].status == NOP)
+			continue;
+
+		bool lift = touch_data[i].status == LIFT ? true : false;
+
+		input_mt_slot(tcm_info->input, i);
+		input_mt_report_slot_state(tcm_info->input, MT_TOOL_FINGER,
+					   !lift);
+
+		int x_width = touch_data[i].x_width;
+		int y_width = touch_data[i].y_width;
+		int major_width = x_width > y_width ? x_width : y_width;
+		int minor_width = x_width > y_width ? y_width : x_width;
+		int x_is_major = x_width > y_width ? true : false;
+
+		if (!lift) {
+			input_report_abs(tcm_info->input, ABS_MT_POSITION_X,
+					 touch_data[i].x_pos);
+			input_report_abs(tcm_info->input, ABS_MT_POSITION_Y,
+					 touch_data[i].y_pos);
+			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MAJOR,
+					 major_width);
+			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MINOR,
+					 minor_width);
+			input_report_abs(tcm_info->input, ABS_MT_ORIENTATION,
+					 x_is_major);
+			input_report_abs(tcm_info->input, ABS_MT_PRESSURE,
+					 touch_data[i].z);
+		}
+
+		input_sync(tcm_info->input);
+	}
+
+	kfree(touch_data);
+	return 0;
+}
+
+static irqreturn_t syna_irq_handler(int irq, void *dev)
+{
+	int retval;
+	struct syna_tcm_data *tcm_info = dev;
+
+	retval = syna_tcm_read_message(tcm_info, 0);
+	if (retval < 0) {
+		tcm_info->response_length = -1;
+		goto exit;
+	}
+
+	if (tcm_info->response_buf[1] == REPORT_TOUCH)
+		syna_tcm_parse_touch_report(tcm_info);
+
+exit:
+	if (tcm_info->response_buf[1] <= REPORT_IDENTIFY)
+		complete(&response_complete);
+
+	return IRQ_HANDLED;
+}
+
+static int syna_tcm_power_on(struct syna_tcm_data *tcm_info)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(tcm_info->regulators),
+				    tcm_info->regulators);
+	if (ret)
+		return ret;
+
+	gpiod_set_value(tcm_info->reset_gpio, false);
+	msleep(POWERUP_TO_RESET_TIME);
+	gpiod_set_value(tcm_info->reset_gpio, true);
+	msleep(RESET_TO_NORMAL_TIME);
+
+	return 0;
+}
+
+static int syna_tcm_probe(struct i2c_client *client)
+{
+	struct syna_tcm_data *tcm_info;
+	int err;
+
+	pr_info("starting probe for syna_tcm_oncell touchscreen");
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA |
+					     I2C_FUNC_SMBUS_I2C_BLOCK))
+		return -ENODEV;
+
+	tcm_info = devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL);
+	if (!tcm_info)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, tcm_info);
+	tcm_info->client = client;
+	tcm_info->response_buf = NULL;
+
+	of_property_read_u32(client->dev.of_node, "max-objects",
+			     &tcm_info->touchpanel_max_objects);
+
+	tcm_info->reset_gpio =
+		gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH);
+
+	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply = "vdd";
+	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply = "vcc";
+	err = devm_regulator_bulk_get(&client->dev,
+				      ARRAY_SIZE(tcm_info->regulators),
+				      tcm_info->regulators);
+	if (err)
+		return err;
+
+	// TODO: uncomment once syna_tcm_power_off is implemented
+	// err = devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_off, tcm_info);
+	// if (err)
+	//     return err;
+
+	err = syna_tcm_power_on(tcm_info);
+	if (err < 0)
+		return err;
+
+	// This needs to happen before the first write to the device
+	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+					syna_irq_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"syna_tcm_oncell_irq", tcm_info);
+	if (err)
+		return err;
+
+	err = syna_tcm_run_application_firmware(tcm_info);
+	if (err < 0)
+		return err;
+
+	// err = syna_tcm_set_normal_report_config(tcm_info);
+	// if (err < 0)
+		// pr_err("syna_tcm: failed to set normal touch report config")
+
+	err = syna_tcm_get_report_config(tcm_info);
+	if (err < 0)
+		return err;
+
+	tcm_info->input = devm_input_allocate_device(&client->dev);
+	if (!tcm_info->input)
+		return -ENOMEM;
+
+	tcm_info->input->name = TOUCHPANEL_DEVICE;
+	tcm_info->input->id.bustype = BUS_I2C;
+
+	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_X, 0,
+			     le2_to_uint(tcm_info->app_info.max_x), 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_Y, 0,
+			     le2_to_uint(tcm_info->app_info.max_y), 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+	touchscreen_parse_properties(tcm_info->input, true, &tcm_info->prop);
+
+	err = input_mt_init_slots(tcm_info->input,
+				  tcm_info->touchpanel_max_objects,
+				  INPUT_MT_DIRECT);
+	if (err)
+		return err;
+
+	input_set_drvdata(tcm_info->input, tcm_info);
+
+	err = input_register_device(tcm_info->input);
+	if (err)
+		return err;
+
+	pr_info("syna_tcm: probe done");
+	tcm_info->initialize_done = true;
+
+	return 0;
+}
+
+static const struct of_device_id syna_driver_ids[] = {
+	{
+		.compatible = "syna,s3908",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, syna_driver_ids);
+
+static const struct i2c_device_id syna_i2c_ids[] = { { TOUCHPANEL_DEVICE, 0 },
+						     {} };
+MODULE_DEVICE_TABLE(i2c, syna_i2c_ids);
+
+// static const struct dev_pm_ops syna_pm_ops = {
+//     .suspend = syna_i2c_suspend,
+//     .resume = syna_i2c_resume,
+// };
+
+static struct i2c_driver syna_i2c_driver = {
+	.probe	= syna_tcm_probe,
+	// .remove	   = syna_i2c_remove,
+	// .shutdown   = syna_tp_shutdown,
+	.id_table	= syna_i2c_ids,
+	.driver	= {
+	.name	= TOUCHPANEL_DEVICE,
+	.of_match_table = syna_driver_ids,
+		// .pm = &syna_pm_ops,
+	},
+};
+
+module_i2c_driver(syna_i2c_driver);
-- 
2.44.0


^ permalink raw reply related

* Re: [PATCH] input/touchscreen: synaptics_tcm_oncell: add driver
From: friederhannenheim @ 2024-03-27 22:19 UTC (permalink / raw)
  To: dmitry.torokhov, linux-input, linux-kernel
In-Reply-To: <20240327214643.7055-1-friederhannenheim@riseup.net>

Looks like I forgot the most important thing: What is this actually used for. The Synaptics TCM Oncell Touchscreen is built into multiple smartphones: The OnePlus 8T (my device), Realme GT2 Pro, OnePlus 9 Pro, and more. I'm currently trying to get the OnePlus 8T working on mainline Linux that's why I wrote this driver. 

On 2024-03-27 22:39, Frieder Hannenheim wrote:
> This is a bit of a stripped down and partially reworked driver for the
> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
> LineageOS kernel found at
> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
> lineage-20. The code was originally written by OnePlus developers but
> I'm not sure how to credit them correctly.
> 
> Currently the driver is in a pretty good shape, the only thing that is
> not working is setting a report config. To me it looks like some data is
> sent by the touchscreen firmware after setting the report config that is
> making the irq handler crash. Sadly I haven't been able to test out why.
> The driver works fine also with the default touch report config so maybe
> we can just use that and not set our own. 
> 
> Signed-off-by: Frieder Hannenheim <friederhannenheim@riseup.net>
> ---
>  .../input/touchscreen/syna,s3908.yaml         |   63 +
>  drivers/input/touchscreen/Kconfig             |   11 +
>  drivers/input/touchscreen/Makefile            |    1 +
>  .../input/touchscreen/synaptics_tcm_oncell.c  | 1104 +++++++++++++++++
>  4 files changed, 1179 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
>  create mode 100644 drivers/input/touchscreen/synaptics_tcm_oncell.c
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
> new file mode 100644
> index 000000000000..1a85747e2f52
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
> @@ -0,0 +1,63 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/touchscreen/synaptics,s3908.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Synaptics s3908 touchscreen controller
> +
> +maintainers:
> +  - Frieder Hannenheim <frieder.hannenheim@riseup.net>
> +
> +allOf:
> +  - $ref: touchscreen.yaml#
> +
> +properties:
> +  compatible:
> +    const: syna,s3908
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  reset-gpios:
> +    maxItems: 1
> +    description: reset gpio the chip is connected to.
> +
> +  vdd-supply: true
> +  vcc-supply: true
> +
> +  max-objects: true
> +
> +unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - reset-gpios
> +  - vdd-supply
> +  - vcc-supply
> +  - max-objects
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        touchscreen@48 {
> +            compatible = "syna,s3908";
> +            reg = <0x48>;
> +            interrupt-parent = <&gpa1>;
> +            interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> +            reset-gpios = <38 0>;
> +            vdd-supply = <&ldo30_reg>;
> +            vcc-supply = <&ldo31_reg>;
> +
> +            max-objects = <10>;
> +        };
> +    };
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 1f8b33c2b03d..eba31ae27391 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -502,6 +502,17 @@ config TOUCHSCREEN_S6SY761
>  
>  	  To compile this driver as module, choose M here: the
>  	  module will be called s6sy761.
> +	  
> +config TOUCHSCREEN_SYNA_TCM_ONCELL
> +	tristate "Synaptics TCM Oncell Touchscreen driver"
> +	depends on I2C
> +	help
> +	  Say Y if you have the Synaptics TCM Oncell driver
> +
> +	  If unsure, say N
> +
> +	  To compile this driver as module, choose M here: the
> +	  module will be called synaptics_tcm_oncell.
>  
>  config TOUCHSCREEN_GUNZE
>  	tristate "Gunze AHL-51S touchscreen"
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 7d52592f4290..f5395ccf09d5 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -86,6 +86,7 @@ obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
>  obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
>  obj-$(CONFIG_TOUCHSCREEN_SUR40)		+= sur40.o
>  obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI)	+= surface3_spi.o
> +obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM_ONCELL)	+= synaptics_tcm_oncell.o
>  obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC)	+= ti_am335x_tsc.o
>  obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
>  obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
> diff --git a/drivers/input/touchscreen/synaptics_tcm_oncell.c b/drivers/input/touchscreen/synaptics_tcm_oncell.c
> new file mode 100644
> index 000000000000..b874287f37af
> --- /dev/null
> +++ b/drivers/input/touchscreen/synaptics_tcm_oncell.c
> @@ -0,0 +1,1104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + *  Driver for Synaptics TCM Oncell Touchscreens
> + *
> + *  Copyright (c) 2024 Frieder Hannenheim <friederhannenheim@riseup.net>
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <asm/unaligned.h>
> +#include <linux/delay.h>
> +#include <linux/input/mt.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regulator/consumer.h>
> +
> +/* Meta Information */
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Frieder Hannenheim");
> +MODULE_DESCRIPTION("A driver for Synaptics TCM Oncell Touchpanels");
> +
> +#define TOUCHPANEL_DEVICE "syna-tcm"
> +
> +#define POWERUP_TO_RESET_TIME 10
> +#define RESET_TO_NORMAL_TIME 80
> +
> +#define RESPONSE_TIMEOUT_MS 1000
> +
> +#define MESSAGE_HEADER_SIZE 4
> +#define MESSAGE_MARKER 0xA5
> +#define MESSAGE_PADDING 0x5A
> +
> +#define READ_LENGTH 9
> +#define MAX_I2C_RETRY_TIME 4
> +
> +#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */
> +#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */
> +
> +#define APP_STATUS_POLL_MS 100
> +#define TOUCH_REPORT_CONFIG_SIZE 128
> +
> +enum command {
> +	CMD_NONE = 0x00,
> +	CMD_CONTINUE_WRITE = 0x01,
> +	CMD_IDENTIFY = 0x02,
> +	CMD_RESET = 0x04,
> +	CMD_ENABLE_REPORT = 0x05,
> +	CMD_DISABLE_REPORT = 0x06,
> +	CMD_GET_BOOT_INFO = 0x10,
> +	CMD_ERASE_FLASH = 0x11,
> +	CMD_WRITE_FLASH = 0x12,
> +	CMD_READ_FLASH = 0x13,
> +	CMD_RUN_APPLICATION_FIRMWARE = 0x14,
> +	CMD_SPI_MASTER_WRITE_THEN_READ = 0x15,
> +	CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16,
> +	CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f,
> +	CMD_GET_APPLICATION_INFO = 0x20,
> +	CMD_GET_STATIC_CONFIG = 0x21,
> +	CMD_SET_STATIC_CONFIG = 0x22,
> +	CMD_GET_DYNAMIC_CONFIG = 0x23,
> +	CMD_SET_DYNAMIC_CONFIG = 0x24,
> +	CMD_GET_TOUCH_REPORT_CONFIG = 0x25,
> +	CMD_SET_TOUCH_REPORT_CONFIG = 0x26,
> +	CMD_REZERO = 0x27,
> +	CMD_COMMIT_CONFIG = 0x28,
> +	CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29,
> +	CMD_PRODUCTION_TEST = 0x2a,
> +	CMD_SET_CONFIG_ID = 0x2b,
> +	CMD_ENTER_DEEP_SLEEP = 0x2c,
> +	CMD_EXIT_DEEP_SLEEP = 0x2d,
> +	CMD_GET_TOUCH_INFO = 0x2e,
> +	CMD_GET_DATA_LOCATION = 0x2f,
> +	CMD_DOWNLOAD_CONFIG = 0xc0,
> +	CMD_GET_NSM_INFO = 0xc3,
> +	CMD_EXIT_ESD = 0xc4,
> +};
> +
> +enum touch_report_code {
> +	TOUCH_END = 0,
> +	TOUCH_FOREACH_ACTIVE_OBJECT = 1,
> +	TOUCH_FOREACH_OBJECT = 2,
> +	TOUCH_FOREACH_END = 3,
> +	TOUCH_PAD_TO_NEXT_BYTE = 4,
> +	TOUCH_TIMESTAMP = 5,
> +	TOUCH_OBJECT_N_INDEX = 6,
> +	TOUCH_OBJECT_N_CLASSIFICATION = 7,
> +	TOUCH_OBJECT_N_X_POSITION = 8,
> +	TOUCH_OBJECT_N_Y_POSITION = 9,
> +	TOUCH_OBJECT_N_Z = 10,
> +	TOUCH_OBJECT_N_X_WIDTH = 11,
> +	TOUCH_OBJECT_N_Y_WIDTH = 12,
> +	TOUCH_OBJECT_N_TX_POSITION_TIXELS = 13,
> +	TOUCH_OBJECT_N_RX_POSITION_TIXELS = 14,
> +	TOUCH_0D_BUTTONS_STATE = 15,
> +	TOUCH_GESTURE_DOUBLE_TAP = 16,
> +	TOUCH_FRAME_RATE = 17,
> +	TOUCH_POWER_IM = 18,
> +	TOUCH_CID_IM = 19,
> +	TOUCH_RAIL_IM = 20,
> +	TOUCH_CID_VARIANCE_IM = 21,
> +	TOUCH_NSM_FREQUENCY = 22,
> +	TOUCH_NSM_STATE = 23,
> +	TOUCH_NUM_OF_ACTIVE_OBJECTS = 23,
> +	TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME = 24,
> +	TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80,
> +	TOUCH_TUNING_SMALL_OBJECT_PARAMS = 0x81,
> +	TOUCH_TUNING_0D_BUTTONS_VARIANCE = 0x82,
> +	TOUCH_REPORT_GESTURE_SWIPE = 193,
> +	TOUCH_REPORT_GESTURE_CIRCLE = 194,
> +	TOUCH_REPORT_GESTURE_UNICODE = 195,
> +	TOUCH_REPORT_GESTURE_VEE = 196,
> +	TOUCH_REPORT_GESTURE_TRIANGLE = 197,
> +	TOUCH_REPORT_GESTURE_INFO = 198,
> +	TOUCH_REPORT_GESTURE_COORDINATE = 199,
> +	TOUCH_REPORT_CUSTOMER_GRIP_INFO = 203,
> +};
> +
> +enum touch_status {
> +	LIFT = 0,
> +	FINGER = 1,
> +	GLOVED_FINGER = 2,
> +	NOP = -1,
> +};
> +
> +enum status_code {
> +	STATUS_IDLE = 0x00,
> +	STATUS_OK = 0x01,
> +	STATUS_BUSY = 0x02,
> +	STATUS_CONTINUED_READ = 0x03,
> +	STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
> +	STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
> +	STATUS_NOT_IMPLEMENTED = 0x0e,
> +	STATUS_ERROR = 0x0f,
> +	STATUS_INVALID = 0xff,
> +};
> +
> +enum report_type {
> +	REPORT_IDENTIFY = 0x10,
> +	REPORT_TOUCH = 0x11,
> +	REPORT_DELTA = 0x12,
> +	REPORT_RAW = 0x13,
> +	REPORT_DEBUG = 0x14,
> +	REPORT_LOG = 0x1d,
> +	REPORT_TOUCH_HOLD = 0x20,
> +};
> +
> +enum syna_tcm_oncell_regulators {
> +	SYNA_TCM_ONCELL_REGULATOR_VDD,
> +	SYNA_TCM_ONCELL_REGULATOR_VCC,
> +};
> +
> +enum boot_mode {
> +	MODE_APPLICATION = 0x01,
> +	MODE_HOST_DOWNLOAD = 0x02,
> +	MODE_BOOTLOADER = 0x0b,
> +	MODE_TDDI_BOOTLOADER = 0x0c,
> +};
> +
> +enum app_status {
> +	APP_STATUS_OK = 0x00,
> +	APP_STATUS_BOOTING = 0x01,
> +	APP_STATUS_UPDATING = 0x02,
> +	APP_STATUS_BAD_APP_CONFIG = 0xff,
> +};
> +
> +struct syna_tcm_app_info {
> +	unsigned char version[2];
> +	unsigned char status[2];
> +	unsigned char static_config_size[2];
> +	unsigned char dynamic_config_size[2];
> +	unsigned char app_config_start_write_block[2];
> +	unsigned char app_config_size[2];
> +	unsigned char max_touch_report_config_size[2];
> +	unsigned char max_touch_report_payload_size[2];
> +	unsigned char customer_config_id[16];
> +	unsigned char max_x[2];
> +	unsigned char max_y[2];
> +	unsigned char max_objects[2];
> +	unsigned char num_of_buttons[2];
> +	unsigned char num_of_image_rows[2];
> +	unsigned char num_of_image_cols[2];
> +	unsigned char has_hybrid_data[2];
> +};
> +
> +struct syna_tcm_identification {
> +	unsigned char version;
> +	unsigned char mode;
> +	unsigned char part_number[16];
> +	unsigned char build_id[4];
> +	unsigned char max_write_size[2];
> +};
> +
> +struct syna_tcm_message_header {
> +	unsigned char marker;
> +	unsigned char code;
> +	unsigned char length[2];
> +};
> +
> +struct touch_data {
> +	unsigned char status;
> +	unsigned int x_pos;
> +	unsigned int y_pos;
> +	unsigned int x_width;
> +	unsigned int y_width;
> +	unsigned int z;
> +};
> +
> +struct syna_tcm_data {
> +	struct i2c_client *client;
> +	struct regulator_bulk_data regulators[2];
> +	struct input_dev *input;
> +	struct gpio_desc *reset_gpio;
> +	struct touchscreen_properties prop;
> +	struct syna_tcm_identification id_info;
> +	struct syna_tcm_app_info app_info;
> +
> +	unsigned char *response_buf;
> +	unsigned int response_length; // Response length including header
> +
> +	unsigned int app_status;
> +
> +	unsigned char *report_config;
> +	unsigned int report_size;
> +
> +	bool initialize_done;
> +
> +	unsigned int touchpanel_max_objects;
> +};
> +
> +DECLARE_COMPLETION(response_complete);
> +
> +static inline unsigned int le2_to_uint(const unsigned char *src)
> +{
> +	return (unsigned int)src[0] + (unsigned int)src[1] * 0x100;
> +}
> +
> +static inline unsigned int ceil_div(unsigned int dividend, unsigned int divisor)
> +{
> +	return (dividend + divisor - 1) / divisor;
> +}
> +
> +static int touch_i2c_continue_read(struct i2c_client *client,
> +				   unsigned char *data, unsigned short length)
> +{
> +	int retval;
> +	unsigned char retry;
> +	struct i2c_msg msg;
> +
> +	msg.addr = client->addr;
> +	msg.flags = I2C_M_RD;
> +	msg.len = length;
> +	msg.buf = data;
> +
> +	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
> +		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
> +			retval = length;
> +			break;
> +		}
> +		msleep(20);
> +	}
> +	if (retry == MAX_I2C_RETRY_TIME) {
> +		pr_warn("%s: I2C read over retry limit\n", __func__);
> +		retval = -EIO;
> +	}
> +	return retval;
> +}
> +
> +static int touch_i2c_continue_write(struct i2c_client *client,
> +				    unsigned char *data, unsigned short length)
> +{
> +	int retval;
> +	unsigned char retry;
> +	struct i2c_msg msg;
> +
> +	msg.addr = client->addr;
> +	msg.flags = 0;
> +	msg.buf = data;
> +	msg.len = length;
> +
> +	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
> +		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
> +			retval = length;
> +			break;
> +		}
> +		msleep(20);
> +	}
> +	if (retry == MAX_I2C_RETRY_TIME) {
> +		pr_warn("%s: I2C write over retry limit\n", __func__);
> +		retval = -EIO;
> +	}
> +	return retval;
> +}
> +
> +static int syna_tcm_continued_read(struct syna_tcm_data *tcm_info,
> +				   unsigned int payload_length,
> +				   unsigned char *message_buffer)
> +{
> +	int retval = 0;
> +	unsigned char marker = 0, code = 0;
> +	unsigned int total_length = 0, remaining_length = 0;
> +	unsigned char *temp_buffer;
> +
> +	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
> +	remaining_length = total_length - READ_LENGTH;
> +
> +	temp_buffer = kmalloc(remaining_length + 2, GFP_KERNEL);
> +	if (!temp_buffer)
> +		return -ENOMEM;
> +
> +	retval = touch_i2c_continue_read(tcm_info->client, temp_buffer,
> +					 remaining_length + 2);
> +	if (retval < 0) {
> +		pr_warn("touch_i2c_continue_read failed");
> +		return retval;
> +	}
> +
> +	marker = temp_buffer[0];
> +	code = temp_buffer[1];
> +
> +	if (marker != MESSAGE_MARKER) {
> +		pr_warn("incorrect header marker (0x%02x)\n", marker);
> +		return -EIO;
> +	}
> +
> +	if (code != STATUS_CONTINUED_READ) {
> +		pr_warn("incorrect header code (0x%02x)\n", code);
> +		return -EIO;
> +	}
> +
> +	memcpy(&message_buffer[READ_LENGTH], &temp_buffer[2], remaining_length);
> +	message_buffer[total_length - 1] = MESSAGE_PADDING;
> +	kfree(temp_buffer);
> +
> +	return 0;
> +}
> +
> +/**
> + * syna_tcm_raw_write() - write command/data to device without receiving
> + * response
> + *
> + * @tcm_info: handle of core module
> + * @command: command to send to device
> + * @data: data to send to device
> + * @length: length of data in bytes
> + * @add_length: wether the length of the message should be added after the command
> + *
> + * A command and its data, if any, are sent to the device.
> + */
> +static int syna_tcm_raw_write(struct syna_tcm_data *tcm_info,
> +			      unsigned char command, unsigned char *data,
> +			      unsigned int length, bool add_length)
> +{
> +	int retval = 0;
> +	unsigned char *out_buf;
> +
> +	unsigned int header_size = add_length && length ? 3 : 1;
> +
> +	out_buf = kmalloc(length + header_size, GFP_KERNEL);
> +	if (retval < 0)
> +		return retval;
> +
> +	out_buf[0] = command;
> +	if (add_length && length) {
> +		out_buf[1] = (unsigned char)length;
> +		out_buf[2] = (unsigned char)(length >> 8);
> +	}
> +
> +	if (length)
> +		memcpy(&out_buf[header_size], data, length);
> +
> +	retval = touch_i2c_continue_write(tcm_info->client, out_buf,
> +					  length + header_size);
> +	if (retval < 0) {
> +		kfree(out_buf);
> +		return retval;
> +	}
> +
> +	kfree(out_buf);
> +	return 0;
> +}
> +
> +/**
> + * syna_tcm_read_message() - read message from device
> + *
> + * @tcm_info: handle of core module
> + * @length: length of data in bytes in raw read mode
> + */
> +static int syna_tcm_read_message(struct syna_tcm_data *tcm_info,
> +				 unsigned int length)
> +{
> +	int retval = 0;
> +	unsigned int total_length = 0, payload_length = 0;
> +	unsigned char *message_buffer;
> +	struct syna_tcm_message_header *header = NULL;
> +
> +	message_buffer = kmalloc(READ_LENGTH, GFP_KERNEL);
> +	if (!message_buffer)
> +		return -ENOMEM;
> +
> +	retval = touch_i2c_continue_read(tcm_info->client, message_buffer,
> +					 READ_LENGTH);
> +	if (retval < 0)
> +		return retval;
> +
> +	header = (struct syna_tcm_message_header *)message_buffer;
> +	if (header->marker != MESSAGE_MARKER) {
> +		pr_warn("wrong header marker:0x%02x\n", header->marker);
> +		return -ENXIO;
> +	}
> +
> +	unsigned char report_code = header->code;
> +
> +	payload_length = le2_to_uint(header->length);
> +
> +	if (report_code <= STATUS_ERROR || report_code == STATUS_INVALID) {
> +		switch (report_code) {
> +		case STATUS_OK:
> +			break;
> +		case STATUS_CONTINUED_READ:
> +		case STATUS_IDLE:
> +		case STATUS_BUSY:
> +			payload_length = 0;
> +			kfree(message_buffer);
> +			return 0;
> +		default:
> +			if (report_code != STATUS_ERROR) {
> +				pr_warn("IO ERROR");
> +				kfree(message_buffer);
> +				return -EIO;
> +			}
> +		}
> +	}
> +
> +	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
> +
> +	unsigned char *temp_buffer = message_buffer;
> +
> +	message_buffer = kmalloc(total_length, GFP_KERNEL);
> +	if (!message_buffer)
> +		return -ENOMEM;
> +
> +	// Copy data already read to message_buffer
> +	memcpy(message_buffer, temp_buffer, READ_LENGTH);
> +	kfree(temp_buffer);
> +
> +	if (payload_length == 0) {
> +		message_buffer[total_length - 1] = MESSAGE_PADDING;
> +		goto check_padding;
> +	}
> +
> +	if (MESSAGE_HEADER_SIZE + payload_length > READ_LENGTH) {
> +		retval = syna_tcm_continued_read(tcm_info, payload_length,
> +						 message_buffer);
> +		if (retval < 0) {
> +			pr_warn("failed to do continued read\n");
> +			return retval;
> +		};
> +	}
> +
> +check_padding:
> +	if (message_buffer[total_length - 1] != MESSAGE_PADDING) {
> +		pr_warn("missing message padding");
> +		return -EIO;
> +	}
> +
> +	if (tcm_info->response_buf != NULL)
> +		kfree(tcm_info->response_buf);
> +
> +	tcm_info->response_buf = message_buffer;
> +	tcm_info->response_length = total_length;
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_write_message(struct syna_tcm_data *tcm_info,
> +				  unsigned char command, unsigned char *payload,
> +				  unsigned int length, unsigned char **resp_buf,
> +				  unsigned int *resp_length)
> +{
> +	int retval = 0;
> +
> +	if (resp_buf == NULL) {
> +		retval = syna_tcm_raw_write(tcm_info, command, payload, length,
> +					    false);
> +		goto exit;
> +	}
> +	reinit_completion(&response_complete);
> +
> +	tcm_info->response_buf = *resp_buf;
> +	tcm_info->response_length = *resp_length;
> +
> +	// Here write command to device
> +	retval = syna_tcm_raw_write(tcm_info, command, payload, length, true);
> +	if (retval != 0)
> +		goto exit;
> +
> +	retval = wait_for_completion_timeout(&response_complete,
> +					     RESPONSE_TIMEOUT_MS);
> +	if (retval == 0) {
> +		pr_warn("timed out waiting for response (command 0x%02x)\n",
> +			command);
> +		retval = -EIO;
> +	}
> +
> +exit:
> +	if (tcm_info->response_length < 0)
> +		return -1;
> +	if (resp_buf != NULL) {
> +		*resp_buf = tcm_info->response_buf;
> +		*resp_length = tcm_info->response_length;
> +	}
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_set_normal_report_config(struct syna_tcm_data *tcm_info)
> +{
> +	int retval;
> +	unsigned int idx = 0;
> +	unsigned int length;
> +
> +	length = le2_to_uint(tcm_info->app_info.max_touch_report_config_size);
> +
> +	if (length < TOUCH_REPORT_CONFIG_SIZE) {
> +		pr_warn("invalid maximum touch report config size: %d\n",
> +			length);
> +		return -EINVAL;
> +	}
> +
> +	unsigned char *report_buf = kmalloc(length, GFP_KERNEL);
> +
> +	if (!report_buf)
> +		return -ENOMEM;
> +
> +	report_buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT;
> +	report_buf[idx++] = TOUCH_OBJECT_N_INDEX;
> +	report_buf[idx++] = 4;
> +	report_buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION;
> +	report_buf[idx++] = 4;
> +	report_buf[idx++] = TOUCH_OBJECT_N_X_POSITION;
> +	report_buf[idx++] = 16;
> +	report_buf[idx++] = TOUCH_OBJECT_N_Y_POSITION;
> +	report_buf[idx++] = 16;
> +	report_buf[idx++] = TOUCH_OBJECT_N_X_WIDTH;
> +	report_buf[idx++] = 12;
> +	report_buf[idx++] = TOUCH_OBJECT_N_Y_WIDTH;
> +	report_buf[idx++] = 12;
> +	report_buf[idx++] = TOUCH_FOREACH_END;
> +	report_buf[idx++] = TOUCH_END;
> +
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_SET_TOUCH_REPORT_CONFIG,
> +					report_buf, length, &resp_buf,
> +					&resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to set report config");
> +		return retval;
> +	}
> +
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_get_report_config(struct syna_tcm_data *tcm_info)
> +{
> +	int retval;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_GET_TOUCH_REPORT_CONFIG,
> +					NULL, 0, &resp_buf, &resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to write command 0x%02x\n",
> +			CMD_GET_TOUCH_REPORT_CONFIG);
> +		return retval;
> +	}
> +
> +	tcm_info->report_size = resp_length - MESSAGE_HEADER_SIZE - 1;
> +
> +	kfree(tcm_info->report_config);
> +	tcm_info->report_config = kmalloc(tcm_info->report_size, GFP_KERNEL);
> +
> +	memcpy(tcm_info->report_config, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       tcm_info->report_size);
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_get_app_info(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +	unsigned int timeout = RESPONSE_TIMEOUT_MS;
> +
> +get_app_info:
> +	retval = syna_tcm_write_message(tcm_info, CMD_GET_APPLICATION_INFO,
> +					NULL, 0, &resp_buf, &resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to write command 0x%02x\n",
> +			CMD_GET_APPLICATION_INFO);
> +		goto exit;
> +	}
> +
> +	memcpy(&tcm_info->app_info, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       sizeof(tcm_info->app_info));
> +
> +	tcm_info->app_status = le2_to_uint(tcm_info->app_info.status);
> +
> +	if (tcm_info->app_status == APP_STATUS_BOOTING ||
> +	    tcm_info->app_status == APP_STATUS_UPDATING) {
> +		if (timeout > 0) {
> +			msleep(APP_STATUS_POLL_MS);
> +			timeout -= APP_STATUS_POLL_MS;
> +			goto get_app_info;
> +		}
> +	}
> +
> +	retval = 0;
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_identify(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_IDENTIFY, NULL, 0,
> +					&resp_buf, &resp_length);
> +	if (retval < 0)
> +		goto exit;
> +
> +	memcpy(&tcm_info->id_info, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       sizeof(tcm_info->id_info));
> +
> +	if (tcm_info->id_info.mode == MODE_APPLICATION)
> +		retval = syna_tcm_get_app_info(tcm_info);
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_run_application_firmware(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned int retry = 3;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +retry:
> +	retval = syna_tcm_write_message(tcm_info, CMD_RUN_APPLICATION_FIRMWARE,
> +					NULL, 0, &resp_buf, &resp_length);
> +
> +	if (retval < 0) {
> +		if (retry--)
> +			goto retry;
> +		goto exit;
> +	}
> +
> +	retval = syna_tcm_identify(tcm_info);
> +	if (retval < 0) {
> +		if (retry--)
> +			goto retry;
> +		goto exit;
> +	}
> +
> +	pr_info("syna_tcm: boot_mode: 0x%02x", tcm_info->id_info.mode);
> +
> +	if (tcm_info->id_info.mode != MODE_APPLICATION) {
> +		if (retry--)
> +			goto retry;
> +		retval = -EINVAL;
> +		goto exit;
> +	} else if (tcm_info->app_status != APP_STATUS_OK) {
> +		pr_warn("syna_tcm: application status = 0x%02x\n",
> +			tcm_info->app_status);
> +		if (retry--)
> +			goto retry;
> +	}
> +
> +	retval = 0;
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +/*
> + * syna_get_report_data - Retrieve data from touch report
> + *
> + * @tcm_info: handle of tcm module
> + * @offset: start bit of retrieved data
> + * @bits: total bits of retrieved data
> + * @data: pointer of data, at most 4 byte
> + * Retrieve data from the touch report based on the bit offset and bit length
> + * information from the touch report configuration.
> + */
> +static int syna_tcm_get_report_data(unsigned char *report_buf,
> +				    unsigned int report_buf_length,
> +				    unsigned int offset, unsigned int num_bits,
> +				    unsigned int *data)
> +{
> +	unsigned char mask = 0;
> +	unsigned char byte_data = 0;
> +	unsigned int output_data = 0;
> +	unsigned int bit_offset = offset % 8;
> +	unsigned int byte_offset = offset / 8;
> +	unsigned int data_bits = 0;
> +	unsigned int available_bits = 0;
> +	unsigned int remaining_bits = num_bits;
> +
> +	if (num_bits == 0 || num_bits > 32) {
> +		pr_warn("report value larger than 32 bits: %d\n", num_bits);
> +		memcpy(data, &report_buf[byte_offset], num_bits / 8);
> +		return 0;
> +	}
> +
> +	if ((offset + num_bits) > report_buf_length * 8) {
> +		pr_warn("report: offset and bits beyond total read length: %d vs %d bits",
> +			offset + num_bits, report_buf_length * 8);
> +		*data = 0;
> +		return -1;
> +	}
> +
> +	while (remaining_bits) {
> +		byte_data = report_buf[byte_offset];
> +		byte_data >>= bit_offset;
> +
> +		available_bits = 8 - bit_offset;
> +		data_bits = available_bits < remaining_bits ? available_bits :
> +							      remaining_bits;
> +		mask = 0xff >> (8 - data_bits);
> +
> +		byte_data &= mask;
> +
> +		output_data |= byte_data << (num_bits - remaining_bits);
> +
> +		bit_offset = 0;
> +		byte_offset += 1;
> +		remaining_bits -= data_bits;
> +	}
> +
> +	*data = output_data;
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_parse_touch_report(struct syna_tcm_data *tcm_info)
> +{
> +	int config_data_pointer = 0, obj = 0, foreach_start = 0, foreach_end,
> +	    retval;
> +	unsigned int offset = 0, num_bits, data = 0;
> +	unsigned char *report_config = tcm_info->report_config;
> +	unsigned int config_size = tcm_info->report_size;
> +
> +	unsigned char *report = &tcm_info->response_buf[MESSAGE_HEADER_SIZE];
> +	unsigned int report_size =
> +		tcm_info->response_length - MESSAGE_HEADER_SIZE - 1;
> +	unsigned char code;
> +
> +	struct touch_data *touch_data =
> +		kmalloc(sizeof(touch_data) * tcm_info->touchpanel_max_objects,
> +			GFP_KERNEL);
> +	memset(touch_data, 0,
> +	       sizeof(touch_data) * tcm_info->touchpanel_max_objects);
> +
> +	while (config_data_pointer < config_size) {
> +		code = report_config[config_data_pointer++];
> +		switch (code) {
> +		case TOUCH_END:
> +			goto exit;
> +		case TOUCH_FOREACH_ACTIVE_OBJECT:
> +			obj = 0;
> +			foreach_start = config_data_pointer;
> +			if (offset >= report_size * 8)
> +				goto exit;
> +			break;
> +		case TOUCH_FOREACH_END:
> +			foreach_end = config_data_pointer;
> +			if (offset < report_size * 8)
> +				config_data_pointer = foreach_start;
> +			break;
> +		// Ignored
> +		case TOUCH_OBJECT_N_TX_POSITION_TIXELS:
> +		case TOUCH_OBJECT_N_RX_POSITION_TIXELS:
> +		case TOUCH_FRAME_RATE:
> +		case TOUCH_0D_BUTTONS_STATE:
> +		case 30: // I don't know what command 30 is and it isn't in the downstream driver. After it comes a length of 8 bits and this is ignored
> +			num_bits = report_config[config_data_pointer++];
> +			offset += num_bits;
> +			break;
> +		case TOUCH_PAD_TO_NEXT_BYTE:
> +			offset = ceil_div(offset, 8) * 8;
> +			break;
> +		case TOUCH_OBJECT_N_INDEX:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &obj);
> +			if (retval < 0) {
> +				pr_warn("failed to get object index\n");
> +				return retval;
> +			}
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_CLASSIFICATION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object classification\n");
> +				return retval;
> +			}
> +			if (obj >= tcm_info->touchpanel_max_objects) {
> +				pr_warn("too many objects\n");
> +				return -1;
> +			}
> +			touch_data[obj].status = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_X_POSITION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object x position\n");
> +				return retval;
> +			}
> +			touch_data[obj].x_pos = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Y_POSITION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object y position\n");
> +				return retval;
> +			}
> +			touch_data[obj].y_pos = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Z:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object z\n");
> +				return retval;
> +			}
> +			touch_data[obj].z = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_X_WIDTH:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object x width\n");
> +				return retval;
> +			}
> +			touch_data[obj].x_width = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Y_WIDTH:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object y width\n");
> +				return retval;
> +			}
> +			touch_data[obj].y_width = data;
> +			offset += num_bits;
> +			break;
> +		default:
> +			pr_notice("unrecognized touch report config code: %d",
> +				  code);
> +		}
> +	}
> +
> +exit:
> +	for (int i = 0; i < tcm_info->touchpanel_max_objects; i++) {
> +		if (touch_data[i].status == NOP)
> +			continue;
> +
> +		bool lift = touch_data[i].status == LIFT ? true : false;
> +
> +		input_mt_slot(tcm_info->input, i);
> +		input_mt_report_slot_state(tcm_info->input, MT_TOOL_FINGER,
> +					   !lift);
> +
> +		int x_width = touch_data[i].x_width;
> +		int y_width = touch_data[i].y_width;
> +		int major_width = x_width > y_width ? x_width : y_width;
> +		int minor_width = x_width > y_width ? y_width : x_width;
> +		int x_is_major = x_width > y_width ? true : false;
> +
> +		if (!lift) {
> +			input_report_abs(tcm_info->input, ABS_MT_POSITION_X,
> +					 touch_data[i].x_pos);
> +			input_report_abs(tcm_info->input, ABS_MT_POSITION_Y,
> +					 touch_data[i].y_pos);
> +			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MAJOR,
> +					 major_width);
> +			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MINOR,
> +					 minor_width);
> +			input_report_abs(tcm_info->input, ABS_MT_ORIENTATION,
> +					 x_is_major);
> +			input_report_abs(tcm_info->input, ABS_MT_PRESSURE,
> +					 touch_data[i].z);
> +		}
> +
> +		input_sync(tcm_info->input);
> +	}
> +
> +	kfree(touch_data);
> +	return 0;
> +}
> +
> +static irqreturn_t syna_irq_handler(int irq, void *dev)
> +{
> +	int retval;
> +	struct syna_tcm_data *tcm_info = dev;
> +
> +	retval = syna_tcm_read_message(tcm_info, 0);
> +	if (retval < 0) {
> +		tcm_info->response_length = -1;
> +		goto exit;
> +	}
> +
> +	if (tcm_info->response_buf[1] == REPORT_TOUCH)
> +		syna_tcm_parse_touch_report(tcm_info);
> +
> +exit:
> +	if (tcm_info->response_buf[1] <= REPORT_IDENTIFY)
> +		complete(&response_complete);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int syna_tcm_power_on(struct syna_tcm_data *tcm_info)
> +{
> +	int ret;
> +
> +	ret = regulator_bulk_enable(ARRAY_SIZE(tcm_info->regulators),
> +				    tcm_info->regulators);
> +	if (ret)
> +		return ret;
> +
> +	gpiod_set_value(tcm_info->reset_gpio, false);
> +	msleep(POWERUP_TO_RESET_TIME);
> +	gpiod_set_value(tcm_info->reset_gpio, true);
> +	msleep(RESET_TO_NORMAL_TIME);
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_probe(struct i2c_client *client)
> +{
> +	struct syna_tcm_data *tcm_info;
> +	int err;
> +
> +	pr_info("starting probe for syna_tcm_oncell touchscreen");
> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA |
> +					     I2C_FUNC_SMBUS_I2C_BLOCK))
> +		return -ENODEV;
> +
> +	tcm_info = devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL);
> +	if (!tcm_info)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, tcm_info);
> +	tcm_info->client = client;
> +	tcm_info->response_buf = NULL;
> +
> +	of_property_read_u32(client->dev.of_node, "max-objects",
> +			     &tcm_info->touchpanel_max_objects);
> +
> +	tcm_info->reset_gpio =
> +		gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH);
> +
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply = "vdd";
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply = "vcc";
> +	err = devm_regulator_bulk_get(&client->dev,
> +				      ARRAY_SIZE(tcm_info->regulators),
> +				      tcm_info->regulators);
> +	if (err)
> +		return err;
> +
> +	// TODO: uncomment once syna_tcm_power_off is implemented
> +	// err = devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_off, tcm_info);
> +	// if (err)
> +	//     return err;
> +
> +	err = syna_tcm_power_on(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// This needs to happen before the first write to the device
> +	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> +					syna_irq_handler,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					"syna_tcm_oncell_irq", tcm_info);
> +	if (err)
> +		return err;
> +
> +	err = syna_tcm_run_application_firmware(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// err = syna_tcm_set_normal_report_config(tcm_info);
> +	// if (err < 0)
> +		// pr_err("syna_tcm: failed to set normal touch report config")
> +
> +	err = syna_tcm_get_report_config(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	tcm_info->input = devm_input_allocate_device(&client->dev);
> +	if (!tcm_info->input)
> +		return -ENOMEM;
> +
> +	tcm_info->input->name = TOUCHPANEL_DEVICE;
> +	tcm_info->input->id.bustype = BUS_I2C;
> +
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_X, 0,
> +			     le2_to_uint(tcm_info->app_info.max_x), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_Y, 0,
> +			     le2_to_uint(tcm_info->app_info.max_y), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
> +
> +	touchscreen_parse_properties(tcm_info->input, true, &tcm_info->prop);
> +
> +	err = input_mt_init_slots(tcm_info->input,
> +				  tcm_info->touchpanel_max_objects,
> +				  INPUT_MT_DIRECT);
> +	if (err)
> +		return err;
> +
> +	input_set_drvdata(tcm_info->input, tcm_info);
> +
> +	err = input_register_device(tcm_info->input);
> +	if (err)
> +		return err;
> +
> +	pr_info("syna_tcm: probe done");
> +	tcm_info->initialize_done = true;
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id syna_driver_ids[] = {
> +	{
> +		.compatible = "syna,s3908",
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, syna_driver_ids);
> +
> +static const struct i2c_device_id syna_i2c_ids[] = { { TOUCHPANEL_DEVICE, 0 },
> +						     {} };
> +MODULE_DEVICE_TABLE(i2c, syna_i2c_ids);
> +
> +// static const struct dev_pm_ops syna_pm_ops = {
> +//     .suspend = syna_i2c_suspend,
> +//     .resume = syna_i2c_resume,
> +// };
> +
> +static struct i2c_driver syna_i2c_driver = {
> +	.probe	= syna_tcm_probe,
> +	// .remove	   = syna_i2c_remove,
> +	// .shutdown   = syna_tp_shutdown,
> +	.id_table	= syna_i2c_ids,
> +	.driver	= {
> +	.name	= TOUCHPANEL_DEVICE,
> +	.of_match_table = syna_driver_ids,
> +		// .pm = &syna_pm_ops,
> +	},
> +};
> +
> +module_i2c_driver(syna_i2c_driver);

^ permalink raw reply

* [PATCH RFC 0/7] regulator: new APIs for voltage reference supplies
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input

In the IIO subsystem, we noticed a pattern in many drivers where we need
to get, enable and get the voltage of a supply that provides a reference
voltage. In these cases, we only need the voltage and not a handle to
the regulator. Another common pattern is for chips to have an internal
reference voltage that is used when an external reference is not
available. There are also a few drivers outside of IIO that do the same.

So we would like to propose a couple of new regulator consumer APIs to
handle these specific cases to avoid repeating the same boilerplate code
in multiple drivers.

As an example of how these functions are used, I have included a few
patches to consumer drivers. But to avoid a giant patch bomb, I have
omitted the iio/adc and iio/dac patches I have prepared from this
series. I will send those separately but these will add 12 more users
of devm_regulator_get_enable_get_voltage() and 24 more users of
devm_regulator_get_optional_enable_get_voltage(). In total, this will
eliminate nearly 1000 lines of similar code.

---
David Lechner (7):
      regulator: devres: add APIs for reference supplies
      hwmon: (adc128d818) Use devm_regulator_get_optional_enable_get_voltage()
      hwmon: (da9052) Use devm_regulator_get_enable_get_voltage()
      iio: addac: ad74115: Use devm_regulator_get_enable_get_voltage()
      iio: frequency: admv1013: Use devm_regulator_get_enable_get_voltage()
      staging: iio: impedance-analyzer: admv1013: Use devm_regulator_get_enable_get_voltage()
      Input: mpr121: Use devm_regulator_get_enable_get_voltage()

 Documentation/driver-api/driver-model/devres.rst |  2 +
 drivers/hwmon/adc128d818.c                       | 55 +++++-----------
 drivers/hwmon/da9052-hwmon.c                     | 33 ++--------
 drivers/iio/addac/ad74115.c                      | 28 +-------
 drivers/iio/frequency/admv1013.c                 | 37 +++--------
 drivers/input/keyboard/mpr121_touchkey.c         | 45 +------------
 drivers/regulator/devres.c                       | 83 ++++++++++++++++++++++++
 drivers/staging/iio/impedance-analyzer/ad5933.c  | 24 +------
 include/linux/regulator/consumer.h               | 14 ++++
 9 files changed, 138 insertions(+), 183 deletions(-)
---
base-commit: c5b2db5859957150ac6ed305ab41a4a92ca40cfb
change-id: 20240326-regulator-get-enable-get-votlage-5dedf40ff338

^ permalink raw reply

* [PATCH RFC 1/7] regulator: devres: add APIs for reference supplies
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

A common use case for regulators is to supply a reference voltage to an
analog input or output device. This adds two new devres APIs to get,
enable, and get the voltage in a single call. This allows eliminating
boilerplate code in drivers that use reference supplies in this way.

devm_regulator_get_enable_get_voltage() is intended for cases where the
supply is required to provide an external reference voltage.

devm_regulator_get_optional_enable_get_voltage() is intended for cases
where the supply is optional and device typically uses an internal
reference voltage if the supply is not available.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 Documentation/driver-api/driver-model/devres.rst |  2 +
 drivers/regulator/devres.c                       | 83 ++++++++++++++++++++++++
 include/linux/regulator/consumer.h               | 14 ++++
 3 files changed, 99 insertions(+)

diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst
index 7be8b8dd5f00..fd954d09e13c 100644
--- a/Documentation/driver-api/driver-model/devres.rst
+++ b/Documentation/driver-api/driver-model/devres.rst
@@ -433,9 +433,11 @@ REGULATOR
   devm_regulator_bulk_put()
   devm_regulator_get()
   devm_regulator_get_enable()
+  devm_regulator_get_enable_get_voltage()
   devm_regulator_get_enable_optional()
   devm_regulator_get_exclusive()
   devm_regulator_get_optional()
+  devm_regulator_get_optional_enable_get_voltage()
   devm_regulator_irq_helper()
   devm_regulator_put()
   devm_regulator_register()
diff --git a/drivers/regulator/devres.c b/drivers/regulator/devres.c
index 90bb0d178885..e240926defc5 100644
--- a/drivers/regulator/devres.c
+++ b/drivers/regulator/devres.c
@@ -145,6 +145,89 @@ struct regulator *devm_regulator_get_optional(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(devm_regulator_get_optional);
 
+static int _devm_regulator_get_enable_get_voltage(struct device *dev,
+						  const char *id,
+						  bool silent_enodev)
+{
+	struct regulator *r;
+	int ret;
+
+	/*
+	 * Since we need a real voltage, we use devm_regulator_get_optional()
+	 * here to avoid getting a dummy regulator if the supply is not present.
+	 */
+	r = devm_regulator_get_optional(dev, id);
+	if (silent_enodev && PTR_ERR(r) == -ENODEV)
+		return -ENODEV;
+	if (IS_ERR(r))
+		return dev_err_probe(dev, PTR_ERR(r),
+				     "failed to get regulator '%s'\n", id);
+
+	ret = regulator_enable(r);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to enable regulator '%s'\n", id);
+
+	ret = devm_add_action_or_reset(dev, regulator_action_disable, r);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to add disable action for regulator '%s'\n",
+				     id);
+
+	ret = regulator_get_voltage(r);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "failed to get voltage for regulator '%s'\n",
+				     id);
+
+	return ret;
+}
+
+/**
+ * devm_regulator_get_enable_get_voltage - Resource managed regulator get and
+ *                                         enable that returns the voltage
+ * @dev: device to supply
+ * @id:  supply name or regulator ID.
+ *
+ * Get and enable regulator for duration of the device life-time.
+ * regulator_disable() and regulator_put() are automatically called on driver
+ * detach. See regulator_get_optional(), regulator_enable(), and
+ * regulator_get_voltage() for more information.
+ *
+ * This is a convenience function for supplies that provide a reference voltage
+ * where the consumer driver just needs to know the voltage and keep the
+ * regulator enabled. Also, as a convenience, this prints error messages on
+ * failure.
+ *
+ * Returns: voltage in microvolts on success, or an error code on failure.
+ */
+int devm_regulator_get_enable_get_voltage(struct device *dev, const char *id)
+{
+	return _devm_regulator_get_enable_get_voltage(dev, id, false);
+}
+EXPORT_SYMBOL_GPL(devm_regulator_get_enable_get_voltage);
+
+/**
+ * devm_regulator_get_optional_enable_get_voltage - Resource managed regulator
+ *                                                  get and enable that returns
+ *                                                  the voltage
+ * @dev: device to supply
+ * @id:  supply name or regulator ID.
+ *
+ * This function is identical to devm_regulator_get_enable_get_voltage() except
+ * that it does not print an error message in the case of -ENODEV. Callers are
+ * expected to handle -ENODEV as a special case instead of passing it on as an
+ * error.
+ *
+ * Returns: voltage in microvolts on success, or an error code on failure.
+ */
+int devm_regulator_get_optional_enable_get_voltage(struct device *dev,
+						   const char *id)
+{
+	return _devm_regulator_get_enable_get_voltage(dev, id, true);
+}
+EXPORT_SYMBOL_GPL(devm_regulator_get_optional_enable_get_voltage);
+
 static int devm_regulator_match(struct device *dev, void *res, void *data)
 {
 	struct regulator **r = res;
diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h
index 4660582a3302..35596db521a0 100644
--- a/include/linux/regulator/consumer.h
+++ b/include/linux/regulator/consumer.h
@@ -164,6 +164,8 @@ struct regulator *__must_check devm_regulator_get_optional(struct device *dev,
 							   const char *id);
 int devm_regulator_get_enable(struct device *dev, const char *id);
 int devm_regulator_get_enable_optional(struct device *dev, const char *id);
+int devm_regulator_get_enable_get_voltage(struct device *dev, const char *id);
+int devm_regulator_get_optional_enable_get_voltage(struct device *dev, const char *id);
 void regulator_put(struct regulator *regulator);
 void devm_regulator_put(struct regulator *regulator);
 
@@ -329,6 +331,18 @@ static inline int devm_regulator_get_enable_optional(struct device *dev,
 	return -ENODEV;
 }
 
+static inline int devm_regulator_get_enable_get_voltage(struct device *dev,
+							const char *id)
+{
+	return -ENODEV;
+}
+
+static inline int devm_regulator_get_optional_enable_get_voltage(struct device *dev,
+								 const char *id)
+{
+	return -ENODEV;
+}
+
 static inline struct regulator *__must_check
 regulator_get_optional(struct device *dev, const char *id)
 {

-- 
2.43.2


^ permalink raw reply related

* [PATCH RFC 2/7] hwmon: (adc128d818) Use devm_regulator_get_optional_enable_get_voltage()
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

We can reduce boilerplate code and eliminate the driver remove()
function by using devm_regulator_get_optional_enable_get_voltage().

A new external_vref flag is added since we no longer have the handle
to the regulator to check if it is present.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/hwmon/adc128d818.c | 55 ++++++++++++++--------------------------------
 1 file changed, 17 insertions(+), 38 deletions(-)

diff --git a/drivers/hwmon/adc128d818.c b/drivers/hwmon/adc128d818.c
index 46e3c8c50765..119e2841720f 100644
--- a/drivers/hwmon/adc128d818.c
+++ b/drivers/hwmon/adc128d818.c
@@ -58,7 +58,6 @@ static const u8 num_inputs[] = { 7, 8, 4, 6 };
 
 struct adc128_data {
 	struct i2c_client *client;
-	struct regulator *regulator;
 	int vref;		/* Reference voltage in mV */
 	struct mutex update_lock;
 	u8 mode;		/* Operation mode */
@@ -389,7 +388,7 @@ static int adc128_detect(struct i2c_client *client, struct i2c_board_info *info)
 	return 0;
 }
 
-static int adc128_init_client(struct adc128_data *data)
+static int adc128_init_client(struct adc128_data *data, bool external_vref)
 {
 	struct i2c_client *client = data->client;
 	int err;
@@ -408,7 +407,7 @@ static int adc128_init_client(struct adc128_data *data)
 		regval |= data->mode << 1;
 
 	/* If external vref is selected, configure the chip to use it */
-	if (data->regulator)
+	if (external_vref)
 		regval |= 0x01;
 
 	/* Write advanced configuration register */
@@ -430,30 +429,25 @@ static int adc128_init_client(struct adc128_data *data)
 static int adc128_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
-	struct regulator *regulator;
 	struct device *hwmon_dev;
 	struct adc128_data *data;
-	int err, vref;
+	bool external_vref;
+	int err;
 
 	data = devm_kzalloc(dev, sizeof(struct adc128_data), GFP_KERNEL);
 	if (!data)
 		return -ENOMEM;
 
 	/* vref is optional. If specified, is used as chip reference voltage */
-	regulator = devm_regulator_get_optional(dev, "vref");
-	if (!IS_ERR(regulator)) {
-		data->regulator = regulator;
-		err = regulator_enable(regulator);
-		if (err < 0)
-			return err;
-		vref = regulator_get_voltage(regulator);
-		if (vref < 0) {
-			err = vref;
-			goto error;
-		}
-		data->vref = DIV_ROUND_CLOSEST(vref, 1000);
-	} else {
+	err = devm_regulator_get_optional_enable_get_voltage(dev, "vref");
+	if (err == -ENODEV) {
+		external_vref = false;
 		data->vref = 2560;	/* 2.56V, in mV */
+	} else if (err < 0) {
+		return err;
+	} else {
+		external_vref = true;
+		data->vref = DIV_ROUND_CLOSEST(err, 1000);
 	}
 
 	/* Operation mode is optional. If unspecified, keep current mode */
@@ -461,13 +455,12 @@ static int adc128_probe(struct i2c_client *client)
 		if (data->mode > 3) {
 			dev_err(dev, "invalid operation mode %d\n",
 				data->mode);
-			err = -EINVAL;
-			goto error;
+			return -EINVAL;
 		}
 	} else {
 		err = i2c_smbus_read_byte_data(client, ADC128_REG_CONFIG_ADV);
 		if (err < 0)
-			goto error;
+			return err;
 		data->mode = (err >> 1) & ADC128_REG_MASK;
 	}
 
@@ -476,31 +469,18 @@ static int adc128_probe(struct i2c_client *client)
 	mutex_init(&data->update_lock);
 
 	/* Initialize the chip */
-	err = adc128_init_client(data);
+	err = adc128_init_client(data, external_vref);
 	if (err < 0)
-		goto error;
+		return err;
 
 	hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
 							   data, adc128_groups);
 	if (IS_ERR(hwmon_dev)) {
 		err = PTR_ERR(hwmon_dev);
-		goto error;
+		return err;
 	}
 
 	return 0;
-
-error:
-	if (data->regulator)
-		regulator_disable(data->regulator);
-	return err;
-}
-
-static void adc128_remove(struct i2c_client *client)
-{
-	struct adc128_data *data = i2c_get_clientdata(client);
-
-	if (data->regulator)
-		regulator_disable(data->regulator);
 }
 
 static const struct i2c_device_id adc128_id[] = {
@@ -522,7 +502,6 @@ static struct i2c_driver adc128_driver = {
 		.of_match_table = of_match_ptr(adc128_of_match),
 	},
 	.probe		= adc128_probe,
-	.remove		= adc128_remove,
 	.id_table	= adc128_id,
 	.detect		= adc128_detect,
 	.address_list	= normal_i2c,

-- 
2.43.2


^ permalink raw reply related

* [PATCH RFC 3/7] hwmon: (da9052) Use devm_regulator_get_enable_get_voltage()
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

We can reduce boilerplate code by using
devm_regulator_get_enable_get_voltage().

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/hwmon/da9052-hwmon.c | 33 +++++++--------------------------
 1 file changed, 7 insertions(+), 26 deletions(-)

diff --git a/drivers/hwmon/da9052-hwmon.c b/drivers/hwmon/da9052-hwmon.c
index 2bd7ae8100d7..70e7bc72e980 100644
--- a/drivers/hwmon/da9052-hwmon.c
+++ b/drivers/hwmon/da9052-hwmon.c
@@ -26,7 +26,6 @@ struct da9052_hwmon {
 	struct mutex		hwmon_lock;
 	bool			tsi_as_adc;
 	int			tsiref_mv;
-	struct regulator	*tsiref;
 	struct completion	tsidone;
 };
 
@@ -414,32 +413,19 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
 		device_property_read_bool(pdev->dev.parent, "dlg,tsi-as-adc");
 
 	if (hwmon->tsi_as_adc) {
-		hwmon->tsiref = devm_regulator_get(pdev->dev.parent, "tsiref");
-		if (IS_ERR(hwmon->tsiref)) {
-			err = PTR_ERR(hwmon->tsiref);
-			dev_err(&pdev->dev, "failed to get tsiref: %d", err);
+		err = devm_regulator_get_enable_get_voltage(pdev->dev.parent,
+							    "tsiref");
+		if (err < 0)
 			return err;
-		}
-
-		err = regulator_enable(hwmon->tsiref);
-		if (err)
-			return err;
-
-		hwmon->tsiref_mv = regulator_get_voltage(hwmon->tsiref);
-		if (hwmon->tsiref_mv < 0) {
-			err = hwmon->tsiref_mv;
-			goto exit_regulator;
-		}
 
 		/* convert from microvolt (DT) to millivolt (hwmon) */
-		hwmon->tsiref_mv /= 1000;
+		hwmon->tsiref_mv = err / 1000;
 
 		/* TSIREF limits from datasheet */
 		if (hwmon->tsiref_mv < 1800 || hwmon->tsiref_mv > 2600) {
 			dev_err(hwmon->da9052->dev, "invalid TSIREF voltage: %d",
 				hwmon->tsiref_mv);
-			err = -ENXIO;
-			goto exit_regulator;
+			return -ENXIO;
 		}
 
 		/* disable touchscreen features */
@@ -456,7 +442,7 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
 		if (err) {
 			dev_err(&pdev->dev, "Failed to register TSIRDY IRQ: %d",
 				err);
-			goto exit_regulator;
+			return err;
 		}
 	}
 
@@ -472,9 +458,6 @@ static int da9052_hwmon_probe(struct platform_device *pdev)
 exit_irq:
 	if (hwmon->tsi_as_adc)
 		da9052_free_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, hwmon);
-exit_regulator:
-	if (hwmon->tsiref)
-		regulator_disable(hwmon->tsiref);
 
 	return err;
 }
@@ -483,10 +466,8 @@ static void da9052_hwmon_remove(struct platform_device *pdev)
 {
 	struct da9052_hwmon *hwmon = platform_get_drvdata(pdev);
 
-	if (hwmon->tsi_as_adc) {
+	if (hwmon->tsi_as_adc)
 		da9052_free_irq(hwmon->da9052, DA9052_IRQ_TSIREADY, hwmon);
-		regulator_disable(hwmon->tsiref);
-	}
 }
 
 static struct platform_driver da9052_hwmon_driver = {

-- 
2.43.2


^ permalink raw reply related

* [PATCH RFC 4/7] iio: addac: ad74115: Use devm_regulator_get_enable_get_voltage()
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

We can reduce boilerplate code by using
devm_regulator_get_enable_get_voltage().

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/iio/addac/ad74115.c | 28 +++-------------------------
 1 file changed, 3 insertions(+), 25 deletions(-)

diff --git a/drivers/iio/addac/ad74115.c b/drivers/iio/addac/ad74115.c
index e6bc5eb3788d..01073d7de6aa 100644
--- a/drivers/iio/addac/ad74115.c
+++ b/drivers/iio/addac/ad74115.c
@@ -199,7 +199,6 @@ struct ad74115_state {
 	struct spi_device		*spi;
 	struct regmap			*regmap;
 	struct iio_trigger		*trig;
-	struct regulator		*avdd;
 
 	/*
 	 * Synchronize consecutive operations when doing a one-shot
@@ -1672,14 +1671,6 @@ static int ad74115_setup(struct iio_dev *indio_dev)
 	if (ret)
 		return ret;
 
-	if (val == AD74115_DIN_THRESHOLD_MODE_AVDD) {
-		ret = regulator_get_voltage(st->avdd);
-		if (ret < 0)
-			return ret;
-
-		st->avdd_mv = ret / 1000;
-	}
-
 	st->din_threshold_mode = val;
 
 	ret = ad74115_apply_fw_prop(st, &ad74115_dac_bipolar_fw_prop, &val);
@@ -1788,11 +1779,6 @@ static int ad74115_reset(struct ad74115_state *st)
 	return 0;
 }
 
-static void ad74115_regulator_disable(void *data)
-{
-	regulator_disable(data);
-}
-
 static int ad74115_setup_trigger(struct iio_dev *indio_dev)
 {
 	struct ad74115_state *st = iio_priv(indio_dev);
@@ -1855,19 +1841,11 @@ static int ad74115_probe(struct spi_device *spi)
 	indio_dev->modes = INDIO_DIRECT_MODE;
 	indio_dev->info = &ad74115_info;
 
-	st->avdd = devm_regulator_get(dev, "avdd");
-	if (IS_ERR(st->avdd))
-		return PTR_ERR(st->avdd);
-
-	ret = regulator_enable(st->avdd);
-	if (ret) {
-		dev_err(dev, "Failed to enable avdd regulator\n");
+	ret = devm_regulator_get_enable_get_voltage(dev, "avdd");
+	if (ret < 0)
 		return ret;
-	}
 
-	ret = devm_add_action_or_reset(dev, ad74115_regulator_disable, st->avdd);
-	if (ret)
-		return ret;
+	st->avdd_mv = ret / 1000;
 
 	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
 					     regulator_names);

-- 
2.43.2


^ permalink raw reply related

* [PATCH RFC 5/7] iio: frequency: admv1013: Use devm_regulator_get_enable_get_voltage()
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

We can reduce boilerplate code by using
devm_regulator_get_enable_get_voltage().

The common mode voltage is now passed as a parameter in the init
functions so we can avoid adding a state member that is only used
during init.

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/iio/frequency/admv1013.c | 37 ++++++++-----------------------------
 1 file changed, 8 insertions(+), 29 deletions(-)

diff --git a/drivers/iio/frequency/admv1013.c b/drivers/iio/frequency/admv1013.c
index 92923074f930..b0aa3cc27ea9 100644
--- a/drivers/iio/frequency/admv1013.c
+++ b/drivers/iio/frequency/admv1013.c
@@ -95,7 +95,6 @@ struct admv1013_state {
 	struct clk		*clkin;
 	/* Protect against concurrent accesses to the device and to data */
 	struct mutex		lock;
-	struct regulator	*reg;
 	struct notifier_block	nb;
 	unsigned int		input_mode;
 	unsigned int		quad_se_mode;
@@ -342,14 +341,9 @@ static int admv1013_update_quad_filters(struct admv1013_state *st)
 					FIELD_PREP(ADMV1013_QUAD_FILTERS_MSK, filt_raw));
 }
 
-static int admv1013_update_mixer_vgate(struct admv1013_state *st)
+static int admv1013_update_mixer_vgate(struct admv1013_state *st, int vcm)
 {
 	unsigned int mixer_vgate;
-	int vcm;
-
-	vcm = regulator_get_voltage(st->reg);
-	if (vcm < 0)
-		return vcm;
 
 	if (vcm <= 1800000)
 		mixer_vgate = (2389 * vcm / 1000000 + 8100) / 100;
@@ -443,7 +437,7 @@ static const struct iio_chan_spec admv1013_channels[] = {
 	ADMV1013_CHAN_CALIB(1, Q),
 };
 
-static int admv1013_init(struct admv1013_state *st)
+static int admv1013_init(struct admv1013_state *st, int vcm_uv)
 {
 	int ret;
 	unsigned int data;
@@ -483,7 +477,7 @@ static int admv1013_init(struct admv1013_state *st)
 	if (ret)
 		return ret;
 
-	ret = admv1013_update_mixer_vgate(st);
+	ret = admv1013_update_mixer_vgate(st, vcm_uv);
 	if (ret)
 		return ret;
 
@@ -498,11 +492,6 @@ static int admv1013_init(struct admv1013_state *st)
 					  st->input_mode);
 }
 
-static void admv1013_reg_disable(void *data)
-{
-	regulator_disable(data);
-}
-
 static void admv1013_powerdown(void *data)
 {
 	unsigned int enable_reg, enable_reg_msk;
@@ -557,11 +546,6 @@ static int admv1013_properties_parse(struct admv1013_state *st)
 	else
 		return -EINVAL;
 
-	st->reg = devm_regulator_get(&spi->dev, "vcm");
-	if (IS_ERR(st->reg))
-		return dev_err_probe(&spi->dev, PTR_ERR(st->reg),
-				     "failed to get the common-mode voltage\n");
-
 	ret = devm_regulator_bulk_get_enable(&st->spi->dev,
 					     ARRAY_SIZE(admv1013_vcc_regs),
 					     admv1013_vcc_regs);
@@ -578,7 +562,7 @@ static int admv1013_probe(struct spi_device *spi)
 {
 	struct iio_dev *indio_dev;
 	struct admv1013_state *st;
-	int ret;
+	int ret, vcm_uv;
 
 	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
 	if (!indio_dev)
@@ -597,16 +581,11 @@ static int admv1013_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
-	ret = regulator_enable(st->reg);
-	if (ret) {
-		dev_err(&spi->dev, "Failed to enable specified Common-Mode Voltage!\n");
+	ret = devm_regulator_get_enable_get_voltage(&spi->dev, "vcm");
+	if (ret < 0)
 		return ret;
-	}
 
-	ret = devm_add_action_or_reset(&spi->dev, admv1013_reg_disable,
-				       st->reg);
-	if (ret)
-		return ret;
+	vcm_uv = ret;
 
 	st->clkin = devm_clk_get_enabled(&spi->dev, "lo_in");
 	if (IS_ERR(st->clkin))
@@ -620,7 +599,7 @@ static int admv1013_probe(struct spi_device *spi)
 
 	mutex_init(&st->lock);
 
-	ret = admv1013_init(st);
+	ret = admv1013_init(st, vcm_uv);
 	if (ret) {
 		dev_err(&spi->dev, "admv1013 init failed\n");
 		return ret;

-- 
2.43.2


^ permalink raw reply related

* [PATCH RFC 6/7] staging: iio: impedance-analyzer: admv1013: Use devm_regulator_get_enable_get_voltage()
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

We can reduce boilerplate code by using
devm_regulator_get_enable_get_voltage().

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/staging/iio/impedance-analyzer/ad5933.c | 24 +-----------------------
 1 file changed, 1 insertion(+), 23 deletions(-)

diff --git a/drivers/staging/iio/impedance-analyzer/ad5933.c b/drivers/staging/iio/impedance-analyzer/ad5933.c
index 9149d41fe65b..e4942833b793 100644
--- a/drivers/staging/iio/impedance-analyzer/ad5933.c
+++ b/drivers/staging/iio/impedance-analyzer/ad5933.c
@@ -84,7 +84,6 @@
 
 struct ad5933_state {
 	struct i2c_client		*client;
-	struct regulator		*reg;
 	struct clk			*mclk;
 	struct delayed_work		work;
 	struct mutex			lock; /* Protect sensor state */
@@ -660,13 +659,6 @@ static void ad5933_work(struct work_struct *work)
 	}
 }
 
-static void ad5933_reg_disable(void *data)
-{
-	struct ad5933_state *st = data;
-
-	regulator_disable(st->reg);
-}
-
 static int ad5933_probe(struct i2c_client *client)
 {
 	const struct i2c_device_id *id = i2c_client_get_device_id(client);
@@ -685,21 +677,7 @@ static int ad5933_probe(struct i2c_client *client)
 
 	mutex_init(&st->lock);
 
-	st->reg = devm_regulator_get(&client->dev, "vdd");
-	if (IS_ERR(st->reg))
-		return PTR_ERR(st->reg);
-
-	ret = regulator_enable(st->reg);
-	if (ret) {
-		dev_err(&client->dev, "Failed to enable specified VDD supply\n");
-		return ret;
-	}
-
-	ret = devm_add_action_or_reset(&client->dev, ad5933_reg_disable, st);
-	if (ret)
-		return ret;
-
-	ret = regulator_get_voltage(st->reg);
+	ret = devm_regulator_get_enable_get_voltage(&client->dev, "vdd");
 	if (ret < 0)
 		return ret;
 

-- 
2.43.2


^ permalink raw reply related

* [PATCH RFC 7/7] Input: mpr121: Use devm_regulator_get_enable_get_voltage()
From: David Lechner @ 2024-03-27 23:18 UTC (permalink / raw)
  To: Jonathan Corbet, Liam Girdwood, Mark Brown, Jean Delvare,
	Guenter Roeck, Support Opensource, Cosmin Tanislav,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	Antoniu Miclaus, Greg Kroah-Hartman, Dmitry Torokhov
  Cc: David Lechner, linux-doc, linux-kernel, linux-hwmon, linux-iio,
	linux-staging, linux-input
In-Reply-To: <20240327-regulator-get-enable-get-votlage-v1-0-5f4517faa059@baylibre.com>

We can reduce boilerplate code by using
devm_regulator_get_enable_get_voltage().

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
 drivers/input/keyboard/mpr121_touchkey.c | 45 +++-----------------------------
 1 file changed, 3 insertions(+), 42 deletions(-)

diff --git a/drivers/input/keyboard/mpr121_touchkey.c b/drivers/input/keyboard/mpr121_touchkey.c
index d434753afab1..c59e7451f3cd 100644
--- a/drivers/input/keyboard/mpr121_touchkey.c
+++ b/drivers/input/keyboard/mpr121_touchkey.c
@@ -82,42 +82,6 @@ static const struct mpr121_init_register init_reg_table[] = {
 	{ AUTO_CONFIG_CTRL_ADDR, 0x0b },
 };
 
-static void mpr121_vdd_supply_disable(void *data)
-{
-	struct regulator *vdd_supply = data;
-
-	regulator_disable(vdd_supply);
-}
-
-static struct regulator *mpr121_vdd_supply_init(struct device *dev)
-{
-	struct regulator *vdd_supply;
-	int err;
-
-	vdd_supply = devm_regulator_get(dev, "vdd");
-	if (IS_ERR(vdd_supply)) {
-		dev_err(dev, "failed to get vdd regulator: %ld\n",
-			PTR_ERR(vdd_supply));
-		return vdd_supply;
-	}
-
-	err = regulator_enable(vdd_supply);
-	if (err) {
-		dev_err(dev, "failed to enable vdd regulator: %d\n", err);
-		return ERR_PTR(err);
-	}
-
-	err = devm_add_action_or_reset(dev, mpr121_vdd_supply_disable,
-				       vdd_supply);
-	if (err) {
-		dev_err(dev, "failed to add disable regulator action: %d\n",
-			err);
-		return ERR_PTR(err);
-	}
-
-	return vdd_supply;
-}
-
 static void mpr_touchkey_report(struct input_dev *dev)
 {
 	struct mpr121_touchkey *mpr121 = input_get_drvdata(dev);
@@ -233,7 +197,6 @@ static int mpr121_phys_init(struct mpr121_touchkey *mpr121,
 static int mpr_touchkey_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
-	struct regulator *vdd_supply;
 	int vdd_uv;
 	struct mpr121_touchkey *mpr121;
 	struct input_dev *input_dev;
@@ -241,11 +204,9 @@ static int mpr_touchkey_probe(struct i2c_client *client)
 	int error;
 	int i;
 
-	vdd_supply = mpr121_vdd_supply_init(dev);
-	if (IS_ERR(vdd_supply))
-		return PTR_ERR(vdd_supply);
-
-	vdd_uv = regulator_get_voltage(vdd_supply);
+	vdd_uv = devm_regulator_get_enable_get_voltage(dev, "vdd");
+	if (vdd_uv < 0)
+		return vdd_uv;
 
 	mpr121 = devm_kzalloc(dev, sizeof(*mpr121), GFP_KERNEL);
 	if (!mpr121)

-- 
2.43.2


^ permalink raw reply related

* [PATCH 0/1] Input: add gamecube adapter support
From: Milas Robin @ 2024-03-28  2:06 UTC (permalink / raw)
  To: dmitry.torokhov; +Cc: linux-input, Milas Robin

Hi everyone,

This patch add support for the Wii U / Nintendo Switch GameCube adapter
This is also my first contribution to the linux kernel

I tried as my best to make it based on github.com/ToadKing/wii-u-gc-adapter and
Dolphin Emulator project as well as the xpad driver.

There still an issue with it as the device report itself as an usb-hid device
the usb-hid generic driver take the priority rather than this module if usb-hid
is compiled directly into the kernel and this module as an external module.
How can I fix this problem ? The module function properly if bind manually or
if the usb-hid module is not enabled.

Also I'm not really good with parallel programming I tried my best to make sure
than there is no race condition but would be grateful if some can check too.

Thank you for your time reading my contribution
Robin

Milas Robin (1):
  Input: add gamecube adapter support

 drivers/input/joystick/Kconfig            |  20 +
 drivers/input/joystick/Makefile           |   1 +
 drivers/input/joystick/gamecube-adapter.c | 607 ++++++++++++++++++++++
 3 files changed, 628 insertions(+)
 create mode 100644 drivers/input/joystick/gamecube-adapter.c

-- 
2.44.0


^ permalink raw reply

* [PATCH 1/1] Input: add gamecube adapter support
From: Milas Robin @ 2024-03-28  2:06 UTC (permalink / raw)
  To: dmitry.torokhov; +Cc: linux-input, Milas Robin
In-Reply-To: <20240328020651.358662-1-milas.robin@live.fr>

Add support for the Wii U / Nintendo Switch gamecube controller adapter

Signed-off-by: Milas Robin <milas.robin@live.fr>
---
 drivers/input/joystick/Kconfig            |  20 +
 drivers/input/joystick/Makefile           |   1 +
 drivers/input/joystick/gamecube-adapter.c | 607 ++++++++++++++++++++++
 3 files changed, 628 insertions(+)
 create mode 100644 drivers/input/joystick/gamecube-adapter.c

diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 7755e5b454d2..18ab1f893ed0 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -422,4 +422,24 @@ config JOYSTICK_SEESAW
 	  To compile this driver as a module, choose M here: the module will be
 	  called adafruit-seesaw.
 
+config JOYSTICK_NGC
+	tristate "Nintendo GameCube adapter support"
+	depends on USB_ARCH_HAS_HCD
+	select USB
+	help
+	  Say Y here if you want to use Nintendo GameCube adapter with
+	  your computer.
+	  Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
+	  and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gamecube_adapter.
+
+config JOYSTICK_NGC_FF
+	bool "Nintendo GameCube adapter rumble support"
+	depends on JOYSTICK_NGC && INPUT
+	select INPUT_FF_MEMLESS
+	help
+	  Say Y here if you want to take advantage of GameCube controller rumble features.
+
 endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 9976f596a920..db0f137ba57f 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_JOYSTICK_JOYDUMP)		+= joydump.o
 obj-$(CONFIG_JOYSTICK_MAGELLAN)		+= magellan.o
 obj-$(CONFIG_JOYSTICK_MAPLE)		+= maplecontrol.o
 obj-$(CONFIG_JOYSTICK_N64)		+= n64joy.o
+obj-$(CONFIG_JOYSTICK_NGC)		+= gamecube-adapter.o
 obj-$(CONFIG_JOYSTICK_PSXPAD_SPI)	+= psxpad-spi.o
 obj-$(CONFIG_JOYSTICK_PXRC)		+= pxrc.o
 obj-$(CONFIG_JOYSTICK_QWIIC)		+= qwiic-joystick.o
diff --git a/drivers/input/joystick/gamecube-adapter.c b/drivers/input/joystick/gamecube-adapter.c
new file mode 100644
index 000000000000..85d69f39d732
--- /dev/null
+++ b/drivers/input/joystick/gamecube-adapter.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  Copyright (c) 2024 Milas Robin
+ *
+ *  Based on the work of:
+ *	Michael Lelli
+ *	Dolphin Emulator project
+ */
+
+#include <linux/usb.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb/input.h>
+
+/* Did not use usb-hid as it is not an hid driver */
+#define USB_VENDOR_ID_NINTENDO		0x057e
+#define USB_DEVICE_ID_NINTENDO_GCADAPTER 0x0337
+
+#define EP_IN  0x81
+#define EP_OUT 0x02
+
+#define GCC_OUT_PKT_LEN 5
+#define GCC_IN_PKT_LEN 37
+
+enum gamecube_status {
+	GAMECUBE_NONE,
+	GAMECUBE_WIRED = 0x10,
+	GAMECUBE_WIRELESS = 0x20,
+};
+
+struct gcc_data {
+	struct ngc_data *adapter;
+	struct input_dev *input;
+	u8 no;
+	u8 status;
+	bool enable;
+};
+
+struct ngc_data {
+	char phys[64];
+
+	struct usb_device *udev;
+	struct usb_interface *intf;
+
+	struct urb *irq_in;
+	u8 *idata;
+	dma_addr_t idata_dma;
+	spinlock_t idata_lock;
+
+	struct urb *irq_out;
+	struct usb_anchor irq_out_anchor;
+	u8 *odata;
+	dma_addr_t odata_dma;
+	spinlock_t odata_lock;		/* output data */
+	bool irq_out_active;		/* we must not use an active URB */
+#ifdef CONFIG_JOYSTICK_NGC_FF
+	u8 odata_rumbles[4];
+	bool rumble_changed;		/* if rumble need update*/
+#endif
+
+	struct gcc_data controllers[4];
+	struct work_struct work;	/* create/delete controller input files */
+};
+
+#ifdef CONFIG_JOYSTICK_NGC_FF
+/* Callers must hold gdata->odata_lock spinlock */
+static int ngc_queue_rumble(struct ngc_data *gdata)
+{
+	int error;
+
+	memcpy(gdata->odata + 1, gdata->odata_rumbles,
+			 sizeof(gdata->odata_rumbles));
+	gdata->irq_out_active = true;
+	gdata->rumble_changed = false;
+	gdata->odata[0] = 0x11;
+	gdata->irq_out->transfer_buffer_length = 5;
+
+	usb_anchor_urb(gdata->irq_out, &gdata->irq_out_anchor);
+	error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
+	if (error) {
+		dev_err(&gdata->intf->dev,
+			"%s - usb_submit_urb failed with result %d\n",
+			__func__, error);
+		usb_unanchor_urb(gdata->irq_out);
+		error = -EIO;
+	}
+	return error;
+}
+
+static int ngc_set_rumble_value(struct ngc_data *gdata, u8 controller, u8 value)
+{
+	unsigned long flags;
+	int error;
+
+	value = !!value;
+	if (controller > 4)
+		return -EINVAL;
+
+	spin_lock_irqsave(&gdata->odata_lock, flags);
+	if (gdata->odata_rumbles[controller] == value) {
+		spin_unlock_irqrestore(&gdata->odata_lock, flags);
+		return 0;
+	}
+	gdata->odata_rumbles[controller] = value;
+	gdata->rumble_changed = true;
+	if (!gdata->irq_out_active)
+		error = ngc_queue_rumble(gdata);
+	spin_unlock_irqrestore(&gdata->odata_lock, flags);
+	return error;
+}
+
+static int ngc_rumble_play(struct input_dev *dev, void *data,
+			      struct ff_effect *eff)
+{
+	struct gcc_data *gccdata = input_get_drvdata(dev);
+	u8 value;
+
+	/*
+	 * The gamecube controller  supports only a single rumble motor so if any
+	 * magnitude is set to non-zero then we start the rumble motor. If both are
+	 * set to zero, we stop the rumble motor.
+	 */
+
+	if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
+		value = 1;
+	else
+		value = 0;
+	return ngc_set_rumble_value(gccdata->adapter, gccdata->no, value);
+}
+static int ngc_init_ff(struct gcc_data *gccdev)
+{
+	input_set_capability(gccdev->input, EV_FF, FF_RUMBLE);
+
+	return input_ff_create_memless(gccdev->input, NULL, ngc_rumble_play);
+}
+#else
+static int ngc_init_ff(struct gcc_data *gccdev) { return 0; }
+#endif
+
+static void ngc_irq_out(struct urb *urb)
+{
+	struct ngc_data *gdata = urb->context;
+	struct device *dev = &gdata->intf->dev;
+	int status = urb->status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gdata->odata_lock, flags);
+
+	switch (status) {
+	case 0:
+		/* success */
+#ifdef CONFIG_JOYSTICK_NGC_FF
+		gdata->irq_out_active = gdata->rumble_changed;
+#else
+		gdata->irq_out_active = false;
+#endif
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+		dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+			__func__, status);
+		gdata->irq_out_active = false;
+		break;
+
+	default:
+		dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+			__func__, status);
+		break;
+	}
+#ifdef CONFIG_JOYSTICK_NGC_FF
+	if (gdata->irq_out_active)
+		ngc_queue_rumble(gdata);
+#endif
+	spin_unlock_irqrestore(&gdata->odata_lock, flags);
+}
+
+static int ngc_init_output(struct ngc_data *gdata,
+			 struct usb_endpoint_descriptor *irq)
+{
+	int error = -ENOMEM;
+
+	init_usb_anchor(&gdata->irq_out_anchor);
+
+	gdata->odata = usb_alloc_coherent(gdata->udev, GCC_OUT_PKT_LEN, GFP_KERNEL,
+			 &gdata->odata_dma);
+	if (!gdata->odata)
+		return error;
+
+	spin_lock_init(&gdata->odata_lock);
+
+	gdata->irq_out = usb_alloc_urb(0, GFP_KERNEL);
+
+	if (!gdata->irq_out)
+		goto err_free_coherent;
+
+	usb_fill_int_urb(gdata->irq_out, gdata->udev,
+			 usb_sndintpipe(gdata->udev, irq->bEndpointAddress),
+			 gdata->odata, GCC_OUT_PKT_LEN, ngc_irq_out, gdata,
+			 irq->bInterval);
+	gdata->irq_out->transfer_dma = gdata->odata_dma;
+	gdata->irq_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	return 0;
+
+err_free_coherent:
+	usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
+			 gdata->odata_dma);
+	return error;
+}
+
+static void ngc_deinit_output(struct ngc_data *gdata)
+{
+	usb_free_urb(gdata->irq_out);
+	usb_free_coherent(gdata->udev, GCC_OUT_PKT_LEN, gdata->odata,
+			 gdata->odata_dma);
+}
+
+static void gcc_input(struct gcc_data *gccdata, const u8 *keys)
+{
+	input_report_key(gccdata->input, BTN_A, !!(keys[1] & BIT(0)));
+	input_report_key(gccdata->input, BTN_B, !!(keys[1] & BIT(1)));
+	input_report_key(gccdata->input, BTN_X, !!(keys[1] & BIT(2)));
+	input_report_key(gccdata->input, BTN_Y, !!(keys[1] & BIT(3)));
+	input_report_key(gccdata->input, BTN_DPAD_LEFT, !!(keys[1] & BIT(4)));
+	input_report_key(gccdata->input, BTN_DPAD_RIGHT, !!(keys[1] & BIT(5)));
+	input_report_key(gccdata->input, BTN_DPAD_DOWN, !!(keys[1] & BIT(6)));
+	input_report_key(gccdata->input, BTN_DPAD_UP, !!(keys[1] & BIT(7)));
+
+	input_report_key(gccdata->input, BTN_START, !!(keys[2] & BIT(0)));
+	input_report_key(gccdata->input, BTN_TR2, !!(keys[2] & BIT(1)));
+	input_report_key(gccdata->input, BTN_TR, !!(keys[2] & BIT(2)));
+	input_report_key(gccdata->input, BTN_TL, !!(keys[2] & BIT(3)));
+
+	input_report_abs(gccdata->input, ABS_X, keys[3]);
+	input_report_abs(gccdata->input, ABS_Y, keys[4] ^ 0xFF);
+	input_report_abs(gccdata->input, ABS_RX, keys[5]);
+	input_report_abs(gccdata->input, ABS_RY, keys[6] ^ 0xFF);
+	input_report_abs(gccdata->input, ABS_Z, keys[7]);
+	input_report_abs(gccdata->input, ABS_RZ, keys[8]);
+
+	input_sync(gccdata->input);
+}
+
+
+static u8 ngc_connected_type(u8 status)
+{
+	u8 type = status & (GAMECUBE_WIRED | GAMECUBE_WIRELESS);
+
+	switch (type) {
+	case GAMECUBE_WIRED:
+	case GAMECUBE_WIRELESS:
+		return type;
+	default:
+		return 0;
+	}
+}
+
+static int ngc_controller_init(struct gcc_data *gccdev, u8 status)
+{
+	int error;
+
+	gccdev->input = input_allocate_device();
+	if (!gccdev->input)
+		return -ENOMEM;
+
+	input_set_drvdata(gccdev->input, gccdev);
+	usb_to_input_id(gccdev->adapter->udev, &gccdev->input->id);
+	gccdev->input->name = "Nintendo GameCube Controller";
+	gccdev->input->phys = gccdev->adapter->phys;
+
+	set_bit(EV_KEY, gccdev->input->evbit);
+
+	set_bit(BTN_A, gccdev->input->keybit);
+	set_bit(BTN_B, gccdev->input->keybit);
+	set_bit(BTN_X, gccdev->input->keybit);
+	set_bit(BTN_Y, gccdev->input->keybit);
+	set_bit(BTN_DPAD_LEFT, gccdev->input->keybit);
+	set_bit(BTN_DPAD_RIGHT, gccdev->input->keybit);
+	set_bit(BTN_DPAD_DOWN, gccdev->input->keybit);
+	set_bit(BTN_DPAD_UP, gccdev->input->keybit);
+	set_bit(BTN_START, gccdev->input->keybit);
+	set_bit(BTN_TR2, gccdev->input->keybit);
+	set_bit(BTN_TR, gccdev->input->keybit);
+	set_bit(BTN_TL, gccdev->input->keybit);
+
+	set_bit(EV_ABS, gccdev->input->evbit);
+
+	set_bit(ABS_X, gccdev->input->absbit);
+	set_bit(ABS_Y, gccdev->input->absbit);
+	set_bit(ABS_RX, gccdev->input->absbit);
+	set_bit(ABS_RY, gccdev->input->absbit);
+	set_bit(ABS_Z, gccdev->input->absbit);
+	set_bit(ABS_RZ, gccdev->input->absbit);
+
+	input_set_abs_params(gccdev->input, ABS_X, 0, 255, 16, 16);
+	input_set_abs_params(gccdev->input, ABS_Y, 0, 255, 16, 16);
+	input_set_abs_params(gccdev->input, ABS_RX, 0, 255, 16, 16);
+	input_set_abs_params(gccdev->input, ABS_RY, 0, 255, 16, 16);
+	input_set_abs_params(gccdev->input, ABS_Z, 0, 255, 16, 0);
+	input_set_abs_params(gccdev->input, ABS_RZ, 0, 255, 16, 0);
+	error = ngc_init_ff(gccdev);
+	if (error) {
+		dev_warn(&gccdev->input->dev, "Could not create ff (skipped)");
+		goto ngc_deinit_controller;
+	}
+	error = input_register_device(gccdev->input);
+	if (error)
+		goto ngc_deinit_controller_ff;
+	gccdev->enable = true;
+	return 0;
+ngc_deinit_controller_ff:
+	input_ff_destroy(gccdev->input);
+ngc_deinit_controller:
+	input_free_device(gccdev->input);
+	return error;
+}
+
+static void ngc_controller_update_work(struct work_struct *work)
+{
+	int i;
+	u8 status[4];
+	bool enable[4];
+	unsigned long flags;
+	struct ngc_data *gdata = container_of(work, struct ngc_data, work);
+
+	for (i = 0; i < 4; i++) {
+		status[i] = gdata->controllers[i].status;
+		enable[i] = ngc_connected_type(status[i]) != 0;
+	}
+
+	for (i = 0; i < 4; i++) {
+		if (enable[i] && !gdata->controllers[i].enable) {
+			if (ngc_controller_init(&gdata->controllers[i], status[i]) != 0)
+				enable[i] = false;
+		}
+	}
+
+	spin_lock_irqsave(&gdata->idata_lock, flags);
+	for (i = 0; i < 4; i++)
+		swap(gdata->controllers[i].enable, enable[i]);
+	spin_unlock_irqrestore(&gdata->idata_lock, flags);
+
+	for (i = 0; i < 4; i++) {
+		if (enable[i] && !gdata->controllers[i].enable)
+			input_unregister_device(gdata->controllers[i].input);
+	}
+}
+
+static void ngc_input(struct ngc_data *gdata)
+{
+	int i;
+	unsigned long flags;
+	bool updated = false;
+
+	for (i = 0; i < 4; i++) {
+		updated = updated ||
+			 gdata->idata[1 + 9 * i] != gdata->controllers[i].status;
+		gdata->controllers[i].status = gdata->idata[1 + 9 * i];
+	}
+	if (updated)
+		schedule_work(&gdata->work);
+	spin_lock_irqsave(&gdata->idata_lock, flags);
+	for (i = 0; i < 4; i++) {
+		if (gdata->controllers[i].enable)
+			gcc_input(&gdata->controllers[i], &gdata->idata[1 + 9 * i]);
+	}
+	spin_unlock_irqrestore(&gdata->idata_lock, flags);
+}
+
+static void ngc_irq_in(struct urb *urb)
+{
+	struct ngc_data *gdata = urb->context;
+	struct usb_interface *intf = gdata->intf;
+	int error;
+
+	switch (urb->status) {
+	case 0:
+		break;
+	case -EOVERFLOW:
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		dev_dbg(&intf->dev, "controller urb shutting down: %d\n",
+			urb->status);
+		return;
+	default:
+		dev_dbg(&intf->dev, "controller urb status: %d\n", urb->status);
+		goto exit;
+	}
+	if (gdata->irq_in->actual_length != GCC_IN_PKT_LEN)
+		dev_warn(&intf->dev, "Bad sized packet\n");
+	else if (gdata->idata[0] != 0x21)
+		dev_warn(&intf->dev, "Unknown opcode %d\n", gdata->idata[0]);
+	else
+		ngc_input(gdata);
+exit:
+	error = usb_submit_urb(gdata->irq_in, GFP_ATOMIC);
+	if (error)
+		dev_err(&intf->dev, "controller urb failed: %d\n", error);
+}
+
+static int ngc_init_input(struct ngc_data *gdata,
+			 struct usb_endpoint_descriptor *irq)
+{
+	int error = -ENOMEM;
+
+	gdata->idata = usb_alloc_coherent(gdata->udev, GCC_IN_PKT_LEN, GFP_KERNEL,
+			 &gdata->idata_dma);
+	if (!gdata->idata)
+		return error;
+
+	gdata->irq_in = usb_alloc_urb(0, GFP_KERNEL);
+	if (!gdata->irq_in)
+		goto err_free_coherent;
+
+	usb_fill_int_urb(gdata->irq_in, gdata->udev,
+			 usb_rcvintpipe(gdata->udev, irq->bEndpointAddress),
+			 gdata->idata, GCC_IN_PKT_LEN, ngc_irq_in, gdata,
+			 irq->bInterval);
+	gdata->irq_in->transfer_dma = gdata->idata_dma;
+	gdata->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	spin_lock_init(&gdata->idata_lock);
+	INIT_WORK(&gdata->work, ngc_controller_update_work);
+
+	return 0;
+
+err_free_coherent:
+	usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
+			 gdata->idata_dma);
+	return error;
+
+}
+
+
+static void ngc_deinit_input(struct ngc_data *gdata)
+{
+	usb_free_urb(gdata->irq_in);
+	usb_free_coherent(gdata->udev, GCC_IN_PKT_LEN, gdata->idata,
+			 gdata->idata_dma);
+}
+
+
+static int ngc_init_irq(struct ngc_data *gdata)
+{
+	struct usb_endpoint_descriptor *eps[] = { NULL, NULL };
+	int error;
+
+	error = usb_find_common_endpoints(gdata->intf->cur_altsetting, NULL, NULL,
+					  &eps[0], &eps[1]);
+	if (error)
+		return -ENODEV;
+	error = ngc_init_output(gdata, eps[1]);
+	if (error)
+		return error;
+	error = ngc_init_input(gdata, eps[0]);
+	if (error)
+		goto err_deinit_out;
+#ifdef CONFIG_JOYSTICK_NGC_FF
+	memset(gdata->odata_rumbles, 0, 4);
+	gdata->rumble_changed = false;
+#endif
+	gdata->irq_out_active = true;
+	gdata->odata[0] = 0x13;
+	gdata->irq_out->transfer_buffer_length = 1;
+
+	error = usb_submit_urb(gdata->irq_in, GFP_KERNEL);
+	if (error)
+		goto err_deinit_in;
+
+	usb_anchor_urb(gdata->irq_out, &gdata->irq_out_anchor);
+	error = usb_submit_urb(gdata->irq_out, GFP_ATOMIC);
+	if (error) {
+		dev_err(&gdata->intf->dev,
+			"%s - usb_submit_urb failed with result %d\n",
+			__func__, error);
+		usb_unanchor_urb(gdata->irq_out);
+		error = -EIO;
+		goto err_kill_in_urb;
+	}
+
+	return 0;
+err_kill_in_urb:
+	usb_kill_urb(gdata->irq_in);
+err_deinit_in:
+	ngc_deinit_input(gdata);
+err_deinit_out:
+	ngc_deinit_output(gdata);
+	return error;
+}
+
+static void ngc_deinit_irq(struct ngc_data *gdata)
+{
+	if (!usb_wait_anchor_empty_timeout(&gdata->irq_out_anchor, 5000)) {
+		dev_warn(&gdata->intf->dev,
+			 "timed out waiting for output URB to complete, killing\n");
+		usb_kill_anchored_urbs(&gdata->irq_out_anchor);
+	}
+	usb_kill_urb(gdata->irq_in);
+	/* Make sure we are done with presence work if it was scheduled */
+	flush_work(&gdata->work);
+
+	ngc_deinit_input(gdata);
+	ngc_deinit_output(gdata);
+}
+
+static void ngc_init_controllers(struct ngc_data *gdata)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gdata->controllers); i++) {
+		gdata->controllers[i].adapter = gdata;
+		gdata->controllers[i].no = i;
+		gdata->controllers[i].status = GAMECUBE_NONE;
+		gdata->controllers[i].enable = false;
+	}
+}
+
+static struct attribute *ngc_attrs[] = {
+	NULL,
+};
+
+static const struct attribute_group ngc_attr_group = {
+	.attrs = ngc_attrs,
+};
+
+static int ngc_init_attr(struct ngc_data *gdata)
+{
+	return sysfs_create_group(&gdata->intf->dev.kobj, &ngc_attr_group);
+}
+
+static void ngc_deinit_attr(struct ngc_data *gdata)
+{
+	sysfs_remove_group(&gdata->intf->dev.kobj, &ngc_attr_group);
+}
+
+
+static int ngc_usb_probe(struct usb_interface *iface, const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+	struct ngc_data *gdata;
+	int error;
+
+	gdata = kzalloc(sizeof(struct ngc_data), GFP_KERNEL);
+	if (!gdata)
+		return -ENOMEM;
+	usb_set_intfdata(iface, gdata);
+	gdata->udev = udev;
+	gdata->intf = iface;
+
+	usb_make_path(udev, gdata->phys, sizeof(gdata->phys));
+	strlcat(gdata->phys, "/input0", sizeof(gdata->phys));
+
+	ngc_init_controllers(gdata);
+	error = ngc_init_irq(gdata);
+	if (error)
+		goto err_free_devs;
+	error = ngc_init_attr(gdata);
+	if (error)
+		goto err_deinit_endpoints;
+	dev_info(&iface->dev, "New device registered\n");
+	return 0;
+err_deinit_endpoints:
+	ngc_deinit_irq(gdata);
+err_free_devs:
+	usb_set_intfdata(iface, NULL);
+	kfree(gdata);
+	return error;
+}
+
+static void ngc_usb_disconnect(struct usb_interface *iface)
+{
+	int i;
+	struct ngc_data *gdata = usb_get_intfdata(iface);
+
+	for (i = 0; i < 4; i++) {
+		if (gdata->controllers[i].enable)
+			input_unregister_device(gdata->controllers[i].input);
+	}
+	ngc_deinit_attr(gdata);
+	ngc_deinit_irq(gdata);
+	usb_set_intfdata(iface, NULL);
+	kfree(gdata);
+}
+
+static const struct usb_device_id ngc_usb_devices[] = {
+	{ USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+		     USB_DEVICE_ID_NINTENDO_GCADAPTER) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, ngc_usb_devices);
+
+static struct usb_driver ngc_usb_driver = {
+	.name		= "gamecube_adapter",
+	.id_table	= ngc_usb_devices,
+	.probe		= ngc_usb_probe,
+	.disconnect	= ngc_usb_disconnect,
+};
+
+module_usb_driver(ngc_usb_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robin Milas <milas.robin@live.fr>");
+MODULE_DESCRIPTION("Driver for GameCube adapter");
-- 
2.44.0


^ permalink raw reply related

* Re: [PATCH 16/19] dmaengine: pl330: drop owner assignment
From: Vinod Koul @ 2024-03-28  4:48 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Russell King, Suzuki K Poulose, Mike Leach, James Clark,
	Alexander Shishkin, Maxime Coquelin, Alexandre Torgue,
	Linus Walleij, Andi Shyti, Olivia Mackall, Herbert Xu,
	Dmitry Torokhov, Miquel Raynal, Michal Simek, Eric Auger,
	Alex Williamson, linux-kernel, coresight, linux-arm-kernel,
	linux-stm32, linux-i2c, linux-crypto, dmaengine, linux-input, kvm
In-Reply-To: <20240326-module-owner-amba-v1-16-4517b091385b@linaro.org>

On 26-03-24, 21:23, Krzysztof Kozlowski wrote:
> Amba bus core already sets owner, so driver does not need to.

Acked-by: Vinod Koul <vkoul@kernel.org>

-- 
~Vinod

^ 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