diff --git a/Documentation/ABI/testing/dev-g6ts-heat b/Documentation/ABI/testing/dev-g6ts-heat new file mode 100644 index 00000000000000..6e459694cb1c8e --- /dev/null +++ b/Documentation/ABI/testing/dev-g6ts-heat @@ -0,0 +1,55 @@ +What: /dev/g6ts-heat +Date: August 2026 +KernelVersion: 7.2 +Contact: linux-input@vger.kernel.org +Description: + Binary raw-frame stream for the Microsoft Surface G6 touch/pen + processor. HEAT reports 0x0b, 0x0c, 0x0d, and 0x1a plus optional + opaque sideband reports 0x07 and 0x6e are exported in transport + order before the touch-readiness filter is applied. Sideband + records are not cycle members or pen-presence notifications. + + Only one reader may open the misc character device at a time. A + blocking read waits for a complete record. O_NONBLOCK returns + -EAGAIN when empty. Every successful read returns exactly one + whole record; a buffer smaller than record_len returns -EMSGSIZE + without consuming it. poll(2) reports POLLIN while records are + queued and POLLHUP after device removal. + + The first 32 bytes are struct g6ts_heat_record_header from + , followed by content_len bytes copied exactly + from the HID-over-SPI content (the report ID is not repeated in the + payload). All multibyte fields are little-endian: + + ======= ===== ================================================== + Offset Size Meaning + ======= ===== ================================================== + 0 4 Magic 0x31483647 (ASCII bytes "G6H1") + 4 2 ABI version (1) + 6 2 Header length (32) + 8 4 Total record length + 12 4 Transport generation + 16 8 CLOCK_MONOTONIC timestamp in nanoseconds + 24 4 Enqueue sequence, including boundary records + 28 2 Content length (maximum 4349) + 30 1 HID report ID, or zero for a boundary record + 31 1 G6TS_HEAT_RECORD_F_* flags + ======= ===== ================================================== + + Generation starts at one. A panel reset, suspend, or transport + fault increments it, flushes queued frames, and enqueues a zero-data + boundary record. RESET, SUSPEND, or TRANSPORT_FAULT in that record + tells userspace to release pen state immediately. Queue overflow + drops the oldest record so retained records remain ordered and current. + Because overflow can drop the boundary record itself, readers must + also treat every generation change or sequence gap as a discontinuity, + release pen state immediately, and discard any partial measurement + cycle. + + G6TS_HEAT_IOC_GET_INFO returns the ABI/header sizes, maximum content + length, queue capacity, and supported flags in struct + g6ts_heat_info. G6TS_HEAT_IOC_GET_STATS returns the generation, + queue depth, enqueue/drop/flush counters, oversize drops, and the + four core HEAT report counts in struct g6ts_heat_stats. Both ioctl + structures begin with ABI version and structure size and reserve + zero-filled space for compatible extension. diff --git a/Documentation/devicetree/bindings/input/touchscreen/microsoft,mshw0485.yaml b/Documentation/devicetree/bindings/input/touchscreen/microsoft,mshw0485.yaml new file mode 100644 index 00000000000000..974722060cb521 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/microsoft,mshw0485.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/touchscreen/microsoft,mshw0485.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microsoft Surface G6 MSHW0485 touchscreen + +maintainers: + - Leon Silcott + +description: + The MSHW0485 is a HID-over-SPI touchscreen used by the Microsoft Surface + Pro 11. It uses a protocol-9 Qualcomm GENI controller in quad-SPI mode. + +allOf: + - $ref: touchscreen.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: microsoft,mshw0485 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-gpios: + maxItems: 1 + + power-gpios: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + microsoft,enable-heat-frame-abi: + type: boolean + description: + Opt in to the optional raw heat-frame misc-device ABI. The matching + kernel configuration must also be enabled. + + microsoft,enable-iptsd-bridge: + type: boolean + description: + Opt in to the optional HIDRAW-only DFT bridge used by iptsd. The + matching kernel configuration must also be enabled. + + spi-max-frequency: + maximum: 40000000 + +required: + - compatible + - reg + - interrupts + - interrupt-gpios + - power-gpios + - reset-gpios + - spi-max-frequency + +additionalProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + touchscreen@0 { + compatible = "microsoft,mshw0485"; + reg = <0>; + spi-max-frequency = <40000000>; + + interrupt-parent = <&tlmm>; + interrupts = <51 IRQ_TYPE_LEVEL_LOW>; + interrupt-gpios = <&tlmm 51 GPIO_ACTIVE_LOW>; + power-gpios = <&tlmm 64 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 48 GPIO_ACTIVE_HIGH>; + + microsoft,enable-heat-frame-abi; + microsoft,enable-iptsd-bridge; + }; + }; +... diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml index edf399681d7a99..83a117fb8dbe35 100644 --- a/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml +++ b/Documentation/devicetree/bindings/spi/qcom,spi-geni-qcom.yaml @@ -26,6 +26,13 @@ description: allOf: - $ref: /schemas/spi/spi-controller.yaml# - $ref: /schemas/soc/qcom/qcom,se-common-props.yaml# + - if: + required: + - qcom,geni-qspi + then: + required: + - dmas + - dma-names properties: compatible: @@ -64,6 +71,13 @@ properties: power-domains: maxItems: 1 + qcom,geni-qspi: + type: boolean + description: + Select protocol-9 quad-SPI operation for the Microsoft Denali touch + controller. The serial engine firmware must already be loaded by the + platform firmware and the DMA channels must use QCOM_GPI_QSPI. + reg: maxItems: 1 diff --git a/MAINTAINERS b/MAINTAINERS index 70fc03bf3d367d..fbe9dab2cae06f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17929,6 +17929,15 @@ F: Documentation/driver-api/surface_aggregator/clients/dtx.rst F: drivers/platform/surface/surface_dtx.c F: include/uapi/linux/surface_aggregator/dtx.h +MICROSOFT SURFACE G6 TOUCHSCREEN DRIVER +M: Leon Silcott +L: linux-input@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/dev-g6ts-heat +F: Documentation/devicetree/bindings/input/touchscreen/microsoft,mshw0485.yaml +F: drivers/input/touchscreen/mshw0485_touch.c +F: include/uapi/linux/g6ts_heat.h + MICROSOFT SURFACE SENSOR FAN DRIVER M: Maximilian Luz M: Ivor Wanders diff --git a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi index 0886eb11095d60..a9a4545fb28a4d 100644 --- a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi @@ -716,6 +716,10 @@ }; }; +&gpi_dma1 { + status = "okay"; +}; + &gpu { status = "okay"; }; @@ -1064,6 +1068,38 @@ vdd3-supply = <&vreg_l8b_3p0>; }; +&spi10 { + qcom,geni-qspi; + + dmas = <&gpi_dma1 0 2 QCOM_GPI_QSPI>, + <&gpi_dma1 1 2 QCOM_GPI_QSPI>; + dma-names = "tx", "rx"; + + pinctrl-0 = <&qup_spi10_data_clk>, + <&qup_spi10_cs>, + <&g6ts_qspi_data23>; + pinctrl-names = "default"; + + status = "okay"; + + touchscreen@0 { + compatible = "microsoft,mshw0485"; + reg = <0>; + spi-max-frequency = <40000000>; + + interrupt-parent = <&tlmm>; + interrupts = <51 IRQ_TYPE_LEVEL_LOW>; + interrupt-gpios = <&tlmm 51 GPIO_ACTIVE_LOW>; + power-gpios = <&tlmm 64 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 48 GPIO_ACTIVE_HIGH>; + microsoft,enable-heat-frame-abi; + microsoft,enable-iptsd-bridge; + + pinctrl-0 = <&g6ts_interrupt>, <&g6ts_reset_power>; + pinctrl-names = "default"; + }; +}; + &swr0 { status = "okay"; @@ -1099,6 +1135,26 @@ gpio-reserved-ranges = <44 4>, /* SPI (TPM) */ <238 1>; /* UFS Reset */ + g6ts_interrupt: g6ts-interrupt-state { + pins = "gpio51"; + function = "gpio"; + bias-pull-up; + }; + + g6ts_qspi_data23: g6ts-qspi-data23-state { + pins = "gpio49", "gpio50"; + function = "qup1_se2"; + drive-strength = <6>; + bias-disable; + }; + + g6ts_reset_power: g6ts-reset-power-state { + pins = "gpio48", "gpio64"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + hall_int_n_default: hall-int-n-state { pins = "gpio2"; function = "gpio"; diff --git a/debian.qcom-x1e/config/annotations b/debian.qcom-x1e/config/annotations index 6dc5c30bdc3d27..aaed29229c3c37 100644 --- a/debian.qcom-x1e/config/annotations +++ b/debian.qcom-x1e/config/annotations @@ -12144,6 +12144,9 @@ CONFIG_TOUCHSCREEN_MC13783 policy<{'arm64': 'm'}> CONFIG_TOUCHSCREEN_MELFAS_MIP4 policy<{'arm64': 'm'}> CONFIG_TOUCHSCREEN_MMS114 policy<{'arm64': 'm'}> CONFIG_TOUCHSCREEN_MSG2638 policy<{'arm64': 'm'}> +CONFIG_TOUCHSCREEN_MSHW0485 policy<{'arm64': 'm'}> +CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME policy<{'arm64': 'y'}> +CONFIG_TOUCHSCREEN_MSHW0485_IPTSD policy<{'arm64': 'y'}> CONFIG_TOUCHSCREEN_MTOUCH policy<{'arm64': 'm'}> CONFIG_TOUCHSCREEN_NOVATEK_NVT_TS policy<{'arm64': 'm'}> CONFIG_TOUCHSCREEN_PENMOUNT policy<{'arm64': 'm'}> diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c index a5055a6273af62..c89c2e40135559 100644 --- a/drivers/dma/qcom/gpi.c +++ b/drivers/dma/qcom/gpi.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "../dmaengine.h" #include "../virt-dma.h" @@ -22,6 +23,12 @@ #define TRE_TYPE_GO 0x20 #define TRE_TYPE_CONFIG0 0x22 +#define TRE_QSPI_GO_FLAG BIT(29) +#define TRE_QSPI_CONFIG0_DW0 0x03000004 +#define TRE_QSPI_CONFIG0_DW2 0x00010001 +#define TRE_QSPI_GO_TX_DW0 0x20000001 +#define TRE_QSPI_GO_BIDI_DW0 0x20000007 + /* TRE flags */ #define TRE_FLAGS_CHAIN BIT(0) #define TRE_FLAGS_IEOB BIT(8) @@ -214,6 +221,9 @@ enum CNTXT_OFFS { (FIELD_PREP(GPII_n_CH_k_SCRATCH_0_PAIR, pair) | \ FIELD_PREP(GPII_n_CH_k_SCRATCH_0_PROTO, proto) | \ FIELD_PREP(GPII_n_CH_k_SCRATCH_0_SEID, seid)) +#define GPII_n_CH_k_SCRATCH_0_QSPI_TX 0x16010092 +#define GPII_n_CH_k_SCRATCH_0_QSPI_RX 0x17000092 +#define GPII_n_CH_k_SCRATCH_2_QSPI 0x00000002 #define GPII_n_CH_k_SCRATCH_1_OFFS(n, k) (0x20064 + (0x4000 * (n)) + (0x80 * (k))) #define GPII_n_CH_k_SCRATCH_2_OFFS(n, k) (0x20068 + (0x4000 * (n)) + (0x80 * (k))) #define GPII_n_CH_k_SCRATCH_3_OFFS(n, k) (0x2006C + (0x4000 * (n)) + (0x80 * (k))) @@ -238,7 +248,6 @@ enum msm_gpi_tce_code { #define EV_FACTOR (2) #define REQ_OF_DMA_ARGS (5) /* # of arguments required from client */ #define CHAN_TRES 64 - struct __packed xfer_compl_event { u64 ptr; u32 length:24; @@ -516,6 +525,11 @@ struct gpii { enum gpi_cmd gpi_cmd; u32 cntxt_type_irq_msk; bool ieob_set; + struct virt_dma_desc *qspi_deferred_vd; + struct gchan *qspi_deferred_gchan; + struct dmaengine_result qspi_deferred_result; + struct timer_list qspi_deferred_timer; + bool qspi_deferred_grace_elapsed; }; #define MAX_TRE 3 @@ -548,6 +562,27 @@ static inline struct gpi_desc *to_gpi_desc(struct virt_dma_desc *vd) return container_of(vd, struct gpi_desc, vd); } +static bool gpii_has_active_qspi(struct gpii *gpii) +{ + int i; + + for (i = 0; i < MAX_CHANNELS_PER_GPII; i++) + if (gpii->gchan[i].protocol == QCOM_GPI_QSPI && + gpii->gchan[i].pm_state == ACTIVE_STATE) + return true; + + return false; +} + +static const char *gpi_qspi_side_name(u32 chid) +{ + if (chid == GPI_TX_CHAN) + return "TX"; + if (chid == GPI_RX_CHAN) + return "RX"; + return "?"; +} + static inline phys_addr_t to_physical(const struct gpi_ring *const ring, void *addr) { @@ -559,11 +594,60 @@ static inline void *to_virtual(const struct gpi_ring *const ring, phys_addr_t ad return ring->base + (addr - ring->phys_addr); } +static void gpi_qspi_complete_deferred(struct gpii *gpii, + struct qup_notif_event *notif) +{ + struct virt_dma_desc *vd = gpii->qspi_deferred_vd; + struct gchan *gchan = gpii->qspi_deferred_gchan; + struct gpi_desc *gpi_desc; + struct dmaengine_result result = gpii->qspi_deferred_result; + unsigned long flags; + + if (!vd || !gchan) + return; + + gpi_desc = to_gpi_desc(vd); + timer_delete(&gpii->qspi_deferred_timer); + gpii->qspi_deferred_vd = NULL; + gpii->qspi_deferred_gchan = NULL; + WRITE_ONCE(gpii->qspi_deferred_grace_elapsed, false); + + if (notif) + dev_dbg(gpii->gpi_dev->dev, + "SP11 QSPI completing deferred TX after QUP notification status:%#x count:%u residue:%u\n", + notif->status, notif->count, result.residue); + else + dev_dbg(gpii->gpi_dev->dev, + "SP11 QSPI completing TX-only descriptor after grace period residue:%u\n", + result.residue); + + dma_cookie_complete(&vd->tx); + dmaengine_desc_get_callback_invoke(&vd->tx, &result); + + spin_lock_irqsave(&gchan->vc.lock, flags); + list_del(&vd->node); + spin_unlock_irqrestore(&gchan->vc.lock, flags); + kfree(gpi_desc); +} + static inline u32 gpi_read_reg(struct gpii *gpii, void __iomem *addr) { return readl_relaxed(addr); } +static phys_addr_t gpi_read_ev_rp(struct gpii *gpii) +{ + u32 lsb = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg); + u32 msb; + + if (!gpii_has_active_qspi(gpii)) + return lsb; + + msb = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg + 4); + + return ((phys_addr_t)msb << 32) | lsb; +} + static inline void gpi_write_reg(struct gpii *gpii, void __iomem *addr, u32 val) { writel_relaxed(val, addr); @@ -721,7 +805,13 @@ static inline void gpi_write_ch_db(struct gchan *gchan, phys_addr_t p_wp; p_wp = to_physical(ring, wp); - gpi_write_reg(gpii, gchan->ch_cntxt_db_reg, p_wp); + if (gchan->protocol == QCOM_GPI_QSPI) { + gpi_write_reg(gpii, gchan->ch_cntxt_db_reg + 4, + upper_32_bits(p_wp)); + /* Commit the high half before ringing the low doorbell. */ + wmb(); + } + gpi_write_reg(gpii, gchan->ch_cntxt_db_reg, lower_32_bits(p_wp)); } /* program event ring DB register */ @@ -731,7 +821,13 @@ static inline void gpi_write_ev_db(struct gpii *gpii, phys_addr_t p_wp; p_wp = ring->phys_addr + (wp - ring->base); - gpi_write_reg(gpii, gpii->ev_cntxt_db_reg, p_wp); + if (gpii_has_active_qspi(gpii)) { + gpi_write_reg(gpii, gpii->ev_cntxt_db_reg + 4, + upper_32_bits(p_wp)); + /* Commit the high half before ringing the low doorbell. */ + wmb(); + } + gpi_write_reg(gpii, gpii->ev_cntxt_db_reg, lower_32_bits(p_wp)); } /* process transfer completion interrupt */ @@ -826,6 +922,7 @@ static irqreturn_t gpi_handle_irq(int irq, void *data) u32 gpii_id = gpii->gpii_id; u32 type, offset; unsigned long flags; + bool qspi_drain_events = false; read_lock_irqsave(&gpii->pm_lock, flags); @@ -838,6 +935,7 @@ static irqreturn_t gpi_handle_irq(int irq, void *data) TO_GPI_PM_STR(gpii->pm_state)); goto exit_irq; } + qspi_drain_events = gpii_has_active_qspi(gpii); offset = GPII_n_CNTXT_TYPE_IRQ_OFFS(gpii->gpii_id); type = gpi_read_reg(gpii, gpii->regs + offset); @@ -904,6 +1002,9 @@ static irqreturn_t gpi_handle_irq(int irq, void *data) type = gpi_read_reg(gpii, gpii->regs + offset); } while (type); + if (qspi_drain_events) + tasklet_hi_schedule(&gpii->ev_task); + exit_irq: read_unlock_irqrestore(&gpii->pm_lock, flags); @@ -1018,6 +1119,21 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan, struct gpi_ere *gpi_ere; spin_unlock_irqrestore(&gchan->vc.lock, flags); + /* + * QSPI can deliver a late duplicate EOT after the matching + * descriptor has already been retired by its QUP notification. + * There is no work left to complete, so this is expected ring + * housekeeping rather than a channel error. + */ + if (gchan->protocol == QCOM_GPI_QSPI && + compl_event->code == MSM_GPI_TCE_EOT) { + dev_dbg_ratelimited(gpii->gpi_dev->dev, + "discarding late QSPI EOT side:%s ptr:%pa len:%u\n", + gpi_qspi_side_name(gchan->chid), + &compl_event->ptr, + compl_event->length); + return; + } dev_err(gpii->gpi_dev->dev, "Event without a pending descriptor!\n"); gpi_ere = (struct gpi_ere *)compl_event; dev_err(gpii->gpi_dev->dev, @@ -1030,6 +1146,31 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan, gpi_desc = to_gpi_desc(vd); spin_unlock_irqrestore(&gchan->vc.lock, flags); + /* + * TX-only QSPI produces a late duplicate EOT whose pointer is one TRE + * beyond the descriptor's DMA TRE. If the next descriptor has already + * been queued, that address aliases its first CONFIG TRE. Never attribute + * such an event to the new descriptor or advance its software ring RP. + */ + if (gchan->protocol == QCOM_GPI_QSPI && + compl_event->code == MSM_GPI_TCE_EOT) { + void *last_tre = gpi_desc->db; + phys_addr_t last_tre_phys; + + if (last_tre == ch_ring->base) + last_tre = ch_ring->base + ch_ring->len; + last_tre -= ch_ring->el_size; + last_tre_phys = to_physical(ch_ring, last_tre); + if (compl_event->ptr != last_tre_phys) { + dev_dbg_ratelimited(gpii->gpi_dev->dev, + "SP11 QSPI ignoring non-terminal EOT side:%s ptr:%pa expected:%pa len:%u pending_len:%zu\n", + gpi_qspi_side_name(gchan->chid), + &compl_event->ptr, &last_tre_phys, + compl_event->length, gpi_desc->len); + return; + } + } + /* * RP pointed by Event is to last TRE processed, * we need to update ring rp to ev_rp + 1 @@ -1043,11 +1184,19 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan, smp_wmb(); chid = compl_event->chid; - if (compl_event->code == MSM_GPI_TCE_EOT && gpii->ieob_set) { + if (gchan->protocol == QCOM_GPI_QSPI) { + if (compl_event->code == MSM_GPI_TCE_EOB) { + gpii->ieob_set = true; + return; + } + + if (compl_event->code == MSM_GPI_TCE_EOT && gpii->ieob_set) + gpii->ieob_set = false; + } else if (compl_event->code == MSM_GPI_TCE_EOT && + gpii->ieob_set) { if (chid == GPI_RX_CHAN) goto gpi_free_desc; - else - return; + return; } if (compl_event->code == MSM_GPI_TCE_UNEXP_ERR) { @@ -1057,9 +1206,40 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan, dev_dbg(gpii->gpi_dev->dev, "Transaction Success\n"); result.result = DMA_TRANS_NOERROR; } - result.residue = gpi_desc->len - compl_event->length; + if (gchan->protocol == QCOM_GPI_QSPI) + result.residue = compl_event->length < gpi_desc->len ? + gpi_desc->len - compl_event->length : 0; + else + result.residue = gpi_desc->len - compl_event->length; dev_dbg(gpii->gpi_dev->dev, "Residue %d\n", result.residue); + /* + * A QSPI TX data EOT precedes the matching QUP command-completion + * notification. Completing the DMA cookie here lets the SPI client ring + * the next command while the previous command is still retiring, which + * wedges both channels. The SP11 code already carries a single deferred + * descriptor slot; populate it and let QUP_NOTIF_EV_TYPE retire it. + */ + if (gchan->protocol == QCOM_GPI_QSPI && + gchan->chid == GPI_TX_CHAN && + compl_event->code == MSM_GPI_TCE_EOT) { + if (gpii->qspi_deferred_vd) { + dev_warn(gpii->gpi_dev->dev, + "SP11 QSPI ignoring duplicate TX EOT while completion is deferred\n"); + return; + } + + gpii->qspi_deferred_vd = vd; + gpii->qspi_deferred_gchan = gchan; + gpii->qspi_deferred_result = result; + WRITE_ONCE(gpii->qspi_deferred_grace_elapsed, false); + mod_timer(&gpii->qspi_deferred_timer, jiffies + 1); + dev_dbg(gpii->gpi_dev->dev, + "SP11 QSPI deferring TX EOT until QUP notification residue:%u\n", + result.residue); + return; + } + dma_cookie_complete(&vd->tx); dmaengine_desc_get_callback_invoke(&vd->tx, &result); @@ -1081,7 +1261,7 @@ static void gpi_process_events(struct gpii *gpii) struct gchan *gchan; u32 chid, type; - cntxt_rp = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg); + cntxt_rp = gpi_read_ev_rp(gpii); rp = to_virtual(ev_ring, cntxt_rp); do { @@ -1095,7 +1275,6 @@ static void gpi_process_events(struct gpii *gpii) chid, type, gpi_event->gpi_ere.dword[0], gpi_event->gpi_ere.dword[1], gpi_event->gpi_ere.dword[2], gpi_event->gpi_ere.dword[3]); - switch (type) { case XFER_COMPLETE_EV_TYPE: gchan = &gpii->gchan[chid]; @@ -1111,7 +1290,14 @@ static void gpi_process_events(struct gpii *gpii) &gpi_event->immediate_data_event); break; case QUP_NOTIF_EV_TYPE: - dev_dbg(gpii->gpi_dev->dev, "QUP_NOTIF_EV_TYPE\n"); + if (gpii_has_active_qspi(gpii)) { + struct qup_notif_event *notif; + + notif = &gpi_event->qup_notif_event; + gpi_qspi_complete_deferred(gpii, notif); + } else { + dev_dbg(gpii->gpi_dev->dev, "QUP_NOTIF_EV_TYPE\n"); + } break; default: dev_dbg(gpii->gpi_dev->dev, @@ -1124,7 +1310,7 @@ static void gpi_process_events(struct gpii *gpii) /* clear pending IEOB events */ gpi_write_reg(gpii, gpii->ieob_clr_reg, BIT(0)); - cntxt_rp = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg); + cntxt_rp = gpi_read_ev_rp(gpii); rp = to_virtual(ev_ring, cntxt_rp); } while (rp != ev_ring->rp); @@ -1145,24 +1331,40 @@ static void gpi_ev_tasklet(unsigned long data) /* process the events */ gpi_process_events(gpii); + if (READ_ONCE(gpii->qspi_deferred_grace_elapsed) && + READ_ONCE(gpii->qspi_deferred_vd)) + gpi_qspi_complete_deferred(gpii, NULL); /* enable IEOB, switching back to interrupts */ gpi_config_interrupts(gpii, MASK_IEOB_SETTINGS, 1); read_unlock(&gpii->pm_lock); } +static void gpi_qspi_deferred_timer(struct timer_list *timer) +{ + struct gpii *gpii = timer_container_of(gpii, timer, + qspi_deferred_timer); + + if (READ_ONCE(gpii->qspi_deferred_vd)) { + WRITE_ONCE(gpii->qspi_deferred_grace_elapsed, true); + dev_dbg(gpii->gpi_dev->dev, + "SP11 QSPI grace period elapsed; rescheduling deferred TX\n"); + tasklet_schedule(&gpii->ev_task); + } +} + /* marks all pending events for the channel as stale */ static void gpi_mark_stale_events(struct gchan *gchan) { struct gpii *gpii = gchan->gpii; struct gpi_ring *ev_ring = &gpii->ev_ring; - u32 cntxt_rp, local_rp; + phys_addr_t cntxt_rp, local_rp; void *ev_rp; - cntxt_rp = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg); + cntxt_rp = gpi_read_ev_rp(gpii); ev_rp = ev_ring->rp; - local_rp = (u32)to_physical(ev_ring, ev_rp); + local_rp = to_physical(ev_ring, ev_rp); while (local_rp != cntxt_rp) { union gpi_event *gpi_event = ev_rp; u32 chid = gpi_event->xfer_compl_event.chid; @@ -1172,8 +1374,8 @@ static void gpi_mark_stale_events(struct gchan *gchan) ev_rp += ev_ring->el_size; if (ev_rp >= (ev_ring->base + ev_ring->len)) ev_rp = ev_ring->base; - cntxt_rp = gpi_read_reg(gpii, gpii->ev_ring_rp_lsb_reg); - local_rp = (u32)to_physical(ev_ring, ev_rp); + cntxt_rp = gpi_read_ev_rp(gpii); + local_rp = to_physical(ev_ring, ev_rp); } } @@ -1275,10 +1477,22 @@ static int gpi_alloc_chan(struct gchan *chan, bool send_alloc_cmd) upper_32_bits(ring->phys_addr)); gpi_write_reg(gpii, chan->ch_cntxt_db_reg + CNTXT_5_RING_RP_MSB - CNTXT_4_RING_RP_LSB, upper_32_bits(ring->phys_addr)); - gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_0_OFFS(id, chid), - GPII_n_CH_k_SCRATCH_0(pair_chid, chan->protocol, chan->seid)); - gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_1_OFFS(id, chid), 0); - gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_2_OFFS(id, chid), 0); + if (chan->protocol == QCOM_GPI_QSPI) { + u32 scratch0 = chid == GPI_TX_CHAN ? + GPII_n_CH_k_SCRATCH_0_QSPI_TX : + GPII_n_CH_k_SCRATCH_0_QSPI_RX; + + gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_0_OFFS(id, chid), + scratch0); + gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_1_OFFS(id, chid), 0); + gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_2_OFFS(id, chid), + GPII_n_CH_k_SCRATCH_2_QSPI); + } else { + gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_0_OFFS(id, chid), + GPII_n_CH_k_SCRATCH_0(pair_chid, chan->protocol, chan->seid)); + gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_1_OFFS(id, chid), 0); + gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_2_OFFS(id, chid), 0); + } gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_SCRATCH_3_OFFS(id, chid), 0); gpi_write_reg(gpii, gpii->regs + GPII_n_CH_k_QOS_OFFS(id, chid), 1); @@ -1454,6 +1668,20 @@ static void gpi_queue_xfer(struct gpii *gpii, struct gchan *gchan, *wp = ch_tre; } +static void gpi_qspi_clear_deferred(struct gpii *gpii) +{ + if (!gpii->qspi_deferred_vd) + return; + timer_delete_sync(&gpii->qspi_deferred_timer); + + dev_warn(gpii->gpi_dev->dev, + "SP11 QSPI clearing stale deferred TX completion on terminate/reset\n"); + + gpii->qspi_deferred_vd = NULL; + gpii->qspi_deferred_gchan = NULL; + WRITE_ONCE(gpii->qspi_deferred_grace_elapsed, false); +} + /* reset and restart transfer channel */ static int gpi_terminate_all(struct dma_chan *chan) { @@ -1464,6 +1692,9 @@ static int gpi_terminate_all(struct dma_chan *chan) mutex_lock(&gpii->ctrl_lock); + if (gchan->protocol == QCOM_GPI_QSPI) + gpi_qspi_clear_deferred(gpii); + /* * treat both channels as a group if its protocol is not UART * STOP, RESET, or START needs to be in lockstep @@ -1715,44 +1946,82 @@ static int gpi_create_spi_tre(struct gchan *chan, struct gpi_desc *desc, tre = &desc->tre[tre_idx]; tre_idx++; - tre->dword[0] = u32_encode_bits(spi->word_len, TRE_SPI_C0_WORD_SZ); - tre->dword[0] |= u32_encode_bits(spi->loopback_en, TRE_SPI_C0_LOOPBACK); - tre->dword[0] |= u32_encode_bits(spi->clock_pol_high, TRE_SPI_C0_CPOL); - tre->dword[0] |= u32_encode_bits(spi->data_pol_high, TRE_SPI_C0_CPHA); - tre->dword[0] |= u32_encode_bits(spi->pack_en, TRE_SPI_C0_TX_PACK); - tre->dword[0] |= u32_encode_bits(spi->pack_en, TRE_SPI_C0_RX_PACK); + if (spi->qspi) { + tre->dword[0] = TRE_QSPI_CONFIG0_DW0; + } else { + tre->dword[0] = u32_encode_bits(spi->word_len, TRE_SPI_C0_WORD_SZ); + tre->dword[0] |= u32_encode_bits(spi->loopback_en, TRE_SPI_C0_LOOPBACK); + tre->dword[0] |= u32_encode_bits(spi->clock_pol_high, TRE_SPI_C0_CPOL); + tre->dword[0] |= u32_encode_bits(spi->data_pol_high, TRE_SPI_C0_CPHA); + tre->dword[0] |= u32_encode_bits(spi->pack_en, TRE_SPI_C0_TX_PACK); + tre->dword[0] |= u32_encode_bits(spi->pack_en, TRE_SPI_C0_RX_PACK); + } tre->dword[1] = 0; - tre->dword[2] = u32_encode_bits(spi->clk_div, TRE_C0_CLK_DIV); - tre->dword[2] |= u32_encode_bits(spi->clk_src, TRE_C0_CLK_SRC); + if (spi->qspi) { + tre->dword[2] = TRE_QSPI_CONFIG0_DW2; + } else { + tre->dword[2] = u32_encode_bits(spi->clk_div, TRE_C0_CLK_DIV); + tre->dword[2] |= u32_encode_bits(spi->clk_src, TRE_C0_CLK_SRC); + } tre->dword[3] = u32_encode_bits(TRE_TYPE_CONFIG0, TRE_FLAGS_TYPE); tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN); + if (spi->qspi) + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB); } - /* create the GO tre for Tx */ + /* + * SP11 QSPI Windows ring captures show TX uses CONFIG0/GO/DMA, while + * RX ring entries are DMA-only. Do not synthesize a GO TRE on RX. + */ if (direction == DMA_MEM_TO_DEV) { tre = &desc->tre[tre_idx]; tre_idx++; - tre->dword[0] = u32_encode_bits(spi->fragmentation, TRE_SPI_GO_FRAG); - tre->dword[0] |= u32_encode_bits(spi->cs, TRE_SPI_GO_CS); - tre->dword[0] |= u32_encode_bits(spi->cmd, TRE_SPI_GO_CMD); + if (spi->qspi) { + /* + * qcspi8380 emits raw QGPI command 1 for QSPI TX-only and + * command 7 when TX and RX both participate. Command 5 was + * an incorrect interpretation of the Windows builder and is + * rejected before the TX DMA TRE is consumed. + */ + tre->dword[0] = spi->rx_len ? TRE_QSPI_GO_BIDI_DW0 : + TRE_QSPI_GO_TX_DW0; + } else { + tre->dword[0] = u32_encode_bits(spi->fragmentation, TRE_SPI_GO_FRAG); + tre->dword[0] |= u32_encode_bits(spi->cs, TRE_SPI_GO_CS); + tre->dword[0] |= u32_encode_bits(spi->cmd, TRE_SPI_GO_CMD); + } tre->dword[1] = 0; + /* qcspi8380 places only the receive length in QSPI GO dword 2. */ tre->dword[2] = u32_encode_bits(spi->rx_len, TRE_RX_LEN); tre->dword[3] = u32_encode_bits(TRE_TYPE_GO, TRE_FLAGS_TYPE); - if (spi->cmd == SPI_RX) { + if (spi->qspi) { + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN); + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB); + /* + * Windows QGPI couples its channel contexts implicitly. The + * Linux GPI context needs LINK on a bidirectional QSPI GO or + * the pre-doorbelled RX ring never advances. + */ + if (spi->rx_len) + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_LINK); + } else if (spi->cmd == SPI_RX) { tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB); tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_LINK); } else if (spi->cmd == SPI_TX) { tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN); } else { /* SPI_DUPLEX */ tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN); - tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_LINK); + if (!spi->rx_len) + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_LINK); + else + tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB); } } @@ -1764,7 +2033,7 @@ static int gpi_create_spi_tre(struct gchan *chan, struct gpi_desc *desc, len = sg_dma_len(sgl); /* Support Immediate dma for write transfers for data length up to 8 bytes */ - if (direction == DMA_MEM_TO_DEV && len <= 2 * sizeof(tre->dword[0])) { + if (!spi->qspi && direction == DMA_MEM_TO_DEV && len <= 2 * sizeof(tre->dword[0])) { /* * For Immediate dma, data length may not always be length of 8 bytes, * it can be length less than 8, hence initialize both dword's with 0 @@ -1783,12 +2052,20 @@ static int gpi_create_spi_tre(struct gchan *chan, struct gpi_desc *desc, tre->dword[3] = u32_encode_bits(TRE_TYPE_DMA, TRE_FLAGS_TYPE); } - tre->dword[3] |= u32_encode_bits(direction == DMA_MEM_TO_DEV, + tre->dword[3] |= u32_encode_bits(spi->qspi || direction == DMA_MEM_TO_DEV, TRE_FLAGS_IEOT); for (i = 0; i < tre_idx; i++) - dev_dbg(dev, "TRE:%d %x:%x:%x:%x\n", i, desc->tre[i].dword[0], - desc->tre[i].dword[1], desc->tre[i].dword[2], desc->tre[i].dword[3]); + if (spi->qspi) + dev_dbg_ratelimited(dev, "SP11 QSPI TRE:%d %08x:%08x:%08x:%08x\n", + i, desc->tre[i].dword[0], + desc->tre[i].dword[1], + desc->tre[i].dword[2], + desc->tre[i].dword[3]); + else + dev_dbg(dev, "TRE:%d %x:%x:%x:%x\n", i, desc->tre[i].dword[0], + desc->tre[i].dword[1], desc->tre[i].dword[2], + desc->tre[i].dword[3]); return tre_idx; } @@ -1841,7 +2118,7 @@ gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, return NULL; /* create TREs for xfer */ - if (gchan->protocol == QCOM_GPI_SPI) { + if (gchan->protocol == QCOM_GPI_SPI || gchan->protocol == QCOM_GPI_QSPI) { i = gpi_create_spi_tre(gchan, gpi_desc, sgl, direction); } else if (gchan->protocol == QCOM_GPI_I2C) { i = gpi_create_i2c_tre(gchan, gpi_desc, sgl, direction, flags); @@ -1887,6 +2164,7 @@ static void gpi_issue_pending(struct dma_chan *chan) } gpi_desc = to_gpi_desc(vd); + for (i = 0; i < gpi_desc->num_tre; i++) { tre = &gpi_desc->tre[i]; gpi_queue_xfer(gpii, gchan, tre, &wp); @@ -2028,6 +2306,7 @@ static void gpi_free_chan_resources(struct dma_chan *chan) write_unlock_irq(&gpii->pm_lock); /* wait for threads to complete out */ + timer_delete_sync(&gpii->qspi_deferred_timer); tasklet_kill(&gpii->ev_task); /* send command to de allocate event ring */ @@ -2249,6 +2528,8 @@ static int gpi_probe(struct platform_device *pdev) rwlock_init(&gpii->pm_lock); tasklet_init(&gpii->ev_task, gpi_ev_tasklet, (unsigned long)gpii); + timer_setup(&gpii->qspi_deferred_timer, + gpi_qspi_deferred_timer, 0); init_completion(&gpii->cmd_completion); gpii->gpii_id = i; gpii->regs = gpi_dev->ee_base; diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h index 0fd8e7d9074275..3da146477f69b7 100644 --- a/drivers/hid/bpf/progs/hid_bpf_helpers.h +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -148,6 +148,7 @@ DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); #define BUS_AMIGA 0x16 #define BUS_ADB 0x17 #define BUS_I2C 0x18 +#define BUS_SPI 0x1C #define BUS_HOST 0x19 #define BUS_GSC 0x1A #define BUS_ATARI 0x1B @@ -169,6 +170,7 @@ DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock); #define HID_GROUP_STEAM 0x0103 #define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104 #define HID_GROUP_VIVALDI 0x0105 +#define HID_GROUP_MSHW0485_IPTS 0x0106 /* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */ #define HID_VID_ANY 0x0000 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index d6fbc2111facdd..e5c70bc2b72c7e 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2377,6 +2377,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) case BUS_I2C: bus = "I2C"; break; + case BUS_SPI: + bus = "SPI"; + break; case BUS_SDW: bus = "SOUNDWIRE"; break; diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 9b9ae8ac3f7fd3..2194b7d8a277de 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1414,4 +1414,39 @@ config TOUCHSCREEN_HIMAX_HX83112B To compile this driver as a module, choose M here: the module will be called himax_hx83112b. +config TOUCHSCREEN_MSHW0485 + tristate "Microsoft Surface G6 MSHW0485 touchscreen" + depends on SPI && OF + help + Say Y here to enable support for the MSHW0485 touchscreen found + on the Microsoft Surface Pro 11. + + To compile this driver as a module, choose M here: the module will + be called mshw0485_touch. + +config TOUCHSCREEN_MSHW0485_HEAT_FRAME + bool "MSHW0485 raw heat-frame userspace ABI" + depends on TOUCHSCREEN_MSHW0485 + default n + help + Expose raw MSHW0485 heat and sideband reports through the + /dev/g6ts-heat misc-device ABI. The touchscreen node must also carry + the microsoft,enable-heat-frame-abi property before the endpoint is + registered. + + If unsure, say N. + +config TOUCHSCREEN_MSHW0485_IPTSD + bool "MSHW0485 HIDRAW bridge for iptsd" + depends on TOUCHSCREEN_MSHW0485 && HID && HIDRAW + depends on HID=y || TOUCHSCREEN_MSHW0485=m + default n + help + Expose the native MSHW0485 DFT stream through a HIDRAW-only sibling + for userspace pen processing by iptsd. The touchscreen node must also + carry the microsoft,enable-iptsd-bridge property before the endpoint + is registered. + + If unsure, say N. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index bfd9de83389d88..8a116a707f6ff0 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -119,3 +119,4 @@ obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o obj-$(CONFIG_TOUCHSCREEN_IQS7211) += iqs7211.o obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX83112B) += himax_hx83112b.o +obj-$(CONFIG_TOUCHSCREEN_MSHW0485) += mshw0485_touch.o diff --git a/drivers/input/touchscreen/g6ts_classifier_profile.h b/drivers/input/touchscreen/g6ts_classifier_profile.h new file mode 100644 index 00000000000000..b95ab191b243f9 --- /dev/null +++ b/drivers/input/touchscreen/g6ts_classifier_profile.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Generated by tools/generate_classifier_header.py; do not edit. */ +#ifndef _G6TS_CLASSIFIER_PROFILE_H +#define _G6TS_CLASSIFIER_PROFILE_H + +#define G6TS_CLASS_COUNT 4U +#define G6TS_FEATURE_COUNT 10U +#define G6TS_CLASSIFIER_SHIFT 12U + +struct g6ts_classifier_model { + u16 max_points; + s32 score_offset_q12; + s32 means_q12[G6TS_FEATURE_COUNT]; + s32 transform_q12[G6TS_FEATURE_COUNT][G6TS_FEATURE_COUNT]; +}; + +static const struct g6ts_classifier_model g6ts_classifier_models[] = { + { + .max_points = 25, + .score_offset_q12 = 69201, + .means_q12 = { + 23673, 15143, 11507, 9733, 4096, 4210, 4314, 5825, 4248, 1257, + }, + .transform_q12 = { + { 4436, -4353, -552, -403, 0, -6991, -1734, -11172, 31879, 2290 }, + { 0, 4722, -4650, -402, 0, -21389, -3782, -1423, 4129, -683 }, + { 0, 0, 6558, -6653, 0, 16454, -18713, -2170, 4013, -2251 }, + { 0, 0, 0, 3411, 0, 673, 2973, -4213, 10377, -8562 }, + { 0, 0, 0, 0, 12952689, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 34310, -14872, -2125, 1078, 205 }, + { 0, 0, 0, 0, 0, 0, 19755, -7406, 5165, -4256 }, + { 0, 0, 0, 0, 0, 0, 0, 18819, -25979, -4623 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 21619, -2922 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 17268 }, + }, + }, + { + .max_points = 9999, + .score_offset_q12 = 22537, + .means_q12 = { + 44522, 26295, 20014, 16012, 4290, 4502, 4878, 9293, 5060, 6484, + }, + .transform_q12 = { + { 2651, -2405, -791, -287, -7039, -765, -1487, -10236, 21680, 2919 }, + { 0, 3189, -3490, -145, 3092, -6707, -2049, -2082, 2032, 651 }, + { 0, 0, 3720, -4152, -1126, 6598, -9724, -1214, -110, -525 }, + { 0, 0, 0, 1699, 38, 1352, 1558, -6660, 5435, -2881 }, + { 0, 0, 0, 0, 21196, -3134, -583, -511, -3286, 157 }, + { 0, 0, 0, 0, 0, 15050, -5342, -295, -1551, 679 }, + { 0, 0, 0, 0, 0, 0, 8604, -2753, -3285, 297 }, + { 0, 0, 0, 0, 0, 0, 0, 12285, -20929, 2906 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 18123, -3031 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 9982 }, + }, + }, + { + .max_points = 30, + .score_offset_q12 = 20818, + .means_q12 = { + 49521, 31956, 23366, 17379, 4602, 5687, 6891, 12408, 6246, 3383, + }, + .transform_q12 = { + { 3114, -2822, -403, -341, -4259, -401, -869, -15060, 38505, 1502 }, + { 0, 3100, -2802, -323, 8054, -5654, -399, 497, -3197, 691 }, + { 0, 0, 3909, -3577, 590, 9073, -7027, 399, -4118, -315 }, + { 0, 0, 0, 2544, 2168, 1200, 4596, -3374, 3258, -2384 }, + { 0, 0, 0, 0, 12320, -3800, 597, -1647, 1344, 1258 }, + { 0, 0, 0, 0, 0, 8543, -4070, -1598, 923, 1229 }, + { 0, 0, 0, 0, 0, 0, 5923, -990, -3679, 2912 }, + { 0, 0, 0, 0, 0, 0, 0, 17840, -40118, 859 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 15241, -2536 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 13165 }, + }, + }, + { + .max_points = 50, + .score_offset_q12 = 15612, + .means_q12 = { + 73118, 15909, 11125, 8869, 12805, 13176, 12635, 20448, 12870, 2737, + }, + .transform_q12 = { + { 2283, -2597, -257, -722, -13265, -2680, -428, 181, 4916, 282 }, + { 0, 4070, -2869, -598, 6445, -4628, -233, 317, -1793, 675 }, + { 0, 0, 6301, -4724, 133, 550, -1225, 278, 240, 169 }, + { 0, 0, 0, 5560, -774, 462, -806, -5, 2343, -2665 }, + { 0, 0, 0, 0, 12494, -6339, -1114, 782, -5492, -331 }, + { 0, 0, 0, 0, 0, 10192, -5053, 688, -6344, 549 }, + { 0, 0, 0, 0, 0, 0, 7047, -332, -5038, -792 }, + { 0, 0, 0, 0, 0, 0, 0, 4286, -5370, 2891 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 4363, -8900 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 11001 }, + }, + }, +}; + +#endif /* _G6TS_CLASSIFIER_PROFILE_H */ diff --git a/drivers/input/touchscreen/g6ts_lifecycle_profile.h b/drivers/input/touchscreen/g6ts_lifecycle_profile.h new file mode 100644 index 00000000000000..fcb343a560eeec --- /dev/null +++ b/drivers/input/touchscreen/g6ts_lifecycle_profile.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Generated by tools/generate_lifecycle_header.py; do not edit. */ +#ifndef _G6TS_LIFECYCLE_PROFILE_H +#define _G6TS_LIFECYCLE_PROFILE_H + +#define G6TS_WINDOWS_UNCLASSIFIED 4U +#define G6TS_WINDOWS_HISTORY_CAPACITY 10U +#define G6TS_WINDOWS_SCORE_SHIFT 24U +#define G6TS_WINDOWS_RUNTIME_OFFSET_Q24 -177430463LL +#define G6TS_WINDOWS_SCORE3_PRIMARY_Q24 838860800LL +#define G6TS_WINDOWS_SCORE3_SINGLE_Q24 335544320LL +#define G6TS_WINDOWS_ASSIGN_X_SCALE_Q24 68082944U +#define G6TS_WINDOWS_ASSIGN_Y_SCALE_Q24 67306464U +#define G6TS_WINDOWS_ASSIGN_RADIUS 5U + +struct g6ts_transition_rule { + s32 history_margins[G6TS_CLASS_COUNT]; + s32 current_margins[G6TS_CLASS_COUNT]; + s16 absolute_minimum; + s8 context_score_bias; + u8 history_depth; + u8 initial_age_limit; + u8 context_allowed; + u8 context_extra_history; +}; + +static const u16 g6ts_class_point_minimums[G6TS_CLASS_COUNT] = { 0, 6, 4, 4 }; +static const u16 g6ts_class_point_maximums[G6TS_CLASS_COUNT] = { 20, 9999, 50, 100 }; + +static const struct g6ts_transition_rule g6ts_transition_rules[] = { + /* 0 -> 0 */ + { + .history_margins = { 0, 0, 0, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = 0, + .context_score_bias = 2, + .history_depth = 0, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 0 -> 1 */ + { + .history_margins = { 20, 0, 10, 30 }, + .current_margins = { 100, 0, 10, 30 }, + .absolute_minimum = -500, + .context_score_bias = 2, + .history_depth = 5, + .initial_age_limit = 3, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 0 -> 2 */ + { + .history_margins = { 2, 30, 0, 30 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -25, + .context_score_bias = 2, + .history_depth = 2, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 0 -> 3 */ + { + .history_margins = { 200, 15, 7, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -45, + .context_score_bias = 2, + .history_depth = 1, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 1 -> 0 */ + { + .history_margins = { 0, 5, 5, 40 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -2, + .context_score_bias = 2, + .history_depth = 8, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 1 -> 1 */ + { + .history_margins = { 0, 0, 0, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = 0, + .context_score_bias = 2, + .history_depth = 0, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 1 -> 2 */ + { + .history_margins = { 1, 30, 0, 40 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -25, + .context_score_bias = 2, + .history_depth = 4, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 1 -> 3 */ + { + .history_margins = { 30, 30, 30, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -20, + .context_score_bias = 2, + .history_depth = 30, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 2 -> 0 */ + { + .history_margins = { 0, 15, 1, 15 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -25, + .context_score_bias = 2, + .history_depth = 2, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 2 -> 1 */ + { + .history_margins = { 40, 0, 80, 30 }, + .current_margins = { 100, 0, 80, 80 }, + .absolute_minimum = -500, + .context_score_bias = 2, + .history_depth = 5, + .initial_age_limit = 3, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 2 -> 2 */ + { + .history_margins = { 0, 0, 0, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = 0, + .context_score_bias = 2, + .history_depth = 0, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 2 -> 3 */ + { + .history_margins = { 50, 10, 30, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -15, + .context_score_bias = 2, + .history_depth = 4, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 3 -> 0 */ + { + .history_margins = { 0, 15, 1, 15 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -25, + .context_score_bias = 2, + .history_depth = 1, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 3 -> 1 */ + { + .history_margins = { 20, 0, 40, 100 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -40, + .context_score_bias = 2, + .history_depth = 5, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 3 -> 2 */ + { + .history_margins = { 2, 12, 0, 30 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -25, + .context_score_bias = 2, + .history_depth = 3, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 3 -> 3 */ + { + .history_margins = { 0, 0, 0, 0 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = 0, + .context_score_bias = 2, + .history_depth = 0, + .initial_age_limit = 0, + .context_allowed = 1, + .context_extra_history = 0, + }, + /* 4 -> 0 */ + { + .history_margins = { 0, 6, 1, 30 }, + .current_margins = { 0, 10, 1, 30 }, + .absolute_minimum = -3, + .context_score_bias = 0, + .history_depth = 2, + .initial_age_limit = 3, + .context_allowed = 0, + .context_extra_history = 3, + }, + /* 4 -> 1 */ + { + .history_margins = { 22, 0, 15, 15 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -9999, + .context_score_bias = 2, + .history_depth = 2, + .initial_age_limit = 0, + .context_allowed = 0, + .context_extra_history = 0, + }, + /* 4 -> 2 */ + { + .history_margins = { 1, 3, 0, 8 }, + .current_margins = { 255, 255, 255, 255 }, + .absolute_minimum = -8, + .context_score_bias = 2, + .history_depth = 4, + .initial_age_limit = 3, + .context_allowed = 0, + .context_extra_history = 2, + }, + /* 4 -> 3 */ + { + .history_margins = { 100, 7, 8, 0 }, + .current_margins = { 100, 30, 10, 0 }, + .absolute_minimum = -20, + .context_score_bias = 2, + .history_depth = 3, + .initial_age_limit = 3, + .context_allowed = 1, + .context_extra_history = 0, + }, +}; + +#endif /* _G6TS_LIFECYCLE_PROFILE_H */ diff --git a/drivers/input/touchscreen/mshw0485_touch.c b/drivers/input/touchscreen/mshw0485_touch.c new file mode 100644 index 00000000000000..dc9fb4a1f64340 --- /dev/null +++ b/drivers/input/touchscreen/mshw0485_touch.c @@ -0,0 +1,4336 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microsoft Surface G6 Touch (MSHW0485) touchscreen driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "g6ts_classifier_profile.h" +#include "g6ts_lifecycle_profile.h" + +#define G6TS_NAME "mshw0485-touch" +#define G6TS_SPI_HZ 40000000U +#define G6TS_MAX_BODY 8192U +#define G6TS_HEADER_SYNC 0x5a +#define G6TS_HEADER_VERSION 0x03 +#define G6TS_FEATURE_RESPONSE_LIMIT 64U +#define G6TS_IRQ_DRAIN_LIMIT 128U +#define G6TS_HEAT_QUEUE_CAPACITY 64U +#define G6TS_HEATMAP_REPORT_ID 0x12 +#define G6TS_RAW_SIDEBAND_REPORT_07 0x07 +#define G6TS_RAW_HEAT_REPORT_0B 0x0b +#define G6TS_RAW_HEAT_REPORT_0C 0x0c +#define G6TS_RAW_HEAT_REPORT_0D 0x0d +#define G6TS_RAW_HEAT_REPORT_1A 0x1a +#define G6TS_RAW_SIDEBAND_REPORT_6E 0x6e +#define G6TS_IPTS_MODE_REPORT_ID 0x05 +#define G6TS_IPTS_METADATA_REPORT_ID 0x06 +#define G6TS_IPTS_METADATA_LEN 119U +#define G6TS_PEN_REPORT_ID 0x01 +#define G6TS_PEN_REPORT_LEN 15U +#define G6TS_PEN_X_MAX 9600U +#define G6TS_PEN_Y_MAX 7200U +#define G6TS_PEN_PRESSURE_MAX 4096U +#define G6TS_PEN_TILT_RAW_MAX 18000U +#define G6TS_PEN_TILT_CENTER 9000 +#define G6TS_PEN_X_RESOLUTION 35U +#define G6TS_PEN_Y_RESOLUTION 39U +#define G6TS_PEN_TILT_RESOLUTION 5730U +#define G6TS_PEN_IN_RANGE BIT(0) +#define G6TS_PEN_TIP_SWITCH BIT(1) +#define G6TS_PEN_BARREL_SWITCH BIT(2) +#define G6TS_PEN_INVERT BIT(3) +#define G6TS_PEN_ERASER BIT(4) +#define G6TS_MODE_ATTEMPT_LIMIT 3U +#define G6TS_RECOVERY_DELAY_MS 100U +#define G6TS_RECOVERY_RETRY_MS 500U +#define G6TS_RECOVERY_LIMIT 3U +#define G6TS_HID_REBIND_STABLE_MS 250U +#define G6TS_HID_REBIND_RETRY_MS 1000U +#define G6TS_HID_REBIND_RETRY_LIMIT 2U +#define G6TS_RESET_STORM_WINDOW_MS 5000U +#define G6TS_RESET_STORM_LIMIT 3U +#define G6TS_HEAT_ROWS 46U +#define G6TS_HEAT_COLS 68U +#define G6TS_HEAT_SAMPLES (G6TS_HEAT_ROWS * G6TS_HEAT_COLS) +#define G6TS_HEAT_SECTION 0x0100 +#define G6TS_METADATA_SECTION 0xff00 +#define G6TS_NSR_BINS 16U +#define G6TS_NSR_CUTOFF 655U +/* + * TouchPenProcessor0C83.dll converts a calibrated byte through a linear + * lookup whose zero crossing is approximately 180. The SP11 configuration + * is inferred to start a candidate at raw <= 171. Its scan-line union joins + * only edge-adjacent cells. One- and two-cell candidates survive only when + * their peak is stronger than the secondary detector threshold (approximately + * raw <= 162); all candidates of three or more cells continue downstream. + */ +#define G6TS_HEAT_SIGNAL_ZERO 180U +#define G6TS_HEAT_THRESHOLD 9U +#define G6TS_HEAT_ACTIVE_MAX (G6TS_HEAT_SIGNAL_ZERO - G6TS_HEAT_THRESHOLD) +#define G6TS_HEAT_STRONG_MAX 162U +#define G6TS_HEAT_MIN_PIXELS 3U +#define G6TS_HEAT_PALM_PIXELS 48U +#define G6TS_HEAT_PALM_SPAN 12U +#define G6TS_MAX_CONTACTS 10U +#define G6TS_LOGICAL_MAX 32767U +#define G6TS_TRACK_MATCH_MAX 4096U +#define G6TS_CONTACT_HOLD_FRAMES 6U +#define G6TS_TRACK_CONFIRM_NORMAL 3U +#define G6TS_TRACK_CONFIRM_WEAK 5U +#define G6TS_TRACK_CONFIRM_SPLIT 8U +#define G6TS_TRACK_SPLIT_RADIUS 2048U +#define G6TS_BEHAVIOR_CONFIRM_NORMAL 2U +#define G6TS_WINDOWS_CENTROID_BASELINE 171U +#define G6TS_SMOOTH_STATIONARY_MAX 64U +#define G6TS_SMOOTH_SLOW_MAX 256U +#define G6TS_SIGNAL_INTERCEPT_Q24 6710886 +#define G6TS_SIGNAL_STEP_Q24 37251 +#define G6TS_SECONDARY_A_Q12 645663 +#define G6TS_SECONDARY_NOISE_Q12 36895 +#define G6TS_SECONDARY_SEED_Q12 225 +#define G6TS_LOCAL_PEAK_FLOOR_Q12 164 +#define G6TS_LOCAL_PEAK_CAPACITY 10U +#define G6TS_AXIS_SCALE_Q12 18919 +#define G6TS_SPREAD_SCALE_Q12 25736 +#define G6TS_HALO_RATIO_Q12 614 +#define G6TS_HALO_UNAVAILABLE_Q12 (100U << G6TS_CLASSIFIER_SHIFT) +#define G6TS_ASSIGN_MAX (G6TS_MAX_CONTACTS * 2U) +#define G6TS_ASSIGN_UNMATCHED_COST 1000000 +#define G6TS_ASSIGN_INVALID_COST 3000000 + +/* Exact SP11 descriptor values shared by the supported X1E and X1P panels. */ +#define G6TS_SP11_REPORT_DESCRIPTOR_LEN 1484U +#define G6TS_SP11_MAX_INPUT_LEN 8192U +#define G6TS_SP11_MAX_OUTPUT_LEN 512U +#define G6TS_SP11_MAX_FRAGMENT_LEN 8192U +#define G6TS_SP11_VENDOR_ID 0x045eU +#define G6TS_SP11_X1E_PRODUCT_ID 0x0c83U +#define G6TS_SP11_X1P_PRODUCT_ID 0x0c80U +#define G6TS_SP11_VERSION_ID 0x0004U +#define G6TS_SP11_DESCRIPTOR_FLAGS 0x0001U +#define G6TS_WINDOWS_FEEDBACK_LEN 63U +#define G6TS_WINDOWS_REPORT56_ID_LEN 6U +#define G6TS_WINDOWS_CONFIG_DELAY_MS 470U +#define G6TS_WINDOWS_CFU_DELAY_MS 825U +#define G6TS_WINDOWS_FINAL_CONFIG_DELAY_MS 600U +#define G6TS_WINDOWS_CFU_OFFER_LEN 16U +#define G6TS_WINDOWS_CFU_VERSION_LEN 60U +#define G6TS_WINDOWS_CFU_TOKEN 0xa0U +#define G6TS_WINDOWS_HEADER_BODY_MIN_US 490U +#define G6TS_WINDOWS_HEADER_BODY_MAX_US 550U + +enum g6ts_initialization_stage { + G6TS_INIT_IDLE, + G6TS_INIT_WINDOWS_POWER_PS0, + G6TS_INIT_WINDOWS_RESET_METHOD, + G6TS_INIT_RESET_RESPONSE, + G6TS_INIT_DEVICE_DESCRIPTOR, + G6TS_INIT_REPORT_DESCRIPTOR, + G6TS_INIT_WINDOWS_EARLY_FEATURE73, + G6TS_INIT_WINDOWS_HEAT_CAPS06, + G6TS_INIT_WINDOWS_FEEDBACK_REQUIRED, + G6TS_INIT_WINDOWS_FEEDBACK_A1, + G6TS_INIT_WINDOWS_FEEDBACK_A5, + G6TS_INIT_WINDOWS_SET_FEATURE05, + G6TS_INIT_WINDOWS_CONFIG_OWNER_REQUIRED, + G6TS_INIT_WINDOWS_GET_FEATURE70, + G6TS_INIT_WINDOWS_SET_FEATURE70, + G6TS_INIT_WINDOWS_SET_FEATURE56, + G6TS_INIT_WINDOWS_CFU_OWNER_REQUIRED, + G6TS_INIT_WINDOWS_CFU_GET_VERSION, + G6TS_INIT_WINDOWS_CFU_START_TRANSACTION, + G6TS_INIT_WINDOWS_CFU_START_LIST, + G6TS_INIT_WINDOWS_CFU_OFFER, + G6TS_INIT_WINDOWS_CFU_BRANCH_REQUIRED, + G6TS_INIT_WINDOWS_CFU_END_LIST, + G6TS_INIT_WINDOWS_FINAL_FEATURE73, + G6TS_INIT_WINDOWS_HEAT_OWNER_REQUIRED, + G6TS_INIT_SET_FEATURE05, + G6TS_INIT_GET_FEATURE70, + G6TS_INIT_SET_FEATURE70, + G6TS_INIT_SET_FEATURE56, + G6TS_INIT_WAIT_HEAT, +}; + +#define G6TS_INIT_STAGE_COUNT (G6TS_INIT_WAIT_HEAT + 1) + +/* + * Keep the hardware-validated touch policy fixed. Experimental alternatives + * stay inaccessible until they have a reviewable kernel interface. + */ +static const bool g6ts_windows_orchestrator; +static const bool g6ts_heat_feedback; +static const bool g6ts_behavior_v2; +static const bool g6ts_mode_config_fix = true; +static const bool g6ts_feature70_one_byte; +static const bool g6ts_reset_recovery_v2; +static const bool g6ts_reset_storm_breaker; +static const bool g6ts_windows_init_parity; +static const bool g6ts_parity_linux_power; +static const bool g6ts_windows_read_cadence; +static const int g6ts_parity_display_bitmap = -1; +static const int g6ts_parity_stitching_flag = -1; +static const int g6ts_parity_hinge_angle = -1; +static const int g6ts_parity_fast_host_id = -1; +static const u8 g6ts_parity_report56_identity[G6TS_WINDOWS_REPORT56_ID_LEN]; +static const unsigned int g6ts_parity_report56_identity_count; +static const int g6ts_parity_report56_flag = -1; +static const bool g6ts_parity_cfu_inventory; +static const u8 g6ts_parity_cfu_offer[G6TS_WINDOWS_CFU_OFFER_LEN]; +static const unsigned int g6ts_parity_cfu_offer_count; +static const bool g6ts_parity_heat_input; + +enum g6ts_recovery_path { + G6TS_RECOVERY_HARDWARE, + G6TS_RECOVERY_SOFTWARE, +}; + +static const u8 g6ts_header_cmd[8] = { + 0xeb, 0x00, 0x10, 0x00, 0xff, 0xff, 0xff, 0xff, +}; + +static const u8 g6ts_body_cmd[8] = { + 0xeb, 0x00, 0x10, 0x04, 0xff, 0xff, 0xff, 0xff, +}; + +/* Windows HID-over-SPI DEVICE_DESCRIPTOR request captured in ETW. */ +static const u8 g6ts_device_descriptor_cmd[8] = { + 0xe2, 0x00, 0x20, 0x00, DEVICE_DESCRIPTOR, 0x00, 0x00, 0x00, +}; + +static const u8 g6ts_report_descriptor_cmd[8] = { + 0xe2, 0x00, 0x20, 0x00, REPORT_DESCRIPTOR, 0x00, 0x00, 0x00, +}; + +static const u8 g6ts_mode_enable[] = { 0x01 }; +/* SHA-256 fc5772d4...0bd58af; metadata only, never a firmware payload. */ +static const u8 g6ts_sp11_cfu_offer[G6TS_WINDOWS_CFU_OFFER_LEN] = { + 0x00, 0x00, 0x12, 0x00, 0x89, 0x14, 0x00, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0x04, 0x04, 0x75, 0x00, +}; + +static const u8 g6ts_mode_handshake[] = { + 0xbc, 0xe6, 0x4a, 0x2e, 0x86, 0x78, 0x00, +}; + +/* TouchPenProcessor project-0x0c83 sensor-row to NSR-bin mapping. */ +static const u8 g6ts_nsr_row_to_bin[G6TS_HEAT_ROWS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +}; + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) +struct g6ts_heat_frame { + struct g6ts_heat_record_header header; + u8 content[G6TS_HEAT_MAX_CONTENT_SIZE]; +}; + +static_assert(sizeof(struct g6ts_heat_record_header) == + G6TS_HEAT_RECORD_HEADER_SIZE); +static_assert(sizeof(struct g6ts_heat_info) == 48); +static_assert(sizeof(struct g6ts_heat_stats) == 112); + +struct g6ts_heat_device { + struct miscdevice miscdev; + /* Protects queue data and device lifetime state. */ + struct mutex lock; + wait_queue_head_t read_wait; + struct kref ref; + struct g6ts_heat_frame *frames; + unsigned int head; + unsigned int count; + u32 generation; + u32 sequence; + u64 records_enqueued; + u64 records_dropped; + u64 queue_flushes; + u64 oversize_drops; + u64 report_counts[4]; + atomic_t opened; + bool dead; +}; +#endif + +struct g6ts_a5_live_state { + u16 fast_host_id; + u8 sequence; + bool fast_host_id_valid; + bool cycle_saw_0x0d; +}; + +struct g6ts_contact { + u64 weighted_x; + u64 weighted_y; + u32 strength; + u32 sensor_x_q24; + u32 sensor_y_q24; + u16 pixels; + u16 x; + u16 y; + u16 output_x; + u16 output_y; + u8 peak_value; + u8 min_col; + u8 max_col; + u8 min_row; + u8 max_row; + s32 features_q12[G6TS_FEATURE_COUNT]; + s64 scores_q24[G6TS_CLASS_COUNT]; + u8 local_peak_count; + u8 strong_local_peak_count; + u8 shape_class; + bool shape_allowed; +}; + +struct g6ts_track { + u32 strength; + u16 raw_x; + u16 raw_y; + u16 output_x; + u16 output_y; + u16 pixels; + u32 sensor_x_q24; + u32 sensor_y_q24; + s32 sensor_velocity_x_q24; + s32 sensor_velocity_y_q24; + s16 velocity_x; + s16 velocity_y; + u16 age; + s64 score_history_q24[G6TS_WINDOWS_HISTORY_CAPACITY][G6TS_CLASS_COUNT]; + u8 missed; + u8 evidence; + u8 required_evidence; + u8 shape_class; + u8 windows_class; + u8 score_history_count; + bool active; + bool confirmed; +}; + +struct g6ts_assignment_workspace { + int cost[G6TS_ASSIGN_MAX][G6TS_ASSIGN_MAX]; + int u[G6TS_ASSIGN_MAX + 1]; + int v[G6TS_ASSIGN_MAX + 1]; + int p[G6TS_ASSIGN_MAX + 1]; + int way[G6TS_ASSIGN_MAX + 1]; + int minv[G6TS_ASSIGN_MAX + 1]; + bool used[G6TS_ASSIGN_MAX + 1]; + int active_slots[G6TS_MAX_CONTACTS]; +}; + +struct g6ts { + struct spi_device *spi; + struct input_dev *input; + struct g6ts_heat_device *heat; + struct hid_device *ipts_hid; + struct input_dev *pen_input; + struct touchscreen_properties prop; + struct touchscreen_properties pen_prop; + /* Serializes transport, initialization, recovery, and power changes. */ + struct mutex io_lock; + /* Serializes the Phase 84 IRQ enable depth across recovery and PM. */ + struct mutex irq_state_lock; + struct gpio_desc *interrupt_gpio; + struct gpio_desc *power_gpio; + struct gpio_desc *reset_gpio; + u8 *body; + u8 heatmap[G6TS_HEAT_SAMPLES]; + u8 heat_seen[G6TS_HEAT_SAMPLES]; + u8 heat_component[G6TS_HEAT_SAMPLES]; + u8 heat_work[G6TS_HEAT_SAMPLES]; + u16 heat_queue[G6TS_HEAT_SAMPLES]; + u16 nsr_bins[G6TS_NSR_BINS]; + struct g6ts_contact contacts[G6TS_MAX_CONTACTS]; + struct g6ts_track tracks[G6TS_MAX_CONTACTS]; + struct g6ts_assignment_workspace assignment; + struct delayed_work recovery_work; + struct delayed_work hid_rebind_work; + u8 last_header[HIDSPI_INPUT_HEADER_SIZE]; + u8 report_descriptor[G6TS_SP11_REPORT_DESCRIPTOR_LEN]; + u8 ipts_metadata[G6TS_IPTS_METADATA_LEN]; + u8 last_class; + u16 last_content_len; + u16 expected_report_descriptor_len; + u16 hid_product_id; + u8 last_content_id; + u32 hid_target_generation; + u32 hid_bound_generation; + u8 hid_rebind_failures; + /* Phase 72 Linux response content used by the combined experimental path. */ + u8 mode_config[G6TS_FEATURE_RESPONSE_LIMIT]; + u8 mode_config_len; + bool mode_config_valid; + int interrupt_irq; + atomic64_t interrupt_edges; + s64 handled_interrupt_edges; + u64 reset_notifications; + u64 recovery_successes; + u64 recovery_failures; + u64 hardware_recovery_attempts; + u64 software_recovery_attempts; + u64 software_recovery_fallbacks; + u64 reset_storm_escalations; + u64 host_fault_recoveries; + u64 irq_transport_errors; + u64 irq_protocol_errors; + u64 irq_drain_overflows; + u64 quiesced_empty_reads; + u64 cadence_single_response_irqs; + u64 ready_heat_frames; + u64 ready_verification_failures; + u64 report_counts[U8_MAX + 1]; + struct g6ts_a5_live_state a5; + u64 heat_feedback_sent; + u64 heat_feedback_phase_a; + u64 heat_feedback_phase_b; + u64 heat_frames; + u64 heat_errors; + u64 component_total; + u64 contact_total; + u64 weak_rejections; + u64 nsr_rejections; + u64 palm_rejections; + u64 assignment_matches; + u64 new_tracks; + u64 output_frames; + u64 output_contacts; + u64 processing_ns_total; + u64 processing_ns_max; + unsigned long last_reset_jiffies; + u8 nsr_bin_count; + enum g6ts_initialization_stage initialization_stage; + u8 recovery_fail_streak; + u8 rapid_reset_streak; + int last_host_fault; + enum g6ts_recovery_path recovery_path; + bool nsr_valid; + bool report_descriptor_valid; + bool ipts_metadata_valid; + bool ipts_hid_ready; + bool awaiting_ready_heat; + bool mode_enabled; + bool irq_enabled; + bool fatal_transport_error; + bool stopping; + bool quiescing; + bool heat_abi_enabled; + bool ipts_enabled; + bool parity_feedback_required; + bool parity_config_owner_required; + bool parity_cfu_owner_required; + bool parity_cfu_branch_required; + bool parity_heat_owner_required; + u8 parity_feature73_early[2]; + u8 parity_feature73_late[2]; + u8 parity_feature06_prefix[16]; + u8 parity_cfu_version_prefix[12]; + u8 parity_cfu_offer_response[G6TS_WINDOWS_CFU_OFFER_LEN]; + bool init_get_feature_dumped[G6TS_INIT_STAGE_COUNT]; +}; + +static bool g6ts_is_quiescing(const struct g6ts *ts) +{ + return READ_ONCE(ts->stopping) || READ_ONCE(ts->quiescing); +} + +static bool g6ts_is_raw_export_report(u8 report_id) +{ + switch (report_id) { + case G6TS_RAW_SIDEBAND_REPORT_07: + case G6TS_RAW_HEAT_REPORT_0B: + case G6TS_RAW_HEAT_REPORT_0C: + case G6TS_RAW_HEAT_REPORT_0D: + case G6TS_RAW_HEAT_REPORT_1A: + case G6TS_RAW_SIDEBAND_REPORT_6E: + return true; + default: + return false; + } +} + +/* + * iptsd consumes only the four core DFT reports. Keep sideband reports on the + * diagnostic ABI: report 0x6e can contain a device or pen identifier and is + * not needed by the stylus decoder. + */ +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) +static bool g6ts_is_ipts_hid_report(u8 report_id) +{ + switch (report_id) { + case G6TS_RAW_HEAT_REPORT_0B: + case G6TS_RAW_HEAT_REPORT_0C: + case G6TS_RAW_HEAT_REPORT_0D: + case G6TS_RAW_HEAT_REPORT_1A: + return true; + default: + return false; + } +} +#endif + +/* io_lock is held by every transport-generation caller. */ +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) +static void g6ts_hid_generation_boundary(struct g6ts *ts) +{ + ts->hid_target_generation++; + if (!ts->hid_target_generation) + ts->hid_target_generation = 1; + ts->hid_rebind_failures = 0; + + /* Never destroy hidraw from a feature ioctl or IRQ/reset call stack. */ + mod_delayed_work(system_wq, &ts->hid_rebind_work, 0); +} +#else +static void g6ts_hid_generation_boundary(struct g6ts *ts) +{ + (void)ts; +} +#endif + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) +static void g6ts_heat_device_release(struct kref *ref) +{ + struct g6ts_heat_device *heat = + container_of(ref, struct g6ts_heat_device, ref); + + kvfree(heat->frames); + kfree(heat); +} + +static void g6ts_heat_enqueue_locked(struct g6ts_heat_device *heat, + u8 report_id, u8 flags, + const u8 *content, u16 content_len) +{ + struct g6ts_heat_frame *frame; + unsigned int tail; + + if (heat->count == G6TS_HEAT_QUEUE_CAPACITY) { + heat->head = (heat->head + 1) % G6TS_HEAT_QUEUE_CAPACITY; + heat->count--; + heat->records_dropped++; + } + + tail = (heat->head + heat->count) % G6TS_HEAT_QUEUE_CAPACITY; + frame = &heat->frames[tail]; + frame->header.magic = cpu_to_le32(G6TS_HEAT_RECORD_MAGIC); + frame->header.abi_version = cpu_to_le16(G6TS_HEAT_ABI_VERSION); + frame->header.header_len = cpu_to_le16(sizeof(frame->header)); + frame->header.record_len = + cpu_to_le32(sizeof(frame->header) + content_len); + frame->header.generation = cpu_to_le32(heat->generation); + frame->header.timestamp_ns = cpu_to_le64(ktime_get_ns()); + frame->header.sequence = cpu_to_le32(heat->sequence++); + frame->header.content_len = cpu_to_le16(content_len); + frame->header.report_id = report_id; + frame->header.flags = flags; + if (content_len) + memcpy(frame->content, content, content_len); + + heat->count++; + heat->records_enqueued++; + switch (report_id) { + case G6TS_RAW_HEAT_REPORT_0B: + heat->report_counts[0]++; + break; + case G6TS_RAW_HEAT_REPORT_0C: + heat->report_counts[1]++; + break; + case G6TS_RAW_HEAT_REPORT_0D: + heat->report_counts[2]++; + break; + case G6TS_RAW_HEAT_REPORT_1A: + heat->report_counts[3]++; + break; + } +} + +static void g6ts_heat_enqueue(struct g6ts *ts, u8 report_id, + const u8 *content, u16 content_len) +{ + struct g6ts_heat_device *heat = ts->heat; + + if (!heat) + return; + + mutex_lock(&heat->lock); + if (heat->dead) { + mutex_unlock(&heat->lock); + return; + } + if (content_len > G6TS_HEAT_MAX_CONTENT_SIZE) { + heat->oversize_drops++; + mutex_unlock(&heat->lock); + return; + } + g6ts_heat_enqueue_locked(heat, report_id, 0, content, content_len); + mutex_unlock(&heat->lock); + wake_up_interruptible(&heat->read_wait); +} +#else +static void g6ts_heat_enqueue(struct g6ts *ts, u8 report_id, + const u8 *content, u16 content_len) +{ + (void)ts; + (void)report_id; + (void)content; + (void)content_len; +} +#endif + +static void g6ts_heat_generation_boundary(struct g6ts *ts, u8 flags) +{ +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) + struct g6ts_heat_device *heat = ts->heat; +#endif + + ts->a5.cycle_saw_0x0d = false; + g6ts_hid_generation_boundary(ts); + (void)flags; +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) + if (!heat) + return; + + mutex_lock(&heat->lock); + if (heat->dead) { + mutex_unlock(&heat->lock); + return; + } + heat->generation++; + if (!heat->generation) + heat->generation = 1; + heat->head = 0; + heat->count = 0; + heat->queue_flushes++; + g6ts_heat_enqueue_locked(heat, 0, flags, NULL, 0); + mutex_unlock(&heat->lock); + wake_up_interruptible(&heat->read_wait); +#endif +} + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) +static bool g6ts_heat_read_ready(struct g6ts_heat_device *heat) +{ + return READ_ONCE(heat->count) || READ_ONCE(heat->dead); +} + +static int g6ts_heat_open(struct inode *inode, struct file *file) +{ + struct miscdevice *miscdev = file->private_data; + struct g6ts_heat_device *heat = + container_of(miscdev, struct g6ts_heat_device, miscdev); + + if (!kref_get_unless_zero(&heat->ref)) + return -ENODEV; + if (atomic_cmpxchg(&heat->opened, 0, 1)) { + kref_put(&heat->ref, g6ts_heat_device_release); + return -EBUSY; + } + + mutex_lock(&heat->lock); + if (heat->dead) { + mutex_unlock(&heat->lock); + atomic_set(&heat->opened, 0); + kref_put(&heat->ref, g6ts_heat_device_release); + return -ENODEV; + } + file->private_data = heat; + mutex_unlock(&heat->lock); + return nonseekable_open(inode, file); +} + +static int g6ts_heat_release(struct inode *inode, struct file *file) +{ + struct g6ts_heat_device *heat = file->private_data; + + atomic_set(&heat->opened, 0); + kref_put(&heat->ref, g6ts_heat_device_release); + return 0; +} + +static ssize_t g6ts_heat_read(struct file *file, char __user *buffer, + size_t size, loff_t *offset) +{ + struct g6ts_heat_device *heat = file->private_data; + struct g6ts_heat_frame *frame; + size_t content_len; + size_t record_len; + int ret; + + for (;;) { + if (!(file->f_flags & O_NONBLOCK)) { + ret = wait_event_interruptible(heat->read_wait, + g6ts_heat_read_ready(heat)); + if (ret) + return ret; + } + + mutex_lock(&heat->lock); + if (heat->count) + break; + if (heat->dead) { + mutex_unlock(&heat->lock); + return 0; + } + mutex_unlock(&heat->lock); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + } + + frame = &heat->frames[heat->head]; + record_len = le32_to_cpu(frame->header.record_len); + content_len = le16_to_cpu(frame->header.content_len); + if (size < record_len) { + ret = -EMSGSIZE; + goto out_unlock; + } + if (copy_to_user(buffer, &frame->header, sizeof(frame->header)) || + (content_len && + copy_to_user(buffer + sizeof(frame->header), frame->content, + content_len))) { + ret = -EFAULT; + goto out_unlock; + } + + heat->head = (heat->head + 1) % G6TS_HEAT_QUEUE_CAPACITY; + heat->count--; + mutex_unlock(&heat->lock); + return record_len; + +out_unlock: + mutex_unlock(&heat->lock); + return ret; +} + +static __poll_t g6ts_heat_poll(struct file *file, poll_table *wait) +{ + struct g6ts_heat_device *heat = file->private_data; + __poll_t mask = 0; + + poll_wait(file, &heat->read_wait, wait); + mutex_lock(&heat->lock); + if (heat->count) + mask |= EPOLLIN | EPOLLRDNORM; + if (heat->dead) + mask |= EPOLLHUP; + mutex_unlock(&heat->lock); + return mask; +} + +static long g6ts_heat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct g6ts_heat_device *heat = file->private_data; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case G6TS_HEAT_IOC_GET_INFO: { + struct g6ts_heat_info info = { + .abi_version = cpu_to_le16(G6TS_HEAT_ABI_VERSION), + .struct_size = cpu_to_le16(sizeof(info)), + .record_header_size = + cpu_to_le16(sizeof(struct g6ts_heat_record_header)), + .max_content_size = + cpu_to_le32(G6TS_HEAT_MAX_CONTENT_SIZE), + .queue_capacity = + cpu_to_le32(G6TS_HEAT_QUEUE_CAPACITY), + .supported_record_flags = + cpu_to_le64(G6TS_HEAT_RECORD_F_RESET | + G6TS_HEAT_RECORD_F_SUSPEND | + G6TS_HEAT_RECORD_F_TRANSPORT_FAULT), + }; + + return copy_to_user(argp, &info, sizeof(info)) ? -EFAULT : 0; + } + case G6TS_HEAT_IOC_GET_STATS: { + struct g6ts_heat_stats stats = { + .abi_version = cpu_to_le16(G6TS_HEAT_ABI_VERSION), + .struct_size = cpu_to_le16(sizeof(stats)), + .queue_capacity = + cpu_to_le32(G6TS_HEAT_QUEUE_CAPACITY), + }; + + mutex_lock(&heat->lock); + stats.generation = cpu_to_le32(heat->generation); + stats.queued_records = cpu_to_le32(heat->count); + stats.records_enqueued = cpu_to_le64(heat->records_enqueued); + stats.records_dropped = cpu_to_le64(heat->records_dropped); + stats.queue_flushes = cpu_to_le64(heat->queue_flushes); + stats.oversize_drops = cpu_to_le64(heat->oversize_drops); + stats.report_0b = cpu_to_le64(heat->report_counts[0]); + stats.report_0c = cpu_to_le64(heat->report_counts[1]); + stats.report_0d = cpu_to_le64(heat->report_counts[2]); + stats.report_1a = cpu_to_le64(heat->report_counts[3]); + mutex_unlock(&heat->lock); + + return copy_to_user(argp, &stats, sizeof(stats)) ? -EFAULT : 0; + } + default: + return -ENOTTY; + } +} + +static const struct file_operations g6ts_heat_fops = { + .owner = THIS_MODULE, + .open = g6ts_heat_open, + .release = g6ts_heat_release, + .read = g6ts_heat_read, + .poll = g6ts_heat_poll, + .unlocked_ioctl = g6ts_heat_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = compat_ptr_ioctl, +#endif +}; + +static void g6ts_heat_unregister(void *data) +{ + struct g6ts_heat_device *heat = data; + + misc_deregister(&heat->miscdev); + mutex_lock(&heat->lock); + heat->dead = true; + heat->head = 0; + heat->count = 0; + mutex_unlock(&heat->lock); + wake_up_interruptible(&heat->read_wait); + kref_put(&heat->ref, g6ts_heat_device_release); +} + +static int g6ts_heat_register(struct g6ts *ts) +{ + struct g6ts_heat_device *heat; + int ret; + + heat = kzalloc_obj(*heat); + if (!heat) + return -ENOMEM; + heat->frames = kvcalloc(G6TS_HEAT_QUEUE_CAPACITY, + sizeof(*heat->frames), GFP_KERNEL); + if (!heat->frames) { + kfree(heat); + return -ENOMEM; + } + + mutex_init(&heat->lock); + init_waitqueue_head(&heat->read_wait); + kref_init(&heat->ref); + atomic_set(&heat->opened, 0); + heat->generation = 1; + heat->sequence = 1; + heat->miscdev.minor = MISC_DYNAMIC_MINOR; + heat->miscdev.name = "g6ts-heat"; + heat->miscdev.fops = &g6ts_heat_fops; + heat->miscdev.parent = &ts->spi->dev; + + ret = misc_register(&heat->miscdev); + if (ret) { + kref_put(&heat->ref, g6ts_heat_device_release); + return ret; + } + ts->heat = heat; + ret = devm_add_action_or_reset(&ts->spi->dev, g6ts_heat_unregister, + heat); + if (ret) + ts->heat = NULL; + return ret; +} +#endif + +static const char * +g6ts_initialization_stage_name(enum g6ts_initialization_stage stage) +{ + switch (stage) { + case G6TS_INIT_IDLE: + return "idle"; + case G6TS_INIT_WINDOWS_POWER_PS0: + return "windows-power-ps0"; + case G6TS_INIT_WINDOWS_RESET_METHOD: + return "windows-reset-method"; + case G6TS_INIT_RESET_RESPONSE: + return "reset-response"; + case G6TS_INIT_DEVICE_DESCRIPTOR: + return "device-descriptor"; + case G6TS_INIT_REPORT_DESCRIPTOR: + return "report-descriptor"; + case G6TS_INIT_WINDOWS_EARLY_FEATURE73: + return "windows-early-feature73"; + case G6TS_INIT_WINDOWS_HEAT_CAPS06: + return "windows-heat-caps06"; + case G6TS_INIT_WINDOWS_FEEDBACK_REQUIRED: + return "windows-feedback-required"; + case G6TS_INIT_WINDOWS_FEEDBACK_A1: + return "windows-feedback-a1"; + case G6TS_INIT_WINDOWS_FEEDBACK_A5: + return "windows-feedback-a5"; + case G6TS_INIT_WINDOWS_SET_FEATURE05: + return "windows-set-feature05"; + case G6TS_INIT_WINDOWS_CONFIG_OWNER_REQUIRED: + return "windows-config-owner-required"; + case G6TS_INIT_WINDOWS_GET_FEATURE70: + return "windows-get-feature70"; + case G6TS_INIT_WINDOWS_SET_FEATURE70: + return "windows-set-feature70"; + case G6TS_INIT_WINDOWS_SET_FEATURE56: + return "windows-set-feature56"; + case G6TS_INIT_WINDOWS_CFU_OWNER_REQUIRED: + return "windows-cfu-owner-required"; + case G6TS_INIT_WINDOWS_CFU_GET_VERSION: + return "windows-cfu-get-version"; + case G6TS_INIT_WINDOWS_CFU_START_TRANSACTION: + return "windows-cfu-start-transaction"; + case G6TS_INIT_WINDOWS_CFU_START_LIST: + return "windows-cfu-start-list"; + case G6TS_INIT_WINDOWS_CFU_OFFER: + return "windows-cfu-offer"; + case G6TS_INIT_WINDOWS_CFU_BRANCH_REQUIRED: + return "windows-cfu-branch-required"; + case G6TS_INIT_WINDOWS_CFU_END_LIST: + return "windows-cfu-end-list"; + case G6TS_INIT_WINDOWS_FINAL_FEATURE73: + return "windows-final-feature73"; + case G6TS_INIT_WINDOWS_HEAT_OWNER_REQUIRED: + return "windows-heat-owner-required"; + case G6TS_INIT_SET_FEATURE05: + return "set-feature05"; + case G6TS_INIT_GET_FEATURE70: + return "get-feature70"; + case G6TS_INIT_SET_FEATURE70: + return "set-feature70"; + case G6TS_INIT_SET_FEATURE56: + return "set-feature56"; + case G6TS_INIT_WAIT_HEAT: + return "wait-heat"; + } + + return "invalid"; +} + +static const char *g6ts_profile_name(const struct g6ts *ts) +{ + if (g6ts_windows_init_parity) + return "windows-init-parity"; + if (ts->ipts_enabled) + return "phase84-ipts-irq-cadence"; + if (g6ts_feature70_one_byte) + return "phase79"; + if (g6ts_reset_storm_breaker) + return "phase78"; + if (g6ts_reset_recovery_v2) + return "phase77"; + if (g6ts_behavior_v2) + return "phase76"; + if (g6ts_windows_orchestrator) + return "windows-orchestrator"; + return "phase75"; +} + +static int g6ts_power_on(struct g6ts *ts) +{ + gpiod_set_value_cansleep(ts->reset_gpio, 0); + gpiod_set_value_cansleep(ts->power_gpio, 1); + msleep(500); + gpiod_set_value_cansleep(ts->reset_gpio, 1); + msleep(300); + + return 0; +} + +/* + * Reproduce the GPIO effects and delays in the SP11 GTCH firmware power and + * reset sequence for the fixed, input-disabled parity path. + */ +static int g6ts_windows_power_on(struct g6ts *ts) +{ + ts->initialization_stage = G6TS_INIT_WINDOWS_POWER_PS0; + gpiod_set_value_cansleep(ts->power_gpio, 1); + msleep(500); + gpiod_set_value_cansleep(ts->reset_gpio, 1); + + ts->initialization_stage = G6TS_INIT_WINDOWS_RESET_METHOD; + gpiod_set_value_cansleep(ts->reset_gpio, 0); + msleep(300); + gpiod_set_value_cansleep(ts->reset_gpio, 1); + return 0; +} + +static int g6ts_power_off(struct g6ts *ts) +{ + gpiod_set_value_cansleep(ts->reset_gpio, 0); + usleep_range(10000, 12000); + gpiod_set_value_cansleep(ts->power_gpio, 0); + + return 0; +} + +/* + * Submit RX first and TX second through the controller's SP11 QSPI pairing + * path. The ordering and 1-4-4 lane selection match the Windows GPI capture. + */ +static int g6ts_dma_read_pair(struct g6ts *ts, const u8 cmd[8], + void *rx, size_t rx_len) +{ + struct spi_transfer xfers[2] = { + { + .tx_buf = cmd, + .len = 8, + .speed_hz = G6TS_SPI_HZ, + .bits_per_word = 8, + .tx_nbits = SPI_NBITS_QUAD, + }, { + .rx_buf = rx, + .len = rx_len, + .speed_hz = G6TS_SPI_HZ, + .bits_per_word = 8, + .rx_nbits = SPI_NBITS_QUAD, + }, + }; + struct spi_message msg; + + spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers)); + return spi_sync(ts->spi, &msg); +} + +static int g6ts_dma_output(struct g6ts *ts, const void *buf, size_t len) +{ + struct spi_transfer xfer = { + .tx_buf = buf, + .len = len, + .speed_hz = G6TS_SPI_HZ, + .bits_per_word = 8, + .tx_nbits = SPI_NBITS_QUAD, + }; + struct spi_message msg; + + spi_message_init_with_transfers(&msg, &xfer, 1); + return spi_sync(ts->spi, &msg); +} + +static int g6ts_dma_hidspi_output(struct g6ts *ts, u8 report_type, + u8 content_id, const u8 *content, + size_t content_len) +{ + u8 packet[72] = { 0xe2, 0x00, 0x20, 0x00 }; + size_t packet_len; + + if (content_len > sizeof(packet) - 8) + return -E2BIG; + + packet[4] = report_type; + put_unaligned_le16(content_len, &packet[5]); + packet[7] = content_id; + if (content_len) + memcpy(&packet[8], content, content_len); + packet_len = round_up(8 + content_len, 4); + + dev_dbg(&ts->spi->dev, + "G6TS DMA output type=%u id=%#02x content_len=%zu wire=%*ph\n", + report_type, content_id, content_len, (int)packet_len, packet); + return g6ts_dma_output(ts, packet, packet_len); +} + +static int g6ts_pending(struct g6ts *ts) +{ + return gpiod_get_value_cansleep(ts->interrupt_gpio); +} + +static bool g6ts_has_unread_response(struct g6ts *ts) +{ + int pending = g6ts_pending(ts); + + return pending > 0 || + atomic64_read(&ts->interrupt_edges) > ts->handled_interrupt_edges; +} + +static irqreturn_t g6ts_interrupt_edge(int irq, void *data) +{ + struct g6ts *ts = data; + + atomic64_inc(&ts->interrupt_edges); + return IRQ_WAKE_THREAD; +} + +/* Phase 84 owns one IRQ-disable depth while initialization is synchronous. */ +static void g6ts_ipts_irq_disable_nosync(struct g6ts *ts) +{ + if (!ts->ipts_enabled) + return; + + mutex_lock(&ts->irq_state_lock); + if (ts->irq_enabled) { + ts->irq_enabled = false; + disable_irq_nosync(ts->interrupt_irq); + } + mutex_unlock(&ts->irq_state_lock); +} + +/* Never call this synchronous form from either IRQ handler. */ +static void g6ts_ipts_irq_disable_sync(struct g6ts *ts) +{ + if (!ts->ipts_enabled) + return; + + g6ts_ipts_irq_disable_nosync(ts); + synchronize_irq(ts->interrupt_irq); +} + +static void g6ts_ipts_irq_enable_runtime(struct g6ts *ts) +{ + if (!ts->ipts_enabled) + return; + + mutex_lock(&ts->irq_state_lock); + if (!ts->irq_enabled && READ_ONCE(ts->mode_enabled) && + !g6ts_is_quiescing(ts)) { + /* Publish ownership before the level-low IRQ can wake its thread. */ + ts->irq_enabled = true; + enable_irq(ts->interrupt_irq); + } + mutex_unlock(&ts->irq_state_lock); +} + +static int g6ts_wait_pending(struct g6ts *ts, unsigned int timeout_ms) +{ + unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms); + int value; + + do { + value = g6ts_pending(ts); + if (value < 0 || value || + atomic64_read(&ts->interrupt_edges) > + ts->handled_interrupt_edges) + return value < 0 ? value : 0; + usleep_range(1000, 2000); + } while (time_before(jiffies, deadline)); + + return -ETIMEDOUT; +} + +static void g6ts_clear_last_response(struct g6ts *ts) +{ + ts->last_class = 0xff; + ts->last_content_len = 0; + ts->last_content_id = 0xff; +} + +static int g6ts_extract_nsr_metadata(struct g6ts *ts, const u8 *section, + size_t section_len) +{ + size_t position = 7; + + /* Windows starts its nested metadata dispatcher at section byte seven. */ + while (position < section_len) { + size_t payload_len, record_end; + const u8 *record; + unsigned int count, i; + + if (section_len - position < 4) + return -EPROTO; + record = section + position; + payload_len = get_unaligned_le16(record + 2); + if (payload_len > section_len - position - 4) + return -EPROTO; + record_end = position + 4 + payload_len; + + if (record[0] == 0x04) { + if (ts->nsr_valid || payload_len < 4) + return -EPROTO; + count = record[4]; + if (count > G6TS_NSR_BINS || + count > (payload_len - 4) / 4) + return -EPROTO; + memset(ts->nsr_bins, 0, sizeof(ts->nsr_bins)); + for (i = 0; i < count; i++) { + u16 value = get_unaligned_le16(record + 8 + i * 4); + + ts->nsr_bins[i] = value; + } + ts->nsr_bin_count = count; + ts->nsr_valid = true; + } + position = record_end; + } + + return 0; +} + +static int g6ts_extract_heatmap(struct g6ts *ts, const u8 *content, + size_t content_len) +{ + size_t container_end, offset; + u32 container_len; + bool found = false; + + /* + * Report 0x12 begins with a two-byte scan time, followed by a Heat + * container. Its seven-byte container header is followed by length- + * prefixed sections. Windows asks IHeatFrameRawData for section 0x0100. + */ + if (content_len < 9) + return -EMSGSIZE; + container_len = get_unaligned_le32(content + 2); + if (container_len < 7 || container_len > content_len - 2) + return -EPROTO; + container_end = 2 + container_len; + offset = 9; + ts->nsr_valid = false; + ts->nsr_bin_count = 0; + + while (offset < container_end) { + const u8 *section = content + offset; + size_t section_end, position; + u32 section_len; + u16 section_type; + u32 written = 0; + + if (container_end - offset < 8) + return -EPROTO; + section_len = get_unaligned_le32(section); + if (section_len < 8 || section_len > container_end - offset) + return -EPROTO; + section_end = offset + section_len; + section_type = get_unaligned_le16(section + 4); + + if (section_type == G6TS_METADATA_SECTION) { + int ret = g6ts_extract_nsr_metadata(ts, section, + section_len); + + if (ret) + return ret; + offset = section_end; + continue; + } + if (section_type != G6TS_HEAT_SECTION) { + offset = section_end; + continue; + } + if (found || section[6] != 1 || section[7] != 8) + return -EPROTO; + + memset(ts->heatmap, 0, sizeof(ts->heatmap)); + memset(ts->heat_seen, 0, sizeof(ts->heat_seen)); + position = offset + 8; + while (position < section_end) { + u32 destination, count; + u32 i; + + if (section_end - position < 8) + return -EPROTO; + destination = get_unaligned_le32(content + position); + count = get_unaligned_le32(content + position + 4); + position += 8; + if (count > section_end - position || + count > G6TS_HEAT_SAMPLES || + destination > G6TS_HEAT_SAMPLES - count) + return -EPROTO; + for (i = 0; i < count; i++) { + if (ts->heat_seen[destination + i]) + return -EPROTO; + ts->heat_seen[destination + i] = 1; + } + memcpy(ts->heatmap + destination, content + position, count); + written += count; + position += count; + } + if (written != G6TS_HEAT_SAMPLES) + return -EPROTO; + found = true; + offset = section_end; + } + + return found ? 0 : -ENOENT; +} + +static bool g6ts_heat_active(const struct g6ts *ts, unsigned int index) +{ + return ts->heatmap[index] <= G6TS_HEAT_ACTIVE_MAX; +} + +static void g6ts_store_contact(struct g6ts *ts, + const struct g6ts_contact *contact, + unsigned int *contact_count) +{ + unsigned int weakest = 0; + unsigned int i; + + if (*contact_count < G6TS_MAX_CONTACTS) { + ts->contacts[(*contact_count)++] = *contact; + return; + } + + for (i = 1; i < G6TS_MAX_CONTACTS; i++) + if (ts->contacts[i].strength < ts->contacts[weakest].strength) + weakest = i; + if (contact->strength > ts->contacts[weakest].strength) + ts->contacts[weakest] = *contact; +} + +static s32 g6ts_signal_q12(u8 value) +{ + s32 signal_q24 = G6TS_SIGNAL_INTERCEPT_Q24 - + value * G6TS_SIGNAL_STEP_Q24; + + return (signal_q24 + BIT(G6TS_CLASSIFIER_SHIFT - 1)) >> + G6TS_CLASSIFIER_SHIFT; +} + +/* + * FUN_180047078 recomputes the normal output centroid relative to project + * baseline index 171. On the ordinary zero-context path captured for project + * 0x0c83, empty cells outside the thresholded component cannot have positive + * weight against that baseline. The expanded-window traversal therefore + * reduces exactly to this component-local weighted centroid. Keep the + * detector centroid in contact->x/y for assignment and place this later + * output-stage position in contact->output_x/y. + */ +static void g6ts_phase76_output_centroid(struct g6ts *ts, + struct g6ts_contact *contact) +{ + u64 weighted_x = 0, weighted_y = 0; + u64 weight_total = 0; + unsigned int index; + + for (index = 0; index < G6TS_HEAT_SAMPLES; index++) { + u8 value; + u32 weight; + + if (!ts->heat_component[index]) + continue; + value = ts->heatmap[index]; + if (value >= G6TS_WINDOWS_CENTROID_BASELINE) + continue; + weight = G6TS_WINDOWS_CENTROID_BASELINE - value; + weight_total += weight; + weighted_x += (u64)(index % G6TS_HEAT_COLS) * weight; + weighted_y += (u64)(index / G6TS_HEAT_COLS) * weight; + } + if (!weight_total) + return; + + contact->output_x = div_u64(weighted_x * G6TS_LOGICAL_MAX, + weight_total * (G6TS_HEAT_COLS - 1)); + contact->output_y = div_u64(weighted_y * G6TS_LOGICAL_MAX, + weight_total * (G6TS_HEAT_ROWS - 1)); +} + +/* + * Mirror FUN_180040438's candidate +0x4d local-maximum producer and + * FUN_180041fd8's +0x4e strict 0.04 filter. The DLL's equality epsilon is + * 0.0001 while adjacent byte lookup values differ by about 0.00222, so Q12 + * ordering is identical for this byte-input path. + */ +static void g6ts_local_peak_counts(const struct g6ts *ts, + struct g6ts_contact *contact) +{ + static const s8 neighbours[][2] = { + { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, + }; + unsigned int index; + + contact->local_peak_count = 0; + contact->strong_local_peak_count = 0; + for (index = 0; index < G6TS_HEAT_SAMPLES; index++) { + unsigned int row, col, equal_index = UINT_MAX; + s32 current_signal, equal_signal = 0; + unsigned int lower = 0, near_equal = 0, n; + bool selected; + + if (!ts->heat_component[index]) + continue; + row = index / G6TS_HEAT_COLS; + col = index % G6TS_HEAT_COLS; + current_signal = g6ts_signal_q12(ts->heatmap[index]); + for (n = 0; n < ARRAY_SIZE(neighbours); n++) { + int neighbour_col = col + neighbours[n][0]; + int neighbour_row = row + neighbours[n][1]; + unsigned int neighbour_index; + s32 neighbour_signal; + + if (neighbour_row < 0 || neighbour_row >= G6TS_HEAT_ROWS || + neighbour_col < 0 || neighbour_col >= G6TS_HEAT_COLS) { + neighbour_index = UINT_MAX; + neighbour_signal = 0; + } else { + neighbour_index = neighbour_row * G6TS_HEAT_COLS + + neighbour_col; + neighbour_signal = + g6ts_signal_q12(ts->heatmap[neighbour_index]); + } + if (current_signal > neighbour_signal) { + lower++; + } else if (current_signal == neighbour_signal) { + near_equal++; + equal_index = neighbour_index; + equal_signal = neighbour_signal; + } + } + + selected = lower == 4; + if (lower == 3 && near_equal == 1) + selected = equal_signal < current_signal || + (equal_signal == current_signal && + index < equal_index); + if (!selected || + contact->local_peak_count >= G6TS_LOCAL_PEAK_CAPACITY) + continue; + contact->local_peak_count++; + if (current_signal > G6TS_LOCAL_PEAK_FLOOR_Q12) + contact->strong_local_peak_count++; + } +} + +static u8 g6ts_secondary_cutoff(u8 peak_value, unsigned int pass) +{ + static const u16 fractions_q12[] = { 2048, 3072, 3584 }; + u32 fraction = fractions_q12[pass]; + u64 cutoff_q24; + u32 cutoff_q12; + + cutoff_q24 = (u64)((1U << G6TS_CLASSIFIER_SHIFT) - fraction) * + G6TS_SECONDARY_A_Q12; + cutoff_q24 += (u64)fraction * + ((peak_value << G6TS_CLASSIFIER_SHIFT) + + G6TS_SECONDARY_NOISE_Q12); + cutoff_q12 = (cutoff_q24 + BIT(G6TS_CLASSIFIER_SHIFT - 1)) >> + G6TS_CLASSIFIER_SHIFT; + return min_t(u32, U8_MAX, + (cutoff_q12 + BIT(G6TS_CLASSIFIER_SHIFT - 1)) >> + G6TS_CLASSIFIER_SHIFT); +} + +static bool g6ts_secondary_neighbour(const struct g6ts *ts, int row, int col, + u8 cutoff) +{ + unsigned int index; + + if (row < 0 || row >= G6TS_HEAT_ROWS || + col < 0 || col >= G6TS_HEAT_COLS) + return false; + index = row * G6TS_HEAT_COLS + col; + return ts->heat_component[index] && !ts->heat_work[index] && + ts->heatmap[index] <= cutoff; +} + +static void g6ts_secondary_features(struct g6ts *ts, + struct g6ts_contact *contact) +{ + static const s8 neighbours[][2] = { + { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 }, + }; + unsigned int area = (contact->max_col - contact->min_col + 1) * + (contact->max_row - contact->min_row + 1); + unsigned int pass; + + for (pass = 0; pass < 3; pass++) { + contact->features_q12[1 + pass] = + contact->pixels << G6TS_CLASSIFIER_SHIFT; + contact->features_q12[4 + pass] = 1U << G6TS_CLASSIFIER_SHIFT; + } + /* FUN_180041fd8 only reruns bounded candidates above 0.05 + 0.02. */ + if (area >= 0x4e3 || g6ts_signal_q12(contact->peak_value) <= 287) + return; + + for (pass = 0; pass < 3; pass++) { + u8 cutoff = g6ts_secondary_cutoff(contact->peak_value, pass); + unsigned int accepted = 0, largest = 0, start; + + memset(ts->heat_work, 0, sizeof(ts->heat_work)); + for (start = 0; start < G6TS_HEAT_SAMPLES; start++) { + unsigned int head = 0, tail = 0, pixels = 0; + u8 peak = U8_MAX; + + if (!ts->heat_component[start] || ts->heat_work[start] || + ts->heatmap[start] > cutoff) + continue; + ts->heat_work[start] = 1; + ts->heat_queue[tail++] = start; + while (head < tail) { + unsigned int index = ts->heat_queue[head++]; + unsigned int row = index / G6TS_HEAT_COLS; + unsigned int col = index % G6TS_HEAT_COLS; + unsigned int n; + + pixels++; + peak = min_t(u8, peak, ts->heatmap[index]); + for (n = 0; n < ARRAY_SIZE(neighbours); n++) { + int nr = row + neighbours[n][1]; + int nc = col + neighbours[n][0]; + unsigned int neighbour; + + if (!g6ts_secondary_neighbour(ts, nr, nc, cutoff)) + continue; + neighbour = nr * G6TS_HEAT_COLS + nc; + ts->heat_work[neighbour] = 1; + ts->heat_queue[tail++] = neighbour; + } + } + if (pixels <= 2 && + g6ts_signal_q12(peak) <= G6TS_SECONDARY_SEED_Q12) + continue; + accepted++; + largest = max(largest, pixels); + } + contact->features_q12[1 + pass] = + largest << G6TS_CLASSIFIER_SHIFT; + contact->features_q12[4 + pass] = + accepted << G6TS_CLASSIFIER_SHIFT; + } +} + +static void g6ts_geometry_features(struct g6ts *ts, + struct g6ts_contact *contact) +{ + u64 sum = 0, sum_x = 0, sum_y = 0; + u64 sum_xx = 0, sum_yy = 0, sum_xy = 0; + s64 var_x, var_y, covariance, trace, delta, discriminant; + u64 denominator; + u32 major_axis, minor_axis; + unsigned int index; + + for (index = 0; index < G6TS_HEAT_SAMPLES; index++) { + u32 row, col; + s32 signal; + + if (!ts->heat_component[index]) + continue; + row = index / G6TS_HEAT_COLS; + col = index % G6TS_HEAT_COLS; + signal = g6ts_signal_q12(ts->heatmap[index]); + if (signal <= 0) + continue; + sum += signal; + sum_x += (u64)signal * col; + sum_y += (u64)signal * row; + sum_xx += (u64)signal * col * col; + sum_yy += (u64)signal * row * row; + sum_xy += (u64)signal * col * row; + } + if (!sum) { + contact->features_q12[7] = 1U << G6TS_CLASSIFIER_SHIFT; + contact->features_q12[8] = 1U << G6TS_CLASSIFIER_SHIFT; + return; + } + + denominator = sum * sum; + var_x = div64_s64(((s64)(sum_xx * sum) - (s64)(sum_x * sum_x)) * + (1U << G6TS_CLASSIFIER_SHIFT), denominator); + var_y = div64_s64(((s64)(sum_yy * sum) - (s64)(sum_y * sum_y)) * + (1U << G6TS_CLASSIFIER_SHIFT), denominator); + covariance = div64_s64(((s64)(sum_xy * sum) - (s64)(sum_x * sum_y)) * + (1U << G6TS_CLASSIFIER_SHIFT), denominator); + trace = max_t(s64, 0, var_x + var_y); + delta = var_x - var_y; + discriminant = int_sqrt64(delta * delta + 4 * covariance * covariance); + major_axis = int_sqrt64(((trace + discriminant) / 2) << + G6TS_CLASSIFIER_SHIFT); + minor_axis = int_sqrt64(max_t(s64, 0, (trace - discriminant) / 2) << + G6TS_CLASSIFIER_SHIFT); + major_axis = max_t(u32, 1U << G6TS_CLASSIFIER_SHIFT, + ((u64)major_axis * G6TS_AXIS_SCALE_Q12) >> + G6TS_CLASSIFIER_SHIFT); + minor_axis = max_t(u32, 1U << G6TS_CLASSIFIER_SHIFT, + ((u64)minor_axis * G6TS_AXIS_SCALE_Q12) >> + G6TS_CLASSIFIER_SHIFT); + contact->features_q12[7] = div_u64((u64)major_axis << + G6TS_CLASSIFIER_SHIFT, minor_axis); + if (contact->pixels < 2) { + contact->features_q12[8] = 1U << G6TS_CLASSIFIER_SHIFT; + } else { + s64 spread = trace * G6TS_SPREAD_SCALE_Q12; + u32 samples = (1U << G6TS_CLASSIFIER_SHIFT) * + (contact->pixels - 1); + + contact->features_q12[8] = div64_s64(spread, samples); + } +} + +static s32 g6ts_halo_feature(struct g6ts *ts, + const struct g6ts_contact *contact) +{ + static const s8 neighbours[][2] = { + { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 }, + }; + u8 state[16][16]; + int origin_col = contact->min_col - 3; + int origin_row = contact->min_row - 3; + s32 peak = g6ts_signal_q12(contact->peak_value); + s32 peak_threshold = peak / 4; + s64 halo = 0; + unsigned int radius, n; + int row, col; + + if (contact->max_col - contact->min_col + 1 >= 11 || + contact->max_row - contact->min_row + 1 >= 11 || peak <= 0) + return G6TS_HALO_UNAVAILABLE_Q12; + memset(state, 5, sizeof(state)); + for (row = max(0, origin_row); row <= + min_t(int, G6TS_HEAT_ROWS - 1, contact->max_row + 3); row++) { + for (col = max(0, origin_col); col <= + min_t(int, G6TS_HEAT_COLS - 1, contact->max_col + 3); col++) { + unsigned int index = row * G6TS_HEAT_COLS + col; + + if (ts->heat_component[index]) + state[row - origin_row][col - origin_col] = 0; + else if (!g6ts_heat_active(ts, index)) + state[row - origin_row][col - origin_col] = 4; + } + } + for (row = contact->min_row; row <= contact->max_row; row++) { + for (col = contact->min_col; col <= contact->max_col; col++) { + if (state[row - origin_row][col - origin_col]) + continue; + for (n = 0; n < ARRAY_SIZE(neighbours); n++) { + int nc = col + neighbours[n][0]; + int nr = row + neighbours[n][1]; + + if (nr < 0 || nr >= G6TS_HEAT_ROWS || nc < 0 || + nc >= G6TS_HEAT_COLS || + state[nr - origin_row][nc - origin_col] != 4) + continue; + if (g6ts_signal_q12(ts->heatmap[nr * G6TS_HEAT_COLS + nc]) > + peak_threshold) + state[nr - origin_row][nc - origin_col] = 1; + } + } + } + for (radius = 1; radius <= 2; radius++) { + for (row = max_t(int, 0, contact->min_row - radius); row <= + min_t(int, G6TS_HEAT_ROWS - 1, contact->max_row + radius); row++) { + for (col = max_t(int, 0, contact->min_col - radius); col <= + min_t(int, G6TS_HEAT_COLS - 1, contact->max_col + radius); col++) { + unsigned int current_index; + s32 current_signal; + + if (state[row - origin_row][col - origin_col] > radius) + continue; + current_index = row * G6TS_HEAT_COLS + col; + current_signal = g6ts_signal_q12(ts->heatmap[current_index]); + for (n = 0; n < ARRAY_SIZE(neighbours); n++) { + int nc = col + neighbours[n][0]; + int nr = row + neighbours[n][1]; + unsigned int next_index; + s32 next; + + if (nr < 0 || nr >= G6TS_HEAT_ROWS || nc < 0 || + nc >= G6TS_HEAT_COLS || + state[nr - origin_row][nc - origin_col] != 4) + continue; + next_index = nr * G6TS_HEAT_COLS + nc; + next = g6ts_signal_q12(ts->heatmap[next_index]); + if (next < peak_threshold && current_signal > next && + current_signal > 0 && + (s64)next << G6TS_CLASSIFIER_SHIFT > + (s64)current_signal * G6TS_HALO_RATIO_Q12) { + state[nr - origin_row][nc - origin_col] = + radius + 1; + halo += next; + } + } + } + } + } + return div64_s64(halo << G6TS_CLASSIFIER_SHIFT, peak); +} + +static u8 g6ts_classify_contact(struct g6ts_contact *contact) +{ + s64 best_score = S64_MIN; + u8 best_class = 0; + unsigned int class, row, col; + + for (class = 0; class < ARRAY_SIZE(g6ts_classifier_models); class++) { + const struct g6ts_classifier_model *model = + &g6ts_classifier_models[class]; + s64 distance = 0; + + contact->scores_q24[class] = + -9999LL * (1LL << G6TS_WINDOWS_SCORE_SHIFT); + if (contact->pixels > model->max_points) + continue; + for (row = 0; row < G6TS_FEATURE_COUNT; row++) { + s64 transformed = 0; + + for (col = row; col < G6TS_FEATURE_COUNT; col++) { + s32 residual; + + if (col == 9 && contact->features_q12[col] == + G6TS_HALO_UNAVAILABLE_Q12) + residual = 0; + else + residual = contact->features_q12[col] - + model->means_q12[col]; + transformed += (s64)residual * + model->transform_q12[row][col]; + } + transformed >>= G6TS_CLASSIFIER_SHIFT; + if (transformed > INT_MAX || transformed < -INT_MAX || + distance > S64_MAX - transformed * transformed) { + distance = S64_MAX; + break; + } + distance += transformed * transformed; + } + if (distance != S64_MAX) { + s64 score = ((s64)model->score_offset_q12 << + G6TS_CLASSIFIER_SHIFT) - distance / 2 + + G6TS_WINDOWS_RUNTIME_OFFSET_Q24; + + contact->scores_q24[class] = score; + } + } + if (g6ts_windows_orchestrator) { + /* FUN_180049638 applies these in strict if/else-if order. */ + if (contact->local_peak_count == 1) + contact->scores_q24[3] -= + G6TS_WINDOWS_SCORE3_PRIMARY_Q24; + else if (contact->strong_local_peak_count == 1) + contact->scores_q24[3] -= + G6TS_WINDOWS_SCORE3_SINGLE_Q24; + } + for (class = 0; class < G6TS_CLASS_COUNT; class++) { + if (contact->scores_q24[class] > best_score) { + best_score = contact->scores_q24[class]; + best_class = class; + } + } + return best_class; +} + +static unsigned int g6ts_find_contacts(struct g6ts *ts) +{ + unsigned int contact_count = 0; + unsigned int start; + + memset(ts->heat_seen, 0, sizeof(ts->heat_seen)); + + for (start = 0; start < G6TS_HEAT_SAMPLES; start++) { + struct g6ts_contact contact = { + .min_col = G6TS_HEAT_COLS - 1, + .min_row = G6TS_HEAT_ROWS - 1, + .peak_value = U8_MAX, + }; + unsigned int head = 0, tail = 0; + + if (ts->heat_seen[start] || !g6ts_heat_active(ts, start)) + continue; + ts->heat_seen[start] = 1; + ts->heat_queue[tail++] = start; + + while (head < tail) { + unsigned int index = ts->heat_queue[head++]; + unsigned int row = index / G6TS_HEAT_COLS; + unsigned int col = index % G6TS_HEAT_COLS; + unsigned int strength = G6TS_HEAT_SIGNAL_ZERO - + ts->heatmap[index]; + int dr, dc; + + contact.pixels++; + contact.strength += strength; + contact.weighted_x += (u64)col * strength; + contact.weighted_y += (u64)row * strength; + contact.min_col = min_t(u8, contact.min_col, col); + contact.max_col = max_t(u8, contact.max_col, col); + contact.min_row = min_t(u8, contact.min_row, row); + contact.max_row = max_t(u8, contact.max_row, row); + contact.peak_value = min_t(u8, contact.peak_value, + ts->heatmap[index]); + + for (dr = -1; dr <= 1; dr++) { + for (dc = -1; dc <= 1; dc++) { + int neighbour_row = row + dr; + int neighbour_col = col + dc; + unsigned int neighbour; + + if (abs(dr) + abs(dc) != 1 || + neighbour_row < 0 || + neighbour_row >= G6TS_HEAT_ROWS || + neighbour_col < 0 || + neighbour_col >= G6TS_HEAT_COLS) + continue; + neighbour = neighbour_row * G6TS_HEAT_COLS + + neighbour_col; + if (ts->heat_seen[neighbour] || + !g6ts_heat_active(ts, neighbour)) + continue; + ts->heat_seen[neighbour] = 1; + ts->heat_queue[tail++] = neighbour; + } + } + } + + ts->component_total++; + if (contact.pixels < G6TS_HEAT_MIN_PIXELS && + contact.peak_value > G6TS_HEAT_STRONG_MAX) { + ts->weak_rejections++; + continue; + } + if (ts->nsr_valid) { + unsigned int sensor_row = div_u64(contact.weighted_y + + contact.strength / 2, + contact.strength); + + if (sensor_row < ARRAY_SIZE(g6ts_nsr_row_to_bin)) { + u8 bin = g6ts_nsr_row_to_bin[sensor_row]; + + if (bin < ts->nsr_bin_count && + ts->nsr_bins[bin] > G6TS_NSR_CUTOFF) { + ts->nsr_rejections++; + continue; + } + } + } + if (contact.pixels > G6TS_HEAT_PALM_PIXELS || + contact.max_col - contact.min_col + 1 > G6TS_HEAT_PALM_SPAN || + contact.max_row - contact.min_row + 1 > G6TS_HEAT_PALM_SPAN) { + ts->palm_rejections++; + continue; + } + contact.x = div_u64(contact.weighted_x * G6TS_LOGICAL_MAX, + (u64)contact.strength * (G6TS_HEAT_COLS - 1)); + contact.y = div_u64(contact.weighted_y * G6TS_LOGICAL_MAX, + (u64)contact.strength * (G6TS_HEAT_ROWS - 1)); + contact.output_x = contact.x; + contact.output_y = contact.y; + contact.sensor_x_q24 = div_u64(contact.weighted_x << 24, + contact.strength); + contact.sensor_y_q24 = div_u64(contact.weighted_y << 24, + contact.strength); + memset(ts->heat_component, 0, sizeof(ts->heat_component)); + while (tail) + ts->heat_component[ts->heat_queue[--tail]] = 1; + if (g6ts_behavior_v2) + g6ts_phase76_output_centroid(ts, &contact); + g6ts_local_peak_counts(ts, &contact); + contact.features_q12[0] = + contact.pixels << G6TS_CLASSIFIER_SHIFT; + g6ts_secondary_features(ts, &contact); + g6ts_geometry_features(ts, &contact); + contact.features_q12[9] = g6ts_halo_feature(ts, &contact); + contact.shape_class = g6ts_classify_contact(&contact); + /* FUN_180049458 permits the classifier's classes zero and two. */ + contact.shape_allowed = contact.shape_class == 0 || + contact.shape_class == 2; + dev_dbg(&ts->spi->dev, + "candidate class=%u allowed=%u pixels=%u peaks=%u/%u halo_q12=%d scores=[%lld,%lld,%lld,%lld]\n", + contact.shape_class, contact.shape_allowed, contact.pixels, + contact.local_peak_count, contact.strong_local_peak_count, + contact.features_q12[9], contact.scores_q24[0], + contact.scores_q24[1], contact.scores_q24[2], + contact.scores_q24[3]); + memset(ts->heat_component, 0, sizeof(ts->heat_component)); + g6ts_store_contact(ts, &contact, &contact_count); + } + + ts->contact_total += contact_count; + return contact_count; +} + +static bool +g6ts_transition_current_passes(const struct g6ts_transition_rule *rule, + u8 candidate, + const s64 scores[G6TS_CLASS_COUNT]) +{ + s64 selected = scores[candidate]; + unsigned int class; + + for (class = 0; class < G6TS_CLASS_COUNT; class++) { + s64 margin; + + if (class == candidate) + continue; + margin = (s64)rule->current_margins[class] * + (1LL << G6TS_WINDOWS_SCORE_SHIFT); + if (selected - scores[class] < margin) + return false; + } + return selected >= (s64)rule->absolute_minimum * + (1LL << G6TS_WINDOWS_SCORE_SHIFT); +} + +static bool +g6ts_transition_history_passes(const struct g6ts_track *track, + const struct g6ts_transition_rule *rule, + u8 candidate) +{ + unsigned int sample, class; + + if (rule->history_depth > G6TS_WINDOWS_HISTORY_CAPACITY || + track->score_history_count < rule->history_depth) + return false; + for (sample = 0; sample < rule->history_depth; sample++) { + s64 selected = track->score_history_q24[sample][candidate]; + + for (class = 0; class < G6TS_CLASS_COUNT; class++) { + s64 margin; + + if (class == candidate) + continue; + margin = (s64)rule->history_margins[class] * + (1LL << G6TS_WINDOWS_SCORE_SHIFT); + if (selected - track->score_history_q24[sample][class] < + margin) + return false; + } + if (selected < (s64)rule->absolute_minimum * + (1LL << G6TS_WINDOWS_SCORE_SHIFT)) + return false; + } + return true; +} + +/* + * Bounded ordinary-finger subset of FUN_180041150. Context bias, the + * candidate +0x4d/+0x4e score-three producer, and the later output override + * remain outside this opt-in path until their live provider fields exist. + */ +static void g6ts_windows_update_class(struct g6ts_track *track, + const struct g6ts_contact *contact) +{ + const struct g6ts_transition_rule *rule; + unsigned int sample, class; + u8 candidate = 0; + bool accepted = false; + + for (sample = min_t(unsigned int, track->score_history_count, + G6TS_WINDOWS_HISTORY_CAPACITY - 1); + sample > 0; sample--) + memcpy(track->score_history_q24[sample], + track->score_history_q24[sample - 1], + sizeof(track->score_history_q24[sample])); + memcpy(track->score_history_q24[0], contact->scores_q24, + sizeof(track->score_history_q24[0])); + if (track->score_history_count < G6TS_WINDOWS_HISTORY_CAPACITY) + track->score_history_count++; + + for (class = 1; class < G6TS_CLASS_COUNT; class++) { + if (contact->scores_q24[candidate] < contact->scores_q24[class]) + candidate = class; + } + if (track->windows_class == candidate) { + accepted = true; + } else { + rule = &g6ts_transition_rules[track->windows_class * + G6TS_CLASS_COUNT + candidate]; + if (track->age == 1 || track->age <= rule->initial_age_limit) + accepted = g6ts_transition_current_passes(rule, candidate, + contact->scores_q24); + if (!accepted && track->age > 1) + accepted = g6ts_transition_history_passes(track, rule, + candidate); + if (accepted && + (contact->pixels < g6ts_class_point_minimums[candidate] || + contact->pixels > g6ts_class_point_maximums[candidate])) + accepted = false; + } + if (accepted) + track->windows_class = candidate; + track->confirmed = track->windows_class == 0 || + track->windows_class == 2; +} + +static u16 g6ts_windows_assignment_coordinate(u32 position_q24, u32 scale_q24) +{ + u64 scaled = (u64)position_q24 * scale_q24; + + return min_t(u64, (scaled + BIT_ULL(47)) >> 48, S16_MAX); +} + +static u16 g6ts_filter_coordinate(u16 previous, u16 sample) +{ + unsigned int delta = previous > sample ? previous - sample : + sample - previous; + + /* + * TouchPenProcessor uses output = alpha * previous + + * (1 - alpha) * sample. The project-tuning table selecting alpha is + * not recovered, so these two bands are explicit SP11 fits: suppress + * stationary sensor jitter, reduce lag for slow motion, and pass fast + * motion through unchanged. + */ + if (delta <= G6TS_SMOOTH_STATIONARY_MAX) + return (3U * previous + sample + 2U) / 4U; + if (delta <= G6TS_SMOOTH_SLOW_MAX) + return (previous + 3U * sample + 2U) / 4U; + return sample; +} + +static unsigned int g6ts_track_distance(const struct g6ts_track *track, + const struct g6ts_contact *contact) +{ + if (g6ts_windows_orchestrator || g6ts_behavior_v2) { + const u32 x_scale = G6TS_WINDOWS_ASSIGN_X_SCALE_Q24; + const u32 y_scale = G6TS_WINDOWS_ASSIGN_Y_SCALE_Q24; + s64 predicted_x = clamp_t(s64, + (s64)track->sensor_x_q24 + track->sensor_velocity_x_q24, + 0, (s64)(G6TS_HEAT_COLS - 1) << 24); + s64 predicted_y = clamp_t(s64, + (s64)track->sensor_y_q24 + track->sensor_velocity_y_q24, + 0, (s64)(G6TS_HEAT_ROWS - 1) << 24); + s32 track_x = g6ts_windows_assignment_coordinate(predicted_x, + x_scale); + s32 track_y = g6ts_windows_assignment_coordinate(predicted_y, + y_scale); + s32 candidate_x = g6ts_windows_assignment_coordinate(contact->sensor_x_q24, + x_scale); + s32 candidate_y = g6ts_windows_assignment_coordinate(contact->sensor_y_q24, + y_scale); + s32 dx = track_x - candidate_x; + s32 dy = track_y - candidate_y; + u32 squared = dx * dx + dy * dy; + + if (squared >= G6TS_WINDOWS_ASSIGN_RADIUS * + G6TS_WINDOWS_ASSIGN_RADIUS) + return G6TS_ASSIGN_INVALID_COST; + return squared; + } + + s32 predicted_x = clamp_t(s32, (s32)track->raw_x + track->velocity_x, + 0, G6TS_LOGICAL_MAX); + s32 predicted_y = clamp_t(s32, (s32)track->raw_y + track->velocity_y, + 0, G6TS_LOGICAL_MAX); + s32 dx = predicted_x - contact->x; + s32 dy = predicted_y - contact->y; + u64 squared = (s64)dx * dx + (s64)dy * dy; + + if (squared > (u64)G6TS_TRACK_MATCH_MAX * G6TS_TRACK_MATCH_MAX) + return G6TS_ASSIGN_INVALID_COST; + return int_sqrt64(squared); +} + +static u8 g6ts_confirmation_requirement(const struct g6ts *ts, + const struct g6ts_contact *contact) +{ + unsigned int i; + u8 required = g6ts_behavior_v2 ? G6TS_BEHAVIOR_CONFIRM_NORMAL : + G6TS_TRACK_CONFIRM_NORMAL; + + /* + * TouchPenProcessor does not expose a blob immediately. Its recovered + * lifecycle keeps a per-track class history and uses transition-specific + * evidence windows. The proprietary four-class score coefficients are + * intentionally not copied here; use observable component quality to + * select the same short, medium, and long lifecycle windows instead. + */ + if (contact->pixels < G6TS_HEAT_MIN_PIXELS) + required = G6TS_TRACK_CONFIRM_WEAK; + if (!contact->shape_allowed) + required = max_t(u8, required, G6TS_TRACK_CONFIRM_WEAK); + + for (i = 0; i < G6TS_MAX_CONTACTS; i++) { + const struct g6ts_track *track = &ts->tracks[i]; + s32 dx, dy; + u64 squared; + + if (!track->active || !track->confirmed || track->missed) + continue; + dx = (s32)track->raw_x - contact->x; + dy = (s32)track->raw_y - contact->y; + squared = (s64)dx * dx + (s64)dy * dy; + if (squared > (u64)G6TS_TRACK_SPLIT_RADIUS * + G6TS_TRACK_SPLIT_RADIUS) + continue; + + /* + * A new, smaller island beside an established finger is commonly a + * transient split of that finger. Preserve real close multitouch by + * accepting it after sustained evidence rather than deleting it. + */ + required = max_t(u8, required, G6TS_TRACK_CONFIRM_WEAK); + if ((u64)contact->strength * 2 <= track->strength || + (u32)contact->pixels * 2 <= track->pixels) + required = G6TS_TRACK_CONFIRM_SPLIT; + } + + return required; +} + +/* + * Windows builds a predicted-position Euclidean cost matrix and solves a + * global assignment. Use a square matrix with explicit dummy rows/columns + * so a gated-out pairing loses to closing one track and opening another. + */ +static void g6ts_assign_tracks(struct g6ts *ts, unsigned int count, + int contact_slots[G6TS_MAX_CONTACTS]) +{ + struct g6ts_assignment_workspace *work = &ts->assignment; + unsigned int active_count = 0; + unsigned int n, row, col, i, j; + + memset(work, 0, sizeof(*work)); + for (i = 0; i < G6TS_MAX_CONTACTS; i++) + contact_slots[i] = -1; + + for (i = 0; i < G6TS_MAX_CONTACTS; i++) { + if (ts->tracks[i].active) + work->active_slots[active_count++] = i; + } + if (!active_count || !count) + return; + + n = active_count + count; + for (row = 0; row < n; row++) { + for (col = 0; col < n; col++) { + if (row < active_count && col < count) { + struct g6ts_track *track; + + track = &ts->tracks[work->active_slots[row]]; + work->cost[row][col] = + g6ts_track_distance(track, + &ts->contacts[col]); + } else if (row < active_count || col < count) { + work->cost[row][col] = G6TS_ASSIGN_UNMATCHED_COST; + } else { + work->cost[row][col] = 0; + } + } + } + + /* Hungarian minimum-cost assignment, using one-based work arrays. */ + for (i = 1; i <= n; i++) { + int j0 = 0; + + work->p[0] = i; + for (j = 0; j <= n; j++) { + work->minv[j] = INT_MAX; + work->used[j] = false; + } + do { + int i0, delta = INT_MAX, j1 = 0; + + work->used[j0] = true; + i0 = work->p[j0]; + for (j = 1; j <= n; j++) { + int reduced_cost; + + if (work->used[j]) + continue; + reduced_cost = work->cost[i0 - 1][j - 1] - + work->u[i0] - work->v[j]; + if (reduced_cost < work->minv[j]) { + work->minv[j] = reduced_cost; + work->way[j] = j0; + } + if (work->minv[j] < delta) { + delta = work->minv[j]; + j1 = j; + } + } + for (j = 0; j <= n; j++) { + if (work->used[j]) { + work->u[work->p[j]] += delta; + work->v[j] -= delta; + } else if (j) { + work->minv[j] -= delta; + } + } + j0 = j1; + } while (work->p[j0]); + + do { + int j1 = work->way[j0]; + + work->p[j0] = work->p[j1]; + j0 = j1; + } while (j0); + } + + for (j = 1; j <= n; j++) { + row = work->p[j] - 1; + col = j - 1; + if (row < active_count && col < count && + work->cost[row][col] < G6TS_ASSIGN_INVALID_COST && + (g6ts_windows_orchestrator || g6ts_behavior_v2 || + work->cost[row][col] <= G6TS_TRACK_MATCH_MAX)) + contact_slots[col] = work->active_slots[row]; + } + for (i = 0; i < count; i++) + if (contact_slots[i] >= 0) + ts->assignment_matches++; +} + +static void g6ts_update_track(struct g6ts_track *track, + const struct g6ts_contact *contact) +{ + u16 old_x = track->raw_x; + u16 old_y = track->raw_y; + u32 old_sensor_x_q24 = track->sensor_x_q24; + u32 old_sensor_y_q24 = track->sensor_y_q24; + + track->raw_x = contact->x; + track->raw_y = contact->y; + track->velocity_x = (s32)contact->x - old_x; + track->velocity_y = (s32)contact->y - old_y; + track->sensor_x_q24 = contact->sensor_x_q24; + track->sensor_y_q24 = contact->sensor_y_q24; + track->sensor_velocity_x_q24 = (s32)((s64)contact->sensor_x_q24 - + old_sensor_x_q24); + track->sensor_velocity_y_q24 = (s32)((s64)contact->sensor_y_q24 - + old_sensor_y_q24); + if (g6ts_windows_orchestrator) { + /* FUN_18004a330 stores X/Y directly; its alpha blends another scalar. */ + track->output_x = contact->x; + track->output_y = contact->y; + } else if (g6ts_behavior_v2) { + track->output_x = contact->output_x; + track->output_y = contact->output_y; + } else { + track->output_x = g6ts_filter_coordinate(track->output_x, + contact->x); + track->output_y = g6ts_filter_coordinate(track->output_y, + contact->y); + } + track->strength = contact->strength; + track->pixels = contact->pixels; + track->shape_class = contact->shape_class; + if (track->age < U16_MAX) + track->age++; + if (g6ts_windows_orchestrator) { + g6ts_windows_update_class(track, contact); + } else if (!track->confirmed && contact->shape_allowed && + track->evidence < U8_MAX) { + track->evidence++; + if (track->evidence >= track->required_evidence) + track->confirmed = true; + } + track->missed = 0; +} + +static int g6ts_new_track(struct g6ts *ts, + const struct g6ts_contact *contact) +{ + unsigned int slot; + + for (slot = 0; slot < G6TS_MAX_CONTACTS; slot++) { + struct g6ts_track *track = &ts->tracks[slot]; + + if (track->active) + continue; + memset(track, 0, sizeof(*track)); + track->raw_x = contact->x; + track->raw_y = contact->y; + track->output_x = g6ts_behavior_v2 ? contact->output_x : contact->x; + track->output_y = g6ts_behavior_v2 ? contact->output_y : contact->y; + track->sensor_x_q24 = contact->sensor_x_q24; + track->sensor_y_q24 = contact->sensor_y_q24; + track->strength = contact->strength; + track->pixels = contact->pixels; + track->shape_class = contact->shape_class; + track->age = 1; + if (g6ts_windows_orchestrator) { + track->windows_class = G6TS_WINDOWS_UNCLASSIFIED; + g6ts_windows_update_class(track, contact); + } else { + track->evidence = contact->shape_allowed ? 1 : 0; + track->required_evidence = + g6ts_confirmation_requirement(ts, contact); + track->confirmed = track->required_evidence <= 1; + } + track->active = true; + ts->new_tracks++; + return slot; + } + return -ENOSPC; +} + +static void g6ts_advance_unmatched_tracks(struct g6ts *ts, + unsigned long current_slots) +{ + unsigned int i; + + for (i = 0; i < G6TS_MAX_CONTACTS; i++) { + struct g6ts_track *track = &ts->tracks[i]; + + if (!track->active || (current_slots & BIT(i))) + continue; + if (track->missed < G6TS_CONTACT_HOLD_FRAMES) { + track->missed++; + track->velocity_x /= 2; + track->velocity_y /= 2; + track->sensor_velocity_x_q24 /= 2; + track->sensor_velocity_y_q24 /= 2; + } else { + memset(track, 0, sizeof(*track)); + } + } +} + +static unsigned long +g6ts_create_unmatched_tracks(struct g6ts *ts, unsigned int count, + const int contact_slots[G6TS_MAX_CONTACTS], + unsigned long current_slots) +{ + unsigned int i; + + for (i = 0; i < count; i++) { + int slot; + + if (contact_slots[i] >= 0) + continue; + slot = g6ts_new_track(ts, &ts->contacts[i]); + if (slot >= 0) + current_slots |= BIT(slot); + } + return current_slots; +} + +static void g6ts_collect_linux_contacts(struct g6ts *ts, + unsigned long current_slots) +{ + unsigned int reported = 0; + unsigned int i; + + for (i = 0; i < G6TS_MAX_CONTACTS; i++) { + struct g6ts_track *track = &ts->tracks[i]; + + /* Retained tracks remain assignable but never become ghost contacts. */ + if (!track->active || !track->confirmed || + !(current_slots & BIT(i))) + continue; + input_mt_slot(ts->input, i); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); + touchscreen_report_pos(ts->input, &ts->prop, track->output_x, + track->output_y, true); + reported++; + } + input_mt_sync_frame(ts->input); + input_sync(ts->input); + ts->output_frames++; + ts->output_contacts += reported; +} + +static int g6ts_report_heat_contacts(struct g6ts *ts, const u8 *content, + size_t content_len) +{ + u64 started_ns = ktime_get_ns(); + unsigned long current_slots = 0; + int contact_slots[G6TS_MAX_CONTACTS]; + unsigned int count, i; + int ret; + + ret = g6ts_extract_heatmap(ts, content, content_len); + if (ret) { + ts->heat_errors++; + return ret; + } + count = g6ts_find_contacts(ts); + g6ts_assign_tracks(ts, count, contact_slots); + + for (i = 0; i < count; i++) { + int slot = contact_slots[i]; + + if (slot < 0) + continue; + g6ts_update_track(&ts->tracks[slot], &ts->contacts[i]); + current_slots |= BIT(slot); + } + + /* + * The frame transaction is deliberately explicit and single-threaded: + * assignment -> matched updates -> unmatched lifecycle -> new tracks -> + * final Linux collection. No input event escapes an intermediate stage. + */ + g6ts_advance_unmatched_tracks(ts, current_slots); + current_slots = g6ts_create_unmatched_tracks(ts, count, contact_slots, + current_slots); + g6ts_collect_linux_contacts(ts, current_slots); + ts->heat_frames++; + started_ns = ktime_get_ns() - started_ns; + ts->processing_ns_total += started_ns; + ts->processing_ns_max = max(ts->processing_ns_max, started_ns); + + return 0; +} + +static void g6ts_release_contacts(struct g6ts *ts) +{ + input_mt_sync_frame(ts->input); + input_sync(ts->input); + memset(ts->tracks, 0, sizeof(ts->tracks)); +} + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) +static void g6ts_release_pen(struct g6ts *ts) +{ + if (!ts->pen_input) + return; + + input_report_abs(ts->pen_input, ABS_PRESSURE, 0); + input_report_key(ts->pen_input, BTN_TOUCH, false); + input_report_key(ts->pen_input, BTN_STYLUS, false); + input_report_key(ts->pen_input, BTN_TOOL_PEN, false); + input_report_key(ts->pen_input, BTN_TOOL_RUBBER, false); + input_sync(ts->pen_input); +} +#endif + +static void g6ts_release_inputs(struct g6ts *ts) +{ + g6ts_release_contacts(ts); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + g6ts_release_pen(ts); +#endif +} + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) +static void g6ts_report_pen(struct g6ts *ts, const u8 *content) +{ + struct input_dev *input = ts->pen_input; + u8 flags = content[0]; + bool in_range = flags & G6TS_PEN_IN_RANGE; + bool touching = flags & (G6TS_PEN_TIP_SWITCH | G6TS_PEN_ERASER); + bool barrel = flags & G6TS_PEN_BARREL_SWITCH; + bool rubber = flags & (G6TS_PEN_INVERT | G6TS_PEN_ERASER); + u16 x, y, pressure, raw_tilt_x, raw_tilt_y; + s16 tilt_x, tilt_y; + + /* Layout and logical ranges come from the report-0x01 HID collection. */ + x = min_t(u16, get_unaligned_le16(&content[1]), G6TS_PEN_X_MAX); + y = min_t(u16, get_unaligned_le16(&content[3]), G6TS_PEN_Y_MAX); + pressure = min_t(u16, get_unaligned_le16(&content[5]), + G6TS_PEN_PRESSURE_MAX); + raw_tilt_x = min_t(u16, get_unaligned_le16(&content[7]), + G6TS_PEN_TILT_RAW_MAX); + raw_tilt_y = min_t(u16, get_unaligned_le16(&content[9]), + G6TS_PEN_TILT_RAW_MAX); + /* + * HID maps logical 0..18000 to physical -9000..9000 at a -2 + * degree exponent. Linux therefore sees signed hundredths of a + * degree, centered at zero, while this dormant native path stays a + * guarded fallback. + */ + tilt_x = raw_tilt_x - G6TS_PEN_TILT_CENTER; + tilt_y = raw_tilt_y - G6TS_PEN_TILT_CENTER; + + touchscreen_report_pos(input, &ts->pen_prop, x, y, false); + input_report_abs(input, ABS_PRESSURE, pressure); + input_report_abs(input, ABS_TILT_X, tilt_x); + input_report_abs(input, ABS_TILT_Y, tilt_y); + input_report_key(input, BTN_TOUCH, touching); + input_report_key(input, BTN_STYLUS, barrel); + input_report_key(input, BTN_TOOL_PEN, in_range && !rubber); + input_report_key(input, BTN_TOOL_RUBBER, in_range && rubber); + input_sync(input); +} +#endif + +static void g6ts_send_heat_feedback_a5(struct g6ts *ts, + bool after_heat_group); + +static void g6ts_handle_data_report(struct g6ts *ts) +{ + const u8 *payload = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + u16 x, y; + bool active; + + if (ts->last_class != DATA) + return; + ts->report_counts[ts->last_content_id]++; + /* Preserve raw pen measurements before touch-readiness can discard them. */ + if (g6ts_is_raw_export_report(ts->last_content_id)) + g6ts_heat_enqueue(ts, ts->last_content_id, payload, + ts->last_content_len); + /* + * HID-SPI stores the report ID immediately before content, so body + 3 + * is the exact hidraw packet expected by iptsd. The full allocation size + * is supplied because report 0x1a is 4,350 bytes including its ID. + */ +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + if (ts->ipts_hid_ready && + g6ts_is_ipts_hid_report(ts->last_content_id)) { + int ret; + + ret = hid_safe_input_report(ts->ipts_hid, HID_INPUT_REPORT, + ts->body + 3, G6TS_MAX_BODY - 3, + ts->last_content_len + 1, 1); + if (ret && ret != -ENODEV && ret != -EBUSY) + dev_warn_ratelimited(&ts->spi->dev, + "failed to relay DFT report 0x%02x: %d\n", + ts->last_content_id, ret); + } +#endif + switch (ts->last_content_id) { + case 0x1a: + g6ts_send_heat_feedback_a5(ts, true); + break; + case 0x0d: + ts->a5.cycle_saw_0x0d = true; + break; + case 0x0b: + if (ts->a5.cycle_saw_0x0d) { + ts->a5.cycle_saw_0x0d = false; + g6ts_send_heat_feedback_a5(ts, false); + } + break; + } + /* + * Heat is demand-driven on this panel: the first frame may not exist until + * a finger reaches the glass. Phase 77 therefore opens the response path + * after the mode handshake, but suppresses every non-Heat input report + * until a complete Heat frame has passed the normal structural parser. + * Pen input is an independent HID collection and remains usable while + * touch readiness is being established. + */ + if (ts->awaiting_ready_heat && + ts->last_content_id != G6TS_HEATMAP_REPORT_ID && + ts->last_content_id != G6TS_PEN_REPORT_ID) + return; + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + if (ts->last_content_id == G6TS_PEN_REPORT_ID) { + if (!ts->ipts_enabled) + return; + if (ts->last_content_len != G6TS_PEN_REPORT_LEN) { + dev_warn_ratelimited(&ts->spi->dev, + "malformed pen report length: %u\n", + ts->last_content_len); + return; + } + g6ts_report_pen(ts, payload); + return; + } +#endif + + if (ts->last_content_id == 0x40 && ts->last_content_len == 5) { + active = payload[0] & BIT(0); + x = get_unaligned_le16(&payload[1]); + y = get_unaligned_le16(&payload[3]); + + input_report_key(ts->input, BTN_TOUCH, active); + if (active) + touchscreen_report_pos(ts->input, &ts->prop, x, y, false); + input_sync(ts->input); + return; + } + + if (ts->last_content_id == G6TS_HEATMAP_REPORT_ID && + ts->last_content_len + 1 <= G6TS_MAX_BODY) { + int ret; + + ret = g6ts_report_heat_contacts(ts, payload, ts->last_content_len); + if (ret) { + if (ts->awaiting_ready_heat) + ts->ready_verification_failures++; + g6ts_release_contacts(ts); + dev_warn_ratelimited(&ts->spi->dev, + "malformed Heat frame: %d\n", ret); + } else if (ts->awaiting_ready_heat) { + ts->awaiting_ready_heat = false; + ts->ready_heat_frames++; + dev_info(&ts->spi->dev, + "touch input ready after first valid Heat frame\n"); + } + } +} + +/* Read one complete pending HID-over-SPI response, and never retry. */ +static int g6ts_dma_read_response(struct g6ts *ts) +{ + const struct hidspi_dev_descriptor *descriptor; + s64 interrupt_edges; + size_t body_len; + u16 words; + int pending; + int ret; + + g6ts_clear_last_response(ts); + pending = g6ts_pending(ts); + interrupt_edges = atomic64_read(&ts->interrupt_edges); + if (pending <= 0 && + interrupt_edges <= ts->handled_interrupt_edges) { + return pending < 0 ? pending : -EAGAIN; + } + ts->handled_interrupt_edges = interrupt_edges; + + ret = g6ts_dma_read_pair(ts, g6ts_header_cmd, ts->last_header, + sizeof(ts->last_header)); + if (ret) { + ts->fatal_transport_error = true; + return ret; + } + + if ((ts->last_header[0] & 0x0f) != G6TS_HEADER_VERSION || + ts->last_header[3] != G6TS_HEADER_SYNC) { + if (ts->ipts_enabled) { + /* Let the panel retire the body transaction before rechecking. */ + usleep_range(100, 200); + pending = g6ts_pending(ts); + if (!pending) { + ts->quiesced_empty_reads++; + return -EAGAIN; + } + if (pending < 0) + return pending; + } + dev_warn_ratelimited(&ts->spi->dev, + "invalid HID-SPI header=%4ph ready=%d\n", + ts->last_header, g6ts_pending(ts)); + return -EPROTO; + } + + words = get_unaligned_le16(&ts->last_header[1]) & 0x3fff; + body_len = (size_t)words * 4; + if (body_len < HIDSPI_INPUT_BODY_HEADER_SIZE || + body_len > G6TS_MAX_BODY) + return -EMSGSIZE; + if (g6ts_windows_read_cadence) + usleep_range(G6TS_WINDOWS_HEADER_BODY_MIN_US, + G6TS_WINDOWS_HEADER_BODY_MAX_US); + + ret = g6ts_dma_read_pair(ts, g6ts_body_cmd, ts->body, body_len); + if (ret) { + ts->fatal_transport_error = true; + return ret; + } + + ts->last_class = ts->body[0]; + ts->last_content_len = get_unaligned_le16(&ts->body[1]); + ts->last_content_id = ts->body[3]; + if (ts->last_content_len > body_len - HIDSPI_INPUT_BODY_HEADER_SIZE) + return -EPROTO; + + if (ts->last_class == DEVICE_DESCRIPTOR_RESPONSE && + ts->last_content_len == HIDSPI_DEVICE_DESCRIPTOR_SIZE) { + descriptor = (const struct hidspi_dev_descriptor *) + (ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE); + ts->expected_report_descriptor_len = + le16_to_cpu(descriptor->rep_desc_len); + } + /* Recovery consumes and validates replies before reopening Linux input. */ + if (!g6ts_is_quiescing(ts) && READ_ONCE(ts->mode_enabled)) + g6ts_handle_data_report(ts); + return 0; +} + +/* io_lock is held by both the IRQ thread and the recovery worker. */ +static void g6ts_note_panel_reset_locked(struct g6ts *ts, + bool schedule_recovery) +{ + unsigned long now = jiffies; + unsigned long interval_ms = 0; + + if (ts->last_reset_jiffies) + interval_ms = jiffies_to_msecs(now - ts->last_reset_jiffies); + ts->last_reset_jiffies = now; + ts->reset_notifications++; + ts->mode_enabled = false; + g6ts_ipts_irq_disable_nosync(ts); + ts->awaiting_ready_heat = false; + ts->recovery_path = g6ts_reset_recovery_v2 ? + G6TS_RECOVERY_SOFTWARE : G6TS_RECOVERY_HARDWARE; + if (g6ts_reset_storm_breaker) { + if (interval_ms && interval_ms <= G6TS_RESET_STORM_WINDOW_MS) + ts->rapid_reset_streak++; + else + ts->rapid_reset_streak = 1; + + if (ts->rapid_reset_streak >= G6TS_RESET_STORM_LIMIT) { + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + ts->reset_storm_escalations++; + dev_warn(&ts->spi->dev, + "rapid reset circuit breaker #%llu after %u resets\n", + ts->reset_storm_escalations, + ts->rapid_reset_streak); + } + } + g6ts_release_inputs(ts); + g6ts_heat_generation_boundary(ts, G6TS_HEAT_RECORD_F_RESET); + dev_warn(&ts->spi->dev, + "panel reset notification #%llu interval=%lums\n", + ts->reset_notifications, interval_ms); + + if (schedule_recovery && !g6ts_is_quiescing(ts)) + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_DELAY_MS)); +} + +/* io_lock is held and the caller has observed one complete IRQ read failure. */ +static void g6ts_note_host_fault_locked(struct g6ts *ts, int error) +{ + if (error == -EPROTO || error == -EMSGSIZE) + ts->irq_protocol_errors++; + else + ts->irq_transport_errors++; + ts->last_host_fault = error; + g6ts_heat_generation_boundary(ts, + G6TS_HEAT_RECORD_F_TRANSPORT_FAULT); + + if (!ts->ipts_enabled) { + dev_warn_ratelimited(&ts->spi->dev, + "IRQ response read failed: %d\n", error); + return; + } + + /* + * A fresh hardware recovery is allowed to use the transport again. A new + * fatal error during that attempt will still stop the worker, preserving + * the existing bounded-failure behavior. + */ + ts->fatal_transport_error = false; + ts->mode_enabled = false; + g6ts_ipts_irq_disable_nosync(ts); + ts->awaiting_ready_heat = false; + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + ts->rapid_reset_streak = 0; + ts->host_fault_recoveries++; + g6ts_release_inputs(ts); + dev_warn(&ts->spi->dev, + "host HID-SPI fault #%llu ret=%d; scheduling cold recovery\n", + ts->host_fault_recoveries, error); + + if (!g6ts_is_quiescing(ts)) + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_DELAY_MS)); +} + +static irqreturn_t g6ts_interrupt_thread(int irq, void *data) +{ + struct g6ts *ts = data; + unsigned int i; + int ret; + + if (g6ts_is_quiescing(ts) || !READ_ONCE(ts->mode_enabled)) { + g6ts_ipts_irq_disable_nosync(ts); + return IRQ_HANDLED; + } + + mutex_lock(&ts->io_lock); + for (i = 0; i < G6TS_IRQ_DRAIN_LIMIT; i++) { + if (!g6ts_has_unread_response(ts)) + break; + ret = g6ts_dma_read_response(ts); + if (ret) { + if (ret != -EAGAIN) + g6ts_note_host_fault_locked(ts, ret); + break; + } + if (ts->last_class == RESET_RESPONSE) { + g6ts_note_panel_reset_locked(ts, true); + break; + } + if (g6ts_windows_read_cadence || ts->ipts_enabled) { + ts->cadence_single_response_irqs++; + break; + } + } + /* + * The default IRQ path drains the edge-triggered stream while GPIO51 still + * advertises an unread packet. Phase 84 uses the firmware's level-low IRQ, + * so returning after one response retriggers while more data is pending. + * Bound only the default drain for safety, then use the same observable + * host-fault recovery as a timed-out Windows transfer. + */ + if (i == G6TS_IRQ_DRAIN_LIMIT && g6ts_has_unread_response(ts)) { + ts->irq_drain_overflows++; + g6ts_note_host_fault_locked(ts, -EOVERFLOW); + } + mutex_unlock(&ts->io_lock); + return IRQ_HANDLED; +} + +static int __g6ts_dma_feature_exchange(struct g6ts *ts, u8 report_type, + u8 content_id, const u8 *content, + size_t content_len, bool dump_response) +{ + char dump_prefix[64]; + u8 expected_class; + unsigned int response_index; + int ret; + + if (report_type == SET_FEATURE) + expected_class = SET_FEATURE_RESPONSE; + else if (report_type == GET_FEATURE) + expected_class = GET_FEATURE_RESPONSE; + else + return -EINVAL; + + ret = g6ts_dma_hidspi_output(ts, report_type, content_id, + content, content_len); + if (ret) { + ts->fatal_transport_error = true; + return ret; + } + + for (response_index = 0; + response_index < G6TS_FEATURE_RESPONSE_LIMIT; + response_index++) { + ret = g6ts_wait_pending(ts, 1000); + if (ret) + return ret; + + ret = g6ts_dma_read_response(ts); + if (ret) + return ret; + + if (ts->last_class == expected_class && + ts->last_content_id == content_id) { + if (dump_response && report_type == GET_FEATURE && + !ts->init_get_feature_dumped[ts->initialization_stage]) { + ts->init_get_feature_dumped[ts->initialization_stage] = true; + scnprintf(dump_prefix, sizeof(dump_prefix), + "%s init GET_FEATURE 0x%02x: ", + dev_name(&ts->spi->dev), content_id); + dev_info(&ts->spi->dev, + "init GET_FEATURE response stage=%s id=0x%02x len=%u\n", + g6ts_initialization_stage_name(ts->initialization_stage), + content_id, ts->last_content_len); + print_hex_dump(KERN_INFO, dump_prefix, + DUMP_PREFIX_OFFSET, 16, 1, + ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE, + ts->last_content_len, false); + } + return 0; + } + if (ts->last_class == RESET_RESPONSE) { + g6ts_note_panel_reset_locked(ts, false); + return -EPIPE; + } + + /* Streaming data can precede a solicited feature response. */ + if (ts->last_class == DATA) + continue; + if (ts->last_class == OUTPUT_REPORT_RESPONSE && + ts->last_content_id == 0x09) + continue; + + return -EPROTO; + } + + return -EOVERFLOW; +} + +static int g6ts_dma_feature_exchange(struct g6ts *ts, u8 report_type, + u8 content_id, const u8 *content, + size_t content_len) +{ + return __g6ts_dma_feature_exchange(ts, report_type, content_id, content, + content_len, true); +} + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) +static int g6ts_cache_ipts_metadata_locked(struct g6ts *ts) +{ + const u8 *metadata = + ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + + if (ts->last_class != GET_FEATURE_RESPONSE || + ts->last_content_id != G6TS_IPTS_METADATA_REPORT_ID || + ts->last_content_len != G6TS_IPTS_METADATA_LEN) + return -EPROTO; + if (ts->ipts_metadata_valid && + memcmp(ts->ipts_metadata, metadata, G6TS_IPTS_METADATA_LEN)) { + dev_err(&ts->spi->dev, + "IPTS metadata changed across transport generation\n"); + return -EPROTO; + } + + memcpy(ts->ipts_metadata, metadata, G6TS_IPTS_METADATA_LEN); + ts->ipts_metadata_valid = true; + return 0; +} + +static int g6ts_ipts_hid_ll_parse(struct hid_device *hid) +{ + struct g6ts *ts = hid->driver_data; + + if (!ts) + return -ENODEV; + + if (!READ_ONCE(ts->report_descriptor_valid)) + return -ENODEV; + + return hid_parse_report(hid, ts->report_descriptor, + G6TS_SP11_REPORT_DESCRIPTOR_LEN); +} + +static int g6ts_ipts_hid_ll_start(struct hid_device *hid) +{ + return 0; +} + +static void g6ts_ipts_hid_ll_stop(struct hid_device *hid) +{ +} + +static int g6ts_ipts_hid_ll_open(struct hid_device *hid) +{ + return 0; +} + +static void g6ts_ipts_hid_ll_close(struct hid_device *hid) +{ +} + +/* + * The custom transport owns mode selection. iptsd's report-0x05 writes are + * therefore acknowledged without reaching the panel; in particular, its + * shutdown write must not switch the shared controller back to singletouch. + * The metadata GET is proxied once and then served from the stable cache. + */ +static int g6ts_ipts_hid_ll_raw_request(struct hid_device *hid, + unsigned char reportnum, u8 *buf, + size_t len, unsigned char rtype, + int reqtype) +{ + struct g6ts *ts = hid->driver_data; + size_t metadata_len; + int ret; + + if (!ts) + return -ENODEV; + if (!buf || len < 2) + return -EINVAL; + if (rtype != HID_FEATURE_REPORT) + return -EOPNOTSUPP; + + mutex_lock(&ts->io_lock); + if (g6ts_is_quiescing(ts) || ts->ipts_hid != hid) { + ret = -ENODEV; + goto out_unlock; + } + + if (reqtype == HID_REQ_SET_REPORT) { + if (reportnum != G6TS_IPTS_MODE_REPORT_ID || + buf[0] != reportnum || len != 2 || buf[1] > 1) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + ret = len; + goto out_unlock; + } + + if (reqtype != HID_REQ_GET_REPORT || + reportnum != G6TS_IPTS_METADATA_REPORT_ID) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + if (!READ_ONCE(ts->mode_enabled)) { + ret = -EHOSTDOWN; + goto out_unlock; + } + + if (!ts->ipts_metadata_valid) { + ret = __g6ts_dma_feature_exchange(ts, GET_FEATURE, + G6TS_IPTS_METADATA_REPORT_ID, + NULL, 0, false); + if (!ret) + ret = g6ts_cache_ipts_metadata_locked(ts); + if (ret) { + if (ret == -EPIPE && !g6ts_is_quiescing(ts)) + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_DELAY_MS)); + goto out_unlock; + } + } + + metadata_len = min_t(size_t, len - 1, G6TS_IPTS_METADATA_LEN); + buf[0] = reportnum; + memcpy(buf + 1, ts->ipts_metadata, metadata_len); + ret = metadata_len + 1; + +out_unlock: + mutex_unlock(&ts->io_lock); + return ret; +} + +static const struct hid_ll_driver g6ts_ipts_hid_ll_driver = { + .parse = g6ts_ipts_hid_ll_parse, + .start = g6ts_ipts_hid_ll_start, + .stop = g6ts_ipts_hid_ll_stop, + .open = g6ts_ipts_hid_ll_open, + .close = g6ts_ipts_hid_ll_close, + .raw_request = g6ts_ipts_hid_ll_raw_request, + .max_buffer_size = G6TS_MAX_BODY, +}; + +static bool g6ts_ipts_hid_desired_locked(const struct g6ts *ts) +{ + return ts->ipts_enabled && !g6ts_is_quiescing(ts) && + READ_ONCE(ts->mode_enabled) && + ts->report_descriptor_valid && ts->hid_product_id; +} + +static struct hid_device *g6ts_ipts_hid_detach_locked(struct g6ts *ts) +{ + struct hid_device *hid = ts->ipts_hid; + + ts->ipts_hid = NULL; + ts->ipts_hid_ready = false; + ts->hid_bound_generation = 0; + return hid; +} + +static struct hid_device *g6ts_ipts_hid_allocate(struct g6ts *ts, + u16 product_id) +{ + struct hid_device *hid; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return hid; + + /* Low-level transports own driver_data; drvdata belongs to the HID client. */ + hid->driver_data = ts; + hid->ll_driver = &g6ts_ipts_hid_ll_driver; + hid->dev.parent = &ts->spi->dev; + hid->bus = BUS_SPI; + hid->group = HID_GROUP_MSHW0485_IPTS; + hid->vendor = G6TS_SP11_VENDOR_ID; + hid->product = product_id; + hid->version = G6TS_SP11_VERSION_ID; + snprintf(hid->name, sizeof(hid->name), + "Microsoft Surface G6 IPTS %04X:%04X", + G6TS_SP11_VENDOR_ID, product_id); + snprintf(hid->phys, sizeof(hid->phys), "%s/ipts", + dev_name(&ts->spi->dev)); + + return hid; +} + +static bool g6ts_ipts_hid_retry_locked(struct g6ts *ts, u32 generation) +{ + if (!g6ts_ipts_hid_desired_locked(ts) || ts->ipts_hid || + ts->hid_target_generation != generation || + ts->hid_rebind_failures >= G6TS_HID_REBIND_RETRY_LIMIT) + return false; + + ts->hid_rebind_failures++; + return true; +} + +/* + * HID teardown may take hidraw's minors_rwsem for write. Never destroy while + * io_lock is held: a concurrent feature ioctl takes that rwsem for read before + * entering our raw_request callback and acquiring io_lock. + */ +static void g6ts_ipts_hid_rebind_work(struct work_struct *work) +{ + struct g6ts *ts = container_of(to_delayed_work(work), struct g6ts, + hid_rebind_work); + struct hid_device *old_hid = NULL; + struct hid_device *hid; + bool create = false; + bool retry = false; + u32 generation = 0; + u16 product_id = 0; + int ret; + + mutex_lock(&ts->io_lock); + if (ts->ipts_hid && + (!g6ts_ipts_hid_desired_locked(ts) || + ts->hid_bound_generation != ts->hid_target_generation)) + old_hid = g6ts_ipts_hid_detach_locked(ts); + if (!ts->ipts_hid && g6ts_ipts_hid_desired_locked(ts)) { + generation = ts->hid_target_generation; + product_id = ts->hid_product_id; + create = true; + } + mutex_unlock(&ts->io_lock); + + if (old_hid) + hid_destroy_device(old_hid); + if (!create) + return; + + hid = g6ts_ipts_hid_allocate(ts, product_id); + if (IS_ERR(hid)) { + ret = PTR_ERR(hid); + mutex_lock(&ts->io_lock); + retry = g6ts_ipts_hid_retry_locked(ts, generation); + mutex_unlock(&ts->io_lock); + dev_err(&ts->spi->dev, + "failed to allocate IPTS HID device: %d%s\n", ret, + retry ? "; retrying" : ""); + if (retry) + mod_delayed_work(system_wq, &ts->hid_rebind_work, + msecs_to_jiffies(G6TS_HID_REBIND_RETRY_MS)); + return; + } + + mutex_lock(&ts->io_lock); + if (!g6ts_ipts_hid_desired_locked(ts) || ts->ipts_hid || + ts->hid_target_generation != generation || + ts->hid_product_id != product_id) { + mutex_unlock(&ts->io_lock); + hid_destroy_device(hid); + return; + } + ts->ipts_hid = hid; + ts->ipts_hid_ready = false; + ts->hid_bound_generation = generation; + mutex_unlock(&ts->io_lock); + + ret = hid_add_device(hid); + if (!ret && hid->claimed != HID_CLAIMED_HIDRAW) + ret = -ENODEV; + + mutex_lock(&ts->io_lock); + if (!ret && ts->ipts_hid == hid && + g6ts_ipts_hid_desired_locked(ts) && + ts->hid_target_generation == generation) { + ts->ipts_hid_ready = true; + ts->hid_rebind_failures = 0; + mutex_unlock(&ts->io_lock); + dev_info(&ts->spi->dev, + "IPTS HIDRAW bridge ready generation=%u product=%04x\n", + generation, product_id); + return; + } + if (ts->ipts_hid == hid) + g6ts_ipts_hid_detach_locked(ts); + if (ret) + retry = g6ts_ipts_hid_retry_locked(ts, generation); + mutex_unlock(&ts->io_lock); + + if (ret) + dev_err(&ts->spi->dev, + "failed to register HIDRAW-only IPTS device: %d%s\n", + ret, retry ? "; retrying" : ""); + hid_destroy_device(hid); + if (retry) + mod_delayed_work(system_wq, &ts->hid_rebind_work, + msecs_to_jiffies(G6TS_HID_REBIND_RETRY_MS)); +} +#endif + +static int g6ts_expect_response(struct g6ts *ts, u8 response_class, + u8 content_id, size_t min_content_len) +{ + if (ts->last_class != response_class || + ts->last_content_id != content_id || + ts->last_content_len < min_content_len) + return -EPROTO; + + return 0; +} + +static int g6ts_validate_sp11_device_descriptor(struct g6ts *ts) +{ + const struct hidspi_dev_descriptor *descriptor; + u16 product_id; + + if (ts->last_class != DEVICE_DESCRIPTOR_RESPONSE || + ts->last_content_id != 0 || + ts->last_content_len != HIDSPI_DEVICE_DESCRIPTOR_SIZE) + return -EPROTO; + + descriptor = (const struct hidspi_dev_descriptor *) + (ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE); + if (le16_to_cpu(descriptor->dev_desc_len) != + HIDSPI_DEVICE_DESCRIPTOR_SIZE || + le16_to_cpu(descriptor->bcd_ver) != 0x0300 || + le16_to_cpu(descriptor->rep_desc_len) != + G6TS_SP11_REPORT_DESCRIPTOR_LEN || + le16_to_cpu(descriptor->max_input_len) != G6TS_SP11_MAX_INPUT_LEN || + le16_to_cpu(descriptor->max_output_len) != G6TS_SP11_MAX_OUTPUT_LEN || + le16_to_cpu(descriptor->max_frag_len) != G6TS_SP11_MAX_FRAGMENT_LEN || + le16_to_cpu(descriptor->vendor_id) != G6TS_SP11_VENDOR_ID || + (le16_to_cpu(descriptor->product_id) != G6TS_SP11_X1E_PRODUCT_ID && + le16_to_cpu(descriptor->product_id) != G6TS_SP11_X1P_PRODUCT_ID) || + le16_to_cpu(descriptor->version_id) != G6TS_SP11_VERSION_ID || + le16_to_cpu(descriptor->flags) != G6TS_SP11_DESCRIPTOR_FLAGS || + le32_to_cpu(descriptor->reserved) != 0) + return -ENODEV; + + product_id = le16_to_cpu(descriptor->product_id); + if (ts->hid_product_id && ts->hid_product_id != product_id) + return -EPROTO; + ts->hid_product_id = product_id; + return 0; +} + +static int g6ts_recovery_read_expected(struct g6ts *ts, u8 response_class, + u8 content_id, size_t min_content_len); + +static bool g6ts_windows_feedback_provider_valid(void) +{ + return g6ts_parity_display_bitmap >= 0 && + g6ts_parity_display_bitmap <= U8_MAX && + (g6ts_parity_stitching_flag == 0 || + g6ts_parity_stitching_flag == 1) && + g6ts_parity_hinge_angle >= 0 && + g6ts_parity_fast_host_id >= 0 && + g6ts_parity_fast_host_id <= U16_MAX; +} + +static bool g6ts_windows_config_provider_valid(void) +{ + return g6ts_parity_report56_identity_count == + G6TS_WINDOWS_REPORT56_ID_LEN && + (g6ts_parity_report56_flag == 0 || + g6ts_parity_report56_flag == 1); +} + +static void g6ts_build_windows_feedback_a1(u8 content[G6TS_WINDOWS_FEEDBACK_LEN]) +{ + memset(content, 0, G6TS_WINDOWS_FEEDBACK_LEN); + content[0] = 0x8e; + content[1] = 0xa1; + content[2] = g6ts_parity_display_bitmap; + content[3] = g6ts_parity_stitching_flag; + put_unaligned_le32(g6ts_parity_hinge_angle, &content[4]); + put_unaligned_le16(g6ts_parity_fast_host_id, &content[40]); +} + +static void g6ts_build_windows_feedback_a5(struct g6ts *ts, + u8 content[G6TS_WINDOWS_FEEDBACK_LEN]) +{ + memset(content, 0, G6TS_WINDOWS_FEEDBACK_LEN); + content[0] = 0x8e; + content[1] = 0xa5; + /* Initial V06 sequence and current-feedback flags with no pen record. */ + content[2] = 0; + content[3] = BIT(1); + put_unaligned_le16(ts->a5.fast_host_id, &content[39]); + /* The V06 sender unconditionally adds validity bit 0x0040. */ + put_unaligned_le16(0x0040, &content[46]); +} + +static void g6ts_send_heat_feedback_a5(struct g6ts *ts, + bool after_heat_group) +{ + /* + * This bounded encoder preserves the hardware-proven V06 timing and wire + * template; only sequence, cycle phase, and configured FastHostId are + * validated live state. Offsets 8..38 and the validity maps remain + * unmapped, so never replay a captured body or accept an opaque userspace + * payload in their place. + */ + u8 content[G6TS_WINDOWS_FEEDBACK_LEN] = { 0 }; + int ret; + + if (!g6ts_heat_feedback) + return; + if (!ts->a5.fast_host_id_valid) { + dev_warn_once(&ts->spi->dev, + "heat feedback disabled: parity_fast_host_id is unavailable or out of range\n"); + return; + } + + content[0] = 0x8e; + content[1] = 0xa5; + content[2] = ts->a5.sequence; + content[3] = 0x02; + put_unaligned_le16(0x1770, &content[12]); + content[34] = 0xff; + content[35] = 0x4c; + put_unaligned_le16(ts->a5.fast_host_id, &content[39]); + content[41] = 0x01; + content[43] = after_heat_group ? 0x04 : 0x02; + content[44] = 0xff; + content[46] = 0x52; + content[47] = 0x43; + content[48] = 0xff; + content[49] = 0x02; + content[50] = 0x01; + + ret = g6ts_dma_hidspi_output(ts, OUTPUT_REPORT, 0x09, content, + sizeof(content)); + if (ret) { + dev_warn_ratelimited(&ts->spi->dev, + "failed to send heat feedback phase %#02x: %d\n", + content[43], ret); + return; + } + + /* Advance only after a successful write; u8 wrap is the wire behavior. */ + ts->a5.sequence++; + ts->heat_feedback_sent++; + if (after_heat_group) + ts->heat_feedback_phase_a++; + else + ts->heat_feedback_phase_b++; +} + +static bool g6ts_windows_cfu_provider_valid(void) +{ + /* Force-immediate and force-ignore-version are development-only bits. */ + return g6ts_parity_cfu_offer_count == G6TS_WINDOWS_CFU_OFFER_LEN && + !(g6ts_parity_cfu_offer[1] & (BIT(6) | BIT(7))) && + !memcmp(g6ts_parity_cfu_offer, g6ts_sp11_cfu_offer, + G6TS_WINDOWS_CFU_OFFER_LEN); +} + +static void +g6ts_build_windows_cfu_info(u8 content[G6TS_WINDOWS_CFU_OFFER_LEN], + u8 information_code) +{ + memset(content, 0, G6TS_WINDOWS_CFU_OFFER_LEN); + content[0] = information_code; + content[2] = 0xff; + content[3] = G6TS_WINDOWS_CFU_TOKEN; +} + +static bool g6ts_windows_cfu_response_well_formed(const u8 *content) +{ + return content[0] == 0 && content[1] == 0 && content[2] == 0 && + content[3] == G6TS_WINDOWS_CFU_TOKEN && + get_unaligned_le32(&content[4]) == 0 && + content[9] == 0 && content[10] == 0 && content[11] == 0 && + content[13] == 0 && content[14] == 0 && content[15] == 0; +} + +static int g6ts_windows_cfu_send_locked(struct g6ts *ts, const u8 *content) +{ + int ret; + + ret = g6ts_dma_hidspi_output(ts, OUTPUT_REPORT, 0x65, content, + G6TS_WINDOWS_CFU_OFFER_LEN); + if (ret) + return ret; + + ret = g6ts_recovery_read_expected(ts, DATA, 0x65, + G6TS_WINDOWS_CFU_OFFER_LEN); + if (ret || ts->last_content_len != G6TS_WINDOWS_CFU_OFFER_LEN) + return ret ? ret : -EPROTO; + + content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + if (!g6ts_windows_cfu_response_well_formed(content)) + return -EPROTO; + + return 0; +} + +static int g6ts_windows_cfu_expect_info_accept(struct g6ts *ts) +{ + const u8 *content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + + /* The target returns 0xff in the don't-care reject-reason byte. */ + if (content[8] != 0xff || content[12] != 0x01) + return -EPROTO; + + return 0; +} + +static int g6ts_windows_cfu_inventory_locked(struct g6ts *ts) +{ + u8 transaction[G6TS_WINDOWS_CFU_OFFER_LEN]; + const u8 *content; + int ret; + + /* Captured gap between the config owner and CFU collection attach. */ + msleep(G6TS_WINDOWS_CFU_DELAY_MS); + + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_GET_VERSION; + ret = g6ts_dma_feature_exchange(ts, GET_FEATURE, 0x60, NULL, 0); + if (ret) + return ret; + ret = g6ts_expect_response(ts, GET_FEATURE_RESPONSE, 0x60, + G6TS_WINDOWS_CFU_VERSION_LEN); + if (ret || ts->last_content_len != G6TS_WINDOWS_CFU_VERSION_LEN) + return ret ? ret : -EPROTO; + content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + memcpy(ts->parity_cfu_version_prefix, content, + sizeof(ts->parity_cfu_version_prefix)); + /* + * Decode only the header and the one declared component. Bytes after the + * component-count boundary are not inputs to the CFU decision. + */ + if (content[0] != 1 || content[1] != 0 || content[2] != 0 || + content[3] != 0x04 || + memcmp(&content[4], &g6ts_parity_cfu_offer[4], 4) || + content[9] != g6ts_parity_cfu_offer[2]) + return -EPROTO; + + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_START_TRANSACTION; + g6ts_build_windows_cfu_info(transaction, 0); + ret = g6ts_windows_cfu_send_locked(ts, transaction); + if (ret) + return ret; + ret = g6ts_windows_cfu_expect_info_accept(ts); + if (ret) + return ret; + + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_START_LIST; + g6ts_build_windows_cfu_info(transaction, 1); + ret = g6ts_windows_cfu_send_locked(ts, transaction); + if (ret) + return ret; + ret = g6ts_windows_cfu_expect_info_accept(ts); + if (ret) + return ret; + + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_OFFER; + memcpy(transaction, g6ts_parity_cfu_offer, sizeof(transaction)); + transaction[3] = G6TS_WINDOWS_CFU_TOKEN; + ret = g6ts_windows_cfu_send_locked(ts, transaction); + if (ret) + return ret; + content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + memcpy(ts->parity_cfu_offer_response, content, + sizeof(ts->parity_cfu_offer_response)); + if (content[8] != 0 || content[12] != 0x02) { + /* + * Windows branches into payload, replay, or busy handling here. This + * driver intentionally implements none of those firmware-update paths. + */ + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_BRANCH_REQUIRED; + ts->parity_cfu_branch_required = true; + dev_notice(&ts->spi->dev, + "Windows CFU branch required: reject_reason=%#x status=%#x; no payload sent\n", + content[8], content[12]); + return 0; + } + + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_END_LIST; + g6ts_build_windows_cfu_info(transaction, 2); + ret = g6ts_windows_cfu_send_locked(ts, transaction); + if (ret) + return ret; + ret = g6ts_windows_cfu_expect_info_accept(ts); + if (ret) + return ret; + + /* Captured gap before the device-config owner's post-CFU query. */ + msleep(G6TS_WINDOWS_FINAL_CONFIG_DELAY_MS); + ts->initialization_stage = G6TS_INIT_WINDOWS_FINAL_FEATURE73; + ret = g6ts_dma_feature_exchange(ts, GET_FEATURE, 0x73, NULL, 0); + if (ret) + return ret; + ret = g6ts_expect_response(ts, GET_FEATURE_RESPONSE, 0x73, 2); + if (ret || ts->last_content_len != 2) + return ret ? ret : -EPROTO; + content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + memcpy(ts->parity_feature73_late, content, + sizeof(ts->parity_feature73_late)); + + if (!g6ts_parity_heat_input) { + ts->initialization_stage = G6TS_INIT_WINDOWS_HEAT_OWNER_REQUIRED; + ts->parity_heat_owner_required = true; + dev_notice(&ts->spi->dev, + "Windows cold chronology reached Heat boundary: early73=%2ph late73=%2ph; input remains disabled\n", + ts->parity_feature73_early, + ts->parity_feature73_late); + return 0; + } + + /* + * SET_FEATURE 0x05 already activated the Heat collection in the captured + * owner order. Do not send another mode command here: merely open the + * response consumer and require the first complete report 0x12 to pass the + * normal structural parser before declaring input ready. + */ + ts->initialization_stage = G6TS_INIT_WAIT_HEAT; + ts->awaiting_ready_heat = true; + ts->mode_enabled = true; + dev_notice(&ts->spi->dev, + "Windows init+CFU chronology complete: early73=%2ph late73=%2ph; waiting for first valid Heat frame\n", + ts->parity_feature73_early, ts->parity_feature73_late); + return 0; +} + +/* + * Reproduce only the proven, serialized part of the Windows cold collection + * attach. Dynamic values must come from their owning provider. Returning + * success with mode_enabled clear intentionally leaves this diagnostic driver + * bound and observable without claiming that normal touch mode was reached. + */ +static int g6ts_windows_cold_attach_locked(struct g6ts *ts) +{ + u8 feedback[G6TS_WINDOWS_FEEDBACK_LEN]; + u8 report56[G6TS_WINDOWS_REPORT56_ID_LEN + 1]; + const u8 *content; + int ret; + + ts->initialization_stage = G6TS_INIT_WINDOWS_EARLY_FEATURE73; + ret = g6ts_dma_feature_exchange(ts, GET_FEATURE, 0x73, NULL, 0); + if (ret) + return ret; + ret = g6ts_expect_response(ts, GET_FEATURE_RESPONSE, 0x73, 2); + if (ret || ts->last_content_len != 2) + return ret ? ret : -EPROTO; + content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + memcpy(ts->parity_feature73_early, content, + sizeof(ts->parity_feature73_early)); + if (content[0] != 0xfe || content[1] != 0xff) + return -EPROTO; + + ts->initialization_stage = G6TS_INIT_WINDOWS_HEAT_CAPS06; + ret = g6ts_dma_feature_exchange(ts, GET_FEATURE, + G6TS_IPTS_METADATA_REPORT_ID, NULL, 0); + if (ret) + return ret; + ret = g6ts_expect_response(ts, GET_FEATURE_RESPONSE, + G6TS_IPTS_METADATA_REPORT_ID, + G6TS_IPTS_METADATA_LEN); + if (ret || ts->last_content_len != G6TS_IPTS_METADATA_LEN) + return ret ? ret : -EPROTO; +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + if (ts->ipts_enabled) { + ret = g6ts_cache_ipts_metadata_locked(ts); + if (ret) + return ret; + } +#endif + content = ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE; + memcpy(ts->parity_feature06_prefix, content, + sizeof(ts->parity_feature06_prefix)); + + if (!g6ts_windows_feedback_provider_valid()) { + ts->initialization_stage = G6TS_INIT_WINDOWS_FEEDBACK_REQUIRED; + ts->parity_feedback_required = true; + ts->mode_enabled = false; + dev_notice(&ts->spi->dev, + "Windows parity boundary reached: early feature73=%2ph; A1/A5 provider inputs are required\n", + ts->parity_feature73_early); + return 0; + } + + ts->initialization_stage = G6TS_INIT_WINDOWS_FEEDBACK_A1; + g6ts_build_windows_feedback_a1(feedback); + ret = g6ts_dma_hidspi_output(ts, OUTPUT_REPORT, 0x09, feedback, + sizeof(feedback)); + if (ret) + return ret; + /* The captured SP11 cold attach acknowledges A1 with DATA report A0={01}. */ + ret = g6ts_recovery_read_expected(ts, DATA, 0xa0, 1); + if (ret || ts->last_content_len != 1 || + ts->body[HIDSPI_INPUT_BODY_HEADER_SIZE] != 0x01) + return ret ? ret : -EPROTO; + + ts->initialization_stage = G6TS_INIT_WINDOWS_FEEDBACK_A5; + g6ts_build_windows_feedback_a5(ts, feedback); + ret = g6ts_dma_hidspi_output(ts, OUTPUT_REPORT, 0x09, feedback, + sizeof(feedback)); + if (ret) + return ret; + + ts->initialization_stage = G6TS_INIT_WINDOWS_SET_FEATURE05; + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x05, + g6ts_mode_enable, + sizeof(g6ts_mode_enable)); + if (ret) + return ret; + ret = g6ts_expect_response(ts, SET_FEATURE_RESPONSE, 0x05, 0); + if (ret || ts->last_content_len != 0) + return ret ? ret : -EPROTO; + + if (!g6ts_windows_config_provider_valid()) { + ts->initialization_stage = G6TS_INIT_WINDOWS_CONFIG_OWNER_REQUIRED; + ts->parity_config_owner_required = true; + ts->mode_enabled = false; + dev_notice(&ts->spi->dev, + "Windows parity boundary reached: report56 platform identity and flag are required\n"); + return 0; + } + + /* + * The complete cold KDNET trace places 476 ms between the SET_FEATURE 0x05 + * response and the independent config owner's GET_FEATURE 0x70. Preserve + * a conservative 470 ms owner boundary; this is not a panel retry delay. + */ + msleep(G6TS_WINDOWS_CONFIG_DELAY_MS); + + ts->initialization_stage = G6TS_INIT_WINDOWS_GET_FEATURE70; + ret = g6ts_dma_feature_exchange(ts, GET_FEATURE, 0x70, NULL, 0); + if (ret) + return ret; + ret = g6ts_expect_response(ts, GET_FEATURE_RESPONSE, 0x70, 1); + if (ret || ts->last_content_len != 1 || + ts->body[HIDSPI_INPUT_BODY_HEADER_SIZE] != 0x02) + return ret ? ret : -EPROTO; + + ts->initialization_stage = G6TS_INIT_WINDOWS_SET_FEATURE70; + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x70, + g6ts_mode_enable, + sizeof(g6ts_mode_enable)); + if (ret) + return ret; + ret = g6ts_expect_response(ts, SET_FEATURE_RESPONSE, 0x70, 0); + if (ret || ts->last_content_len != 0) + return ret ? ret : -EPROTO; + + memcpy(report56, g6ts_parity_report56_identity, + G6TS_WINDOWS_REPORT56_ID_LEN); + report56[G6TS_WINDOWS_REPORT56_ID_LEN] = g6ts_parity_report56_flag; + ts->initialization_stage = G6TS_INIT_WINDOWS_SET_FEATURE56; + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x56, + report56, sizeof(report56)); + if (ret) + return ret; + ret = g6ts_expect_response(ts, SET_FEATURE_RESPONSE, 0x56, 0); + if (ret || ts->last_content_len != 0) + return ret ? ret : -EPROTO; + + /* CFU collection attach is deliberately separate from touch activation. */ + if (!g6ts_parity_cfu_inventory || + !g6ts_windows_cfu_provider_valid()) { + ts->initialization_stage = G6TS_INIT_WINDOWS_CFU_OWNER_REQUIRED; + ts->parity_cfu_owner_required = true; + ts->mode_enabled = false; + dev_notice(&ts->spi->dev, + "Windows parity reached the CFU-owner boundary; inventory opt-in and exact offer are required\n"); + return 0; + } + + return g6ts_windows_cfu_inventory_locked(ts); +} + +static int g6ts_recovery_read_expected(struct g6ts *ts, u8 response_class, + u8 content_id, size_t min_content_len) +{ + unsigned int response_index; + int ret; + + for (response_index = 0; + response_index < G6TS_FEATURE_RESPONSE_LIMIT; + response_index++) { + ret = g6ts_wait_pending(ts, 1000); + if (ret) + return ret; + + ret = g6ts_dma_read_response(ts); + if (ret) + return ret; + if (ts->last_class == response_class && + ts->last_content_id == content_id && + ts->last_content_len >= min_content_len) + return 0; + if (ts->last_class == RESET_RESPONSE) { + g6ts_note_panel_reset_locked(ts, false); + return -EPIPE; + } + + /* Stale input can precede the solicited reply. */ + if (ts->last_class == DATA || + (ts->last_class == OUTPUT_REPORT_RESPONSE && + ts->last_content_id == 0x09)) + continue; + + return -EPROTO; + } + + return -EOVERFLOW; +} + +/* + * Cold startup requires the validated power/reset sequence and its resulting + * RESET_RESPONSE. A panel-originated reset has already completed that part + * and its response was consumed by the IRQ thread, so the Phase 77 path starts + * at host re-enumeration instead of forcing another hardware reset. + */ +static int g6ts_full_reinitialize_locked(struct g6ts *ts, + enum g6ts_recovery_path path) +{ + int ret; + + ts->mode_enabled = false; + ts->awaiting_ready_heat = false; + ts->expected_report_descriptor_len = 0; + ts->initialization_stage = G6TS_INIT_IDLE; + ts->parity_feedback_required = false; + ts->parity_config_owner_required = false; + ts->parity_cfu_owner_required = false; + ts->parity_cfu_branch_required = false; + ts->parity_heat_owner_required = false; + memset(ts->parity_feature73_early, 0, + sizeof(ts->parity_feature73_early)); + memset(ts->parity_feature73_late, 0, + sizeof(ts->parity_feature73_late)); + memset(ts->parity_feature06_prefix, 0, + sizeof(ts->parity_feature06_prefix)); + memset(ts->parity_cfu_version_prefix, 0, + sizeof(ts->parity_cfu_version_prefix)); + memset(ts->parity_cfu_offer_response, 0, + sizeof(ts->parity_cfu_offer_response)); + + if (path == G6TS_RECOVERY_HARDWARE) { + ts->hardware_recovery_attempts++; + ret = g6ts_power_off(ts); + if (ret) + return ret; + msleep(100); + ret = g6ts_windows_init_parity && !g6ts_parity_linux_power ? + g6ts_windows_power_on(ts) : g6ts_power_on(ts); + if (ret) + return ret; + + ts->initialization_stage = G6TS_INIT_RESET_RESPONSE; + ret = g6ts_wait_pending(ts, 1000); + if (ret) + goto out; + ret = g6ts_dma_read_response(ts); + if (ret) + goto out; + ret = g6ts_expect_response(ts, RESET_RESPONSE, 0, 0); + if (ret) + goto out; + } else { + ts->software_recovery_attempts++; + ts->initialization_stage = G6TS_INIT_RESET_RESPONSE; + } + + ts->initialization_stage = G6TS_INIT_DEVICE_DESCRIPTOR; + ret = g6ts_dma_output(ts, g6ts_device_descriptor_cmd, + sizeof(g6ts_device_descriptor_cmd)); + if (ret) { + ts->fatal_transport_error = true; + goto out; + } + ret = g6ts_recovery_read_expected(ts, DEVICE_DESCRIPTOR_RESPONSE, 0, + HIDSPI_DEVICE_DESCRIPTOR_SIZE); + if (ret) + goto out; + ret = g6ts_validate_sp11_device_descriptor(ts); + if (ret) + goto out; + if (!ts->expected_report_descriptor_len || + ts->expected_report_descriptor_len > + G6TS_MAX_BODY - HIDSPI_INPUT_BODY_HEADER_SIZE) { + ret = -EPROTO; + goto out; + } + + ts->initialization_stage = G6TS_INIT_REPORT_DESCRIPTOR; + ret = g6ts_dma_output(ts, g6ts_report_descriptor_cmd, + sizeof(g6ts_report_descriptor_cmd)); + if (ret) { + ts->fatal_transport_error = true; + goto out; + } + ret = g6ts_recovery_read_expected(ts, REPORT_DESCRIPTOR_RESPONSE, 0, + ts->expected_report_descriptor_len); + if (ret || ts->last_content_len != ts->expected_report_descriptor_len) { + if (!ret) + ret = -EPROTO; + goto out; + } + if (ts->report_descriptor_valid) { + if (memcmp(ts->report_descriptor, + ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE, + G6TS_SP11_REPORT_DESCRIPTOR_LEN)) { + dev_err(&ts->spi->dev, + "report descriptor changed across transport generation\n"); + ret = -EPROTO; + goto out; + } + } else { + memcpy(ts->report_descriptor, + ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE, + G6TS_SP11_REPORT_DESCRIPTOR_LEN); + ts->report_descriptor_valid = true; + } + + if (g6ts_windows_init_parity) { + /* A software reset has a different, multi-owner Windows ordering. */ + if (path != G6TS_RECOVERY_HARDWARE) { + ret = -EOPNOTSUPP; + goto out; + } + ret = g6ts_windows_cold_attach_locked(ts); + if (ret) + goto out; + return 0; + } + + /* + * Enter the panel's streaming personality with the smallest sequence + * proven by the Phase 55 through Phase 65 cold boots and reset recoveries. + * Reports 0x60, 0x65, 0x06, 0x09, and 0x73 in the Windows ETW capture + * belong to collection/application setup. Replaying them here prevented + * Heat from starting in both the Phase 66 and Phase 67 hardware trials. + */ + ts->initialization_stage = G6TS_INIT_SET_FEATURE05; + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x05, + g6ts_mode_enable, + sizeof(g6ts_mode_enable)); + if (ret) + goto out; + ret = g6ts_expect_response(ts, SET_FEATURE_RESPONSE, 0x05, 0); + if (ret) + goto out; + + ts->mode_config_valid = false; + ts->mode_config_len = 0; + if (ts->ipts_enabled) { + dev_info(&ts->spi->dev, + "IPTS minimal init: SET_FEATURE 0x05 only; skipped feature 0x70/0x56\n"); + goto mode_ready; + } + + ts->initialization_stage = G6TS_INIT_GET_FEATURE70; + ret = g6ts_dma_feature_exchange(ts, GET_FEATURE, 0x70, NULL, 0); + if (ret) + goto out; + ret = g6ts_expect_response(ts, GET_FEATURE_RESPONSE, 0x70, 1); + if (ret) + goto out; + /* + * Capture the Linux panel's GET_FEATURE 0x70 content now, before another + * response overwrites ts->body. Phase 72 used it in the empirically stable + * combined sequence; this is not a byte-for-byte Windows feature exchange. + * Body layout: [0]=class [1..2]=len [3]=id [4..]=content. + */ + if (g6ts_mode_config_fix) { + size_t cfg_len = ts->last_content_len; + + if (cfg_len > sizeof(ts->mode_config)) + cfg_len = sizeof(ts->mode_config); + memcpy(ts->mode_config, + ts->body + HIDSPI_INPUT_BODY_HEADER_SIZE, cfg_len); + ts->mode_config_len = cfg_len; + ts->mode_config_valid = cfg_len > 0; + dev_info(&ts->spi->dev, + "phase72: GET_FEATURE 0x70 config len=%zu bytes=%*ph\n", + cfg_len, (int)cfg_len, ts->mode_config); + } + + ts->initialization_stage = G6TS_INIT_SET_FEATURE70; + if (g6ts_feature70_one_byte) { + dev_info(&ts->spi->dev, + "feature70-one-byte: SET_FEATURE 0x70 len=1 bytes=01\n"); + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x70, + g6ts_mode_enable, + sizeof(g6ts_mode_enable)); + } else if (g6ts_mode_config_fix && ts->mode_config_valid) { + /* + * Phase 72 Linux sequence: SET_FEATURE 0x70 is 0x01 followed by the + * panel's GET_FEATURE 0x70 content. Windows declares one logical content + * byte for its captured SET_FEATURE 0x70 writes. + */ + u8 mode_setup[1 + sizeof(ts->mode_config)]; + size_t setup_len = 1 + ts->mode_config_len; + + mode_setup[0] = 0x01; + memcpy(&mode_setup[1], ts->mode_config, ts->mode_config_len); + dev_info(&ts->spi->dev, + "phase72: SET_FEATURE 0x70 derived len=%zu bytes=%*ph\n", + setup_len, (int)setup_len, mode_setup); + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x70, + mode_setup, setup_len); + } else { + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x70, + g6ts_mode_enable, + sizeof(g6ts_mode_enable)); + } + if (ret) + goto out; + ret = g6ts_expect_response(ts, SET_FEATURE_RESPONSE, 0x70, 0); + if (ret) + goto out; + + /* + * Phase 72 emits a short OUTPUT_REPORT 0x09 containing 0x8e followed by the + * Linux GET_FEATURE 0x70 content. KDNET confirms that Windows uses report + * 0x09 here, but with 63-byte content rather than this short form. This is + * best-effort: the read path already skips OUTPUT_REPORT_RESPONSE, so a + * missing or late acknowledgment does not abort initialization. + */ + if (g6ts_mode_config_fix && ts->mode_config_valid) { + u8 report09[1 + sizeof(ts->mode_config)]; + size_t report_len = 1 + ts->mode_config_len; + + report09[0] = 0x8e; + memcpy(&report09[1], ts->mode_config, ts->mode_config_len); + dev_info(&ts->spi->dev, + "phase72: OUTPUT_REPORT 0x09 len=%zu bytes=%*ph\n", + report_len, (int)report_len, report09); + ret = g6ts_dma_hidspi_output(ts, OUTPUT_REPORT, 0x09, + report09, report_len); + if (ret) { + ts->fatal_transport_error = true; + goto out; + } + } + + ts->initialization_stage = G6TS_INIT_SET_FEATURE56; + ret = g6ts_dma_feature_exchange(ts, SET_FEATURE, 0x56, + g6ts_mode_handshake, + sizeof(g6ts_mode_handshake)); + if (ret) + goto out; + ret = g6ts_expect_response(ts, SET_FEATURE_RESPONSE, 0x56, 0); + if (ret) + goto out; + +mode_ready: + if (g6ts_reset_recovery_v2) { + ts->initialization_stage = G6TS_INIT_WAIT_HEAT; + /* + * Do not synchronously wait here. The panel produces Heat on touch, + * so waiting before enabling the IRQ response path deadlocks startup. + * The first frame is parsed and admitted by g6ts_handle_data_report(). + */ + ts->awaiting_ready_heat = true; + } + + ts->mode_enabled = true; + return 0; +out: + ts->mode_enabled = false; + return ret; +} + +static void g6ts_recovery_work(struct work_struct *work) +{ + struct g6ts *ts = container_of(to_delayed_work(work), struct g6ts, + recovery_work); + enum g6ts_recovery_path path; + bool retry = false; + int ret; + + /* Synchronous recovery owns every response until mode_enabled is restored. */ + g6ts_ipts_irq_disable_sync(ts); + mutex_lock(&ts->io_lock); + if (g6ts_is_quiescing(ts)) { + mutex_unlock(&ts->io_lock); + return; + } + + ts->mode_enabled = false; + g6ts_release_inputs(ts); + path = ts->recovery_path; + ret = g6ts_full_reinitialize_locked(ts, path); + if (!ret) { + ts->recovery_fail_streak = 0; + ts->recovery_successes++; + if (path == G6TS_RECOVERY_HARDWARE) + ts->rapid_reset_streak = 0; + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + if (ts->parity_feedback_required || + ts->parity_config_owner_required || + ts->parity_cfu_owner_required || + ts->parity_cfu_branch_required || + ts->parity_heat_owner_required) + dev_info(&ts->spi->dev, + "Windows parity attach paused at %s; touch input intentionally disabled\n", + g6ts_initialization_stage_name(ts->initialization_stage)); + else + dev_info(&ts->spi->dev, + "touch controller initialized path=%s recoveries=%llu resets=%llu\n", + path == G6TS_RECOVERY_SOFTWARE ? + "software" : "hardware", + ts->recovery_successes, + ts->reset_notifications); + } else { + ts->recovery_failures++; + ts->recovery_fail_streak++; + if (path == G6TS_RECOVERY_SOFTWARE && + !ts->fatal_transport_error) { + ts->software_recovery_fallbacks++; + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + retry = true; + } else { + retry = !ts->fatal_transport_error && + ts->recovery_fail_streak < G6TS_RECOVERY_LIMIT; + } + dev_warn(&ts->spi->dev, + "touch controller initialization failed path=%s stage=%s ret=%d failures=%llu%s\n", + path == G6TS_RECOVERY_SOFTWARE ? "software" : "hardware", + g6ts_initialization_stage_name(ts->initialization_stage), + ret, ts->recovery_failures, + retry ? "; retrying" : ""); + } + mutex_unlock(&ts->io_lock); + if (!ret && READ_ONCE(ts->mode_enabled)) + g6ts_ipts_irq_enable_runtime(ts); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + if (!ret && READ_ONCE(ts->mode_enabled) && !g6ts_is_quiescing(ts)) { + mod_delayed_work(system_wq, &ts->hid_rebind_work, + msecs_to_jiffies(G6TS_HID_REBIND_STABLE_MS)); + } else { + mod_delayed_work(system_wq, &ts->hid_rebind_work, 0); + } +#endif + + if (retry && !g6ts_is_quiescing(ts)) + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_RETRY_MS)); +} + +static int g6ts_probe(struct spi_device *spi) +{ + struct g6ts *ts; + unsigned long irq_flags; + int ret; + + if (!of_machine_is_compatible("microsoft,denali") || + !of_device_is_compatible(spi->dev.of_node, "microsoft,mshw0485")) + return -ENODEV; + + if (g6ts_behavior_v2 && g6ts_windows_orchestrator) + return dev_err_probe(&spi->dev, -EINVAL, + "behavior_v2 and windows_orchestrator are mutually exclusive\n"); + if (g6ts_reset_storm_breaker && !g6ts_reset_recovery_v2) + return dev_err_probe(&spi->dev, -EINVAL, + "reset_storm_breaker requires reset_recovery_v2\n"); + if (g6ts_parity_heat_input && + (!g6ts_windows_init_parity || !g6ts_parity_cfu_inventory)) + return dev_err_probe(&spi->dev, -EINVAL, + "parity_heat_input requires windows_init_parity and parity_cfu_inventory\n"); + if (g6ts_windows_read_cadence && !g6ts_windows_init_parity) + return dev_err_probe(&spi->dev, -EINVAL, + "windows_read_cadence requires windows_init_parity\n"); + + ts = devm_kzalloc(&spi->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + ts->body = devm_kmalloc(&spi->dev, G6TS_MAX_BODY, GFP_KERNEL); + if (!ts->body) + return -ENOMEM; + ts->spi = spi; + ts->heat_abi_enabled = + IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) && + of_property_read_bool(spi->dev.of_node, + "microsoft,enable-heat-frame-abi"); + ts->ipts_enabled = IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) && + of_property_read_bool(spi->dev.of_node, + "microsoft,enable-iptsd-bridge"); + /* + * Initial attach A5 carries sequence zero. Streaming starts at one and + * deliberately survives transport boundaries until evidence says otherwise. + */ + ts->a5.sequence = 1; + if (g6ts_parity_fast_host_id >= 0 && + g6ts_parity_fast_host_id <= U16_MAX) { + ts->a5.fast_host_id = g6ts_parity_fast_host_id; + ts->a5.fast_host_id_valid = true; + } + ts->interrupt_gpio = devm_gpiod_get(&spi->dev, "interrupt", GPIOD_IN); + if (IS_ERR(ts->interrupt_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(ts->interrupt_gpio), + "failed to get interrupt GPIO\n"); + ts->interrupt_irq = gpiod_to_irq(ts->interrupt_gpio); + if (ts->interrupt_irq < 0) + return dev_err_probe(&spi->dev, ts->interrupt_irq, + "failed to map interrupt GPIO\n"); + ts->power_gpio = devm_gpiod_get(&spi->dev, "power", GPIOD_OUT_LOW); + if (IS_ERR(ts->power_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(ts->power_gpio), + "failed to get power GPIO\n"); + ts->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(ts->reset_gpio), + "failed to get reset GPIO\n"); + + mutex_init(&ts->io_lock); + mutex_init(&ts->irq_state_lock); + INIT_DELAYED_WORK(&ts->recovery_work, g6ts_recovery_work); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + INIT_DELAYED_WORK(&ts->hid_rebind_work, + g6ts_ipts_hid_rebind_work); + ts->hid_target_generation = 1; +#endif + spi_set_drvdata(spi, ts); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_HEAT_FRAME) + if (ts->heat_abi_enabled) { + ret = g6ts_heat_register(ts); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to register raw Heat device\n"); + } +#endif + irq_flags = IRQF_ONESHOT | + (ts->ipts_enabled ? + IRQF_TRIGGER_LOW | IRQF_NO_AUTOEN : IRQF_TRIGGER_FALLING); + ret = devm_request_threaded_irq(&spi->dev, ts->interrupt_irq, + g6ts_interrupt_edge, + g6ts_interrupt_thread, + irq_flags, + G6TS_NAME, ts); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to request interrupt\n"); + ts->irq_enabled = !ts->ipts_enabled; + spi->max_speed_hz = G6TS_SPI_HZ; + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0 | SPI_TX_QUAD | SPI_RX_QUAD; + ret = spi_setup(spi); + if (ret) + return dev_err_probe(&spi->dev, ret, "spi_setup failed\n"); + + ts->input = devm_input_allocate_device(&spi->dev); + if (!ts->input) + return -ENOMEM; + ts->input->name = "Microsoft Surface G6 Touch"; + ts->input->id.bustype = BUS_SPI; + ts->input->dev.parent = &spi->dev; + input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, + G6TS_LOGICAL_MAX, 0, 0); + input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, + G6TS_LOGICAL_MAX, 0, 0); + touchscreen_parse_properties(ts->input, true, &ts->prop); + ret = input_mt_init_slots(ts->input, G6TS_MAX_CONTACTS, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED | + INPUT_MT_TRACK); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to initialize touch slots\n"); + ret = input_register_device(ts->input); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to register touch input\n"); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + if (ts->ipts_enabled) { + ts->pen_input = devm_input_allocate_device(&spi->dev); + if (!ts->pen_input) + return -ENOMEM; + ts->pen_input->name = "Microsoft Surface G6 Pen"; + ts->pen_input->id.bustype = BUS_SPI; + ts->pen_input->dev.parent = &spi->dev; + input_set_capability(ts->pen_input, EV_KEY, BTN_TOUCH); + input_set_capability(ts->pen_input, EV_KEY, BTN_TOOL_PEN); + input_set_capability(ts->pen_input, EV_KEY, BTN_TOOL_RUBBER); + input_set_capability(ts->pen_input, EV_KEY, BTN_STYLUS); + input_set_abs_params(ts->pen_input, ABS_X, 0, G6TS_PEN_X_MAX, 0, 0); + input_set_abs_params(ts->pen_input, ABS_Y, 0, G6TS_PEN_Y_MAX, 0, 0); + input_set_abs_params(ts->pen_input, ABS_PRESSURE, 0, + G6TS_PEN_PRESSURE_MAX, 0, 0); + input_set_abs_params(ts->pen_input, ABS_TILT_X, + -G6TS_PEN_TILT_CENTER, G6TS_PEN_TILT_CENTER, 0, 0); + input_set_abs_params(ts->pen_input, ABS_TILT_Y, + -G6TS_PEN_TILT_CENTER, G6TS_PEN_TILT_CENTER, 0, 0); + touchscreen_parse_properties(ts->pen_input, false, &ts->pen_prop); + input_abs_set_res(ts->pen_input, ABS_X, ts->pen_prop.swap_x_y ? + G6TS_PEN_Y_RESOLUTION : G6TS_PEN_X_RESOLUTION); + input_abs_set_res(ts->pen_input, ABS_Y, ts->pen_prop.swap_x_y ? + G6TS_PEN_X_RESOLUTION : G6TS_PEN_Y_RESOLUTION); + input_abs_set_res(ts->pen_input, ABS_TILT_X, + G6TS_PEN_TILT_RESOLUTION); + input_abs_set_res(ts->pen_input, ABS_TILT_Y, + G6TS_PEN_TILT_RESOLUTION); + __set_bit(INPUT_PROP_DIRECT, ts->pen_input->propbit); + ret = input_register_device(ts->pen_input); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to register pen input\n"); + } +#endif + + ret = g6ts_power_on(ts); + if (ret) + return ret; + + g6ts_clear_last_response(ts); + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_DELAY_MS)); + dev_info(&spi->dev, "touch controller initialization scheduled profile=%s\n", + g6ts_profile_name(ts)); + return 0; +} + +static void g6ts_remove(struct spi_device *spi) +{ + struct g6ts *ts = spi_get_drvdata(spi); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + struct hid_device *hid; +#endif + + WRITE_ONCE(ts->stopping, true); + WRITE_ONCE(ts->quiescing, true); + WRITE_ONCE(ts->mode_enabled, false); + if (ts->ipts_enabled) + g6ts_ipts_irq_disable_sync(ts); + else + disable_irq(ts->interrupt_irq); + cancel_delayed_work_sync(&ts->recovery_work); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + cancel_delayed_work_sync(&ts->hid_rebind_work); +#endif + mutex_lock(&ts->io_lock); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + hid = g6ts_ipts_hid_detach_locked(ts); +#endif + g6ts_release_inputs(ts); + mutex_unlock(&ts->io_lock); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + /* Drain generation work queued by an in-flight transport caller. */ + cancel_delayed_work_sync(&ts->hid_rebind_work); + if (hid) + hid_destroy_device(hid); +#endif + (void)g6ts_power_off(ts); +} + +static int g6ts_suspend(struct device *dev) +{ + struct g6ts *ts = dev_get_drvdata(dev); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + struct hid_device *hid; +#endif + int ret; + + WRITE_ONCE(ts->quiescing, true); + WRITE_ONCE(ts->mode_enabled, false); + if (ts->ipts_enabled) + g6ts_ipts_irq_disable_sync(ts); + else + disable_irq(ts->interrupt_irq); + cancel_delayed_work_sync(&ts->recovery_work); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + cancel_delayed_work_sync(&ts->hid_rebind_work); +#endif + + mutex_lock(&ts->io_lock); + WRITE_ONCE(ts->mode_enabled, false); + g6ts_heat_generation_boundary(ts, + G6TS_HEAT_RECORD_F_SUSPEND); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + hid = g6ts_ipts_hid_detach_locked(ts); +#endif + g6ts_release_inputs(ts); + mutex_unlock(&ts->io_lock); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + /* The generation boundary queued a teardown pass; the child is detached. */ + cancel_delayed_work_sync(&ts->hid_rebind_work); + if (hid) + hid_destroy_device(hid); +#endif + + mutex_lock(&ts->io_lock); + ret = g6ts_power_off(ts); + if (ret) { + WRITE_ONCE(ts->quiescing, false); + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + } + mutex_unlock(&ts->io_lock); + + if (ret) { + if (!ts->ipts_enabled) + enable_irq(ts->interrupt_irq); + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_DELAY_MS)); + } + + return ret; +} + +static int g6ts_resume(struct device *dev) +{ + struct g6ts *ts = dev_get_drvdata(dev); + int ret; + + mutex_lock(&ts->io_lock); + ts->fatal_transport_error = false; + ts->recovery_fail_streak = 0; + ts->recovery_path = G6TS_RECOVERY_HARDWARE; + ret = g6ts_power_on(ts); + if (!ret) + WRITE_ONCE(ts->quiescing, false); + mutex_unlock(&ts->io_lock); + if (ret) + return ret; + + if (!ts->ipts_enabled) + enable_irq(ts->interrupt_irq); + schedule_delayed_work(&ts->recovery_work, + msecs_to_jiffies(G6TS_RECOVERY_DELAY_MS)); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(g6ts_pm_ops, g6ts_suspend, g6ts_resume); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) +static int g6ts_ipts_hid_probe(struct hid_device *hid, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hid); + if (ret) + return ret; + + return hid_hw_start(hid, HID_CONNECT_HIDRAW); +} + +static void g6ts_ipts_hid_remove(struct hid_device *hid) +{ + hid_hw_stop(hid); +} + +static const struct hid_device_id g6ts_ipts_hid_ids[] = { + { HID_DEVICE(BUS_SPI, HID_GROUP_MSHW0485_IPTS, + G6TS_SP11_VENDOR_ID, G6TS_SP11_X1E_PRODUCT_ID) }, + { HID_DEVICE(BUS_SPI, HID_GROUP_MSHW0485_IPTS, + G6TS_SP11_VENDOR_ID, G6TS_SP11_X1P_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE(hid, g6ts_ipts_hid_ids); + +static struct hid_driver g6ts_ipts_hid_driver = { + .name = "mshw0485-ipts", + .id_table = g6ts_ipts_hid_ids, + .probe = g6ts_ipts_hid_probe, + .remove = g6ts_ipts_hid_remove, +}; +#endif + +static const struct of_device_id g6ts_of_match[] = { + { .compatible = "microsoft,mshw0485" }, + { } +}; +MODULE_DEVICE_TABLE(of, g6ts_of_match); + +static struct spi_driver g6ts_driver = { + .driver = { + .name = G6TS_NAME, + .of_match_table = g6ts_of_match, + .pm = pm_sleep_ptr(&g6ts_pm_ops), + }, + .probe = g6ts_probe, + .remove = g6ts_remove, +}; + +static int __init g6ts_init(void) +{ + int ret; + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + ret = hid_register_driver(&g6ts_ipts_hid_driver); + if (ret) + return ret; +#endif + + ret = spi_register_driver(&g6ts_driver); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + if (ret) + hid_unregister_driver(&g6ts_ipts_hid_driver); +#endif + + return ret; +} +module_init(g6ts_init); + +static void __exit g6ts_exit(void) +{ + /* Destroy every child HID device before its special driver disappears. */ + spi_unregister_driver(&g6ts_driver); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_MSHW0485_IPTSD) + hid_unregister_driver(&g6ts_ipts_hid_driver); +#endif +} +module_exit(g6ts_exit); + +MODULE_DESCRIPTION("Microsoft Surface G6 MSHW0485 touch and IPTS pen bridge"); +MODULE_AUTHOR("SP11 reverse-engineering project"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c index 26e723cfea61ef..74568940625f76 100644 --- a/drivers/spi/spi-geni-qcom.c +++ b/drivers/spi/spi-geni-qcom.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,20 @@ #define SE_SPI_PRE_POST_CMD_DLY 0x274 +#define SE_GSI_EVENT_EN 0xe18 +#define SE_GSI_IRQ_EN 0xe1c +#define SE_DMA_TX_IRQ_EN 0xc4c +#define SE_DMA_TX_IRQ_MSK 0xc50 +#define SE_DMA_RX_IRQ_EN 0xd4c +#define SE_DMA_RX_IRQ_MSK 0xd50 + +#define SP11_QSPI_M_IRQ_INIT 0x33c00046 +#define SP11_QSPI_M_IRQ_LIVE 0xffc0007f +#define SP11_QSPI_S_IRQ_INIT 0x03001e06 +#define SP11_QSPI_S_IRQ_LIVE 0x03001e36 +#define SP11_QSPI_M_IRQ_CLEAR 0xffc07fff +#define SP11_QSPI_S_IRQ_CLEAR 0x0fc07f3f + #define SE_SPI_DELAY_COUNTERS 0x278 #define SPI_INTER_WORDS_DELAY_MSK GENMASK(9, 0) #define SPI_CS_CLK_DELAY_MSK GENMASK(19, 10) @@ -59,6 +74,12 @@ #define SE_SPI_SLAVE_EN (0x2BC) #define SPI_SLAVE_EN BIT(0) +/* QSPI proto 9 host mode - Surface Pro 11 touch */ +#define GENI_SE_QSPI 9 +#define M_CMD_LANE_QUAD 2 +#define M_CMD_TX_LANES_SHFT 11 +#define M_CMD_RX_LANES_SHFT 13 + /* M_CMD OP codes for SPI */ #define SPI_TX_ONLY 1 #define SPI_RX_ONLY 2 @@ -102,11 +123,75 @@ struct spi_geni_master { int irq; bool cs_flag; bool abort_failed; + bool is_qspi; struct dma_chan *tx; struct dma_chan *rx; int cur_xfer_mode; }; +static bool spi_geni_is_sp11_qspi(struct spi_geni_master *mas) +{ + return mas->is_qspi; +} + +static int spi_geni_transfer_one(struct spi_controller *spi, + struct spi_device *slv, + struct spi_transfer *xfer); + +static void spi_geni_sp11_qspi_prepare_hw(struct spi_geni_master *mas) +{ + struct geni_se *se = &mas->se; + + if (!spi_geni_is_sp11_qspi(mas)) + return; + + writel(readl(se->base + SE_GENI_DMA_MODE_EN) | GENI_DMA_MODE_EN, + se->base + SE_GENI_DMA_MODE_EN); + writel(0, se->base + SE_GSI_IRQ_EN); + writel(0xf, se->base + SE_GSI_EVENT_EN); + writel(SP11_QSPI_M_IRQ_INIT, se->base + SE_GENI_M_IRQ_EN); + writel(SP11_QSPI_S_IRQ_INIT, se->base + SE_GENI_S_IRQ_EN); + writel(0xf, se->base + SE_DMA_TX_IRQ_MSK); + writel(0xd, se->base + SE_DMA_TX_IRQ_EN); + writel(0xfff, se->base + SE_DMA_RX_IRQ_MSK); + writel(0x1d, se->base + SE_DMA_RX_IRQ_EN); + writel(SP11_QSPI_M_IRQ_CLEAR, se->base + SE_GENI_M_IRQ_CLEAR); + writel(SP11_QSPI_S_IRQ_CLEAR, se->base + SE_GENI_S_IRQ_CLEAR); + writel(0xf, se->base + SE_DMA_TX_IRQ_CLR); + writel(0xfff, se->base + SE_DMA_RX_IRQ_CLR); + /* Complete the SE register sequence before starting GPI channels. */ + wmb(); + + dev_info_once(mas->dev, + "SP11: applied Linux-integrated QSPI SE preparation before GPI channel start\n"); +} + +static void spi_geni_sp11_qspi_arm_live(struct spi_geni_master *mas) +{ + struct geni_se *se = &mas->se; + + if (!spi_geni_is_sp11_qspi(mas)) + return; + + writel(SP11_QSPI_M_IRQ_LIVE, se->base + SE_GENI_M_IRQ_EN); + writel(SP11_QSPI_S_IRQ_LIVE, se->base + SE_GENI_S_IRQ_EN); + /* Make the live completion masks visible before submitting descriptors. */ + wmb(); +} + +static void spi_geni_sp11_qspi_restore_rest(struct spi_geni_master *mas) +{ + struct geni_se *se = &mas->se; + + if (!spi_geni_is_sp11_qspi(mas)) + return; + + writel(SP11_QSPI_M_IRQ_INIT, se->base + SE_GENI_M_IRQ_EN); + writel(SP11_QSPI_S_IRQ_INIT, se->base + SE_GENI_S_IRQ_EN); + /* Restore the resting masks before another transfer can be prepared. */ + wmb(); +} + static void spi_slv_setup(struct spi_geni_master *mas) { struct geni_se *se = &mas->se; @@ -228,6 +313,7 @@ static void handle_gpi_timeout(struct spi_controller *spi) { struct spi_geni_master *mas = spi_controller_get_devdata(spi); + spi_geni_sp11_qspi_restore_rest(mas); dmaengine_terminate_sync(mas->tx); dmaengine_terminate_sync(mas->rx); } @@ -382,6 +468,9 @@ static void spi_gsi_callback_result(void *cb, const struct dmaengine_result *result) { struct spi_controller *spi = cb; + struct spi_geni_master *mas = spi_controller_get_devdata(spi); + + spi_geni_sp11_qspi_restore_rest(mas); spi->cur_msg->status = -EIO; if (result->result != DMA_TRANS_NOERROR) { @@ -400,6 +489,303 @@ spi_gsi_callback_result(void *cb, const struct dmaengine_result *result) spi_finalize_current_transfer(spi); } +static void spi_gsi_fill_config(struct spi_geni_master *mas, + struct spi_device *spi_slv, + struct gpi_spi_config *peripheral, + u32 rx_len, u32 cmd) +{ + peripheral->cmd = cmd; + peripheral->rx_len = rx_len; + peripheral->loopback_en = !!(spi_slv->mode & SPI_LOOP); + peripheral->clock_pol_high = !!(spi_slv->mode & SPI_CPOL); + peripheral->data_pol_high = !!(spi_slv->mode & SPI_CPHA); + peripheral->cs = spi_get_chipselect(spi_slv, 0); + peripheral->pack_en = true; + peripheral->word_len = mas->cur_bits_per_word - MIN_WORD_LEN; + peripheral->qspi = mas->is_qspi; +} + +struct spi_geni_sp11_qspi_pair { + struct completion tx_done; + struct completion rx_done; + struct dmaengine_result tx_result; + struct dmaengine_result rx_result; +}; + +static void spi_geni_sp11_qspi_tx_done(void *data, + const struct dmaengine_result *result) +{ + struct spi_geni_sp11_qspi_pair *pair = data; + + pair->tx_result = *result; + complete(&pair->tx_done); +} + +static void spi_geni_sp11_qspi_rx_done(void *data, + const struct dmaengine_result *result) +{ + struct spi_geni_sp11_qspi_pair *pair = data; + + pair->rx_result = *result; + complete(&pair->rx_done); +} + +/* + * The SPI core maps messages with DMA_ATTR_SKIP_CPU_SYNC and its normal + * transfer_one_message() implementation performs these two synchronizations + * around transfer_one(). The SP11 paired-QSPI path replaces that whole + * implementation, so it must preserve the same DMA API contract itself. + * Use the core-recorded mapping devices rather than the GENI child or GPI + * control device; on SP11 the QUP wrapper is the payload DMA master. + */ +static void qspi_sync_for_device(struct spi_controller *spi, + struct spi_transfer *xfer) +{ + if (xfer->tx_sg_mapped) + dma_sync_sgtable_for_device(spi->cur_tx_dma_dev, &xfer->tx_sg, + DMA_TO_DEVICE); + if (xfer->rx_sg_mapped) + dma_sync_sgtable_for_device(spi->cur_rx_dma_dev, &xfer->rx_sg, + DMA_FROM_DEVICE); +} + +static void qspi_sync_for_cpu(struct spi_controller *spi, + struct spi_transfer *xfer) +{ + if (xfer->rx_sg_mapped) + dma_sync_sgtable_for_cpu(spi->cur_rx_dma_dev, &xfer->rx_sg, + DMA_FROM_DEVICE); + if (xfer->tx_sg_mapped) + dma_sync_sgtable_for_cpu(spi->cur_tx_dma_dev, &xfer->tx_sg, + DMA_TO_DEVICE); +} + +static int spi_geni_sp11_qspi_submit_read_pair(struct spi_controller *spi, + struct spi_message *msg, + struct spi_transfer *tx_xfer, + struct spi_transfer *rx_xfer) +{ + struct spi_geni_master *mas = spi_controller_get_devdata(spi); + unsigned long flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK; + struct dma_slave_config config = {}; + struct gpi_spi_config peripheral = {}; + struct spi_geni_sp11_qspi_pair pair = {}; + struct dma_async_tx_descriptor *tx_desc, *rx_desc; + dma_cookie_t tx_cookie, rx_cookie; + unsigned long deadline, timeout; + int ret; + + if (tx_xfer->bits_per_word != mas->cur_bits_per_word || + tx_xfer->speed_hz != mas->cur_speed_hz) { + mas->cur_bits_per_word = tx_xfer->bits_per_word; + mas->cur_speed_hz = tx_xfer->speed_hz; + } + + ret = get_spi_clk_cfg(mas->cur_speed_hz, mas, + &peripheral.clk_src, &peripheral.clk_div); + if (ret) { + msg->status = ret; + return ret; + } + + config.peripheral_config = &peripheral; + config.peripheral_size = sizeof(peripheral); + peripheral.set_config = true; + spi_gsi_fill_config(mas, msg->spi, &peripheral, rx_xfer->len, + SPI_DUPLEX); + /* Arm the broader completion masks for every live transfer. */ + spi_geni_sp11_qspi_arm_live(mas); + qspi_sync_for_device(spi, tx_xfer); + qspi_sync_for_device(spi, rx_xfer); + + ret = dmaengine_slave_config(mas->rx, &config); + if (ret) { + dev_err(mas->dev, + "SP11 QSPI RX configuration failed: %d\n", ret); + goto terminate; + } + rx_desc = dmaengine_prep_slave_sg(mas->rx, rx_xfer->rx_sg.sgl, + rx_xfer->rx_sg.nents, + DMA_DEV_TO_MEM, flags); + if (!rx_desc) { + ret = -EIO; + dev_err(mas->dev, "SP11 QSPI RX descriptor preparation failed\n"); + goto terminate; + } + + ret = dmaengine_slave_config(mas->tx, &config); + if (ret) { + dev_err(mas->dev, + "SP11 QSPI TX configuration failed: %d\n", ret); + goto terminate; + } + tx_desc = dmaengine_prep_slave_sg(mas->tx, tx_xfer->tx_sg.sgl, + tx_xfer->tx_sg.nents, + DMA_MEM_TO_DEV, flags); + if (!tx_desc) { + ret = -EIO; + dev_err(mas->dev, "SP11 QSPI TX descriptor preparation failed\n"); + goto terminate; + } + + init_completion(&pair.tx_done); + init_completion(&pair.rx_done); + tx_desc->callback_result = spi_geni_sp11_qspi_tx_done; + tx_desc->callback_param = &pair; + rx_desc->callback_result = spi_geni_sp11_qspi_rx_done; + rx_desc->callback_param = &pair; + + rx_cookie = dmaengine_submit(rx_desc); + ret = dma_submit_error(rx_cookie); + if (ret) { + dev_err(mas->dev, + "SP11 QSPI RX submission failed: %d\n", ret); + goto terminate; + } + tx_cookie = dmaengine_submit(tx_desc); + ret = dma_submit_error(tx_cookie); + if (ret) { + dev_err(mas->dev, + "SP11 QSPI TX submission failed: %d\n", ret); + goto terminate; + } + dma_async_issue_pending(mas->rx); + dma_async_issue_pending(mas->tx); + + deadline = jiffies + msecs_to_jiffies(500); + timeout = wait_for_completion_timeout(&pair.rx_done, + msecs_to_jiffies(500)); + if (!timeout) { + dev_err(&msg->spi->dev, "SPI RX transfer timed out\n"); + goto timeout; + } + + timeout = time_before(jiffies, deadline) ? deadline - jiffies : 1; + if (!wait_for_completion_timeout(&pair.tx_done, timeout)) { + dev_err(&msg->spi->dev, + "SPI TX transfer timed out after RX completion\n"); + goto timeout; + } + + if (pair.tx_result.result != DMA_TRANS_NOERROR || + pair.rx_result.result != DMA_TRANS_NOERROR || + pair.tx_result.residue || pair.rx_result.residue) { + qspi_sync_for_cpu(spi, tx_xfer); + qspi_sync_for_cpu(spi, rx_xfer); + dev_err(&msg->spi->dev, + "SP11 QSPI pair failed tx(result:%d residue:%u) rx(result:%d residue:%u)\n", + pair.tx_result.result, pair.tx_result.residue, + pair.rx_result.result, pair.rx_result.residue); + msg->status = -EIO; + spi_geni_sp11_qspi_restore_rest(mas); + spi_geni_handle_err(spi, msg); + return -EIO; + } + qspi_sync_for_cpu(spi, tx_xfer); + qspi_sync_for_cpu(spi, rx_xfer); + + dev_dbg(mas->dev, "SP11 QSPI pair complete tx:%u rx:%u data:%*ph\n", + tx_xfer->len, rx_xfer->len, + min_t(unsigned int, rx_xfer->len, 16), rx_xfer->rx_buf); + + spi_geni_sp11_qspi_restore_rest(mas); + if (msg->status == -EINPROGRESS) + msg->status = 0; + if (!msg->status) + msg->actual_length += tx_xfer->len + rx_xfer->len; + + return msg->status; + +timeout: + msg->status = -ETIMEDOUT; + spi_geni_sp11_qspi_restore_rest(mas); + spi_geni_handle_err(spi, msg); + qspi_sync_for_cpu(spi, tx_xfer); + qspi_sync_for_cpu(spi, rx_xfer); + return -ETIMEDOUT; + +terminate: + msg->status = ret; + dmaengine_terminate_sync(mas->tx); + dmaengine_terminate_sync(mas->rx); + qspi_sync_for_cpu(spi, tx_xfer); + qspi_sync_for_cpu(spi, rx_xfer); + spi_geni_sp11_qspi_restore_rest(mas); + return ret; +} + +static bool spi_geni_sp11_qspi_is_read_pair(struct spi_geni_master *mas, + struct spi_message *msg, + struct spi_transfer **tx_xfer, + struct spi_transfer **rx_xfer) +{ + struct spi_transfer *first, *second; + + if (!spi_geni_is_sp11_qspi(mas) || mas->cur_xfer_mode != GENI_GPI_DMA) + return false; + if (list_count_nodes(&msg->transfers) != 2) + return false; + + first = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list); + second = list_next_entry(first, transfer_list); + if (!first->tx_buf || first->rx_buf || !second->rx_buf) + return false; + if (!first->len || !second->len) + return false; + + *tx_xfer = first; + *rx_xfer = second; + return true; +} + +static int spi_geni_sp11_qspi_transfer_one_message(struct spi_controller *spi, + struct spi_message *msg) +{ + struct spi_geni_master *mas = spi_controller_get_devdata(spi); + struct spi_transfer *tx_xfer, *rx_xfer, *xfer; + int ret = 0; + + if (spi_geni_sp11_qspi_is_read_pair(mas, msg, &tx_xfer, &rx_xfer)) { + dev_info_once(mas->dev, + "SP11: QSPI combining HID read tx_len=%u rx_len=%u\n", + tx_xfer->len, rx_xfer->len); + ret = spi_geni_sp11_qspi_submit_read_pair(spi, msg, tx_xfer, rx_xfer); + spi_finalize_current_message(spi); + return ret; + } + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + if ((xfer->tx_buf || xfer->rx_buf) && xfer->len) { + reinit_completion(&spi->xfer_completion); + qspi_sync_for_device(spi, xfer); + ret = spi_geni_transfer_one(spi, msg->spi, xfer); + if (ret < 0) { + qspi_sync_for_cpu(spi, xfer); + break; + } + if (ret > 0) { + ret = wait_for_completion_timeout(&spi->xfer_completion, + msecs_to_jiffies(500)) ? + 0 : -ETIMEDOUT; + } + qspi_sync_for_cpu(spi, xfer); + if (ret) + break; + } + + if (msg->status != -EINPROGRESS) + break; + msg->actual_length += xfer->len; + } + + if (msg->status == -EINPROGRESS) + msg->status = ret; + if (msg->status) + spi_geni_handle_err(spi, msg); + spi_finalize_current_message(spi); + return ret; +} + static int setup_gsi_xfer(struct spi_transfer *xfer, struct spi_geni_master *mas, struct spi_device *spi_slv, struct spi_controller *spi) { @@ -412,6 +798,7 @@ static int setup_gsi_xfer(struct spi_transfer *xfer, struct spi_geni_master *mas config.peripheral_config = &peripheral; config.peripheral_size = sizeof(peripheral); peripheral.set_config = true; + peripheral.qspi = mas->is_qspi; if (xfer->bits_per_word != mas->cur_bits_per_word || xfer->speed_hz != mas->cur_speed_hz) { @@ -421,6 +808,13 @@ static int setup_gsi_xfer(struct spi_transfer *xfer, struct spi_geni_master *mas if (xfer->tx_buf && xfer->rx_buf) { peripheral.cmd = SPI_DUPLEX; + if (mas->is_qspi && !(mas->cur_bits_per_word % MIN_WORD_LEN)) { + peripheral.rx_len = ((xfer->len << 3) / mas->cur_bits_per_word); + } else if (mas->is_qspi) { + int bytes_per_word = (mas->cur_bits_per_word / BITS_PER_BYTE) + 1; + + peripheral.rx_len = (xfer->len / bytes_per_word); + } } else if (xfer->tx_buf) { peripheral.cmd = SPI_TX; peripheral.rx_len = 0; @@ -435,12 +829,10 @@ static int setup_gsi_xfer(struct spi_transfer *xfer, struct spi_geni_master *mas } } - peripheral.loopback_en = !!(spi_slv->mode & SPI_LOOP); - peripheral.clock_pol_high = !!(spi_slv->mode & SPI_CPOL); - peripheral.data_pol_high = !!(spi_slv->mode & SPI_CPHA); - peripheral.cs = spi_get_chipselect(spi_slv, 0); - peripheral.pack_en = true; - peripheral.word_len = xfer->bits_per_word - MIN_WORD_LEN; + spi_gsi_fill_config(mas, spi_slv, &peripheral, peripheral.rx_len, + peripheral.cmd); + if (mas->is_qspi) + spi_geni_sp11_qspi_arm_live(mas); ret = get_spi_clk_cfg(mas->cur_speed_hz, mas, &peripheral.clk_src, &peripheral.clk_div); @@ -629,10 +1021,16 @@ static int spi_geni_init(struct spi_geni_master *mas) dev_err(mas->dev, "spi master firmware load failed ret: %d\n", ret); goto out_pm; } + } else if (proto == GENI_SE_QSPI && mas->is_qspi) { + dev_info(mas->dev, "using protocol 9 QSPI firmware\n"); } else if (proto != GENI_SE_SPI) { dev_err(mas->dev, "Invalid proto %d\n", proto); goto out_pm; } + + if (mas->is_qspi) + spi->mode_bits |= SPI_TX_QUAD | SPI_RX_QUAD; + mas->tx_fifo_depth = geni_se_get_tx_fifo_depth(se); /* Width of Tx and Rx FIFO is same */ @@ -642,7 +1040,8 @@ static int spi_geni_init(struct spi_geni_master *mas) * Hardware programming guide suggests to configure * RX FIFO RFR level to fifo_depth-2. */ - geni_se_init(se, mas->tx_fifo_depth - 3, mas->tx_fifo_depth - 2); + geni_se_init(se, mas->tx_fifo_depth - 3, + mas->tx_fifo_depth - 2); /* Transmit an entire FIFO worth of data per IRQ */ mas->tx_wm = 1; ver = geni_se_get_qup_hw_version(se); @@ -654,6 +1053,25 @@ static int spi_geni_init(struct spi_geni_master *mas) else mas->oversampling = 1; + if (mas->is_qspi) { + /* + * TrustZone has already loaded protocol 9. Reloading the + * firmware here prevents the GPI channels from allocating. + */ + spi_geni_sp11_qspi_prepare_hw(mas); + ret = spi_geni_grab_gpi_chan(mas); + if (!ret) { + mas->cur_xfer_mode = GENI_GPI_DMA; + geni_se_select_mode(se, GENI_GPI_DMA); + spi_geni_sp11_qspi_arm_live(mas); + dev_info(mas->dev, "QSPI using GPI DMA descriptor mode\n"); + goto setup_cs; + } + + dev_err(mas->dev, "QSPI requires GPI DMA channels: %d\n", ret); + goto out_pm; + } + fifo_disable = readl(se->base + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE; switch (fifo_disable) { case 1: @@ -686,6 +1104,7 @@ static int spi_geni_init(struct spi_geni_master *mas) break; } +setup_cs: /* We never control CS manually */ if (!spi->target) { spi_tx_cfg = readl(se->base + SE_SPI_TRANS_CFG); @@ -839,12 +1258,16 @@ static int setup_se_xfer(struct spi_transfer *xfer, m_cmd |= SPI_TX_ONLY; mas->tx_rem_bytes = xfer->len; writel(len, se->base + SE_SPI_TX_TRANS_LEN); + } else if (mas->is_qspi) { + writel(0, se->base + SE_SPI_TX_TRANS_LEN); } if (xfer->rx_buf) { m_cmd |= SPI_RX_ONLY; writel(len, se->base + SE_SPI_RX_TRANS_LEN); mas->rx_rem_bytes = xfer->len; + } else if (mas->is_qspi) { + writel(0, se->base + SE_SPI_RX_TRANS_LEN); } /* @@ -874,6 +1297,19 @@ static int setup_se_xfer(struct spi_transfer *xfer, xfer->cs_change : !xfer->cs_change) m_params = FRAGMENTATION; + if (mas->is_qspi) { + if (xfer->tx_nbits == SPI_NBITS_QUAD) + m_params |= M_CMD_LANE_QUAD << M_CMD_TX_LANES_SHFT; + if (xfer->rx_nbits == SPI_NBITS_QUAD) + m_params |= M_CMD_LANE_QUAD << M_CMD_RX_LANES_SHFT; + dev_dbg(mas->dev, + "SP11 QSPI xfer mode=%d cmd=%#x params=%#x len=%u tx=%u rx=%u tx_len=%#x rx_len=%#x\n", + mas->cur_xfer_mode, m_cmd, m_params, xfer->len, + !!xfer->tx_buf, !!xfer->rx_buf, + readl(se->base + SE_SPI_TX_TRANS_LEN), + readl(se->base + SE_SPI_RX_TRANS_LEN)); + } + /* * Lock around right before we start the transfer since our * interrupt could come in at any time now. @@ -1080,6 +1516,12 @@ static int spi_geni_probe(struct platform_device *pdev) mas->se.wrapper = dev_get_drvdata(dev->parent); mas->se.base = base; mas->se.clk = clk; + if (device_property_read_bool(dev, "qcom,geni-qspi")) { + if (!of_machine_is_compatible("microsoft,denali")) + return dev_err_probe(dev, -EINVAL, + "QSPI mode is limited to Microsoft Denali\n"); + mas->is_qspi = true; + } ret = devm_pm_opp_set_clkname(&pdev->dev, "se"); if (ret) @@ -1142,6 +1584,8 @@ static int spi_geni_probe(struct platform_device *pdev) */ if (mas->cur_xfer_mode == GENI_GPI_DMA) spi->flags = SPI_CONTROLLER_MUST_TX; + if (spi_geni_is_sp11_qspi(mas) && mas->cur_xfer_mode == GENI_GPI_DMA) + spi->transfer_one_message = spi_geni_sp11_qspi_transfer_one_message; ret = devm_request_irq(dev, mas->irq, geni_spi_isr, 0, dev_name(dev), spi); if (ret) diff --git a/include/dt-bindings/dma/qcom-gpi.h b/include/dt-bindings/dma/qcom-gpi.h index ebda2a37f52aa0..373ef65f992ddd 100644 --- a/include/dt-bindings/dma/qcom-gpi.h +++ b/include/dt-bindings/dma/qcom-gpi.h @@ -7,5 +7,6 @@ #define QCOM_GPI_SPI 1 #define QCOM_GPI_UART 2 #define QCOM_GPI_I2C 3 +#define QCOM_GPI_QSPI 4 #endif /* __DT_BINDINGS_DMA_QCOM_GPI_H__ */ diff --git a/include/linux/dma/qcom-gpi-dma.h b/include/linux/dma/qcom-gpi-dma.h index 332be28427e472..67ec35f897d31d 100644 --- a/include/linux/dma/qcom-gpi-dma.h +++ b/include/linux/dma/qcom-gpi-dma.h @@ -36,6 +36,7 @@ enum spi_transfer_cmd { */ struct gpi_spi_config { u8 set_config; + u8 qspi; u8 loopback_en; u8 clock_pol_high; u8 data_pol_high; diff --git a/include/linux/hid.h b/include/linux/hid.h index 4e2422ec03db7e..bb7646c196015b 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -441,6 +441,7 @@ struct hid_item { #define HID_GROUP_STEAM 0x0103 #define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104 #define HID_GROUP_VIVALDI 0x0105 +#define HID_GROUP_MSHW0485_IPTS 0x0106 /* * HID protocol status @@ -821,6 +822,8 @@ struct hid_descriptor { .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod) #define HID_I2C_DEVICE(ven, prod) \ .bus = BUS_I2C, .vendor = (ven), .product = (prod) +#define HID_SPI_DEVICE(ven, prod) \ + .bus = BUS_SPI, .vendor = (ven), .product = (prod) #define HID_REPORT_ID(rep) \ .report_type = (rep) diff --git a/include/uapi/linux/g6ts_heat.h b/include/uapi/linux/g6ts_heat.h new file mode 100644 index 00000000000000..827f23e2ce8934 --- /dev/null +++ b/include/uapi/linux/g6ts_heat.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_G6TS_HEAT_H +#define _UAPI_LINUX_G6TS_HEAT_H + +#include +#include + +#define G6TS_HEAT_ABI_VERSION 1U +#define G6TS_HEAT_RECORD_MAGIC 0x31483647U /* "G6H1", little-endian */ +#define G6TS_HEAT_RECORD_HEADER_SIZE 32U +#define G6TS_HEAT_MAX_CONTENT_SIZE 4349U + +#define G6TS_HEAT_RECORD_F_RESET (1U << 0) +#define G6TS_HEAT_RECORD_F_SUSPEND (1U << 1) +#define G6TS_HEAT_RECORD_F_TRANSPORT_FAULT (1U << 2) + +/* + * One read returns one complete header followed by content_len exact report + * content bytes. The HID report ID is carried separately in report_id. + * All multibyte members are little-endian and this header has no padding. + */ +struct g6ts_heat_record_header { + __le32 magic; + __le16 abi_version; + __le16 header_len; + __le32 record_len; + __le32 generation; + __le64 timestamp_ns; + __le32 sequence; + __le16 content_len; + __u8 report_id; + __u8 flags; +} __attribute__((__packed__)); + +struct g6ts_heat_info { + __le16 abi_version; + __le16 struct_size; + __le16 record_header_size; + __le16 reserved0; + __le32 max_content_size; + __le32 queue_capacity; + __le64 supported_record_flags; + __le64 reserved[3]; +}; + +struct g6ts_heat_stats { + __le16 abi_version; + __le16 struct_size; + __le32 generation; + __le32 queued_records; + __le32 queue_capacity; + __le64 records_enqueued; + __le64 records_dropped; + __le64 queue_flushes; + __le64 oversize_drops; + __le64 report_0b; + __le64 report_0c; + __le64 report_0d; + __le64 report_1a; + __le64 reserved[4]; +}; + +#define G6TS_HEAT_IOC_MAGIC 'G' +#define G6TS_HEAT_IOC_GET_INFO \ + _IOR(G6TS_HEAT_IOC_MAGIC, 0x00, struct g6ts_heat_info) +#define G6TS_HEAT_IOC_GET_STATS \ + _IOR(G6TS_HEAT_IOC_MAGIC, 0x01, struct g6ts_heat_stats) + +#endif /* _UAPI_LINUX_G6TS_HEAT_H */