From d5c6d8adb019a0871781373cc244bc546a0f36d9 Mon Sep 17 00:00:00 2001 From: "tristan.champomier" Date: Fri, 13 Nov 2020 23:01:01 +0100 Subject: [PATCH] Added gslx680-acpi sources module. --- gslx680-acpi/CHANGELOG | 10 + gslx680-acpi/LICENSE | 339 ++++++++++++++ gslx680-acpi/Makefile | 29 ++ gslx680-acpi/README.md | 81 ++++ gslx680-acpi/acpi/gsl-dsdt.aml | 420 ++++++++++++++++++ gslx680-acpi/dkms.conf | 8 + gslx680-acpi/gslx680_ts_acpi.c | 775 +++++++++++++++++++++++++++++++++ gslx680-acpi/silead_ts.fw | Bin 0 -> 19428 bytes 8 files changed, 1662 insertions(+) create mode 100644 gslx680-acpi/CHANGELOG create mode 100644 gslx680-acpi/LICENSE create mode 100644 gslx680-acpi/Makefile create mode 100644 gslx680-acpi/README.md create mode 100644 gslx680-acpi/acpi/gsl-dsdt.aml create mode 100644 gslx680-acpi/dkms.conf create mode 100644 gslx680-acpi/gslx680_ts_acpi.c create mode 100644 gslx680-acpi/silead_ts.fw diff --git a/gslx680-acpi/CHANGELOG b/gslx680-acpi/CHANGELOG new file mode 100644 index 0000000..ff3e72a --- /dev/null +++ b/gslx680-acpi/CHANGELOG @@ -0,0 +1,10 @@ +0.2.1 (2016-04-28 onitake): + + - Changed versioning scheme (2.0.0 becomes 0.2.0) + - Fixed DeviceTree support + - Added DKMS script + - Added module parameter for specifying firmware name + - Fixed cross compilation + - Improved wakeup pin control (ACPI/GPIO) + - Moved firmware and tools to https://github.com/onitake/gsl-firmware + - Improved handling of panel dimensions diff --git a/gslx680-acpi/LICENSE b/gslx680-acpi/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/gslx680-acpi/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/gslx680-acpi/Makefile b/gslx680-acpi/Makefile new file mode 100644 index 0000000..e5b17fd --- /dev/null +++ b/gslx680-acpi/Makefile @@ -0,0 +1,29 @@ +MODULE_NAME = gslx680_ts_acpi + +#CROSS_COMPILE ?= arm-linux-gnueabihf- +#ARCH ?= arm +ARCH := $(shell uname -m | sed -e s/i.86/i386/) +KVER := $(shell uname -r) +KSRC := /lib/modules/$(KVER)/build +PWD = $(shell pwd) +MODDESTDIR := /lib/modules/$(KVER)/kernel/drivers/input/touchscreen + +obj-m += gslx680_ts_acpi.o + +.PHONY: all modules clean + +all: modules + +modules: + make -C $(KSRC) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules + +install: + install -p -m 644 $(MODULE_NAME).ko $(MODDESTDIR) + /sbin/depmod -a $(KVER) + +uninstall: + rm -f $(MODDESTDIR)/$(MODULE_NAME).ko + /sbin/depmod -a $(KVER) + +clean: + make -C $(KSRC) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean diff --git a/gslx680-acpi/README.md b/gslx680-acpi/README.md new file mode 100644 index 0000000..359436a --- /dev/null +++ b/gslx680-acpi/README.md @@ -0,0 +1,81 @@ +About +----- + +This is a generic Linux kernel driver for the Silead GSLx68y +series of touch screen controllers. +It is currently designed to work on ACPI platforms, but +support for DeviceTree/OpenFirmware is also in the works. + +The code was adapted from the platform specific driver here: +https://github.com/jabjoe/sunxi-gslx680 + +Kernel-based finger tracking is available and can be enabled if +the hardware doesn't support it. It works reasonably well, +but touches close to the edges are not registered reliably, +and dragging is very inaccurate. + + +Firmware Instructions +--------------------- + +The controller requires firmware to work properly. Firmware +images extracted from vendor drivers are maintained in a separate +repository: https://github.com/onitake/gsl-firmware + +If your device is not mentioned yet, or the required silead_ts.fw +is not available, please post a request in the issue tracker there, +or consult [gsl-firmware/README.md](https://github.com/onitake/gsl-firmware/blob/master/README.md) +for information on how to obtain the firmware yourself. + + +Build Instructions +------------------ + +If you don't need to cross compile, just make sure you have headers +for your running Linux kernel installed, then type + + make + +This will produce gslx680_ts_acpi.ko + +If you need to cross compile, pass appropriate KSRC, ARCH and +CROSS_COMPILE variables to the make command. For example: + + make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=../linux-arm + +CROSS_COMPILE is the compiler prefix (i.e. gcc will become +arm-linux-gnueabihf-gcc with CROSS_COMPILE=arm-linux-gnueabihf-), ARCH +is the target architecture as understood by the kernel (note: use i386 +for 32 bit Intel platforms) and KSRC is the path to the target +kernel sources or kernel headers. + + +Install Instructions +-------------------- + +Install appropriate firmware for your device, as per the Firmware +Instructions above. + +Load and test the driver with + + insmod ./gslx680_ts_acpi.ko + +Running dmesg should produce some output if the device was matched +by the driver. You should also see a message from the input +subsystem that a new input device was added. + +You may then observe the output from evtest. X.org touchscreen input +should work too, but you will notice that the touch points are +off if the panel width and height were not set accurately. + +This can be fixed by installing xinput_calibrator and using it to +generate a configuration file for your touchscreen. Some desktop +environments may offer their own touchscreen calibrator, +which you can also use. + +xinput_calibrator, when run from an X terminal, will present a +series of points on the screen. Touch each of them when prompted, +then save the configuration printed to the terminal to the +indicated location. + +After restarting X, you should have a working touchscreen. diff --git a/gslx680-acpi/acpi/gsl-dsdt.aml b/gslx680-acpi/acpi/gsl-dsdt.aml new file mode 100644 index 0000000..661f541 --- /dev/null +++ b/gslx680-acpi/acpi/gsl-dsdt.aml @@ -0,0 +1,420 @@ +Device (TCS5) +{ + Name (_ADR, Zero) // _ADR: Address + Name (_HID, "MSSL1680") // _HID: Hardware ID + Name (_CID, "PNP1680") // _CID: Compatible ID + Name (_S0W, Zero) // _S0W: S0 Device Wake State + Name (_DEP, Package (0x02) // _DEP: Dependencies + { + GPO1, + I2C5 + }) + Method (_PS3, 0, Serialized) // _PS3: Power State 3 + { + If (LEqual (^^^GPO0.AVBL, One)) + { + // TCON = Touchscreen On, SHUTDOWN pin, Deepsleep on low + Store (Zero, ^^^GPO0.TCON) /* \_SB_.GPO0.TCON */ + } + } + + Method (_PS0, 0, Serialized) // _PS0: Power State 0 + { + If (LEqual (^^^GPO1.AVBL, One)) + { + // TCD3 = Touchscreen 3.3V or 1.8V?, power gate? -> Power-on reset on low? + Store (Zero, ^^^GPO1.TCD3) /* \_SB_.GPO1.TCD3 */ + } + + Sleep (0x05) + If (LEqual (^^^GPO0.AVBL, One)) + { + // TCON = Touchscreen On, SHUTDOWN pin, Wakeup on high + Store (One, ^^^GPO0.TCON) /* \_SB_.GPO0.TCON */ + } + + Sleep (0x1E) + If (LEqual (^^^GPO1.AVBL, One)) + { + // TCD3 = Touchscreen 3.3V or 1.8V?, power gate? -> Reset complete on high? + Store (One, ^^^GPO1.TCD3) /* \_SB_.GPO1.TCD3 */ + } + + Sleep (0x78) + } + + Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings + { + Name (RBUF, ResourceTemplate () + { + // I2CSerialBusV2 ( SlaveAddress=_ADR , SlaveMode=_SLV , ConnectionSpeed=_SPE , AddressingMode=_MOD , ResourceSource , + // ResourceSourceIndex , ResourceUsage , DescriptorName , Shared=_SHR, VendorData=_VEN ) + // Interpretation: Device 0x40 (=64) on I2C controller \\_SB.I2C4, 7bit addressing, pure slave, 400kHz clock + I2cSerialBus (0x0040, ControllerInitiated, 0x00061A80, + AddressingMode7Bit, "\\_SB.I2C4", + 0x00, ResourceConsumer, , + ) + // Interrupt (ResourceUsage, EdgeLevel=_HE, ActiveLevel=_LL, Shared=_SHR, ResourceSourceIndex, + // ResourceSource, DescriptorName) { InterruptList=_INT } => Buffer + // Interpretation: Maps to APIC Interrupt 0x44 (=70) + Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, ) + { + 0x00000044, + } + // GpioIo ( Shared=_SHR, PinConfig=_PPI, DebounceTimeout=_DBT, DriveStrength=_DRS, IORestriction=_IOR, ResourceSource, + // ResourceSourceIndex, ResourceUsage, DescriptorName, VendorData=_VEN ) {PinList=_PIN} + // Interpretation: Output GPIO pin, controller \\_SB.GPO1, pin number 0x1a (=26) + GpioIo (Shared, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO1", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x001A + } + // Interpretation: Bidrectional GPIO pin, controller \\_SB.GPO1, pin number 0x03 (=3) + GpioIo (Shared, PullDefault, 0x0000, 0x0000, IoRestrictionNone, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0003 + } + }) + Return (RBUF) /* \_SB_.I2C4.TCS5._CRS.RBUF */ + } + + Method (_STA, 0, NotSerialized) // _STA: Status + { + If (LEqual (TPID, 0x04)) + { + Return (0x0F) + } + + Return (Zero) + } +} + +Device (GPO0) +{ + Name (_ADR, Zero) // _ADR: Address + Name (_HID, "INT33FC" /* Intel Baytrail GPIO Controller */) // _HID: Hardware ID + Name (_CID, "INT33FC" /* Intel Baytrail GPIO Controller */) // _CID: Compatible ID + Name (_DDN, "ValleyView General Purpose Input/Output (GPIO) controller") // _DDN: DOS Device Name + Name (_UID, One) // _UID: Unique ID + Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings + { + Name (RBUF, ResourceTemplate () + { + Memory32Fixed (ReadWrite, + 0xFED0C000, // Address Base + 0x00001000, // Address Length + ) + Interrupt (ResourceConsumer, Level, ActiveLow, Shared, ,, ) + { + 0x00000031, + } + }) + Return (RBUF) /* \_SB_.GPO0._CRS.RBUF */ + } + + Method (_STA, 0, NotSerialized) // _STA: Status + { + Return (0x0F) + } + + Name (AVBL, Zero) + Method (_REG, 2, NotSerialized) // _REG: Region Availability + { + If (LEqual (Arg0, 0x08)) + { + Store (Arg1, AVBL) /* \_SB_.GPO0.AVBL */ + } + } + + OperationRegion (GPOP, GeneralPurposeIo, Zero, 0x0C) + Field (GPOP, ByteAcc, NoLock, Preserve) + { + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO0", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0002 + } + ), + CCU2, 1, + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO0", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0003 + } + ), + CCU3, 1, + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO0", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x005F + } + ), + TCON, 1, + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO0", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0064 + } + ), + WWD3, 1 + } +} + +Device (GPO1) +{ + Name (_ADR, Zero) // _ADR: Address + Name (_HID, "INT33FC" /* Intel Baytrail GPIO Controller */) // _HID: Hardware ID + Name (_CID, "INT33FC" /* Intel Baytrail GPIO Controller */) // _CID: Compatible ID + Name (_DDN, "ValleyView GPNCORE controller") // _DDN: DOS Device Name + Name (_UID, 0x02) // _UID: Unique ID + Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings + { + Name (RBUF, ResourceTemplate () + { + Memory32Fixed (ReadWrite, + 0xFED0D000, // Address Base + 0x00001000, // Address Length + ) + Interrupt (ResourceConsumer, Level, ActiveLow, Shared, ,, ) + { + 0x00000030, + } + }) + Return (RBUF) /* \_SB_.GPO1._CRS.RBUF */ + } + + Name (AVBL, Zero) + Method (_REG, 2, NotSerialized) // _REG: Region Availability + { + If (LEqual (Arg0, 0x08)) + { + Store (Arg1, AVBL) /* \_SB_.GPO1.AVBL */ + } + } + + OperationRegion (GPOP, GeneralPurposeIo, Zero, 0x0C) + Field (GPOP, ByteAcc, NoLock, Preserve) + { + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO1", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x000F + } + ), + BST5, 1, + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO1", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x001A + } + ), + TCD3, 1 + } + + Method (_STA, 0, NotSerialized) // _STA: Status + { + Return (0x0F) + } +} + +Device (GPO2) +{ + Name (_ADR, Zero) // _ADR: Address + Name (_HID, "INT33FC" /* Intel Baytrail GPIO Controller */) // _HID: Hardware ID + Name (_CID, "INT33FC" /* Intel Baytrail GPIO Controller */) // _CID: Compatible ID + Name (_DDN, "ValleyView GPSUS controller") // _DDN: DOS Device Name + Name (_UID, 0x03) // _UID: Unique ID + Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings + { + Name (RBUF, ResourceTemplate () + { + Memory32Fixed (ReadWrite, + 0xFED0E000, // Address Base + 0x00001000, // Address Length + ) + Interrupt (ResourceConsumer, Level, ActiveLow, Shared, ,, ) + { + 0x00000032, + } + }) + Return (RBUF) /* \_SB_.GPO2._CRS.RBUF */ + } + + Method (_STA, 0, NotSerialized) // _STA: Status + { + Return (0x0F) + } + + Method (_AEI, 0, NotSerialized) // _AEI: ACPI Event Interrupts + { + Name (RBUF, ResourceTemplate () + { + GpioInt (Edge, ActiveLow, ExclusiveAndWake, PullUp, 0x0000, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0012 + } + GpioInt (Edge, ActiveLow, ExclusiveAndWake, PullUp, 0x0000, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0002 + } + GpioInt (Edge, ActiveBoth, ExclusiveAndWake, PullDefault, 0x0000, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0006 + } + }) + Name (FBUF, ResourceTemplate () + { + GpioInt (Edge, ActiveBoth, SharedAndWake, PullUp, 0x0000, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0012 + } + GpioInt (Edge, ActiveBoth, ExclusiveAndWake, PullDefault, 0x0000, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0006 + } + }) + Return (FBUF) /* \_SB_.GPO2._AEI.FBUF */ + } + + Name (BMUX, Buffer (0x03) + { + 0x00, 0x01, 0x00 /* ... */ + }) + CreateByteField (BMUX, Zero, BBBY) + CreateByteField (BMUX, 0x02, DDDT) + Method (_E06, 0, NotSerialized) // _Exx: Edge-Triggered GPE + { + If (LNotEqual (LIDA, Zero)) + { + Store (And (PVAL, One), LIDS) /* \LIDS */ + ^^PCI0.GFX0.GLID (LIDS) + Notify (LID0, 0x80) // Status Change + } + } + + Method (_E12, 0, NotSerialized) // _Exx: Edge-Triggered GPE + { + If (LAnd (LEqual (AVBL, One), LEqual (^^GPO1.AVBL, One))) + { + If (LEqual (^^I2C5.AVBL, One)) + { + If (LEqual (USID, One)) + { + Store (Zero, ^^GPO1.BST5) /* \_SB_.GPO1.BST5 */ + Sleep (0x05) + Store (^^I2C5.XP30, BMUX) /* \_SB_.GPO2.BMUX */ + And (DDDT, 0x7F, DDDT) /* \_SB_.GPO2.DDDT */ + Store (BMUX, ^^I2C5.XP30) /* \_SB_.I2C5.XP30 */ + Store (One, MOTG) /* \_SB_.GPO2.MOTG */ + ^^PCI0.XHC1.PWOF () + } + Else + { + Store (^^I2C5.XP30, BMUX) /* \_SB_.GPO2.BMUX */ + Or (DDDT, 0x80, DDDT) /* \_SB_.GPO2.DDDT */ + Store (BMUX, ^^I2C5.XP30) /* \_SB_.I2C5.XP30 */ + Sleep (0x05) + Store (One, ^^GPO1.BST5) /* \_SB_.GPO1.BST5 */ + Sleep (0x05) + Store (Zero, MOTG) /* \_SB_.GPO2.MOTG */ + ^^PCI0.XHC1.PWON () + } + } + } + } + + Name (BMBQ, Buffer (0x03) + { + 0x00, 0x01, 0x00 /* ... */ + }) + CreateByteField (BMBQ, Zero, BBBQ) + CreateByteField (BMBQ, 0x02, DDBQ) + Method (BOST, 1, NotSerialized) + { + Store (^^I2C1.BQ01, BMBQ) /* \_SB_.GPO2.BMBQ */ + If (LEqual (Arg0, One)) + { + And (DDBQ, 0xCF, DDBQ) /* \_SB_.GPO2.DDBQ */ + Or (DDBQ, 0x20, DDBQ) /* \_SB_.GPO2.DDBQ */ + } + Else + { + And (DDBQ, 0xCF, DDBQ) /* \_SB_.GPO2.DDBQ */ + Or (DDBQ, 0x10, DDBQ) /* \_SB_.GPO2.DDBQ */ + } + + Store (BMBQ, ^^I2C1.BQ01) /* \_SB_.I2C1.BQ01 */ + } + + Name (BUFC, Buffer (0x03) + { + 0x00, 0x01, 0x00 /* ... */ + }) + CreateByteField (BUFC, Zero, BYAT) + CreateByteField (BUFC, 0x02, DATA) + Name (AVBL, Zero) + Method (_REG, 2, NotSerialized) // _REG: Region Availability + { + If (LEqual (Arg0, 0x08)) + { + Store (Arg1, AVBL) /* \_SB_.GPO2.AVBL */ + } + } + + OperationRegion (GPOP, GeneralPurposeIo, Zero, 0x0C) + Field (GPOP, ByteAcc, NoLock, Preserve) + { + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0014 + } + ), + WFD3, 1, + Connection ( + GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0001 + } + ), + MOTG, 1, + Connection ( + GpioIo (Shared, PullDefault, 0x0000, 0x0000, IoRestrictionInputOnly, + "\\_SB.GPO2", 0x00, ResourceConsumer, , + ) + { // Pin list + 0x0012 + } + ), + USID, 1 + } +} diff --git a/gslx680-acpi/dkms.conf b/gslx680-acpi/dkms.conf new file mode 100644 index 0000000..411e815 --- /dev/null +++ b/gslx680-acpi/dkms.conf @@ -0,0 +1,8 @@ +PACKAGE_NAME="gslx680-acpi" +PACKAGE_VERSION="0.2.1" +CLEAN="make clean" +MAKE="make all KVER=${kernelver} KSRC=/lib/modules/${kernelver}/build" +BUILT_MODULE_NAME="gslx680_ts_acpi" +DEST_MODULE_LOCATION="/updates" +AUTOINSTALL="yes" +REMAKE_INITRD="yes" \ No newline at end of file diff --git a/gslx680-acpi/gslx680_ts_acpi.c b/gslx680-acpi/gslx680_ts_acpi.c new file mode 100644 index 0000000..748cedb --- /dev/null +++ b/gslx680-acpi/gslx680_ts_acpi.c @@ -0,0 +1,775 @@ +/* + * Silead GSL1680/3680 touchscreen driver + * + * Copyright (c) 2015 Gregor Riepl + * + * This driver is based on gslx680_ts.c and elan_ts.c + * Copyright (c) 2012 Shanghai Basewin + * Guan Yuwei + * Copyright (c) 2013 Joe Burmeister + * Joe Burmeister + * Copyright (C) 2014 Elan Microelectronics Corporation. + * Scott Liu + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Device and driver information */ +#define DEVICE_NAME "gslx680" +#define DRIVER_VERSION "0.2.1" + +/* Hardware API constants */ +#define GSL_DATA_REG 0x80 +#define GSL_STATUS_REG 0xe0 +#define GSL_PAGE_REG 0xf0 +#define GSL_TOUCH_STATUS_REG 0xbc +#define GSL_PAGE_SIZE 128 +/* Why 126? */ +#define GSL_MAX_READ 126 +/* Actually 129: 1 command byte and 128 data bytes + * this was 125 originally - using 128 to allow firmware transfers + */ +#define GSL_MAX_WRITE 128 +#define GSL_STATUS_FW 0x80 +#define GSL_STATUS_TOUCH 0x00 + +#define GSL_PWR_GPIO "power" + +#define GSL_MAX_CONTACTS 10 +#define GSL_MAX_AXIS 0xfff +#define GSL_DMAX 0 +#define GSL_JITTER 0 +#define GSL_DEADZONE 0 + +#define GSL_PACKET_SIZE ( \ + GSL_MAX_CONTACTS * sizeof(struct gsl_ts_packet_touch) + \ + sizeof(struct gsl_ts_packet_header) \ +) + +#define GSL_FW_NAME_MAXLEN 32 +#define GSL_FW_NAME_DEFAULT "silead_ts.fw" +#define GSL_FW_VERSION 1 +#define GSL_FW_ID_LIT(a, b, c, d) ( \ + ((a) & 0xff) | \ + (((b) & 0xff) << 8) | \ + (((c) & 0xff) << 16) | \ + (((d) & 0xff) << 24) \ +) +#define GSL_FW_MAGIC GSL_FW_ID_LIT('G', 'S', 'L', 'X') +#define GSL_FW_MODEL_LIT(m) GSL_FW_ID_LIT( \ + (m)/1000%10+'0', \ + (m)/100%10+'0', \ + (m)/10%10+'0', \ + (m)%10+'0' \ +) +#define GSL_FW_MODEL_1680 GSL_FW_MODEL_LIT(1680) + +/* Module Parameters (optional) */ +static char *gsl_fw_name = NULL; +module_param_named(fw_name, gsl_fw_name, charp, 0); + +/* Driver state */ +enum gsl_ts_state { + GSL_TS_INIT, + GSL_TS_SHUTDOWN, + GSL_TS_GREEN, +}; + +/* Driver instance data structure */ +struct gsl_ts_data { + struct i2c_client *client; + struct input_dev *input; + struct gpio_desc *gpio; + char fw_name[GSL_FW_NAME_MAXLEN]; + + enum gsl_ts_state state; + + bool wake_irq_enabled; + + unsigned int x_max; + unsigned int y_max; + unsigned int multi_touches; + bool x_reversed; + bool y_reversed; + bool xy_swapped; + bool soft_tracking; + int jitter; + int deadzone; +}; + +/* Firmware header */ +struct gsl_ts_fw_header { + u32 magic; + u32 model; + u16 version; + u16 touches; + u16 width; + u16 height; + u8 swapped; + u8 xflipped; + u8 yflipped; + u8 tracking; + u32 pages; +} __packed; +/* Firmware data page */ +struct gsl_ts_fw_page { + u16 address; + u16 size; + u8 data[128]; +} __packed; + +/* TODO use get_unaligned_le16 instead of packed structures */ +/* Hardware touch event data header */ +struct gsl_ts_packet_header { + u8 num_fingers; + u8 reserved; + u16 time_stamp; /* little-endian */ +} __packed; +/* Hardware touch event data per finger */ +struct gsl_ts_packet_touch { + u16 y_z; /* little endian, lower 12 bits = y, upper 4 bits = pressure (?) */ + u16 x_id; /* little endian, lower 12 bits = x, upper 4 bits = id */ +} __packed; + + +static int gsl_ts_init(struct gsl_ts_data *ts, const struct firmware *fw) +{ + struct gsl_ts_fw_header *header; + u32 magic; + u16 version; + + dev_dbg(&ts->client->dev, "%s: initialising driver state\n", __func__); + + ts->wake_irq_enabled = false; + ts->state = GSL_TS_INIT; + + if (fw->size < sizeof(struct gsl_ts_fw_header)) { + dev_err(&ts->client->dev, "%s: invalid firmware: file too small.\n", __func__); + return -EINVAL; + } + + header = (struct gsl_ts_fw_header *) fw->data; + magic = le32_to_cpu(header->magic); + if (magic != GSL_FW_MAGIC) { + dev_err(&ts->client->dev, "%s: invalid firmware: invalid magic 0x%08x.\n", __func__, magic); + return -EINVAL; + } + + version = le16_to_cpu(header->version); + if (version != GSL_FW_VERSION) { + dev_err(&ts->client->dev, "%s: invalid firmware: unsupported version %d.\n", __func__, version); + return -EINVAL; + } + + ts->x_max = le16_to_cpu(header->width); + ts->y_max = le16_to_cpu(header->height); + ts->multi_touches = le16_to_cpu(header->touches); + if (ts->x_max == 0 || ts->y_max == 0 || ts->multi_touches == 0) { + dev_err(&ts->client->dev, "%s: invalid firmware: panel width, height or number of touch points is zero.\n", __func__); + return -EINVAL; + } + if (ts->x_max > GSL_MAX_AXIS || ts->y_max > GSL_MAX_AXIS || ts->multi_touches > GSL_MAX_CONTACTS) { + dev_err(&ts->client->dev, "%s: invalid firmware: maximum panel width, height or number of touch points exceeded.\n", __func__); + return -EINVAL; + } + + ts->x_reversed = header->xflipped ? 1 : 0; + ts->y_reversed = header->yflipped ? 1 : 0; + ts->xy_swapped = header->swapped ? 1 : 0; + ts->soft_tracking = header->tracking ? 1 : 0; + ts->jitter = GSL_JITTER; + ts->deadzone = GSL_DEADZONE; + + return 0; +} + +static int gsl_ts_write(struct i2c_client *client, u8 reg, const u8 *pdata, int datalen) +{ + u8 buf[GSL_PAGE_SIZE + 1]; + unsigned int bytelen = 0; + + if (datalen > GSL_MAX_WRITE) { + dev_err(&client->dev, "%s: data transfer too large (%d > %d)\n", __func__, datalen, GSL_MAX_WRITE); + return -EINVAL; + } + + buf[0] = reg; + bytelen++; + + if (datalen != 0 && pdata != NULL) { + memcpy(&buf[bytelen], pdata, datalen); + bytelen += datalen; + } + + return i2c_master_send(client, buf, bytelen); +} + +static int gsl_ts_read(struct i2c_client *client, u8 reg, u8 *pdata, unsigned int datalen) +{ + int ret = 0; + + if (datalen > GSL_MAX_READ) { + dev_err(&client->dev, "%s: data transfer too large (%d > %d)\n", __func__, datalen, GSL_MAX_READ); + return -EINVAL; + } + + ret = gsl_ts_write(client, reg, NULL, 0); + if (ret < 0) { + dev_err(&client->dev, "%s: sending register location failed\n", __func__); + return ret; + } + + return i2c_master_recv(client, pdata, datalen); +} + +static int gsl_ts_startup_chip(struct i2c_client *client) +{ + int rc; + u8 tmp = 0x00; + + dev_dbg(&client->dev, "%s: starting up\n", __func__); + rc = gsl_ts_write(client, 0xe0, &tmp, 1); + usleep_range(10000, 20000); + + return rc; +} + +static int gsl_ts_reset_chip(struct i2c_client *client) +{ + int rc; + u8 arg[4] = { 0x00, 0x00, 0x00, 0x00 }; + + dev_dbg(&client->dev, "%s: resetting\n", __func__); + + arg[0] = 0x88; + rc = gsl_ts_write(client, 0xe0, arg, 1); + if (rc < 0) { + dev_err(&client->dev, "%s: gsl_ts_write 1 fail!\n", __func__); + return rc; + } + usleep_range(10000, 20000); + + arg[0] = 0x04; + rc = gsl_ts_write(client, 0xe4, arg, 1); + if (rc < 0) { + dev_err(&client->dev, "%s: gsl_ts_write 2 fail!\n", __func__); + return rc; + } + usleep_range(10000, 20000); + + arg[0] = 0x00; + rc = gsl_ts_write(client, 0xbc, arg, 4); + if (rc < 0) { + dev_err(&client->dev, "%s: gsl_ts_write 3 fail!\n", __func__); + return rc; + } + usleep_range(10000, 20000); + + return 0; +} + +static int gsl_ts_write_fw(struct gsl_ts_data *ts, const struct firmware *fw) +{ + int rc = 0; + struct i2c_client *client = ts->client; + const struct gsl_ts_fw_header *header; + const struct gsl_ts_fw_page *page; + u32 pages, address; + u16 size; + size_t i; + + dev_dbg(&client->dev, "%s: sending firmware\n", __func__); + + header = (const struct gsl_ts_fw_header *) fw->data; + pages = le32_to_cpu(header->pages); + if (fw->size < sizeof(struct gsl_ts_fw_header) + pages * sizeof(struct gsl_ts_fw_page)) { + dev_err(&client->dev, "%s: firmware page data too small.\n", __func__); + return -EINVAL; + } + + for (i = 0; rc >= 0 && i < pages; i++) { + page = (const struct gsl_ts_fw_page *) &fw->data[sizeof(struct gsl_ts_fw_header) + i * sizeof(struct gsl_ts_fw_page)]; + /* The controller expects a little endian address */ + address = cpu_to_le32(le16_to_cpu(page->address)); + size = le16_to_cpu(page->size); + rc = gsl_ts_write(client, GSL_PAGE_REG, (u8 *) &address, sizeof(address)); + if (rc < 0) { + dev_err(&client->dev, "%s: error setting page register. (page = 0x%x)\n", __func__, le32_to_cpu(address)); + } else { + rc = gsl_ts_write(client, 0, page->data, size); + if (rc < 0) { + dev_err(&client->dev, "%s: error writing page data. (page = 0x%x)\n", __func__, le32_to_cpu(address)); + } + } + } + + if (rc < 0) { + return rc; + } + return 0; +} + +static void gsl_ts_mt_event(struct gsl_ts_data *ts, u8 *buf) +{ + int rc; + struct input_dev *input = ts->input; + struct device *dev = &ts->client->dev; + struct gsl_ts_packet_header *header; + struct gsl_ts_packet_touch *touch; + u8 i; + u16 touches, tseq, x, y, id, pressure; + struct input_mt_pos positions[GSL_MAX_CONTACTS]; + int slots[GSL_MAX_CONTACTS]; + + header = (struct gsl_ts_packet_header *) buf; + touches = header->num_fingers; + tseq = le16_to_cpu(header->time_stamp); + /* time_stamp is 0 on zero-touch events, seems to wrap around 21800 */ + dev_vdbg(dev, "%s: got touch events for %u fingers @%u\n", __func__, touches, tseq); + + if (touches > GSL_MAX_CONTACTS) { + touches = GSL_MAX_CONTACTS; + } + + for (i = 0; i < touches; i++) { + touch = (struct gsl_ts_packet_touch *) &buf[sizeof(*header) + i * sizeof(*touch)]; + y = le16_to_cpu(touch->y_z); + x = le16_to_cpu(touch->x_id); + id = x >> 12; + x &= 0xfff; + pressure = y >> 12; + y &= 0xfff; + + if (ts->xy_swapped) { + swap(x, y); + } + if (ts->x_reversed) { + x = ts->x_max - x; + } + if (ts->y_reversed) { + y = ts->y_max - y; + } + + dev_vdbg(dev, "%s: touch event %u: x=%u y=%u id=0x%x p=%u\n", __func__, i, x, y, id, pressure); + + positions[i].x = x; + positions[i].y = y; + if (!ts->soft_tracking) { + slots[i] = id; + } + } + if (ts->soft_tracking) { + /* This platform does not support finger tracking. + * Use the input core finger tracker instead. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) + rc = input_mt_assign_slots(input, slots, positions, touches); +#else + rc = input_mt_assign_slots(input, slots, positions, touches, GSL_DMAX); +#endif + if (rc < 0) { + dev_err(dev, "%s: input_mt_assign_slots returned %d\n", __func__, rc); + return; + } + } + + for (i = 0; i < touches; i++) { + input_mt_slot(input, slots[i]); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + input_report_abs(input, ABS_MT_POSITION_X, positions[i].x); + input_report_abs(input, ABS_MT_POSITION_Y, positions[i].y); + } + + input_mt_sync_frame(input); + input_sync(input); +} + +static irqreturn_t gsl_ts_irq(int irq, void *arg) +{ + int rc; + struct gsl_ts_data *ts = (struct gsl_ts_data *) arg; + struct i2c_client *client = ts->client; + struct device *dev = &client->dev; + u8 status[4] = { 0, 0, 0, 0 }; + u8 event[GSL_PACKET_SIZE]; + + dev_dbg(&client->dev, "%s: IRQ received\n", __func__); + + if (ts->state == GSL_TS_SHUTDOWN) { + dev_warn(&client->dev, "%s: device supended, not handling interrupt\n", __func__); + return IRQ_HANDLED; + } + + rc = gsl_ts_read(client, GSL_STATUS_REG, status, sizeof(status)); + if (rc < 0) { + dev_err(dev, "%s: error reading chip status\n", __func__); + return IRQ_HANDLED; + } + + if (status[0] == GSL_STATUS_FW) { + /* TODO: Send firmware here instead of during init */ + dev_info(dev, "%s: device waiting for firmware\n", __func__); + + } else if (status[0] == GSL_STATUS_TOUCH) { + dev_vdbg(dev, "%s: touch event\n", __func__); + + rc = gsl_ts_read(client, GSL_DATA_REG, event, sizeof(event)); + if (rc < 0) { + dev_err(dev, "%s: touch data read failed\n", __func__); + return IRQ_HANDLED; + } + if (event[0] == 0xff) { + dev_warn(dev, "%s: ignoring invalid touch record (0xff)\n", __func__); + return IRQ_HANDLED; + } + + rc = gsl_ts_read(client, GSL_TOUCH_STATUS_REG, status, sizeof(status)); + if (rc < 0) { + dev_err(dev, "%s: reading touch status register failed\n", __func__); + return IRQ_HANDLED; + } + + if ((status[0] | status[1] | status[2] | status[3]) == 0) { + gsl_ts_mt_event(ts, event); + + } else { + dev_warn(dev, "%s: device seems to be stuck, resetting\n", __func__); + + rc = gsl_ts_reset_chip(ts->client); + if (rc < 0) { + dev_err(dev, "%s: reset_chip failed\n", __func__); + return IRQ_HANDLED; + } + rc = gsl_ts_startup_chip(ts->client); + if (rc < 0) { + dev_err(dev, "%s: startup_chip failed\n", __func__); + return IRQ_HANDLED; + } + } + } else { + dev_warn(&client->dev, "%s: IRQ received, unknown status 0x%02x\n", __func__, status[0]); + } + + return IRQ_HANDLED; +} + +static void gsl_ts_power(struct i2c_client *client, bool turnoff) +{ + struct gsl_ts_data *data = i2c_get_clientdata(client); +#ifdef CONFIG_ACPI + int error; +#endif + + if (data) { + if (data->gpio) { + gpiod_set_value_cansleep(data->gpio, turnoff ? 0 : 1); +#ifdef CONFIG_ACPI + } else { + error = acpi_bus_set_power(ACPI_HANDLE(&client->dev), turnoff ? ACPI_STATE_D3 : ACPI_STATE_D0); + if (error) { + dev_warn(&client->dev, "%s: error changing power state: %d\n", __func__, error); + } +#endif + } + usleep_range(20000, 50000); + } +} + +static int gsl_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct gsl_ts_data *ts; + const struct firmware *fw = NULL; + unsigned long irqflags; + int error; + bool acpipower; + + dev_warn(&client->dev, "%s: got a device named %s at address 0x%x, IRQ %d, flags 0x%x\n", __func__, client->name, client->addr, client->irq, client->flags); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "%s: i2c check functionality error\n", __func__); + error = -ENXIO; + goto release; + } + + if (client->irq <= 0) { + dev_err(&client->dev, "%s: missing IRQ configuration\n", __func__); + error = -ENODEV; + goto release; + } + + ts = devm_kzalloc(&client->dev, sizeof(struct gsl_ts_data), GFP_KERNEL); + if (!ts) { + error = -ENOMEM; + goto release; + } + + ts->client = client; + i2c_set_clientdata(client, ts); + + if (gsl_fw_name != NULL) { + strncpy(ts->fw_name, gsl_fw_name, sizeof(ts->fw_name)); + } else { + strncpy(ts->fw_name, GSL_FW_NAME_DEFAULT, sizeof(ts->fw_name)); + } + error = request_firmware(&fw, ts->fw_name, &ts->client->dev); + if (error < 0) { + dev_err(&client->dev, "%s: failed to load firmware: %d\n", __func__, error); + goto release; + } + + error = gsl_ts_init(ts, fw); + if (error < 0) { + dev_err(&client->dev, "%s: failed to initialize: %d\n", __func__, error); + goto release; + } + + ts->input = devm_input_allocate_device(&client->dev); + if (!ts->input) { + dev_err(&client->dev, "%s: failed to allocate input device\n", __func__); + error = -ENOMEM; + goto release; + } + + ts->input->name = "Silead GSLx680 Touchscreen"; + ts->input->id.bustype = BUS_I2C; + ts->input->phys = "input/ts"; + + input_set_capability(ts->input, EV_ABS, ABS_X); + input_set_capability(ts->input, EV_ABS, ABS_Y); + + input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->x_max, ts->jitter, ts->deadzone); + input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->y_max, ts->jitter, ts->deadzone); + + input_mt_init_slots(ts->input, ts->multi_touches, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK); + + input_set_drvdata(ts->input, ts); + + error = input_register_device(ts->input); + if (error) { + dev_err(&client->dev, "%s: unable to register input device: %d\n", __func__, error); + goto release; + } + + /* Try to use ACPI power methods first */ + acpipower = false; +#ifdef CONFIG_ACPI + if (ACPI_COMPANION(&client->dev)) { + /* Wake the device up with a power on reset */ + if (acpi_bus_set_power(ACPI_HANDLE(&client->dev), ACPI_STATE_D3)) { + dev_warn(&client->dev, "%s: failed to wake up device through ACPI: %d, using GPIO controls instead\n", __func__, error); + } else { + acpipower = true; + } + } +#endif + /* Not available, use GPIO settings from DSDT/DT instead */ + if (!acpipower) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) + ts->gpio = devm_gpiod_get_index(&client->dev, GSL_PWR_GPIO, 0); +#else + ts->gpio = devm_gpiod_get_index(&client->dev, GSL_PWR_GPIO, 0, GPIOD_OUT_LOW); +#endif + if (IS_ERR(ts->gpio)) { + dev_err(&client->dev, "%s: error obtaining power pin GPIO resource\n", __func__); + error = PTR_ERR(ts->gpio); + goto release; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) + error = gpiod_direction_output(ts->gpio, 0); + if (error < 0) { + dev_err(&client->dev, "%s: error setting GPIO pin direction\n", __func__); + goto release; + } +#endif + } else { + ts->gpio = NULL; + } + + /* Enable power */ + gsl_ts_power(client, false); + + /* Execute the controller startup sequence */ + error = gsl_ts_reset_chip(client); + if (error < 0) { + dev_err(&client->dev, "%s: chip reset failed\n", __func__); + goto release; + } + error = gsl_ts_write_fw(ts, fw); + if (error < 0) { + dev_err(&client->dev, "%s: firmware transfer failed\n", __func__); + goto release; + } + error = gsl_ts_startup_chip(client); + if (error < 0) { + dev_err(&client->dev, "%s: chip startup failed\n", __func__); + goto release; + } + + /* + * Systems using device tree should set up interrupt via DTS, + * the rest will use the default falling edge interrupts. + */ + irqflags = client->dev.of_node ? 0 : IRQF_TRIGGER_FALLING; + + /* Set up interrupt handler - do we still need to account for shared interrupts? */ + error = devm_request_threaded_irq( + &client->dev, + client->irq, + NULL, + gsl_ts_irq, + irqflags | IRQF_ONESHOT, + client->name, + ts + ); + if (error) { + dev_err(&client->dev, "%s: failed to register interrupt\n", __func__); + goto release; + } + + /* + * Systems using device tree should set up wakeup via DTS, + * the rest will configure device as wakeup source by default. + */ + if (!client->dev.of_node) { + device_init_wakeup(&client->dev, true); + } + + ts->state = GSL_TS_GREEN; + +release: + if (fw) { + release_firmware(fw); + } + + if (error < 0) { + return error; + } + return 0; +} + +int gsl_ts_remove(struct i2c_client *client) { + /* Power the device off */ + gsl_ts_power(client, true); + return 0; +} + +static int __maybe_unused gsl_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct gsl_ts_data *ts = i2c_get_clientdata(client); + + dev_warn(&client->dev, "%s: suspending device\n", __func__); + + disable_irq(client->irq); + + gsl_ts_reset_chip(client); + usleep_range(10000, 20000); + + gsl_ts_power(client, true); + + if (device_may_wakeup(dev)) { + ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0); + } + + ts->state = GSL_TS_SHUTDOWN; + + return 0; +} + +static int __maybe_unused gsl_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct gsl_ts_data *ts = i2c_get_clientdata(client); + + dev_warn(&client->dev, "%s: resuming device\n", __func__); + + if (device_may_wakeup(dev) && ts->wake_irq_enabled) { + disable_irq_wake(client->irq); + } + + gsl_ts_power(client, false); + + gsl_ts_reset_chip(client); + gsl_ts_startup_chip(client); + + enable_irq(client->irq); + + ts->state = GSL_TS_GREEN; + + return 0; +} + +static const struct i2c_device_id gsl_ts_i2c_id[] = { + { DEVICE_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gsl_ts_i2c_id); + +#ifdef CONFIG_PM +static SIMPLE_DEV_PM_OPS(gsl_ts_pm_ops, gsl_ts_suspend, gsl_ts_resume); +#endif + +#ifdef CONFIG_ACPI +/* GSL3680 ACPI IDs are untested */ +static const struct acpi_device_id gsl_ts_acpi_match[] = { + { "MSSL1680", 0 }, + { "MSSL3680", 0 }, + { "PNP1680", 0 }, + { "PNP3680", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, gsl_ts_acpi_match); +#endif + +#ifdef CONFIG_OF +/* These should work, but more testing is needed */ +static const struct of_device_id gsl_ts_of_match[] = { + { .compatible = "silead,gsl1680" }, + { .compatible = "silead,gsl3680" }, + { .compatible = "silead,gslx680" }, + { } +}; +MODULE_DEVICE_TABLE(of, gsl_ts_of_match); +#endif + +static struct i2c_driver gslx680_ts_driver = { + .probe = gsl_ts_probe, + .remove = gsl_ts_remove, + .id_table = gsl_ts_i2c_id, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &gsl_ts_pm_ops, +#endif +#ifdef CONFIG_ACPI + .acpi_match_table = ACPI_PTR(gsl_ts_acpi_match), +#endif +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(gsl_ts_of_match), +#endif + }, +}; +module_i2c_driver(gslx680_ts_driver); + +MODULE_DESCRIPTION("GSLX680 touchscreen controller driver"); +MODULE_AUTHOR("Gregor Riepl "); +MODULE_PARM_DESC(fw_name, "firmware file name (default: " GSL_FW_NAME_DEFAULT ")"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/gslx680-acpi/silead_ts.fw b/gslx680-acpi/silead_ts.fw new file mode 100644 index 0000000000000000000000000000000000000000..c106cd42c82bdc5b19d66ff1bbbe55f392373283 GIT binary patch literal 19428 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 RFMz}M{tMvn;(q}g{ujaJNAv&y literal 0 HcmV?d00001