4.19 armv6 kernel with new driver patches 4.19 arnv7 kernel with new driver patches 4.19 armv7l kernel with new driver patches Using post-release public repos
2543 lines
72 KiB
Diff
2543 lines
72 KiB
Diff
From 167a7a4237b0642cec9bcab50912123eb4cc4ff4 Mon Sep 17 00:00:00 2001
|
|
From: procount <kevin.procount@googlemail.com>
|
|
Date: Fri, 21 Jun 2019 23:49:40 +0100
|
|
Subject: [PATCH] Added pimoroni hyperpixel drivers
|
|
|
|
---
|
|
arch/arm/boot/dts/overlays/Makefile | 2 +
|
|
arch/arm/boot/dts/overlays/pimhyp3-overlay.dts | 198 ++++
|
|
arch/arm/boot/dts/overlays/pimhyp4-overlay.dts | 167 ++++
|
|
drivers/input/touchscreen/Kconfig | 26 +
|
|
drivers/input/touchscreen/Makefile | 2 +
|
|
drivers/input/touchscreen/pimhyp3.c | 808 +++++++++++++++
|
|
drivers/input/touchscreen/pimhyp4.c | 1263 ++++++++++++++++++++++++
|
|
7 files changed, 2466 insertions(+)
|
|
create mode 100644 arch/arm/boot/dts/overlays/pimhyp3-overlay.dts
|
|
create mode 100644 arch/arm/boot/dts/overlays/pimhyp4-overlay.dts
|
|
create mode 100644 drivers/input/touchscreen/pimhyp3.c
|
|
create mode 100644 drivers/input/touchscreen/pimhyp4.c
|
|
|
|
diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
|
|
index 2faeb11..cd4fbf2 100644
|
|
--- a/arch/arm/boot/dts/overlays/Makefile
|
|
+++ b/arch/arm/boot/dts/overlays/Makefile
|
|
@@ -102,6 +102,8 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
|
|
pi3-miniuart-bt.dtbo \
|
|
pibell.dtbo \
|
|
piglow.dtbo \
|
|
+ pimhyp3.dtbo \
|
|
+ pimhyp4.dtbo \
|
|
piscreen.dtbo \
|
|
piscreen2r.dtbo \
|
|
pisound.dtbo \
|
|
diff --git a/arch/arm/boot/dts/overlays/pimhyp3-overlay.dts b/arch/arm/boot/dts/overlays/pimhyp3-overlay.dts
|
|
new file mode 100644
|
|
index 0000000..1bfb718
|
|
--- /dev/null
|
|
+++ b/arch/arm/boot/dts/overlays/pimhyp3-overlay.dts
|
|
@@ -0,0 +1,198 @@
|
|
+/*
|
|
+ * Device tree overlay for Cypress Cy8c20466 Touchscreen and & Pimoroni Hyperpixel3.5" LCD
|
|
+ * V2
|
|
+ *
|
|
+ * Compile:
|
|
+ * dtc -@ -I dts -O dtb -o pimhyp3.dtbo pimhyp3-overlay.dts
|
|
+ */
|
|
+
|
|
+/dts-v1/;
|
|
+/plugin/;
|
|
+
|
|
+/{
|
|
+ /* Identify the RPi models this is compatible with (is this sufficient?) */
|
|
+ compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709";
|
|
+
|
|
+ /* Define a group of pins to be configured by the GPIO driver as function 6 = ALT2 = DPI mode */
|
|
+ fragment@0 {
|
|
+ target = <&gpio>;
|
|
+ __overlay__ {
|
|
+ dpi18_pins: dpi18_pins {
|
|
+ brcm,pins = <0 1 2 3 4 5 6 7 8 9 12 13 14 15 16 17 20 21 22 23 24 25>;
|
|
+ brcm,function = <6>;
|
|
+ brcm,pull = <0>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+
|
|
+ /* Define cypress_pins as child of gpio group */
|
|
+ /* referenced by pinctrl to set up gpio 27 as input with pull-down */
|
|
+ fragment@1 {
|
|
+ target = <&gpio>;
|
|
+ __overlay__ {
|
|
+ cypress_pins: cypress_pins {
|
|
+ brcm,pins = <27 26 18 >; // IRQ, Mosi, CS
|
|
+ brcm,function = <0 1 1>; // in out out
|
|
+ brcm,pull = <1 0 0>; // pull-down, none, none?
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Create i2c-gpio driver instance for software i2c on gpio 10 & 11 as i2c-3. 4us delay for 100kHz */
|
|
+ fragment@2 {
|
|
+ target-path = "/";
|
|
+ __overlay__ {
|
|
+ i2c_gpio: i2c@0 {
|
|
+ compatible = "i2c-gpio";
|
|
+ gpios = <&gpio 10 0 /* sda */
|
|
+ &gpio 11 0 /* scl */
|
|
+ >;
|
|
+ i2c-gpio,delay-us = <4>;
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Add the touchscreen controller as a device under i2c_gpio (for default case) */
|
|
+ fragment@3 {
|
|
+ target = <&i2c_gpio>;
|
|
+ __overlay__ {
|
|
+ /* needed to avoid dtc warning */
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ cy8c20466: cy8c20466@5c {
|
|
+ compatible = "pimoroni,cy8c20466";
|
|
+ reg = <0x5c>;
|
|
+ interrupt-parent = <&gpio>;
|
|
+ interrupts = <27 2>;
|
|
+ irq-gpios = <&gpio 27 0>; // specify GPIO27 as the irq line from the TS controller, CLK from LCD
|
|
+ mosi-gpios = <&gpio 26 0>; // specify GPIO26 as the mosi line from the LCD controller
|
|
+ cs-gpios = <&gpio 18 0>; // specify GPIO27 as the cs line from the LCD controller
|
|
+ touchscreen-x-mm = <68>;
|
|
+ touchscreen-y-mm = <46>;
|
|
+ touchscreen-refresh-rate = <17>;
|
|
+ touchscreen-poll = <0>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Define a group of pins to be configured by the GPIO driver as function 6 = ALT2 = DPI mode */
|
|
+ /* THIS IS AN ALTERNATIVE GROUP WHERE ARGON FAN IS FITTED, REMOVING GPIO 0&1 from GROUP */
|
|
+ fragment@4 {
|
|
+ target = <&cy8c20466>;
|
|
+ __overlay__ {
|
|
+ pinctrl-names = "default";
|
|
+ pinctrl-0 = <&cypress_pins &dpi18_pins>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Add the touchscreen controller as a device under i2c_gpio (For check-only)*/
|
|
+ fragment@5 {
|
|
+ target = <&cy8c20466>;
|
|
+ __dormant__ {
|
|
+ pinctrl-names = "default";
|
|
+ pinctrl-0 = <&cypress_pins>;
|
|
+ touchscreen-check; //Indicate to driver that we are just checking presence.
|
|
+ };
|
|
+ };
|
|
+
|
|
+/////////////////////////////////////////////////////////////////
|
|
+
|
|
+ /* display_lcd_rotate=0 */
|
|
+ /* Portrait, USB ports on top */
|
|
+ fragment@6 {
|
|
+ target = <&cy8c20466>;
|
|
+ __overlay__ {
|
|
+ touchscreen-size-x = <800>;
|
|
+ touchscreen-size-y = <480>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=1 */
|
|
+ /* Landscape, USB ports on left */
|
|
+ fragment@7 {
|
|
+ target = <&cy8c20466>;
|
|
+ __dormant__ {
|
|
+ touchscreen-inverted-y;
|
|
+ touchscreen-swapped-x-y;
|
|
+ touchscreen-size-x = <480>;
|
|
+ touchscreen-size-y = <800>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=2 */
|
|
+ /* Portrait, USB ports on bottom */
|
|
+ fragment@8 {
|
|
+ target = <&cy8c20466>;
|
|
+ __dormant__ {
|
|
+ touchscreen-inverted-y;
|
|
+ touchscreen-inverted-x;
|
|
+ touchscreen-size-x = <800>;
|
|
+ touchscreen-size-y = <480>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=3 */
|
|
+ /* Landscape, USB ports on right */
|
|
+ fragment@9 {
|
|
+ target = <&cy8c20466>;
|
|
+ __dormant__ {
|
|
+ touchscreen-swapped-x-y;
|
|
+ touchscreen-inverted-x;
|
|
+ touchscreen-size-x = <480>;
|
|
+ touchscreen-size-y = <800>;
|
|
+ };
|
|
+ };
|
|
+/////////////////////////////////////////////////////////////////////
|
|
+
|
|
+
|
|
+
|
|
+ fragment@10 {
|
|
+ target-path = "/aliases";
|
|
+ __overlay__ {
|
|
+ i2c_gpio = "/i2c@0";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@11 {
|
|
+ target-path = "/__symbols__";
|
|
+ __overlay__ {
|
|
+ i2c_gpio = "/i2c@0";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Disable SPI - because DPI uses it. Not necessary, just in case */
|
|
+ fragment@12 {
|
|
+ target = <&spi0>;
|
|
+ __overlay__ {
|
|
+ status = "disabled";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Disable i2c_arm - because DPI uses it. Not necessary, just in case */
|
|
+ fragment@13 {
|
|
+ target = <&i2c_arm>;
|
|
+ __overlay__ {
|
|
+ status = "disabled";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ __overrides__ {
|
|
+ x-invert = <&cy8c20466>, "touchscreen-inverted-x:0";
|
|
+ y-invert = <&cy8c20466>, "touchscreen-inverted-y:0";
|
|
+ xy-swap = <&cy8c20466>, "touchscreen-swapped-x-y:0";
|
|
+ x-size = <&cy8c20466>, "touchscreen-size-x:0";
|
|
+ y-size = <&cy8c20466>, "touchscreen-size-y:0";
|
|
+ x2y = <&cy8c20466>, "touchscreen-x2y:0";
|
|
+ refresh-rate = <&cy8c20466>, "touchscreen-refresh-rate:0";
|
|
+ poll = <&cy8c20466>, "touchscreen-use-poll:0";
|
|
+ rotate = <0>,"+6-7-8-9";
|
|
+ rotate_0 = <0>,"+6-7-8-9";
|
|
+ rotate_1 = <0>,"-6+7-8-9";
|
|
+ rotate_2 = <0>,"-6-7+8-9";
|
|
+ rotate_3 = <0>,"-6-7-8+9";
|
|
+ checkonly= <0>,"-4+5-6-7-8-9-10";
|
|
+ };
|
|
+};
|
|
diff --git a/arch/arm/boot/dts/overlays/pimhyp4-overlay.dts b/arch/arm/boot/dts/overlays/pimhyp4-overlay.dts
|
|
new file mode 100644
|
|
index 0000000..a667005
|
|
--- /dev/null
|
|
+++ b/arch/arm/boot/dts/overlays/pimhyp4-overlay.dts
|
|
@@ -0,0 +1,167 @@
|
|
+/*
|
|
+ * Device tree overlay for Goodix ft6236 Touchscreen & Pimoroni Hyperpixel4 LCD
|
|
+ * V2
|
|
+ *
|
|
+ * Compile:
|
|
+ * dtc -@ -I dts -O dtb -o cypress.dtbo cypress.dts
|
|
+ */
|
|
+
|
|
+/dts-v1/;
|
|
+/plugin/;
|
|
+/ {
|
|
+ /* Identify the RPi models this is compatible with (is this sufficient?) */
|
|
+ compatible = "brcm,bcm2708";
|
|
+
|
|
+ fragment@0 {
|
|
+ target = <&leds>;
|
|
+ __overlay__ {
|
|
+ pinctrl-names = "default";
|
|
+ pinctrl-0 = <&dpi18_pins>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Define a group of pins to be configured by the GPIO driver as function 6 = ALT2 = DPI mode */
|
|
+ fragment@1 {
|
|
+ target = <&gpio>;
|
|
+ __overlay__ {
|
|
+ dpi18_pins: dpi18_pins {
|
|
+ brcm,pins = <0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xc 0xd 0xe 0xf 0x10 0x11 0x14 0x15 0x16 0x17 0x18 0x19>;
|
|
+ brcm,function = <0x6>;
|
|
+ brcm,pull = <0x0>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@2 {
|
|
+ target-path = "/";
|
|
+ __overlay__ {
|
|
+ i2c_gpio: i2c@0 {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ compatible = "i2c-gpio";
|
|
+ gpios = <&gpio 10 0 /* sda */
|
|
+ &gpio 11 0 /* scl */
|
|
+ >;
|
|
+ i2c-gpio,delay-us = <4>; /* ~100 kHz */
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@3 {
|
|
+ target = <&i2c_gpio>;
|
|
+ __overlay__ {
|
|
+ /* needed to avoid dtc warning */
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ ft6236: ft6236@5d {
|
|
+ compatible = "pimoroni,ft6236";
|
|
+ reg = <0x5d>;
|
|
+ interrupt-parent = <&gpio>;
|
|
+ interrupts = <27 2>;
|
|
+ irq-gpios = <&gpio 27 0>; // specify GPIO27 as the irq line from the TS controller, CLK from LCD
|
|
+ mosi-gpios = <&gpio 26 0>; // specify GPIO26 as the mosi line from the LCD controller
|
|
+ cs-gpios = <&gpio 18 0>; // specify GPIO27 as the cs line from the LCD controller
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@4 { //TO BE DELETED, OR PUT DPI18_PINS GERE TO OVERLAY FT6236
|
|
+ target = <&i2c_gpio>;
|
|
+ __dormant__ {
|
|
+ /* needed to avoid dtc warning */
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ ft6236a: ft6236@5d {
|
|
+ compatible = "pimoroni,ft6236";
|
|
+ reg = <0x5d>;
|
|
+ interrupt-parent = <&gpio>;
|
|
+ interrupts = <27 2>;
|
|
+ irq-gpios = <&gpio 27 0>; // specify GPIO27 as the irq line from the TS controller, CLK from LCD
|
|
+ touchscreen-check; //Indicate to driver that we are just checking presence.
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@5 {
|
|
+ target = <&ft6236>;
|
|
+ __dormant__ {
|
|
+ touchscreen-check; //Indicate to driver that we are just checking presence.
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* Define a group of pins to be configured by the GPIO driver as function 6 = ALT2 = DPI mode */
|
|
+ fragment@6 {
|
|
+ target = <&dpi18_pins>;
|
|
+ __dormant__ {
|
|
+ brcm,pins = <0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xc 0xd 0xe 0xf 0x10 0x11 0x14 0x15 0x16 0x17 0x18 0x19>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ fragment@7 {
|
|
+ target-path = "/";
|
|
+ __overlay__ {
|
|
+ rpi_backlight: rpi_backlight {
|
|
+ compatible = "gpio-backlight";
|
|
+ gpios = <&gpio 19 0>;
|
|
+ default-on;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=0 */
|
|
+ /* Portrait, USB ports on top */
|
|
+ fragment@8 {
|
|
+ target = <&ft6236>;
|
|
+ __overlay__ {
|
|
+ touchscreen-inverted-x;
|
|
+ touchscreen-size-x = <480>;
|
|
+ touchscreen-size-y = <800>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=1 */
|
|
+ /* Landscape, USB ports on left */
|
|
+ fragment@9 {
|
|
+ target = <&ft6236>;
|
|
+ __dormant__ {
|
|
+ touchscreen-swapped-x-y;
|
|
+ touchscreen-size-x = <480>;
|
|
+ touchscreen-size-y = <800>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=2 */
|
|
+ /* Portrait, USB ports on bottom */
|
|
+ fragment@10 {
|
|
+ target = <&ft6236>;
|
|
+ __dormant__ {
|
|
+ touchscreen-inverted-y;
|
|
+ touchscreen-size-x = <480>;
|
|
+ touchscreen-size-y = <800>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ /* display_lcd_rotate=3 */
|
|
+ /* Landscape, USB ports on right */
|
|
+ fragment@11 {
|
|
+ target = <&ft6236>;
|
|
+ __dormant__ {
|
|
+ touchscreen-swapped-x-y;
|
|
+ touchscreen-inverted-x;
|
|
+ touchscreen-inverted-y;
|
|
+ touchscreen-size-x = <480>;
|
|
+ touchscreen-size-y = <800>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+
|
|
+ __overrides__ {
|
|
+ rotate = <0>,"+8-9-10-11";
|
|
+ rotate_0 = <0>,"+8-9-10-11";
|
|
+ rotate_1 = <0>,"-8+9-10-11";
|
|
+ rotate_2 = <0>,"-8-9+10-11";
|
|
+ rotate_3 = <0>,"-8-9-10+11";
|
|
+ checkonly= <0>,"-3+4-8-9-10-11";
|
|
+ };
|
|
+};
|
|
+
|
|
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
|
|
index 8e6cb40..6a24f5b 100644
|
|
--- a/drivers/input/touchscreen/Kconfig
|
|
+++ b/drivers/input/touchscreen/Kconfig
|
|
@@ -401,6 +401,32 @@ config TOUCHSCREEN_HIDEEP
|
|
To compile this driver as a module, choose M here : the
|
|
module will be called hideep_ts.
|
|
|
|
+config TOUCHSCREEN_PIMHYP3
|
|
+ tristate "Cypress I2C touchscreen for Pimoroni Hyperpixel 3.5"
|
|
+ depends on I2C
|
|
+ depends on GPIOLIB || COMPILE_TEST
|
|
+ help
|
|
+ Say Y here if you have the Pimoroni Hyperpixel 3.5 touchscreen
|
|
+ display connected to your system.
|
|
+
|
|
+ If unsure, say N.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called pimhyp3.
|
|
+
|
|
+config TOUCHSCREEN_PIMHYP4
|
|
+ tristate "Goodix I2C touchscreen for Pimoroni Hyperpixel 4.0"
|
|
+ depends on I2C
|
|
+ depends on GPIOLIB || COMPILE_TEST
|
|
+ help
|
|
+ Say Y here if you have the Pimoroni Hyperpixel 4.0 touchscreen
|
|
+ display connected to your system.
|
|
+
|
|
+ If unsure, say N.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called pimhyp4.
|
|
+
|
|
config TOUCHSCREEN_ILI210X
|
|
tristate "Ilitek ILI210X based touchscreen"
|
|
depends on I2C
|
|
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
|
|
index cbc2159..c6916af 100644
|
|
--- a/drivers/input/touchscreen/Makefile
|
|
+++ b/drivers/input/touchscreen/Makefile
|
|
@@ -110,3 +110,5 @@ obj-$(CONFIG_TOUCHSCREEN_ZET6223) += zet6223.o
|
|
obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o
|
|
obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o
|
|
obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o
|
|
+obj-$(CONFIG_TOUCHSCREEN_PIMHYP3) += pimhyp3.o
|
|
+obj-$(CONFIG_TOUCHSCREEN_PIMHYP4) += pimhyp4.o
|
|
diff --git a/drivers/input/touchscreen/pimhyp3.c b/drivers/input/touchscreen/pimhyp3.c
|
|
new file mode 100644
|
|
index 0000000..fe283fb
|
|
--- /dev/null
|
|
+++ b/drivers/input/touchscreen/pimhyp3.c
|
|
@@ -0,0 +1,808 @@
|
|
+/*
|
|
+ * Driver for Pimoroni Hyperpixel 3.5" Touchscreen LCD
|
|
+ * uses Cypress Cy8c20466 Touchscreen Capsense Psoc
|
|
+ *
|
|
+ * Adapted from Pimoroni's Python driver.
|
|
+ * Adds commands to program the LCD, from Pimoroni's hyperpixel-init.c
|
|
+ *
|
|
+ * Copyright (c) 2019 ProCount
|
|
+ */
|
|
+
|
|
+//#define DEBUG
|
|
+
|
|
+/*
|
|
+ * 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; version 2 of the License.
|
|
+ */
|
|
+
|
|
+/* DTparameters:
|
|
+ * x-invert = <&cy8c20466>, "touchscreen-inverted-x:0";
|
|
+ * y-invert = <&cy8c20466>, "touchscreen-inverted-y:0";
|
|
+ * xy-swap = <&cy8c20466>, "touchscreen-swapped-x-y:0";
|
|
+ * x-size = <&cy8c20466>, "touchscreen-size-x:0";
|
|
+ * y-size = <&cy8c20466>, "touchscreen-size-y:0";
|
|
+ * x2y = <&cy8c20466>, "touchscreen-x2y:0"; //Determines order of inversion vs swapping (?)
|
|
+ * refresh-rate = <&cy8c20466>, "touchscreen-refresh-rate:0";
|
|
+ * rotate_0 = rotate 0 deg cw
|
|
+ * rotate_1 = rotate 90 deg cw
|
|
+ * rotate_2 = rotate 180 deg cw
|
|
+ * rotate_3 = rotate 270 deg cw
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/dmi.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/input/mt.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/of.h>
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/jiffies.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+/////////////////////////////// DEFINITIONS ////////////////////////////////
|
|
+
|
|
+#define uDELAY 100 // clock pulse time in microseconds
|
|
+#define mWAIT 120 // wait time in milliseconds
|
|
+
|
|
+#define PIMHYP3_GPIO_INT_NAME "irq"
|
|
+#define PIMHYP3_GPIO_MOSI_NAME "mosi"
|
|
+#define PIMHYP3_GPIO_CS_NAME "cs"
|
|
+
|
|
+#define PIMHYP3_INT_TRIGGER 0
|
|
+
|
|
+#define PIMHYP3_MAX_WIDTH 800
|
|
+#define PIMHYP3_MAX_HEIGHT 480
|
|
+
|
|
+/* Addresses within i2c map */
|
|
+#define PIMHYP3_FIXED1 0x30
|
|
+#define PIMHYP3_FIXED2 0x70
|
|
+
|
|
+#define PIMHYP3_NUM_TOUCHES 0x6D
|
|
+#define PIMHYP3_INT_SETTINGS 0x6E
|
|
+#define PIMHYP3_COORDS 0x40
|
|
+
|
|
+const u8 fixed1[16] = {0xe0, 0x00, 0x01, 0x04, 0x04, 0x3c, 0x53, 0x78, 0x78, 0x00, 0x01, 0x00, 0x00, 0x0c, 0x14, 0x00};
|
|
+const u8 fixed2[16] = {0xa0, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x23, 0x50, 0x13, 0x01, 0x00, 0x00};
|
|
+
|
|
+struct pimhyp3_ts_data {
|
|
+ struct i2c_client *client;
|
|
+ struct input_dev *input_dev;
|
|
+ struct gpio_desc *gpiod_int;
|
|
+ struct gpio_desc *gpiod_mosi;
|
|
+ struct gpio_desc *gpiod_cs;
|
|
+ struct task_struct *thread;
|
|
+ u16 id;
|
|
+ u16 version;
|
|
+ //instance data
|
|
+ int last_x1;
|
|
+ int last_y1;
|
|
+ int last_x2;
|
|
+ int last_y2;
|
|
+ int last_numTouches;
|
|
+ //Configuration parameters
|
|
+ bool use_poll;
|
|
+ bool X2Y;
|
|
+ u32 abs_x_max;
|
|
+ u32 abs_y_max;
|
|
+ u8 requestedRefreshRate;
|
|
+ bool swapped_x_y;
|
|
+ bool inverted_x;
|
|
+ bool inverted_y;
|
|
+ unsigned int max_touch_num;
|
|
+ unsigned long irq_flags;
|
|
+ unsigned int int_trigger_type;
|
|
+};
|
|
+
|
|
+static const unsigned long pimhyp3_irq_flags[] = {
|
|
+ IRQ_TYPE_EDGE_RISING,
|
|
+ IRQ_TYPE_EDGE_FALLING,
|
|
+ IRQ_TYPE_LEVEL_LOW,
|
|
+ IRQ_TYPE_LEVEL_HIGH,
|
|
+};
|
|
+
|
|
+/////////////////////////////// LOW LEVEL //////////////////////////////////
|
|
+
|
|
+/**
|
|
+ * pimhyp3_i2c_read - read data from a register of the i2c slave device.
|
|
+ *
|
|
+ * @client: i2c Client info.
|
|
+ * @reg: the register to read from.
|
|
+ * @buf: raw read data buffer.
|
|
+ * @len: length of the buffer to read
|
|
+ * @returns 0, or -EIO if error
|
|
+ */
|
|
+static int pimhyp3_i2c_read(struct i2c_client *client,
|
|
+ u16 reg, u8 *buf, int len)
|
|
+{
|
|
+ int bytes_read;
|
|
+
|
|
+ bytes_read = i2c_smbus_read_i2c_block_data(client, reg, len, buf);
|
|
+ return (bytes_read != len ? -EIO : 0);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp3_i2c_write - write data to a register of the i2c slave device.
|
|
+ *
|
|
+ * @client: i2c device.
|
|
+ * @reg: the register to write to.
|
|
+ * @buf: raw data buffer to write.
|
|
+ * @len: length of the buffer to write
|
|
+ */
|
|
+static int pimhyp3_i2c_write(struct i2c_client *client, u16 reg, const u8 *buf,
|
|
+ unsigned len)
|
|
+{
|
|
+ int bytes_written;
|
|
+
|
|
+ bytes_written = i2c_smbus_write_i2c_block_data(client, reg, len, buf);
|
|
+ return (bytes_written != len ? -EIO : 0);
|
|
+}
|
|
+
|
|
+/////////////////////////////// PROCESSING /////////////////////////////////
|
|
+
|
|
+static void pimhyp3_ts_translate_coords(struct pimhyp3_ts_data *ts, u16 *x, u16 *y)
|
|
+{
|
|
+ /* Adjust coordinates to screen rotation */
|
|
+ if(!ts->X2Y)
|
|
+ {
|
|
+ /* Inversions have to happen after axis swapping */
|
|
+ if (ts->swapped_x_y)
|
|
+ swap( *x, *y);
|
|
+ }
|
|
+ if (ts->inverted_x)
|
|
+ *x = ts->abs_x_max - *x;
|
|
+ if (ts->inverted_y)
|
|
+ *y = ts->abs_y_max - *y;
|
|
+
|
|
+ if (ts->X2Y)
|
|
+ {
|
|
+ /* Inversions have to happen before axis swapping */
|
|
+ if (ts->swapped_x_y)
|
|
+ swap( *x, *y);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static void pimhyp3_ts_process_coords(struct pimhyp3_ts_data *ts, u16 x1, u16 y1,u16 x2, u16 y2, u8 numTouches)
|
|
+{
|
|
+ int output;
|
|
+ u16 raw_x1;
|
|
+ u16 raw_y1;
|
|
+
|
|
+ raw_x1 = x1;
|
|
+ raw_y1 = y1;
|
|
+
|
|
+ /* Adjust coordinates to screen rotation */
|
|
+ pimhyp3_ts_translate_coords(ts, &x1, &y1) ;
|
|
+ pimhyp3_ts_translate_coords(ts, &x2, &y2) ;
|
|
+
|
|
+ dev_dbg( &ts->client->dev, "Trans: (%d,%d)->(%d,%d) Max(%d,%d) Inv(%d,%d) Swap(%d,%d)",raw_x1,raw_y1,x1,y1, ts->abs_x_max,ts->abs_y_max, ts->inverted_x, ts->inverted_y, ts->swapped_x_y, ts->X2Y);
|
|
+
|
|
+ output=0;
|
|
+ if (numTouches)
|
|
+ {
|
|
+ if (!ts->last_numTouches)
|
|
+ {
|
|
+ /* new 1 press */
|
|
+
|
|
+ output |= 1;
|
|
+ input_mt_slot(ts->input_dev, 0);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, 0);
|
|
+
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x1);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y1);
|
|
+ input_report_key(ts->input_dev, BTN_TOUCH, 1);
|
|
+ input_report_abs(ts->input_dev, ABS_X, x1);
|
|
+ input_report_abs(ts->input_dev, ABS_Y, y1);
|
|
+ dev_dbg( &ts->client->dev, "Press 1: (%d,%d)",x1,y1);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* Contact moved? */
|
|
+ if ((x1 != ts->last_x1) || (y1 != ts->last_y1))
|
|
+ {
|
|
+ output |= 1;
|
|
+ input_mt_slot(ts->input_dev, 0);
|
|
+
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, 0);
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
|
|
+ if (x1 != ts->last_x1)
|
|
+ input_report_abs(ts->input_dev, ABS_X, x1);
|
|
+ if (y1 != ts->last_y1)
|
|
+ input_report_abs(ts->input_dev, ABS_Y, y1);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x1);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y1);
|
|
+ dev_dbg( &ts->client->dev, "Move 1: (%d,%d)",x1,y1);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ else if (ts->last_numTouches) //!numTouches
|
|
+ {
|
|
+ /* new 1 release */
|
|
+ output |= 1;
|
|
+ input_mt_slot(ts->input_dev, 0);
|
|
+
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, -1);
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
|
|
+ input_report_key(ts->input_dev, BTN_TOUCH, 0);
|
|
+ dev_dbg( &ts->client->dev, "Release 1");
|
|
+ }
|
|
+
|
|
+ if (numTouches==2)
|
|
+ {
|
|
+ if (ts->last_numTouches<2)
|
|
+ {
|
|
+ /* new 2 press */
|
|
+ output |= 2;
|
|
+ input_mt_slot(ts->input_dev, 1);
|
|
+
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, 1);
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x2);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y2);
|
|
+ input_report_key(ts->input_dev, BTN_TOUCH, 1);
|
|
+ input_report_abs(ts->input_dev, ABS_X, x2);
|
|
+ input_report_abs(ts->input_dev, ABS_Y, y2);
|
|
+ dev_dbg( &ts->client->dev, "Press 2: (%d,%d)",x2,y2);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* Contact moved? */
|
|
+ if ((x2 != ts->last_x2) || (y2 != ts->last_y2))
|
|
+ {
|
|
+ output |= 2;
|
|
+ input_mt_slot(ts->input_dev, 1);
|
|
+
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, 1);
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
|
|
+ if (x2 != ts->last_x2)
|
|
+ input_report_abs(ts->input_dev, ABS_X, x2);
|
|
+ if (y2 != ts->last_y2)
|
|
+ input_report_abs(ts->input_dev, ABS_Y, y2);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x2);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y2);
|
|
+ dev_dbg( &ts->client->dev, "Move 2: (%d,%d)",x2,y2);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ else if (ts->last_numTouches==2) // (numTouches<2))
|
|
+ {
|
|
+ /* new 2 release */
|
|
+ output |= 2;
|
|
+ input_mt_slot(ts->input_dev, 1);
|
|
+
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, -1);
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
|
|
+ input_report_key(ts->input_dev, BTN_TOUCH, 0);
|
|
+ dev_dbg( &ts->client->dev, "Release 2");
|
|
+ }
|
|
+
|
|
+ if (output)
|
|
+ input_sync(ts->input_dev);
|
|
+
|
|
+ ts->last_x1 = x1;
|
|
+ ts->last_y1 = y1;
|
|
+ ts->last_x2 = x2;
|
|
+ ts->last_y2 = y2;
|
|
+ ts->last_numTouches = numTouches;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp3_process_events - Process incoming events
|
|
+ *
|
|
+ * @ts: our pimhyp3_ts_data pointer
|
|
+ *
|
|
+ * Called from poll or when the IRQ is triggered. Read the current device state, and push
|
|
+ * the input events to the user space.
|
|
+ */
|
|
+static void pimhyp3_process_events(struct pimhyp3_ts_data *ts)
|
|
+{
|
|
+ //int pin = desc_to_gpio(ts->gpiod_int);
|
|
+
|
|
+ //int value;
|
|
+
|
|
+ if (gpiod_get_value(ts->gpiod_int) || ts->last_numTouches)
|
|
+ { //Only process the TS if something has happened or a contact in progress
|
|
+
|
|
+ u16 x1,y1,x2,y2;
|
|
+ u8 rawdata[8];
|
|
+ u8 numTouches;
|
|
+
|
|
+ pimhyp3_i2c_read(ts->client, PIMHYP3_NUM_TOUCHES, &numTouches, 1);
|
|
+
|
|
+ pimhyp3_i2c_read(ts->client, PIMHYP3_COORDS, rawdata, 8);
|
|
+
|
|
+ x1 = rawdata[0] | (rawdata[4] << 8);
|
|
+ y1 = rawdata[1] | (rawdata[5] << 8);
|
|
+ x2 = rawdata[2] | (rawdata[6] << 8);
|
|
+ y2 = rawdata[3] | (rawdata[7] << 8);
|
|
+
|
|
+ pimhyp3_ts_process_coords(ts, x1,y1,x2,y2,numTouches);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/* Thread to poll for touchscreen events
|
|
+ *
|
|
+ */
|
|
+static int pimhyp3_thread(void *arg)
|
|
+{
|
|
+
|
|
+ while (!kthread_should_stop())
|
|
+ {
|
|
+ struct pimhyp3_ts_data *ts = (struct pimhyp3_ts_data *) arg;
|
|
+
|
|
+ /* 60fps polling */
|
|
+ msleep_interruptible(ts->requestedRefreshRate); //17
|
|
+
|
|
+ pimhyp3_process_events(ts);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * goodix_ts_irq_handler - The IRQ handler
|
|
+ *
|
|
+ * @irq: interrupt number.
|
|
+ * @dev_id: private data pointer.
|
|
+ */
|
|
+static irqreturn_t pimhyp3_ts_irq_handler(int irq, void *dev_id)
|
|
+{
|
|
+ struct pimhyp3_ts_data *ts = dev_id;
|
|
+
|
|
+ pimhyp3_process_events(ts);
|
|
+
|
|
+// if (goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0) < 0)
|
|
+// dev_err(&ts->client->dev, "I2C write end_cmd error\n");
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void pimhyp3_free_irq(struct pimhyp3_ts_data *ts)
|
|
+{
|
|
+ devm_free_irq(&ts->client->dev, ts->client->irq, ts);
|
|
+}
|
|
+
|
|
+static int pimhyp3_request_irq(struct pimhyp3_ts_data *ts)
|
|
+{
|
|
+ return devm_request_threaded_irq(&ts->client->dev, ts->client->irq,
|
|
+ NULL, pimhyp3_ts_irq_handler,
|
|
+ ts->irq_flags, ts->client->name, ts);
|
|
+}
|
|
+
|
|
+/////////////////////////////// DETECTION //////////////////////////////////
|
|
+
|
|
+/**
|
|
+ * pimhyp3_check_fixed - I2C test function to chcek the fixed data is correct.
|
|
+ *
|
|
+ * @client: the i2c client
|
|
+ * @reg: the starting register
|
|
+ * @fixed: a 16 byte array of hte expected data
|
|
+ * @returns 0 if ok. -1 data mismatch, -2 can't read data
|
|
+ */
|
|
+static int pimhyp3_check_fixed(struct i2c_client *client, int reg, const u8* fixed)
|
|
+{
|
|
+ u8 input_buffer[16];
|
|
+ int error=0;
|
|
+ int i;
|
|
+
|
|
+
|
|
+ error = pimhyp3_i2c_read(client, reg, input_buffer, 16);
|
|
+ if (error)
|
|
+ {
|
|
+ dev_err(&client->dev, "i2c test failed: %d\n",error);
|
|
+ return(-2);
|
|
+ }
|
|
+ for (i=0; i<16; i++)
|
|
+ {
|
|
+ if (input_buffer[i] != fixed[i])
|
|
+ {
|
|
+ dev_err(&client->dev, "i2c check failed at: %02x = [%02x]\n",reg+i,input_buffer[i] );
|
|
+ error = -1;
|
|
+ }
|
|
+ }
|
|
+ return(error);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp3_i2c_test - I2C test function to check if the device answers.
|
|
+ *
|
|
+ * @client: the i2c client
|
|
+ * @returns: 0 if ok, -1 or -2 if error
|
|
+ */
|
|
+static int pimhyp3_i2c_test(struct i2c_client *client)
|
|
+{
|
|
+ /* Code to detect TS is fitted -ok */
|
|
+ //int retry = 0;
|
|
+ int error1, error2;
|
|
+
|
|
+ //while (retry++ <2)
|
|
+ //{
|
|
+ error1 = pimhyp3_check_fixed(client, PIMHYP3_FIXED1, fixed1);
|
|
+ error2 = pimhyp3_check_fixed(client, PIMHYP3_FIXED2, fixed2);
|
|
+
|
|
+ if (!error1 && !error2)
|
|
+ return(0);
|
|
+ msleep(20);
|
|
+ //}
|
|
+
|
|
+ if (error2)
|
|
+ error1 = error2;
|
|
+
|
|
+ return error1;
|
|
+}
|
|
+
|
|
+
|
|
+/////////////////////////////// TERMINATION ////////////////////////////////
|
|
+
|
|
+
|
|
+static int pimhyp3_ts_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct pimhyp3_ts_data *ts = i2c_get_clientdata(client);
|
|
+
|
|
+ if (ts->gpiod_int)
|
|
+ devm_gpiod_put(&client->dev, ts->gpiod_int);
|
|
+
|
|
+ if (ts->use_poll)
|
|
+ kthread_stop(ts->thread);
|
|
+ else
|
|
+ pimhyp3_free_irq(ts);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/////////////////////////////// INITIALISATION /////////////////////////////
|
|
+
|
|
+
|
|
+static int32_t commands[] = {
|
|
+ -1, 0x0011, -1, 0x0001, -1, 0x00c1, 0x01a8, 0x01b1,
|
|
+ 0x0145, 0x0104, 0x00c5, 0x0180, 0x016c, 0x00c6, 0x01bd, 0x0184,
|
|
+ 0x00c7, 0x01bd, 0x0184, 0x00bd, 0x0102, 0x0011, -1, 0x0100,
|
|
+ 0x0100, 0x0182, 0x0026, 0x0108, 0x00e0, 0x0100, 0x0104, 0x0108,
|
|
+ 0x010b, 0x010c, 0x010d, 0x010e, 0x0100, 0x0104, 0x0108, 0x0113,
|
|
+ 0x0114, 0x012f, 0x0129, 0x0124, 0x00e1, 0x0100, 0x0104, 0x0108,
|
|
+ 0x010b, 0x010c, 0x0111, 0x010d, 0x010e, 0x0100, 0x0104, 0x0108,
|
|
+ 0x0113, 0x0114, 0x012f, 0x0129, 0x0124, 0x0026, 0x0108, 0x00fd,
|
|
+ 0x0100, 0x0108, 0x0029
|
|
+};
|
|
+
|
|
+
|
|
+static void send_bits(struct pimhyp3_ts_data *ts, uint16_t data, uint16_t count)
|
|
+{
|
|
+ int x;
|
|
+ int mask = 1 << (count-1);
|
|
+ for(x = 0; x < count; x++){
|
|
+ gpiod_set_value(ts->gpiod_mosi,(data & mask) > 0);
|
|
+ data <<= 1;
|
|
+ gpiod_set_value(ts->gpiod_int, 0);
|
|
+ udelay(uDELAY);
|
|
+ gpiod_set_value(ts->gpiod_int, 1);
|
|
+ udelay(uDELAY);
|
|
+ }
|
|
+ gpiod_set_value(ts->gpiod_mosi,0);
|
|
+}
|
|
+
|
|
+static void write(struct pimhyp3_ts_data *ts, uint16_t command)
|
|
+{
|
|
+ gpiod_set_value(ts->gpiod_cs,0);
|
|
+ send_bits(ts, command, 9);
|
|
+ gpiod_set_value(ts->gpiod_cs,1);
|
|
+}
|
|
+
|
|
+static void lcd_init(struct pimhyp3_ts_data *ts)
|
|
+{
|
|
+ int count;
|
|
+ int x;
|
|
+ //setup_pins
|
|
+
|
|
+ gpiod_direction_output(ts->gpiod_cs,1);
|
|
+ gpiod_direction_output(ts->gpiod_int,1); //CLK = output
|
|
+ gpiod_direction_output(ts->gpiod_mosi,0);
|
|
+
|
|
+ //setup_lcd
|
|
+ count = sizeof(commands) / sizeof(int32_t);
|
|
+ for(x = 0; x < count; x++){
|
|
+ int32_t command = commands[x];
|
|
+ if(command == -1){
|
|
+ mdelay(mWAIT);
|
|
+ continue;
|
|
+ }
|
|
+ write(ts,(uint16_t)command);
|
|
+ }
|
|
+
|
|
+ //cleanup_pins
|
|
+ // Return the touch interrupt pin to a usable state
|
|
+ gpiod_direction_input(ts->gpiod_int);
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * pimhyp3_get_gpio_config - Get GPIO config from ACPI/DT
|
|
+ *
|
|
+ * @ts: pimhyp3_ts_data pointer
|
|
+ */
|
|
+static int pimhyp3_get_gpio_config(struct pimhyp3_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+ struct device *dev;
|
|
+ struct gpio_desc *gpiod;
|
|
+ u32 sizeX, sizeY, refreshRate;
|
|
+
|
|
+ if (!ts->client)
|
|
+ return -EINVAL;
|
|
+ dev = &ts->client->dev;
|
|
+
|
|
+ /* Get the interrupt GPIO pin description */
|
|
+ gpiod = devm_gpiod_get(dev, PIMHYP3_GPIO_INT_NAME, GPIOD_IN);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP3_GPIO_INT_NAME, error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ /* Ensure the correct direction is set because this pin is also used to
|
|
+ * program the LCD controller
|
|
+ */
|
|
+ ts->gpiod_int = gpiod;
|
|
+ gpiod_direction_input(ts->gpiod_int);
|
|
+
|
|
+ /* Get the MOSI GPIO pin description */
|
|
+ gpiod = devm_gpiod_get(dev, PIMHYP3_GPIO_MOSI_NAME, GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP3_GPIO_MOSI_NAME, error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ ts->gpiod_mosi = gpiod;
|
|
+
|
|
+ /* Get the CS GPIO pin description */
|
|
+ gpiod = devm_gpiod_get(dev, PIMHYP3_GPIO_CS_NAME, GPIOD_OUT_HIGH);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP3_GPIO_CS_NAME, error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ ts->gpiod_cs = gpiod;
|
|
+
|
|
+
|
|
+ ts->id = 0x1001;
|
|
+ ts->version=0x0101;
|
|
+
|
|
+ ts->last_numTouches=0;
|
|
+ ts->max_touch_num=2;
|
|
+ ts->abs_x_max = PIMHYP3_MAX_WIDTH;
|
|
+ ts->abs_y_max = PIMHYP3_MAX_HEIGHT;
|
|
+ ts->use_poll = false;
|
|
+ ts->int_trigger_type = PIMHYP3_INT_TRIGGER;
|
|
+
|
|
+ // Read DT configuration parameters
|
|
+ ts->X2Y = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-x2y");
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-x2y %u", ts->X2Y);
|
|
+
|
|
+ ts->use_poll = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-poll");
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-poll %u", ts->use_poll);
|
|
+
|
|
+
|
|
+ if(device_property_read_u32(&ts->client->dev, "touchscreen-refresh-rate", &refreshRate))
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-refresh-rate not found");
|
|
+ ts->requestedRefreshRate = 17;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-refresh-rate found %u", refreshRate);
|
|
+ ts->requestedRefreshRate = (u8)refreshRate;
|
|
+ }
|
|
+
|
|
+ if(device_property_read_u32(&ts->client->dev, "touchscreen-size-x", &sizeX))
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-x not found. Using default of %u",ts->abs_x_max);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ts->abs_x_max = (u16)sizeX;
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-x found %u", ts->abs_x_max);
|
|
+ }
|
|
+
|
|
+ if(device_property_read_u32(&ts->client->dev, "touchscreen-size-y", &sizeY))
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-y not found. Using default of %u",ts->abs_y_max);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ts->abs_y_max = (u16)sizeY;
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-y found %u", ts->abs_y_max);
|
|
+ }
|
|
+
|
|
+ dev_dbg(&ts->client->dev, "requested size (%u, %u)", ts->abs_x_max, ts->abs_y_max);
|
|
+
|
|
+ ts->swapped_x_y = device_property_read_bool(&ts->client->dev, "touchscreen-swapped-x-y");
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-swapped-x-y %u", ts->swapped_x_y);
|
|
+
|
|
+ ts->inverted_x = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-inverted-x");
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-inverted-x %u", ts->inverted_x);
|
|
+
|
|
+ ts->inverted_y = device_property_read_bool(&ts->client->dev, "touchscreen-inverted-y");
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-inverted-y %u", ts->inverted_y);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp3_request_input_dev - Allocate, populate and register the input device
|
|
+ * @ts: our pimhyp3_ts_data pointer
|
|
+ * Must be called during probe
|
|
+ */
|
|
+static int pimhyp3_request_input_dev(struct pimhyp3_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ ts->input_dev = devm_input_allocate_device(&ts->client->dev);
|
|
+ if (!ts->input_dev) {
|
|
+ dev_err(&ts->client->dev, "Failed to allocate input device.");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /* Set up device parameters */
|
|
+ input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X,
|
|
+ 0, ts->abs_x_max, 0, 0);
|
|
+ input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y,
|
|
+ 0, ts->abs_y_max, 0, 0);
|
|
+
|
|
+ input_mt_init_slots(ts->input_dev, ts->max_touch_num,
|
|
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
|
|
+
|
|
+ ts->input_dev->name = "Cypress Capacitive TouchScreen";
|
|
+ ts->input_dev->phys = "input/ts";
|
|
+ ts->input_dev->id.bustype = BUS_I2C;
|
|
+ ts->input_dev->id.vendor = 0x0416;
|
|
+ ts->input_dev->id.product = ts->id;
|
|
+ ts->input_dev->id.version = ts->version;
|
|
+
|
|
+ error = input_register_device(ts->input_dev);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev,
|
|
+ "Failed to register input device: %d", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int pimhyp3_ts_probe(struct i2c_client *client,
|
|
+ const struct i2c_device_id *id)
|
|
+{
|
|
+ struct pimhyp3_ts_data *ts;
|
|
+ int error;
|
|
+ u8 buf;
|
|
+
|
|
+ dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr);
|
|
+
|
|
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
+ dev_err(&client->dev, "I2C check functionality failed.\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
|
|
+ if (!ts)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ts->client = client;
|
|
+ i2c_set_clientdata(client, ts);
|
|
+
|
|
+
|
|
+ error = pimhyp3_i2c_test(client);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev, "I2C communication failure: %d\n", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ //Device is detected!
|
|
+ pr_info("Detected: pimhyp3");
|
|
+ if (device_property_read_bool(&ts->client->dev,"touchscreen-check"))
|
|
+ {
|
|
+ //We have notified the device is fitted, but we don't want to use it
|
|
+ pr_info("Device check only. Exiting pimhyp3");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ pimhyp3_get_gpio_config(ts);
|
|
+
|
|
+ if (ts->use_poll)
|
|
+ buf = 0b00001110; //En_Int, Int_POLL, INT_MODE1, INT_MODE2
|
|
+ else
|
|
+ buf = 0b00001100; //En_Int, Int_POLL, INT_MODE1, INT_MODE2
|
|
+ //For polling, use 00001110
|
|
+ //for interrupt use 00001100
|
|
+ pimhyp3_i2c_write(client, PIMHYP3_INT_SETTINGS, &buf,1);
|
|
+
|
|
+ lcd_init(ts);
|
|
+
|
|
+ pimhyp3_request_input_dev(ts);
|
|
+
|
|
+ dev_dbg(&client->dev, "Cypress cy8c20466 driver installed\n");
|
|
+
|
|
+ if (ts->use_poll)
|
|
+ {
|
|
+ /* create thread that polls the touch events */
|
|
+ dev_dbg( &ts->client->dev, "Setting up POLL thread");
|
|
+ ts->thread = kthread_run(pimhyp3_thread, ts, "cy82466?");
|
|
+ if (ts->thread == NULL) {
|
|
+ dev_err(&client->dev, "Failed to create kernel thread");
|
|
+ error = -ENOMEM;
|
|
+ return error;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* Enable Interrupt to handle touch events */
|
|
+ dev_dbg( &ts->client->dev, "Setting up IRQ");
|
|
+ ts->irq_flags = pimhyp3_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT;
|
|
+ error = pimhyp3_request_irq(ts);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev, "request IRQ failed: %d\n", error);
|
|
+ return error;
|
|
+ }
|
|
+ }
|
|
+ pr_info("Using: pimhyp3");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/////////////////////////////// ACPI ///////////////////////////////////////
|
|
+
|
|
+
|
|
+/////////////////////////////// DEVICE DRIVER DATA /////////////////////////
|
|
+
|
|
+static const struct i2c_device_id pimhyp3_ts_id[] = {
|
|
+ { "cy8c20466:00", 0 },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(i2c, pimhyp3_ts_id);
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+static const struct of_device_id pimhyp3_of_match[] = {
|
|
+ { .compatible = "pimoroni,cy8c20466" },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, pimhyp3_of_match);
|
|
+#endif
|
|
+
|
|
+static struct i2c_driver pimhyp3_ts_driver = {
|
|
+ .probe = pimhyp3_ts_probe,
|
|
+ .remove = pimhyp3_ts_remove,
|
|
+ .id_table = pimhyp3_ts_id,
|
|
+ .driver = {
|
|
+ .name = "pimhyp3-TS",
|
|
+ .of_match_table = of_match_ptr(pimhyp3_of_match),
|
|
+ },
|
|
+};
|
|
+module_i2c_driver(pimhyp3_ts_driver);
|
|
+
|
|
+MODULE_AUTHOR("procount <kevin.procount@googlemail.com>");
|
|
+MODULE_DESCRIPTION("Pimoroni Hyperpixel 3.5 touchscreen driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
+
|
|
diff --git a/drivers/input/touchscreen/pimhyp4.c b/drivers/input/touchscreen/pimhyp4.c
|
|
new file mode 100644
|
|
index 0000000..12de17c
|
|
--- /dev/null
|
|
+++ b/drivers/input/touchscreen/pimhyp4.c
|
|
@@ -0,0 +1,1263 @@
|
|
+/*
|
|
+ * Driver for Pimoroni Hyperpixel 4.0" Touchscreen LCD
|
|
+ * Includes driver for Goodix Touchscreens
|
|
+ * Modified for writing X2Y, width and height for screens without rst/int pins
|
|
+ *
|
|
+ * Adds commands to program the LCD, from Pimoroni' hyperpixel4-init.c
|
|
+ *
|
|
+ * Copyright (c) 2014 Red Hat Inc.
|
|
+ * Copyright (c) 2015 K. Merker <merker@debian.org>
|
|
+ * Copyright (c) 2019 ProCount
|
|
+ *
|
|
+ * This code is based on gt9xx.c authored by andrew@goodix.com:
|
|
+ * and goodix.c
|
|
+ * 2010 - 2012 Goodix Technology.
|
|
+ */
|
|
+
|
|
+//#define DEBUG
|
|
+
|
|
+/*
|
|
+ * 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; version 2 of the License.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/dmi.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/input/mt.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/of.h>
|
|
+#include <asm/unaligned.h>
|
|
+
|
|
+struct pimhyp4_ts_data {
|
|
+ struct i2c_client *client;
|
|
+ struct input_dev *input_dev;
|
|
+ int abs_x_max;
|
|
+ int abs_y_max;
|
|
+ bool swapped_x_y;
|
|
+ bool inverted_x;
|
|
+ bool inverted_y;
|
|
+ unsigned int max_touch_num;
|
|
+ unsigned int int_trigger_type;
|
|
+ int cfg_len;
|
|
+ struct gpio_desc *gpiod_int;
|
|
+ struct gpio_desc *gpiod_rst;
|
|
+ struct gpio_desc *gpiod_mosi;
|
|
+ struct gpio_desc *gpiod_cs;
|
|
+ u16 id;
|
|
+ u16 version;
|
|
+ const char *cfg_name;
|
|
+ struct completion firmware_loading_complete;
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ bool X2Y;
|
|
+ bool requestedX2Y;
|
|
+ u16 requestedXSize;
|
|
+ u16 requestedYSize;
|
|
+ u8 requestedRefreshRate;
|
|
+};
|
|
+
|
|
+#define uDELAY 100 // clock pulse time in microseconds
|
|
+#define mWAIT 120 // wait time in milliseconds
|
|
+
|
|
+
|
|
+#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
|
|
+
|
|
+#define PIMHYP4_GPIO_INT_NAME "irq"
|
|
+#define PIMHYP4_GPIO_RST_NAME "reset"
|
|
+#define PIMHYP4_GPIO_MOSI_NAME "mosi"
|
|
+#define PIMHYP4_GPIO_CS_NAME "cs"
|
|
+
|
|
+#define PIMHYP4_MAX_HEIGHT 4096
|
|
+#define PIMHYP4_MAX_WIDTH 4096
|
|
+#define PIMHYP4_INT_TRIGGER 1
|
|
+#define PIMHYP4_CONTACT_SIZE 8
|
|
+#define PIMHYP4_MAX_CONTACTS 10
|
|
+
|
|
+#define PIMHYP4_CONFIG_MAX_LENGTH 240
|
|
+#define PIMHYP4_CONFIG_911_LENGTH 186
|
|
+#define PIMHYP4_CONFIG_967_LENGTH 228
|
|
+
|
|
+/* Register defines */
|
|
+#define PIMHYP4_REG_COMMAND 0x8040
|
|
+#define PIMHYP4_CMD_SCREEN_OFF 0x05
|
|
+
|
|
+#define PIMHYP4_READ_COOR_ADDR 0x814E
|
|
+#define PIMHYP4_REG_CONFIG_DATA 0x8047
|
|
+#define PIMHYP4_REG_ID 0x8140
|
|
+
|
|
+#define PIMHYP4_BUFFER_STATUS_READY BIT(7)
|
|
+#define PIMHYP4_BUFFER_STATUS_TIMEOUT 20
|
|
+
|
|
+#define RESOLUTION_LOC 1
|
|
+#define RESOLUTION_LOC_X 1
|
|
+#define RESOLUTION_LOC_Y 3
|
|
+#define MAX_CONTACTS_LOC 5
|
|
+#define TRIGGER_LOC 6
|
|
+
|
|
+static const unsigned long pimhyp4_irq_flags[] = {
|
|
+ IRQ_TYPE_EDGE_RISING,
|
|
+ IRQ_TYPE_EDGE_FALLING,
|
|
+ IRQ_TYPE_LEVEL_LOW,
|
|
+ IRQ_TYPE_LEVEL_HIGH,
|
|
+};
|
|
+
|
|
+static int checkFirmware(struct pimhyp4_ts_data *ts, u8 *config);
|
|
+
|
|
+
|
|
+/**
|
|
+ * pimhyp4_i2c_read - read data from a register of the i2c slave device.
|
|
+ *
|
|
+ * @client: i2c device.
|
|
+ * @reg: the register to read from.
|
|
+ * @buf: raw write data buffer.
|
|
+ * @len: length of the buffer to write
|
|
+ */
|
|
+static int pimhyp4_i2c_read(struct i2c_client *client,
|
|
+ u16 reg, u8 *buf, int len)
|
|
+{
|
|
+ struct i2c_msg msgs[2];
|
|
+ u16 wbuf = cpu_to_be16(reg);
|
|
+ int ret;
|
|
+
|
|
+ msgs[0].flags = 0;
|
|
+ msgs[0].addr = client->addr;
|
|
+ msgs[0].len = 2;
|
|
+ msgs[0].buf = (u8 *)&wbuf;
|
|
+
|
|
+ msgs[1].flags = I2C_M_RD;
|
|
+ msgs[1].addr = client->addr;
|
|
+ msgs[1].len = len;
|
|
+ msgs[1].buf = buf;
|
|
+
|
|
+ ret = i2c_transfer(client->adapter, msgs, 2);
|
|
+ return ret < 0 ? ret : (ret != ARRAY_SIZE(msgs) ? -EIO : 0);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_i2c_write - write data to a register of the i2c slave device.
|
|
+ *
|
|
+ * @client: i2c device.
|
|
+ * @reg: the register to write to.
|
|
+ * @buf: raw data buffer to write.
|
|
+ * @len: length of the buffer to write
|
|
+ */
|
|
+static int pimhyp4_i2c_write(struct i2c_client *client, u16 reg, const u8 *buf,
|
|
+ unsigned len)
|
|
+{
|
|
+ u8 *addr_buf;
|
|
+ struct i2c_msg msg;
|
|
+ int ret;
|
|
+
|
|
+ addr_buf = kmalloc(len + 2, GFP_KERNEL);
|
|
+ if (!addr_buf)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ addr_buf[0] = reg >> 8;
|
|
+ addr_buf[1] = reg & 0xFF;
|
|
+ memcpy(&addr_buf[2], buf, len);
|
|
+
|
|
+ msg.flags = 0;
|
|
+ msg.addr = client->addr;
|
|
+ msg.buf = addr_buf;
|
|
+ msg.len = len + 2;
|
|
+
|
|
+ ret = i2c_transfer(client->adapter, &msg, 1);
|
|
+ kfree(addr_buf);
|
|
+ return ret < 0 ? ret : (ret != 1 ? -EIO : 0);
|
|
+}
|
|
+
|
|
+static int pimhyp4_i2c_write_u8(struct i2c_client *client, u16 reg, u8 value)
|
|
+{
|
|
+ return pimhyp4_i2c_write(client, reg, &value, sizeof(value));
|
|
+}
|
|
+
|
|
+
|
|
+static int pimhyp4_get_cfg_len(u16 id)
|
|
+{
|
|
+ switch (id) {
|
|
+ case 911:
|
|
+ case 9271:
|
|
+ case 9110:
|
|
+ case 927:
|
|
+ case 928:
|
|
+ return PIMHYP4_CONFIG_911_LENGTH;
|
|
+
|
|
+ case 912:
|
|
+ case 967:
|
|
+ return PIMHYP4_CONFIG_967_LENGTH;
|
|
+
|
|
+ default:
|
|
+ return PIMHYP4_CONFIG_MAX_LENGTH;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int pimhyp4_ts_read_input_report(struct pimhyp4_ts_data *ts, u8 *data)
|
|
+{
|
|
+ unsigned long max_timeout;
|
|
+ int touch_num;
|
|
+ int error;
|
|
+
|
|
+ /*
|
|
+ * The 'buffer status' bit, which indicates that the data is valid, is
|
|
+ * not set as soon as the interrupt is raised, but slightly after.
|
|
+ * This takes around 10 ms to happen, so we poll for 20 ms.
|
|
+ */
|
|
+ max_timeout = jiffies + msecs_to_jiffies(PIMHYP4_BUFFER_STATUS_TIMEOUT);
|
|
+ do {
|
|
+ error = pimhyp4_i2c_read(ts->client, PIMHYP4_READ_COOR_ADDR,
|
|
+ data, PIMHYP4_CONTACT_SIZE + 1);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev, "I2C transfer error: %d\n",
|
|
+ error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ if (data[0] & PIMHYP4_BUFFER_STATUS_READY) {
|
|
+ touch_num = data[0] & 0x0f;
|
|
+ if (touch_num > ts->max_touch_num)
|
|
+ return -EPROTO;
|
|
+
|
|
+ if (touch_num > 1) {
|
|
+ data += 1 + PIMHYP4_CONTACT_SIZE;
|
|
+ error = pimhyp4_i2c_read(ts->client,
|
|
+ PIMHYP4_READ_COOR_ADDR +
|
|
+ 1 + PIMHYP4_CONTACT_SIZE,
|
|
+ data,
|
|
+ PIMHYP4_CONTACT_SIZE *
|
|
+ (touch_num - 1));
|
|
+ if (error)
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ return touch_num;
|
|
+ }
|
|
+
|
|
+ usleep_range(1000, 2000); /* Poll every 1 - 2 ms */
|
|
+ } while (time_before(jiffies, max_timeout));
|
|
+
|
|
+ /*
|
|
+ * The Goodix panel will send spurious interrupts after a
|
|
+ * 'finger up' event, which will always cause a timeout.
|
|
+ */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void pimhyp4_ts_report_touch(struct pimhyp4_ts_data *ts, u8 *coor_data)
|
|
+{
|
|
+ int id = coor_data[0] & 0x0F;
|
|
+ int input_x = get_unaligned_le16(&coor_data[1]);
|
|
+ int input_y = get_unaligned_le16(&coor_data[3]);
|
|
+ int input_w = get_unaligned_le16(&coor_data[5]);
|
|
+
|
|
+ if(ts->X2Y)
|
|
+ {
|
|
+ /* Inversions have to happen before axis swapping */
|
|
+ if (ts->inverted_x)
|
|
+ input_x = ts->abs_x_max - input_x;
|
|
+ if (ts->inverted_y)
|
|
+ input_y = ts->abs_y_max - input_y;
|
|
+ if (ts->swapped_x_y)
|
|
+ swap(input_x, input_y);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* Inversions have to happen after axis swapping */
|
|
+ if (ts->swapped_x_y)
|
|
+ swap(input_x, input_y);
|
|
+ if (ts->inverted_x)
|
|
+ input_x = ts->abs_x_max - input_x;
|
|
+ if (ts->inverted_y)
|
|
+ input_y = ts->abs_y_max - input_y;
|
|
+ }
|
|
+
|
|
+ input_mt_slot(ts->input_dev, id);
|
|
+ input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);
|
|
+ input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w);
|
|
+
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_process_events - Process incoming events
|
|
+ *
|
|
+ * @ts: our pimhyp4_ts_data pointer
|
|
+ *
|
|
+ * Called when the IRQ is triggered. Read the current device state, and push
|
|
+ * the input events to the user space.
|
|
+ */
|
|
+static void pimhyp4_process_events(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ u8 point_data[1 + PIMHYP4_CONTACT_SIZE * PIMHYP4_MAX_CONTACTS];
|
|
+ int touch_num;
|
|
+ int i;
|
|
+
|
|
+ touch_num = pimhyp4_ts_read_input_report(ts, point_data);
|
|
+ if (touch_num < 0)
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * Bit 4 of the first byte reports the status of the capacitive
|
|
+ * Windows/Home button.
|
|
+ */
|
|
+ input_report_key(ts->input_dev, KEY_LEFTMETA, point_data[0] & BIT(4));
|
|
+
|
|
+ for (i = 0; i < touch_num; i++)
|
|
+ pimhyp4_ts_report_touch(ts,
|
|
+ &point_data[1 + PIMHYP4_CONTACT_SIZE * i]);
|
|
+
|
|
+ input_mt_sync_frame(ts->input_dev);
|
|
+ input_sync(ts->input_dev);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_ts_irq_handler - The IRQ handler
|
|
+ *
|
|
+ * @irq: interrupt number.
|
|
+ * @dev_id: private data pointer.
|
|
+ */
|
|
+static irqreturn_t pimhyp4_ts_irq_handler(int irq, void *dev_id)
|
|
+{
|
|
+ struct pimhyp4_ts_data *ts = dev_id;
|
|
+
|
|
+
|
|
+ pimhyp4_process_events(ts);
|
|
+
|
|
+ if (pimhyp4_i2c_write_u8(ts->client, PIMHYP4_READ_COOR_ADDR, 0) < 0)
|
|
+ dev_err(&ts->client->dev, "I2C write end_cmd error\n");
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void pimhyp4_free_irq(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ devm_free_irq(&ts->client->dev, ts->client->irq, ts);
|
|
+}
|
|
+
|
|
+static int pimhyp4_request_irq(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ return devm_request_threaded_irq(&ts->client->dev, ts->client->irq,
|
|
+ NULL, pimhyp4_ts_irq_handler,
|
|
+ ts->irq_flags, ts->client->name, ts);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_check_cfg - Checks if config fw is valid
|
|
+ *
|
|
+ * @ts: pimhyp4_ts_data pointer
|
|
+ * @cfg: firmware config data
|
|
+ */
|
|
+static int pimhyp4_check_cfg(struct pimhyp4_ts_data *ts,
|
|
+ const struct firmware *cfg)
|
|
+{
|
|
+ int i, raw_cfg_len;
|
|
+ u8 check_sum = 0;
|
|
+
|
|
+ if (cfg->size > PIMHYP4_CONFIG_MAX_LENGTH) {
|
|
+ dev_err(&ts->client->dev,
|
|
+ "The length of the config fw is not correct");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ raw_cfg_len = cfg->size - 2;
|
|
+ for (i = 0; i < raw_cfg_len; i++)
|
|
+ check_sum += cfg->data[i];
|
|
+ check_sum = (~check_sum) + 1;
|
|
+ if (check_sum != cfg->data[raw_cfg_len]) {
|
|
+ dev_err(&ts->client->dev,
|
|
+ "The checksum of the config fw is not correct");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (cfg->data[raw_cfg_len + 1] != 1) {
|
|
+ dev_err(&ts->client->dev,
|
|
+ "Config fw must have Config_Fresh register set");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_send_cfg - Write fw config to device
|
|
+ *
|
|
+ * @ts: pimhyp4_ts_data pointer
|
|
+ * @cfg: config firmware to write to device
|
|
+ */
|
|
+static int pimhyp4_send_cfg(struct pimhyp4_ts_data *ts,
|
|
+ const struct firmware *cfg)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ error = pimhyp4_check_cfg(ts, cfg);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ error = pimhyp4_i2c_write(ts->client, PIMHYP4_REG_CONFIG_DATA, cfg->data,
|
|
+ cfg->size);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev, "Failed to write config data: %d",
|
|
+ error);
|
|
+ return error;
|
|
+ }
|
|
+ dev_dbg(&ts->client->dev, "Config sent successfully.");
|
|
+
|
|
+ /* Let the firmware reconfigure itself, so sleep for 10ms */
|
|
+ usleep_range(10000, 11000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pimhyp4_int_sync(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ error = gpiod_direction_output(ts->gpiod_int, 0);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ msleep(50); /* T5: 50ms */
|
|
+
|
|
+ error = gpiod_direction_input(ts->gpiod_int);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/////////////////////////////// INITIALISATION /////////////////////////////
|
|
+
|
|
+
|
|
+static int32_t commands[] = {
|
|
+ 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x101, 0x008, 0x110,
|
|
+ 0x021, 0x109, 0x030, 0x102, 0x031, 0x100, 0x040, 0x110,
|
|
+ 0x041, 0x155, 0x042, 0x102, 0x043, 0x109, 0x044, 0x107,
|
|
+ 0x050, 0x178, 0x051, 0x178, 0x052, 0x100, 0x053, 0x16d,
|
|
+ 0x060, 0x107, 0x061, 0x100, 0x062, 0x108, 0x063, 0x100,
|
|
+ 0x0a0, 0x100, 0x0a1, 0x107, 0x0a2, 0x10c, 0x0a3, 0x10b,
|
|
+ 0x0a4, 0x103, 0x0a5, 0x107, 0x0a6, 0x106, 0x0a7, 0x104,
|
|
+ 0x0a8, 0x108, 0x0a9, 0x10c, 0x0aa, 0x113, 0x0ab, 0x106,
|
|
+ 0x0ac, 0x10d, 0x0ad, 0x119, 0x0ae, 0x110, 0x0af, 0x100,
|
|
+ 0x0c0, 0x100, 0x0c1, 0x107, 0x0c2, 0x10c, 0x0c3, 0x10b,
|
|
+ 0x0c4, 0x103, 0x0c5, 0x107, 0x0c6, 0x107, 0x0c7, 0x104,
|
|
+ 0x0c8, 0x108, 0x0c9, 0x10c, 0x0ca, 0x113, 0x0cb, 0x106,
|
|
+ 0x0cc, 0x10d, 0x0cd, 0x118, 0x0ce, 0x110, 0x0cf, 0x100,
|
|
+ 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x106, 0x000, 0x120,
|
|
+ 0x001, 0x10a, 0x002, 0x100, 0x003, 0x100, 0x004, 0x101,
|
|
+ 0x005, 0x101, 0x006, 0x198, 0x007, 0x106, 0x008, 0x101,
|
|
+ 0x009, 0x180, 0x00a, 0x100, 0x00b, 0x100, 0x00c, 0x101,
|
|
+ 0x00d, 0x101, 0x00e, 0x100, 0x00f, 0x100, 0x010, 0x1f0,
|
|
+ 0x011, 0x1f4, 0x012, 0x101, 0x013, 0x100, 0x014, 0x100,
|
|
+ 0x015, 0x1c0, 0x016, 0x108, 0x017, 0x100, 0x018, 0x100,
|
|
+ 0x019, 0x100, 0x01a, 0x100, 0x01b, 0x100, 0x01c, 0x100,
|
|
+ 0x01d, 0x100, 0x020, 0x101, 0x021, 0x123, 0x022, 0x145,
|
|
+ 0x023, 0x167, 0x024, 0x101, 0x025, 0x123, 0x026, 0x145,
|
|
+ 0x027, 0x167, 0x030, 0x111, 0x031, 0x111, 0x032, 0x100,
|
|
+ 0x033, 0x1ee, 0x034, 0x1ff, 0x035, 0x1bb, 0x036, 0x1aa,
|
|
+ 0x037, 0x1dd, 0x038, 0x1cc, 0x039, 0x166, 0x03a, 0x177,
|
|
+ 0x03b, 0x122, 0x03c, 0x122, 0x03d, 0x122, 0x03e, 0x122,
|
|
+ 0x03f, 0x122, 0x040, 0x122, 0x052, 0x110, 0x053, 0x110,
|
|
+ 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x107, 0x018, 0x11d,
|
|
+ 0x017, 0x122, 0x002, 0x177, 0x026, 0x1b2, 0x0e1, 0x179,
|
|
+ 0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x100, 0x03a, 0x160,
|
|
+ 0x035, 0x100, 0x011, 0x100, -1, 0x029, 0x100, -1
|
|
+};
|
|
+
|
|
+
|
|
+static void send_bits(struct pimhyp4_ts_data *ts, uint16_t data, uint16_t count)
|
|
+{
|
|
+ int x;
|
|
+ int mask = 1 << (count-1);
|
|
+ for(x = 0; x < count; x++){
|
|
+ gpiod_set_value(ts->gpiod_mosi,(data & mask) > 0);
|
|
+ //bcm2835_gpio_write(MOSI, (data & mask) > 0);
|
|
+ data <<= 1;
|
|
+
|
|
+ gpiod_set_value(ts->gpiod_int, 0);
|
|
+ //bcm2835_gpio_write(CLK, LOW);
|
|
+
|
|
+ udelay(uDELAY);
|
|
+ //bcm2835_delayMicroseconds(DELAY);
|
|
+
|
|
+ gpiod_set_value(ts->gpiod_int, 1);
|
|
+ //bcm2835_gpio_write(CLK, HIGH);
|
|
+
|
|
+ udelay(uDELAY);
|
|
+ //bcm2835_delayMicroseconds(DELAY);
|
|
+ }
|
|
+ gpiod_set_value(ts->gpiod_mosi,0);
|
|
+ //bcm2835_gpio_write(MOSI, LOW);
|
|
+}
|
|
+
|
|
+static void write(struct pimhyp4_ts_data *ts, uint16_t command)
|
|
+{
|
|
+ gpiod_set_value(ts->gpiod_cs,0);
|
|
+ //bcm2835_gpio_write(CS, LOW);
|
|
+
|
|
+ send_bits(ts, command, 9);
|
|
+
|
|
+ gpiod_set_value(ts->gpiod_cs,1);
|
|
+ //bcm2835_gpio_write(CS, HIGH);
|
|
+}
|
|
+
|
|
+static void lcd_init(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+#if 1
|
|
+ int count;
|
|
+ int x;
|
|
+ //setup_pins
|
|
+
|
|
+ gpiod_direction_output(ts->gpiod_cs,1);
|
|
+ gpiod_direction_output(ts->gpiod_int,1); //CLK = output
|
|
+ gpiod_direction_output(ts->gpiod_mosi,0);
|
|
+
|
|
+ //setup_lcd
|
|
+ count = sizeof(commands) / sizeof(int32_t);
|
|
+ for(x = 0; x < count; x++){
|
|
+ int32_t command = commands[x];
|
|
+ if(command == -1){
|
|
+ mdelay(mWAIT);
|
|
+ continue;
|
|
+ }
|
|
+ write(ts,(uint16_t)command);
|
|
+ }
|
|
+
|
|
+ //cleanup_pins
|
|
+ // Return the touch interrupt pin to a usable state
|
|
+ gpiod_direction_input(ts->gpiod_int);
|
|
+ //bcm2835_gpio_set_pud(CLK, BCM2835_GPIO_PUD_OFF);
|
|
+#endif
|
|
+}
|
|
+
|
|
+////////////////////////////////////////////////////
|
|
+
|
|
+/**
|
|
+ * pimhyp4_reset - Reset device during power on
|
|
+ *
|
|
+ * @ts: pimhyp4_ts_data pointer
|
|
+ */
|
|
+static int pimhyp4_reset(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ /* begin select I2C slave addr */
|
|
+ error = gpiod_direction_output(ts->gpiod_rst, 0);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ msleep(20); /* T2: > 10ms */
|
|
+
|
|
+ /* HIGH: 0x28/0x29, LOW: 0xBA/0xBB */
|
|
+ error = gpiod_direction_output(ts->gpiod_int, ts->client->addr == 0x14);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ usleep_range(100, 2000); /* T3: > 100us */
|
|
+
|
|
+ error = gpiod_direction_output(ts->gpiod_rst, 1);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ usleep_range(6000, 10000); /* T4: > 5ms */
|
|
+
|
|
+ /* end select I2C slave addr */
|
|
+ error = gpiod_direction_input(ts->gpiod_rst);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ error = pimhyp4_int_sync(ts);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_get_gpio_config - Get GPIO config from ACPI/DT
|
|
+ *
|
|
+ * @ts: pimhyp4_ts_data pointer
|
|
+ */
|
|
+static int pimhyp4_get_gpio_config(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+ struct device *dev;
|
|
+ struct gpio_desc *gpiod;
|
|
+
|
|
+ if (!ts->client)
|
|
+ return -EINVAL;
|
|
+ dev = &ts->client->dev;
|
|
+
|
|
+ /* Get the interrupt GPIO pin number */
|
|
+ gpiod = devm_gpiod_get_optional(dev, PIMHYP4_GPIO_INT_NAME, GPIOD_IN);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP4_GPIO_INT_NAME, error);
|
|
+ //return error;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ts->gpiod_int = gpiod;
|
|
+ /* Ensure the correct direction is set because this pin is also used to
|
|
+ * program the LCD controller
|
|
+ */
|
|
+ gpiod_direction_input(ts->gpiod_int);
|
|
+ }
|
|
+
|
|
+ /* Get the reset line GPIO pin number */
|
|
+ gpiod = devm_gpiod_get_optional(dev, PIMHYP4_GPIO_RST_NAME, GPIOD_IN);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP4_GPIO_RST_NAME, error);
|
|
+ //return error;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ts->gpiod_rst = gpiod;
|
|
+ }
|
|
+
|
|
+
|
|
+ /* Get the MOSI GPIO pin description */
|
|
+ gpiod = devm_gpiod_get(dev, PIMHYP4_GPIO_MOSI_NAME, GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP4_GPIO_MOSI_NAME, error);
|
|
+ //return error;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ts->gpiod_mosi = gpiod;
|
|
+ }
|
|
+
|
|
+ /* Get the CS GPIO pin description */
|
|
+ gpiod = devm_gpiod_get(dev, PIMHYP4_GPIO_CS_NAME, GPIOD_OUT_HIGH);
|
|
+ if (IS_ERR(gpiod)) {
|
|
+ error = PTR_ERR(gpiod);
|
|
+ if (error != -EPROBE_DEFER)
|
|
+ dev_dbg(dev, "Failed to get %s GPIO: %d\n",
|
|
+ PIMHYP4_GPIO_CS_NAME, error);
|
|
+ //return error;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ts->gpiod_cs = gpiod;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_read_config - Read the embedded configuration of the panel
|
|
+ *
|
|
+ * @ts: our pimhyp4_ts_data pointer
|
|
+ *
|
|
+ * Must be called during probe
|
|
+ */
|
|
+static void pimhyp4_read_config(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ u8 config[PIMHYP4_CONFIG_MAX_LENGTH];
|
|
+ int error;
|
|
+
|
|
+ error = pimhyp4_i2c_read(ts->client, PIMHYP4_REG_CONFIG_DATA,
|
|
+ config, ts->cfg_len);
|
|
+ if (error) {
|
|
+ dev_warn(&ts->client->dev,
|
|
+ "Error reading config (%d), using defaults\n",
|
|
+ error);
|
|
+ ts->abs_x_max = PIMHYP4_MAX_WIDTH;
|
|
+ ts->abs_y_max = PIMHYP4_MAX_HEIGHT;
|
|
+ if (ts->swapped_x_y)
|
|
+ swap(ts->abs_x_max, ts->abs_y_max);
|
|
+ ts->int_trigger_type = PIMHYP4_INT_TRIGGER;
|
|
+ ts->max_touch_num = PIMHYP4_MAX_CONTACTS;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ dev_dbg(&ts->client->dev, "Current size = (%u, %u), X2Y = %u, refresh = %u", ts->requestedXSize, ts->requestedYSize, ts->X2Y, ts->requestedRefreshRate);
|
|
+
|
|
+ checkFirmware(ts, config);
|
|
+
|
|
+ ts->abs_x_max = get_unaligned_le16(&config[RESOLUTION_LOC]);
|
|
+ ts->abs_y_max = get_unaligned_le16(&config[RESOLUTION_LOC + 2]);
|
|
+ ts->X2Y = CHECK_BIT(config[0x804d - PIMHYP4_REG_CONFIG_DATA], 3);
|
|
+
|
|
+ dev_dbg(&ts->client->dev, "Current size = (%u, %u), X2Y = %u, refresh = %u", ts->requestedXSize, ts->requestedYSize, ts->X2Y, ts->requestedRefreshRate);
|
|
+
|
|
+ if (ts->swapped_x_y)
|
|
+ swap(ts->abs_x_max, ts->abs_y_max);
|
|
+ ts->int_trigger_type = config[TRIGGER_LOC] & 0x03;
|
|
+ ts->max_touch_num = config[MAX_CONTACTS_LOC] & 0x0f;
|
|
+ if (!ts->abs_x_max || !ts->abs_y_max || !ts->max_touch_num) {
|
|
+ dev_err(&ts->client->dev,
|
|
+ "Invalid config, using defaults\n");
|
|
+ ts->abs_x_max = PIMHYP4_MAX_WIDTH;
|
|
+ ts->abs_y_max = PIMHYP4_MAX_HEIGHT;
|
|
+ if (ts->swapped_x_y)
|
|
+ swap(ts->abs_x_max, ts->abs_y_max);
|
|
+ ts->max_touch_num = PIMHYP4_MAX_CONTACTS;
|
|
+ }
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_read_version - Read goodix touchscreen version
|
|
+ *
|
|
+ * @ts: our pimhyp4_ts_data pointer
|
|
+ */
|
|
+static int pimhyp4_read_version(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+ u8 buf[6];
|
|
+ char id_str[5];
|
|
+
|
|
+ error = pimhyp4_i2c_read(ts->client, PIMHYP4_REG_ID, buf, sizeof(buf));
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev, "read version failed: %d\n", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ memcpy(id_str, buf, 4);
|
|
+ id_str[4] = 0;
|
|
+ if (kstrtou16(id_str, 10, &ts->id))
|
|
+ ts->id = 0x1001;
|
|
+
|
|
+ ts->version = get_unaligned_le16(&buf[4]);
|
|
+
|
|
+ dev_info(&ts->client->dev, "ID %d, version: %04x\n", ts->id,
|
|
+ ts->version);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_i2c_test - I2C test function to check if the device answers.
|
|
+ *
|
|
+ * @client: the i2c client
|
|
+ */
|
|
+static int pimhyp4_i2c_test(struct i2c_client *client)
|
|
+{
|
|
+ //int retry = 0;
|
|
+ int error;
|
|
+ u8 test;
|
|
+
|
|
+ //while (retry++ < 2) {
|
|
+ error = pimhyp4_i2c_read(client, PIMHYP4_REG_CONFIG_DATA,
|
|
+ &test, 1);
|
|
+ if (!error)
|
|
+ return 0;
|
|
+
|
|
+ dev_err(&client->dev, "i2c test failed: %d\n", error);
|
|
+ msleep(20);
|
|
+ //}
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_request_input_dev - Allocate, populate and register the input device
|
|
+ *
|
|
+ * @ts: our pimhyp4_ts_data pointer
|
|
+ *
|
|
+ * Must be called during probe
|
|
+ */
|
|
+static int pimhyp4_request_input_dev(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ ts->input_dev = devm_input_allocate_device(&ts->client->dev);
|
|
+ if (!ts->input_dev) {
|
|
+ dev_err(&ts->client->dev, "Failed to allocate input device.");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X,
|
|
+ 0, ts->abs_x_max, 0, 0);
|
|
+ input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y,
|
|
+ 0, ts->abs_y_max, 0, 0);
|
|
+ input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
|
|
+ input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
|
|
+
|
|
+ input_mt_init_slots(ts->input_dev, ts->max_touch_num,
|
|
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
|
|
+
|
|
+ ts->input_dev->name = "Pimoroni Hyperpixel4 Capacitive TouchScreen";
|
|
+ ts->input_dev->phys = "input/ts";
|
|
+ ts->input_dev->id.bustype = BUS_I2C;
|
|
+ ts->input_dev->id.vendor = 0x0416;
|
|
+ ts->input_dev->id.product = ts->id;
|
|
+ ts->input_dev->id.version = ts->version;
|
|
+
|
|
+ error = input_register_device(ts->input_dev);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev,
|
|
+ "Failed to register input device: %d", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_configure_dev - Finish device initialization
|
|
+ *
|
|
+ * @ts: our pimhyp4_ts_data pointer
|
|
+ *
|
|
+ * Must be called from probe to finish initialization of the device.
|
|
+ * Contains the common initialization code for both devices that
|
|
+ * declare gpio pins and devices that do not. It is either called
|
|
+ * directly from probe or from request_firmware_wait callback.
|
|
+ */
|
|
+static int pimhyp4_configure_dev(struct pimhyp4_ts_data *ts)
|
|
+{
|
|
+ int error;
|
|
+ u32 sizeX, sizeY, refreshRate;
|
|
+
|
|
+ // X2Y setting
|
|
+ ts->requestedX2Y = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-x2y");
|
|
+
|
|
+ if(device_property_read_u32(&ts->client->dev, "touchscreen-refresh-rate", &refreshRate))
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-refresh-rate not found");
|
|
+ ts->requestedRefreshRate = 5;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-refresh-rate found %u", sizeX);
|
|
+ ts->requestedRefreshRate = (u8)refreshRate;
|
|
+ }
|
|
+
|
|
+ if(device_property_read_u32(&ts->client->dev, "touchscreen-size-x", &sizeX))
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-x not found");
|
|
+ ts->requestedXSize = 0;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-x found %u", sizeX);
|
|
+ ts->requestedXSize = (u16)sizeX;
|
|
+ }
|
|
+
|
|
+ if(device_property_read_u32(&ts->client->dev, "touchscreen-size-y", &sizeY))
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-y not found");
|
|
+ ts->requestedYSize = 0;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "touchscreen-size-y found %u", sizeY);
|
|
+ ts->requestedYSize = (u16)sizeY;
|
|
+ }
|
|
+
|
|
+ dev_dbg(&ts->client->dev, "requested size (%u, %u)", ts->requestedXSize, ts->requestedYSize);
|
|
+
|
|
+ ts->swapped_x_y = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-swapped-x-y");
|
|
+ ts->inverted_x = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-inverted-x");
|
|
+ ts->inverted_y = device_property_read_bool(&ts->client->dev,
|
|
+ "touchscreen-inverted-y");
|
|
+
|
|
+ pimhyp4_read_config(ts);
|
|
+
|
|
+ error = pimhyp4_request_input_dev(ts);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ ts->irq_flags = pimhyp4_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT;
|
|
+ error = pimhyp4_request_irq(ts);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev, "request IRQ failed: %d\n", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pimhyp4_config_cb - Callback to finish device init
|
|
+ *
|
|
+ * @ts: our pimhyp4_ts_data pointer
|
|
+ *
|
|
+ * request_firmware_wait callback that finishes
|
|
+ * initialization of the device.
|
|
+ */
|
|
+static void pimhyp4_config_cb(const struct firmware *cfg, void *ctx)
|
|
+{
|
|
+ struct pimhyp4_ts_data *ts = ctx;
|
|
+ int error;
|
|
+
|
|
+ if (cfg) {
|
|
+ /* send device configuration to the firmware */
|
|
+ error = pimhyp4_send_cfg(ts, cfg);
|
|
+ if (error)
|
|
+ goto err_release_cfg;
|
|
+ }
|
|
+
|
|
+ pimhyp4_configure_dev(ts);
|
|
+
|
|
+err_release_cfg:
|
|
+ release_firmware(cfg);
|
|
+ complete_all(&ts->firmware_loading_complete);
|
|
+}
|
|
+
|
|
+static int pimhyp4_ts_probe(struct i2c_client *client,
|
|
+ const struct i2c_device_id *id)
|
|
+{
|
|
+ struct pimhyp4_ts_data *ts;
|
|
+ int error;
|
|
+
|
|
+ dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr);
|
|
+
|
|
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
+ dev_err(&client->dev, "I2C check functionality failed.\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
|
|
+ if (!ts)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ts->client = client;
|
|
+ i2c_set_clientdata(client, ts);
|
|
+ init_completion(&ts->firmware_loading_complete);
|
|
+
|
|
+ error = pimhyp4_get_gpio_config(ts);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ if (ts->gpiod_int && ts->gpiod_rst) {
|
|
+ /* reset the controller */
|
|
+ error = pimhyp4_reset(ts);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev, "Controller reset failed.\n");
|
|
+ return error;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ error = pimhyp4_i2c_test(client);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev, "I2C communication failure: %d\n", error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ error = pimhyp4_read_version(ts);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev, "Read version failed.\n");
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ //Device is detected!
|
|
+ pr_info("Detected: pimhyp4");
|
|
+ if (device_property_read_bool(&ts->client->dev,"touchscreen-check"))
|
|
+ {
|
|
+ //We have notified the device is fitted, but we don't want to use it
|
|
+ pr_info("Device check only. Exiting pimhyp4");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+
|
|
+ ts->cfg_len = pimhyp4_get_cfg_len(ts->id);
|
|
+
|
|
+ if (ts->gpiod_int && ts->gpiod_rst) {
|
|
+ /* update device config */
|
|
+ ts->cfg_name = devm_kasprintf(&client->dev, GFP_KERNEL,
|
|
+ "goodix_%d_cfg.bin", ts->id);
|
|
+ if (!ts->cfg_name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ error = request_firmware_nowait(THIS_MODULE, true, ts->cfg_name,
|
|
+ &client->dev, GFP_KERNEL, ts,
|
|
+ pimhyp4_config_cb);
|
|
+ if (error) {
|
|
+ dev_err(&client->dev,
|
|
+ "Failed to invoke firmware loader: %d\n",
|
|
+ error);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+ } else {
|
|
+ error = pimhyp4_configure_dev(ts);
|
|
+ if (error)
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ lcd_init(ts);
|
|
+ pr_info("Using: pimhyp4");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pimhyp4_ts_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct pimhyp4_ts_data *ts = i2c_get_clientdata(client);
|
|
+
|
|
+ if (ts->gpiod_int && ts->gpiod_rst)
|
|
+ wait_for_completion(&ts->firmware_loading_complete);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused pimhyp4_suspend(struct device *dev)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pimhyp4_ts_data *ts = i2c_get_clientdata(client);
|
|
+ int error;
|
|
+
|
|
+ /* We need gpio pins to suspend/resume */
|
|
+ if (!ts->gpiod_int || !ts->gpiod_rst) {
|
|
+ disable_irq(client->irq);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ wait_for_completion(&ts->firmware_loading_complete);
|
|
+
|
|
+ /* Free IRQ as IRQ pin is used as output in the suspend sequence */
|
|
+ pimhyp4_free_irq(ts);
|
|
+
|
|
+ /* Output LOW on the INT pin for 5 ms */
|
|
+ error = gpiod_direction_output(ts->gpiod_int, 0);
|
|
+ if (error) {
|
|
+ pimhyp4_request_irq(ts);
|
|
+ return error;
|
|
+ }
|
|
+
|
|
+ usleep_range(5000, 6000);
|
|
+
|
|
+ error = pimhyp4_i2c_write_u8(ts->client, PIMHYP4_REG_COMMAND,
|
|
+ PIMHYP4_CMD_SCREEN_OFF);
|
|
+ if (error) {
|
|
+ dev_err(&ts->client->dev, "Screen off command failed\n");
|
|
+ gpiod_direction_input(ts->gpiod_int);
|
|
+ pimhyp4_request_irq(ts);
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The datasheet specifies that the interval between sending screen-off
|
|
+ * command and wake-up should be longer than 58 ms. To avoid waking up
|
|
+ * sooner, delay 58ms here.
|
|
+ */
|
|
+ msleep(58);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused pimhyp4_resume(struct device *dev)
|
|
+{
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
+ struct pimhyp4_ts_data *ts = i2c_get_clientdata(client);
|
|
+ int error;
|
|
+
|
|
+ if (!ts->gpiod_int || !ts->gpiod_rst) {
|
|
+ enable_irq(client->irq);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Exit sleep mode by outputting HIGH level to INT pin
|
|
+ * for 2ms~5ms.
|
|
+ */
|
|
+ error = gpiod_direction_output(ts->gpiod_int, 1);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ usleep_range(2000, 5000);
|
|
+
|
|
+ error = pimhyp4_int_sync(ts);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ error = pimhyp4_request_irq(ts);
|
|
+ if (error)
|
|
+ return error;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+//0x41,0xe0,0x01,0x20,0x03,0x05,0x08,0x00,
|
|
+//0x01,0x0a,0x28,0x0a,0x50,0x32,0x03,0x05,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,
|
|
+//0x19,0x1e,0x14,0x87,0x26,0x08,0x43,0x41,
|
|
+//0x0c,0x08,0x00,0x00,0x01,0x02,0x03,0x1d,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x03,0x64,0x32,
|
|
+//0x00,0x00,0x00,0x1e,0x50,0x94,0xc5,0x02,
|
|
+//0x07,0x00,0x00,0x00,0xb2,0x21,0x00,0xc0,
|
|
+//0x28,0x00,0x7d,0x31,0x00,0x6b,0x3b,0x00,
|
|
+//0x5c,0x48,0x00,0x5c,0x00,0x00,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,
|
|
+//0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,
|
|
+//0x04,0x06,0x08,0x0a,0x0c,0x1d,0x1e,0x1f,
|
|
+//0x20,0x21,0x22,0xff,0xff,0xff,0xff,0xff,
|
|
+//0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+//0x7c,0x00
|
|
+
|
|
+
|
|
+static int checkFirmware(struct pimhyp4_ts_data *ts, u8 *config)
|
|
+{
|
|
+ int result = 0;
|
|
+
|
|
+ bool bUpdate = false;
|
|
+ bool bNewX2Y = ts->requestedX2Y;
|
|
+ bool bCurrentX2Y = CHECK_BIT(config[0x804d - PIMHYP4_REG_CONFIG_DATA], 3);
|
|
+
|
|
+ u8 currentRefreshRate = config[0x8056 - PIMHYP4_REG_CONFIG_DATA];
|
|
+
|
|
+
|
|
+ dev_info(&ts->client->dev, "Checking firmware");
|
|
+ if(ts->requestedRefreshRate != currentRefreshRate)
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "Requested refresh rate = %u, orig = %u", ts->requestedRefreshRate, currentRefreshRate);
|
|
+ config[0x8056 - PIMHYP4_REG_CONFIG_DATA] = (config[0x8056 - PIMHYP4_REG_CONFIG_DATA] & 0xF0) + (ts->requestedRefreshRate & 0x0F);
|
|
+ bUpdate = true;
|
|
+ }
|
|
+
|
|
+ if(ts->requestedXSize)
|
|
+ {
|
|
+ u16 xOrig;
|
|
+
|
|
+ xOrig = get_unaligned_le16(&config[RESOLUTION_LOC_X]);
|
|
+ dev_dbg(&ts->client->dev, "Requested X size = %u, orig = %u", ts->requestedXSize, xOrig);
|
|
+ if(ts->requestedXSize != xOrig)
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "Requested X size needs firmware update");
|
|
+ put_unaligned_le16(ts->requestedXSize, &config[RESOLUTION_LOC_X]);
|
|
+ bUpdate = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if(ts->requestedYSize)
|
|
+ {
|
|
+ u16 yOrig;
|
|
+
|
|
+ yOrig = get_unaligned_le16(&config[RESOLUTION_LOC_Y]);
|
|
+ dev_dbg(&ts->client->dev, "Requested Y size = %u, orig = %u", ts->requestedYSize, yOrig);
|
|
+ if(ts->requestedYSize != yOrig)
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "Requested Y size needs firmware update");
|
|
+ put_unaligned_le16(ts->requestedYSize, &config[RESOLUTION_LOC_Y]);
|
|
+ bUpdate = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if(bNewX2Y != bCurrentX2Y)
|
|
+ {
|
|
+ dev_dbg(&ts->client->dev, "Requested X2Y size needs firmware update");
|
|
+
|
|
+ if(bNewX2Y)
|
|
+ config[0x804d - PIMHYP4_REG_CONFIG_DATA] |= 0x08;
|
|
+ else
|
|
+ config[0x804d - PIMHYP4_REG_CONFIG_DATA] &= 0xf7;
|
|
+
|
|
+ bUpdate = true;
|
|
+ }
|
|
+
|
|
+ if(bUpdate)
|
|
+ {
|
|
+ int error;
|
|
+ struct firmware fw;
|
|
+ int check;
|
|
+ int rawCfgLlen;
|
|
+ int i;
|
|
+ u8 checkSum = 0;
|
|
+
|
|
+ dev_info(&ts->client->dev, "Updating firmware");
|
|
+
|
|
+ fw.data = config;
|
|
+ fw.size = PIMHYP4_CONFIG_911_LENGTH;
|
|
+ config[PIMHYP4_CONFIG_911_LENGTH-1]=1; // flash fw flag
|
|
+
|
|
+ // calculate checksum
|
|
+ rawCfgLlen = PIMHYP4_CONFIG_911_LENGTH- 2;
|
|
+ for (i = 0; i < rawCfgLlen; i++)
|
|
+ checkSum += config[i];
|
|
+ checkSum = (~checkSum) + 1;
|
|
+ config[rawCfgLlen] = checkSum;
|
|
+
|
|
+ // check firmware is ok
|
|
+ check = pimhyp4_check_cfg(ts, &fw);
|
|
+
|
|
+ if(!check)
|
|
+ {
|
|
+ error = pimhyp4_i2c_write(ts->client, PIMHYP4_REG_CONFIG_DATA, fw.data, fw.size);
|
|
+ if (error)
|
|
+ dev_err(&ts->client->dev, "Failed to write config data: (%d)", error);
|
|
+ else
|
|
+ dev_info(&ts->client->dev, "Updated firmware correctly.");
|
|
+
|
|
+
|
|
+ /* Let the firmware reconfigure itself, so sleep for 10ms */
|
|
+ usleep_range(10000, 11000);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ dev_err(&ts->client->dev, "Firmware data is invalid.");
|
|
+ result = 1;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ dev_dbg(&ts->client->dev, "Firmware already updated with correct values");
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(pimhyp4_pm_ops, pimhyp4_suspend, pimhyp4_resume);
|
|
+
|
|
+static const struct i2c_device_id pimhyp4_ts_id[] = {
|
|
+ { "GDIX1001:00", 0 },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(i2c, pimhyp4_ts_id);
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+static const struct of_device_id pimhyp4_of_match[] = {
|
|
+ { .compatible = "pimoroni,ft6236" },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, pimhyp4_of_match);
|
|
+#endif
|
|
+
|
|
+static struct i2c_driver pimhyp4_ts_driver = {
|
|
+ .probe = pimhyp4_ts_probe,
|
|
+ .remove = pimhyp4_ts_remove,
|
|
+ .id_table = pimhyp4_ts_id,
|
|
+ .driver = {
|
|
+ .name = "pimhyp4-TS",
|
|
+ .of_match_table = of_match_ptr(pimhyp4_of_match),
|
|
+ .pm = &pimhyp4_pm_ops,
|
|
+ },
|
|
+};
|
|
+module_i2c_driver(pimhyp4_ts_driver);
|
|
+
|
|
+MODULE_AUTHOR("procount <kevin.procount@googlemail.com>");
|
|
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
|
|
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
|
|
+MODULE_DESCRIPTION("pimhyp4 touchscreen driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
--
|
|
1.9.1
|
|
|