Files
package_application_recovery/buildroot/linux/0002-Add-pimoroni-hyperpixel-drivers.patch
procount 1bc287ca7f p3.3 for Rpi4
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
2019-06-25 13:15:54 +01:00

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